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