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