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 : * Copyright (c) 2012-2019, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "gdal_pdf.h"
14 : #include "pdfcreatecopy.h"
15 :
16 : #include "cpl_vsi_virtual.h"
17 : #include "cpl_conv.h"
18 : #include "cpl_error.h"
19 : #include "ogr_spatialref.h"
20 : #include "ogr_geometry.h"
21 : #include "memdataset.h"
22 : #include "vrtdataset.h"
23 :
24 : #include "pdfobject.h"
25 :
26 : #include <cmath>
27 : #include <algorithm>
28 : #include <utility>
29 : #include <vector>
30 :
31 : // #define HACK_TO_GENERATE_OCMD can be set to produce a (single layer)
32 : // non-structured vector PDF with a OCMD (Optional Content Group Membership
33 : // Dictionary) similar to test case of https://github.com/OSGeo/gdal/issues/8372
34 : // like with "ogr2ogr poly.pdf poly.shp -dsco STREAM_COMPRESS=NONE -limit 1"
35 :
36 : /************************************************************************/
37 : /* GDALPDFBaseWriter() */
38 : /************************************************************************/
39 :
40 175 : GDALPDFBaseWriter::GDALPDFBaseWriter(VSILFILE *fp) : m_fp(fp)
41 : {
42 175 : }
43 :
44 : /************************************************************************/
45 : /* ~GDALPDFBaseWriter() */
46 : /************************************************************************/
47 :
48 175 : GDALPDFBaseWriter::~GDALPDFBaseWriter()
49 : {
50 175 : Close();
51 175 : }
52 :
53 : /************************************************************************/
54 : /* ~Close() */
55 : /************************************************************************/
56 :
57 487 : void GDALPDFBaseWriter::Close()
58 : {
59 487 : if (m_fp)
60 : {
61 175 : VSIFCloseL(m_fp);
62 175 : m_fp = nullptr;
63 : }
64 487 : }
65 :
66 : /************************************************************************/
67 : /* GDALPDFUpdateWriter() */
68 : /************************************************************************/
69 :
70 23 : GDALPDFUpdateWriter::GDALPDFUpdateWriter(VSILFILE *fp) : GDALPDFBaseWriter(fp)
71 : {
72 23 : }
73 :
74 : /************************************************************************/
75 : /* ~GDALPDFUpdateWriter() */
76 : /************************************************************************/
77 :
78 23 : GDALPDFUpdateWriter::~GDALPDFUpdateWriter()
79 : {
80 23 : Close();
81 23 : }
82 :
83 : /************************************************************************/
84 : /* ~Close() */
85 : /************************************************************************/
86 :
87 46 : void GDALPDFUpdateWriter::Close()
88 : {
89 46 : if (m_fp)
90 : {
91 23 : CPLAssert(!m_bInWriteObj);
92 23 : if (m_bUpdateNeeded)
93 : {
94 23 : WriteXRefTableAndTrailer(true, m_nLastStartXRef);
95 : }
96 : }
97 46 : GDALPDFBaseWriter::Close();
98 46 : }
99 :
100 : /************************************************************************/
101 : /* StartNewDoc() */
102 : /************************************************************************/
103 :
104 152 : void GDALPDFBaseWriter::StartNewDoc()
105 : {
106 152 : VSIFPrintfL(m_fp, "%%PDF-1.6\n");
107 :
108 : // See PDF 1.7 reference, page 92. Write 4 non-ASCII bytes to indicate
109 : // that the content will be binary.
110 152 : VSIFPrintfL(m_fp, "%%%c%c%c%c\n", 0xFF, 0xFF, 0xFF, 0xFF);
111 :
112 152 : m_nPageResourceId = AllocNewObject();
113 152 : m_nCatalogId = AllocNewObject();
114 152 : }
115 :
116 : /************************************************************************/
117 : /* GDALPDFWriter() */
118 : /************************************************************************/
119 :
120 114 : GDALPDFWriter::GDALPDFWriter(VSILFILE *fpIn) : GDALPDFBaseWriter(fpIn)
121 : {
122 114 : StartNewDoc();
123 114 : }
124 :
125 : /************************************************************************/
126 : /* ~GDALPDFWriter() */
127 : /************************************************************************/
128 :
129 114 : GDALPDFWriter::~GDALPDFWriter()
130 : {
131 114 : Close();
132 114 : }
133 :
134 : /************************************************************************/
135 : /* ParseIndirectRef() */
136 : /************************************************************************/
137 :
138 27 : static int ParseIndirectRef(const char *pszStr, GDALPDFObjectNum &nNum,
139 : int &nGen)
140 : {
141 27 : while (*pszStr == ' ')
142 0 : pszStr++;
143 :
144 27 : nNum = atoi(pszStr);
145 58 : while (*pszStr >= '0' && *pszStr <= '9')
146 31 : pszStr++;
147 27 : if (*pszStr != ' ')
148 0 : return FALSE;
149 :
150 54 : while (*pszStr == ' ')
151 27 : pszStr++;
152 :
153 27 : nGen = atoi(pszStr);
154 54 : while (*pszStr >= '0' && *pszStr <= '9')
155 27 : pszStr++;
156 27 : if (*pszStr != ' ')
157 0 : return FALSE;
158 :
159 54 : while (*pszStr == ' ')
160 27 : pszStr++;
161 :
162 27 : return *pszStr == 'R';
163 : }
164 :
165 : /************************************************************************/
166 : /* ParseTrailerAndXRef() */
167 : /************************************************************************/
168 :
169 23 : int GDALPDFUpdateWriter::ParseTrailerAndXRef()
170 : {
171 23 : VSIFSeekL(m_fp, 0, SEEK_END);
172 : char szBuf[1024 + 1];
173 23 : vsi_l_offset nOffset = VSIFTellL(m_fp);
174 :
175 23 : if (nOffset > 128)
176 23 : nOffset -= 128;
177 : else
178 0 : nOffset = 0;
179 :
180 : /* Find startxref section */
181 23 : VSIFSeekL(m_fp, nOffset, SEEK_SET);
182 23 : int nRead = static_cast<int>(VSIFReadL(szBuf, 1, 128, m_fp));
183 23 : szBuf[nRead] = 0;
184 23 : if (nRead < 9)
185 0 : return FALSE;
186 :
187 23 : const char *pszStartXRef = nullptr;
188 : int i;
189 297 : for (i = nRead - 9; i >= 0; i--)
190 : {
191 297 : if (STARTS_WITH(szBuf + i, "startxref"))
192 : {
193 23 : pszStartXRef = szBuf + i;
194 23 : break;
195 : }
196 : }
197 23 : if (pszStartXRef == nullptr)
198 : {
199 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find startxref");
200 0 : return FALSE;
201 : }
202 23 : pszStartXRef += 9;
203 46 : while (*pszStartXRef == '\r' || *pszStartXRef == '\n')
204 23 : pszStartXRef++;
205 23 : if (*pszStartXRef == '\0')
206 : {
207 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find startxref");
208 0 : return FALSE;
209 : }
210 :
211 23 : m_nLastStartXRef = CPLScanUIntBig(pszStartXRef, 16);
212 :
213 : /* Skip to beginning of xref section */
214 23 : VSIFSeekL(m_fp, m_nLastStartXRef, SEEK_SET);
215 :
216 : /* And skip to trailer */
217 23 : const char *pszLine = nullptr;
218 258 : while ((pszLine = CPLReadLineL(m_fp)) != nullptr)
219 : {
220 258 : if (STARTS_WITH(pszLine, "trailer"))
221 23 : break;
222 : }
223 :
224 23 : if (pszLine == nullptr)
225 : {
226 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find trailer");
227 0 : return FALSE;
228 : }
229 :
230 : /* Read trailer content */
231 23 : nRead = static_cast<int>(VSIFReadL(szBuf, 1, 1024, m_fp));
232 23 : szBuf[nRead] = 0;
233 :
234 : /* Find XRef size */
235 23 : const char *pszSize = strstr(szBuf, "/Size");
236 23 : if (pszSize == nullptr)
237 : {
238 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find trailer /Size");
239 0 : return FALSE;
240 : }
241 23 : pszSize += 5;
242 46 : while (*pszSize == ' ')
243 23 : pszSize++;
244 23 : m_nLastXRefSize = atoi(pszSize);
245 :
246 : /* Find Root object */
247 23 : const char *pszRoot = strstr(szBuf, "/Root");
248 23 : if (pszRoot == nullptr)
249 : {
250 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find trailer /Root");
251 0 : return FALSE;
252 : }
253 23 : pszRoot += 5;
254 46 : while (*pszRoot == ' ')
255 23 : pszRoot++;
256 :
257 23 : if (!ParseIndirectRef(pszRoot, m_nCatalogId, m_nCatalogGen))
258 : {
259 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot parse trailer /Root");
260 0 : return FALSE;
261 : }
262 :
263 : /* Find Info object */
264 23 : const char *pszInfo = strstr(szBuf, "/Info");
265 23 : if (pszInfo != nullptr)
266 : {
267 4 : pszInfo += 5;
268 8 : while (*pszInfo == ' ')
269 4 : pszInfo++;
270 :
271 4 : if (!ParseIndirectRef(pszInfo, m_nInfoId, m_nInfoGen))
272 : {
273 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot parse trailer /Info");
274 0 : m_nInfoId = 0;
275 0 : m_nInfoGen = 0;
276 : }
277 : }
278 :
279 23 : VSIFSeekL(m_fp, 0, SEEK_END);
280 :
281 23 : return TRUE;
282 : }
283 :
284 : /************************************************************************/
285 : /* Close() */
286 : /************************************************************************/
287 :
288 228 : void GDALPDFWriter::Close()
289 : {
290 228 : if (m_fp)
291 : {
292 114 : CPLAssert(!m_bInWriteObj);
293 114 : if (m_nPageResourceId.toBool())
294 : {
295 114 : WritePages();
296 114 : WriteXRefTableAndTrailer(false, 0);
297 : }
298 : }
299 228 : GDALPDFBaseWriter::Close();
300 228 : }
301 :
302 : /************************************************************************/
303 : /* UpdateProj() */
304 : /************************************************************************/
305 :
306 11 : void GDALPDFUpdateWriter::UpdateProj(GDALDataset *poSrcDS, double dfDPI,
307 : GDALPDFDictionaryRW *poPageDict,
308 : const GDALPDFObjectNum &nPageId,
309 : int nPageGen)
310 : {
311 11 : m_bUpdateNeeded = true;
312 11 : if (static_cast<int>(m_asXRefEntries.size()) < m_nLastXRefSize - 1)
313 11 : m_asXRefEntries.resize(m_nLastXRefSize - 1);
314 :
315 11 : GDALPDFObjectNum nViewportId;
316 11 : GDALPDFObjectNum nLGIDictId;
317 :
318 11 : CPLAssert(nPageId.toBool());
319 11 : CPLAssert(poPageDict != nullptr);
320 :
321 11 : PDFMargins sMargins;
322 :
323 : const char *pszGEO_ENCODING =
324 11 : CPLGetConfigOption("GDAL_PDF_GEO_ENCODING", "ISO32000");
325 11 : if (EQUAL(pszGEO_ENCODING, "ISO32000") || EQUAL(pszGEO_ENCODING, "BOTH"))
326 : nViewportId = WriteSRS_ISO32000(poSrcDS, dfDPI * USER_UNIT_IN_INCH,
327 11 : nullptr, &sMargins, TRUE);
328 :
329 : #ifdef invalidate_xref_entry
330 : GDALPDFObject *poVP = poPageDict->Get("VP");
331 : if (poVP)
332 : {
333 : if (poVP->GetType() == PDFObjectType_Array &&
334 : poVP->GetArray()->GetLength() == 1)
335 : poVP = poVP->GetArray()->Get(0);
336 :
337 : int nVPId = poVP->GetRefNum();
338 : if (nVPId)
339 : {
340 : m_asXRefEntries[nVPId - 1].bFree = TRUE;
341 : m_asXRefEntries[nVPId - 1].nGen++;
342 : }
343 : }
344 : #endif
345 :
346 11 : poPageDict->Remove("VP");
347 11 : poPageDict->Remove("LGIDict");
348 :
349 11 : if (nViewportId.toBool())
350 : {
351 9 : poPageDict->Add("VP", &((new GDALPDFArrayRW())->Add(nViewportId, 0)));
352 : }
353 :
354 11 : if (nLGIDictId.toBool())
355 : {
356 0 : poPageDict->Add("LGIDict", nLGIDictId, 0);
357 : }
358 :
359 11 : StartObj(nPageId, nPageGen);
360 11 : VSIFPrintfL(m_fp, "%s\n", poPageDict->Serialize().c_str());
361 11 : EndObj();
362 11 : }
363 :
364 : /************************************************************************/
365 : /* UpdateInfo() */
366 : /************************************************************************/
367 :
368 6 : void GDALPDFUpdateWriter::UpdateInfo(GDALDataset *poSrcDS)
369 : {
370 6 : m_bUpdateNeeded = true;
371 6 : if (static_cast<int>(m_asXRefEntries.size()) < m_nLastXRefSize - 1)
372 6 : m_asXRefEntries.resize(m_nLastXRefSize - 1);
373 :
374 6 : auto nNewInfoId = SetInfo(poSrcDS, nullptr);
375 : /* Write empty info, because podofo driver will find the dangling info
376 : * instead */
377 6 : if (!nNewInfoId.toBool() && m_nInfoId.toBool())
378 : {
379 : #ifdef invalidate_xref_entry
380 : m_asXRefEntries[m_nInfoId.toInt() - 1].bFree = TRUE;
381 : m_asXRefEntries[m_nInfoId.toInt() - 1].nGen++;
382 : #else
383 2 : StartObj(m_nInfoId, m_nInfoGen);
384 2 : VSIFPrintfL(m_fp, "<< >>\n");
385 2 : EndObj();
386 : #endif
387 : }
388 6 : }
389 :
390 : /************************************************************************/
391 : /* UpdateXMP() */
392 : /************************************************************************/
393 :
394 6 : void GDALPDFUpdateWriter::UpdateXMP(GDALDataset *poSrcDS,
395 : GDALPDFDictionaryRW *poCatalogDict)
396 : {
397 6 : m_bUpdateNeeded = true;
398 6 : if (static_cast<int>(m_asXRefEntries.size()) < m_nLastXRefSize - 1)
399 6 : m_asXRefEntries.resize(m_nLastXRefSize - 1);
400 :
401 6 : CPLAssert(m_nCatalogId.toBool());
402 6 : CPLAssert(poCatalogDict != nullptr);
403 :
404 6 : GDALPDFObject *poMetadata = poCatalogDict->Get("Metadata");
405 6 : if (poMetadata)
406 : {
407 4 : m_nXMPId = poMetadata->GetRefNum();
408 4 : m_nXMPGen = poMetadata->GetRefGen();
409 : }
410 :
411 6 : poCatalogDict->Remove("Metadata");
412 6 : auto nNewXMPId = SetXMP(poSrcDS, nullptr);
413 :
414 : /* Write empty metadata, because podofo driver will find the dangling info
415 : * instead */
416 6 : if (!nNewXMPId.toBool() && m_nXMPId.toBool())
417 : {
418 2 : StartObj(m_nXMPId, m_nXMPGen);
419 2 : VSIFPrintfL(m_fp, "<< >>\n");
420 2 : EndObj();
421 : }
422 :
423 6 : if (m_nXMPId.toBool())
424 6 : poCatalogDict->Add("Metadata", m_nXMPId, 0);
425 :
426 6 : StartObj(m_nCatalogId, m_nCatalogGen);
427 6 : VSIFPrintfL(m_fp, "%s\n", poCatalogDict->Serialize().c_str());
428 6 : EndObj();
429 6 : }
430 :
431 : /************************************************************************/
432 : /* AllocNewObject() */
433 : /************************************************************************/
434 :
435 2430 : GDALPDFObjectNum GDALPDFBaseWriter::AllocNewObject()
436 : {
437 2430 : m_asXRefEntries.push_back(GDALXRefEntry());
438 2430 : return GDALPDFObjectNum(static_cast<int>(m_asXRefEntries.size()));
439 : }
440 :
441 : /************************************************************************/
442 : /* WriteXRefTableAndTrailer() */
443 : /************************************************************************/
444 :
445 175 : void GDALPDFBaseWriter::WriteXRefTableAndTrailer(bool bUpdate,
446 : vsi_l_offset nLastStartXRef)
447 : {
448 175 : vsi_l_offset nOffsetXREF = VSIFTellL(m_fp);
449 175 : VSIFPrintfL(m_fp, "xref\n");
450 :
451 : char buffer[16];
452 175 : if (bUpdate)
453 : {
454 23 : VSIFPrintfL(m_fp, "0 1\n");
455 23 : VSIFPrintfL(m_fp, "0000000000 65535 f \n");
456 326 : for (size_t i = 0; i < m_asXRefEntries.size();)
457 : {
458 303 : if (m_asXRefEntries[i].nOffset != 0 || m_asXRefEntries[i].bFree)
459 : {
460 : /* Find number of consecutive objects */
461 38 : size_t nCount = 1;
462 91 : while (i + nCount < m_asXRefEntries.size() &&
463 35 : (m_asXRefEntries[i + nCount].nOffset != 0 ||
464 17 : m_asXRefEntries[i + nCount].bFree))
465 18 : nCount++;
466 :
467 38 : VSIFPrintfL(m_fp, "%d %d\n", static_cast<int>(i) + 1,
468 : static_cast<int>(nCount));
469 38 : size_t iEnd = i + nCount;
470 94 : for (; i < iEnd; i++)
471 : {
472 56 : snprintf(buffer, sizeof(buffer),
473 : "%010" CPL_FRMT_GB_WITHOUT_PREFIX "u",
474 56 : m_asXRefEntries[i].nOffset);
475 112 : VSIFPrintfL(m_fp, "%s %05d %c \n", buffer,
476 56 : m_asXRefEntries[i].nGen,
477 56 : m_asXRefEntries[i].bFree ? 'f' : 'n');
478 : }
479 : }
480 : else
481 : {
482 265 : i++;
483 : }
484 : }
485 : }
486 : else
487 : {
488 152 : VSIFPrintfL(m_fp, "%d %d\n", 0,
489 152 : static_cast<int>(m_asXRefEntries.size()) + 1);
490 152 : VSIFPrintfL(m_fp, "0000000000 65535 f \n");
491 2551 : for (size_t i = 0; i < m_asXRefEntries.size(); i++)
492 : {
493 2399 : snprintf(buffer, sizeof(buffer),
494 : "%010" CPL_FRMT_GB_WITHOUT_PREFIX "u",
495 2399 : m_asXRefEntries[i].nOffset);
496 2399 : VSIFPrintfL(m_fp, "%s %05d n \n", buffer, m_asXRefEntries[i].nGen);
497 : }
498 : }
499 :
500 175 : VSIFPrintfL(m_fp, "trailer\n");
501 350 : GDALPDFDictionaryRW oDict;
502 175 : oDict.Add("Size", static_cast<int>(m_asXRefEntries.size()) + 1)
503 175 : .Add("Root", m_nCatalogId, m_nCatalogGen);
504 175 : if (m_nInfoId.toBool())
505 11 : oDict.Add("Info", m_nInfoId, m_nInfoGen);
506 175 : if (nLastStartXRef)
507 23 : oDict.Add("Prev", static_cast<double>(nLastStartXRef));
508 175 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
509 :
510 175 : VSIFPrintfL(m_fp,
511 : "startxref\n" CPL_FRMT_GUIB "\n"
512 : "%%%%EOF\n",
513 : nOffsetXREF);
514 175 : }
515 :
516 : /************************************************************************/
517 : /* StartObj() */
518 : /************************************************************************/
519 :
520 2432 : void GDALPDFBaseWriter::StartObj(const GDALPDFObjectNum &nObjectId, int nGen)
521 : {
522 2432 : CPLAssert(!m_bInWriteObj);
523 2432 : CPLAssert(nObjectId.toInt() - 1 < static_cast<int>(m_asXRefEntries.size()));
524 2432 : CPLAssert(m_asXRefEntries[nObjectId.toInt() - 1].nOffset == 0);
525 2432 : m_asXRefEntries[nObjectId.toInt() - 1].nOffset = VSIFTellL(m_fp);
526 2432 : m_asXRefEntries[nObjectId.toInt() - 1].nGen = nGen;
527 2432 : VSIFPrintfL(m_fp, "%d %d obj\n", nObjectId.toInt(), nGen);
528 2432 : m_bInWriteObj = true;
529 2432 : }
530 :
531 : /************************************************************************/
532 : /* EndObj() */
533 : /************************************************************************/
534 :
535 2432 : void GDALPDFBaseWriter::EndObj()
536 : {
537 2432 : CPLAssert(m_bInWriteObj);
538 2432 : CPLAssert(!m_fpBack);
539 2432 : VSIFPrintfL(m_fp, "endobj\n");
540 2432 : m_bInWriteObj = false;
541 2432 : }
542 :
543 : /************************************************************************/
544 : /* StartObjWithStream() */
545 : /************************************************************************/
546 :
547 562 : void GDALPDFBaseWriter::StartObjWithStream(const GDALPDFObjectNum &nObjectId,
548 : GDALPDFDictionaryRW &oDict,
549 : bool bDeflate)
550 : {
551 562 : CPLAssert(!m_nContentLengthId.toBool());
552 562 : CPLAssert(!m_fpGZip);
553 562 : CPLAssert(!m_fpBack);
554 562 : CPLAssert(m_nStreamStart == 0);
555 :
556 562 : m_nContentLengthId = AllocNewObject();
557 :
558 562 : StartObj(nObjectId);
559 : {
560 562 : oDict.Add("Length", m_nContentLengthId, 0);
561 562 : if (bDeflate)
562 : {
563 425 : oDict.Add("Filter", GDALPDFObjectRW::CreateName("FlateDecode"));
564 : }
565 562 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
566 : }
567 :
568 : /* -------------------------------------------------------------- */
569 : /* Write content stream */
570 : /* -------------------------------------------------------------- */
571 562 : VSIFPrintfL(m_fp, "stream\n");
572 562 : m_nStreamStart = VSIFTellL(m_fp);
573 :
574 562 : m_fpGZip = nullptr;
575 562 : m_fpBack = m_fp;
576 562 : if (bDeflate)
577 : {
578 425 : m_fpGZip = VSICreateGZipWritable(m_fp, TRUE, FALSE);
579 425 : m_fp = m_fpGZip;
580 : }
581 562 : }
582 :
583 : /************************************************************************/
584 : /* EndObjWithStream() */
585 : /************************************************************************/
586 :
587 562 : void GDALPDFBaseWriter::EndObjWithStream()
588 : {
589 562 : if (m_fpGZip)
590 425 : VSIFCloseL(m_fpGZip);
591 562 : m_fp = m_fpBack;
592 562 : m_fpBack = nullptr;
593 :
594 562 : vsi_l_offset nStreamEnd = VSIFTellL(m_fp);
595 562 : if (m_fpGZip)
596 425 : VSIFPrintfL(m_fp, "\n");
597 562 : m_fpGZip = nullptr;
598 562 : VSIFPrintfL(m_fp, "endstream\n");
599 562 : EndObj();
600 :
601 562 : StartObj(m_nContentLengthId);
602 562 : VSIFPrintfL(m_fp, " %ld\n",
603 562 : static_cast<long>(nStreamEnd - m_nStreamStart));
604 562 : EndObj();
605 :
606 562 : m_nContentLengthId = GDALPDFObjectNum();
607 562 : m_nStreamStart = 0;
608 562 : }
609 :
610 : /************************************************************************/
611 : /* GDALPDFFind4Corners() */
612 : /************************************************************************/
613 :
614 18 : static void GDALPDFFind4Corners(const GDAL_GCP *pasGCPList, int &iUL, int &iUR,
615 : int &iLR, int &iLL)
616 : {
617 18 : double dfMeanX = 0.0;
618 18 : double dfMeanY = 0.0;
619 : int i;
620 :
621 18 : iUL = 0;
622 18 : iUR = 0;
623 18 : iLR = 0;
624 18 : iLL = 0;
625 :
626 90 : for (i = 0; i < 4; i++)
627 : {
628 72 : dfMeanX += pasGCPList[i].dfGCPPixel;
629 72 : dfMeanY += pasGCPList[i].dfGCPLine;
630 : }
631 18 : dfMeanX /= 4;
632 18 : dfMeanY /= 4;
633 :
634 90 : for (i = 0; i < 4; i++)
635 : {
636 72 : if (pasGCPList[i].dfGCPPixel < dfMeanX &&
637 36 : pasGCPList[i].dfGCPLine < dfMeanY)
638 18 : iUL = i;
639 :
640 54 : else if (pasGCPList[i].dfGCPPixel > dfMeanX &&
641 36 : pasGCPList[i].dfGCPLine < dfMeanY)
642 18 : iUR = i;
643 :
644 36 : else if (pasGCPList[i].dfGCPPixel > dfMeanX &&
645 18 : pasGCPList[i].dfGCPLine > dfMeanY)
646 18 : iLR = i;
647 :
648 18 : else if (pasGCPList[i].dfGCPPixel < dfMeanX &&
649 18 : pasGCPList[i].dfGCPLine > dfMeanY)
650 18 : iLL = i;
651 : }
652 18 : }
653 :
654 : /************************************************************************/
655 : /* WriteSRS_ISO32000() */
656 : /************************************************************************/
657 :
658 121 : GDALPDFObjectNum GDALPDFBaseWriter::WriteSRS_ISO32000(GDALDataset *poSrcDS,
659 : double dfUserUnit,
660 : const char *pszNEATLINE,
661 : PDFMargins *psMargins,
662 : int bWriteViewport)
663 : {
664 121 : int nWidth = poSrcDS->GetRasterXSize();
665 121 : int nHeight = poSrcDS->GetRasterYSize();
666 121 : const char *pszWKT = poSrcDS->GetProjectionRef();
667 : double adfGeoTransform[6];
668 :
669 121 : int bHasGT = (poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None);
670 : const GDAL_GCP *pasGCPList =
671 121 : (poSrcDS->GetGCPCount() == 4) ? poSrcDS->GetGCPs() : nullptr;
672 121 : if (pasGCPList != nullptr)
673 2 : pszWKT = poSrcDS->GetGCPProjection();
674 :
675 121 : if (!bHasGT && pasGCPList == nullptr)
676 20 : return GDALPDFObjectNum();
677 :
678 101 : if (pszWKT == nullptr || EQUAL(pszWKT, ""))
679 17 : return GDALPDFObjectNum();
680 :
681 : double adfGPTS[8];
682 :
683 84 : double dfULPixel = 0;
684 84 : double dfULLine = 0;
685 84 : double dfLRPixel = nWidth;
686 84 : double dfLRLine = nHeight;
687 :
688 168 : std::vector<gdal::GCP> asNeatLineGCPs(4);
689 84 : if (pszNEATLINE == nullptr)
690 82 : pszNEATLINE = poSrcDS->GetMetadataItem("NEATLINE");
691 84 : if (bHasGT && pszNEATLINE != nullptr && pszNEATLINE[0] != '\0')
692 : {
693 16 : auto [poGeom, _] = OGRGeometryFactory::createFromWkt(pszNEATLINE);
694 16 : if (poGeom != nullptr &&
695 8 : wkbFlatten(poGeom->getGeometryType()) == wkbPolygon)
696 : {
697 8 : OGRLineString *poLS = poGeom->toPolygon()->getExteriorRing();
698 : double adfGeoTransformInv[6];
699 16 : if (poLS != nullptr && poLS->getNumPoints() == 5 &&
700 8 : GDALInvGeoTransform(adfGeoTransform, adfGeoTransformInv))
701 : {
702 40 : for (int i = 0; i < 4; i++)
703 : {
704 32 : const double X = poLS->getX(i);
705 32 : const double Y = poLS->getY(i);
706 32 : asNeatLineGCPs[i].X() = X;
707 32 : asNeatLineGCPs[i].Y() = Y;
708 32 : const double x = adfGeoTransformInv[0] +
709 32 : X * adfGeoTransformInv[1] +
710 32 : Y * adfGeoTransformInv[2];
711 32 : const double y = adfGeoTransformInv[3] +
712 32 : X * adfGeoTransformInv[4] +
713 32 : Y * adfGeoTransformInv[5];
714 32 : asNeatLineGCPs[i].Pixel() = x;
715 32 : asNeatLineGCPs[i].Line() = y;
716 : }
717 :
718 8 : int iUL = 0;
719 8 : int iUR = 0;
720 8 : int iLR = 0;
721 8 : int iLL = 0;
722 8 : GDALPDFFind4Corners(gdal::GCP::c_ptr(asNeatLineGCPs), iUL, iUR,
723 : iLR, iLL);
724 :
725 8 : if (fabs(asNeatLineGCPs[iUL].Pixel() -
726 8 : asNeatLineGCPs[iLL].Pixel()) > .5 ||
727 8 : fabs(asNeatLineGCPs[iUR].Pixel() -
728 8 : asNeatLineGCPs[iLR].Pixel()) > .5 ||
729 8 : fabs(asNeatLineGCPs[iUL].Line() -
730 24 : asNeatLineGCPs[iUR].Line()) > .5 ||
731 8 : fabs(asNeatLineGCPs[iLL].Line() -
732 8 : asNeatLineGCPs[iLR].Line()) > .5)
733 : {
734 0 : CPLError(CE_Warning, CPLE_NotSupported,
735 : "Neatline coordinates should form a rectangle in "
736 : "pixel space. Ignoring it");
737 0 : for (int i = 0; i < 4; i++)
738 : {
739 0 : CPLDebug("PDF", "pixel[%d] = %.1f, line[%d] = %.1f", i,
740 0 : asNeatLineGCPs[i].Pixel(), i,
741 0 : asNeatLineGCPs[i].Line());
742 : }
743 : }
744 : else
745 : {
746 8 : pasGCPList = gdal::GCP::c_ptr(asNeatLineGCPs);
747 : }
748 : }
749 : }
750 : }
751 :
752 84 : if (pasGCPList)
753 : {
754 10 : int iUL = 0;
755 10 : int iUR = 0;
756 10 : int iLR = 0;
757 10 : int iLL = 0;
758 10 : GDALPDFFind4Corners(pasGCPList, iUL, iUR, iLR, iLL);
759 :
760 10 : if (fabs(pasGCPList[iUL].dfGCPPixel - pasGCPList[iLL].dfGCPPixel) >
761 10 : .5 ||
762 10 : fabs(pasGCPList[iUR].dfGCPPixel - pasGCPList[iLR].dfGCPPixel) >
763 10 : .5 ||
764 10 : fabs(pasGCPList[iUL].dfGCPLine - pasGCPList[iUR].dfGCPLine) > .5 ||
765 10 : fabs(pasGCPList[iLL].dfGCPLine - pasGCPList[iLR].dfGCPLine) > .5)
766 : {
767 0 : CPLError(CE_Failure, CPLE_NotSupported,
768 : "GCPs should form a rectangle in pixel space");
769 0 : return GDALPDFObjectNum();
770 : }
771 :
772 10 : dfULPixel = pasGCPList[iUL].dfGCPPixel;
773 10 : dfULLine = pasGCPList[iUL].dfGCPLine;
774 10 : dfLRPixel = pasGCPList[iLR].dfGCPPixel;
775 10 : dfLRLine = pasGCPList[iLR].dfGCPLine;
776 :
777 : /* Upper-left */
778 10 : adfGPTS[0] = pasGCPList[iUL].dfGCPX;
779 10 : adfGPTS[1] = pasGCPList[iUL].dfGCPY;
780 :
781 : /* Lower-left */
782 10 : adfGPTS[2] = pasGCPList[iLL].dfGCPX;
783 10 : adfGPTS[3] = pasGCPList[iLL].dfGCPY;
784 :
785 : /* Lower-right */
786 10 : adfGPTS[4] = pasGCPList[iLR].dfGCPX;
787 10 : adfGPTS[5] = pasGCPList[iLR].dfGCPY;
788 :
789 : /* Upper-right */
790 10 : adfGPTS[6] = pasGCPList[iUR].dfGCPX;
791 10 : adfGPTS[7] = pasGCPList[iUR].dfGCPY;
792 : }
793 : else
794 : {
795 : /* Upper-left */
796 74 : adfGPTS[0] = APPLY_GT_X(adfGeoTransform, 0, 0);
797 74 : adfGPTS[1] = APPLY_GT_Y(adfGeoTransform, 0, 0);
798 :
799 : /* Lower-left */
800 74 : adfGPTS[2] = APPLY_GT_X(adfGeoTransform, 0, nHeight);
801 74 : adfGPTS[3] = APPLY_GT_Y(adfGeoTransform, 0, nHeight);
802 :
803 : /* Lower-right */
804 74 : adfGPTS[4] = APPLY_GT_X(adfGeoTransform, nWidth, nHeight);
805 74 : adfGPTS[5] = APPLY_GT_Y(adfGeoTransform, nWidth, nHeight);
806 :
807 : /* Upper-right */
808 74 : adfGPTS[6] = APPLY_GT_X(adfGeoTransform, nWidth, 0);
809 74 : adfGPTS[7] = APPLY_GT_Y(adfGeoTransform, nWidth, 0);
810 : }
811 :
812 84 : OGRSpatialReferenceH hSRS = OSRNewSpatialReference(pszWKT);
813 84 : if (hSRS == nullptr)
814 0 : return GDALPDFObjectNum();
815 84 : OSRSetAxisMappingStrategy(hSRS, OAMS_TRADITIONAL_GIS_ORDER);
816 84 : OGRSpatialReferenceH hSRSGeog = OSRCloneGeogCS(hSRS);
817 84 : if (hSRSGeog == nullptr)
818 : {
819 0 : OSRDestroySpatialReference(hSRS);
820 0 : return GDALPDFObjectNum();
821 : }
822 84 : OSRSetAxisMappingStrategy(hSRSGeog, OAMS_TRADITIONAL_GIS_ORDER);
823 : OGRCoordinateTransformationH hCT =
824 84 : OCTNewCoordinateTransformation(hSRS, hSRSGeog);
825 84 : if (hCT == nullptr)
826 : {
827 0 : OSRDestroySpatialReference(hSRS);
828 0 : OSRDestroySpatialReference(hSRSGeog);
829 0 : return GDALPDFObjectNum();
830 : }
831 :
832 84 : int bSuccess = TRUE;
833 :
834 84 : bSuccess &= (OCTTransform(hCT, 1, adfGPTS + 0, adfGPTS + 1, nullptr) == 1);
835 84 : bSuccess &= (OCTTransform(hCT, 1, adfGPTS + 2, adfGPTS + 3, nullptr) == 1);
836 84 : bSuccess &= (OCTTransform(hCT, 1, adfGPTS + 4, adfGPTS + 5, nullptr) == 1);
837 84 : bSuccess &= (OCTTransform(hCT, 1, adfGPTS + 6, adfGPTS + 7, nullptr) == 1);
838 :
839 84 : if (!bSuccess)
840 : {
841 0 : OSRDestroySpatialReference(hSRS);
842 0 : OSRDestroySpatialReference(hSRSGeog);
843 0 : OCTDestroyCoordinateTransformation(hCT);
844 0 : return GDALPDFObjectNum();
845 : }
846 :
847 84 : const char *pszAuthorityCode = OSRGetAuthorityCode(hSRS, nullptr);
848 84 : const char *pszAuthorityName = OSRGetAuthorityName(hSRS, nullptr);
849 84 : int nEPSGCode = 0;
850 153 : if (pszAuthorityName != nullptr && pszAuthorityCode != nullptr &&
851 69 : (EQUAL(pszAuthorityName, "EPSG") ||
852 0 : (EQUAL(pszAuthorityName, "ESRI") &&
853 0 : CPLTestBool(
854 : CPLGetConfigOption("GDAL_PDF_WRITE_ESRI_CODE_AS_EPSG", "NO")))))
855 : {
856 69 : nEPSGCode = atoi(pszAuthorityCode);
857 : }
858 :
859 84 : int bIsGeographic = OSRIsGeographic(hSRS);
860 :
861 84 : OSRMorphToESRI(hSRS);
862 84 : char *pszESRIWKT = nullptr;
863 84 : OSRExportToWkt(hSRS, &pszESRIWKT);
864 :
865 84 : OSRDestroySpatialReference(hSRS);
866 84 : OSRDestroySpatialReference(hSRSGeog);
867 84 : OCTDestroyCoordinateTransformation(hCT);
868 84 : hSRS = nullptr;
869 84 : hSRSGeog = nullptr;
870 84 : hCT = nullptr;
871 :
872 84 : if (pszESRIWKT == nullptr)
873 0 : return GDALPDFObjectNum();
874 :
875 84 : auto nViewportId = (bWriteViewport) ? AllocNewObject() : GDALPDFObjectNum();
876 84 : auto nMeasureId = AllocNewObject();
877 84 : auto nGCSId = AllocNewObject();
878 :
879 84 : if (nViewportId.toBool())
880 : {
881 84 : StartObj(nViewportId);
882 168 : GDALPDFDictionaryRW oViewPortDict;
883 84 : oViewPortDict.Add("Type", GDALPDFObjectRW::CreateName("Viewport"))
884 84 : .Add("Name", "Layer")
885 84 : .Add("BBox", &((new GDALPDFArrayRW())
886 84 : ->Add(dfULPixel / dfUserUnit + psMargins->nLeft)
887 84 : .Add((nHeight - dfLRLine) / dfUserUnit +
888 84 : psMargins->nBottom)
889 84 : .Add(dfLRPixel / dfUserUnit + psMargins->nLeft)
890 84 : .Add((nHeight - dfULLine) / dfUserUnit +
891 84 : psMargins->nBottom)))
892 84 : .Add("Measure", nMeasureId, 0);
893 84 : VSIFPrintfL(m_fp, "%s\n", oViewPortDict.Serialize().c_str());
894 84 : EndObj();
895 : }
896 :
897 84 : StartObj(nMeasureId);
898 168 : GDALPDFDictionaryRW oMeasureDict;
899 84 : oMeasureDict.Add("Type", GDALPDFObjectRW::CreateName("Measure"))
900 84 : .Add("Subtype", GDALPDFObjectRW::CreateName("GEO"))
901 84 : .Add("Bounds", &((new GDALPDFArrayRW())
902 84 : ->Add(0)
903 84 : .Add(1)
904 84 : .Add(0)
905 84 : .Add(0)
906 84 : .Add(1)
907 84 : .Add(0)
908 84 : .Add(1)
909 84 : .Add(1)))
910 84 : .Add("GPTS", &((new GDALPDFArrayRW())
911 84 : ->Add(adfGPTS[1])
912 84 : .Add(adfGPTS[0])
913 84 : .Add(adfGPTS[3])
914 84 : .Add(adfGPTS[2])
915 84 : .Add(adfGPTS[5])
916 84 : .Add(adfGPTS[4])
917 84 : .Add(adfGPTS[7])
918 84 : .Add(adfGPTS[6])))
919 84 : .Add("LPTS", &((new GDALPDFArrayRW())
920 84 : ->Add(0)
921 84 : .Add(1)
922 84 : .Add(0)
923 84 : .Add(0)
924 84 : .Add(1)
925 84 : .Add(0)
926 84 : .Add(1)
927 84 : .Add(1)))
928 84 : .Add("GCS", nGCSId, 0);
929 84 : VSIFPrintfL(m_fp, "%s\n", oMeasureDict.Serialize().c_str());
930 84 : EndObj();
931 :
932 84 : StartObj(nGCSId);
933 84 : GDALPDFDictionaryRW oGCSDict;
934 : oGCSDict
935 : .Add("Type",
936 84 : GDALPDFObjectRW::CreateName(bIsGeographic ? "GEOGCS" : "PROJCS"))
937 84 : .Add("WKT", pszESRIWKT);
938 84 : if (nEPSGCode)
939 69 : oGCSDict.Add("EPSG", nEPSGCode);
940 84 : VSIFPrintfL(m_fp, "%s\n", oGCSDict.Serialize().c_str());
941 84 : EndObj();
942 :
943 84 : CPLFree(pszESRIWKT);
944 :
945 84 : return nViewportId.toBool() ? nViewportId : nMeasureId;
946 : }
947 :
948 : /************************************************************************/
949 : /* GDALPDFGetValueFromDSOrOption() */
950 : /************************************************************************/
951 :
952 826 : static const char *GDALPDFGetValueFromDSOrOption(GDALDataset *poSrcDS,
953 : char **papszOptions,
954 : const char *pszKey)
955 : {
956 826 : const char *pszValue = CSLFetchNameValue(papszOptions, pszKey);
957 826 : if (pszValue == nullptr)
958 814 : pszValue = poSrcDS->GetMetadataItem(pszKey);
959 826 : if (pszValue != nullptr && pszValue[0] == '\0')
960 2 : return nullptr;
961 : else
962 824 : return pszValue;
963 : }
964 :
965 : /************************************************************************/
966 : /* SetInfo() */
967 : /************************************************************************/
968 :
969 118 : GDALPDFObjectNum GDALPDFBaseWriter::SetInfo(GDALDataset *poSrcDS,
970 : char **papszOptions)
971 : {
972 : const char *pszAUTHOR =
973 118 : GDALPDFGetValueFromDSOrOption(poSrcDS, papszOptions, "AUTHOR");
974 : const char *pszPRODUCER =
975 118 : GDALPDFGetValueFromDSOrOption(poSrcDS, papszOptions, "PRODUCER");
976 : const char *pszCREATOR =
977 118 : GDALPDFGetValueFromDSOrOption(poSrcDS, papszOptions, "CREATOR");
978 : const char *pszCREATION_DATE =
979 118 : GDALPDFGetValueFromDSOrOption(poSrcDS, papszOptions, "CREATION_DATE");
980 : const char *pszSUBJECT =
981 118 : GDALPDFGetValueFromDSOrOption(poSrcDS, papszOptions, "SUBJECT");
982 : const char *pszTITLE =
983 118 : GDALPDFGetValueFromDSOrOption(poSrcDS, papszOptions, "TITLE");
984 : const char *pszKEYWORDS =
985 118 : GDALPDFGetValueFromDSOrOption(poSrcDS, papszOptions, "KEYWORDS");
986 : return SetInfo(pszAUTHOR, pszPRODUCER, pszCREATOR, pszCREATION_DATE,
987 118 : pszSUBJECT, pszTITLE, pszKEYWORDS);
988 : }
989 :
990 : /************************************************************************/
991 : /* SetInfo() */
992 : /************************************************************************/
993 :
994 : GDALPDFObjectNum
995 119 : GDALPDFBaseWriter::SetInfo(const char *pszAUTHOR, const char *pszPRODUCER,
996 : const char *pszCREATOR, const char *pszCREATION_DATE,
997 : const char *pszSUBJECT, const char *pszTITLE,
998 : const char *pszKEYWORDS)
999 : {
1000 119 : if (pszAUTHOR == nullptr && pszPRODUCER == nullptr &&
1001 110 : pszCREATOR == nullptr && pszCREATION_DATE == nullptr &&
1002 110 : pszSUBJECT == nullptr && pszTITLE == nullptr && pszKEYWORDS == nullptr)
1003 110 : return GDALPDFObjectNum();
1004 :
1005 9 : if (!m_nInfoId.toBool())
1006 7 : m_nInfoId = AllocNewObject();
1007 9 : StartObj(m_nInfoId, m_nInfoGen);
1008 9 : GDALPDFDictionaryRW oDict;
1009 9 : if (pszAUTHOR != nullptr)
1010 9 : oDict.Add("Author", pszAUTHOR);
1011 9 : if (pszPRODUCER != nullptr)
1012 4 : oDict.Add("Producer", pszPRODUCER);
1013 9 : if (pszCREATOR != nullptr)
1014 4 : oDict.Add("Creator", pszCREATOR);
1015 9 : if (pszCREATION_DATE != nullptr)
1016 0 : oDict.Add("CreationDate", pszCREATION_DATE);
1017 9 : if (pszSUBJECT != nullptr)
1018 4 : oDict.Add("Subject", pszSUBJECT);
1019 9 : if (pszTITLE != nullptr)
1020 4 : oDict.Add("Title", pszTITLE);
1021 9 : if (pszKEYWORDS != nullptr)
1022 4 : oDict.Add("Keywords", pszKEYWORDS);
1023 9 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
1024 9 : EndObj();
1025 :
1026 9 : return m_nInfoId;
1027 : }
1028 :
1029 : /************************************************************************/
1030 : /* SetXMP() */
1031 : /************************************************************************/
1032 :
1033 99 : GDALPDFObjectNum GDALPDFBaseWriter::SetXMP(GDALDataset *poSrcDS,
1034 : const char *pszXMP)
1035 : {
1036 99 : if (pszXMP != nullptr && STARTS_WITH_CI(pszXMP, "NO"))
1037 2 : return GDALPDFObjectNum();
1038 97 : if (pszXMP != nullptr && pszXMP[0] == '\0')
1039 0 : return GDALPDFObjectNum();
1040 :
1041 97 : if (poSrcDS && pszXMP == nullptr)
1042 : {
1043 96 : char **papszXMP = poSrcDS->GetMetadata("xml:XMP");
1044 96 : if (papszXMP != nullptr && papszXMP[0] != nullptr)
1045 6 : pszXMP = papszXMP[0];
1046 : }
1047 :
1048 97 : if (pszXMP == nullptr)
1049 90 : return GDALPDFObjectNum();
1050 :
1051 7 : CPLXMLNode *psNode = CPLParseXMLString(pszXMP);
1052 7 : if (psNode == nullptr)
1053 0 : return GDALPDFObjectNum();
1054 7 : CPLDestroyXMLNode(psNode);
1055 :
1056 7 : if (!m_nXMPId.toBool())
1057 5 : m_nXMPId = AllocNewObject();
1058 7 : StartObj(m_nXMPId, m_nXMPGen);
1059 7 : GDALPDFDictionaryRW oDict;
1060 7 : oDict.Add("Type", GDALPDFObjectRW::CreateName("Metadata"))
1061 7 : .Add("Subtype", GDALPDFObjectRW::CreateName("XML"))
1062 7 : .Add("Length", static_cast<int>(strlen(pszXMP)));
1063 7 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
1064 7 : VSIFPrintfL(m_fp, "stream\n");
1065 7 : VSIFPrintfL(m_fp, "%s\n", pszXMP);
1066 7 : VSIFPrintfL(m_fp, "endstream\n");
1067 7 : EndObj();
1068 7 : return m_nXMPId;
1069 : }
1070 :
1071 : /************************************************************************/
1072 : /* WriteOCG() */
1073 : /************************************************************************/
1074 :
1075 267 : GDALPDFObjectNum GDALPDFBaseWriter::WriteOCG(const char *pszLayerName,
1076 : const GDALPDFObjectNum &nParentId)
1077 : {
1078 267 : if (pszLayerName == nullptr || pszLayerName[0] == '\0')
1079 198 : return GDALPDFObjectNum();
1080 :
1081 69 : auto nOCGId = AllocNewObject();
1082 :
1083 69 : GDALPDFOCGDesc oOCGDesc;
1084 69 : oOCGDesc.nId = nOCGId;
1085 69 : oOCGDesc.nParentId = nParentId;
1086 69 : oOCGDesc.osLayerName = pszLayerName;
1087 :
1088 69 : m_asOCGs.push_back(oOCGDesc);
1089 :
1090 69 : StartObj(nOCGId);
1091 : {
1092 69 : GDALPDFDictionaryRW oDict;
1093 69 : oDict.Add("Type", GDALPDFObjectRW::CreateName("OCG"));
1094 69 : oDict.Add("Name", pszLayerName);
1095 69 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
1096 : }
1097 69 : EndObj();
1098 :
1099 69 : return nOCGId;
1100 : }
1101 :
1102 : /************************************************************************/
1103 : /* StartPage() */
1104 : /************************************************************************/
1105 :
1106 114 : bool GDALPDFWriter::StartPage(GDALDataset *poClippingDS, double dfDPI,
1107 : bool bWriteUserUnit, const char *pszGEO_ENCODING,
1108 : const char *pszNEATLINE, PDFMargins *psMargins,
1109 : PDFCompressMethod eStreamCompressMethod,
1110 : int bHasOGRData)
1111 : {
1112 114 : int nWidth = poClippingDS->GetRasterXSize();
1113 114 : int nHeight = poClippingDS->GetRasterYSize();
1114 114 : int nBands = poClippingDS->GetRasterCount();
1115 :
1116 114 : double dfUserUnit = dfDPI * USER_UNIT_IN_INCH;
1117 114 : double dfWidthInUserUnit =
1118 114 : nWidth / dfUserUnit + psMargins->nLeft + psMargins->nRight;
1119 114 : double dfHeightInUserUnit =
1120 114 : nHeight / dfUserUnit + psMargins->nBottom + psMargins->nTop;
1121 :
1122 114 : auto nPageId = AllocNewObject();
1123 114 : m_asPageId.push_back(nPageId);
1124 :
1125 114 : auto nContentId = AllocNewObject();
1126 114 : auto nResourcesId = AllocNewObject();
1127 :
1128 114 : auto nAnnotsId = AllocNewObject();
1129 :
1130 114 : const bool bISO32000 =
1131 114 : EQUAL(pszGEO_ENCODING, "ISO32000") || EQUAL(pszGEO_ENCODING, "BOTH");
1132 :
1133 114 : GDALPDFObjectNum nViewportId;
1134 114 : if (bISO32000)
1135 : nViewportId = WriteSRS_ISO32000(poClippingDS, dfUserUnit, pszNEATLINE,
1136 110 : psMargins, TRUE);
1137 :
1138 114 : StartObj(nPageId);
1139 114 : GDALPDFDictionaryRW oDictPage;
1140 114 : oDictPage.Add("Type", GDALPDFObjectRW::CreateName("Page"))
1141 114 : .Add("Parent", m_nPageResourceId, 0)
1142 114 : .Add("MediaBox", &((new GDALPDFArrayRW())
1143 114 : ->Add(0)
1144 114 : .Add(0)
1145 114 : .Add(dfWidthInUserUnit)
1146 114 : .Add(dfHeightInUserUnit)));
1147 114 : if (bWriteUserUnit)
1148 102 : oDictPage.Add("UserUnit", dfUserUnit);
1149 114 : oDictPage.Add("Contents", nContentId, 0)
1150 114 : .Add("Resources", nResourcesId, 0)
1151 114 : .Add("Annots", nAnnotsId, 0);
1152 :
1153 114 : if (nBands == 4)
1154 : {
1155 : oDictPage.Add(
1156 : "Group",
1157 7 : &((new GDALPDFDictionaryRW())
1158 7 : ->Add("Type", GDALPDFObjectRW::CreateName("Group"))
1159 7 : .Add("S", GDALPDFObjectRW::CreateName("Transparency"))
1160 7 : .Add("CS", GDALPDFObjectRW::CreateName("DeviceRGB"))));
1161 : }
1162 114 : if (nViewportId.toBool())
1163 : {
1164 75 : oDictPage.Add("VP", &((new GDALPDFArrayRW())->Add(nViewportId, 0)));
1165 : }
1166 :
1167 : #ifndef HACK_TO_GENERATE_OCMD
1168 114 : if (bHasOGRData)
1169 23 : oDictPage.Add("StructParents", 0);
1170 : #endif
1171 :
1172 114 : VSIFPrintfL(m_fp, "%s\n", oDictPage.Serialize().c_str());
1173 114 : EndObj();
1174 :
1175 114 : oPageContext.poClippingDS = poClippingDS;
1176 114 : oPageContext.nPageId = nPageId;
1177 114 : oPageContext.nContentId = nContentId;
1178 114 : oPageContext.nResourcesId = nResourcesId;
1179 114 : oPageContext.nAnnotsId = nAnnotsId;
1180 114 : oPageContext.dfDPI = dfDPI;
1181 114 : oPageContext.sMargins = *psMargins;
1182 114 : oPageContext.eStreamCompressMethod = eStreamCompressMethod;
1183 :
1184 228 : return true;
1185 : }
1186 :
1187 : /************************************************************************/
1188 : /* WriteColorTable() */
1189 : /************************************************************************/
1190 :
1191 305 : GDALPDFObjectNum GDALPDFBaseWriter::WriteColorTable(GDALDataset *poSrcDS)
1192 : {
1193 : /* Does the source image has a color table ? */
1194 305 : GDALColorTable *poCT = nullptr;
1195 305 : if (poSrcDS->GetRasterCount() > 0)
1196 305 : poCT = poSrcDS->GetRasterBand(1)->GetColorTable();
1197 305 : GDALPDFObjectNum nColorTableId;
1198 305 : if (poCT != nullptr && poCT->GetColorEntryCount() <= 256)
1199 : {
1200 2 : int nColors = poCT->GetColorEntryCount();
1201 2 : nColorTableId = AllocNewObject();
1202 :
1203 2 : auto nLookupTableId = AllocNewObject();
1204 :
1205 : /* Index object */
1206 2 : StartObj(nColorTableId);
1207 : {
1208 2 : GDALPDFArrayRW oArray;
1209 2 : oArray.Add(GDALPDFObjectRW::CreateName("Indexed"))
1210 2 : .Add(&((new GDALPDFArrayRW())
1211 2 : ->Add(GDALPDFObjectRW::CreateName("DeviceRGB"))))
1212 2 : .Add(nColors - 1)
1213 2 : .Add(nLookupTableId, 0);
1214 2 : VSIFPrintfL(m_fp, "%s\n", oArray.Serialize().c_str());
1215 : }
1216 2 : EndObj();
1217 :
1218 : /* Lookup table object */
1219 2 : StartObj(nLookupTableId);
1220 : {
1221 2 : GDALPDFDictionaryRW oDict;
1222 2 : oDict.Add("Length", nColors * 3);
1223 2 : VSIFPrintfL(m_fp, "%s %% Lookup table\n",
1224 4 : oDict.Serialize().c_str());
1225 : }
1226 2 : VSIFPrintfL(m_fp, "stream\n");
1227 : GByte pabyLookup[768];
1228 514 : for (int i = 0; i < nColors; i++)
1229 : {
1230 512 : const GDALColorEntry *poEntry = poCT->GetColorEntry(i);
1231 512 : pabyLookup[3 * i + 0] = static_cast<GByte>(poEntry->c1);
1232 512 : pabyLookup[3 * i + 1] = static_cast<GByte>(poEntry->c2);
1233 512 : pabyLookup[3 * i + 2] = static_cast<GByte>(poEntry->c3);
1234 : }
1235 2 : VSIFWriteL(pabyLookup, 3 * nColors, 1, m_fp);
1236 2 : VSIFPrintfL(m_fp, "\n");
1237 2 : VSIFPrintfL(m_fp, "endstream\n");
1238 2 : EndObj();
1239 : }
1240 :
1241 305 : return nColorTableId;
1242 : }
1243 :
1244 : /************************************************************************/
1245 : /* WriteImagery() */
1246 : /************************************************************************/
1247 :
1248 90 : bool GDALPDFWriter::WriteImagery(GDALDataset *poDS, const char *pszLayerName,
1249 : PDFCompressMethod eCompressMethod,
1250 : int nPredictor, int nJPEGQuality,
1251 : const char *pszJPEG2000_DRIVER,
1252 : int nBlockXSize, int nBlockYSize,
1253 : GDALProgressFunc pfnProgress,
1254 : void *pProgressData)
1255 : {
1256 90 : int nWidth = poDS->GetRasterXSize();
1257 90 : int nHeight = poDS->GetRasterYSize();
1258 90 : double dfUserUnit = oPageContext.dfDPI * USER_UNIT_IN_INCH;
1259 :
1260 180 : GDALPDFRasterDesc oRasterDesc;
1261 :
1262 90 : if (pfnProgress == nullptr)
1263 0 : pfnProgress = GDALDummyProgress;
1264 :
1265 90 : oRasterDesc.nOCGRasterId = WriteOCG(pszLayerName);
1266 :
1267 : /* Does the source image has a color table ? */
1268 90 : auto nColorTableId = WriteColorTable(poDS);
1269 :
1270 90 : int nXBlocks = DIV_ROUND_UP(nWidth, nBlockXSize);
1271 90 : int nYBlocks = DIV_ROUND_UP(nHeight, nBlockYSize);
1272 90 : int nBlocks = nXBlocks * nYBlocks;
1273 : int nBlockXOff, nBlockYOff;
1274 194 : for (nBlockYOff = 0; nBlockYOff < nYBlocks; nBlockYOff++)
1275 : {
1276 288 : for (nBlockXOff = 0; nBlockXOff < nXBlocks; nBlockXOff++)
1277 : {
1278 : const int nReqWidth =
1279 184 : std::min(nBlockXSize, nWidth - nBlockXOff * nBlockXSize);
1280 : const int nReqHeight =
1281 184 : std::min(nBlockYSize, nHeight - nBlockYOff * nBlockYSize);
1282 184 : int iImage = nBlockYOff * nXBlocks + nBlockXOff;
1283 :
1284 368 : void *pScaledData = GDALCreateScaledProgress(
1285 184 : iImage / double(nBlocks), (iImage + 1) / double(nBlocks),
1286 : pfnProgress, pProgressData);
1287 184 : int nX = nBlockXOff * nBlockXSize;
1288 184 : int nY = nBlockYOff * nBlockYSize;
1289 :
1290 : auto nImageId =
1291 : WriteBlock(poDS, nX, nY, nReqWidth, nReqHeight, nColorTableId,
1292 : eCompressMethod, nPredictor, nJPEGQuality,
1293 184 : pszJPEG2000_DRIVER, GDALScaledProgress, pScaledData);
1294 :
1295 184 : GDALDestroyScaledProgress(pScaledData);
1296 :
1297 184 : if (!nImageId.toBool())
1298 2 : return false;
1299 :
1300 182 : GDALPDFImageDesc oImageDesc;
1301 182 : oImageDesc.nImageId = nImageId;
1302 182 : oImageDesc.dfXOff = nX / dfUserUnit + oPageContext.sMargins.nLeft;
1303 182 : oImageDesc.dfYOff = (nHeight - nY - nReqHeight) / dfUserUnit +
1304 182 : oPageContext.sMargins.nBottom;
1305 182 : oImageDesc.dfXSize = nReqWidth / dfUserUnit;
1306 182 : oImageDesc.dfYSize = nReqHeight / dfUserUnit;
1307 :
1308 182 : oRasterDesc.asImageDesc.push_back(oImageDesc);
1309 : }
1310 : }
1311 :
1312 88 : oPageContext.asRasterDesc.push_back(oRasterDesc);
1313 :
1314 88 : return true;
1315 : }
1316 :
1317 : /************************************************************************/
1318 : /* WriteClippedImagery() */
1319 : /************************************************************************/
1320 :
1321 4 : bool GDALPDFWriter::WriteClippedImagery(
1322 : GDALDataset *poDS, const char *pszLayerName,
1323 : PDFCompressMethod eCompressMethod, int nPredictor, int nJPEGQuality,
1324 : const char *pszJPEG2000_DRIVER, int nBlockXSize, int nBlockYSize,
1325 : GDALProgressFunc pfnProgress, void *pProgressData)
1326 : {
1327 4 : double dfUserUnit = oPageContext.dfDPI * USER_UNIT_IN_INCH;
1328 :
1329 8 : GDALPDFRasterDesc oRasterDesc;
1330 :
1331 : /* Get clipping dataset bounding-box */
1332 : double adfClippingGeoTransform[6];
1333 4 : GDALDataset *poClippingDS = oPageContext.poClippingDS;
1334 4 : poClippingDS->GetGeoTransform(adfClippingGeoTransform);
1335 4 : int nClippingWidth = poClippingDS->GetRasterXSize();
1336 4 : int nClippingHeight = poClippingDS->GetRasterYSize();
1337 4 : double dfClippingMinX = adfClippingGeoTransform[0];
1338 4 : double dfClippingMaxX =
1339 4 : dfClippingMinX + nClippingWidth * adfClippingGeoTransform[1];
1340 4 : double dfClippingMaxY = adfClippingGeoTransform[3];
1341 4 : double dfClippingMinY =
1342 4 : dfClippingMaxY + nClippingHeight * adfClippingGeoTransform[5];
1343 :
1344 4 : if (dfClippingMaxY < dfClippingMinY)
1345 : {
1346 0 : std::swap(dfClippingMinY, dfClippingMaxY);
1347 : }
1348 :
1349 : /* Get current dataset dataset bounding-box */
1350 : double adfGeoTransform[6];
1351 4 : poDS->GetGeoTransform(adfGeoTransform);
1352 4 : int nWidth = poDS->GetRasterXSize();
1353 4 : int nHeight = poDS->GetRasterYSize();
1354 4 : double dfRasterMinX = adfGeoTransform[0];
1355 : // double dfRasterMaxX = dfRasterMinX + nWidth * adfGeoTransform[1];
1356 4 : double dfRasterMaxY = adfGeoTransform[3];
1357 4 : double dfRasterMinY = dfRasterMaxY + nHeight * adfGeoTransform[5];
1358 :
1359 4 : if (dfRasterMaxY < dfRasterMinY)
1360 : {
1361 0 : std::swap(dfRasterMinY, dfRasterMaxY);
1362 : }
1363 :
1364 4 : if (pfnProgress == nullptr)
1365 2 : pfnProgress = GDALDummyProgress;
1366 :
1367 4 : oRasterDesc.nOCGRasterId = WriteOCG(pszLayerName);
1368 :
1369 : /* Does the source image has a color table ? */
1370 4 : auto nColorTableId = WriteColorTable(poDS);
1371 :
1372 4 : int nXBlocks = DIV_ROUND_UP(nWidth, nBlockXSize);
1373 4 : int nYBlocks = DIV_ROUND_UP(nHeight, nBlockYSize);
1374 4 : int nBlocks = nXBlocks * nYBlocks;
1375 : int nBlockXOff, nBlockYOff;
1376 8 : for (nBlockYOff = 0; nBlockYOff < nYBlocks; nBlockYOff++)
1377 : {
1378 8 : for (nBlockXOff = 0; nBlockXOff < nXBlocks; nBlockXOff++)
1379 : {
1380 : int nReqWidth =
1381 4 : std::min(nBlockXSize, nWidth - nBlockXOff * nBlockXSize);
1382 : int nReqHeight =
1383 4 : std::min(nBlockYSize, nHeight - nBlockYOff * nBlockYSize);
1384 4 : int iImage = nBlockYOff * nXBlocks + nBlockXOff;
1385 :
1386 8 : void *pScaledData = GDALCreateScaledProgress(
1387 4 : iImage / double(nBlocks), (iImage + 1) / double(nBlocks),
1388 : pfnProgress, pProgressData);
1389 :
1390 4 : int nX = nBlockXOff * nBlockXSize;
1391 4 : int nY = nBlockYOff * nBlockYSize;
1392 :
1393 : /* Compute extent of block to write */
1394 4 : double dfBlockMinX = adfGeoTransform[0] + nX * adfGeoTransform[1];
1395 4 : double dfBlockMaxX =
1396 4 : adfGeoTransform[0] + (nX + nReqWidth) * adfGeoTransform[1];
1397 4 : double dfBlockMinY =
1398 4 : adfGeoTransform[3] + (nY + nReqHeight) * adfGeoTransform[5];
1399 4 : double dfBlockMaxY = adfGeoTransform[3] + nY * adfGeoTransform[5];
1400 :
1401 4 : if (dfBlockMaxY < dfBlockMinY)
1402 : {
1403 0 : std::swap(dfBlockMinY, dfBlockMaxY);
1404 : }
1405 :
1406 : // Clip the extent of the block with the extent of the main raster.
1407 : const double dfIntersectMinX =
1408 4 : std::max(dfBlockMinX, dfClippingMinX);
1409 : const double dfIntersectMinY =
1410 4 : std::max(dfBlockMinY, dfClippingMinY);
1411 : const double dfIntersectMaxX =
1412 4 : std::min(dfBlockMaxX, dfClippingMaxX);
1413 : const double dfIntersectMaxY =
1414 4 : std::min(dfBlockMaxY, dfClippingMaxY);
1415 :
1416 4 : if (dfIntersectMinX < dfIntersectMaxX &&
1417 : dfIntersectMinY < dfIntersectMaxY)
1418 : {
1419 : /* Re-compute (x,y,width,height) subwindow of current raster
1420 : * from */
1421 : /* the extent of the clipped block */
1422 4 : nX = static_cast<int>((dfIntersectMinX - dfRasterMinX) /
1423 4 : adfGeoTransform[1] +
1424 : 0.5);
1425 4 : if (adfGeoTransform[5] < 0)
1426 4 : nY = static_cast<int>((dfRasterMaxY - dfIntersectMaxY) /
1427 4 : (-adfGeoTransform[5]) +
1428 : 0.5);
1429 : else
1430 0 : nY = static_cast<int>((dfIntersectMinY - dfRasterMinY) /
1431 0 : adfGeoTransform[5] +
1432 : 0.5);
1433 4 : nReqWidth = static_cast<int>((dfIntersectMaxX - dfRasterMinX) /
1434 4 : adfGeoTransform[1] +
1435 : 0.5) -
1436 : nX;
1437 4 : if (adfGeoTransform[5] < 0)
1438 4 : nReqHeight =
1439 4 : static_cast<int>((dfRasterMaxY - dfIntersectMinY) /
1440 4 : (-adfGeoTransform[5]) +
1441 : 0.5) -
1442 : nY;
1443 : else
1444 0 : nReqHeight =
1445 0 : static_cast<int>((dfIntersectMaxY - dfRasterMinY) /
1446 0 : adfGeoTransform[5] +
1447 : 0.5) -
1448 : nY;
1449 :
1450 4 : if (nReqWidth > 0 && nReqHeight > 0)
1451 : {
1452 : auto nImageId = WriteBlock(
1453 : poDS, nX, nY, nReqWidth, nReqHeight, nColorTableId,
1454 : eCompressMethod, nPredictor, nJPEGQuality,
1455 4 : pszJPEG2000_DRIVER, GDALScaledProgress, pScaledData);
1456 :
1457 4 : if (!nImageId.toBool())
1458 : {
1459 0 : GDALDestroyScaledProgress(pScaledData);
1460 0 : return false;
1461 : }
1462 :
1463 : /* Compute the subwindow in image coordinates of the main
1464 : * raster corresponding */
1465 : /* to the extent of the clipped block */
1466 : double dfXInClippingUnits, dfYInClippingUnits,
1467 : dfReqWidthInClippingUnits, dfReqHeightInClippingUnits;
1468 :
1469 4 : dfXInClippingUnits = (dfIntersectMinX - dfClippingMinX) /
1470 4 : adfClippingGeoTransform[1];
1471 4 : if (adfClippingGeoTransform[5] < 0)
1472 4 : dfYInClippingUnits =
1473 4 : (dfClippingMaxY - dfIntersectMaxY) /
1474 4 : (-adfClippingGeoTransform[5]);
1475 : else
1476 0 : dfYInClippingUnits =
1477 0 : (dfIntersectMinY - dfClippingMinY) /
1478 0 : adfClippingGeoTransform[5];
1479 4 : dfReqWidthInClippingUnits =
1480 4 : (dfIntersectMaxX - dfClippingMinX) /
1481 4 : adfClippingGeoTransform[1] -
1482 : dfXInClippingUnits;
1483 4 : if (adfClippingGeoTransform[5] < 0)
1484 4 : dfReqHeightInClippingUnits =
1485 4 : (dfClippingMaxY - dfIntersectMinY) /
1486 4 : (-adfClippingGeoTransform[5]) -
1487 : dfYInClippingUnits;
1488 : else
1489 0 : dfReqHeightInClippingUnits =
1490 0 : (dfIntersectMaxY - dfClippingMinY) /
1491 0 : adfClippingGeoTransform[5] -
1492 : dfYInClippingUnits;
1493 :
1494 4 : GDALPDFImageDesc oImageDesc;
1495 4 : oImageDesc.nImageId = nImageId;
1496 4 : oImageDesc.dfXOff = dfXInClippingUnits / dfUserUnit +
1497 4 : oPageContext.sMargins.nLeft;
1498 4 : oImageDesc.dfYOff = (nClippingHeight - dfYInClippingUnits -
1499 4 : dfReqHeightInClippingUnits) /
1500 4 : dfUserUnit +
1501 4 : oPageContext.sMargins.nBottom;
1502 4 : oImageDesc.dfXSize = dfReqWidthInClippingUnits / dfUserUnit;
1503 4 : oImageDesc.dfYSize =
1504 4 : dfReqHeightInClippingUnits / dfUserUnit;
1505 :
1506 4 : oRasterDesc.asImageDesc.push_back(oImageDesc);
1507 : }
1508 : }
1509 :
1510 4 : GDALDestroyScaledProgress(pScaledData);
1511 : }
1512 : }
1513 :
1514 4 : oPageContext.asRasterDesc.push_back(oRasterDesc);
1515 :
1516 4 : return true;
1517 : }
1518 :
1519 : /************************************************************************/
1520 : /* WriteOGRDataSource() */
1521 : /************************************************************************/
1522 :
1523 4 : bool GDALPDFWriter::WriteOGRDataSource(const char *pszOGRDataSource,
1524 : const char *pszOGRDisplayField,
1525 : const char *pszOGRDisplayLayerNames,
1526 : const char *pszOGRLinkField,
1527 : int bWriteOGRAttributes)
1528 : {
1529 : GDALDatasetH hDS =
1530 4 : GDALOpenEx(pszOGRDataSource, GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR,
1531 : nullptr, nullptr, nullptr);
1532 4 : if (hDS == nullptr)
1533 0 : return false;
1534 :
1535 4 : int iObj = 0;
1536 :
1537 4 : int nLayers = GDALDatasetGetLayerCount(hDS);
1538 :
1539 : char **papszLayerNames =
1540 4 : CSLTokenizeString2(pszOGRDisplayLayerNames, ",", 0);
1541 :
1542 8 : for (int iLayer = 0; iLayer < nLayers; iLayer++)
1543 : {
1544 8 : CPLString osLayerName;
1545 4 : if (CSLCount(papszLayerNames) < nLayers)
1546 0 : osLayerName = OGR_L_GetName(GDALDatasetGetLayer(hDS, iLayer));
1547 : else
1548 4 : osLayerName = papszLayerNames[iLayer];
1549 :
1550 4 : WriteOGRLayer(hDS, iLayer, pszOGRDisplayField, pszOGRLinkField,
1551 : osLayerName, bWriteOGRAttributes, iObj);
1552 : }
1553 :
1554 4 : GDALClose(hDS);
1555 :
1556 4 : CSLDestroy(papszLayerNames);
1557 :
1558 4 : return true;
1559 : }
1560 :
1561 : /************************************************************************/
1562 : /* StartOGRLayer() */
1563 : /************************************************************************/
1564 :
1565 41 : GDALPDFLayerDesc GDALPDFWriter::StartOGRLayer(const std::string &osLayerName,
1566 : int bWriteOGRAttributes)
1567 : {
1568 41 : GDALPDFLayerDesc osVectorDesc;
1569 41 : osVectorDesc.osLayerName = osLayerName;
1570 : #ifdef HACK_TO_GENERATE_OCMD
1571 : osVectorDesc.bWriteOGRAttributes = false;
1572 : auto nParentOCGId = WriteOCG("parent");
1573 : osVectorDesc.nOCGId = WriteOCG(osLayerName.c_str(), nParentOCGId);
1574 : #else
1575 41 : osVectorDesc.bWriteOGRAttributes = bWriteOGRAttributes;
1576 41 : osVectorDesc.nOCGId = WriteOCG(osLayerName.c_str());
1577 : #endif
1578 41 : if (bWriteOGRAttributes)
1579 38 : osVectorDesc.nFeatureLayerId = AllocNewObject();
1580 :
1581 41 : return osVectorDesc;
1582 : }
1583 :
1584 : /************************************************************************/
1585 : /* EndOGRLayer() */
1586 : /************************************************************************/
1587 :
1588 41 : void GDALPDFWriter::EndOGRLayer(GDALPDFLayerDesc &osVectorDesc)
1589 : {
1590 41 : if (osVectorDesc.bWriteOGRAttributes)
1591 : {
1592 38 : StartObj(osVectorDesc.nFeatureLayerId);
1593 :
1594 76 : GDALPDFDictionaryRW oDict;
1595 38 : oDict.Add("A", &(new GDALPDFDictionaryRW())
1596 38 : ->Add("O", GDALPDFObjectRW::CreateName(
1597 38 : "UserProperties")));
1598 :
1599 38 : GDALPDFArrayRW *poArray = new GDALPDFArrayRW();
1600 38 : oDict.Add("K", poArray);
1601 :
1602 145 : for (const auto &prop : osVectorDesc.aUserPropertiesIds)
1603 : {
1604 107 : poArray->Add(prop, 0);
1605 : }
1606 :
1607 38 : if (!m_nStructTreeRootId.toBool())
1608 23 : m_nStructTreeRootId = AllocNewObject();
1609 :
1610 38 : oDict.Add("P", m_nStructTreeRootId, 0);
1611 38 : oDict.Add("S", GDALPDFObjectRW::CreateName("Feature"));
1612 38 : oDict.Add("T", osVectorDesc.osLayerName);
1613 :
1614 38 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
1615 :
1616 38 : EndObj();
1617 : }
1618 :
1619 41 : oPageContext.asVectorDesc.push_back(osVectorDesc);
1620 41 : }
1621 :
1622 : /************************************************************************/
1623 : /* WriteOGRLayer() */
1624 : /************************************************************************/
1625 :
1626 41 : int GDALPDFWriter::WriteOGRLayer(GDALDatasetH hDS, int iLayer,
1627 : const char *pszOGRDisplayField,
1628 : const char *pszOGRLinkField,
1629 : const std::string &osLayerName,
1630 : int bWriteOGRAttributes, int &iObj)
1631 : {
1632 41 : GDALDataset *poClippingDS = oPageContext.poClippingDS;
1633 : double adfGeoTransform[6];
1634 41 : if (poClippingDS->GetGeoTransform(adfGeoTransform) != CE_None)
1635 0 : return FALSE;
1636 :
1637 : GDALPDFLayerDesc osVectorDesc =
1638 41 : StartOGRLayer(osLayerName, bWriteOGRAttributes);
1639 41 : OGRLayerH hLyr = GDALDatasetGetLayer(hDS, iLayer);
1640 :
1641 41 : const auto poLayerDefn = OGRLayer::FromHandle(hLyr)->GetLayerDefn();
1642 163 : for (int i = 0; i < poLayerDefn->GetFieldCount(); i++)
1643 : {
1644 122 : const auto poFieldDefn = poLayerDefn->GetFieldDefn(i);
1645 122 : const char *pszName = poFieldDefn->GetNameRef();
1646 122 : osVectorDesc.aosIncludedFields.push_back(pszName);
1647 : }
1648 :
1649 41 : OGRSpatialReferenceH hGDAL_SRS = OGRSpatialReference::ToHandle(
1650 41 : const_cast<OGRSpatialReference *>(poClippingDS->GetSpatialRef()));
1651 41 : OGRSpatialReferenceH hOGR_SRS = OGR_L_GetSpatialRef(hLyr);
1652 41 : OGRCoordinateTransformationH hCT = nullptr;
1653 :
1654 41 : if (hGDAL_SRS == nullptr && hOGR_SRS != nullptr)
1655 : {
1656 0 : CPLError(CE_Warning, CPLE_AppDefined,
1657 : "Vector layer has a SRS set, but Raster layer has no SRS set. "
1658 : "Assuming they are the same.");
1659 : }
1660 41 : else if (hGDAL_SRS != nullptr && hOGR_SRS == nullptr)
1661 : {
1662 0 : CPLError(CE_Warning, CPLE_AppDefined,
1663 : "Vector layer has no SRS set, but Raster layer has a SRS set. "
1664 : "Assuming they are the same.");
1665 : }
1666 41 : else if (hGDAL_SRS != nullptr && hOGR_SRS != nullptr)
1667 : {
1668 11 : if (!OSRIsSame(hGDAL_SRS, hOGR_SRS))
1669 : {
1670 2 : hCT = OCTNewCoordinateTransformation(hOGR_SRS, hGDAL_SRS);
1671 2 : if (hCT == nullptr)
1672 : {
1673 0 : CPLError(CE_Warning, CPLE_AppDefined,
1674 : "Cannot compute coordinate transformation from vector "
1675 : "SRS to raster SRS");
1676 : }
1677 : }
1678 : }
1679 :
1680 41 : if (hCT == nullptr)
1681 : {
1682 39 : double dfXMin = adfGeoTransform[0];
1683 39 : double dfYMin = adfGeoTransform[3] +
1684 39 : poClippingDS->GetRasterYSize() * adfGeoTransform[5];
1685 39 : double dfXMax = adfGeoTransform[0] +
1686 39 : poClippingDS->GetRasterXSize() * adfGeoTransform[1];
1687 39 : double dfYMax = adfGeoTransform[3];
1688 39 : OGR_L_SetSpatialFilterRect(hLyr, dfXMin, dfYMin, dfXMax, dfYMax);
1689 : }
1690 :
1691 : OGRFeatureH hFeat;
1692 :
1693 195 : while ((hFeat = OGR_L_GetNextFeature(hLyr)) != nullptr)
1694 : {
1695 154 : WriteOGRFeature(osVectorDesc, hFeat, hCT, pszOGRDisplayField,
1696 : pszOGRLinkField, bWriteOGRAttributes, iObj);
1697 :
1698 154 : OGR_F_Destroy(hFeat);
1699 : }
1700 :
1701 41 : EndOGRLayer(osVectorDesc);
1702 :
1703 41 : if (hCT != nullptr)
1704 2 : OCTDestroyCoordinateTransformation(hCT);
1705 :
1706 41 : return TRUE;
1707 : }
1708 :
1709 : /************************************************************************/
1710 : /* DrawGeometry() */
1711 : /************************************************************************/
1712 :
1713 118 : static void DrawGeometry(CPLString &osDS, OGRGeometryH hGeom,
1714 : const double adfMatrix[4], bool bPaint = true)
1715 : {
1716 118 : switch (wkbFlatten(OGR_G_GetGeometryType(hGeom)))
1717 : {
1718 61 : case wkbLineString:
1719 : {
1720 61 : int nPoints = OGR_G_GetPointCount(hGeom);
1721 300 : for (int i = 0; i < nPoints; i++)
1722 : {
1723 239 : double dfX = OGR_G_GetX(hGeom, i) * adfMatrix[1] + adfMatrix[0];
1724 239 : double dfY = OGR_G_GetY(hGeom, i) * adfMatrix[3] + adfMatrix[2];
1725 : osDS +=
1726 239 : CPLOPrintf("%f %f %c\n", dfX, dfY, (i == 0) ? 'm' : 'l');
1727 : }
1728 61 : if (bPaint)
1729 16 : osDS += CPLOPrintf("S\n");
1730 61 : break;
1731 : }
1732 :
1733 31 : case wkbPolygon:
1734 : {
1735 31 : int nParts = OGR_G_GetGeometryCount(hGeom);
1736 70 : for (int i = 0; i < nParts; i++)
1737 : {
1738 39 : DrawGeometry(osDS, OGR_G_GetGeometryRef(hGeom, i), adfMatrix,
1739 : false);
1740 39 : osDS += CPLOPrintf("h\n");
1741 : }
1742 31 : if (bPaint)
1743 21 : osDS += CPLOPrintf("b*\n");
1744 31 : break;
1745 : }
1746 :
1747 6 : case wkbMultiLineString:
1748 : {
1749 6 : int nParts = OGR_G_GetGeometryCount(hGeom);
1750 12 : for (int i = 0; i < nParts; i++)
1751 : {
1752 6 : DrawGeometry(osDS, OGR_G_GetGeometryRef(hGeom, i), adfMatrix,
1753 : false);
1754 : }
1755 6 : if (bPaint)
1756 6 : osDS += CPLOPrintf("S\n");
1757 6 : break;
1758 : }
1759 :
1760 8 : case wkbMultiPolygon:
1761 : {
1762 8 : int nParts = OGR_G_GetGeometryCount(hGeom);
1763 18 : for (int i = 0; i < nParts; i++)
1764 : {
1765 10 : DrawGeometry(osDS, OGR_G_GetGeometryRef(hGeom, i), adfMatrix,
1766 : false);
1767 : }
1768 8 : if (bPaint)
1769 8 : osDS += CPLOPrintf("b*\n");
1770 8 : break;
1771 : }
1772 :
1773 12 : default:
1774 12 : break;
1775 : }
1776 118 : }
1777 :
1778 : /************************************************************************/
1779 : /* CalculateText() */
1780 : /************************************************************************/
1781 :
1782 11 : static void CalculateText(const CPLString &osText, CPLString &osFont,
1783 : const double dfSize, const bool bBold,
1784 : const bool bItalic, double &dfWidth, double &dfHeight)
1785 : {
1786 : // Character widths of Helvetica, Win-1252 characters 32 to 255
1787 : // Helvetica bold, oblique and bold oblique have their own widths,
1788 : // but for now we will put up with these widths on all Helvetica variants
1789 11 : constexpr GUInt16 anHelveticaCharWidths[] = {
1790 : 569, 569, 727, 1139, 1139, 1821, 1366, 391, 682, 682, 797, 1196,
1791 : 569, 682, 569, 569, 1139, 1139, 1139, 1139, 1139, 1139, 1139, 1139,
1792 : 1139, 1139, 569, 569, 1196, 1196, 1196, 1139, 2079, 1366, 1366, 1479,
1793 : 1479, 1366, 1251, 1593, 1479, 569, 1024, 1366, 1139, 1706, 1479, 1593,
1794 : 1366, 1593, 1479, 1366, 1251, 1479, 1366, 1933, 1366, 1366, 1251, 569,
1795 : 569, 569, 961, 1139, 682, 1139, 1139, 1024, 1139, 1139, 569, 1139,
1796 : 1139, 455, 455, 1024, 455, 1706, 1139, 1139, 1139, 1139, 682, 1024,
1797 : 569, 1139, 1024, 1479, 1024, 1024, 1024, 684, 532, 684, 1196, 1536,
1798 : 1139, 2048, 455, 1139, 682, 2048, 1139, 1139, 682, 2048, 1366, 682,
1799 : 2048, 2048, 1251, 2048, 2048, 455, 455, 682, 682, 717, 1139, 2048,
1800 : 682, 2048, 1024, 682, 1933, 2048, 1024, 1366, 569, 682, 1139, 1139,
1801 : 1139, 1139, 532, 1139, 682, 1509, 758, 1139, 1196, 682, 1509, 1131,
1802 : 819, 1124, 682, 682, 682, 1180, 1100, 682, 682, 682, 748, 1139,
1803 : 1708, 1708, 1708, 1251, 1366, 1366, 1366, 1366, 1366, 1366, 2048, 1479,
1804 : 1366, 1366, 1366, 1366, 569, 569, 569, 569, 1479, 1479, 1593, 1593,
1805 : 1593, 1593, 1593, 1196, 1593, 1479, 1479, 1479, 1479, 1366, 1366, 1251,
1806 : 1139, 1139, 1139, 1139, 1139, 1139, 1821, 1024, 1139, 1139, 1139, 1139,
1807 : 569, 569, 569, 569, 1139, 1139, 1139, 1139, 1139, 1139, 1139, 1124,
1808 : 1251, 1139, 1139, 1139, 1139, 1024, 1139, 1024};
1809 :
1810 : // Character widths of Times-Roman, Win-1252 characters 32 to 255
1811 : // Times bold, italic and bold italic have their own widths,
1812 : // but for now we will put up with these widths on all Times variants
1813 11 : constexpr GUInt16 anTimesCharWidths[] = {
1814 : 512, 682, 836, 1024, 1024, 1706, 1593, 369, 682, 682, 1024, 1155,
1815 : 512, 682, 512, 569, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
1816 : 1024, 1024, 569, 569, 1155, 1155, 1155, 909, 1886, 1479, 1366, 1366,
1817 : 1479, 1251, 1139, 1479, 1479, 682, 797, 1479, 1251, 1821, 1479, 1479,
1818 : 1139, 1479, 1366, 1139, 1251, 1479, 1479, 1933, 1479, 1479, 1251, 682,
1819 : 569, 682, 961, 1024, 682, 909, 1024, 909, 1024, 909, 682, 1024,
1820 : 1024, 569, 569, 1024, 569, 1593, 1024, 1024, 1024, 1024, 682, 797,
1821 : 569, 1024, 1024, 1479, 1024, 1024, 909, 983, 410, 983, 1108, 0,
1822 : 1024, 2048, 682, 1024, 909, 2048, 1024, 1024, 682, 2048, 1139, 682,
1823 : 1821, 2048, 1251, 2048, 2048, 682, 682, 909, 909, 717, 1024, 2048,
1824 : 682, 2007, 797, 682, 1479, 2048, 909, 1479, 512, 682, 1024, 1024,
1825 : 1024, 1024, 410, 1024, 682, 1556, 565, 1024, 1155, 682, 1556, 1024,
1826 : 819, 1124, 614, 614, 682, 1180, 928, 682, 682, 614, 635, 1024,
1827 : 1536, 1536, 1536, 909, 1479, 1479, 1479, 1479, 1479, 1479, 1821, 1366,
1828 : 1251, 1251, 1251, 1251, 682, 682, 682, 682, 1479, 1479, 1479, 1479,
1829 : 1479, 1479, 1479, 1155, 1479, 1479, 1479, 1479, 1479, 1479, 1139, 1024,
1830 : 909, 909, 909, 909, 909, 909, 1366, 909, 909, 909, 909, 909,
1831 : 569, 569, 569, 569, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1124,
1832 : 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024};
1833 :
1834 11 : const GUInt16 *panCharacterWidths = nullptr;
1835 :
1836 22 : if (STARTS_WITH_CI(osFont, "times") ||
1837 11 : osFont.find("Serif", 0) != std::string::npos)
1838 : {
1839 0 : if (bBold && bItalic)
1840 0 : osFont = "Times-BoldItalic";
1841 0 : else if (bBold)
1842 0 : osFont = "Times-Bold";
1843 0 : else if (bItalic)
1844 0 : osFont = "Times-Italic";
1845 : else
1846 0 : osFont = "Times-Roman";
1847 :
1848 0 : panCharacterWidths = anTimesCharWidths;
1849 0 : dfHeight = dfSize * 1356.0 / 2048;
1850 : }
1851 22 : else if (STARTS_WITH_CI(osFont, "courier") ||
1852 11 : osFont.find("Mono", 0) != std::string::npos)
1853 : {
1854 0 : if (bBold && bItalic)
1855 0 : osFont = "Courier-BoldOblique";
1856 0 : else if (bBold)
1857 0 : osFont = "Courier-Bold";
1858 0 : else if (bItalic)
1859 0 : osFont = "Courier-Oblique";
1860 : else
1861 0 : osFont = "Courier";
1862 :
1863 0 : dfHeight = dfSize * 1170.0 / 2048;
1864 : }
1865 : else
1866 : {
1867 11 : if (bBold && bItalic)
1868 0 : osFont = "Helvetica-BoldOblique";
1869 11 : else if (bBold)
1870 0 : osFont = "Helvetica-Bold";
1871 11 : else if (bItalic)
1872 0 : osFont = "Helvetica-Oblique";
1873 : else
1874 11 : osFont = "Helvetica";
1875 :
1876 11 : panCharacterWidths = anHelveticaCharWidths;
1877 11 : dfHeight = dfSize * 1467.0 / 2048;
1878 : }
1879 :
1880 11 : dfWidth = 0.0;
1881 110 : for (const char &ch : osText)
1882 : {
1883 99 : const int nCh = static_cast<int>(ch);
1884 99 : if (nCh < 32)
1885 0 : continue;
1886 :
1887 198 : dfWidth +=
1888 99 : (panCharacterWidths ? panCharacterWidths[nCh - 32]
1889 : : 1229); // Courier's fixed character width
1890 : }
1891 11 : dfWidth *= dfSize / 2048;
1892 11 : }
1893 :
1894 : /************************************************************************/
1895 : /* GetObjectStyle() */
1896 : /************************************************************************/
1897 :
1898 163 : void GDALPDFBaseWriter::GetObjectStyle(
1899 : const char *pszStyleString, OGRFeatureH hFeat, const double adfMatrix[4],
1900 : std::map<CPLString, GDALPDFImageDesc> oMapSymbolFilenameToDesc,
1901 : ObjectStyle &os)
1902 : {
1903 163 : OGRStyleMgrH hSM = OGR_SM_Create(nullptr);
1904 163 : if (pszStyleString)
1905 0 : OGR_SM_InitStyleString(hSM, pszStyleString);
1906 : else
1907 163 : OGR_SM_InitFromFeature(hSM, hFeat);
1908 163 : int nCount = OGR_SM_GetPartCount(hSM, nullptr);
1909 257 : for (int iPart = 0; iPart < nCount; iPart++)
1910 : {
1911 94 : OGRStyleToolH hTool = OGR_SM_GetPart(hSM, iPart, nullptr);
1912 94 : if (hTool)
1913 : {
1914 : // Figure out how to involve adfMatrix[3] here and below
1915 92 : OGR_ST_SetUnit(hTool, OGRSTUMM, 1000.0 / adfMatrix[1]);
1916 92 : if (OGR_ST_GetType(hTool) == OGRSTCPen)
1917 : {
1918 4 : os.bHasPenBrushOrSymbol = true;
1919 :
1920 4 : int bIsNull = TRUE;
1921 : const char *pszColor =
1922 4 : OGR_ST_GetParamStr(hTool, OGRSTPenColor, &bIsNull);
1923 4 : if (pszColor && !bIsNull)
1924 : {
1925 4 : unsigned int nRed = 0;
1926 4 : unsigned int nGreen = 0;
1927 4 : unsigned int nBlue = 0;
1928 4 : unsigned int nAlpha = 255;
1929 4 : int nVals = sscanf(pszColor, "#%2x%2x%2x%2x", &nRed,
1930 : &nGreen, &nBlue, &nAlpha);
1931 4 : if (nVals >= 3)
1932 : {
1933 4 : os.nPenR = nRed;
1934 4 : os.nPenG = nGreen;
1935 4 : os.nPenB = nBlue;
1936 4 : if (nVals == 4)
1937 0 : os.nPenA = nAlpha;
1938 : }
1939 : }
1940 :
1941 : const char *pszDash =
1942 4 : OGR_ST_GetParamStr(hTool, OGRSTPenPattern, &bIsNull);
1943 4 : if (pszDash && !bIsNull)
1944 : {
1945 2 : char **papszTokens = CSLTokenizeString2(pszDash, " ", 0);
1946 2 : int nTokens = CSLCount(papszTokens);
1947 2 : if ((nTokens % 2) == 0)
1948 : {
1949 6 : for (int i = 0; i < nTokens; i++)
1950 : {
1951 4 : double dfElement = CPLAtof(papszTokens[i]);
1952 4 : dfElement *= adfMatrix[1]; // should involve
1953 : // adfMatrix[3] too
1954 4 : os.osDashArray += CPLSPrintf("%f ", dfElement);
1955 : }
1956 : }
1957 2 : CSLDestroy(papszTokens);
1958 : }
1959 :
1960 : // OGRSTUnitId eUnit = OGR_ST_GetUnit(hTool);
1961 : double dfWidth =
1962 4 : OGR_ST_GetParamDbl(hTool, OGRSTPenWidth, &bIsNull);
1963 4 : if (!bIsNull)
1964 4 : os.dfPenWidth = dfWidth;
1965 : }
1966 88 : else if (OGR_ST_GetType(hTool) == OGRSTCBrush)
1967 : {
1968 2 : os.bHasPenBrushOrSymbol = true;
1969 :
1970 : int bIsNull;
1971 : const char *pszColor =
1972 2 : OGR_ST_GetParamStr(hTool, OGRSTBrushFColor, &bIsNull);
1973 2 : if (pszColor)
1974 : {
1975 2 : unsigned int nRed = 0;
1976 2 : unsigned int nGreen = 0;
1977 2 : unsigned int nBlue = 0;
1978 2 : unsigned int nAlpha = 255;
1979 2 : int nVals = sscanf(pszColor, "#%2x%2x%2x%2x", &nRed,
1980 : &nGreen, &nBlue, &nAlpha);
1981 2 : if (nVals >= 3)
1982 : {
1983 2 : os.nBrushR = nRed;
1984 2 : os.nBrushG = nGreen;
1985 2 : os.nBrushB = nBlue;
1986 2 : if (nVals == 4)
1987 0 : os.nBrushA = nAlpha;
1988 : }
1989 : }
1990 : }
1991 86 : else if (OGR_ST_GetType(hTool) == OGRSTCLabel)
1992 : {
1993 : int bIsNull;
1994 : const char *pszStr =
1995 13 : OGR_ST_GetParamStr(hTool, OGRSTLabelTextString, &bIsNull);
1996 13 : if (pszStr)
1997 : {
1998 13 : os.osLabelText = pszStr;
1999 :
2000 : /* If the text is of the form {stuff}, then it means we want
2001 : * to fetch */
2002 : /* the value of the field "stuff" in the feature */
2003 19 : if (!os.osLabelText.empty() && os.osLabelText[0] == '{' &&
2004 6 : os.osLabelText.back() == '}')
2005 : {
2006 6 : os.osLabelText = pszStr + 1;
2007 6 : os.osLabelText.resize(os.osLabelText.size() - 1);
2008 :
2009 : int nIdxField =
2010 6 : OGR_F_GetFieldIndex(hFeat, os.osLabelText);
2011 6 : if (nIdxField >= 0)
2012 : os.osLabelText =
2013 6 : OGR_F_GetFieldAsString(hFeat, nIdxField);
2014 : else
2015 0 : os.osLabelText = "";
2016 : }
2017 : }
2018 :
2019 : const char *pszColor =
2020 13 : OGR_ST_GetParamStr(hTool, OGRSTLabelFColor, &bIsNull);
2021 13 : if (pszColor && !bIsNull)
2022 : {
2023 2 : unsigned int nRed = 0;
2024 2 : unsigned int nGreen = 0;
2025 2 : unsigned int nBlue = 0;
2026 2 : unsigned int nAlpha = 255;
2027 2 : int nVals = sscanf(pszColor, "#%2x%2x%2x%2x", &nRed,
2028 : &nGreen, &nBlue, &nAlpha);
2029 2 : if (nVals >= 3)
2030 : {
2031 2 : os.nTextR = nRed;
2032 2 : os.nTextG = nGreen;
2033 2 : os.nTextB = nBlue;
2034 2 : if (nVals == 4)
2035 2 : os.nTextA = nAlpha;
2036 : }
2037 : }
2038 :
2039 : pszStr =
2040 13 : OGR_ST_GetParamStr(hTool, OGRSTLabelFontName, &bIsNull);
2041 13 : if (pszStr && !bIsNull)
2042 2 : os.osTextFont = pszStr;
2043 :
2044 : double dfVal =
2045 13 : OGR_ST_GetParamDbl(hTool, OGRSTLabelSize, &bIsNull);
2046 13 : if (!bIsNull)
2047 7 : os.dfTextSize = dfVal;
2048 :
2049 13 : dfVal = OGR_ST_GetParamDbl(hTool, OGRSTLabelAngle, &bIsNull);
2050 13 : if (!bIsNull)
2051 8 : os.dfTextAngle = dfVal * M_PI / 180.0;
2052 :
2053 13 : dfVal = OGR_ST_GetParamDbl(hTool, OGRSTLabelStretch, &bIsNull);
2054 13 : if (!bIsNull)
2055 0 : os.dfTextStretch = dfVal / 100.0;
2056 :
2057 13 : dfVal = OGR_ST_GetParamDbl(hTool, OGRSTLabelDx, &bIsNull);
2058 13 : if (!bIsNull)
2059 6 : os.dfTextDx = dfVal;
2060 :
2061 13 : dfVal = OGR_ST_GetParamDbl(hTool, OGRSTLabelDy, &bIsNull);
2062 13 : if (!bIsNull)
2063 6 : os.dfTextDy = dfVal;
2064 :
2065 : int nVal =
2066 13 : OGR_ST_GetParamNum(hTool, OGRSTLabelAnchor, &bIsNull);
2067 13 : if (!bIsNull)
2068 6 : os.nTextAnchor = nVal;
2069 :
2070 13 : nVal = OGR_ST_GetParamNum(hTool, OGRSTLabelBold, &bIsNull);
2071 13 : if (!bIsNull)
2072 0 : os.bTextBold = (nVal != 0);
2073 :
2074 13 : nVal = OGR_ST_GetParamNum(hTool, OGRSTLabelItalic, &bIsNull);
2075 13 : if (!bIsNull)
2076 0 : os.bTextItalic = (nVal != 0);
2077 : }
2078 73 : else if (OGR_ST_GetType(hTool) == OGRSTCSymbol)
2079 : {
2080 73 : os.bHasPenBrushOrSymbol = true;
2081 :
2082 : int bIsNull;
2083 : const char *pszSymbolId =
2084 73 : OGR_ST_GetParamStr(hTool, OGRSTSymbolId, &bIsNull);
2085 73 : if (pszSymbolId && !bIsNull)
2086 : {
2087 73 : os.osSymbolId = pszSymbolId;
2088 :
2089 73 : if (strstr(pszSymbolId, "ogr-sym-") == nullptr)
2090 : {
2091 7 : if (oMapSymbolFilenameToDesc.find(os.osSymbolId) ==
2092 14 : oMapSymbolFilenameToDesc.end())
2093 : {
2094 7 : CPLPushErrorHandler(CPLQuietErrorHandler);
2095 : GDALDatasetH hImageDS =
2096 7 : GDALOpen(os.osSymbolId, GA_ReadOnly);
2097 7 : CPLPopErrorHandler();
2098 7 : if (hImageDS != nullptr)
2099 : {
2100 7 : os.nImageWidth = GDALGetRasterXSize(hImageDS);
2101 7 : os.nImageHeight = GDALGetRasterYSize(hImageDS);
2102 :
2103 : os.nImageSymbolId = WriteBlock(
2104 : GDALDataset::FromHandle(hImageDS), 0, 0,
2105 : os.nImageWidth, os.nImageHeight,
2106 7 : GDALPDFObjectNum(), COMPRESS_DEFAULT, 0, -1,
2107 7 : nullptr, nullptr, nullptr);
2108 7 : GDALClose(hImageDS);
2109 : }
2110 :
2111 7 : GDALPDFImageDesc oDesc;
2112 7 : oDesc.nImageId = os.nImageSymbolId;
2113 7 : oDesc.dfXOff = 0;
2114 7 : oDesc.dfYOff = 0;
2115 7 : oDesc.dfXSize = os.nImageWidth;
2116 7 : oDesc.dfYSize = os.nImageHeight;
2117 7 : oMapSymbolFilenameToDesc[os.osSymbolId] = oDesc;
2118 : }
2119 : else
2120 : {
2121 : const GDALPDFImageDesc &oDesc =
2122 0 : oMapSymbolFilenameToDesc[os.osSymbolId];
2123 0 : os.nImageSymbolId = oDesc.nImageId;
2124 0 : os.nImageWidth = static_cast<int>(oDesc.dfXSize);
2125 0 : os.nImageHeight = static_cast<int>(oDesc.dfYSize);
2126 : }
2127 : }
2128 : }
2129 :
2130 : double dfVal =
2131 73 : OGR_ST_GetParamDbl(hTool, OGRSTSymbolSize, &bIsNull);
2132 73 : if (!bIsNull)
2133 : {
2134 67 : os.dfSymbolSize = dfVal;
2135 : }
2136 :
2137 : const char *pszColor =
2138 73 : OGR_ST_GetParamStr(hTool, OGRSTSymbolColor, &bIsNull);
2139 73 : if (pszColor && !bIsNull)
2140 : {
2141 72 : unsigned int nRed = 0;
2142 72 : unsigned int nGreen = 0;
2143 72 : unsigned int nBlue = 0;
2144 72 : unsigned int nAlpha = 255;
2145 72 : int nVals = sscanf(pszColor, "#%2x%2x%2x%2x", &nRed,
2146 : &nGreen, &nBlue, &nAlpha);
2147 72 : if (nVals >= 3)
2148 : {
2149 72 : os.bSymbolColorDefined = TRUE;
2150 72 : os.nSymbolR = nRed;
2151 72 : os.nSymbolG = nGreen;
2152 72 : os.nSymbolB = nBlue;
2153 72 : if (nVals == 4)
2154 1 : os.nSymbolA = nAlpha;
2155 : }
2156 : }
2157 : }
2158 :
2159 92 : OGR_ST_Destroy(hTool);
2160 : }
2161 : }
2162 163 : OGR_SM_Destroy(hSM);
2163 :
2164 163 : OGRGeometryH hGeom = OGR_F_GetGeometryRef(hFeat);
2165 263 : if (wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint &&
2166 100 : os.bSymbolColorDefined)
2167 : {
2168 72 : os.nPenR = os.nSymbolR;
2169 72 : os.nPenG = os.nSymbolG;
2170 72 : os.nPenB = os.nSymbolB;
2171 72 : os.nPenA = os.nSymbolA;
2172 72 : os.nBrushR = os.nSymbolR;
2173 72 : os.nBrushG = os.nSymbolG;
2174 72 : os.nBrushB = os.nSymbolB;
2175 72 : os.nBrushA = os.nSymbolA;
2176 : }
2177 163 : }
2178 :
2179 : /************************************************************************/
2180 : /* ComputeIntBBox() */
2181 : /************************************************************************/
2182 :
2183 147 : void GDALPDFBaseWriter::ComputeIntBBox(
2184 : OGRGeometryH hGeom, const OGREnvelope &sEnvelope, const double adfMatrix[4],
2185 : const GDALPDFWriter::ObjectStyle &os, double dfRadius, int &bboxXMin,
2186 : int &bboxYMin, int &bboxXMax, int &bboxYMax)
2187 : {
2188 233 : if (wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint &&
2189 86 : os.nImageSymbolId.toBool())
2190 : {
2191 6 : const double dfSemiWidth =
2192 6 : (os.nImageWidth >= os.nImageHeight)
2193 6 : ? dfRadius
2194 0 : : dfRadius * os.nImageWidth / os.nImageHeight;
2195 6 : const double dfSemiHeight =
2196 6 : (os.nImageWidth >= os.nImageHeight)
2197 6 : ? dfRadius * os.nImageHeight / os.nImageWidth
2198 : : dfRadius;
2199 6 : bboxXMin = static_cast<int>(
2200 6 : floor(sEnvelope.MinX * adfMatrix[1] + adfMatrix[0] - dfSemiWidth));
2201 6 : bboxYMin = static_cast<int>(
2202 6 : floor(sEnvelope.MinY * adfMatrix[3] + adfMatrix[2] - dfSemiHeight));
2203 6 : bboxXMax = static_cast<int>(
2204 6 : ceil(sEnvelope.MaxX * adfMatrix[1] + adfMatrix[0] + dfSemiWidth));
2205 6 : bboxYMax = static_cast<int>(
2206 6 : ceil(sEnvelope.MaxY * adfMatrix[3] + adfMatrix[2] + dfSemiHeight));
2207 : }
2208 : else
2209 : {
2210 141 : double dfMargin = os.dfPenWidth;
2211 141 : if (wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint)
2212 : {
2213 80 : if (os.osSymbolId == "ogr-sym-6" || os.osSymbolId == "ogr-sym-7")
2214 : {
2215 12 : const double dfSqrt3 = 1.73205080757;
2216 12 : dfMargin += dfRadius * 2 * dfSqrt3 / 3;
2217 : }
2218 : else
2219 68 : dfMargin += dfRadius;
2220 : }
2221 141 : bboxXMin = static_cast<int>(
2222 141 : floor(sEnvelope.MinX * adfMatrix[1] + adfMatrix[0] - dfMargin));
2223 141 : bboxYMin = static_cast<int>(
2224 141 : floor(sEnvelope.MinY * adfMatrix[3] + adfMatrix[2] - dfMargin));
2225 141 : bboxXMax = static_cast<int>(
2226 141 : ceil(sEnvelope.MaxX * adfMatrix[1] + adfMatrix[0] + dfMargin));
2227 141 : bboxYMax = static_cast<int>(
2228 141 : ceil(sEnvelope.MaxY * adfMatrix[3] + adfMatrix[2] + dfMargin));
2229 : }
2230 147 : }
2231 :
2232 : /************************************************************************/
2233 : /* WriteLink() */
2234 : /************************************************************************/
2235 :
2236 147 : GDALPDFObjectNum GDALPDFBaseWriter::WriteLink(OGRFeatureH hFeat,
2237 : const char *pszOGRLinkField,
2238 : const double adfMatrix[4],
2239 : int bboxXMin, int bboxYMin,
2240 : int bboxXMax, int bboxYMax)
2241 : {
2242 147 : GDALPDFObjectNum nAnnotId;
2243 147 : int iField = -1;
2244 147 : const char *pszLinkVal = nullptr;
2245 232 : if (pszOGRLinkField != nullptr &&
2246 85 : (iField = OGR_FD_GetFieldIndex(OGR_F_GetDefnRef(hFeat),
2247 85 : pszOGRLinkField)) >= 0 &&
2248 239 : OGR_F_IsFieldSetAndNotNull(hFeat, iField) &&
2249 7 : strcmp((pszLinkVal = OGR_F_GetFieldAsString(hFeat, iField)), "") != 0)
2250 : {
2251 7 : nAnnotId = AllocNewObject();
2252 7 : StartObj(nAnnotId);
2253 : {
2254 7 : GDALPDFDictionaryRW oDict;
2255 7 : oDict.Add("Type", GDALPDFObjectRW::CreateName("Annot"));
2256 7 : oDict.Add("Subtype", GDALPDFObjectRW::CreateName("Link"));
2257 7 : oDict.Add("Rect", &(new GDALPDFArrayRW())
2258 7 : ->Add(bboxXMin)
2259 7 : .Add(bboxYMin)
2260 7 : .Add(bboxXMax)
2261 7 : .Add(bboxYMax));
2262 7 : oDict.Add("A", &(new GDALPDFDictionaryRW())
2263 7 : ->Add("S", GDALPDFObjectRW::CreateName("URI"))
2264 7 : .Add("URI", pszLinkVal));
2265 : oDict.Add("BS",
2266 7 : &(new GDALPDFDictionaryRW())
2267 7 : ->Add("Type", GDALPDFObjectRW::CreateName("Border"))
2268 7 : .Add("S", GDALPDFObjectRW::CreateName("S"))
2269 7 : .Add("W", 0));
2270 7 : oDict.Add("Border", &(new GDALPDFArrayRW())->Add(0).Add(0).Add(0));
2271 7 : oDict.Add("H", GDALPDFObjectRW::CreateName("I"));
2272 :
2273 7 : OGRGeometryH hGeom = OGR_F_GetGeometryRef(hFeat);
2274 14 : if (wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPolygon &&
2275 7 : OGR_G_GetGeometryCount(hGeom) == 1)
2276 : {
2277 7 : OGRGeometryH hSubGeom = OGR_G_GetGeometryRef(hGeom, 0);
2278 7 : int nPoints = OGR_G_GetPointCount(hSubGeom);
2279 7 : if (nPoints == 4 || nPoints == 5)
2280 : {
2281 14 : std::vector<double> adfX, adfY;
2282 42 : for (int i = 0; i < nPoints; i++)
2283 : {
2284 35 : double dfX = OGR_G_GetX(hSubGeom, i) * adfMatrix[1] +
2285 35 : adfMatrix[0];
2286 35 : double dfY = OGR_G_GetY(hSubGeom, i) * adfMatrix[3] +
2287 35 : adfMatrix[2];
2288 35 : adfX.push_back(dfX);
2289 35 : adfY.push_back(dfY);
2290 : }
2291 7 : if (nPoints == 4)
2292 : {
2293 0 : oDict.Add("QuadPoints", &(new GDALPDFArrayRW())
2294 0 : ->Add(adfX[0])
2295 0 : .Add(adfY[0])
2296 0 : .Add(adfX[1])
2297 0 : .Add(adfY[1])
2298 0 : .Add(adfX[2])
2299 0 : .Add(adfY[2])
2300 0 : .Add(adfX[0])
2301 0 : .Add(adfY[0]));
2302 : }
2303 7 : else if (nPoints == 5)
2304 : {
2305 7 : oDict.Add("QuadPoints", &(new GDALPDFArrayRW())
2306 7 : ->Add(adfX[0])
2307 7 : .Add(adfY[0])
2308 7 : .Add(adfX[1])
2309 7 : .Add(adfY[1])
2310 7 : .Add(adfX[2])
2311 7 : .Add(adfY[2])
2312 7 : .Add(adfX[3])
2313 7 : .Add(adfY[3]));
2314 : }
2315 : }
2316 : }
2317 :
2318 7 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
2319 : }
2320 7 : EndObj();
2321 : }
2322 147 : return nAnnotId;
2323 : }
2324 :
2325 : /************************************************************************/
2326 : /* GenerateDrawingStream() */
2327 : /************************************************************************/
2328 :
2329 154 : CPLString GDALPDFBaseWriter::GenerateDrawingStream(OGRGeometryH hGeom,
2330 : const double adfMatrix[4],
2331 : ObjectStyle &os,
2332 : double dfRadius)
2333 : {
2334 154 : CPLString osDS;
2335 :
2336 154 : if (!os.nImageSymbolId.toBool())
2337 : {
2338 294 : osDS += CPLOPrintf("%f w\n"
2339 : "0 J\n"
2340 : "0 j\n"
2341 : "10 M\n"
2342 : "[%s]0 d\n",
2343 147 : os.dfPenWidth, os.osDashArray.c_str());
2344 :
2345 147 : osDS += CPLOPrintf("%f %f %f RG\n", os.nPenR / 255.0, os.nPenG / 255.0,
2346 147 : os.nPenB / 255.0);
2347 147 : osDS += CPLOPrintf("%f %f %f rg\n", os.nBrushR / 255.0,
2348 147 : os.nBrushG / 255.0, os.nBrushB / 255.0);
2349 : }
2350 :
2351 308 : if ((os.bHasPenBrushOrSymbol || os.osLabelText.empty()) &&
2352 154 : wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint)
2353 : {
2354 91 : double dfX = OGR_G_GetX(hGeom, 0) * adfMatrix[1] + adfMatrix[0];
2355 91 : double dfY = OGR_G_GetY(hGeom, 0) * adfMatrix[3] + adfMatrix[2];
2356 :
2357 91 : if (os.nImageSymbolId.toBool())
2358 : {
2359 7 : const double dfSemiWidth =
2360 7 : (os.nImageWidth >= os.nImageHeight)
2361 7 : ? dfRadius
2362 0 : : dfRadius * os.nImageWidth / os.nImageHeight;
2363 7 : const double dfSemiHeight =
2364 7 : (os.nImageWidth >= os.nImageHeight)
2365 7 : ? dfRadius * os.nImageHeight / os.nImageWidth
2366 : : dfRadius;
2367 14 : osDS += CPLOPrintf("%f 0 0 %f %f %f cm\n", 2 * dfSemiWidth,
2368 : 2 * dfSemiHeight, dfX - dfSemiWidth,
2369 7 : dfY - dfSemiHeight);
2370 7 : osDS += CPLOPrintf("/SymImage%d Do\n", os.nImageSymbolId.toInt());
2371 : }
2372 84 : else if (os.osSymbolId == "")
2373 20 : os.osSymbolId = "ogr-sym-3"; /* symbol by default */
2374 122 : else if (!(os.osSymbolId == "ogr-sym-0" ||
2375 58 : os.osSymbolId == "ogr-sym-1" ||
2376 48 : os.osSymbolId == "ogr-sym-2" ||
2377 42 : os.osSymbolId == "ogr-sym-3" ||
2378 36 : os.osSymbolId == "ogr-sym-4" ||
2379 30 : os.osSymbolId == "ogr-sym-5" ||
2380 24 : os.osSymbolId == "ogr-sym-6" ||
2381 18 : os.osSymbolId == "ogr-sym-7" ||
2382 12 : os.osSymbolId == "ogr-sym-8" ||
2383 6 : os.osSymbolId == "ogr-sym-9"))
2384 : {
2385 0 : CPLDebug("PDF", "Unhandled symbol id : %s. Using ogr-sym-3 instead",
2386 : os.osSymbolId.c_str());
2387 0 : os.osSymbolId = "ogr-sym-3";
2388 : }
2389 :
2390 91 : if (os.osSymbolId == "ogr-sym-0") /* cross (+) */
2391 : {
2392 6 : osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius, dfY);
2393 6 : osDS += CPLOPrintf("%f %f l\n", dfX + dfRadius, dfY);
2394 6 : osDS += CPLOPrintf("%f %f m\n", dfX, dfY - dfRadius);
2395 6 : osDS += CPLOPrintf("%f %f l\n", dfX, dfY + dfRadius);
2396 6 : osDS += CPLOPrintf("S\n");
2397 : }
2398 85 : else if (os.osSymbolId == "ogr-sym-1") /* diagcross (X) */
2399 : {
2400 10 : osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius, dfY - dfRadius);
2401 10 : osDS += CPLOPrintf("%f %f l\n", dfX + dfRadius, dfY + dfRadius);
2402 10 : osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius, dfY + dfRadius);
2403 10 : osDS += CPLOPrintf("%f %f l\n", dfX + dfRadius, dfY - dfRadius);
2404 10 : osDS += CPLOPrintf("S\n");
2405 : }
2406 144 : else if (os.osSymbolId == "ogr-sym-2" ||
2407 69 : os.osSymbolId == "ogr-sym-3") /* circle */
2408 : {
2409 : /* See http://www.whizkidtech.redprince.net/bezier/circle/kappa/ */
2410 32 : const double dfKappa = 0.5522847498;
2411 :
2412 32 : osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius, dfY);
2413 : osDS +=
2414 32 : CPLOPrintf("%f %f %f %f %f %f c\n", dfX - dfRadius,
2415 32 : dfY - dfRadius * dfKappa, dfX - dfRadius * dfKappa,
2416 32 : dfY - dfRadius, dfX, dfY - dfRadius);
2417 : osDS +=
2418 32 : CPLOPrintf("%f %f %f %f %f %f c\n", dfX + dfRadius * dfKappa,
2419 : dfY - dfRadius, dfX + dfRadius,
2420 32 : dfY - dfRadius * dfKappa, dfX + dfRadius, dfY);
2421 : osDS +=
2422 32 : CPLOPrintf("%f %f %f %f %f %f c\n", dfX + dfRadius,
2423 32 : dfY + dfRadius * dfKappa, dfX + dfRadius * dfKappa,
2424 32 : dfY + dfRadius, dfX, dfY + dfRadius);
2425 : osDS +=
2426 32 : CPLOPrintf("%f %f %f %f %f %f c\n", dfX - dfRadius * dfKappa,
2427 : dfY + dfRadius, dfX - dfRadius,
2428 32 : dfY + dfRadius * dfKappa, dfX - dfRadius, dfY);
2429 32 : if (os.osSymbolId == "ogr-sym-2")
2430 6 : osDS += CPLOPrintf("s\n"); /* not filled */
2431 : else
2432 26 : osDS += CPLOPrintf("b*\n"); /* filled */
2433 : }
2434 80 : else if (os.osSymbolId == "ogr-sym-4" ||
2435 37 : os.osSymbolId == "ogr-sym-5") /* square */
2436 : {
2437 12 : osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius, dfY + dfRadius);
2438 12 : osDS += CPLOPrintf("%f %f l\n", dfX + dfRadius, dfY + dfRadius);
2439 12 : osDS += CPLOPrintf("%f %f l\n", dfX + dfRadius, dfY - dfRadius);
2440 12 : osDS += CPLOPrintf("%f %f l\n", dfX - dfRadius, dfY - dfRadius);
2441 12 : if (os.osSymbolId == "ogr-sym-4")
2442 6 : osDS += CPLOPrintf("s\n"); /* not filled */
2443 : else
2444 6 : osDS += CPLOPrintf("b*\n"); /* filled */
2445 : }
2446 56 : else if (os.osSymbolId == "ogr-sym-6" ||
2447 25 : os.osSymbolId == "ogr-sym-7") /* triangle */
2448 : {
2449 12 : const double dfSqrt3 = 1.73205080757;
2450 12 : osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius,
2451 12 : dfY - dfRadius * dfSqrt3 / 3);
2452 : osDS +=
2453 12 : CPLOPrintf("%f %f l\n", dfX, dfY + 2 * dfRadius * dfSqrt3 / 3);
2454 12 : osDS += CPLOPrintf("%f %f l\n", dfX + dfRadius,
2455 12 : dfY - dfRadius * dfSqrt3 / 3);
2456 12 : if (os.osSymbolId == "ogr-sym-6")
2457 6 : osDS += CPLOPrintf("s\n"); /* not filled */
2458 : else
2459 6 : osDS += CPLOPrintf("b*\n"); /* filled */
2460 : }
2461 32 : else if (os.osSymbolId == "ogr-sym-8" ||
2462 13 : os.osSymbolId == "ogr-sym-9") /* star */
2463 : {
2464 12 : const double dfSin18divSin126 = 0.38196601125;
2465 12 : osDS += CPLOPrintf("%f %f m\n", dfX, dfY + dfRadius);
2466 120 : for (int i = 1; i < 10; i++)
2467 : {
2468 108 : double dfFactor = ((i % 2) == 1) ? dfSin18divSin126 : 1.0;
2469 108 : osDS += CPLOPrintf("%f %f l\n",
2470 108 : dfX + cos(M_PI / 2 - i * M_PI * 36 / 180) *
2471 108 : dfRadius * dfFactor,
2472 108 : dfY + sin(M_PI / 2 - i * M_PI * 36 / 180) *
2473 108 : dfRadius * dfFactor);
2474 : }
2475 12 : if (os.osSymbolId == "ogr-sym-8")
2476 6 : osDS += CPLOPrintf("s\n"); /* not filled */
2477 : else
2478 6 : osDS += CPLOPrintf("b*\n"); /* filled */
2479 : }
2480 : }
2481 : else
2482 : {
2483 63 : DrawGeometry(osDS, hGeom, adfMatrix);
2484 : }
2485 :
2486 154 : return osDS;
2487 : }
2488 :
2489 : /************************************************************************/
2490 : /* WriteAttributes() */
2491 : /************************************************************************/
2492 :
2493 115 : GDALPDFObjectNum GDALPDFBaseWriter::WriteAttributes(
2494 : OGRFeatureH hFeat, const std::vector<CPLString> &aosIncludedFields,
2495 : const char *pszOGRDisplayField, int nMCID, const GDALPDFObjectNum &oParent,
2496 : const GDALPDFObjectNum &oPage, CPLString &osOutFeatureName)
2497 : {
2498 :
2499 115 : int iField = -1;
2500 115 : if (pszOGRDisplayField)
2501 : iField =
2502 17 : OGR_FD_GetFieldIndex(OGR_F_GetDefnRef(hFeat), pszOGRDisplayField);
2503 115 : if (iField >= 0)
2504 7 : osOutFeatureName = OGR_F_GetFieldAsString(hFeat, iField);
2505 : else
2506 : osOutFeatureName =
2507 108 : CPLSPrintf("feature" CPL_FRMT_GIB, OGR_F_GetFID(hFeat));
2508 :
2509 115 : auto nFeatureUserProperties = AllocNewObject();
2510 115 : StartObj(nFeatureUserProperties);
2511 :
2512 115 : GDALPDFDictionaryRW oDict;
2513 :
2514 115 : GDALPDFDictionaryRW *poDictA = new GDALPDFDictionaryRW();
2515 115 : oDict.Add("A", poDictA);
2516 115 : poDictA->Add("O", GDALPDFObjectRW::CreateName("UserProperties"));
2517 :
2518 115 : GDALPDFArrayRW *poArray = new GDALPDFArrayRW();
2519 495 : for (const auto &fieldName : aosIncludedFields)
2520 : {
2521 380 : int i = OGR_F_GetFieldIndex(hFeat, fieldName);
2522 380 : if (i >= 0 && OGR_F_IsFieldSetAndNotNull(hFeat, i))
2523 : {
2524 203 : OGRFieldDefnH hFDefn = OGR_F_GetFieldDefnRef(hFeat, i);
2525 203 : GDALPDFDictionaryRW *poKV = new GDALPDFDictionaryRW();
2526 203 : poKV->Add("N", OGR_Fld_GetNameRef(hFDefn));
2527 203 : if (OGR_Fld_GetType(hFDefn) == OFTInteger)
2528 49 : poKV->Add("V", OGR_F_GetFieldAsInteger(hFeat, i));
2529 154 : else if (OGR_Fld_GetType(hFDefn) == OFTReal)
2530 33 : poKV->Add("V", OGR_F_GetFieldAsDouble(hFeat, i));
2531 : else
2532 121 : poKV->Add("V", OGR_F_GetFieldAsString(hFeat, i));
2533 203 : poArray->Add(poKV);
2534 : }
2535 : }
2536 :
2537 115 : poDictA->Add("P", poArray);
2538 :
2539 115 : oDict.Add("K", nMCID);
2540 115 : oDict.Add("P", oParent, 0);
2541 115 : oDict.Add("Pg", oPage, 0);
2542 115 : oDict.Add("S", GDALPDFObjectRW::CreateName("feature"));
2543 115 : oDict.Add("T", osOutFeatureName);
2544 :
2545 115 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
2546 :
2547 115 : EndObj();
2548 :
2549 230 : return nFeatureUserProperties;
2550 : }
2551 :
2552 : /************************************************************************/
2553 : /* WriteLabel() */
2554 : /************************************************************************/
2555 :
2556 11 : GDALPDFObjectNum GDALPDFBaseWriter::WriteLabel(
2557 : OGRGeometryH hGeom, const double adfMatrix[4], ObjectStyle &os,
2558 : PDFCompressMethod eStreamCompressMethod, double bboxXMin, double bboxYMin,
2559 : double bboxXMax, double bboxYMax)
2560 : {
2561 : /* -------------------------------------------------------------- */
2562 : /* Work out the text metrics for alignment purposes */
2563 : /* -------------------------------------------------------------- */
2564 : double dfWidth, dfHeight;
2565 11 : CalculateText(os.osLabelText, os.osTextFont, os.dfTextSize, os.bTextBold,
2566 11 : os.bTextItalic, dfWidth, dfHeight);
2567 11 : dfWidth *= os.dfTextStretch;
2568 :
2569 11 : if (os.nTextAnchor % 3 == 2) // horizontal center
2570 : {
2571 0 : os.dfTextDx -= (dfWidth / 2) * cos(os.dfTextAngle);
2572 0 : os.dfTextDy -= (dfWidth / 2) * sin(os.dfTextAngle);
2573 : }
2574 11 : else if (os.nTextAnchor % 3 == 0) // right
2575 : {
2576 0 : os.dfTextDx -= dfWidth * cos(os.dfTextAngle);
2577 0 : os.dfTextDy -= dfWidth * sin(os.dfTextAngle);
2578 : }
2579 :
2580 11 : if (os.nTextAnchor >= 4 && os.nTextAnchor <= 6) // vertical center
2581 : {
2582 6 : os.dfTextDx += (dfHeight / 2) * sin(os.dfTextAngle);
2583 6 : os.dfTextDy -= (dfHeight / 2) * cos(os.dfTextAngle);
2584 : }
2585 5 : else if (os.nTextAnchor >= 7 && os.nTextAnchor <= 9) // top
2586 : {
2587 0 : os.dfTextDx += dfHeight * sin(os.dfTextAngle);
2588 0 : os.dfTextDy -= dfHeight * cos(os.dfTextAngle);
2589 : }
2590 : // modes 10,11,12 (baseline) unsupported for the time being
2591 :
2592 : /* -------------------------------------------------------------- */
2593 : /* Write object dictionary */
2594 : /* -------------------------------------------------------------- */
2595 11 : auto nObjectId = AllocNewObject();
2596 11 : GDALPDFDictionaryRW oDict;
2597 :
2598 11 : oDict.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
2599 11 : .Add("BBox", &((new GDALPDFArrayRW())->Add(bboxXMin).Add(bboxYMin))
2600 11 : .Add(bboxXMax)
2601 11 : .Add(bboxYMax))
2602 11 : .Add("Subtype", GDALPDFObjectRW::CreateName("Form"));
2603 :
2604 11 : GDALPDFDictionaryRW *poResources = new GDALPDFDictionaryRW();
2605 :
2606 11 : if (os.nTextA != 255)
2607 : {
2608 2 : GDALPDFDictionaryRW *poGS1 = new GDALPDFDictionaryRW();
2609 2 : poGS1->Add("Type", GDALPDFObjectRW::CreateName("ExtGState"));
2610 2 : poGS1->Add("ca", (os.nTextA == 127 || os.nTextA == 128)
2611 : ? 0.5
2612 4 : : os.nTextA / 255.0);
2613 :
2614 2 : GDALPDFDictionaryRW *poExtGState = new GDALPDFDictionaryRW();
2615 2 : poExtGState->Add("GS1", poGS1);
2616 :
2617 2 : poResources->Add("ExtGState", poExtGState);
2618 : }
2619 :
2620 11 : GDALPDFDictionaryRW *poDictF1 = new GDALPDFDictionaryRW();
2621 11 : poDictF1->Add("Type", GDALPDFObjectRW::CreateName("Font"));
2622 11 : poDictF1->Add("BaseFont", GDALPDFObjectRW::CreateName(os.osTextFont));
2623 11 : poDictF1->Add("Encoding", GDALPDFObjectRW::CreateName("WinAnsiEncoding"));
2624 11 : poDictF1->Add("Subtype", GDALPDFObjectRW::CreateName("Type1"));
2625 :
2626 11 : GDALPDFDictionaryRW *poDictFont = new GDALPDFDictionaryRW();
2627 11 : poDictFont->Add("F1", poDictF1);
2628 11 : poResources->Add("Font", poDictFont);
2629 :
2630 11 : oDict.Add("Resources", poResources);
2631 :
2632 11 : StartObjWithStream(nObjectId, oDict,
2633 : eStreamCompressMethod != COMPRESS_NONE);
2634 :
2635 : /* -------------------------------------------------------------- */
2636 : /* Write object stream */
2637 : /* -------------------------------------------------------------- */
2638 :
2639 : double dfX =
2640 11 : OGR_G_GetX(hGeom, 0) * adfMatrix[1] + adfMatrix[0] + os.dfTextDx;
2641 : double dfY =
2642 11 : OGR_G_GetY(hGeom, 0) * adfMatrix[3] + adfMatrix[2] + os.dfTextDy;
2643 :
2644 11 : VSIFPrintfL(m_fp, "q\n");
2645 11 : VSIFPrintfL(m_fp, "BT\n");
2646 11 : if (os.nTextA != 255)
2647 : {
2648 2 : VSIFPrintfL(m_fp, "/GS1 gs\n");
2649 : }
2650 :
2651 11 : VSIFPrintfL(m_fp, "%f %f %f %f %f %f Tm\n",
2652 11 : cos(os.dfTextAngle) * adfMatrix[1] * os.dfTextStretch,
2653 11 : sin(os.dfTextAngle) * adfMatrix[3] * os.dfTextStretch,
2654 11 : -sin(os.dfTextAngle) * adfMatrix[1],
2655 11 : cos(os.dfTextAngle) * adfMatrix[3], dfX, dfY);
2656 :
2657 11 : VSIFPrintfL(m_fp, "%f %f %f rg\n", os.nTextR / 255.0, os.nTextG / 255.0,
2658 11 : os.nTextB / 255.0);
2659 : // The factor of adfMatrix[1] is introduced in the call to SetUnit near the
2660 : // top of this function. Because we are handling the 2D stretch correctly in
2661 : // Tm above, we don't need that factor here
2662 11 : VSIFPrintfL(m_fp, "/F1 %f Tf\n", os.dfTextSize / adfMatrix[1]);
2663 11 : VSIFPrintfL(m_fp, "(");
2664 110 : for (size_t i = 0; i < os.osLabelText.size(); i++)
2665 : {
2666 198 : if (os.osLabelText[i] == '(' || os.osLabelText[i] == ')' ||
2667 99 : os.osLabelText[i] == '\\')
2668 : {
2669 0 : VSIFPrintfL(m_fp, "\\%c", os.osLabelText[i]);
2670 : }
2671 : else
2672 : {
2673 99 : VSIFPrintfL(m_fp, "%c", os.osLabelText[i]);
2674 : }
2675 : }
2676 11 : VSIFPrintfL(m_fp, ") Tj\n");
2677 11 : VSIFPrintfL(m_fp, "ET\n");
2678 11 : VSIFPrintfL(m_fp, "Q");
2679 :
2680 11 : EndObjWithStream();
2681 :
2682 22 : return nObjectId;
2683 : }
2684 :
2685 : /************************************************************************/
2686 : /* WriteOGRFeature() */
2687 : /************************************************************************/
2688 :
2689 154 : int GDALPDFWriter::WriteOGRFeature(GDALPDFLayerDesc &osVectorDesc,
2690 : OGRFeatureH hFeat,
2691 : OGRCoordinateTransformationH hCT,
2692 : const char *pszOGRDisplayField,
2693 : const char *pszOGRLinkField,
2694 : int bWriteOGRAttributes, int &iObj)
2695 : {
2696 154 : GDALDataset *const poClippingDS = oPageContext.poClippingDS;
2697 154 : const int nHeight = poClippingDS->GetRasterYSize();
2698 154 : const double dfUserUnit = oPageContext.dfDPI * USER_UNIT_IN_INCH;
2699 : double adfGeoTransform[6];
2700 154 : poClippingDS->GetGeoTransform(adfGeoTransform);
2701 :
2702 : double adfMatrix[4];
2703 154 : adfMatrix[0] = -adfGeoTransform[0] / (adfGeoTransform[1] * dfUserUnit) +
2704 154 : oPageContext.sMargins.nLeft;
2705 154 : adfMatrix[1] = 1.0 / (adfGeoTransform[1] * dfUserUnit);
2706 154 : adfMatrix[2] = -(adfGeoTransform[3] + adfGeoTransform[5] * nHeight) /
2707 154 : (-adfGeoTransform[5] * dfUserUnit) +
2708 154 : oPageContext.sMargins.nBottom;
2709 154 : adfMatrix[3] = 1.0 / (-adfGeoTransform[5] * dfUserUnit);
2710 :
2711 154 : OGRGeometryH hGeom = OGR_F_GetGeometryRef(hFeat);
2712 154 : if (hGeom == nullptr)
2713 : {
2714 0 : return TRUE;
2715 : }
2716 :
2717 154 : OGREnvelope sEnvelope;
2718 :
2719 154 : if (hCT != nullptr)
2720 : {
2721 : /* Reproject */
2722 12 : if (OGR_G_Transform(hGeom, hCT) != OGRERR_NONE)
2723 : {
2724 2 : return TRUE;
2725 : }
2726 :
2727 12 : OGREnvelope sRasterEnvelope;
2728 12 : sRasterEnvelope.MinX = adfGeoTransform[0];
2729 12 : sRasterEnvelope.MinY =
2730 24 : adfGeoTransform[3] +
2731 12 : poClippingDS->GetRasterYSize() * adfGeoTransform[5];
2732 12 : sRasterEnvelope.MaxX =
2733 24 : adfGeoTransform[0] +
2734 12 : poClippingDS->GetRasterXSize() * adfGeoTransform[1];
2735 12 : sRasterEnvelope.MaxY = adfGeoTransform[3];
2736 :
2737 : // Check that the reprojected geometry intersects the raster envelope.
2738 12 : OGR_G_GetEnvelope(hGeom, &sEnvelope);
2739 12 : if (!(sRasterEnvelope.Intersects(sEnvelope)))
2740 : {
2741 2 : return TRUE;
2742 : }
2743 : }
2744 : else
2745 : {
2746 142 : OGR_G_GetEnvelope(hGeom, &sEnvelope);
2747 : }
2748 :
2749 : /* -------------------------------------------------------------- */
2750 : /* Get style */
2751 : /* -------------------------------------------------------------- */
2752 304 : ObjectStyle os;
2753 152 : GetObjectStyle(nullptr, hFeat, adfMatrix, m_oMapSymbolFilenameToDesc, os);
2754 :
2755 152 : double dfRadius = os.dfSymbolSize * dfUserUnit;
2756 :
2757 : // For a POINT with only a LABEL style string and non-empty text, we do not
2758 : // output any geometry other than the text itself.
2759 : const bool bLabelOnly =
2760 152 : wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint &&
2761 152 : !os.bHasPenBrushOrSymbol && !os.osLabelText.empty();
2762 :
2763 : /* -------------------------------------------------------------- */
2764 : /* Write object dictionary */
2765 : /* -------------------------------------------------------------- */
2766 152 : if (!bLabelOnly)
2767 : {
2768 146 : auto nObjectId = AllocNewObject();
2769 :
2770 146 : osVectorDesc.aIds.push_back(nObjectId);
2771 :
2772 : int bboxXMin, bboxYMin, bboxXMax, bboxYMax;
2773 146 : ComputeIntBBox(hGeom, sEnvelope, adfMatrix, os, dfRadius, bboxXMin,
2774 : bboxYMin, bboxXMax, bboxYMax);
2775 :
2776 : auto nLinkId = WriteLink(hFeat, pszOGRLinkField, adfMatrix, bboxXMin,
2777 146 : bboxYMin, bboxXMax, bboxYMax);
2778 146 : if (nLinkId.toBool())
2779 6 : oPageContext.anAnnotationsId.push_back(nLinkId);
2780 :
2781 292 : GDALPDFDictionaryRW oDict;
2782 146 : GDALPDFArrayRW *poBBOX = new GDALPDFArrayRW();
2783 146 : poBBOX->Add(bboxXMin).Add(bboxYMin).Add(bboxXMax).Add(bboxYMax);
2784 146 : oDict.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
2785 146 : .Add("BBox", poBBOX)
2786 146 : .Add("Subtype", GDALPDFObjectRW::CreateName("Form"));
2787 :
2788 146 : GDALPDFDictionaryRW *poGS1 = new GDALPDFDictionaryRW();
2789 146 : poGS1->Add("Type", GDALPDFObjectRW::CreateName("ExtGState"));
2790 146 : if (os.nPenA != 255)
2791 0 : poGS1->Add("CA", (os.nPenA == 127 || os.nPenA == 128)
2792 : ? 0.5
2793 0 : : os.nPenA / 255.0);
2794 146 : if (os.nBrushA != 255)
2795 0 : poGS1->Add("ca", (os.nBrushA == 127 || os.nBrushA == 128)
2796 : ? 0.5
2797 80 : : os.nBrushA / 255.0);
2798 :
2799 146 : GDALPDFDictionaryRW *poExtGState = new GDALPDFDictionaryRW();
2800 146 : poExtGState->Add("GS1", poGS1);
2801 :
2802 146 : GDALPDFDictionaryRW *poResources = new GDALPDFDictionaryRW();
2803 146 : poResources->Add("ExtGState", poExtGState);
2804 :
2805 146 : if (os.nImageSymbolId.toBool())
2806 : {
2807 6 : GDALPDFDictionaryRW *poDictXObject = new GDALPDFDictionaryRW();
2808 6 : poResources->Add("XObject", poDictXObject);
2809 :
2810 : poDictXObject->Add(
2811 : CPLSPrintf("SymImage%d", os.nImageSymbolId.toInt()),
2812 6 : os.nImageSymbolId, 0);
2813 : }
2814 :
2815 146 : oDict.Add("Resources", poResources);
2816 :
2817 146 : StartObjWithStream(nObjectId, oDict,
2818 146 : oPageContext.eStreamCompressMethod != COMPRESS_NONE);
2819 :
2820 : /* -------------------------------------------------------------- */
2821 : /* Write object stream */
2822 : /* -------------------------------------------------------------- */
2823 146 : VSIFPrintfL(m_fp, "q\n");
2824 :
2825 146 : VSIFPrintfL(m_fp, "/GS1 gs\n");
2826 :
2827 146 : VSIFPrintfL(
2828 : m_fp, "%s",
2829 292 : GenerateDrawingStream(hGeom, adfMatrix, os, dfRadius).c_str());
2830 :
2831 146 : VSIFPrintfL(m_fp, "Q");
2832 :
2833 146 : EndObjWithStream();
2834 : }
2835 : else
2836 : {
2837 6 : osVectorDesc.aIds.push_back(GDALPDFObjectNum());
2838 : }
2839 :
2840 : /* -------------------------------------------------------------- */
2841 : /* Write label */
2842 : /* -------------------------------------------------------------- */
2843 160 : if (!os.osLabelText.empty() &&
2844 8 : wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint)
2845 : {
2846 8 : if (!osVectorDesc.nOCGTextId.toBool())
2847 8 : osVectorDesc.nOCGTextId = WriteOCG("Text", osVectorDesc.nOCGId);
2848 :
2849 8 : int nWidth = poClippingDS->GetRasterXSize();
2850 8 : double dfWidthInUserUnit = nWidth / dfUserUnit +
2851 8 : oPageContext.sMargins.nLeft +
2852 8 : oPageContext.sMargins.nRight;
2853 8 : double dfHeightInUserUnit = nHeight / dfUserUnit +
2854 8 : oPageContext.sMargins.nBottom +
2855 8 : oPageContext.sMargins.nTop;
2856 : auto nObjectId =
2857 : WriteLabel(hGeom, adfMatrix, os, oPageContext.eStreamCompressMethod,
2858 8 : 0, 0, dfWidthInUserUnit, dfHeightInUserUnit);
2859 :
2860 8 : osVectorDesc.aIdsText.push_back(nObjectId);
2861 : }
2862 : else
2863 : {
2864 144 : osVectorDesc.aIdsText.push_back(GDALPDFObjectNum());
2865 : }
2866 :
2867 : /* -------------------------------------------------------------- */
2868 : /* Write feature attributes */
2869 : /* -------------------------------------------------------------- */
2870 152 : GDALPDFObjectNum nFeatureUserProperties;
2871 :
2872 152 : CPLString osFeatureName;
2873 :
2874 152 : if (bWriteOGRAttributes)
2875 : {
2876 : nFeatureUserProperties = WriteAttributes(
2877 107 : hFeat, osVectorDesc.aosIncludedFields, pszOGRDisplayField, iObj,
2878 107 : osVectorDesc.nFeatureLayerId, oPageContext.nPageId, osFeatureName);
2879 : }
2880 :
2881 152 : iObj++;
2882 :
2883 152 : osVectorDesc.aUserPropertiesIds.push_back(nFeatureUserProperties);
2884 152 : osVectorDesc.aFeatureNames.push_back(osFeatureName);
2885 :
2886 152 : return TRUE;
2887 : }
2888 :
2889 : /************************************************************************/
2890 : /* EndPage() */
2891 : /************************************************************************/
2892 :
2893 112 : int GDALPDFWriter::EndPage(const char *pszExtraImages,
2894 : const char *pszExtraStream,
2895 : const char *pszExtraLayerName,
2896 : const char *pszOffLayers,
2897 : const char *pszExclusiveLayers)
2898 : {
2899 112 : auto nLayerExtraId = WriteOCG(pszExtraLayerName);
2900 112 : if (pszOffLayers)
2901 2 : m_osOffLayers = pszOffLayers;
2902 112 : if (pszExclusiveLayers)
2903 2 : m_osExclusiveLayers = pszExclusiveLayers;
2904 :
2905 : /* -------------------------------------------------------------- */
2906 : /* Write extra images */
2907 : /* -------------------------------------------------------------- */
2908 224 : std::vector<GDALPDFImageDesc> asExtraImageDesc;
2909 112 : if (pszExtraImages)
2910 : {
2911 2 : if (GDALGetDriverCount() == 0)
2912 0 : GDALAllRegister();
2913 :
2914 : char **papszExtraImagesTokens =
2915 2 : CSLTokenizeString2(pszExtraImages, ",", 0);
2916 2 : double dfUserUnit = oPageContext.dfDPI * USER_UNIT_IN_INCH;
2917 2 : int nCount = CSLCount(papszExtraImagesTokens);
2918 6 : for (int i = 0; i + 4 <= nCount; /* */)
2919 : {
2920 4 : const char *pszImageFilename = papszExtraImagesTokens[i + 0];
2921 4 : double dfX = CPLAtof(papszExtraImagesTokens[i + 1]);
2922 4 : double dfY = CPLAtof(papszExtraImagesTokens[i + 2]);
2923 4 : double dfScale = CPLAtof(papszExtraImagesTokens[i + 3]);
2924 4 : const char *pszLinkVal = nullptr;
2925 4 : i += 4;
2926 4 : if (i < nCount &&
2927 2 : STARTS_WITH_CI(papszExtraImagesTokens[i], "link="))
2928 : {
2929 2 : pszLinkVal = papszExtraImagesTokens[i] + 5;
2930 2 : i++;
2931 : }
2932 : auto poImageDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
2933 : pszImageFilename, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
2934 8 : nullptr, nullptr, nullptr));
2935 4 : if (poImageDS)
2936 : {
2937 : auto nImageId = WriteBlock(
2938 : poImageDS.get(), 0, 0, poImageDS->GetRasterXSize(),
2939 0 : poImageDS->GetRasterYSize(), GDALPDFObjectNum(),
2940 4 : COMPRESS_DEFAULT, 0, -1, nullptr, nullptr, nullptr);
2941 :
2942 4 : if (nImageId.toBool())
2943 : {
2944 4 : GDALPDFImageDesc oImageDesc;
2945 4 : oImageDesc.nImageId = nImageId;
2946 4 : oImageDesc.dfXSize =
2947 4 : poImageDS->GetRasterXSize() / dfUserUnit * dfScale;
2948 4 : oImageDesc.dfYSize =
2949 4 : poImageDS->GetRasterYSize() / dfUserUnit * dfScale;
2950 4 : oImageDesc.dfXOff = dfX;
2951 4 : oImageDesc.dfYOff = dfY;
2952 :
2953 4 : asExtraImageDesc.push_back(oImageDesc);
2954 :
2955 4 : if (pszLinkVal != nullptr)
2956 : {
2957 2 : auto nAnnotId = AllocNewObject();
2958 2 : oPageContext.anAnnotationsId.push_back(nAnnotId);
2959 2 : StartObj(nAnnotId);
2960 : {
2961 2 : GDALPDFDictionaryRW oDict;
2962 : oDict.Add("Type",
2963 2 : GDALPDFObjectRW::CreateName("Annot"));
2964 : oDict.Add("Subtype",
2965 2 : GDALPDFObjectRW::CreateName("Link"));
2966 2 : oDict.Add("Rect", &(new GDALPDFArrayRW())
2967 2 : ->Add(oImageDesc.dfXOff)
2968 2 : .Add(oImageDesc.dfYOff)
2969 2 : .Add(oImageDesc.dfXOff +
2970 2 : oImageDesc.dfXSize)
2971 2 : .Add(oImageDesc.dfYOff +
2972 2 : oImageDesc.dfYSize));
2973 : oDict.Add(
2974 : "A",
2975 2 : &(new GDALPDFDictionaryRW())
2976 : ->Add("S",
2977 2 : GDALPDFObjectRW::CreateName("URI"))
2978 2 : .Add("URI", pszLinkVal));
2979 : oDict.Add(
2980 : "BS",
2981 2 : &(new GDALPDFDictionaryRW())
2982 2 : ->Add("Type", GDALPDFObjectRW::CreateName(
2983 2 : "Border"))
2984 2 : .Add("S", GDALPDFObjectRW::CreateName("S"))
2985 2 : .Add("W", 0));
2986 : oDict.Add(
2987 : "Border",
2988 2 : &(new GDALPDFArrayRW())->Add(0).Add(0).Add(0));
2989 2 : oDict.Add("H", GDALPDFObjectRW::CreateName("I"));
2990 :
2991 2 : VSIFPrintfL(m_fp, "%s\n",
2992 4 : oDict.Serialize().c_str());
2993 : }
2994 2 : EndObj();
2995 : }
2996 : }
2997 : }
2998 : }
2999 2 : CSLDestroy(papszExtraImagesTokens);
3000 : }
3001 :
3002 : /* -------------------------------------------------------------- */
3003 : /* Write content stream */
3004 : /* -------------------------------------------------------------- */
3005 112 : GDALPDFDictionaryRW oDictContent;
3006 112 : StartObjWithStream(oPageContext.nContentId, oDictContent,
3007 112 : oPageContext.eStreamCompressMethod != COMPRESS_NONE);
3008 :
3009 : /* -------------------------------------------------------------- */
3010 : /* Write drawing instructions for raster blocks */
3011 : /* -------------------------------------------------------------- */
3012 204 : for (size_t iRaster = 0; iRaster < oPageContext.asRasterDesc.size();
3013 : iRaster++)
3014 : {
3015 92 : const GDALPDFRasterDesc &oDesc = oPageContext.asRasterDesc[iRaster];
3016 92 : if (oDesc.nOCGRasterId.toBool())
3017 6 : VSIFPrintfL(m_fp, "/OC /Lyr%d BDC\n", oDesc.nOCGRasterId.toInt());
3018 :
3019 278 : for (size_t iImage = 0; iImage < oDesc.asImageDesc.size(); iImage++)
3020 : {
3021 186 : VSIFPrintfL(m_fp, "q\n");
3022 : GDALPDFObjectRW *poXSize =
3023 186 : GDALPDFObjectRW::CreateReal(oDesc.asImageDesc[iImage].dfXSize);
3024 : GDALPDFObjectRW *poYSize =
3025 186 : GDALPDFObjectRW::CreateReal(oDesc.asImageDesc[iImage].dfYSize);
3026 : GDALPDFObjectRW *poXOff =
3027 186 : GDALPDFObjectRW::CreateReal(oDesc.asImageDesc[iImage].dfXOff);
3028 : GDALPDFObjectRW *poYOff =
3029 186 : GDALPDFObjectRW::CreateReal(oDesc.asImageDesc[iImage].dfYOff);
3030 744 : VSIFPrintfL(
3031 372 : m_fp, "%s 0 0 %s %s %s cm\n", poXSize->Serialize().c_str(),
3032 558 : poYSize->Serialize().c_str(), poXOff->Serialize().c_str(),
3033 372 : poYOff->Serialize().c_str());
3034 186 : delete poXSize;
3035 186 : delete poYSize;
3036 186 : delete poXOff;
3037 186 : delete poYOff;
3038 186 : VSIFPrintfL(m_fp, "/Image%d Do\n",
3039 186 : oDesc.asImageDesc[iImage].nImageId.toInt());
3040 186 : VSIFPrintfL(m_fp, "Q\n");
3041 : }
3042 :
3043 92 : if (oDesc.nOCGRasterId.toBool())
3044 6 : VSIFPrintfL(m_fp, "EMC\n");
3045 : }
3046 :
3047 : /* -------------------------------------------------------------- */
3048 : /* Write drawing instructions for vector features */
3049 : /* -------------------------------------------------------------- */
3050 112 : int iObj = 0;
3051 153 : for (size_t iLayer = 0; iLayer < oPageContext.asVectorDesc.size(); iLayer++)
3052 : {
3053 41 : const GDALPDFLayerDesc &oLayerDesc = oPageContext.asVectorDesc[iLayer];
3054 :
3055 41 : VSIFPrintfL(m_fp, "/OC /Lyr%d BDC\n", oLayerDesc.nOCGId.toInt());
3056 :
3057 193 : for (size_t iVector = 0; iVector < oLayerDesc.aIds.size(); iVector++)
3058 : {
3059 152 : if (oLayerDesc.aIds[iVector].toBool())
3060 : {
3061 292 : CPLString osName = oLayerDesc.aFeatureNames[iVector];
3062 146 : if (!osName.empty())
3063 : {
3064 104 : VSIFPrintfL(m_fp, "/feature <</MCID %d>> BDC\n", iObj);
3065 : }
3066 :
3067 146 : VSIFPrintfL(m_fp, "/Vector%d Do\n",
3068 146 : oLayerDesc.aIds[iVector].toInt());
3069 :
3070 146 : if (!osName.empty())
3071 : {
3072 104 : VSIFPrintfL(m_fp, "EMC\n");
3073 : }
3074 : }
3075 :
3076 152 : iObj++;
3077 : }
3078 :
3079 41 : VSIFPrintfL(m_fp, "EMC\n");
3080 : }
3081 :
3082 : /* -------------------------------------------------------------- */
3083 : /* Write drawing instructions for labels of vector features */
3084 : /* -------------------------------------------------------------- */
3085 112 : iObj = 0;
3086 153 : for (const GDALPDFLayerDesc &oLayerDesc : oPageContext.asVectorDesc)
3087 : {
3088 41 : if (oLayerDesc.nOCGTextId.toBool())
3089 : {
3090 8 : VSIFPrintfL(m_fp, "/OC /Lyr%d BDC\n", oLayerDesc.nOCGId.toInt());
3091 8 : VSIFPrintfL(m_fp, "/OC /Lyr%d BDC\n",
3092 : oLayerDesc.nOCGTextId.toInt());
3093 :
3094 104 : for (size_t iVector = 0; iVector < oLayerDesc.aIdsText.size();
3095 : iVector++)
3096 : {
3097 96 : if (oLayerDesc.aIdsText[iVector].toBool())
3098 : {
3099 16 : CPLString osName = oLayerDesc.aFeatureNames[iVector];
3100 8 : if (!osName.empty())
3101 : {
3102 5 : VSIFPrintfL(m_fp, "/feature <</MCID %d>> BDC\n", iObj);
3103 : }
3104 :
3105 8 : VSIFPrintfL(m_fp, "/Text%d Do\n",
3106 8 : oLayerDesc.aIdsText[iVector].toInt());
3107 :
3108 8 : if (!osName.empty())
3109 : {
3110 5 : VSIFPrintfL(m_fp, "EMC\n");
3111 : }
3112 : }
3113 :
3114 96 : iObj++;
3115 : }
3116 :
3117 8 : VSIFPrintfL(m_fp, "EMC\n");
3118 8 : VSIFPrintfL(m_fp, "EMC\n");
3119 : }
3120 : else
3121 33 : iObj += static_cast<int>(oLayerDesc.aIds.size());
3122 : }
3123 :
3124 : /* -------------------------------------------------------------- */
3125 : /* Write drawing instructions for extra content. */
3126 : /* -------------------------------------------------------------- */
3127 112 : if (pszExtraStream || !asExtraImageDesc.empty())
3128 : {
3129 2 : if (nLayerExtraId.toBool())
3130 2 : VSIFPrintfL(m_fp, "/OC /Lyr%d BDC\n", nLayerExtraId.toInt());
3131 :
3132 : /* -------------------------------------------------------------- */
3133 : /* Write drawing instructions for extra images. */
3134 : /* -------------------------------------------------------------- */
3135 6 : for (size_t iImage = 0; iImage < asExtraImageDesc.size(); iImage++)
3136 : {
3137 4 : VSIFPrintfL(m_fp, "q\n");
3138 : GDALPDFObjectRW *poXSize =
3139 4 : GDALPDFObjectRW::CreateReal(asExtraImageDesc[iImage].dfXSize);
3140 : GDALPDFObjectRW *poYSize =
3141 4 : GDALPDFObjectRW::CreateReal(asExtraImageDesc[iImage].dfYSize);
3142 : GDALPDFObjectRW *poXOff =
3143 4 : GDALPDFObjectRW::CreateReal(asExtraImageDesc[iImage].dfXOff);
3144 : GDALPDFObjectRW *poYOff =
3145 4 : GDALPDFObjectRW::CreateReal(asExtraImageDesc[iImage].dfYOff);
3146 16 : VSIFPrintfL(
3147 8 : m_fp, "%s 0 0 %s %s %s cm\n", poXSize->Serialize().c_str(),
3148 12 : poYSize->Serialize().c_str(), poXOff->Serialize().c_str(),
3149 8 : poYOff->Serialize().c_str());
3150 4 : delete poXSize;
3151 4 : delete poYSize;
3152 4 : delete poXOff;
3153 4 : delete poYOff;
3154 4 : VSIFPrintfL(m_fp, "/Image%d Do\n",
3155 4 : asExtraImageDesc[iImage].nImageId.toInt());
3156 4 : VSIFPrintfL(m_fp, "Q\n");
3157 : }
3158 :
3159 2 : if (pszExtraStream)
3160 2 : VSIFPrintfL(m_fp, "%s\n", pszExtraStream);
3161 :
3162 2 : if (nLayerExtraId.toBool())
3163 2 : VSIFPrintfL(m_fp, "EMC\n");
3164 : }
3165 :
3166 112 : EndObjWithStream();
3167 :
3168 : /* -------------------------------------------------------------- */
3169 : /* Write objects for feature tree. */
3170 : /* -------------------------------------------------------------- */
3171 112 : if (m_nStructTreeRootId.toBool())
3172 : {
3173 23 : auto nParentTreeId = AllocNewObject();
3174 23 : StartObj(nParentTreeId);
3175 23 : VSIFPrintfL(m_fp, "<< /Nums [ 0 ");
3176 23 : VSIFPrintfL(m_fp, "[ ");
3177 61 : for (size_t iLayer = 0; iLayer < oPageContext.asVectorDesc.size();
3178 : iLayer++)
3179 : {
3180 : const GDALPDFLayerDesc &oLayerDesc =
3181 38 : oPageContext.asVectorDesc[iLayer];
3182 145 : for (size_t iVector = 0; iVector < oLayerDesc.aIds.size();
3183 : iVector++)
3184 : {
3185 107 : const auto &nId = oLayerDesc.aUserPropertiesIds[iVector];
3186 107 : if (nId.toBool())
3187 107 : VSIFPrintfL(m_fp, "%d 0 R ", nId.toInt());
3188 : }
3189 : }
3190 23 : VSIFPrintfL(m_fp, " ]\n");
3191 23 : VSIFPrintfL(m_fp, " ] >> \n");
3192 23 : EndObj();
3193 :
3194 23 : StartObj(m_nStructTreeRootId);
3195 23 : VSIFPrintfL(m_fp,
3196 : "<< "
3197 : "/Type /StructTreeRoot "
3198 : "/ParentTree %d 0 R "
3199 : "/K [ ",
3200 : nParentTreeId.toInt());
3201 61 : for (size_t iLayer = 0; iLayer < oPageContext.asVectorDesc.size();
3202 : iLayer++)
3203 : {
3204 38 : VSIFPrintfL(
3205 : m_fp, "%d 0 R ",
3206 38 : oPageContext.asVectorDesc[iLayer].nFeatureLayerId.toInt());
3207 : }
3208 23 : VSIFPrintfL(m_fp, "] >>\n");
3209 23 : EndObj();
3210 : }
3211 :
3212 : /* -------------------------------------------------------------- */
3213 : /* Write page resource dictionary. */
3214 : /* -------------------------------------------------------------- */
3215 112 : StartObj(oPageContext.nResourcesId);
3216 : {
3217 112 : GDALPDFDictionaryRW oDict;
3218 112 : GDALPDFDictionaryRW *poDictXObject = new GDALPDFDictionaryRW();
3219 112 : oDict.Add("XObject", poDictXObject);
3220 : size_t iImage;
3221 204 : for (const GDALPDFRasterDesc &oDesc : oPageContext.asRasterDesc)
3222 : {
3223 278 : for (iImage = 0; iImage < oDesc.asImageDesc.size(); iImage++)
3224 : {
3225 : poDictXObject->Add(
3226 : CPLSPrintf("Image%d",
3227 186 : oDesc.asImageDesc[iImage].nImageId.toInt()),
3228 186 : oDesc.asImageDesc[iImage].nImageId, 0);
3229 : }
3230 : }
3231 116 : for (iImage = 0; iImage < asExtraImageDesc.size(); iImage++)
3232 : {
3233 : poDictXObject->Add(
3234 : CPLSPrintf("Image%d",
3235 4 : asExtraImageDesc[iImage].nImageId.toInt()),
3236 4 : asExtraImageDesc[iImage].nImageId, 0);
3237 : }
3238 153 : for (const GDALPDFLayerDesc &oLayerDesc : oPageContext.asVectorDesc)
3239 : {
3240 193 : for (size_t iVector = 0; iVector < oLayerDesc.aIds.size();
3241 : iVector++)
3242 : {
3243 152 : if (oLayerDesc.aIds[iVector].toBool())
3244 : poDictXObject->Add(
3245 : CPLSPrintf("Vector%d",
3246 146 : oLayerDesc.aIds[iVector].toInt()),
3247 292 : oLayerDesc.aIds[iVector], 0);
3248 : }
3249 193 : for (size_t iVector = 0; iVector < oLayerDesc.aIdsText.size();
3250 : iVector++)
3251 : {
3252 152 : if (oLayerDesc.aIdsText[iVector].toBool())
3253 : poDictXObject->Add(
3254 : CPLSPrintf("Text%d",
3255 8 : oLayerDesc.aIdsText[iVector].toInt()),
3256 16 : oLayerDesc.aIdsText[iVector], 0);
3257 : }
3258 : }
3259 :
3260 112 : if (pszExtraStream)
3261 : {
3262 4 : std::vector<CPLString> aosNeededFonts;
3263 2 : if (strstr(pszExtraStream, "/FTimes"))
3264 : {
3265 2 : aosNeededFonts.push_back("Times-Roman");
3266 2 : aosNeededFonts.push_back("Times-Bold");
3267 2 : aosNeededFonts.push_back("Times-Italic");
3268 2 : aosNeededFonts.push_back("Times-BoldItalic");
3269 : }
3270 2 : if (strstr(pszExtraStream, "/FHelvetica"))
3271 : {
3272 0 : aosNeededFonts.push_back("Helvetica");
3273 0 : aosNeededFonts.push_back("Helvetica-Bold");
3274 0 : aosNeededFonts.push_back("Helvetica-Oblique");
3275 0 : aosNeededFonts.push_back("Helvetica-BoldOblique");
3276 : }
3277 2 : if (strstr(pszExtraStream, "/FCourier"))
3278 : {
3279 0 : aosNeededFonts.push_back("Courier");
3280 0 : aosNeededFonts.push_back("Courier-Bold");
3281 0 : aosNeededFonts.push_back("Courier-Oblique");
3282 0 : aosNeededFonts.push_back("Courier-BoldOblique");
3283 : }
3284 2 : if (strstr(pszExtraStream, "/FSymbol"))
3285 0 : aosNeededFonts.push_back("Symbol");
3286 2 : if (strstr(pszExtraStream, "/FZapfDingbats"))
3287 0 : aosNeededFonts.push_back("ZapfDingbats");
3288 :
3289 2 : if (!aosNeededFonts.empty())
3290 : {
3291 2 : GDALPDFDictionaryRW *poDictFont = new GDALPDFDictionaryRW();
3292 :
3293 10 : for (CPLString &osFont : aosNeededFonts)
3294 : {
3295 : GDALPDFDictionaryRW *poDictFontInner =
3296 8 : new GDALPDFDictionaryRW();
3297 : poDictFontInner->Add("Type",
3298 8 : GDALPDFObjectRW::CreateName("Font"));
3299 : poDictFontInner->Add("BaseFont",
3300 8 : GDALPDFObjectRW::CreateName(osFont));
3301 : poDictFontInner->Add(
3302 : "Encoding",
3303 8 : GDALPDFObjectRW::CreateName("WinAnsiEncoding"));
3304 : poDictFontInner->Add("Subtype",
3305 8 : GDALPDFObjectRW::CreateName("Type1"));
3306 :
3307 8 : osFont = "F" + osFont;
3308 8 : const size_t nHyphenPos = osFont.find('-');
3309 8 : if (nHyphenPos != std::string::npos)
3310 8 : osFont.erase(nHyphenPos, 1);
3311 8 : poDictFont->Add(osFont, poDictFontInner);
3312 : }
3313 :
3314 2 : oDict.Add("Font", poDictFont);
3315 : }
3316 : }
3317 :
3318 112 : if (!m_asOCGs.empty())
3319 : {
3320 30 : GDALPDFDictionaryRW *poDictProperties = new GDALPDFDictionaryRW();
3321 : #ifdef HACK_TO_GENERATE_OCMD
3322 : GDALPDFDictionaryRW *poOCMD = new GDALPDFDictionaryRW();
3323 : poOCMD->Add("Type", GDALPDFObjectRW::CreateName("OCMD"));
3324 : GDALPDFArrayRW *poArray = new GDALPDFArrayRW();
3325 : poArray->Add(m_asOCGs[0].nId, 0);
3326 : poArray->Add(m_asOCGs[1].nId, 0);
3327 : poOCMD->Add("OCGs", poArray);
3328 : poDictProperties->Add(CPLSPrintf("Lyr%d", m_asOCGs[1].nId.toInt()),
3329 : poOCMD);
3330 : #else
3331 87 : for (size_t i = 0; i < m_asOCGs.size(); i++)
3332 : poDictProperties->Add(
3333 57 : CPLSPrintf("Lyr%d", m_asOCGs[i].nId.toInt()),
3334 57 : m_asOCGs[i].nId, 0);
3335 : #endif
3336 30 : oDict.Add("Properties", poDictProperties);
3337 : }
3338 :
3339 112 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
3340 : }
3341 112 : EndObj();
3342 :
3343 : /* -------------------------------------------------------------- */
3344 : /* Write annotation arrays. */
3345 : /* -------------------------------------------------------------- */
3346 112 : StartObj(oPageContext.nAnnotsId);
3347 : {
3348 112 : GDALPDFArrayRW oArray;
3349 120 : for (size_t i = 0; i < oPageContext.anAnnotationsId.size(); i++)
3350 : {
3351 8 : oArray.Add(oPageContext.anAnnotationsId[i], 0);
3352 : }
3353 112 : VSIFPrintfL(m_fp, "%s\n", oArray.Serialize().c_str());
3354 : }
3355 112 : EndObj();
3356 :
3357 224 : return TRUE;
3358 : }
3359 :
3360 : /************************************************************************/
3361 : /* WriteMask() */
3362 : /************************************************************************/
3363 :
3364 65 : GDALPDFObjectNum GDALPDFBaseWriter::WriteMask(GDALDataset *poSrcDS, int nXOff,
3365 : int nYOff, int nReqXSize,
3366 : int nReqYSize,
3367 : PDFCompressMethod eCompressMethod)
3368 : {
3369 65 : int nMaskSize = nReqXSize * nReqYSize;
3370 65 : GByte *pabyMask = static_cast<GByte *>(VSIMalloc(nMaskSize));
3371 65 : if (pabyMask == nullptr)
3372 0 : return GDALPDFObjectNum();
3373 :
3374 : CPLErr eErr;
3375 65 : eErr = poSrcDS->GetRasterBand(4)->RasterIO(
3376 : GF_Read, nXOff, nYOff, nReqXSize, nReqYSize, pabyMask, nReqXSize,
3377 : nReqYSize, GDT_Byte, 0, 0, nullptr);
3378 65 : if (eErr != CE_None)
3379 : {
3380 0 : VSIFree(pabyMask);
3381 0 : return GDALPDFObjectNum();
3382 : }
3383 :
3384 65 : int bOnly0or255 = TRUE;
3385 65 : int bOnly255 = TRUE;
3386 : /* int bOnly0 = TRUE; */
3387 : int i;
3388 171198 : for (i = 0; i < nReqXSize * nReqYSize; i++)
3389 : {
3390 171180 : if (pabyMask[i] == 0)
3391 166635 : bOnly255 = FALSE;
3392 4545 : else if (pabyMask[i] == 255)
3393 : {
3394 : /* bOnly0 = FALSE; */
3395 : }
3396 : else
3397 : {
3398 : /* bOnly0 = FALSE; */
3399 47 : bOnly255 = FALSE;
3400 47 : bOnly0or255 = FALSE;
3401 47 : break;
3402 : }
3403 : }
3404 :
3405 65 : if (bOnly255)
3406 : {
3407 1 : CPLFree(pabyMask);
3408 1 : return GDALPDFObjectNum();
3409 : }
3410 :
3411 64 : if (bOnly0or255)
3412 : {
3413 : /* Translate to 1 bit */
3414 17 : int nReqXSize1 = (nReqXSize + 7) / 8;
3415 : GByte *pabyMask1 =
3416 17 : static_cast<GByte *>(VSICalloc(nReqXSize1, nReqYSize));
3417 17 : if (pabyMask1 == nullptr)
3418 : {
3419 0 : CPLFree(pabyMask);
3420 0 : return GDALPDFObjectNum();
3421 : }
3422 499 : for (int y = 0; y < nReqYSize; y++)
3423 : {
3424 6686 : for (int x = 0; x < nReqXSize; x++)
3425 : {
3426 6204 : if (pabyMask[y * nReqXSize + x])
3427 1792 : pabyMask1[y * nReqXSize1 + x / 8] |= 1 << (7 - (x % 8));
3428 : }
3429 : }
3430 17 : VSIFree(pabyMask);
3431 17 : pabyMask = pabyMask1;
3432 17 : nMaskSize = nReqXSize1 * nReqYSize;
3433 : }
3434 :
3435 64 : auto nMaskId = AllocNewObject();
3436 :
3437 64 : GDALPDFDictionaryRW oDict;
3438 64 : oDict.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
3439 64 : .Add("Subtype", GDALPDFObjectRW::CreateName("Image"))
3440 64 : .Add("Width", nReqXSize)
3441 64 : .Add("Height", nReqYSize)
3442 64 : .Add("ColorSpace", GDALPDFObjectRW::CreateName("DeviceGray"))
3443 64 : .Add("BitsPerComponent", (bOnly0or255) ? 1 : 8);
3444 :
3445 64 : StartObjWithStream(nMaskId, oDict, eCompressMethod != COMPRESS_NONE);
3446 :
3447 64 : VSIFWriteL(pabyMask, nMaskSize, 1, m_fp);
3448 64 : CPLFree(pabyMask);
3449 :
3450 64 : EndObjWithStream();
3451 :
3452 64 : return nMaskId;
3453 : }
3454 :
3455 : /************************************************************************/
3456 : /* WriteBlock() */
3457 : /************************************************************************/
3458 :
3459 209 : GDALPDFObjectNum GDALPDFBaseWriter::WriteBlock(
3460 : GDALDataset *poSrcDS, int nXOff, int nYOff, int nReqXSize, int nReqYSize,
3461 : const GDALPDFObjectNum &nColorTableIdIn, PDFCompressMethod eCompressMethod,
3462 : int nPredictor, int nJPEGQuality, const char *pszJPEG2000_DRIVER,
3463 : GDALProgressFunc pfnProgress, void *pProgressData)
3464 : {
3465 209 : int nBands = poSrcDS->GetRasterCount();
3466 209 : if (nBands == 0)
3467 0 : return GDALPDFObjectNum();
3468 :
3469 209 : GDALPDFObjectNum nColorTableId(nColorTableIdIn);
3470 209 : if (!nColorTableId.toBool())
3471 207 : nColorTableId = WriteColorTable(poSrcDS);
3472 :
3473 209 : CPLErr eErr = CE_None;
3474 209 : GDALDataset *poBlockSrcDS = nullptr;
3475 209 : std::unique_ptr<MEMDataset> poMEMDS;
3476 209 : GByte *pabyMEMDSBuffer = nullptr;
3477 :
3478 209 : if (eCompressMethod == COMPRESS_DEFAULT)
3479 : {
3480 175 : GDALDataset *poSrcDSToTest = poSrcDS;
3481 :
3482 : /* Test if we can directly copy original JPEG content */
3483 : /* if available */
3484 175 : if (VRTDataset *poVRTDS = dynamic_cast<VRTDataset *>(poSrcDS))
3485 : {
3486 9 : poSrcDSToTest = poVRTDS->GetSingleSimpleSource();
3487 : }
3488 :
3489 173 : if (poSrcDSToTest != nullptr && poSrcDSToTest->GetDriver() != nullptr &&
3490 173 : EQUAL(poSrcDSToTest->GetDriver()->GetDescription(), "JPEG") &&
3491 4 : nXOff == 0 && nYOff == 0 &&
3492 4 : nReqXSize == poSrcDSToTest->GetRasterXSize() &&
3493 348 : nReqYSize == poSrcDSToTest->GetRasterYSize() && nJPEGQuality < 0)
3494 : {
3495 4 : VSILFILE *fpSrc = VSIFOpenL(poSrcDSToTest->GetDescription(), "rb");
3496 4 : if (fpSrc != nullptr)
3497 : {
3498 4 : CPLDebug("PDF", "Copying directly original JPEG file");
3499 :
3500 4 : VSIFSeekL(fpSrc, 0, SEEK_END);
3501 4 : const int nLength = static_cast<int>(VSIFTellL(fpSrc));
3502 4 : VSIFSeekL(fpSrc, 0, SEEK_SET);
3503 :
3504 4 : auto nImageId = AllocNewObject();
3505 :
3506 4 : StartObj(nImageId);
3507 :
3508 8 : GDALPDFDictionaryRW oDict;
3509 4 : oDict.Add("Length", nLength)
3510 4 : .Add("Type", GDALPDFObjectRW::CreateName("XObject"))
3511 4 : .Add("Filter", GDALPDFObjectRW::CreateName("DCTDecode"))
3512 4 : .Add("Subtype", GDALPDFObjectRW::CreateName("Image"))
3513 4 : .Add("Width", nReqXSize)
3514 4 : .Add("Height", nReqYSize)
3515 : .Add("ColorSpace",
3516 : (nBands == 1)
3517 4 : ? GDALPDFObjectRW::CreateName("DeviceGray")
3518 8 : : GDALPDFObjectRW::CreateName("DeviceRGB"))
3519 4 : .Add("BitsPerComponent", 8);
3520 4 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
3521 4 : VSIFPrintfL(m_fp, "stream\n");
3522 :
3523 : GByte abyBuffer[1024];
3524 24 : for (int i = 0; i < nLength; i += 1024)
3525 : {
3526 20 : const auto nRead = VSIFReadL(abyBuffer, 1, 1024, fpSrc);
3527 20 : if (VSIFWriteL(abyBuffer, 1, nRead, m_fp) != nRead)
3528 : {
3529 0 : eErr = CE_Failure;
3530 0 : break;
3531 : }
3532 :
3533 40 : if (eErr == CE_None && pfnProgress != nullptr &&
3534 20 : !pfnProgress(double(i + nRead) / double(nLength),
3535 : nullptr, pProgressData))
3536 : {
3537 0 : CPLError(CE_Failure, CPLE_UserInterrupt,
3538 : "User terminated CreateCopy()");
3539 0 : eErr = CE_Failure;
3540 0 : break;
3541 : }
3542 : }
3543 :
3544 4 : VSIFPrintfL(m_fp, "\nendstream\n");
3545 :
3546 4 : EndObj();
3547 :
3548 4 : VSIFCloseL(fpSrc);
3549 :
3550 4 : return eErr == CE_None ? nImageId : GDALPDFObjectNum();
3551 : }
3552 : }
3553 :
3554 171 : eCompressMethod = COMPRESS_DEFLATE;
3555 : }
3556 :
3557 205 : GDALPDFObjectNum nMaskId;
3558 205 : if (nBands == 4)
3559 : {
3560 : nMaskId = WriteMask(poSrcDS, nXOff, nYOff, nReqXSize, nReqYSize,
3561 65 : eCompressMethod);
3562 : }
3563 :
3564 205 : if (nReqXSize == poSrcDS->GetRasterXSize() &&
3565 205 : nReqYSize == poSrcDS->GetRasterYSize() && nBands != 4)
3566 : {
3567 89 : poBlockSrcDS = poSrcDS;
3568 : }
3569 : else
3570 : {
3571 116 : if (nBands == 4)
3572 65 : nBands = 3;
3573 :
3574 116 : poMEMDS.reset(
3575 : MEMDataset::Create("", nReqXSize, nReqYSize, 0, GDT_Byte, nullptr));
3576 :
3577 : pabyMEMDSBuffer =
3578 116 : static_cast<GByte *>(VSIMalloc3(nReqXSize, nReqYSize, nBands));
3579 116 : if (pabyMEMDSBuffer == nullptr)
3580 : {
3581 0 : return GDALPDFObjectNum();
3582 : }
3583 :
3584 116 : eErr = poSrcDS->RasterIO(GF_Read, nXOff, nYOff, nReqXSize, nReqYSize,
3585 : pabyMEMDSBuffer, nReqXSize, nReqYSize,
3586 : GDT_Byte, nBands, nullptr, 0, 0, 0, nullptr);
3587 :
3588 116 : if (eErr != CE_None)
3589 : {
3590 0 : CPLFree(pabyMEMDSBuffer);
3591 0 : return GDALPDFObjectNum();
3592 : }
3593 :
3594 : int iBand;
3595 362 : for (iBand = 0; iBand < nBands; iBand++)
3596 : {
3597 246 : auto hBand = MEMCreateRasterBandEx(
3598 246 : poMEMDS.get(), iBand + 1,
3599 246 : pabyMEMDSBuffer + iBand * nReqXSize * nReqYSize, GDT_Byte, 0, 0,
3600 : false);
3601 246 : poMEMDS->AddMEMBand(hBand);
3602 : }
3603 :
3604 116 : poBlockSrcDS = poMEMDS.get();
3605 : }
3606 :
3607 205 : auto nImageId = AllocNewObject();
3608 :
3609 205 : GDALPDFObjectNum nMeasureId;
3610 205 : if (CPLTestBool(
3611 0 : CPLGetConfigOption("GDAL_PDF_WRITE_GEOREF_ON_IMAGE", "FALSE")) &&
3612 205 : nReqXSize == poSrcDS->GetRasterXSize() &&
3613 0 : nReqYSize == poSrcDS->GetRasterYSize())
3614 : {
3615 0 : PDFMargins sMargins;
3616 0 : nMeasureId = WriteSRS_ISO32000(poSrcDS, 1, nullptr, &sMargins, FALSE);
3617 : }
3618 :
3619 410 : GDALPDFDictionaryRW oDict;
3620 205 : oDict.Add("Type", GDALPDFObjectRW::CreateName("XObject"));
3621 :
3622 205 : if (eCompressMethod == COMPRESS_DEFLATE)
3623 : {
3624 193 : if (nPredictor == 2)
3625 4 : oDict.Add("DecodeParms", &((new GDALPDFDictionaryRW())
3626 4 : ->Add("Predictor", 2)
3627 4 : .Add("Colors", nBands)
3628 4 : .Add("Columns", nReqXSize)));
3629 : }
3630 12 : else if (eCompressMethod == COMPRESS_JPEG)
3631 : {
3632 6 : oDict.Add("Filter", GDALPDFObjectRW::CreateName("DCTDecode"));
3633 : }
3634 6 : else if (eCompressMethod == COMPRESS_JPEG2000)
3635 : {
3636 4 : oDict.Add("Filter", GDALPDFObjectRW::CreateName("JPXDecode"));
3637 : }
3638 :
3639 205 : oDict.Add("Subtype", GDALPDFObjectRW::CreateName("Image"))
3640 205 : .Add("Width", nReqXSize)
3641 205 : .Add("Height", nReqYSize)
3642 : .Add("ColorSpace",
3643 205 : (nColorTableId.toBool())
3644 2 : ? GDALPDFObjectRW::CreateIndirect(nColorTableId, 0)
3645 128 : : (nBands == 1) ? GDALPDFObjectRW::CreateName("DeviceGray")
3646 335 : : GDALPDFObjectRW::CreateName("DeviceRGB"))
3647 205 : .Add("BitsPerComponent", 8);
3648 205 : if (nMaskId.toBool())
3649 : {
3650 64 : oDict.Add("SMask", nMaskId, 0);
3651 : }
3652 205 : if (nMeasureId.toBool())
3653 : {
3654 0 : oDict.Add("Measure", nMeasureId, 0);
3655 : }
3656 :
3657 205 : StartObjWithStream(nImageId, oDict, eCompressMethod == COMPRESS_DEFLATE);
3658 :
3659 205 : if (eCompressMethod == COMPRESS_JPEG ||
3660 : eCompressMethod == COMPRESS_JPEG2000)
3661 : {
3662 10 : GDALDriver *poJPEGDriver = nullptr;
3663 10 : std::string osTmpfilename;
3664 10 : char **papszOptions = nullptr;
3665 :
3666 10 : bool bEcwEncodeKeyRequiredButNotFound = false;
3667 10 : if (eCompressMethod == COMPRESS_JPEG)
3668 : {
3669 6 : poJPEGDriver = GetGDALDriverManager()->GetDriverByName("JPEG");
3670 6 : if (poJPEGDriver != nullptr && nJPEGQuality > 0)
3671 0 : papszOptions = CSLAddString(
3672 : papszOptions, CPLSPrintf("QUALITY=%d", nJPEGQuality));
3673 6 : osTmpfilename = VSIMemGenerateHiddenFilename("pdf_temp.jpg");
3674 : }
3675 : else
3676 : {
3677 4 : if (pszJPEG2000_DRIVER == nullptr ||
3678 3 : EQUAL(pszJPEG2000_DRIVER, "JP2KAK"))
3679 : poJPEGDriver =
3680 1 : GetGDALDriverManager()->GetDriverByName("JP2KAK");
3681 4 : if (poJPEGDriver == nullptr)
3682 : {
3683 4 : if (pszJPEG2000_DRIVER == nullptr ||
3684 3 : EQUAL(pszJPEG2000_DRIVER, "JP2ECW"))
3685 : {
3686 : poJPEGDriver =
3687 3 : GetGDALDriverManager()->GetDriverByName("JP2ECW");
3688 6 : if (poJPEGDriver &&
3689 3 : poJPEGDriver->GetMetadataItem(
3690 3 : GDAL_DMD_CREATIONDATATYPES) == nullptr)
3691 : {
3692 0 : poJPEGDriver = nullptr;
3693 : }
3694 3 : else if (poJPEGDriver)
3695 : {
3696 6 : if (strstr(poJPEGDriver->GetMetadataItem(
3697 3 : GDAL_DMD_CREATIONOPTIONLIST),
3698 3 : "ECW_ENCODE_KEY"))
3699 : {
3700 0 : if (!CPLGetConfigOption("ECW_ENCODE_KEY", nullptr))
3701 : {
3702 0 : bEcwEncodeKeyRequiredButNotFound = true;
3703 0 : poJPEGDriver = nullptr;
3704 : }
3705 : }
3706 : }
3707 : }
3708 4 : if (poJPEGDriver)
3709 : {
3710 3 : papszOptions = CSLAddString(papszOptions, "PROFILE=NPJE");
3711 3 : papszOptions = CSLAddString(papszOptions, "LAYERS=1");
3712 3 : papszOptions = CSLAddString(papszOptions, "GeoJP2=OFF");
3713 3 : papszOptions = CSLAddString(papszOptions, "GMLJP2=OFF");
3714 : }
3715 : }
3716 4 : if (poJPEGDriver == nullptr)
3717 : {
3718 1 : if (pszJPEG2000_DRIVER == nullptr ||
3719 1 : EQUAL(pszJPEG2000_DRIVER, "JP2OpenJPEG"))
3720 : poJPEGDriver =
3721 1 : GetGDALDriverManager()->GetDriverByName("JP2OpenJPEG");
3722 1 : if (poJPEGDriver)
3723 : {
3724 1 : papszOptions = CSLAddString(papszOptions, "GeoJP2=OFF");
3725 1 : papszOptions = CSLAddString(papszOptions, "GMLJP2=OFF");
3726 : }
3727 : }
3728 4 : osTmpfilename = VSIMemGenerateHiddenFilename("pdf_temp.jp2");
3729 : }
3730 :
3731 10 : if (poJPEGDriver == nullptr)
3732 : {
3733 0 : if (bEcwEncodeKeyRequiredButNotFound)
3734 : {
3735 0 : CPLError(CE_Failure, CPLE_NotSupported,
3736 : "No JPEG2000 driver usable (JP2ECW detected but "
3737 : "ECW_ENCODE_KEY configuration option not set");
3738 : }
3739 : else
3740 : {
3741 0 : CPLError(CE_Failure, CPLE_NotSupported, "No %s driver found",
3742 : (eCompressMethod == COMPRESS_JPEG) ? "JPEG"
3743 : : "JPEG2000");
3744 : }
3745 0 : eErr = CE_Failure;
3746 0 : goto end;
3747 : }
3748 :
3749 : GDALDataset *poJPEGDS =
3750 10 : poJPEGDriver->CreateCopy(osTmpfilename.c_str(), poBlockSrcDS, FALSE,
3751 : papszOptions, pfnProgress, pProgressData);
3752 :
3753 10 : CSLDestroy(papszOptions);
3754 10 : if (poJPEGDS == nullptr)
3755 : {
3756 0 : eErr = CE_Failure;
3757 0 : goto end;
3758 : }
3759 :
3760 10 : GDALClose(poJPEGDS);
3761 :
3762 10 : vsi_l_offset nJPEGDataSize = 0;
3763 : GByte *pabyJPEGData =
3764 10 : VSIGetMemFileBuffer(osTmpfilename.c_str(), &nJPEGDataSize, TRUE);
3765 10 : VSIFWriteL(pabyJPEGData, static_cast<size_t>(nJPEGDataSize), 1, m_fp);
3766 20 : CPLFree(pabyJPEGData);
3767 : }
3768 : else
3769 : {
3770 : GByte *pabyLine = static_cast<GByte *>(
3771 195 : CPLMalloc(static_cast<size_t>(nReqXSize) * nBands));
3772 93132 : for (int iLine = 0; iLine < nReqYSize; iLine++)
3773 : {
3774 : /* Get pixel interleaved data */
3775 92939 : eErr = poBlockSrcDS->RasterIO(
3776 : GF_Read, 0, iLine, nReqXSize, 1, pabyLine, nReqXSize, 1,
3777 : GDT_Byte, nBands, nullptr, nBands, 0, 1, nullptr);
3778 92939 : if (eErr != CE_None)
3779 0 : break;
3780 :
3781 : /* Apply predictor if needed */
3782 92939 : if (nPredictor == 2)
3783 : {
3784 1124 : if (nBands == 1)
3785 : {
3786 1024 : int nPrevValue = pabyLine[0];
3787 524288 : for (int iPixel = 1; iPixel < nReqXSize; iPixel++)
3788 : {
3789 523264 : int nCurValue = pabyLine[iPixel];
3790 523264 : pabyLine[iPixel] =
3791 523264 : static_cast<GByte>(nCurValue - nPrevValue);
3792 523264 : nPrevValue = nCurValue;
3793 : }
3794 : }
3795 100 : else if (nBands == 3)
3796 : {
3797 100 : int nPrevValueR = pabyLine[0];
3798 100 : int nPrevValueG = pabyLine[1];
3799 100 : int nPrevValueB = pabyLine[2];
3800 5000 : for (int iPixel = 1; iPixel < nReqXSize; iPixel++)
3801 : {
3802 4900 : int nCurValueR = pabyLine[3 * iPixel + 0];
3803 4900 : int nCurValueG = pabyLine[3 * iPixel + 1];
3804 4900 : int nCurValueB = pabyLine[3 * iPixel + 2];
3805 4900 : pabyLine[3 * iPixel + 0] =
3806 4900 : static_cast<GByte>(nCurValueR - nPrevValueR);
3807 4900 : pabyLine[3 * iPixel + 1] =
3808 4900 : static_cast<GByte>(nCurValueG - nPrevValueG);
3809 4900 : pabyLine[3 * iPixel + 2] =
3810 4900 : static_cast<GByte>(nCurValueB - nPrevValueB);
3811 4900 : nPrevValueR = nCurValueR;
3812 4900 : nPrevValueG = nCurValueG;
3813 4900 : nPrevValueB = nCurValueB;
3814 : }
3815 : }
3816 : }
3817 :
3818 92939 : if (VSIFWriteL(pabyLine, static_cast<size_t>(nReqXSize) * nBands, 1,
3819 92939 : m_fp) != 1)
3820 : {
3821 2 : eErr = CE_Failure;
3822 2 : break;
3823 : }
3824 :
3825 185535 : if (pfnProgress != nullptr &&
3826 92598 : !pfnProgress((iLine + 1) / double(nReqYSize), nullptr,
3827 : pProgressData))
3828 : {
3829 0 : CPLError(CE_Failure, CPLE_UserInterrupt,
3830 : "User terminated CreateCopy()");
3831 0 : eErr = CE_Failure;
3832 0 : break;
3833 : }
3834 : }
3835 :
3836 195 : CPLFree(pabyLine);
3837 : }
3838 :
3839 205 : end:
3840 205 : CPLFree(pabyMEMDSBuffer);
3841 205 : pabyMEMDSBuffer = nullptr;
3842 :
3843 205 : EndObjWithStream();
3844 :
3845 205 : return eErr == CE_None ? nImageId : GDALPDFObjectNum();
3846 : }
3847 :
3848 : /************************************************************************/
3849 : /* WriteJavascript() */
3850 : /************************************************************************/
3851 :
3852 3 : GDALPDFObjectNum GDALPDFBaseWriter::WriteJavascript(const char *pszJavascript,
3853 : bool bDeflate)
3854 : {
3855 3 : auto nJSId = AllocNewObject();
3856 : {
3857 6 : GDALPDFDictionaryRW oDict;
3858 3 : StartObjWithStream(nJSId, oDict, bDeflate);
3859 :
3860 3 : VSIFWriteL(pszJavascript, strlen(pszJavascript), 1, m_fp);
3861 3 : VSIFPrintfL(m_fp, "\n");
3862 :
3863 3 : EndObjWithStream();
3864 : }
3865 :
3866 3 : m_nNamesId = AllocNewObject();
3867 3 : StartObj(m_nNamesId);
3868 : {
3869 3 : GDALPDFDictionaryRW oDict;
3870 3 : GDALPDFDictionaryRW *poJavaScriptDict = new GDALPDFDictionaryRW();
3871 3 : oDict.Add("JavaScript", poJavaScriptDict);
3872 :
3873 3 : GDALPDFArrayRW *poNamesArray = new GDALPDFArrayRW();
3874 3 : poJavaScriptDict->Add("Names", poNamesArray);
3875 :
3876 3 : poNamesArray->Add("GDAL");
3877 :
3878 3 : GDALPDFDictionaryRW *poJSDict = new GDALPDFDictionaryRW();
3879 3 : poNamesArray->Add(poJSDict);
3880 :
3881 3 : poJSDict->Add("JS", nJSId, 0);
3882 3 : poJSDict->Add("S", GDALPDFObjectRW::CreateName("JavaScript"));
3883 :
3884 3 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
3885 : }
3886 3 : EndObj();
3887 :
3888 3 : return m_nNamesId;
3889 : }
3890 :
3891 2 : GDALPDFObjectNum GDALPDFWriter::WriteJavascript(const char *pszJavascript)
3892 : {
3893 : return GDALPDFBaseWriter::WriteJavascript(
3894 2 : pszJavascript, oPageContext.eStreamCompressMethod != COMPRESS_NONE);
3895 : }
3896 :
3897 : /************************************************************************/
3898 : /* WriteJavascriptFile() */
3899 : /************************************************************************/
3900 :
3901 : GDALPDFObjectNum
3902 0 : GDALPDFWriter::WriteJavascriptFile(const char *pszJavascriptFile)
3903 : {
3904 0 : GDALPDFObjectNum nId;
3905 0 : char *pszJavascriptToFree = static_cast<char *>(CPLMalloc(65536));
3906 0 : VSILFILE *fpJS = VSIFOpenL(pszJavascriptFile, "rb");
3907 0 : if (fpJS != nullptr)
3908 : {
3909 : const int nRead =
3910 0 : static_cast<int>(VSIFReadL(pszJavascriptToFree, 1, 65536, fpJS));
3911 0 : if (nRead < 65536)
3912 : {
3913 0 : pszJavascriptToFree[nRead] = '\0';
3914 0 : nId = WriteJavascript(pszJavascriptToFree);
3915 : }
3916 0 : VSIFCloseL(fpJS);
3917 : }
3918 0 : CPLFree(pszJavascriptToFree);
3919 0 : return nId;
3920 : }
3921 :
3922 : /************************************************************************/
3923 : /* WritePages() */
3924 : /************************************************************************/
3925 :
3926 114 : void GDALPDFWriter::WritePages()
3927 : {
3928 114 : StartObj(m_nPageResourceId);
3929 : {
3930 114 : GDALPDFDictionaryRW oDict;
3931 114 : GDALPDFArrayRW *poKids = new GDALPDFArrayRW();
3932 114 : oDict.Add("Type", GDALPDFObjectRW::CreateName("Pages"))
3933 114 : .Add("Count", static_cast<int>(m_asPageId.size()))
3934 114 : .Add("Kids", poKids);
3935 :
3936 228 : for (size_t i = 0; i < m_asPageId.size(); i++)
3937 114 : poKids->Add(m_asPageId[i], 0);
3938 :
3939 114 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
3940 : }
3941 114 : EndObj();
3942 :
3943 114 : StartObj(m_nCatalogId);
3944 : {
3945 114 : GDALPDFDictionaryRW oDict;
3946 114 : oDict.Add("Type", GDALPDFObjectRW::CreateName("Catalog"))
3947 114 : .Add("Pages", m_nPageResourceId, 0);
3948 114 : if (m_nXMPId.toBool())
3949 2 : oDict.Add("Metadata", m_nXMPId, 0);
3950 114 : if (!m_asOCGs.empty())
3951 : {
3952 30 : GDALPDFDictionaryRW *poDictOCProperties = new GDALPDFDictionaryRW();
3953 30 : oDict.Add("OCProperties", poDictOCProperties);
3954 :
3955 30 : GDALPDFDictionaryRW *poDictD = new GDALPDFDictionaryRW();
3956 30 : poDictOCProperties->Add("D", poDictD);
3957 :
3958 : /* Build "Order" array of D dict */
3959 30 : GDALPDFArrayRW *poArrayOrder = new GDALPDFArrayRW();
3960 79 : for (size_t i = 0; i < m_asOCGs.size(); i++)
3961 : {
3962 49 : poArrayOrder->Add(m_asOCGs[i].nId, 0);
3963 76 : if (i + 1 < m_asOCGs.size() &&
3964 27 : m_asOCGs[i + 1].nParentId == m_asOCGs[i].nId)
3965 : {
3966 8 : GDALPDFArrayRW *poSubArrayOrder = new GDALPDFArrayRW();
3967 8 : poSubArrayOrder->Add(m_asOCGs[i + 1].nId, 0);
3968 8 : poArrayOrder->Add(poSubArrayOrder);
3969 8 : i++;
3970 : }
3971 : }
3972 30 : poDictD->Add("Order", poArrayOrder);
3973 :
3974 : /* Build "OFF" array of D dict */
3975 30 : if (!m_osOffLayers.empty())
3976 : {
3977 2 : GDALPDFArrayRW *poArrayOFF = new GDALPDFArrayRW();
3978 2 : char **papszTokens = CSLTokenizeString2(m_osOffLayers, ",", 0);
3979 4 : for (int i = 0; papszTokens[i] != nullptr; i++)
3980 : {
3981 : size_t j;
3982 2 : int bFound = FALSE;
3983 6 : for (j = 0; j < m_asOCGs.size(); j++)
3984 : {
3985 4 : if (strcmp(papszTokens[i], m_asOCGs[j].osLayerName) ==
3986 : 0)
3987 : {
3988 2 : poArrayOFF->Add(m_asOCGs[j].nId, 0);
3989 2 : bFound = TRUE;
3990 : }
3991 6 : if (j + 1 < m_asOCGs.size() &&
3992 2 : m_asOCGs[j + 1].nParentId == m_asOCGs[j].nId)
3993 : {
3994 0 : j++;
3995 : }
3996 : }
3997 2 : if (!bFound)
3998 : {
3999 0 : CPLError(
4000 : CE_Warning, CPLE_AppDefined,
4001 : "Unknown layer name (%s) specified in OFF_LAYERS",
4002 0 : papszTokens[i]);
4003 : }
4004 : }
4005 2 : CSLDestroy(papszTokens);
4006 :
4007 2 : poDictD->Add("OFF", poArrayOFF);
4008 : }
4009 :
4010 : /* Build "RBGroups" array of D dict */
4011 30 : if (!m_osExclusiveLayers.empty())
4012 : {
4013 2 : GDALPDFArrayRW *poArrayRBGroups = new GDALPDFArrayRW();
4014 : char **papszTokens =
4015 2 : CSLTokenizeString2(m_osExclusiveLayers, ",", 0);
4016 6 : for (int i = 0; papszTokens[i] != nullptr; i++)
4017 : {
4018 : size_t j;
4019 4 : int bFound = FALSE;
4020 12 : for (j = 0; j < m_asOCGs.size(); j++)
4021 : {
4022 8 : if (strcmp(papszTokens[i], m_asOCGs[j].osLayerName) ==
4023 : 0)
4024 : {
4025 4 : poArrayRBGroups->Add(m_asOCGs[j].nId, 0);
4026 4 : bFound = TRUE;
4027 : }
4028 12 : if (j + 1 < m_asOCGs.size() &&
4029 4 : m_asOCGs[j + 1].nParentId == m_asOCGs[j].nId)
4030 : {
4031 0 : j++;
4032 : }
4033 : }
4034 4 : if (!bFound)
4035 : {
4036 0 : CPLError(CE_Warning, CPLE_AppDefined,
4037 : "Unknown layer name (%s) specified in "
4038 : "EXCLUSIVE_LAYERS",
4039 0 : papszTokens[i]);
4040 : }
4041 : }
4042 2 : CSLDestroy(papszTokens);
4043 :
4044 2 : if (poArrayRBGroups->GetLength())
4045 : {
4046 2 : GDALPDFArrayRW *poMainArrayRBGroups = new GDALPDFArrayRW();
4047 2 : poMainArrayRBGroups->Add(poArrayRBGroups);
4048 2 : poDictD->Add("RBGroups", poMainArrayRBGroups);
4049 : }
4050 : else
4051 0 : delete poArrayRBGroups;
4052 : }
4053 :
4054 30 : GDALPDFArrayRW *poArrayOGCs = new GDALPDFArrayRW();
4055 87 : for (size_t i = 0; i < m_asOCGs.size(); i++)
4056 57 : poArrayOGCs->Add(m_asOCGs[i].nId, 0);
4057 30 : poDictOCProperties->Add("OCGs", poArrayOGCs);
4058 : }
4059 :
4060 114 : if (m_nStructTreeRootId.toBool())
4061 : {
4062 23 : GDALPDFDictionaryRW *poDictMarkInfo = new GDALPDFDictionaryRW();
4063 23 : oDict.Add("MarkInfo", poDictMarkInfo);
4064 : poDictMarkInfo->Add("UserProperties",
4065 23 : GDALPDFObjectRW::CreateBool(TRUE));
4066 :
4067 23 : oDict.Add("StructTreeRoot", m_nStructTreeRootId, 0);
4068 : }
4069 :
4070 114 : if (m_nNamesId.toBool())
4071 2 : oDict.Add("Names", m_nNamesId, 0);
4072 :
4073 114 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
4074 : }
4075 114 : EndObj();
4076 114 : }
4077 :
4078 : /************************************************************************/
4079 : /* GDALPDFGetJPEGQuality() */
4080 : /************************************************************************/
4081 :
4082 95 : static int GDALPDFGetJPEGQuality(char **papszOptions)
4083 : {
4084 95 : int nJpegQuality = -1;
4085 95 : const char *pszValue = CSLFetchNameValue(papszOptions, "JPEG_QUALITY");
4086 95 : if (pszValue != nullptr)
4087 : {
4088 0 : nJpegQuality = atoi(pszValue);
4089 0 : if (!(nJpegQuality >= 1 && nJpegQuality <= 100))
4090 : {
4091 0 : CPLError(CE_Warning, CPLE_IllegalArg,
4092 : "JPEG_QUALITY=%s value not recognised, ignoring.",
4093 : pszValue);
4094 0 : nJpegQuality = -1;
4095 : }
4096 : }
4097 95 : return nJpegQuality;
4098 : }
4099 :
4100 : /************************************************************************/
4101 : /* GDALPDFClippingDataset */
4102 : /************************************************************************/
4103 :
4104 : class GDALPDFClippingDataset final : public GDALDataset
4105 : {
4106 : GDALDataset *poSrcDS = nullptr;
4107 : double adfGeoTransform[6];
4108 :
4109 : CPL_DISALLOW_COPY_ASSIGN(GDALPDFClippingDataset)
4110 :
4111 : public:
4112 2 : GDALPDFClippingDataset(GDALDataset *poSrcDSIn, double adfClippingExtent[4])
4113 2 : : poSrcDS(poSrcDSIn)
4114 : {
4115 : double adfSrcGeoTransform[6];
4116 2 : poSrcDS->GetGeoTransform(adfSrcGeoTransform);
4117 2 : adfGeoTransform[0] = adfClippingExtent[0];
4118 2 : adfGeoTransform[1] = adfSrcGeoTransform[1];
4119 2 : adfGeoTransform[2] = 0.0;
4120 2 : adfGeoTransform[3] = adfSrcGeoTransform[5] < 0 ? adfClippingExtent[3]
4121 : : adfClippingExtent[1];
4122 2 : adfGeoTransform[4] = 0.0;
4123 2 : adfGeoTransform[5] = adfSrcGeoTransform[5];
4124 2 : nRasterXSize =
4125 2 : static_cast<int>((adfClippingExtent[2] - adfClippingExtent[0]) /
4126 2 : adfSrcGeoTransform[1]);
4127 2 : nRasterYSize =
4128 2 : static_cast<int>((adfClippingExtent[3] - adfClippingExtent[1]) /
4129 2 : fabs(adfSrcGeoTransform[5]));
4130 2 : }
4131 :
4132 6 : virtual CPLErr GetGeoTransform(double *padfGeoTransform) override
4133 : {
4134 6 : memcpy(padfGeoTransform, adfGeoTransform, 6 * sizeof(double));
4135 6 : return CE_None;
4136 : }
4137 :
4138 2 : virtual const OGRSpatialReference *GetSpatialRef() const override
4139 : {
4140 2 : return poSrcDS->GetSpatialRef();
4141 : }
4142 : };
4143 :
4144 : /************************************************************************/
4145 : /* GDALPDFCreateCopy() */
4146 : /************************************************************************/
4147 :
4148 108 : GDALDataset *GDALPDFCreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
4149 : int bStrict, char **papszOptions,
4150 : GDALProgressFunc pfnProgress,
4151 : void *pProgressData)
4152 : {
4153 108 : int nBands = poSrcDS->GetRasterCount();
4154 108 : int nWidth = poSrcDS->GetRasterXSize();
4155 108 : int nHeight = poSrcDS->GetRasterYSize();
4156 :
4157 108 : if (!pfnProgress(0.0, nullptr, pProgressData))
4158 0 : return nullptr;
4159 :
4160 : /* -------------------------------------------------------------------- */
4161 : /* Some some rudimentary checks */
4162 : /* -------------------------------------------------------------------- */
4163 108 : if (nBands != 1 && nBands != 3 && nBands != 4)
4164 : {
4165 3 : CPLError(CE_Failure, CPLE_NotSupported,
4166 : "PDF driver doesn't support %d bands. Must be 1 (grey or "
4167 : "with color table), "
4168 : "3 (RGB) or 4 bands.\n",
4169 : nBands);
4170 :
4171 3 : return nullptr;
4172 : }
4173 :
4174 105 : GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
4175 105 : if (eDT != GDT_Byte)
4176 : {
4177 10 : CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
4178 : "PDF driver doesn't support data type %s. "
4179 : "Only eight bit byte bands supported.\n",
4180 : GDALGetDataTypeName(
4181 : poSrcDS->GetRasterBand(1)->GetRasterDataType()));
4182 :
4183 10 : if (bStrict)
4184 10 : return nullptr;
4185 : }
4186 :
4187 : /* -------------------------------------------------------------------- */
4188 : /* Read options. */
4189 : /* -------------------------------------------------------------------- */
4190 95 : PDFCompressMethod eCompressMethod = COMPRESS_DEFAULT;
4191 95 : const char *pszCompressMethod = CSLFetchNameValue(papszOptions, "COMPRESS");
4192 95 : if (pszCompressMethod)
4193 : {
4194 14 : if (EQUAL(pszCompressMethod, "NONE"))
4195 2 : eCompressMethod = COMPRESS_NONE;
4196 12 : else if (EQUAL(pszCompressMethod, "DEFLATE"))
4197 2 : eCompressMethod = COMPRESS_DEFLATE;
4198 10 : else if (EQUAL(pszCompressMethod, "JPEG"))
4199 6 : eCompressMethod = COMPRESS_JPEG;
4200 4 : else if (EQUAL(pszCompressMethod, "JPEG2000"))
4201 4 : eCompressMethod = COMPRESS_JPEG2000;
4202 : else
4203 : {
4204 0 : CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
4205 : "Unsupported value for COMPRESS.");
4206 :
4207 0 : if (bStrict)
4208 0 : return nullptr;
4209 : }
4210 : }
4211 :
4212 95 : PDFCompressMethod eStreamCompressMethod = COMPRESS_DEFLATE;
4213 : const char *pszStreamCompressMethod =
4214 95 : CSLFetchNameValue(papszOptions, "STREAM_COMPRESS");
4215 95 : if (pszStreamCompressMethod)
4216 : {
4217 2 : if (EQUAL(pszStreamCompressMethod, "NONE"))
4218 2 : eStreamCompressMethod = COMPRESS_NONE;
4219 0 : else if (EQUAL(pszStreamCompressMethod, "DEFLATE"))
4220 0 : eStreamCompressMethod = COMPRESS_DEFLATE;
4221 : else
4222 : {
4223 0 : CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
4224 : "Unsupported value for STREAM_COMPRESS.");
4225 :
4226 0 : if (bStrict)
4227 0 : return nullptr;
4228 : }
4229 : }
4230 :
4231 97 : if (nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr &&
4232 2 : (eCompressMethod == COMPRESS_JPEG ||
4233 : eCompressMethod == COMPRESS_JPEG2000))
4234 : {
4235 0 : CPLError(CE_Warning, CPLE_AppDefined,
4236 : "The source raster band has a color table, which is not "
4237 : "appropriate with JPEG or JPEG2000 compression.\n"
4238 : "You should rather consider using color table expansion "
4239 : "(-expand option in gdal_translate)");
4240 : }
4241 :
4242 95 : int nBlockXSize = nWidth;
4243 95 : int nBlockYSize = nHeight;
4244 :
4245 95 : const bool bTiled = CPLFetchBool(papszOptions, "TILED", false);
4246 95 : if (bTiled)
4247 : {
4248 2 : nBlockXSize = 256;
4249 2 : nBlockYSize = 256;
4250 : }
4251 :
4252 95 : const char *pszValue = CSLFetchNameValue(papszOptions, "BLOCKXSIZE");
4253 95 : if (pszValue != nullptr)
4254 : {
4255 4 : nBlockXSize = atoi(pszValue);
4256 4 : if (nBlockXSize <= 0 || nBlockXSize >= nWidth)
4257 0 : nBlockXSize = nWidth;
4258 : }
4259 :
4260 95 : pszValue = CSLFetchNameValue(papszOptions, "BLOCKYSIZE");
4261 95 : if (pszValue != nullptr)
4262 : {
4263 4 : nBlockYSize = atoi(pszValue);
4264 4 : if (nBlockYSize <= 0 || nBlockYSize >= nHeight)
4265 0 : nBlockYSize = nHeight;
4266 : }
4267 :
4268 95 : int nJPEGQuality = GDALPDFGetJPEGQuality(papszOptions);
4269 :
4270 : const char *pszJPEG2000_DRIVER =
4271 95 : CSLFetchNameValue(papszOptions, "JPEG2000_DRIVER");
4272 :
4273 : const char *pszGEO_ENCODING =
4274 95 : CSLFetchNameValueDef(papszOptions, "GEO_ENCODING", "ISO32000");
4275 95 : if (EQUAL(pszGEO_ENCODING, "OGC_BP"))
4276 : {
4277 0 : CPLError(CE_Failure, CPLE_NotSupported,
4278 : "GEO_ENCODING=OGC_BP is no longer supported. Switch to using "
4279 : "ISO32000");
4280 0 : return nullptr;
4281 : }
4282 95 : else if (EQUAL(pszGEO_ENCODING, "BOTH"))
4283 : {
4284 0 : CPLError(CE_Warning, CPLE_NotSupported,
4285 : "GEO_ENCODING=BOTH is no longer strictly supported. This now "
4286 : "fallbacks to ISO32000");
4287 0 : pszGEO_ENCODING = "ISO32000";
4288 : }
4289 :
4290 95 : const char *pszXMP = CSLFetchNameValue(papszOptions, "XMP");
4291 :
4292 95 : const char *pszPredictor = CSLFetchNameValue(papszOptions, "PREDICTOR");
4293 95 : int nPredictor = 1;
4294 95 : if (pszPredictor)
4295 : {
4296 4 : if (eCompressMethod == COMPRESS_DEFAULT)
4297 4 : eCompressMethod = COMPRESS_DEFLATE;
4298 :
4299 4 : if (eCompressMethod != COMPRESS_DEFLATE)
4300 : {
4301 0 : CPLError(CE_Warning, CPLE_NotSupported,
4302 : "PREDICTOR option is only taken into account for DEFLATE "
4303 : "compression");
4304 : }
4305 : else
4306 : {
4307 4 : nPredictor = atoi(pszPredictor);
4308 4 : if (nPredictor != 1 && nPredictor != 2)
4309 : {
4310 0 : CPLError(CE_Warning, CPLE_NotSupported,
4311 : "Supported PREDICTOR values are 1 or 2");
4312 0 : nPredictor = 1;
4313 : }
4314 : }
4315 : }
4316 :
4317 95 : const char *pszNEATLINE = CSLFetchNameValue(papszOptions, "NEATLINE");
4318 :
4319 95 : int nMargin = atoi(CSLFetchNameValueDef(papszOptions, "MARGIN", "0"));
4320 :
4321 95 : PDFMargins sMargins;
4322 95 : sMargins.nLeft = nMargin;
4323 95 : sMargins.nRight = nMargin;
4324 95 : sMargins.nTop = nMargin;
4325 95 : sMargins.nBottom = nMargin;
4326 :
4327 95 : const char *pszLeftMargin = CSLFetchNameValue(papszOptions, "LEFT_MARGIN");
4328 95 : if (pszLeftMargin)
4329 4 : sMargins.nLeft = atoi(pszLeftMargin);
4330 :
4331 : const char *pszRightMargin =
4332 95 : CSLFetchNameValue(papszOptions, "RIGHT_MARGIN");
4333 95 : if (pszRightMargin)
4334 2 : sMargins.nRight = atoi(pszRightMargin);
4335 :
4336 95 : const char *pszTopMargin = CSLFetchNameValue(papszOptions, "TOP_MARGIN");
4337 95 : if (pszTopMargin)
4338 4 : sMargins.nTop = atoi(pszTopMargin);
4339 :
4340 : const char *pszBottomMargin =
4341 95 : CSLFetchNameValue(papszOptions, "BOTTOM_MARGIN");
4342 95 : if (pszBottomMargin)
4343 2 : sMargins.nBottom = atoi(pszBottomMargin);
4344 :
4345 95 : const char *pszDPI = CSLFetchNameValue(papszOptions, "DPI");
4346 95 : double dfDPI = DEFAULT_DPI;
4347 95 : if (pszDPI != nullptr)
4348 14 : dfDPI = CPLAtof(pszDPI);
4349 :
4350 : const char *pszWriteUserUnit =
4351 95 : CSLFetchNameValue(papszOptions, "WRITE_USERUNIT");
4352 : bool bWriteUserUnit;
4353 95 : if (pszWriteUserUnit != nullptr)
4354 2 : bWriteUserUnit = CPLTestBool(pszWriteUserUnit);
4355 : else
4356 93 : bWriteUserUnit = (pszDPI == nullptr);
4357 :
4358 95 : double dfUserUnit = dfDPI * USER_UNIT_IN_INCH;
4359 95 : double dfWidthInUserUnit =
4360 95 : nWidth / dfUserUnit + sMargins.nLeft + sMargins.nRight;
4361 95 : double dfHeightInUserUnit =
4362 95 : nHeight / dfUserUnit + sMargins.nBottom + sMargins.nTop;
4363 95 : if (dfWidthInUserUnit > MAXIMUM_SIZE_IN_UNITS ||
4364 : dfHeightInUserUnit > MAXIMUM_SIZE_IN_UNITS)
4365 : {
4366 12 : if (pszDPI == nullptr)
4367 : {
4368 8 : if (sMargins.nLeft + sMargins.nRight >= MAXIMUM_SIZE_IN_UNITS ||
4369 6 : sMargins.nBottom + sMargins.nTop >= MAXIMUM_SIZE_IN_UNITS)
4370 : {
4371 4 : CPLError(
4372 : CE_Warning, CPLE_AppDefined,
4373 : "Margins too big compared to maximum page dimension (%d) "
4374 : "in user units allowed by Acrobat",
4375 : MAXIMUM_SIZE_IN_UNITS);
4376 : }
4377 : else
4378 : {
4379 4 : if (dfWidthInUserUnit >= dfHeightInUserUnit)
4380 : {
4381 2 : dfDPI = ceil(double(nWidth) /
4382 2 : (MAXIMUM_SIZE_IN_UNITS -
4383 2 : (sMargins.nLeft + sMargins.nRight)) /
4384 : USER_UNIT_IN_INCH);
4385 : }
4386 : else
4387 : {
4388 2 : dfDPI = ceil(double(nHeight) /
4389 2 : (MAXIMUM_SIZE_IN_UNITS -
4390 2 : (sMargins.nBottom + sMargins.nTop)) /
4391 : USER_UNIT_IN_INCH);
4392 : }
4393 4 : CPLDebug("PDF",
4394 : "Adjusting DPI to %d so that page dimension in "
4395 : "user units remain in what is accepted by Acrobat",
4396 : static_cast<int>(dfDPI));
4397 : }
4398 : }
4399 : else
4400 : {
4401 4 : CPLError(CE_Warning, CPLE_AppDefined,
4402 : "The page dimension in user units is %d x %d whereas the "
4403 : "maximum allowed by Acrobat is %d x %d",
4404 4 : static_cast<int>(dfWidthInUserUnit + 0.5),
4405 4 : static_cast<int>(dfHeightInUserUnit + 0.5),
4406 : MAXIMUM_SIZE_IN_UNITS, MAXIMUM_SIZE_IN_UNITS);
4407 : }
4408 : }
4409 :
4410 95 : if (dfDPI < DEFAULT_DPI)
4411 0 : dfDPI = DEFAULT_DPI;
4412 :
4413 : const char *pszClippingExtent =
4414 95 : CSLFetchNameValue(papszOptions, "CLIPPING_EXTENT");
4415 95 : int bUseClippingExtent = FALSE;
4416 95 : double adfClippingExtent[4] = {0.0, 0.0, 0.0, 0.0};
4417 95 : if (pszClippingExtent != nullptr)
4418 : {
4419 2 : char **papszTokens = CSLTokenizeString2(pszClippingExtent, ",", 0);
4420 2 : if (CSLCount(papszTokens) == 4)
4421 : {
4422 2 : bUseClippingExtent = TRUE;
4423 2 : adfClippingExtent[0] = CPLAtof(papszTokens[0]);
4424 2 : adfClippingExtent[1] = CPLAtof(papszTokens[1]);
4425 2 : adfClippingExtent[2] = CPLAtof(papszTokens[2]);
4426 2 : adfClippingExtent[3] = CPLAtof(papszTokens[3]);
4427 2 : if (adfClippingExtent[0] > adfClippingExtent[2] ||
4428 2 : adfClippingExtent[1] > adfClippingExtent[3])
4429 : {
4430 0 : CPLError(CE_Warning, CPLE_AppDefined,
4431 : "Invalid value for CLIPPING_EXTENT. Should be "
4432 : "xmin,ymin,xmax,ymax");
4433 0 : bUseClippingExtent = FALSE;
4434 : }
4435 :
4436 2 : if (bUseClippingExtent)
4437 : {
4438 : double adfGeoTransform[6];
4439 2 : if (poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None)
4440 : {
4441 2 : if (adfGeoTransform[2] != 0.0 || adfGeoTransform[4] != 0.0)
4442 : {
4443 0 : CPLError(CE_Warning, CPLE_AppDefined,
4444 : "Cannot use CLIPPING_EXTENT because main "
4445 : "raster has a rotated geotransform");
4446 0 : bUseClippingExtent = FALSE;
4447 : }
4448 : }
4449 : else
4450 : {
4451 0 : CPLError(CE_Warning, CPLE_AppDefined,
4452 : "Cannot use CLIPPING_EXTENT because main raster "
4453 : "has no geotransform");
4454 0 : bUseClippingExtent = FALSE;
4455 : }
4456 : }
4457 : }
4458 2 : CSLDestroy(papszTokens);
4459 : }
4460 :
4461 95 : const char *pszLayerName = CSLFetchNameValue(papszOptions, "LAYER_NAME");
4462 :
4463 : const char *pszExtraImages =
4464 95 : CSLFetchNameValue(papszOptions, "EXTRA_IMAGES");
4465 : const char *pszExtraStream =
4466 95 : CSLFetchNameValue(papszOptions, "EXTRA_STREAM");
4467 : const char *pszExtraLayerName =
4468 95 : CSLFetchNameValue(papszOptions, "EXTRA_LAYER_NAME");
4469 :
4470 : const char *pszOGRDataSource =
4471 95 : CSLFetchNameValue(papszOptions, "OGR_DATASOURCE");
4472 : const char *pszOGRDisplayField =
4473 95 : CSLFetchNameValue(papszOptions, "OGR_DISPLAY_FIELD");
4474 : const char *pszOGRDisplayLayerNames =
4475 95 : CSLFetchNameValue(papszOptions, "OGR_DISPLAY_LAYER_NAMES");
4476 : const char *pszOGRLinkField =
4477 95 : CSLFetchNameValue(papszOptions, "OGR_LINK_FIELD");
4478 : const bool bWriteOGRAttributes =
4479 95 : CPLFetchBool(papszOptions, "OGR_WRITE_ATTRIBUTES", true);
4480 :
4481 : const char *pszExtraRasters =
4482 95 : CSLFetchNameValue(papszOptions, "EXTRA_RASTERS");
4483 : const char *pszExtraRastersLayerName =
4484 95 : CSLFetchNameValue(papszOptions, "EXTRA_RASTERS_LAYER_NAME");
4485 :
4486 95 : const char *pszOffLayers = CSLFetchNameValue(papszOptions, "OFF_LAYERS");
4487 : const char *pszExclusiveLayers =
4488 95 : CSLFetchNameValue(papszOptions, "EXCLUSIVE_LAYERS");
4489 :
4490 95 : const char *pszJavascript = CSLFetchNameValue(papszOptions, "JAVASCRIPT");
4491 : const char *pszJavascriptFile =
4492 95 : CSLFetchNameValue(papszOptions, "JAVASCRIPT_FILE");
4493 :
4494 : /* -------------------------------------------------------------------- */
4495 : /* Create file. */
4496 : /* -------------------------------------------------------------------- */
4497 95 : VSILFILE *fp = VSIFOpenL(pszFilename, "wb");
4498 95 : if (fp == nullptr)
4499 : {
4500 3 : CPLError(CE_Failure, CPLE_OpenFailed, "Unable to create PDF file %s.\n",
4501 : pszFilename);
4502 3 : return nullptr;
4503 : }
4504 :
4505 184 : GDALPDFWriter oWriter(fp);
4506 :
4507 92 : GDALDataset *poClippingDS = poSrcDS;
4508 92 : if (bUseClippingExtent)
4509 2 : poClippingDS = new GDALPDFClippingDataset(poSrcDS, adfClippingExtent);
4510 :
4511 92 : if (CPLFetchBool(papszOptions, "WRITE_INFO", true))
4512 90 : oWriter.SetInfo(poSrcDS, papszOptions);
4513 92 : oWriter.SetXMP(poClippingDS, pszXMP);
4514 :
4515 92 : oWriter.StartPage(poClippingDS, dfDPI, bWriteUserUnit, pszGEO_ENCODING,
4516 : pszNEATLINE, &sMargins, eStreamCompressMethod,
4517 92 : pszOGRDataSource != nullptr && bWriteOGRAttributes);
4518 :
4519 : int bRet;
4520 :
4521 92 : if (!bUseClippingExtent)
4522 : {
4523 90 : bRet = oWriter.WriteImagery(poSrcDS, pszLayerName, eCompressMethod,
4524 : nPredictor, nJPEGQuality,
4525 : pszJPEG2000_DRIVER, nBlockXSize,
4526 : nBlockYSize, pfnProgress, pProgressData);
4527 : }
4528 : else
4529 : {
4530 2 : bRet = oWriter.WriteClippedImagery(
4531 : poSrcDS, pszLayerName, eCompressMethod, nPredictor, nJPEGQuality,
4532 : pszJPEG2000_DRIVER, nBlockXSize, nBlockYSize, pfnProgress,
4533 : pProgressData);
4534 : }
4535 :
4536 : char **papszExtraRasters =
4537 92 : CSLTokenizeString2(pszExtraRasters ? pszExtraRasters : "", ",", 0);
4538 92 : char **papszExtraRastersLayerName = CSLTokenizeString2(
4539 : pszExtraRastersLayerName ? pszExtraRastersLayerName : "", ",", 0);
4540 : int bUseExtraRastersLayerName =
4541 92 : (CSLCount(papszExtraRasters) == CSLCount(papszExtraRastersLayerName));
4542 92 : int bUseExtraRasters = TRUE;
4543 :
4544 92 : const char *pszClippingProjectionRef = poSrcDS->GetProjectionRef();
4545 92 : if (CSLCount(papszExtraRasters) != 0)
4546 : {
4547 : double adfGeoTransform[6];
4548 2 : if (poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None)
4549 : {
4550 2 : if (adfGeoTransform[2] != 0.0 || adfGeoTransform[4] != 0.0)
4551 : {
4552 0 : CPLError(CE_Warning, CPLE_AppDefined,
4553 : "Cannot use EXTRA_RASTERS because main raster has a "
4554 : "rotated geotransform");
4555 0 : bUseExtraRasters = FALSE;
4556 : }
4557 : }
4558 : else
4559 : {
4560 0 : CPLError(CE_Warning, CPLE_AppDefined,
4561 : "Cannot use EXTRA_RASTERS because main raster has no "
4562 : "geotransform");
4563 0 : bUseExtraRasters = FALSE;
4564 : }
4565 2 : if (bUseExtraRasters && (pszClippingProjectionRef == nullptr ||
4566 2 : pszClippingProjectionRef[0] == '\0'))
4567 : {
4568 0 : CPLError(CE_Warning, CPLE_AppDefined,
4569 : "Cannot use EXTRA_RASTERS because main raster has no "
4570 : "projection");
4571 0 : bUseExtraRasters = FALSE;
4572 : }
4573 : }
4574 :
4575 94 : for (int i = 0; bRet && bUseExtraRasters && papszExtraRasters[i] != nullptr;
4576 : i++)
4577 : {
4578 : auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
4579 2 : papszExtraRasters[i], GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
4580 4 : nullptr, nullptr, nullptr));
4581 2 : if (poDS != nullptr)
4582 : {
4583 : double adfGeoTransform[6];
4584 2 : int bUseRaster = TRUE;
4585 2 : if (poDS->GetGeoTransform(adfGeoTransform) == CE_None)
4586 : {
4587 2 : if (adfGeoTransform[2] != 0.0 || adfGeoTransform[4] != 0.0)
4588 : {
4589 0 : CPLError(
4590 : CE_Warning, CPLE_AppDefined,
4591 : "Cannot use %s because it has a rotated geotransform",
4592 0 : papszExtraRasters[i]);
4593 0 : bUseRaster = FALSE;
4594 : }
4595 : }
4596 : else
4597 : {
4598 0 : CPLError(CE_Warning, CPLE_AppDefined,
4599 : "Cannot use %s because it has no geotransform",
4600 0 : papszExtraRasters[i]);
4601 0 : bUseRaster = FALSE;
4602 : }
4603 2 : const char *pszProjectionRef = poDS->GetProjectionRef();
4604 2 : if (bUseRaster &&
4605 2 : (pszProjectionRef == nullptr || pszProjectionRef[0] == '\0'))
4606 : {
4607 0 : CPLError(CE_Warning, CPLE_AppDefined,
4608 : "Cannot use %s because it has no projection",
4609 0 : papszExtraRasters[i]);
4610 0 : bUseRaster = FALSE;
4611 : }
4612 2 : if (bUseRaster)
4613 : {
4614 2 : if (pszClippingProjectionRef != nullptr &&
4615 2 : pszProjectionRef != nullptr &&
4616 2 : !EQUAL(pszClippingProjectionRef, pszProjectionRef))
4617 : {
4618 : OGRSpatialReferenceH hClippingSRS =
4619 2 : OSRNewSpatialReference(pszClippingProjectionRef);
4620 : OGRSpatialReferenceH hSRS =
4621 2 : OSRNewSpatialReference(pszProjectionRef);
4622 2 : if (!OSRIsSame(hClippingSRS, hSRS))
4623 : {
4624 0 : CPLError(CE_Warning, CPLE_AppDefined,
4625 : "Cannot use %s because it has a different "
4626 : "projection than main dataset",
4627 0 : papszExtraRasters[i]);
4628 0 : bUseRaster = FALSE;
4629 : }
4630 2 : OSRDestroySpatialReference(hClippingSRS);
4631 2 : OSRDestroySpatialReference(hSRS);
4632 : }
4633 : }
4634 2 : if (bUseRaster)
4635 : {
4636 4 : bRet = oWriter.WriteClippedImagery(
4637 : poDS.get(),
4638 2 : bUseExtraRastersLayerName ? papszExtraRastersLayerName[i]
4639 : : nullptr,
4640 : eCompressMethod, nPredictor, nJPEGQuality,
4641 : pszJPEG2000_DRIVER, nBlockXSize, nBlockYSize, nullptr,
4642 : nullptr);
4643 : }
4644 : }
4645 : }
4646 :
4647 92 : CSLDestroy(papszExtraRasters);
4648 92 : CSLDestroy(papszExtraRastersLayerName);
4649 :
4650 92 : if (bRet && pszOGRDataSource != nullptr)
4651 4 : oWriter.WriteOGRDataSource(pszOGRDataSource, pszOGRDisplayField,
4652 : pszOGRDisplayLayerNames, pszOGRLinkField,
4653 : bWriteOGRAttributes);
4654 :
4655 92 : if (bRet)
4656 90 : oWriter.EndPage(pszExtraImages, pszExtraStream, pszExtraLayerName,
4657 : pszOffLayers, pszExclusiveLayers);
4658 :
4659 92 : if (pszJavascript)
4660 2 : oWriter.WriteJavascript(pszJavascript);
4661 90 : else if (pszJavascriptFile)
4662 0 : oWriter.WriteJavascriptFile(pszJavascriptFile);
4663 :
4664 92 : oWriter.Close();
4665 :
4666 92 : if (poClippingDS != poSrcDS)
4667 2 : delete poClippingDS;
4668 :
4669 92 : if (!bRet)
4670 : {
4671 2 : VSIUnlink(pszFilename);
4672 2 : return nullptr;
4673 : }
4674 : else
4675 : {
4676 : #ifdef HAVE_PDF_READ_SUPPORT
4677 90 : GDALDataset *poDS = GDALPDFOpen(pszFilename, GA_ReadOnly);
4678 90 : if (poDS == nullptr)
4679 8 : return nullptr;
4680 82 : char **papszMD = CSLDuplicate(poSrcDS->GetMetadata());
4681 82 : papszMD = CSLMerge(papszMD, poDS->GetMetadata());
4682 82 : const char *pszAOP = CSLFetchNameValue(papszMD, GDALMD_AREA_OR_POINT);
4683 82 : if (pszAOP != nullptr && EQUAL(pszAOP, GDALMD_AOP_AREA))
4684 50 : papszMD = CSLSetNameValue(papszMD, GDALMD_AREA_OR_POINT, nullptr);
4685 82 : poDS->SetMetadata(papszMD);
4686 82 : if (EQUAL(pszGEO_ENCODING, "NONE"))
4687 : {
4688 : double adfGeoTransform[6];
4689 4 : if (poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None)
4690 : {
4691 4 : poDS->SetGeoTransform(adfGeoTransform);
4692 : }
4693 4 : const char *pszProjectionRef = poSrcDS->GetProjectionRef();
4694 4 : if (pszProjectionRef != nullptr && pszProjectionRef[0] != '\0')
4695 : {
4696 4 : poDS->SetProjection(pszProjectionRef);
4697 : }
4698 : }
4699 82 : CSLDestroy(papszMD);
4700 82 : return poDS;
4701 : #else
4702 : return new GDALFakePDFDataset();
4703 : #endif
4704 : }
4705 : }
|