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