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 : GDALDatasetH hImageDS =
2081 5 : GDALDataset::ToHandle(GDALDataset::Open(
2082 : os.osSymbolId, GDAL_OF_RASTER));
2083 5 : if (hImageDS != nullptr)
2084 : {
2085 5 : os.nImageWidth = GDALGetRasterXSize(hImageDS);
2086 5 : os.nImageHeight = GDALGetRasterYSize(hImageDS);
2087 :
2088 : os.nImageSymbolId = WriteBlock(
2089 : GDALDataset::FromHandle(hImageDS), 0, 0,
2090 : os.nImageWidth, os.nImageHeight,
2091 5 : GDALPDFObjectNum(), COMPRESS_DEFAULT, 0, -1,
2092 5 : nullptr, nullptr, nullptr);
2093 5 : GDALClose(hImageDS);
2094 : }
2095 :
2096 5 : GDALPDFImageDesc oDesc;
2097 5 : oDesc.nImageId = os.nImageSymbolId;
2098 5 : oDesc.dfXOff = 0;
2099 5 : oDesc.dfYOff = 0;
2100 5 : oDesc.dfXSize = os.nImageWidth;
2101 5 : oDesc.dfYSize = os.nImageHeight;
2102 5 : oMapSymbolFilenameToDesc[os.osSymbolId] = oDesc;
2103 : }
2104 : else
2105 : {
2106 : const GDALPDFImageDesc &oDesc =
2107 0 : oMapSymbolFilenameToDesc[os.osSymbolId];
2108 0 : os.nImageSymbolId = oDesc.nImageId;
2109 0 : os.nImageWidth = static_cast<int>(oDesc.dfXSize);
2110 0 : os.nImageHeight = static_cast<int>(oDesc.dfYSize);
2111 : }
2112 : }
2113 : }
2114 :
2115 : double dfVal =
2116 51 : OGR_ST_GetParamDbl(hTool, OGRSTSymbolSize, &bIsNull);
2117 51 : if (!bIsNull)
2118 : {
2119 47 : os.dfSymbolSize = dfVal;
2120 : }
2121 :
2122 : const char *pszColor =
2123 51 : OGR_ST_GetParamStr(hTool, OGRSTSymbolColor, &bIsNull);
2124 51 : if (pszColor && !bIsNull)
2125 : {
2126 50 : unsigned int nRed = 0;
2127 50 : unsigned int nGreen = 0;
2128 50 : unsigned int nBlue = 0;
2129 50 : unsigned int nAlpha = 255;
2130 50 : int nVals = sscanf(pszColor, "#%2x%2x%2x%2x", &nRed,
2131 : &nGreen, &nBlue, &nAlpha);
2132 50 : if (nVals >= 3)
2133 : {
2134 50 : os.bSymbolColorDefined = TRUE;
2135 50 : os.nSymbolR = nRed;
2136 50 : os.nSymbolG = nGreen;
2137 50 : os.nSymbolB = nBlue;
2138 50 : if (nVals == 4)
2139 1 : os.nSymbolA = nAlpha;
2140 : }
2141 : }
2142 : }
2143 :
2144 64 : OGR_ST_Destroy(hTool);
2145 : }
2146 : }
2147 125 : OGR_SM_Destroy(hSM);
2148 :
2149 125 : OGRGeometryH hGeom = OGR_F_GetGeometryRef(hFeat);
2150 195 : if (wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint &&
2151 70 : os.bSymbolColorDefined)
2152 : {
2153 50 : os.nPenR = os.nSymbolR;
2154 50 : os.nPenG = os.nSymbolG;
2155 50 : os.nPenB = os.nSymbolB;
2156 50 : os.nPenA = os.nSymbolA;
2157 50 : os.nBrushR = os.nSymbolR;
2158 50 : os.nBrushG = os.nSymbolG;
2159 50 : os.nBrushB = os.nSymbolB;
2160 50 : os.nBrushA = os.nSymbolA;
2161 : }
2162 125 : }
2163 :
2164 : /************************************************************************/
2165 : /* ComputeIntBBox() */
2166 : /************************************************************************/
2167 :
2168 111 : void GDALPDFBaseWriter::ComputeIntBBox(
2169 : OGRGeometryH hGeom, const OGREnvelope &sEnvelope, const double adfMatrix[4],
2170 : const GDALPDFWriter::ObjectStyle &os, double dfRadius, int &bboxXMin,
2171 : int &bboxYMin, int &bboxXMax, int &bboxYMax)
2172 : {
2173 169 : if (wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint &&
2174 58 : os.nImageSymbolId.toBool())
2175 : {
2176 4 : const double dfSemiWidth =
2177 4 : (os.nImageWidth >= os.nImageHeight)
2178 4 : ? dfRadius
2179 0 : : dfRadius * os.nImageWidth / os.nImageHeight;
2180 4 : const double dfSemiHeight =
2181 4 : (os.nImageWidth >= os.nImageHeight)
2182 4 : ? dfRadius * os.nImageHeight / os.nImageWidth
2183 : : dfRadius;
2184 4 : bboxXMin = static_cast<int>(
2185 4 : floor(sEnvelope.MinX * adfMatrix[1] + adfMatrix[0] - dfSemiWidth));
2186 4 : bboxYMin = static_cast<int>(
2187 4 : floor(sEnvelope.MinY * adfMatrix[3] + adfMatrix[2] - dfSemiHeight));
2188 4 : bboxXMax = static_cast<int>(
2189 4 : ceil(sEnvelope.MaxX * adfMatrix[1] + adfMatrix[0] + dfSemiWidth));
2190 4 : bboxYMax = static_cast<int>(
2191 4 : ceil(sEnvelope.MaxY * adfMatrix[3] + adfMatrix[2] + dfSemiHeight));
2192 : }
2193 : else
2194 : {
2195 107 : double dfMargin = os.dfPenWidth;
2196 107 : if (wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint)
2197 : {
2198 54 : if (os.osSymbolId == "ogr-sym-6" || os.osSymbolId == "ogr-sym-7")
2199 : {
2200 8 : const double dfSqrt3 = 1.73205080757;
2201 8 : dfMargin += dfRadius * 2 * dfSqrt3 / 3;
2202 : }
2203 : else
2204 46 : dfMargin += dfRadius;
2205 : }
2206 107 : bboxXMin = static_cast<int>(
2207 107 : floor(sEnvelope.MinX * adfMatrix[1] + adfMatrix[0] - dfMargin));
2208 107 : bboxYMin = static_cast<int>(
2209 107 : floor(sEnvelope.MinY * adfMatrix[3] + adfMatrix[2] - dfMargin));
2210 107 : bboxXMax = static_cast<int>(
2211 107 : ceil(sEnvelope.MaxX * adfMatrix[1] + adfMatrix[0] + dfMargin));
2212 107 : bboxYMax = static_cast<int>(
2213 107 : ceil(sEnvelope.MaxY * adfMatrix[3] + adfMatrix[2] + dfMargin));
2214 : }
2215 111 : }
2216 :
2217 : /************************************************************************/
2218 : /* WriteLink() */
2219 : /************************************************************************/
2220 :
2221 111 : GDALPDFObjectNum GDALPDFBaseWriter::WriteLink(OGRFeatureH hFeat,
2222 : const char *pszOGRLinkField,
2223 : const double adfMatrix[4],
2224 : int bboxXMin, int bboxYMin,
2225 : int bboxXMax, int bboxYMax)
2226 : {
2227 111 : GDALPDFObjectNum nAnnotId;
2228 111 : int iField = -1;
2229 111 : const char *pszLinkVal = nullptr;
2230 168 : if (pszOGRLinkField != nullptr &&
2231 57 : (iField = OGR_FD_GetFieldIndex(OGR_F_GetDefnRef(hFeat),
2232 57 : pszOGRLinkField)) >= 0 &&
2233 173 : OGR_F_IsFieldSetAndNotNull(hFeat, iField) &&
2234 5 : strcmp((pszLinkVal = OGR_F_GetFieldAsString(hFeat, iField)), "") != 0)
2235 : {
2236 5 : nAnnotId = AllocNewObject();
2237 5 : StartObj(nAnnotId);
2238 : {
2239 5 : GDALPDFDictionaryRW oDict;
2240 5 : oDict.Add("Type", GDALPDFObjectRW::CreateName("Annot"));
2241 5 : oDict.Add("Subtype", GDALPDFObjectRW::CreateName("Link"));
2242 5 : oDict.Add("Rect", &(new GDALPDFArrayRW())
2243 5 : ->Add(bboxXMin)
2244 5 : .Add(bboxYMin)
2245 5 : .Add(bboxXMax)
2246 5 : .Add(bboxYMax));
2247 5 : oDict.Add("A", &(new GDALPDFDictionaryRW())
2248 5 : ->Add("S", GDALPDFObjectRW::CreateName("URI"))
2249 5 : .Add("URI", pszLinkVal));
2250 : oDict.Add("BS",
2251 5 : &(new GDALPDFDictionaryRW())
2252 5 : ->Add("Type", GDALPDFObjectRW::CreateName("Border"))
2253 5 : .Add("S", GDALPDFObjectRW::CreateName("S"))
2254 5 : .Add("W", 0));
2255 5 : oDict.Add("Border", &(new GDALPDFArrayRW())->Add(0).Add(0).Add(0));
2256 5 : oDict.Add("H", GDALPDFObjectRW::CreateName("I"));
2257 :
2258 5 : OGRGeometryH hGeom = OGR_F_GetGeometryRef(hFeat);
2259 10 : if (wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPolygon &&
2260 5 : OGR_G_GetGeometryCount(hGeom) == 1)
2261 : {
2262 5 : OGRGeometryH hSubGeom = OGR_G_GetGeometryRef(hGeom, 0);
2263 5 : int nPoints = OGR_G_GetPointCount(hSubGeom);
2264 5 : if (nPoints == 4 || nPoints == 5)
2265 : {
2266 10 : std::vector<double> adfX, adfY;
2267 30 : for (int i = 0; i < nPoints; i++)
2268 : {
2269 25 : double dfX = OGR_G_GetX(hSubGeom, i) * adfMatrix[1] +
2270 25 : adfMatrix[0];
2271 25 : double dfY = OGR_G_GetY(hSubGeom, i) * adfMatrix[3] +
2272 25 : adfMatrix[2];
2273 25 : adfX.push_back(dfX);
2274 25 : adfY.push_back(dfY);
2275 : }
2276 5 : if (nPoints == 4)
2277 : {
2278 0 : oDict.Add("QuadPoints", &(new GDALPDFArrayRW())
2279 0 : ->Add(adfX[0])
2280 0 : .Add(adfY[0])
2281 0 : .Add(adfX[1])
2282 0 : .Add(adfY[1])
2283 0 : .Add(adfX[2])
2284 0 : .Add(adfY[2])
2285 0 : .Add(adfX[0])
2286 0 : .Add(adfY[0]));
2287 : }
2288 5 : else if (nPoints == 5)
2289 : {
2290 5 : oDict.Add("QuadPoints", &(new GDALPDFArrayRW())
2291 5 : ->Add(adfX[0])
2292 5 : .Add(adfY[0])
2293 5 : .Add(adfX[1])
2294 5 : .Add(adfY[1])
2295 5 : .Add(adfX[2])
2296 5 : .Add(adfY[2])
2297 5 : .Add(adfX[3])
2298 5 : .Add(adfY[3]));
2299 : }
2300 : }
2301 : }
2302 :
2303 5 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
2304 : }
2305 5 : EndObj();
2306 : }
2307 111 : return nAnnotId;
2308 : }
2309 :
2310 : /************************************************************************/
2311 : /* GenerateDrawingStream() */
2312 : /************************************************************************/
2313 :
2314 118 : CPLString GDALPDFBaseWriter::GenerateDrawingStream(OGRGeometryH hGeom,
2315 : const double adfMatrix[4],
2316 : ObjectStyle &os,
2317 : double dfRadius)
2318 : {
2319 118 : CPLString osDS;
2320 :
2321 118 : if (!os.nImageSymbolId.toBool())
2322 : {
2323 226 : osDS += CPLOPrintf("%f w\n"
2324 : "0 J\n"
2325 : "0 j\n"
2326 : "10 M\n"
2327 : "[%s]0 d\n",
2328 113 : os.dfPenWidth, os.osDashArray.c_str());
2329 :
2330 113 : osDS += CPLOPrintf("%f %f %f RG\n", os.nPenR / 255.0, os.nPenG / 255.0,
2331 113 : os.nPenB / 255.0);
2332 113 : osDS += CPLOPrintf("%f %f %f rg\n", os.nBrushR / 255.0,
2333 113 : os.nBrushG / 255.0, os.nBrushB / 255.0);
2334 : }
2335 :
2336 236 : if ((os.bHasPenBrushOrSymbol || os.osLabelText.empty()) &&
2337 118 : wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint)
2338 : {
2339 63 : double dfX = OGR_G_GetX(hGeom, 0) * adfMatrix[1] + adfMatrix[0];
2340 63 : double dfY = OGR_G_GetY(hGeom, 0) * adfMatrix[3] + adfMatrix[2];
2341 :
2342 63 : if (os.nImageSymbolId.toBool())
2343 : {
2344 5 : const double dfSemiWidth =
2345 5 : (os.nImageWidth >= os.nImageHeight)
2346 5 : ? dfRadius
2347 0 : : dfRadius * os.nImageWidth / os.nImageHeight;
2348 5 : const double dfSemiHeight =
2349 5 : (os.nImageWidth >= os.nImageHeight)
2350 5 : ? dfRadius * os.nImageHeight / os.nImageWidth
2351 : : dfRadius;
2352 10 : osDS += CPLOPrintf("%f 0 0 %f %f %f cm\n", 2 * dfSemiWidth,
2353 : 2 * dfSemiHeight, dfX - dfSemiWidth,
2354 5 : dfY - dfSemiHeight);
2355 5 : osDS += CPLOPrintf("/SymImage%d Do\n", os.nImageSymbolId.toInt());
2356 : }
2357 58 : else if (os.osSymbolId == "")
2358 14 : os.osSymbolId = "ogr-sym-3"; /* symbol by default */
2359 84 : else if (!(os.osSymbolId == "ogr-sym-0" ||
2360 40 : os.osSymbolId == "ogr-sym-1" ||
2361 32 : os.osSymbolId == "ogr-sym-2" ||
2362 28 : os.osSymbolId == "ogr-sym-3" ||
2363 24 : os.osSymbolId == "ogr-sym-4" ||
2364 20 : os.osSymbolId == "ogr-sym-5" ||
2365 16 : os.osSymbolId == "ogr-sym-6" ||
2366 12 : os.osSymbolId == "ogr-sym-7" ||
2367 8 : os.osSymbolId == "ogr-sym-8" ||
2368 4 : os.osSymbolId == "ogr-sym-9"))
2369 : {
2370 0 : CPLDebug("PDF", "Unhandled symbol id : %s. Using ogr-sym-3 instead",
2371 : os.osSymbolId.c_str());
2372 0 : os.osSymbolId = "ogr-sym-3";
2373 : }
2374 :
2375 63 : if (os.osSymbolId == "ogr-sym-0") /* cross (+) */
2376 : {
2377 4 : osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius, dfY);
2378 4 : osDS += CPLOPrintf("%f %f l\n", dfX + dfRadius, dfY);
2379 4 : osDS += CPLOPrintf("%f %f m\n", dfX, dfY - dfRadius);
2380 4 : osDS += CPLOPrintf("%f %f l\n", dfX, dfY + dfRadius);
2381 4 : osDS += CPLOPrintf("S\n");
2382 : }
2383 59 : else if (os.osSymbolId == "ogr-sym-1") /* diagcross (X) */
2384 : {
2385 8 : osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius, dfY - dfRadius);
2386 8 : osDS += CPLOPrintf("%f %f l\n", dfX + dfRadius, dfY + dfRadius);
2387 8 : osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius, dfY + dfRadius);
2388 8 : osDS += CPLOPrintf("%f %f l\n", dfX + dfRadius, dfY - dfRadius);
2389 8 : osDS += CPLOPrintf("S\n");
2390 : }
2391 98 : else if (os.osSymbolId == "ogr-sym-2" ||
2392 47 : os.osSymbolId == "ogr-sym-3") /* circle */
2393 : {
2394 : /* See http://www.whizkidtech.redprince.net/bezier/circle/kappa/ */
2395 22 : const double dfKappa = 0.5522847498;
2396 :
2397 22 : osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius, dfY);
2398 : osDS +=
2399 22 : CPLOPrintf("%f %f %f %f %f %f c\n", dfX - dfRadius,
2400 22 : dfY - dfRadius * dfKappa, dfX - dfRadius * dfKappa,
2401 22 : dfY - dfRadius, dfX, dfY - dfRadius);
2402 : osDS +=
2403 22 : CPLOPrintf("%f %f %f %f %f %f c\n", dfX + dfRadius * dfKappa,
2404 : dfY - dfRadius, dfX + dfRadius,
2405 22 : dfY - dfRadius * dfKappa, dfX + dfRadius, dfY);
2406 : osDS +=
2407 22 : CPLOPrintf("%f %f %f %f %f %f c\n", dfX + dfRadius,
2408 22 : dfY + dfRadius * dfKappa, dfX + dfRadius * dfKappa,
2409 22 : dfY + dfRadius, dfX, dfY + dfRadius);
2410 : osDS +=
2411 22 : CPLOPrintf("%f %f %f %f %f %f c\n", dfX - dfRadius * dfKappa,
2412 : dfY + dfRadius, dfX - dfRadius,
2413 22 : dfY + dfRadius * dfKappa, dfX - dfRadius, dfY);
2414 22 : if (os.osSymbolId == "ogr-sym-2")
2415 4 : osDS += CPLOPrintf("s\n"); /* not filled */
2416 : else
2417 18 : osDS += CPLOPrintf("b*\n"); /* filled */
2418 : }
2419 54 : else if (os.osSymbolId == "ogr-sym-4" ||
2420 25 : os.osSymbolId == "ogr-sym-5") /* square */
2421 : {
2422 8 : osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius, dfY + dfRadius);
2423 8 : osDS += CPLOPrintf("%f %f l\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 : if (os.osSymbolId == "ogr-sym-4")
2427 4 : osDS += CPLOPrintf("s\n"); /* not filled */
2428 : else
2429 4 : osDS += CPLOPrintf("b*\n"); /* filled */
2430 : }
2431 38 : else if (os.osSymbolId == "ogr-sym-6" ||
2432 17 : os.osSymbolId == "ogr-sym-7") /* triangle */
2433 : {
2434 8 : const double dfSqrt3 = 1.73205080757;
2435 8 : osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius,
2436 8 : dfY - dfRadius * dfSqrt3 / 3);
2437 : osDS +=
2438 8 : CPLOPrintf("%f %f l\n", dfX, dfY + 2 * dfRadius * dfSqrt3 / 3);
2439 8 : osDS += CPLOPrintf("%f %f l\n", dfX + dfRadius,
2440 8 : dfY - dfRadius * dfSqrt3 / 3);
2441 8 : if (os.osSymbolId == "ogr-sym-6")
2442 4 : osDS += CPLOPrintf("s\n"); /* not filled */
2443 : else
2444 4 : osDS += CPLOPrintf("b*\n"); /* filled */
2445 : }
2446 22 : else if (os.osSymbolId == "ogr-sym-8" ||
2447 9 : os.osSymbolId == "ogr-sym-9") /* star */
2448 : {
2449 8 : const double dfSin18divSin126 = 0.38196601125;
2450 8 : osDS += CPLOPrintf("%f %f m\n", dfX, dfY + dfRadius);
2451 80 : for (int i = 1; i < 10; i++)
2452 : {
2453 72 : double dfFactor = ((i % 2) == 1) ? dfSin18divSin126 : 1.0;
2454 72 : osDS += CPLOPrintf("%f %f l\n",
2455 72 : dfX + cos(M_PI / 2 - i * M_PI * 36 / 180) *
2456 72 : dfRadius * dfFactor,
2457 72 : dfY + sin(M_PI / 2 - i * M_PI * 36 / 180) *
2458 72 : dfRadius * dfFactor);
2459 : }
2460 8 : if (os.osSymbolId == "ogr-sym-8")
2461 4 : osDS += CPLOPrintf("s\n"); /* not filled */
2462 : else
2463 4 : osDS += CPLOPrintf("b*\n"); /* filled */
2464 : }
2465 : }
2466 : else
2467 : {
2468 55 : DrawGeometry(osDS, hGeom, adfMatrix);
2469 : }
2470 :
2471 118 : return osDS;
2472 : }
2473 :
2474 : /************************************************************************/
2475 : /* WriteAttributes() */
2476 : /************************************************************************/
2477 :
2478 92 : GDALPDFObjectNum GDALPDFBaseWriter::WriteAttributes(
2479 : OGRFeatureH hFeat, const std::vector<CPLString> &aosIncludedFields,
2480 : const char *pszOGRDisplayField, int nMCID, const GDALPDFObjectNum &oParent,
2481 : const GDALPDFObjectNum &oPage, CPLString &osOutFeatureName)
2482 : {
2483 :
2484 92 : int iField = -1;
2485 92 : if (pszOGRDisplayField)
2486 : iField =
2487 9 : OGR_FD_GetFieldIndex(OGR_F_GetDefnRef(hFeat), pszOGRDisplayField);
2488 92 : if (iField >= 0)
2489 4 : osOutFeatureName = OGR_F_GetFieldAsString(hFeat, iField);
2490 : else
2491 : osOutFeatureName =
2492 88 : CPLSPrintf("feature" CPL_FRMT_GIB, OGR_F_GetFID(hFeat));
2493 :
2494 92 : auto nFeatureUserProperties = AllocNewObject();
2495 92 : StartObj(nFeatureUserProperties);
2496 :
2497 92 : GDALPDFDictionaryRW oDict;
2498 :
2499 92 : GDALPDFDictionaryRW *poDictA = new GDALPDFDictionaryRW();
2500 92 : oDict.Add("A", poDictA);
2501 92 : poDictA->Add("O", GDALPDFObjectRW::CreateName("UserProperties"));
2502 :
2503 92 : GDALPDFArrayRW *poArray = new GDALPDFArrayRW();
2504 398 : for (const auto &fieldName : aosIncludedFields)
2505 : {
2506 306 : int i = OGR_F_GetFieldIndex(hFeat, fieldName);
2507 306 : if (i >= 0 && OGR_F_IsFieldSetAndNotNull(hFeat, i))
2508 : {
2509 183 : OGRFieldDefnH hFDefn = OGR_F_GetFieldDefnRef(hFeat, i);
2510 183 : GDALPDFDictionaryRW *poKV = new GDALPDFDictionaryRW();
2511 183 : poKV->Add("N", OGR_Fld_GetNameRef(hFDefn));
2512 183 : if (OGR_Fld_GetType(hFDefn) == OFTInteger)
2513 40 : poKV->Add("V", OGR_F_GetFieldAsInteger(hFeat, i));
2514 143 : else if (OGR_Fld_GetType(hFDefn) == OFTReal)
2515 32 : poKV->Add("V", OGR_F_GetFieldAsDouble(hFeat, i));
2516 : else
2517 111 : poKV->Add("V", OGR_F_GetFieldAsString(hFeat, i));
2518 183 : poArray->Add(poKV);
2519 : }
2520 : }
2521 :
2522 92 : poDictA->Add("P", poArray);
2523 :
2524 92 : oDict.Add("K", nMCID);
2525 92 : oDict.Add("P", oParent, 0);
2526 92 : oDict.Add("Pg", oPage, 0);
2527 92 : oDict.Add("S", GDALPDFObjectRW::CreateName("feature"));
2528 92 : oDict.Add("T", osOutFeatureName);
2529 :
2530 92 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
2531 :
2532 92 : EndObj();
2533 :
2534 184 : return nFeatureUserProperties;
2535 : }
2536 :
2537 : /************************************************************************/
2538 : /* WriteLabel() */
2539 : /************************************************************************/
2540 :
2541 8 : GDALPDFObjectNum GDALPDFBaseWriter::WriteLabel(
2542 : OGRGeometryH hGeom, const double adfMatrix[4], ObjectStyle &os,
2543 : PDFCompressMethod eStreamCompressMethod, double bboxXMin, double bboxYMin,
2544 : double bboxXMax, double bboxYMax)
2545 : {
2546 : /* -------------------------------------------------------------- */
2547 : /* Work out the text metrics for alignment purposes */
2548 : /* -------------------------------------------------------------- */
2549 : double dfWidth, dfHeight;
2550 8 : CalculateText(os.osLabelText, os.osTextFont, os.dfTextSize, os.bTextBold,
2551 8 : os.bTextItalic, dfWidth, dfHeight);
2552 8 : dfWidth *= os.dfTextStretch;
2553 :
2554 8 : if (os.nTextAnchor % 3 == 2) // horizontal center
2555 : {
2556 0 : os.dfTextDx -= (dfWidth / 2) * cos(os.dfTextAngle);
2557 0 : os.dfTextDy -= (dfWidth / 2) * sin(os.dfTextAngle);
2558 : }
2559 8 : else if (os.nTextAnchor % 3 == 0) // right
2560 : {
2561 0 : os.dfTextDx -= dfWidth * cos(os.dfTextAngle);
2562 0 : os.dfTextDy -= dfWidth * sin(os.dfTextAngle);
2563 : }
2564 :
2565 8 : if (os.nTextAnchor >= 4 && os.nTextAnchor <= 6) // vertical center
2566 : {
2567 4 : os.dfTextDx += (dfHeight / 2) * sin(os.dfTextAngle);
2568 4 : os.dfTextDy -= (dfHeight / 2) * cos(os.dfTextAngle);
2569 : }
2570 4 : else if (os.nTextAnchor >= 7 && os.nTextAnchor <= 9) // top
2571 : {
2572 0 : os.dfTextDx += dfHeight * sin(os.dfTextAngle);
2573 0 : os.dfTextDy -= dfHeight * cos(os.dfTextAngle);
2574 : }
2575 : // modes 10,11,12 (baseline) unsupported for the time being
2576 :
2577 : /* -------------------------------------------------------------- */
2578 : /* Write object dictionary */
2579 : /* -------------------------------------------------------------- */
2580 8 : auto nObjectId = AllocNewObject();
2581 8 : GDALPDFDictionaryRW oDict;
2582 :
2583 8 : oDict.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
2584 8 : .Add("BBox", &((new GDALPDFArrayRW())->Add(bboxXMin).Add(bboxYMin))
2585 8 : .Add(bboxXMax)
2586 8 : .Add(bboxYMax))
2587 8 : .Add("Subtype", GDALPDFObjectRW::CreateName("Form"));
2588 :
2589 8 : GDALPDFDictionaryRW *poResources = new GDALPDFDictionaryRW();
2590 :
2591 8 : if (os.nTextA != 255)
2592 : {
2593 1 : GDALPDFDictionaryRW *poGS1 = new GDALPDFDictionaryRW();
2594 1 : poGS1->Add("Type", GDALPDFObjectRW::CreateName("ExtGState"));
2595 1 : poGS1->Add("ca", (os.nTextA == 127 || os.nTextA == 128)
2596 : ? 0.5
2597 2 : : os.nTextA / 255.0);
2598 :
2599 1 : GDALPDFDictionaryRW *poExtGState = new GDALPDFDictionaryRW();
2600 1 : poExtGState->Add("GS1", poGS1);
2601 :
2602 1 : poResources->Add("ExtGState", poExtGState);
2603 : }
2604 :
2605 8 : GDALPDFDictionaryRW *poDictF1 = new GDALPDFDictionaryRW();
2606 8 : poDictF1->Add("Type", GDALPDFObjectRW::CreateName("Font"));
2607 8 : poDictF1->Add("BaseFont", GDALPDFObjectRW::CreateName(os.osTextFont));
2608 8 : poDictF1->Add("Encoding", GDALPDFObjectRW::CreateName("WinAnsiEncoding"));
2609 8 : poDictF1->Add("Subtype", GDALPDFObjectRW::CreateName("Type1"));
2610 :
2611 8 : GDALPDFDictionaryRW *poDictFont = new GDALPDFDictionaryRW();
2612 8 : poDictFont->Add("F1", poDictF1);
2613 8 : poResources->Add("Font", poDictFont);
2614 :
2615 8 : oDict.Add("Resources", poResources);
2616 :
2617 8 : StartObjWithStream(nObjectId, oDict,
2618 : eStreamCompressMethod != COMPRESS_NONE);
2619 :
2620 : /* -------------------------------------------------------------- */
2621 : /* Write object stream */
2622 : /* -------------------------------------------------------------- */
2623 :
2624 : double dfX =
2625 8 : OGR_G_GetX(hGeom, 0) * adfMatrix[1] + adfMatrix[0] + os.dfTextDx;
2626 : double dfY =
2627 8 : OGR_G_GetY(hGeom, 0) * adfMatrix[3] + adfMatrix[2] + os.dfTextDy;
2628 :
2629 8 : VSIFPrintfL(m_fp, "q\n");
2630 8 : VSIFPrintfL(m_fp, "BT\n");
2631 8 : if (os.nTextA != 255)
2632 : {
2633 1 : VSIFPrintfL(m_fp, "/GS1 gs\n");
2634 : }
2635 :
2636 8 : VSIFPrintfL(m_fp, "%f %f %f %f %f %f Tm\n",
2637 8 : cos(os.dfTextAngle) * adfMatrix[1] * os.dfTextStretch,
2638 8 : sin(os.dfTextAngle) * adfMatrix[3] * os.dfTextStretch,
2639 8 : -sin(os.dfTextAngle) * adfMatrix[1],
2640 8 : cos(os.dfTextAngle) * adfMatrix[3], dfX, dfY);
2641 :
2642 8 : VSIFPrintfL(m_fp, "%f %f %f rg\n", os.nTextR / 255.0, os.nTextG / 255.0,
2643 8 : os.nTextB / 255.0);
2644 : // The factor of adfMatrix[1] is introduced in the call to SetUnit near the
2645 : // top of this function. Because we are handling the 2D stretch correctly in
2646 : // Tm above, we don't need that factor here
2647 8 : VSIFPrintfL(m_fp, "/F1 %f Tf\n", os.dfTextSize / adfMatrix[1]);
2648 8 : VSIFPrintfL(m_fp, "(");
2649 73 : for (size_t i = 0; i < os.osLabelText.size(); i++)
2650 : {
2651 130 : if (os.osLabelText[i] == '(' || os.osLabelText[i] == ')' ||
2652 65 : os.osLabelText[i] == '\\')
2653 : {
2654 0 : VSIFPrintfL(m_fp, "\\%c", os.osLabelText[i]);
2655 : }
2656 : else
2657 : {
2658 65 : VSIFPrintfL(m_fp, "%c", os.osLabelText[i]);
2659 : }
2660 : }
2661 8 : VSIFPrintfL(m_fp, ") Tj\n");
2662 8 : VSIFPrintfL(m_fp, "ET\n");
2663 8 : VSIFPrintfL(m_fp, "Q");
2664 :
2665 8 : EndObjWithStream();
2666 :
2667 16 : return nObjectId;
2668 : }
2669 :
2670 : /************************************************************************/
2671 : /* WriteOGRFeature() */
2672 : /************************************************************************/
2673 :
2674 115 : int GDALPDFWriter::WriteOGRFeature(GDALPDFLayerDesc &osVectorDesc,
2675 : OGRFeatureH hFeat,
2676 : OGRCoordinateTransformationH hCT,
2677 : const char *pszOGRDisplayField,
2678 : const char *pszOGRLinkField,
2679 : int bWriteOGRAttributes, int &iObj)
2680 : {
2681 115 : GDALDataset *const poClippingDS = oPageContext.poClippingDS;
2682 115 : const int nHeight = poClippingDS->GetRasterYSize();
2683 115 : const double dfUserUnit = oPageContext.dfDPI * USER_UNIT_IN_INCH;
2684 115 : GDALGeoTransform gt;
2685 115 : poClippingDS->GetGeoTransform(gt);
2686 :
2687 : double adfMatrix[4];
2688 115 : adfMatrix[0] =
2689 115 : -gt.xorig / (gt.xscale * dfUserUnit) + oPageContext.sMargins.nLeft;
2690 115 : adfMatrix[1] = 1.0 / (gt.xscale * dfUserUnit);
2691 115 : adfMatrix[2] =
2692 115 : -(gt.yorig + gt.yscale * nHeight) / (-gt.yscale * dfUserUnit) +
2693 115 : oPageContext.sMargins.nBottom;
2694 115 : adfMatrix[3] = 1.0 / (-gt.yscale * dfUserUnit);
2695 :
2696 115 : OGRGeometryH hGeom = OGR_F_GetGeometryRef(hFeat);
2697 115 : if (hGeom == nullptr)
2698 : {
2699 0 : return TRUE;
2700 : }
2701 :
2702 115 : OGREnvelope sEnvelope;
2703 :
2704 115 : if (hCT != nullptr)
2705 : {
2706 : /* Reproject */
2707 6 : if (OGR_G_Transform(hGeom, hCT) != OGRERR_NONE)
2708 : {
2709 1 : return TRUE;
2710 : }
2711 :
2712 6 : OGREnvelope sRasterEnvelope;
2713 6 : sRasterEnvelope.MinX = gt.xorig;
2714 6 : sRasterEnvelope.MinY =
2715 6 : gt.yorig + poClippingDS->GetRasterYSize() * gt.yscale;
2716 6 : sRasterEnvelope.MaxX =
2717 6 : gt.xorig + poClippingDS->GetRasterXSize() * gt.xscale;
2718 6 : sRasterEnvelope.MaxY = gt.yorig;
2719 :
2720 : // Check that the reprojected geometry intersects the raster envelope.
2721 6 : OGR_G_GetEnvelope(hGeom, &sEnvelope);
2722 6 : if (!(sRasterEnvelope.Intersects(sEnvelope)))
2723 : {
2724 1 : return TRUE;
2725 : }
2726 : }
2727 : else
2728 : {
2729 109 : OGR_G_GetEnvelope(hGeom, &sEnvelope);
2730 : }
2731 :
2732 : /* -------------------------------------------------------------- */
2733 : /* Get style */
2734 : /* -------------------------------------------------------------- */
2735 228 : ObjectStyle os;
2736 114 : GetObjectStyle(nullptr, hFeat, adfMatrix, m_oMapSymbolFilenameToDesc, os);
2737 :
2738 114 : double dfRadius = os.dfSymbolSize * dfUserUnit;
2739 :
2740 : // For a POINT with only a LABEL style string and non-empty text, we do not
2741 : // output any geometry other than the text itself.
2742 : const bool bLabelOnly =
2743 114 : wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint &&
2744 114 : !os.bHasPenBrushOrSymbol && !os.osLabelText.empty();
2745 :
2746 : /* -------------------------------------------------------------- */
2747 : /* Write object dictionary */
2748 : /* -------------------------------------------------------------- */
2749 114 : if (!bLabelOnly)
2750 : {
2751 110 : auto nObjectId = AllocNewObject();
2752 :
2753 110 : osVectorDesc.aIds.push_back(nObjectId);
2754 :
2755 : int bboxXMin, bboxYMin, bboxXMax, bboxYMax;
2756 110 : ComputeIntBBox(hGeom, sEnvelope, adfMatrix, os, dfRadius, bboxXMin,
2757 : bboxYMin, bboxXMax, bboxYMax);
2758 :
2759 : auto nLinkId = WriteLink(hFeat, pszOGRLinkField, adfMatrix, bboxXMin,
2760 110 : bboxYMin, bboxXMax, bboxYMax);
2761 110 : if (nLinkId.toBool())
2762 4 : oPageContext.anAnnotationsId.push_back(nLinkId);
2763 :
2764 220 : GDALPDFDictionaryRW oDict;
2765 110 : GDALPDFArrayRW *poBBOX = new GDALPDFArrayRW();
2766 110 : poBBOX->Add(bboxXMin).Add(bboxYMin).Add(bboxXMax).Add(bboxYMax);
2767 110 : oDict.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
2768 110 : .Add("BBox", poBBOX)
2769 110 : .Add("Subtype", GDALPDFObjectRW::CreateName("Form"));
2770 :
2771 110 : GDALPDFDictionaryRW *poGS1 = new GDALPDFDictionaryRW();
2772 110 : poGS1->Add("Type", GDALPDFObjectRW::CreateName("ExtGState"));
2773 110 : if (os.nPenA != 255)
2774 0 : poGS1->Add("CA", (os.nPenA == 127 || os.nPenA == 128)
2775 : ? 0.5
2776 0 : : os.nPenA / 255.0);
2777 110 : if (os.nBrushA != 255)
2778 0 : poGS1->Add("ca", (os.nBrushA == 127 || os.nBrushA == 128)
2779 : ? 0.5
2780 66 : : os.nBrushA / 255.0);
2781 :
2782 110 : GDALPDFDictionaryRW *poExtGState = new GDALPDFDictionaryRW();
2783 110 : poExtGState->Add("GS1", poGS1);
2784 :
2785 110 : GDALPDFDictionaryRW *poResources = new GDALPDFDictionaryRW();
2786 110 : poResources->Add("ExtGState", poExtGState);
2787 :
2788 110 : if (os.nImageSymbolId.toBool())
2789 : {
2790 4 : GDALPDFDictionaryRW *poDictXObject = new GDALPDFDictionaryRW();
2791 4 : poResources->Add("XObject", poDictXObject);
2792 :
2793 : poDictXObject->Add(
2794 : CPLSPrintf("SymImage%d", os.nImageSymbolId.toInt()),
2795 4 : os.nImageSymbolId, 0);
2796 : }
2797 :
2798 110 : oDict.Add("Resources", poResources);
2799 :
2800 110 : StartObjWithStream(nObjectId, oDict,
2801 110 : oPageContext.eStreamCompressMethod != COMPRESS_NONE);
2802 :
2803 : /* -------------------------------------------------------------- */
2804 : /* Write object stream */
2805 : /* -------------------------------------------------------------- */
2806 110 : VSIFPrintfL(m_fp, "q\n");
2807 :
2808 110 : VSIFPrintfL(m_fp, "/GS1 gs\n");
2809 :
2810 110 : VSIFPrintfL(
2811 : m_fp, "%s",
2812 220 : GenerateDrawingStream(hGeom, adfMatrix, os, dfRadius).c_str());
2813 :
2814 110 : VSIFPrintfL(m_fp, "Q");
2815 :
2816 110 : EndObjWithStream();
2817 : }
2818 : else
2819 : {
2820 4 : osVectorDesc.aIds.push_back(GDALPDFObjectNum());
2821 : }
2822 :
2823 : /* -------------------------------------------------------------- */
2824 : /* Write label */
2825 : /* -------------------------------------------------------------- */
2826 119 : if (!os.osLabelText.empty() &&
2827 5 : wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint)
2828 : {
2829 5 : if (!osVectorDesc.nOCGTextId.toBool())
2830 5 : osVectorDesc.nOCGTextId = WriteOCG("Text", osVectorDesc.nOCGId);
2831 :
2832 5 : int nWidth = poClippingDS->GetRasterXSize();
2833 5 : double dfWidthInUserUnit = nWidth / dfUserUnit +
2834 5 : oPageContext.sMargins.nLeft +
2835 5 : oPageContext.sMargins.nRight;
2836 5 : double dfHeightInUserUnit = nHeight / dfUserUnit +
2837 5 : oPageContext.sMargins.nBottom +
2838 5 : oPageContext.sMargins.nTop;
2839 : auto nObjectId =
2840 : WriteLabel(hGeom, adfMatrix, os, oPageContext.eStreamCompressMethod,
2841 5 : 0, 0, dfWidthInUserUnit, dfHeightInUserUnit);
2842 :
2843 5 : osVectorDesc.aIdsText.push_back(nObjectId);
2844 : }
2845 : else
2846 : {
2847 109 : osVectorDesc.aIdsText.push_back(GDALPDFObjectNum());
2848 : }
2849 :
2850 : /* -------------------------------------------------------------- */
2851 : /* Write feature attributes */
2852 : /* -------------------------------------------------------------- */
2853 114 : GDALPDFObjectNum nFeatureUserProperties;
2854 :
2855 114 : CPLString osFeatureName;
2856 :
2857 114 : if (bWriteOGRAttributes)
2858 : {
2859 : nFeatureUserProperties = WriteAttributes(
2860 84 : hFeat, osVectorDesc.aosIncludedFields, pszOGRDisplayField, iObj,
2861 84 : osVectorDesc.nFeatureLayerId, oPageContext.nPageId, osFeatureName);
2862 : }
2863 :
2864 114 : iObj++;
2865 :
2866 114 : osVectorDesc.aUserPropertiesIds.push_back(nFeatureUserProperties);
2867 114 : osVectorDesc.aFeatureNames.push_back(std::move(osFeatureName));
2868 :
2869 114 : return TRUE;
2870 : }
2871 :
2872 : /************************************************************************/
2873 : /* EndPage() */
2874 : /************************************************************************/
2875 :
2876 73 : int GDALPDFWriter::EndPage(const char *pszExtraImages,
2877 : const char *pszExtraStream,
2878 : const char *pszExtraLayerName,
2879 : const char *pszOffLayers,
2880 : const char *pszExclusiveLayers)
2881 : {
2882 73 : auto nLayerExtraId = WriteOCG(pszExtraLayerName);
2883 73 : if (pszOffLayers)
2884 1 : m_osOffLayers = pszOffLayers;
2885 73 : if (pszExclusiveLayers)
2886 1 : m_osExclusiveLayers = pszExclusiveLayers;
2887 :
2888 : /* -------------------------------------------------------------- */
2889 : /* Write extra images */
2890 : /* -------------------------------------------------------------- */
2891 146 : std::vector<GDALPDFImageDesc> asExtraImageDesc;
2892 73 : if (pszExtraImages)
2893 : {
2894 1 : if (GDALGetDriverCount() == 0)
2895 0 : GDALAllRegister();
2896 :
2897 : char **papszExtraImagesTokens =
2898 1 : CSLTokenizeString2(pszExtraImages, ",", 0);
2899 1 : double dfUserUnit = oPageContext.dfDPI * USER_UNIT_IN_INCH;
2900 1 : int nCount = CSLCount(papszExtraImagesTokens);
2901 3 : for (int i = 0; i + 4 <= nCount; /* */)
2902 : {
2903 2 : const char *pszImageFilename = papszExtraImagesTokens[i + 0];
2904 2 : double dfX = CPLAtof(papszExtraImagesTokens[i + 1]);
2905 2 : double dfY = CPLAtof(papszExtraImagesTokens[i + 2]);
2906 2 : double dfScale = CPLAtof(papszExtraImagesTokens[i + 3]);
2907 2 : const char *pszLinkVal = nullptr;
2908 2 : i += 4;
2909 2 : if (i < nCount &&
2910 1 : STARTS_WITH_CI(papszExtraImagesTokens[i], "link="))
2911 : {
2912 1 : pszLinkVal = papszExtraImagesTokens[i] + 5;
2913 1 : i++;
2914 : }
2915 : auto poImageDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
2916 : pszImageFilename, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
2917 4 : nullptr, nullptr, nullptr));
2918 2 : if (poImageDS)
2919 : {
2920 : auto nImageId = WriteBlock(
2921 : poImageDS.get(), 0, 0, poImageDS->GetRasterXSize(),
2922 0 : poImageDS->GetRasterYSize(), GDALPDFObjectNum(),
2923 2 : COMPRESS_DEFAULT, 0, -1, nullptr, nullptr, nullptr);
2924 :
2925 2 : if (nImageId.toBool())
2926 : {
2927 2 : GDALPDFImageDesc oImageDesc;
2928 2 : oImageDesc.nImageId = nImageId;
2929 2 : oImageDesc.dfXSize =
2930 2 : poImageDS->GetRasterXSize() / dfUserUnit * dfScale;
2931 2 : oImageDesc.dfYSize =
2932 2 : poImageDS->GetRasterYSize() / dfUserUnit * dfScale;
2933 2 : oImageDesc.dfXOff = dfX;
2934 2 : oImageDesc.dfYOff = dfY;
2935 :
2936 2 : asExtraImageDesc.push_back(oImageDesc);
2937 :
2938 2 : if (pszLinkVal != nullptr)
2939 : {
2940 1 : auto nAnnotId = AllocNewObject();
2941 1 : oPageContext.anAnnotationsId.push_back(nAnnotId);
2942 1 : StartObj(nAnnotId);
2943 : {
2944 1 : GDALPDFDictionaryRW oDict;
2945 : oDict.Add("Type",
2946 1 : GDALPDFObjectRW::CreateName("Annot"));
2947 : oDict.Add("Subtype",
2948 1 : GDALPDFObjectRW::CreateName("Link"));
2949 1 : oDict.Add("Rect", &(new GDALPDFArrayRW())
2950 1 : ->Add(oImageDesc.dfXOff)
2951 1 : .Add(oImageDesc.dfYOff)
2952 1 : .Add(oImageDesc.dfXOff +
2953 1 : oImageDesc.dfXSize)
2954 1 : .Add(oImageDesc.dfYOff +
2955 1 : oImageDesc.dfYSize));
2956 : oDict.Add(
2957 : "A",
2958 1 : &(new GDALPDFDictionaryRW())
2959 : ->Add("S",
2960 1 : GDALPDFObjectRW::CreateName("URI"))
2961 1 : .Add("URI", pszLinkVal));
2962 : oDict.Add(
2963 : "BS",
2964 1 : &(new GDALPDFDictionaryRW())
2965 1 : ->Add("Type", GDALPDFObjectRW::CreateName(
2966 1 : "Border"))
2967 1 : .Add("S", GDALPDFObjectRW::CreateName("S"))
2968 1 : .Add("W", 0));
2969 : oDict.Add(
2970 : "Border",
2971 1 : &(new GDALPDFArrayRW())->Add(0).Add(0).Add(0));
2972 1 : oDict.Add("H", GDALPDFObjectRW::CreateName("I"));
2973 :
2974 1 : VSIFPrintfL(m_fp, "%s\n",
2975 2 : oDict.Serialize().c_str());
2976 : }
2977 1 : EndObj();
2978 : }
2979 : }
2980 : }
2981 : }
2982 1 : CSLDestroy(papszExtraImagesTokens);
2983 : }
2984 :
2985 : /* -------------------------------------------------------------- */
2986 : /* Write content stream */
2987 : /* -------------------------------------------------------------- */
2988 73 : GDALPDFDictionaryRW oDictContent;
2989 73 : StartObjWithStream(oPageContext.nContentId, oDictContent,
2990 73 : oPageContext.eStreamCompressMethod != COMPRESS_NONE);
2991 :
2992 : /* -------------------------------------------------------------- */
2993 : /* Write drawing instructions for raster blocks */
2994 : /* -------------------------------------------------------------- */
2995 127 : for (size_t iRaster = 0; iRaster < oPageContext.asRasterDesc.size();
2996 : iRaster++)
2997 : {
2998 54 : const GDALPDFRasterDesc &oDesc = oPageContext.asRasterDesc[iRaster];
2999 54 : if (oDesc.nOCGRasterId.toBool())
3000 3 : VSIFPrintfL(m_fp, "/OC /Lyr%d BDC\n", oDesc.nOCGRasterId.toInt());
3001 :
3002 155 : for (size_t iImage = 0; iImage < oDesc.asImageDesc.size(); iImage++)
3003 : {
3004 101 : VSIFPrintfL(m_fp, "q\n");
3005 : GDALPDFObjectRW *poXSize =
3006 101 : GDALPDFObjectRW::CreateReal(oDesc.asImageDesc[iImage].dfXSize);
3007 : GDALPDFObjectRW *poYSize =
3008 101 : GDALPDFObjectRW::CreateReal(oDesc.asImageDesc[iImage].dfYSize);
3009 : GDALPDFObjectRW *poXOff =
3010 101 : GDALPDFObjectRW::CreateReal(oDesc.asImageDesc[iImage].dfXOff);
3011 : GDALPDFObjectRW *poYOff =
3012 101 : GDALPDFObjectRW::CreateReal(oDesc.asImageDesc[iImage].dfYOff);
3013 404 : VSIFPrintfL(
3014 202 : m_fp, "%s 0 0 %s %s %s cm\n", poXSize->Serialize().c_str(),
3015 303 : poYSize->Serialize().c_str(), poXOff->Serialize().c_str(),
3016 202 : poYOff->Serialize().c_str());
3017 101 : delete poXSize;
3018 101 : delete poYSize;
3019 101 : delete poXOff;
3020 101 : delete poYOff;
3021 101 : VSIFPrintfL(m_fp, "/Image%d Do\n",
3022 101 : oDesc.asImageDesc[iImage].nImageId.toInt());
3023 101 : VSIFPrintfL(m_fp, "Q\n");
3024 : }
3025 :
3026 54 : if (oDesc.nOCGRasterId.toBool())
3027 3 : VSIFPrintfL(m_fp, "EMC\n");
3028 : }
3029 :
3030 : /* -------------------------------------------------------------- */
3031 : /* Write drawing instructions for vector features */
3032 : /* -------------------------------------------------------------- */
3033 73 : int iObj = 0;
3034 110 : for (size_t iLayer = 0; iLayer < oPageContext.asVectorDesc.size(); iLayer++)
3035 : {
3036 37 : const GDALPDFLayerDesc &oLayerDesc = oPageContext.asVectorDesc[iLayer];
3037 :
3038 37 : VSIFPrintfL(m_fp, "/OC /Lyr%d BDC\n", oLayerDesc.nOCGId.toInt());
3039 :
3040 151 : for (size_t iVector = 0; iVector < oLayerDesc.aIds.size(); iVector++)
3041 : {
3042 114 : if (oLayerDesc.aIds[iVector].toBool())
3043 : {
3044 220 : CPLString osName = oLayerDesc.aFeatureNames[iVector];
3045 110 : if (!osName.empty())
3046 : {
3047 82 : VSIFPrintfL(m_fp, "/feature <</MCID %d>> BDC\n", iObj);
3048 : }
3049 :
3050 110 : VSIFPrintfL(m_fp, "/Vector%d Do\n",
3051 110 : oLayerDesc.aIds[iVector].toInt());
3052 :
3053 110 : if (!osName.empty())
3054 : {
3055 82 : VSIFPrintfL(m_fp, "EMC\n");
3056 : }
3057 : }
3058 :
3059 114 : iObj++;
3060 : }
3061 :
3062 37 : VSIFPrintfL(m_fp, "EMC\n");
3063 : }
3064 :
3065 : /* -------------------------------------------------------------- */
3066 : /* Write drawing instructions for labels of vector features */
3067 : /* -------------------------------------------------------------- */
3068 73 : iObj = 0;
3069 110 : for (const GDALPDFLayerDesc &oLayerDesc : oPageContext.asVectorDesc)
3070 : {
3071 37 : if (oLayerDesc.nOCGTextId.toBool())
3072 : {
3073 5 : VSIFPrintfL(m_fp, "/OC /Lyr%d BDC\n", oLayerDesc.nOCGId.toInt());
3074 5 : VSIFPrintfL(m_fp, "/OC /Lyr%d BDC\n",
3075 : oLayerDesc.nOCGTextId.toInt());
3076 :
3077 68 : for (size_t iVector = 0; iVector < oLayerDesc.aIdsText.size();
3078 : iVector++)
3079 : {
3080 63 : if (oLayerDesc.aIdsText[iVector].toBool())
3081 : {
3082 10 : CPLString osName = oLayerDesc.aFeatureNames[iVector];
3083 5 : if (!osName.empty())
3084 : {
3085 3 : VSIFPrintfL(m_fp, "/feature <</MCID %d>> BDC\n", iObj);
3086 : }
3087 :
3088 5 : VSIFPrintfL(m_fp, "/Text%d Do\n",
3089 5 : oLayerDesc.aIdsText[iVector].toInt());
3090 :
3091 5 : if (!osName.empty())
3092 : {
3093 3 : VSIFPrintfL(m_fp, "EMC\n");
3094 : }
3095 : }
3096 :
3097 63 : iObj++;
3098 : }
3099 :
3100 5 : VSIFPrintfL(m_fp, "EMC\n");
3101 5 : VSIFPrintfL(m_fp, "EMC\n");
3102 : }
3103 : else
3104 32 : iObj += static_cast<int>(oLayerDesc.aIds.size());
3105 : }
3106 :
3107 : /* -------------------------------------------------------------- */
3108 : /* Write drawing instructions for extra content. */
3109 : /* -------------------------------------------------------------- */
3110 73 : if (pszExtraStream || !asExtraImageDesc.empty())
3111 : {
3112 1 : if (nLayerExtraId.toBool())
3113 1 : VSIFPrintfL(m_fp, "/OC /Lyr%d BDC\n", nLayerExtraId.toInt());
3114 :
3115 : /* -------------------------------------------------------------- */
3116 : /* Write drawing instructions for extra images. */
3117 : /* -------------------------------------------------------------- */
3118 3 : for (size_t iImage = 0; iImage < asExtraImageDesc.size(); iImage++)
3119 : {
3120 2 : VSIFPrintfL(m_fp, "q\n");
3121 : GDALPDFObjectRW *poXSize =
3122 2 : GDALPDFObjectRW::CreateReal(asExtraImageDesc[iImage].dfXSize);
3123 : GDALPDFObjectRW *poYSize =
3124 2 : GDALPDFObjectRW::CreateReal(asExtraImageDesc[iImage].dfYSize);
3125 : GDALPDFObjectRW *poXOff =
3126 2 : GDALPDFObjectRW::CreateReal(asExtraImageDesc[iImage].dfXOff);
3127 : GDALPDFObjectRW *poYOff =
3128 2 : GDALPDFObjectRW::CreateReal(asExtraImageDesc[iImage].dfYOff);
3129 8 : VSIFPrintfL(
3130 4 : m_fp, "%s 0 0 %s %s %s cm\n", poXSize->Serialize().c_str(),
3131 6 : poYSize->Serialize().c_str(), poXOff->Serialize().c_str(),
3132 4 : poYOff->Serialize().c_str());
3133 2 : delete poXSize;
3134 2 : delete poYSize;
3135 2 : delete poXOff;
3136 2 : delete poYOff;
3137 2 : VSIFPrintfL(m_fp, "/Image%d Do\n",
3138 2 : asExtraImageDesc[iImage].nImageId.toInt());
3139 2 : VSIFPrintfL(m_fp, "Q\n");
3140 : }
3141 :
3142 1 : if (pszExtraStream)
3143 1 : VSIFPrintfL(m_fp, "%s\n", pszExtraStream);
3144 :
3145 1 : if (nLayerExtraId.toBool())
3146 1 : VSIFPrintfL(m_fp, "EMC\n");
3147 : }
3148 :
3149 73 : EndObjWithStream();
3150 :
3151 : /* -------------------------------------------------------------- */
3152 : /* Write objects for feature tree. */
3153 : /* -------------------------------------------------------------- */
3154 73 : if (m_nStructTreeRootId.toBool())
3155 : {
3156 20 : auto nParentTreeId = AllocNewObject();
3157 20 : StartObj(nParentTreeId);
3158 20 : VSIFPrintfL(m_fp, "<< /Nums [ 0 ");
3159 20 : VSIFPrintfL(m_fp, "[ ");
3160 55 : for (size_t iLayer = 0; iLayer < oPageContext.asVectorDesc.size();
3161 : iLayer++)
3162 : {
3163 : const GDALPDFLayerDesc &oLayerDesc =
3164 35 : oPageContext.asVectorDesc[iLayer];
3165 119 : for (size_t iVector = 0; iVector < oLayerDesc.aIds.size();
3166 : iVector++)
3167 : {
3168 84 : const auto &nId = oLayerDesc.aUserPropertiesIds[iVector];
3169 84 : if (nId.toBool())
3170 84 : VSIFPrintfL(m_fp, "%d 0 R ", nId.toInt());
3171 : }
3172 : }
3173 20 : VSIFPrintfL(m_fp, " ]\n");
3174 20 : VSIFPrintfL(m_fp, " ] >> \n");
3175 20 : EndObj();
3176 :
3177 20 : StartObj(m_nStructTreeRootId);
3178 20 : VSIFPrintfL(m_fp,
3179 : "<< "
3180 : "/Type /StructTreeRoot "
3181 : "/ParentTree %d 0 R "
3182 : "/K [ ",
3183 : nParentTreeId.toInt());
3184 55 : for (size_t iLayer = 0; iLayer < oPageContext.asVectorDesc.size();
3185 : iLayer++)
3186 : {
3187 35 : VSIFPrintfL(
3188 : m_fp, "%d 0 R ",
3189 35 : oPageContext.asVectorDesc[iLayer].nFeatureLayerId.toInt());
3190 : }
3191 20 : VSIFPrintfL(m_fp, "] >>\n");
3192 20 : EndObj();
3193 : }
3194 :
3195 : /* -------------------------------------------------------------- */
3196 : /* Write page resource dictionary. */
3197 : /* -------------------------------------------------------------- */
3198 73 : StartObj(oPageContext.nResourcesId);
3199 : {
3200 73 : GDALPDFDictionaryRW oDict;
3201 73 : GDALPDFDictionaryRW *poDictXObject = new GDALPDFDictionaryRW();
3202 73 : oDict.Add("XObject", poDictXObject);
3203 : size_t iImage;
3204 127 : for (const GDALPDFRasterDesc &oDesc : oPageContext.asRasterDesc)
3205 : {
3206 155 : for (iImage = 0; iImage < oDesc.asImageDesc.size(); iImage++)
3207 : {
3208 : poDictXObject->Add(
3209 : CPLSPrintf("Image%d",
3210 101 : oDesc.asImageDesc[iImage].nImageId.toInt()),
3211 101 : oDesc.asImageDesc[iImage].nImageId, 0);
3212 : }
3213 : }
3214 75 : for (iImage = 0; iImage < asExtraImageDesc.size(); iImage++)
3215 : {
3216 : poDictXObject->Add(
3217 : CPLSPrintf("Image%d",
3218 2 : asExtraImageDesc[iImage].nImageId.toInt()),
3219 2 : asExtraImageDesc[iImage].nImageId, 0);
3220 : }
3221 110 : for (const GDALPDFLayerDesc &oLayerDesc : oPageContext.asVectorDesc)
3222 : {
3223 151 : for (size_t iVector = 0; iVector < oLayerDesc.aIds.size();
3224 : iVector++)
3225 : {
3226 114 : if (oLayerDesc.aIds[iVector].toBool())
3227 : poDictXObject->Add(
3228 : CPLSPrintf("Vector%d",
3229 110 : oLayerDesc.aIds[iVector].toInt()),
3230 220 : oLayerDesc.aIds[iVector], 0);
3231 : }
3232 151 : for (size_t iVector = 0; iVector < oLayerDesc.aIdsText.size();
3233 : iVector++)
3234 : {
3235 114 : if (oLayerDesc.aIdsText[iVector].toBool())
3236 : poDictXObject->Add(
3237 : CPLSPrintf("Text%d",
3238 5 : oLayerDesc.aIdsText[iVector].toInt()),
3239 10 : oLayerDesc.aIdsText[iVector], 0);
3240 : }
3241 : }
3242 :
3243 73 : if (pszExtraStream)
3244 : {
3245 2 : std::vector<CPLString> aosNeededFonts;
3246 1 : if (strstr(pszExtraStream, "/FTimes"))
3247 : {
3248 1 : aosNeededFonts.push_back("Times-Roman");
3249 1 : aosNeededFonts.push_back("Times-Bold");
3250 1 : aosNeededFonts.push_back("Times-Italic");
3251 1 : aosNeededFonts.push_back("Times-BoldItalic");
3252 : }
3253 1 : if (strstr(pszExtraStream, "/FHelvetica"))
3254 : {
3255 0 : aosNeededFonts.push_back("Helvetica");
3256 0 : aosNeededFonts.push_back("Helvetica-Bold");
3257 0 : aosNeededFonts.push_back("Helvetica-Oblique");
3258 0 : aosNeededFonts.push_back("Helvetica-BoldOblique");
3259 : }
3260 1 : if (strstr(pszExtraStream, "/FCourier"))
3261 : {
3262 0 : aosNeededFonts.push_back("Courier");
3263 0 : aosNeededFonts.push_back("Courier-Bold");
3264 0 : aosNeededFonts.push_back("Courier-Oblique");
3265 0 : aosNeededFonts.push_back("Courier-BoldOblique");
3266 : }
3267 1 : if (strstr(pszExtraStream, "/FSymbol"))
3268 0 : aosNeededFonts.push_back("Symbol");
3269 1 : if (strstr(pszExtraStream, "/FZapfDingbats"))
3270 0 : aosNeededFonts.push_back("ZapfDingbats");
3271 :
3272 1 : if (!aosNeededFonts.empty())
3273 : {
3274 1 : GDALPDFDictionaryRW *poDictFont = new GDALPDFDictionaryRW();
3275 :
3276 5 : for (CPLString &osFont : aosNeededFonts)
3277 : {
3278 : GDALPDFDictionaryRW *poDictFontInner =
3279 4 : new GDALPDFDictionaryRW();
3280 : poDictFontInner->Add("Type",
3281 4 : GDALPDFObjectRW::CreateName("Font"));
3282 : poDictFontInner->Add("BaseFont",
3283 4 : GDALPDFObjectRW::CreateName(osFont));
3284 : poDictFontInner->Add(
3285 : "Encoding",
3286 4 : GDALPDFObjectRW::CreateName("WinAnsiEncoding"));
3287 : poDictFontInner->Add("Subtype",
3288 4 : GDALPDFObjectRW::CreateName("Type1"));
3289 :
3290 4 : osFont = "F" + osFont;
3291 4 : const size_t nHyphenPos = osFont.find('-');
3292 4 : if (nHyphenPos != std::string::npos)
3293 4 : osFont.erase(nHyphenPos, 1);
3294 4 : poDictFont->Add(osFont, poDictFontInner);
3295 : }
3296 :
3297 1 : oDict.Add("Font", poDictFont);
3298 : }
3299 : }
3300 :
3301 73 : if (!m_asOCGs.empty())
3302 : {
3303 24 : GDALPDFDictionaryRW *poDictProperties = new GDALPDFDictionaryRW();
3304 : #ifdef HACK_TO_GENERATE_OCMD
3305 : GDALPDFDictionaryRW *poOCMD = new GDALPDFDictionaryRW();
3306 : poOCMD->Add("Type", GDALPDFObjectRW::CreateName("OCMD"));
3307 : GDALPDFArrayRW *poArray = new GDALPDFArrayRW();
3308 : poArray->Add(m_asOCGs[0].nId, 0);
3309 : poArray->Add(m_asOCGs[1].nId, 0);
3310 : poOCMD->Add("OCGs", poArray);
3311 : poDictProperties->Add(CPLSPrintf("Lyr%d", m_asOCGs[1].nId.toInt()),
3312 : poOCMD);
3313 : #else
3314 70 : for (size_t i = 0; i < m_asOCGs.size(); i++)
3315 : poDictProperties->Add(
3316 46 : CPLSPrintf("Lyr%d", m_asOCGs[i].nId.toInt()),
3317 46 : m_asOCGs[i].nId, 0);
3318 : #endif
3319 24 : oDict.Add("Properties", poDictProperties);
3320 : }
3321 :
3322 73 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
3323 : }
3324 73 : EndObj();
3325 :
3326 : /* -------------------------------------------------------------- */
3327 : /* Write annotation arrays. */
3328 : /* -------------------------------------------------------------- */
3329 73 : StartObj(oPageContext.nAnnotsId);
3330 : {
3331 73 : GDALPDFArrayRW oArray;
3332 78 : for (size_t i = 0; i < oPageContext.anAnnotationsId.size(); i++)
3333 : {
3334 5 : oArray.Add(oPageContext.anAnnotationsId[i], 0);
3335 : }
3336 73 : VSIFPrintfL(m_fp, "%s\n", oArray.Serialize().c_str());
3337 : }
3338 73 : EndObj();
3339 :
3340 146 : return TRUE;
3341 : }
3342 :
3343 : /************************************************************************/
3344 : /* WriteMask() */
3345 : /************************************************************************/
3346 :
3347 32 : GDALPDFObjectNum GDALPDFBaseWriter::WriteMask(GDALDataset *poSrcDS, int nXOff,
3348 : int nYOff, int nReqXSize,
3349 : int nReqYSize,
3350 : PDFCompressMethod eCompressMethod)
3351 : {
3352 32 : int nMaskSize = nReqXSize * nReqYSize;
3353 32 : GByte *pabyMask = static_cast<GByte *>(VSIMalloc(nMaskSize));
3354 32 : if (pabyMask == nullptr)
3355 0 : return GDALPDFObjectNum();
3356 :
3357 : CPLErr eErr;
3358 32 : eErr = poSrcDS->GetRasterBand(4)->RasterIO(
3359 : GF_Read, nXOff, nYOff, nReqXSize, nReqYSize, pabyMask, nReqXSize,
3360 : nReqYSize, GDT_UInt8, 0, 0, nullptr);
3361 32 : if (eErr != CE_None)
3362 : {
3363 0 : VSIFree(pabyMask);
3364 0 : return GDALPDFObjectNum();
3365 : }
3366 :
3367 32 : int bOnly0or255 = TRUE;
3368 32 : int bOnly255 = TRUE;
3369 : /* int bOnly0 = TRUE; */
3370 : int i;
3371 8225 : for (i = 0; i < nReqXSize * nReqYSize; i++)
3372 : {
3373 8216 : if (pabyMask[i] == 0)
3374 6144 : bOnly255 = FALSE;
3375 2072 : else if (pabyMask[i] == 255)
3376 : {
3377 : /* bOnly0 = FALSE; */
3378 : }
3379 : else
3380 : {
3381 : /* bOnly0 = FALSE; */
3382 23 : bOnly255 = FALSE;
3383 23 : bOnly0or255 = FALSE;
3384 23 : break;
3385 : }
3386 : }
3387 :
3388 32 : if (bOnly255)
3389 : {
3390 0 : CPLFree(pabyMask);
3391 0 : return GDALPDFObjectNum();
3392 : }
3393 :
3394 32 : if (bOnly0or255)
3395 : {
3396 : /* Translate to 1 bit */
3397 9 : int nReqXSize1 = (nReqXSize + 7) / 8;
3398 : GByte *pabyMask1 =
3399 9 : static_cast<GByte *>(VSICalloc(nReqXSize1, nReqYSize));
3400 9 : if (pabyMask1 == nullptr)
3401 : {
3402 0 : CPLFree(pabyMask);
3403 0 : return GDALPDFObjectNum();
3404 : }
3405 255 : for (int y = 0; y < nReqYSize; y++)
3406 : {
3407 3398 : for (int x = 0; x < nReqXSize; x++)
3408 : {
3409 3152 : if (pabyMask[y * nReqXSize + x])
3410 896 : pabyMask1[y * nReqXSize1 + x / 8] |= 1 << (7 - (x % 8));
3411 : }
3412 : }
3413 9 : VSIFree(pabyMask);
3414 9 : pabyMask = pabyMask1;
3415 9 : nMaskSize = nReqXSize1 * nReqYSize;
3416 : }
3417 :
3418 32 : auto nMaskId = AllocNewObject();
3419 :
3420 32 : GDALPDFDictionaryRW oDict;
3421 32 : oDict.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
3422 32 : .Add("Subtype", GDALPDFObjectRW::CreateName("Image"))
3423 32 : .Add("Width", nReqXSize)
3424 32 : .Add("Height", nReqYSize)
3425 32 : .Add("ColorSpace", GDALPDFObjectRW::CreateName("DeviceGray"))
3426 32 : .Add("BitsPerComponent", (bOnly0or255) ? 1 : 8);
3427 :
3428 32 : StartObjWithStream(nMaskId, oDict, eCompressMethod != COMPRESS_NONE);
3429 :
3430 32 : VSIFWriteL(pabyMask, nMaskSize, 1, m_fp);
3431 32 : CPLFree(pabyMask);
3432 :
3433 32 : EndObjWithStream();
3434 :
3435 32 : return nMaskId;
3436 : }
3437 :
3438 : /************************************************************************/
3439 : /* WriteBlock() */
3440 : /************************************************************************/
3441 :
3442 120 : GDALPDFObjectNum GDALPDFBaseWriter::WriteBlock(
3443 : GDALDataset *poSrcDS, int nXOff, int nYOff, int nReqXSize, int nReqYSize,
3444 : const GDALPDFObjectNum &nColorTableIdIn, PDFCompressMethod eCompressMethod,
3445 : int nPredictor, int nJPEGQuality, const char *pszJPEG2000_DRIVER,
3446 : GDALProgressFunc pfnProgress, void *pProgressData)
3447 : {
3448 120 : int nBands = poSrcDS->GetRasterCount();
3449 120 : if (nBands == 0)
3450 0 : return GDALPDFObjectNum();
3451 :
3452 120 : GDALPDFObjectNum nColorTableId(nColorTableIdIn);
3453 120 : if (!nColorTableId.toBool())
3454 119 : nColorTableId = WriteColorTable(poSrcDS);
3455 :
3456 120 : CPLErr eErr = CE_None;
3457 120 : GDALDataset *poBlockSrcDS = nullptr;
3458 120 : std::unique_ptr<MEMDataset> poMEMDS;
3459 120 : GByte *pabyMEMDSBuffer = nullptr;
3460 :
3461 120 : if (eCompressMethod == COMPRESS_DEFAULT)
3462 : {
3463 96 : GDALDataset *poSrcDSToTest = poSrcDS;
3464 :
3465 : /* Test if we can directly copy original JPEG content */
3466 : /* if available */
3467 96 : if (VRTDataset *poVRTDS = dynamic_cast<VRTDataset *>(poSrcDS))
3468 : {
3469 5 : poSrcDSToTest = poVRTDS->GetSingleSimpleSource();
3470 : }
3471 :
3472 95 : if (poSrcDSToTest != nullptr && poSrcDSToTest->GetDriver() != nullptr &&
3473 95 : EQUAL(poSrcDSToTest->GetDriver()->GetDescription(), "JPEG") &&
3474 2 : nXOff == 0 && nYOff == 0 &&
3475 2 : nReqXSize == poSrcDSToTest->GetRasterXSize() &&
3476 191 : nReqYSize == poSrcDSToTest->GetRasterYSize() && nJPEGQuality < 0)
3477 : {
3478 2 : VSILFILE *fpSrc = VSIFOpenL(poSrcDSToTest->GetDescription(), "rb");
3479 2 : if (fpSrc != nullptr)
3480 : {
3481 2 : CPLDebug("PDF", "Copying directly original JPEG file");
3482 :
3483 2 : VSIFSeekL(fpSrc, 0, SEEK_END);
3484 2 : const int nLength = static_cast<int>(VSIFTellL(fpSrc));
3485 2 : VSIFSeekL(fpSrc, 0, SEEK_SET);
3486 :
3487 2 : auto nImageId = AllocNewObject();
3488 :
3489 2 : StartObj(nImageId);
3490 :
3491 4 : GDALPDFDictionaryRW oDict;
3492 2 : oDict.Add("Length", nLength)
3493 2 : .Add("Type", GDALPDFObjectRW::CreateName("XObject"))
3494 2 : .Add("Filter", GDALPDFObjectRW::CreateName("DCTDecode"))
3495 2 : .Add("Subtype", GDALPDFObjectRW::CreateName("Image"))
3496 2 : .Add("Width", nReqXSize)
3497 2 : .Add("Height", nReqYSize)
3498 : .Add("ColorSpace",
3499 : (nBands == 1)
3500 2 : ? GDALPDFObjectRW::CreateName("DeviceGray")
3501 4 : : GDALPDFObjectRW::CreateName("DeviceRGB"))
3502 2 : .Add("BitsPerComponent", 8);
3503 2 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
3504 2 : VSIFPrintfL(m_fp, "stream\n");
3505 :
3506 : GByte abyBuffer[1024];
3507 12 : for (int i = 0; i < nLength; i += 1024)
3508 : {
3509 10 : const auto nRead = VSIFReadL(abyBuffer, 1, 1024, fpSrc);
3510 10 : if (VSIFWriteL(abyBuffer, 1, nRead, m_fp) != nRead)
3511 : {
3512 0 : eErr = CE_Failure;
3513 0 : break;
3514 : }
3515 :
3516 20 : if (eErr == CE_None && pfnProgress != nullptr &&
3517 10 : !pfnProgress(double(i + nRead) / double(nLength),
3518 : nullptr, pProgressData))
3519 : {
3520 0 : CPLError(CE_Failure, CPLE_UserInterrupt,
3521 : "User terminated CreateCopy()");
3522 0 : eErr = CE_Failure;
3523 0 : break;
3524 : }
3525 : }
3526 :
3527 2 : VSIFPrintfL(m_fp, "\nendstream\n");
3528 :
3529 2 : EndObj();
3530 :
3531 2 : VSIFCloseL(fpSrc);
3532 :
3533 2 : return eErr == CE_None ? nImageId : GDALPDFObjectNum();
3534 : }
3535 : }
3536 :
3537 94 : eCompressMethod = COMPRESS_DEFLATE;
3538 : }
3539 :
3540 118 : GDALPDFObjectNum nMaskId;
3541 118 : if (nBands == 4)
3542 : {
3543 : nMaskId = WriteMask(poSrcDS, nXOff, nYOff, nReqXSize, nReqYSize,
3544 32 : eCompressMethod);
3545 : }
3546 :
3547 118 : if (nReqXSize == poSrcDS->GetRasterXSize() &&
3548 118 : nReqYSize == poSrcDS->GetRasterYSize() && nBands != 4)
3549 : {
3550 56 : poBlockSrcDS = poSrcDS;
3551 : }
3552 : else
3553 : {
3554 62 : if (nBands == 4)
3555 32 : nBands = 3;
3556 :
3557 62 : poMEMDS.reset(MEMDataset::Create("", nReqXSize, nReqYSize, 0, GDT_UInt8,
3558 : nullptr));
3559 :
3560 : pabyMEMDSBuffer =
3561 62 : static_cast<GByte *>(VSIMalloc3(nReqXSize, nReqYSize, nBands));
3562 62 : if (pabyMEMDSBuffer == nullptr)
3563 : {
3564 0 : return GDALPDFObjectNum();
3565 : }
3566 :
3567 62 : eErr = poSrcDS->RasterIO(GF_Read, nXOff, nYOff, nReqXSize, nReqYSize,
3568 : pabyMEMDSBuffer, nReqXSize, nReqYSize,
3569 : GDT_UInt8, nBands, nullptr, 0, 0, 0, nullptr);
3570 :
3571 62 : if (eErr != CE_None)
3572 : {
3573 0 : CPLFree(pabyMEMDSBuffer);
3574 0 : return GDALPDFObjectNum();
3575 : }
3576 :
3577 : int iBand;
3578 188 : for (iBand = 0; iBand < nBands; iBand++)
3579 : {
3580 126 : auto hBand = MEMCreateRasterBandEx(
3581 126 : poMEMDS.get(), iBand + 1,
3582 126 : pabyMEMDSBuffer + iBand * nReqXSize * nReqYSize, GDT_UInt8, 0,
3583 : 0, false);
3584 126 : poMEMDS->AddMEMBand(hBand);
3585 : }
3586 :
3587 62 : poBlockSrcDS = poMEMDS.get();
3588 : }
3589 :
3590 118 : auto nImageId = AllocNewObject();
3591 :
3592 118 : GDALPDFObjectNum nMeasureId;
3593 118 : if (CPLTestBool(
3594 0 : CPLGetConfigOption("GDAL_PDF_WRITE_GEOREF_ON_IMAGE", "FALSE")) &&
3595 118 : nReqXSize == poSrcDS->GetRasterXSize() &&
3596 0 : nReqYSize == poSrcDS->GetRasterYSize())
3597 : {
3598 0 : PDFMargins sMargins;
3599 0 : nMeasureId = WriteSRS_ISO32000(poSrcDS, 1, nullptr, &sMargins, FALSE);
3600 : }
3601 :
3602 236 : GDALPDFDictionaryRW oDict;
3603 118 : oDict.Add("Type", GDALPDFObjectRW::CreateName("XObject"));
3604 :
3605 118 : if (eCompressMethod == COMPRESS_DEFLATE)
3606 : {
3607 110 : if (nPredictor == 2)
3608 2 : oDict.Add("DecodeParms", &((new GDALPDFDictionaryRW())
3609 2 : ->Add("Predictor", 2)
3610 2 : .Add("Colors", nBands)
3611 2 : .Add("Columns", nReqXSize)));
3612 : }
3613 8 : else if (eCompressMethod == COMPRESS_JPEG)
3614 : {
3615 3 : oDict.Add("Filter", GDALPDFObjectRW::CreateName("DCTDecode"));
3616 : }
3617 5 : else if (eCompressMethod == COMPRESS_JPEG2000)
3618 : {
3619 4 : oDict.Add("Filter", GDALPDFObjectRW::CreateName("JPXDecode"));
3620 : }
3621 :
3622 118 : oDict.Add("Subtype", GDALPDFObjectRW::CreateName("Image"))
3623 118 : .Add("Width", nReqXSize)
3624 118 : .Add("Height", nReqYSize)
3625 : .Add("ColorSpace",
3626 118 : (nColorTableId.toBool())
3627 1 : ? GDALPDFObjectRW::CreateIndirect(nColorTableId, 0)
3628 78 : : (nBands == 1) ? GDALPDFObjectRW::CreateName("DeviceGray")
3629 197 : : GDALPDFObjectRW::CreateName("DeviceRGB"))
3630 118 : .Add("BitsPerComponent", 8);
3631 118 : if (nMaskId.toBool())
3632 : {
3633 32 : oDict.Add("SMask", nMaskId, 0);
3634 : }
3635 118 : if (nMeasureId.toBool())
3636 : {
3637 0 : oDict.Add("Measure", nMeasureId, 0);
3638 : }
3639 :
3640 118 : StartObjWithStream(nImageId, oDict, eCompressMethod == COMPRESS_DEFLATE);
3641 :
3642 118 : if (eCompressMethod == COMPRESS_JPEG ||
3643 : eCompressMethod == COMPRESS_JPEG2000)
3644 : {
3645 7 : GDALDriver *poJPEGDriver = nullptr;
3646 7 : std::string osTmpfilename;
3647 7 : char **papszOptions = nullptr;
3648 :
3649 7 : bool bEcwEncodeKeyRequiredButNotFound = false;
3650 7 : if (eCompressMethod == COMPRESS_JPEG)
3651 : {
3652 3 : poJPEGDriver = GetGDALDriverManager()->GetDriverByName("JPEG");
3653 3 : if (poJPEGDriver != nullptr && nJPEGQuality > 0)
3654 0 : papszOptions = CSLAddString(
3655 : papszOptions, CPLSPrintf("QUALITY=%d", nJPEGQuality));
3656 3 : osTmpfilename = VSIMemGenerateHiddenFilename("pdf_temp.jpg");
3657 : }
3658 : else
3659 : {
3660 4 : if (pszJPEG2000_DRIVER == nullptr ||
3661 3 : EQUAL(pszJPEG2000_DRIVER, "JP2KAK"))
3662 : poJPEGDriver =
3663 1 : GetGDALDriverManager()->GetDriverByName("JP2KAK");
3664 4 : if (poJPEGDriver == nullptr)
3665 : {
3666 4 : if (pszJPEG2000_DRIVER == nullptr ||
3667 3 : EQUAL(pszJPEG2000_DRIVER, "JP2ECW"))
3668 : {
3669 : poJPEGDriver =
3670 3 : GetGDALDriverManager()->GetDriverByName("JP2ECW");
3671 6 : if (poJPEGDriver &&
3672 3 : poJPEGDriver->GetMetadataItem(
3673 3 : GDAL_DMD_CREATIONDATATYPES) == nullptr)
3674 : {
3675 0 : poJPEGDriver = nullptr;
3676 : }
3677 3 : else if (poJPEGDriver)
3678 : {
3679 6 : if (strstr(poJPEGDriver->GetMetadataItem(
3680 3 : GDAL_DMD_CREATIONOPTIONLIST),
3681 3 : "ECW_ENCODE_KEY"))
3682 : {
3683 0 : if (!CPLGetConfigOption("ECW_ENCODE_KEY", nullptr))
3684 : {
3685 0 : bEcwEncodeKeyRequiredButNotFound = true;
3686 0 : poJPEGDriver = nullptr;
3687 : }
3688 : }
3689 : }
3690 : }
3691 4 : if (poJPEGDriver)
3692 : {
3693 3 : papszOptions = CSLAddString(papszOptions, "PROFILE=NPJE");
3694 3 : papszOptions = CSLAddString(papszOptions, "LAYERS=1");
3695 3 : papszOptions = CSLAddString(papszOptions, "GeoJP2=OFF");
3696 3 : papszOptions = CSLAddString(papszOptions, "GMLJP2=OFF");
3697 : }
3698 : }
3699 4 : if (poJPEGDriver == nullptr)
3700 : {
3701 1 : if (pszJPEG2000_DRIVER == nullptr ||
3702 1 : EQUAL(pszJPEG2000_DRIVER, "JP2OpenJPEG"))
3703 : poJPEGDriver =
3704 1 : GetGDALDriverManager()->GetDriverByName("JP2OpenJPEG");
3705 1 : if (poJPEGDriver)
3706 : {
3707 1 : papszOptions = CSLAddString(papszOptions, "GeoJP2=OFF");
3708 1 : papszOptions = CSLAddString(papszOptions, "GMLJP2=OFF");
3709 : }
3710 : }
3711 4 : osTmpfilename = VSIMemGenerateHiddenFilename("pdf_temp.jp2");
3712 : }
3713 :
3714 7 : if (poJPEGDriver == nullptr)
3715 : {
3716 0 : if (bEcwEncodeKeyRequiredButNotFound)
3717 : {
3718 0 : CPLError(CE_Failure, CPLE_NotSupported,
3719 : "No JPEG2000 driver usable (JP2ECW detected but "
3720 : "ECW_ENCODE_KEY configuration option not set");
3721 : }
3722 : else
3723 : {
3724 0 : CPLError(CE_Failure, CPLE_NotSupported, "No %s driver found",
3725 : (eCompressMethod == COMPRESS_JPEG) ? "JPEG"
3726 : : "JPEG2000");
3727 : }
3728 0 : eErr = CE_Failure;
3729 0 : goto end;
3730 : }
3731 :
3732 : GDALDataset *poJPEGDS =
3733 7 : poJPEGDriver->CreateCopy(osTmpfilename.c_str(), poBlockSrcDS, FALSE,
3734 : papszOptions, pfnProgress, pProgressData);
3735 :
3736 7 : CSLDestroy(papszOptions);
3737 7 : if (poJPEGDS == nullptr)
3738 : {
3739 0 : eErr = CE_Failure;
3740 0 : goto end;
3741 : }
3742 :
3743 7 : GDALClose(poJPEGDS);
3744 :
3745 7 : vsi_l_offset nJPEGDataSize = 0;
3746 : GByte *pabyJPEGData =
3747 7 : VSIGetMemFileBuffer(osTmpfilename.c_str(), &nJPEGDataSize, TRUE);
3748 7 : VSIFWriteL(pabyJPEGData, static_cast<size_t>(nJPEGDataSize), 1, m_fp);
3749 14 : CPLFree(pabyJPEGData);
3750 : }
3751 : else
3752 : {
3753 : GByte *pabyLine = static_cast<GByte *>(
3754 111 : CPLMalloc(static_cast<size_t>(nReqXSize) * nBands));
3755 46788 : for (int iLine = 0; iLine < nReqYSize; iLine++)
3756 : {
3757 : /* Get pixel interleaved data */
3758 46679 : eErr = poBlockSrcDS->RasterIO(
3759 : GF_Read, 0, iLine, nReqXSize, 1, pabyLine, nReqXSize, 1,
3760 : GDT_UInt8, nBands, nullptr, nBands, 0, 1, nullptr);
3761 46679 : if (eErr != CE_None)
3762 0 : break;
3763 :
3764 : /* Apply predictor if needed */
3765 46679 : if (nPredictor == 2)
3766 : {
3767 562 : if (nBands == 1)
3768 : {
3769 512 : int nPrevValue = pabyLine[0];
3770 262144 : for (int iPixel = 1; iPixel < nReqXSize; iPixel++)
3771 : {
3772 261632 : int nCurValue = pabyLine[iPixel];
3773 261632 : pabyLine[iPixel] =
3774 261632 : static_cast<GByte>(nCurValue - nPrevValue);
3775 261632 : nPrevValue = nCurValue;
3776 : }
3777 : }
3778 50 : else if (nBands == 3)
3779 : {
3780 50 : int nPrevValueR = pabyLine[0];
3781 50 : int nPrevValueG = pabyLine[1];
3782 50 : int nPrevValueB = pabyLine[2];
3783 2500 : for (int iPixel = 1; iPixel < nReqXSize; iPixel++)
3784 : {
3785 2450 : int nCurValueR = pabyLine[3 * iPixel + 0];
3786 2450 : int nCurValueG = pabyLine[3 * iPixel + 1];
3787 2450 : int nCurValueB = pabyLine[3 * iPixel + 2];
3788 2450 : pabyLine[3 * iPixel + 0] =
3789 2450 : static_cast<GByte>(nCurValueR - nPrevValueR);
3790 2450 : pabyLine[3 * iPixel + 1] =
3791 2450 : static_cast<GByte>(nCurValueG - nPrevValueG);
3792 2450 : pabyLine[3 * iPixel + 2] =
3793 2450 : static_cast<GByte>(nCurValueB - nPrevValueB);
3794 2450 : nPrevValueR = nCurValueR;
3795 2450 : nPrevValueG = nCurValueG;
3796 2450 : nPrevValueB = nCurValueB;
3797 : }
3798 : }
3799 : }
3800 :
3801 46679 : if (VSIFWriteL(pabyLine, static_cast<size_t>(nReqXSize) * nBands, 1,
3802 46679 : m_fp) != 1)
3803 : {
3804 2 : eErr = CE_Failure;
3805 2 : break;
3806 : }
3807 :
3808 93095 : if (pfnProgress != nullptr &&
3809 46418 : !pfnProgress((iLine + 1) / double(nReqYSize), nullptr,
3810 : pProgressData))
3811 : {
3812 0 : CPLError(CE_Failure, CPLE_UserInterrupt,
3813 : "User terminated CreateCopy()");
3814 0 : eErr = CE_Failure;
3815 0 : break;
3816 : }
3817 : }
3818 :
3819 111 : CPLFree(pabyLine);
3820 : }
3821 :
3822 118 : end:
3823 118 : CPLFree(pabyMEMDSBuffer);
3824 118 : pabyMEMDSBuffer = nullptr;
3825 :
3826 118 : EndObjWithStream();
3827 :
3828 118 : return eErr == CE_None ? nImageId : GDALPDFObjectNum();
3829 : }
3830 :
3831 : /************************************************************************/
3832 : /* WriteJavascript() */
3833 : /************************************************************************/
3834 :
3835 2 : GDALPDFObjectNum GDALPDFBaseWriter::WriteJavascript(const char *pszJavascript,
3836 : bool bDeflate)
3837 : {
3838 2 : auto nJSId = AllocNewObject();
3839 : {
3840 4 : GDALPDFDictionaryRW oDict;
3841 2 : StartObjWithStream(nJSId, oDict, bDeflate);
3842 :
3843 2 : VSIFWriteL(pszJavascript, strlen(pszJavascript), 1, m_fp);
3844 2 : VSIFPrintfL(m_fp, "\n");
3845 :
3846 2 : EndObjWithStream();
3847 : }
3848 :
3849 2 : m_nNamesId = AllocNewObject();
3850 2 : StartObj(m_nNamesId);
3851 : {
3852 2 : GDALPDFDictionaryRW oDict;
3853 2 : GDALPDFDictionaryRW *poJavaScriptDict = new GDALPDFDictionaryRW();
3854 2 : oDict.Add("JavaScript", poJavaScriptDict);
3855 :
3856 2 : GDALPDFArrayRW *poNamesArray = new GDALPDFArrayRW();
3857 2 : poJavaScriptDict->Add("Names", poNamesArray);
3858 :
3859 2 : poNamesArray->Add("GDAL");
3860 :
3861 2 : GDALPDFDictionaryRW *poJSDict = new GDALPDFDictionaryRW();
3862 2 : poNamesArray->Add(poJSDict);
3863 :
3864 2 : poJSDict->Add("JS", nJSId, 0);
3865 2 : poJSDict->Add("S", GDALPDFObjectRW::CreateName("JavaScript"));
3866 :
3867 2 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
3868 : }
3869 2 : EndObj();
3870 :
3871 2 : return m_nNamesId;
3872 : }
3873 :
3874 1 : GDALPDFObjectNum GDALPDFWriter::WriteJavascript(const char *pszJavascript)
3875 : {
3876 : return GDALPDFBaseWriter::WriteJavascript(
3877 1 : pszJavascript, oPageContext.eStreamCompressMethod != COMPRESS_NONE);
3878 : }
3879 :
3880 : /************************************************************************/
3881 : /* WriteJavascriptFile() */
3882 : /************************************************************************/
3883 :
3884 : GDALPDFObjectNum
3885 0 : GDALPDFWriter::WriteJavascriptFile(const char *pszJavascriptFile)
3886 : {
3887 0 : GDALPDFObjectNum nId;
3888 0 : char *pszJavascriptToFree = static_cast<char *>(CPLMalloc(65536));
3889 0 : VSILFILE *fpJS = VSIFOpenL(pszJavascriptFile, "rb");
3890 0 : if (fpJS != nullptr)
3891 : {
3892 : const int nRead =
3893 0 : static_cast<int>(VSIFReadL(pszJavascriptToFree, 1, 65536, fpJS));
3894 0 : if (nRead < 65536)
3895 : {
3896 0 : pszJavascriptToFree[nRead] = '\0';
3897 0 : nId = WriteJavascript(pszJavascriptToFree);
3898 : }
3899 0 : VSIFCloseL(fpJS);
3900 : }
3901 0 : CPLFree(pszJavascriptToFree);
3902 0 : return nId;
3903 : }
3904 :
3905 : /************************************************************************/
3906 : /* WritePages() */
3907 : /************************************************************************/
3908 :
3909 75 : void GDALPDFWriter::WritePages()
3910 : {
3911 75 : StartObj(m_nPageResourceId);
3912 : {
3913 75 : GDALPDFDictionaryRW oDict;
3914 75 : GDALPDFArrayRW *poKids = new GDALPDFArrayRW();
3915 75 : oDict.Add("Type", GDALPDFObjectRW::CreateName("Pages"))
3916 75 : .Add("Count", static_cast<int>(m_asPageId.size()))
3917 75 : .Add("Kids", poKids);
3918 :
3919 150 : for (size_t i = 0; i < m_asPageId.size(); i++)
3920 75 : poKids->Add(m_asPageId[i], 0);
3921 :
3922 75 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
3923 : }
3924 75 : EndObj();
3925 :
3926 75 : StartObj(m_nCatalogId);
3927 : {
3928 75 : GDALPDFDictionaryRW oDict;
3929 75 : oDict.Add("Type", GDALPDFObjectRW::CreateName("Catalog"))
3930 75 : .Add("Pages", m_nPageResourceId, 0);
3931 75 : if (m_nXMPId.toBool())
3932 1 : oDict.Add("Metadata", m_nXMPId, 0);
3933 75 : if (!m_asOCGs.empty())
3934 : {
3935 24 : GDALPDFDictionaryRW *poDictOCProperties = new GDALPDFDictionaryRW();
3936 24 : oDict.Add("OCProperties", poDictOCProperties);
3937 :
3938 24 : GDALPDFDictionaryRW *poDictD = new GDALPDFDictionaryRW();
3939 24 : poDictOCProperties->Add("D", poDictD);
3940 :
3941 : /* Build "Order" array of D dict */
3942 24 : GDALPDFArrayRW *poArrayOrder = new GDALPDFArrayRW();
3943 65 : for (size_t i = 0; i < m_asOCGs.size(); i++)
3944 : {
3945 41 : poArrayOrder->Add(m_asOCGs[i].nId, 0);
3946 63 : if (i + 1 < m_asOCGs.size() &&
3947 22 : m_asOCGs[i + 1].nParentId == m_asOCGs[i].nId)
3948 : {
3949 5 : GDALPDFArrayRW *poSubArrayOrder = new GDALPDFArrayRW();
3950 5 : poSubArrayOrder->Add(m_asOCGs[i + 1].nId, 0);
3951 5 : poArrayOrder->Add(poSubArrayOrder);
3952 5 : i++;
3953 : }
3954 : }
3955 24 : poDictD->Add("Order", poArrayOrder);
3956 :
3957 : /* Build "OFF" array of D dict */
3958 24 : if (!m_osOffLayers.empty())
3959 : {
3960 1 : GDALPDFArrayRW *poArrayOFF = new GDALPDFArrayRW();
3961 1 : char **papszTokens = CSLTokenizeString2(m_osOffLayers, ",", 0);
3962 2 : for (int i = 0; papszTokens[i] != nullptr; i++)
3963 : {
3964 : size_t j;
3965 1 : int bFound = FALSE;
3966 3 : for (j = 0; j < m_asOCGs.size(); j++)
3967 : {
3968 2 : if (strcmp(papszTokens[i], m_asOCGs[j].osLayerName) ==
3969 : 0)
3970 : {
3971 1 : poArrayOFF->Add(m_asOCGs[j].nId, 0);
3972 1 : bFound = TRUE;
3973 : }
3974 3 : if (j + 1 < m_asOCGs.size() &&
3975 1 : m_asOCGs[j + 1].nParentId == m_asOCGs[j].nId)
3976 : {
3977 0 : j++;
3978 : }
3979 : }
3980 1 : if (!bFound)
3981 : {
3982 0 : CPLError(
3983 : CE_Warning, CPLE_AppDefined,
3984 : "Unknown layer name (%s) specified in OFF_LAYERS",
3985 0 : papszTokens[i]);
3986 : }
3987 : }
3988 1 : CSLDestroy(papszTokens);
3989 :
3990 1 : poDictD->Add("OFF", poArrayOFF);
3991 : }
3992 :
3993 : /* Build "RBGroups" array of D dict */
3994 24 : if (!m_osExclusiveLayers.empty())
3995 : {
3996 1 : GDALPDFArrayRW *poArrayRBGroups = new GDALPDFArrayRW();
3997 : char **papszTokens =
3998 1 : CSLTokenizeString2(m_osExclusiveLayers, ",", 0);
3999 3 : for (int i = 0; papszTokens[i] != nullptr; i++)
4000 : {
4001 : size_t j;
4002 2 : int bFound = FALSE;
4003 6 : for (j = 0; j < m_asOCGs.size(); j++)
4004 : {
4005 4 : if (strcmp(papszTokens[i], m_asOCGs[j].osLayerName) ==
4006 : 0)
4007 : {
4008 2 : poArrayRBGroups->Add(m_asOCGs[j].nId, 0);
4009 2 : bFound = TRUE;
4010 : }
4011 6 : if (j + 1 < m_asOCGs.size() &&
4012 2 : m_asOCGs[j + 1].nParentId == m_asOCGs[j].nId)
4013 : {
4014 0 : j++;
4015 : }
4016 : }
4017 2 : if (!bFound)
4018 : {
4019 0 : CPLError(CE_Warning, CPLE_AppDefined,
4020 : "Unknown layer name (%s) specified in "
4021 : "EXCLUSIVE_LAYERS",
4022 0 : papszTokens[i]);
4023 : }
4024 : }
4025 1 : CSLDestroy(papszTokens);
4026 :
4027 1 : if (poArrayRBGroups->GetLength())
4028 : {
4029 1 : GDALPDFArrayRW *poMainArrayRBGroups = new GDALPDFArrayRW();
4030 1 : poMainArrayRBGroups->Add(poArrayRBGroups);
4031 1 : poDictD->Add("RBGroups", poMainArrayRBGroups);
4032 : }
4033 : else
4034 0 : delete poArrayRBGroups;
4035 : }
4036 :
4037 24 : GDALPDFArrayRW *poArrayOGCs = new GDALPDFArrayRW();
4038 70 : for (size_t i = 0; i < m_asOCGs.size(); i++)
4039 46 : poArrayOGCs->Add(m_asOCGs[i].nId, 0);
4040 24 : poDictOCProperties->Add("OCGs", poArrayOGCs);
4041 : }
4042 :
4043 75 : if (m_nStructTreeRootId.toBool())
4044 : {
4045 20 : GDALPDFDictionaryRW *poDictMarkInfo = new GDALPDFDictionaryRW();
4046 20 : oDict.Add("MarkInfo", poDictMarkInfo);
4047 : poDictMarkInfo->Add("UserProperties",
4048 20 : GDALPDFObjectRW::CreateBool(TRUE));
4049 :
4050 20 : oDict.Add("StructTreeRoot", m_nStructTreeRootId, 0);
4051 : }
4052 :
4053 75 : if (m_nNamesId.toBool())
4054 1 : oDict.Add("Names", m_nNamesId, 0);
4055 :
4056 75 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
4057 : }
4058 75 : EndObj();
4059 75 : }
4060 :
4061 : /************************************************************************/
4062 : /* GDALPDFGetJPEGQuality() */
4063 : /************************************************************************/
4064 :
4065 58 : static int GDALPDFGetJPEGQuality(CSLConstList papszOptions)
4066 : {
4067 58 : int nJpegQuality = -1;
4068 58 : const char *pszValue = CSLFetchNameValue(papszOptions, "JPEG_QUALITY");
4069 58 : if (pszValue != nullptr)
4070 : {
4071 0 : nJpegQuality = atoi(pszValue);
4072 0 : if (!(nJpegQuality >= 1 && nJpegQuality <= 100))
4073 : {
4074 0 : CPLError(CE_Warning, CPLE_IllegalArg,
4075 : "JPEG_QUALITY=%s value not recognised, ignoring.",
4076 : pszValue);
4077 0 : nJpegQuality = -1;
4078 : }
4079 : }
4080 58 : return nJpegQuality;
4081 : }
4082 :
4083 : /************************************************************************/
4084 : /* GDALPDFClippingDataset */
4085 : /************************************************************************/
4086 :
4087 : class GDALPDFClippingDataset final : public GDALDataset
4088 : {
4089 : GDALDataset *poSrcDS = nullptr;
4090 : GDALGeoTransform m_gt{};
4091 :
4092 : CPL_DISALLOW_COPY_ASSIGN(GDALPDFClippingDataset)
4093 :
4094 : public:
4095 1 : GDALPDFClippingDataset(GDALDataset *poSrcDSIn, double adfClippingExtent[4])
4096 1 : : poSrcDS(poSrcDSIn)
4097 : {
4098 1 : GDALGeoTransform srcGT;
4099 1 : poSrcDS->GetGeoTransform(srcGT);
4100 1 : m_gt.xorig = adfClippingExtent[0];
4101 1 : m_gt.xscale = srcGT[1];
4102 1 : m_gt.xrot = 0.0;
4103 1 : m_gt.yorig = srcGT[5] < 0 ? adfClippingExtent[3] : adfClippingExtent[1];
4104 1 : m_gt.yrot = 0.0;
4105 1 : m_gt.yscale = srcGT[5];
4106 1 : nRasterXSize = static_cast<int>(
4107 1 : (adfClippingExtent[2] - adfClippingExtent[0]) / srcGT[1]);
4108 1 : nRasterYSize = static_cast<int>(
4109 1 : (adfClippingExtent[3] - adfClippingExtent[1]) / fabs(srcGT[5]));
4110 1 : }
4111 :
4112 3 : CPLErr GetGeoTransform(GDALGeoTransform >) const override
4113 : {
4114 3 : gt = m_gt;
4115 3 : return CE_None;
4116 : }
4117 :
4118 : const OGRSpatialReference *GetSpatialRef() const override;
4119 : };
4120 :
4121 1 : const OGRSpatialReference *GDALPDFClippingDataset::GetSpatialRef() const
4122 : {
4123 1 : return poSrcDS->GetSpatialRef();
4124 : }
4125 :
4126 : /************************************************************************/
4127 : /* GDALPDFCreateCopy() */
4128 : /************************************************************************/
4129 :
4130 72 : GDALDataset *GDALPDFCreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
4131 : int bStrict, CSLConstList papszOptions,
4132 : GDALProgressFunc pfnProgress,
4133 : void *pProgressData)
4134 : {
4135 72 : const int nBands = poSrcDS->GetRasterCount();
4136 72 : const int nWidth = poSrcDS->GetRasterXSize();
4137 72 : const int nHeight = poSrcDS->GetRasterYSize();
4138 :
4139 : /* -------------------------------------------------------------------- */
4140 : /* Some some rudimentary checks */
4141 : /* -------------------------------------------------------------------- */
4142 72 : if (nWidth == 0 || nHeight == 0)
4143 : {
4144 1 : CPLError(CE_Failure, CPLE_NotSupported,
4145 : "nWidth == 0 || nHeight == 0 not supported");
4146 1 : return nullptr;
4147 : }
4148 :
4149 71 : if (nBands != 1 && nBands != 3 && nBands != 4)
4150 : {
4151 3 : CPLError(CE_Failure, CPLE_NotSupported,
4152 : "PDF driver doesn't support %d bands. Must be 1 (grey or "
4153 : "with color table), "
4154 : "3 (RGB) or 4 bands.\n",
4155 : nBands);
4156 :
4157 3 : return nullptr;
4158 : }
4159 :
4160 68 : GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
4161 68 : if (eDT != GDT_UInt8)
4162 : {
4163 10 : CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
4164 : "PDF driver doesn't support data type %s. "
4165 : "Only eight bit byte bands supported.\n",
4166 : GDALGetDataTypeName(
4167 : poSrcDS->GetRasterBand(1)->GetRasterDataType()));
4168 :
4169 10 : if (bStrict)
4170 10 : return nullptr;
4171 : }
4172 :
4173 : /* -------------------------------------------------------------------- */
4174 : /* Read options. */
4175 : /* -------------------------------------------------------------------- */
4176 58 : PDFCompressMethod eCompressMethod = COMPRESS_DEFAULT;
4177 58 : const char *pszCompressMethod = CSLFetchNameValue(papszOptions, "COMPRESS");
4178 58 : if (pszCompressMethod)
4179 : {
4180 9 : if (EQUAL(pszCompressMethod, "NONE"))
4181 1 : eCompressMethod = COMPRESS_NONE;
4182 8 : else if (EQUAL(pszCompressMethod, "DEFLATE"))
4183 1 : eCompressMethod = COMPRESS_DEFLATE;
4184 7 : else if (EQUAL(pszCompressMethod, "JPEG"))
4185 3 : eCompressMethod = COMPRESS_JPEG;
4186 4 : else if (EQUAL(pszCompressMethod, "JPEG2000"))
4187 4 : eCompressMethod = COMPRESS_JPEG2000;
4188 : else
4189 : {
4190 0 : CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
4191 : "Unsupported value for COMPRESS.");
4192 :
4193 0 : if (bStrict)
4194 0 : return nullptr;
4195 : }
4196 : }
4197 :
4198 58 : PDFCompressMethod eStreamCompressMethod = COMPRESS_DEFLATE;
4199 : const char *pszStreamCompressMethod =
4200 58 : CSLFetchNameValue(papszOptions, "STREAM_COMPRESS");
4201 58 : if (pszStreamCompressMethod)
4202 : {
4203 1 : if (EQUAL(pszStreamCompressMethod, "NONE"))
4204 1 : eStreamCompressMethod = COMPRESS_NONE;
4205 0 : else if (EQUAL(pszStreamCompressMethod, "DEFLATE"))
4206 0 : eStreamCompressMethod = COMPRESS_DEFLATE;
4207 : else
4208 : {
4209 0 : CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
4210 : "Unsupported value for STREAM_COMPRESS.");
4211 :
4212 0 : if (bStrict)
4213 0 : return nullptr;
4214 : }
4215 : }
4216 :
4217 59 : if (nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr &&
4218 1 : (eCompressMethod == COMPRESS_JPEG ||
4219 : eCompressMethod == COMPRESS_JPEG2000))
4220 : {
4221 0 : CPLError(CE_Warning, CPLE_AppDefined,
4222 : "The source raster band has a color table, which is not "
4223 : "appropriate with JPEG or JPEG2000 compression.\n"
4224 : "You should rather consider using color table expansion "
4225 : "(-expand option in gdal_translate)");
4226 : }
4227 :
4228 58 : int nBlockXSize = nWidth;
4229 58 : int nBlockYSize = nHeight;
4230 :
4231 58 : const bool bTiled = CPLFetchBool(papszOptions, "TILED", false);
4232 58 : if (bTiled)
4233 : {
4234 1 : nBlockXSize = 256;
4235 1 : nBlockYSize = 256;
4236 : }
4237 :
4238 58 : const char *pszValue = CSLFetchNameValue(papszOptions, "BLOCKXSIZE");
4239 58 : if (pszValue != nullptr)
4240 : {
4241 2 : nBlockXSize = atoi(pszValue);
4242 2 : if (nBlockXSize <= 0 || nBlockXSize >= nWidth)
4243 0 : nBlockXSize = nWidth;
4244 : }
4245 :
4246 58 : pszValue = CSLFetchNameValue(papszOptions, "BLOCKYSIZE");
4247 58 : if (pszValue != nullptr)
4248 : {
4249 2 : nBlockYSize = atoi(pszValue);
4250 2 : if (nBlockYSize <= 0 || nBlockYSize >= nHeight)
4251 0 : nBlockYSize = nHeight;
4252 : }
4253 :
4254 58 : int nJPEGQuality = GDALPDFGetJPEGQuality(papszOptions);
4255 :
4256 : const char *pszJPEG2000_DRIVER =
4257 58 : CSLFetchNameValue(papszOptions, "JPEG2000_DRIVER");
4258 :
4259 : const char *pszGEO_ENCODING =
4260 58 : CSLFetchNameValueDef(papszOptions, "GEO_ENCODING", "ISO32000");
4261 58 : if (EQUAL(pszGEO_ENCODING, "OGC_BP"))
4262 : {
4263 0 : CPLError(CE_Failure, CPLE_NotSupported,
4264 : "GEO_ENCODING=OGC_BP is no longer supported. Switch to using "
4265 : "ISO32000");
4266 0 : return nullptr;
4267 : }
4268 58 : else if (EQUAL(pszGEO_ENCODING, "BOTH"))
4269 : {
4270 0 : CPLError(CE_Warning, CPLE_NotSupported,
4271 : "GEO_ENCODING=BOTH is no longer strictly supported. This now "
4272 : "fallbacks to ISO32000");
4273 0 : pszGEO_ENCODING = "ISO32000";
4274 : }
4275 :
4276 58 : const char *pszXMP = CSLFetchNameValue(papszOptions, "XMP");
4277 :
4278 58 : const char *pszPredictor = CSLFetchNameValue(papszOptions, "PREDICTOR");
4279 58 : int nPredictor = 1;
4280 58 : if (pszPredictor)
4281 : {
4282 2 : if (eCompressMethod == COMPRESS_DEFAULT)
4283 2 : eCompressMethod = COMPRESS_DEFLATE;
4284 :
4285 2 : if (eCompressMethod != COMPRESS_DEFLATE)
4286 : {
4287 0 : CPLError(CE_Warning, CPLE_NotSupported,
4288 : "PREDICTOR option is only taken into account for DEFLATE "
4289 : "compression");
4290 : }
4291 : else
4292 : {
4293 2 : nPredictor = atoi(pszPredictor);
4294 2 : if (nPredictor != 1 && nPredictor != 2)
4295 : {
4296 0 : CPLError(CE_Warning, CPLE_NotSupported,
4297 : "Supported PREDICTOR values are 1 or 2");
4298 0 : nPredictor = 1;
4299 : }
4300 : }
4301 : }
4302 :
4303 58 : const char *pszNEATLINE = CSLFetchNameValue(papszOptions, "NEATLINE");
4304 :
4305 58 : int nMargin = atoi(CSLFetchNameValueDef(papszOptions, "MARGIN", "0"));
4306 :
4307 58 : PDFMargins sMargins;
4308 58 : sMargins.nLeft = nMargin;
4309 58 : sMargins.nRight = nMargin;
4310 58 : sMargins.nTop = nMargin;
4311 58 : sMargins.nBottom = nMargin;
4312 :
4313 58 : const char *pszLeftMargin = CSLFetchNameValue(papszOptions, "LEFT_MARGIN");
4314 58 : if (pszLeftMargin)
4315 2 : sMargins.nLeft = atoi(pszLeftMargin);
4316 :
4317 : const char *pszRightMargin =
4318 58 : CSLFetchNameValue(papszOptions, "RIGHT_MARGIN");
4319 58 : if (pszRightMargin)
4320 1 : sMargins.nRight = atoi(pszRightMargin);
4321 :
4322 58 : const char *pszTopMargin = CSLFetchNameValue(papszOptions, "TOP_MARGIN");
4323 58 : if (pszTopMargin)
4324 2 : sMargins.nTop = atoi(pszTopMargin);
4325 :
4326 : const char *pszBottomMargin =
4327 58 : CSLFetchNameValue(papszOptions, "BOTTOM_MARGIN");
4328 58 : if (pszBottomMargin)
4329 1 : sMargins.nBottom = atoi(pszBottomMargin);
4330 :
4331 58 : const char *pszDPI = CSLFetchNameValue(papszOptions, "DPI");
4332 58 : double dfDPI = DEFAULT_DPI;
4333 58 : if (pszDPI != nullptr)
4334 7 : dfDPI = CPLAtof(pszDPI);
4335 :
4336 : const char *pszWriteUserUnit =
4337 58 : CSLFetchNameValue(papszOptions, "WRITE_USERUNIT");
4338 : bool bWriteUserUnit;
4339 58 : if (pszWriteUserUnit != nullptr)
4340 1 : bWriteUserUnit = CPLTestBool(pszWriteUserUnit);
4341 : else
4342 57 : bWriteUserUnit = (pszDPI == nullptr);
4343 :
4344 58 : double dfUserUnit = dfDPI * USER_UNIT_IN_INCH;
4345 58 : double dfWidthInUserUnit =
4346 58 : nWidth / dfUserUnit + sMargins.nLeft + sMargins.nRight;
4347 58 : double dfHeightInUserUnit =
4348 58 : nHeight / dfUserUnit + sMargins.nBottom + sMargins.nTop;
4349 58 : if (dfWidthInUserUnit > MAXIMUM_SIZE_IN_UNITS ||
4350 : dfHeightInUserUnit > MAXIMUM_SIZE_IN_UNITS)
4351 : {
4352 6 : if (pszDPI == nullptr)
4353 : {
4354 4 : if (sMargins.nLeft + sMargins.nRight >= MAXIMUM_SIZE_IN_UNITS ||
4355 3 : sMargins.nBottom + sMargins.nTop >= MAXIMUM_SIZE_IN_UNITS)
4356 : {
4357 2 : CPLError(
4358 : CE_Warning, CPLE_AppDefined,
4359 : "Margins too big compared to maximum page dimension (%d) "
4360 : "in user units allowed by Acrobat",
4361 : MAXIMUM_SIZE_IN_UNITS);
4362 : }
4363 : else
4364 : {
4365 2 : if (dfWidthInUserUnit >= dfHeightInUserUnit)
4366 : {
4367 1 : dfDPI = ceil(double(nWidth) /
4368 1 : (MAXIMUM_SIZE_IN_UNITS -
4369 1 : (sMargins.nLeft + sMargins.nRight)) /
4370 : USER_UNIT_IN_INCH);
4371 : }
4372 : else
4373 : {
4374 1 : dfDPI = ceil(double(nHeight) /
4375 1 : (MAXIMUM_SIZE_IN_UNITS -
4376 1 : (sMargins.nBottom + sMargins.nTop)) /
4377 : USER_UNIT_IN_INCH);
4378 : }
4379 2 : CPLDebug("PDF",
4380 : "Adjusting DPI to %d so that page dimension in "
4381 : "user units remain in what is accepted by Acrobat",
4382 : static_cast<int>(dfDPI));
4383 : }
4384 : }
4385 : else
4386 : {
4387 2 : CPLError(CE_Warning, CPLE_AppDefined,
4388 : "The page dimension in user units is %d x %d whereas the "
4389 : "maximum allowed by Acrobat is %d x %d",
4390 2 : static_cast<int>(dfWidthInUserUnit + 0.5),
4391 2 : static_cast<int>(dfHeightInUserUnit + 0.5),
4392 : MAXIMUM_SIZE_IN_UNITS, MAXIMUM_SIZE_IN_UNITS);
4393 : }
4394 : }
4395 :
4396 58 : if (dfDPI < DEFAULT_DPI)
4397 0 : dfDPI = DEFAULT_DPI;
4398 :
4399 : const char *pszClippingExtent =
4400 58 : CSLFetchNameValue(papszOptions, "CLIPPING_EXTENT");
4401 58 : int bUseClippingExtent = FALSE;
4402 58 : double adfClippingExtent[4] = {0.0, 0.0, 0.0, 0.0};
4403 58 : if (pszClippingExtent != nullptr)
4404 : {
4405 1 : char **papszTokens = CSLTokenizeString2(pszClippingExtent, ",", 0);
4406 1 : if (CSLCount(papszTokens) == 4)
4407 : {
4408 1 : bUseClippingExtent = TRUE;
4409 1 : adfClippingExtent[0] = CPLAtof(papszTokens[0]);
4410 1 : adfClippingExtent[1] = CPLAtof(papszTokens[1]);
4411 1 : adfClippingExtent[2] = CPLAtof(papszTokens[2]);
4412 1 : adfClippingExtent[3] = CPLAtof(papszTokens[3]);
4413 1 : if (adfClippingExtent[0] > adfClippingExtent[2] ||
4414 1 : adfClippingExtent[1] > adfClippingExtent[3])
4415 : {
4416 0 : CPLError(CE_Warning, CPLE_AppDefined,
4417 : "Invalid value for CLIPPING_EXTENT. Should be "
4418 : "xmin,ymin,xmax,ymax");
4419 0 : bUseClippingExtent = FALSE;
4420 : }
4421 :
4422 1 : if (bUseClippingExtent)
4423 : {
4424 1 : GDALGeoTransform gt;
4425 1 : if (poSrcDS->GetGeoTransform(gt) == CE_None)
4426 : {
4427 1 : if (gt.xrot != 0.0 || gt.yrot != 0.0)
4428 : {
4429 0 : CPLError(CE_Warning, CPLE_AppDefined,
4430 : "Cannot use CLIPPING_EXTENT because main "
4431 : "raster has a rotated geotransform");
4432 0 : bUseClippingExtent = FALSE;
4433 : }
4434 : }
4435 : else
4436 : {
4437 0 : CPLError(CE_Warning, CPLE_AppDefined,
4438 : "Cannot use CLIPPING_EXTENT because main raster "
4439 : "has no geotransform");
4440 0 : bUseClippingExtent = FALSE;
4441 : }
4442 : }
4443 : }
4444 1 : CSLDestroy(papszTokens);
4445 : }
4446 :
4447 58 : const char *pszLayerName = CSLFetchNameValue(papszOptions, "LAYER_NAME");
4448 :
4449 : const char *pszExtraImages =
4450 58 : CSLFetchNameValue(papszOptions, "EXTRA_IMAGES");
4451 : const char *pszExtraStream =
4452 58 : CSLFetchNameValue(papszOptions, "EXTRA_STREAM");
4453 : const char *pszExtraLayerName =
4454 58 : CSLFetchNameValue(papszOptions, "EXTRA_LAYER_NAME");
4455 :
4456 : const char *pszOGRDataSource =
4457 58 : CSLFetchNameValue(papszOptions, "OGR_DATASOURCE");
4458 : const char *pszOGRDisplayField =
4459 58 : CSLFetchNameValue(papszOptions, "OGR_DISPLAY_FIELD");
4460 : const char *pszOGRDisplayLayerNames =
4461 58 : CSLFetchNameValue(papszOptions, "OGR_DISPLAY_LAYER_NAMES");
4462 : const char *pszOGRLinkField =
4463 58 : CSLFetchNameValue(papszOptions, "OGR_LINK_FIELD");
4464 : const bool bWriteOGRAttributes =
4465 58 : CPLFetchBool(papszOptions, "OGR_WRITE_ATTRIBUTES", true);
4466 :
4467 : const char *pszExtraRasters =
4468 58 : CSLFetchNameValue(papszOptions, "EXTRA_RASTERS");
4469 : const char *pszExtraRastersLayerName =
4470 58 : CSLFetchNameValue(papszOptions, "EXTRA_RASTERS_LAYER_NAME");
4471 :
4472 58 : const char *pszOffLayers = CSLFetchNameValue(papszOptions, "OFF_LAYERS");
4473 : const char *pszExclusiveLayers =
4474 58 : CSLFetchNameValue(papszOptions, "EXCLUSIVE_LAYERS");
4475 :
4476 58 : const char *pszJavascript = CSLFetchNameValue(papszOptions, "JAVASCRIPT");
4477 : const char *pszJavascriptFile =
4478 58 : CSLFetchNameValue(papszOptions, "JAVASCRIPT_FILE");
4479 :
4480 58 : if (!pfnProgress(0.0, nullptr, pProgressData))
4481 0 : return nullptr;
4482 :
4483 : /* -------------------------------------------------------------------- */
4484 : /* Create file. */
4485 : /* -------------------------------------------------------------------- */
4486 58 : VSILFILE *fp = VSIFOpenL(pszFilename, "wb");
4487 58 : if (fp == nullptr)
4488 : {
4489 3 : CPLError(CE_Failure, CPLE_OpenFailed, "Unable to create PDF file %s.\n",
4490 : pszFilename);
4491 3 : return nullptr;
4492 : }
4493 :
4494 110 : GDALPDFWriter oWriter(fp);
4495 :
4496 55 : GDALDataset *poClippingDS = poSrcDS;
4497 55 : if (bUseClippingExtent)
4498 1 : poClippingDS = new GDALPDFClippingDataset(poSrcDS, adfClippingExtent);
4499 :
4500 55 : if (CPLFetchBool(papszOptions, "WRITE_INFO", true))
4501 54 : oWriter.SetInfo(poSrcDS, papszOptions);
4502 55 : oWriter.SetXMP(poClippingDS, pszXMP);
4503 :
4504 55 : oWriter.StartPage(poClippingDS, dfDPI, bWriteUserUnit, pszGEO_ENCODING,
4505 : pszNEATLINE, &sMargins, eStreamCompressMethod,
4506 55 : pszOGRDataSource != nullptr && bWriteOGRAttributes);
4507 :
4508 : int bRet;
4509 :
4510 55 : if (!bUseClippingExtent)
4511 : {
4512 54 : bRet = oWriter.WriteImagery(poSrcDS, pszLayerName, eCompressMethod,
4513 : nPredictor, nJPEGQuality,
4514 : pszJPEG2000_DRIVER, nBlockXSize,
4515 : nBlockYSize, pfnProgress, pProgressData);
4516 : }
4517 : else
4518 : {
4519 1 : bRet = oWriter.WriteClippedImagery(
4520 : poSrcDS, pszLayerName, eCompressMethod, nPredictor, nJPEGQuality,
4521 : pszJPEG2000_DRIVER, nBlockXSize, nBlockYSize, pfnProgress,
4522 : pProgressData);
4523 : }
4524 :
4525 : char **papszExtraRasters =
4526 55 : CSLTokenizeString2(pszExtraRasters ? pszExtraRasters : "", ",", 0);
4527 55 : char **papszExtraRastersLayerName = CSLTokenizeString2(
4528 : pszExtraRastersLayerName ? pszExtraRastersLayerName : "", ",", 0);
4529 : int bUseExtraRastersLayerName =
4530 55 : (CSLCount(papszExtraRasters) == CSLCount(papszExtraRastersLayerName));
4531 55 : int bUseExtraRasters = TRUE;
4532 :
4533 55 : const char *pszClippingProjectionRef = poSrcDS->GetProjectionRef();
4534 55 : if (CSLCount(papszExtraRasters) != 0)
4535 : {
4536 1 : GDALGeoTransform gt;
4537 1 : if (poSrcDS->GetGeoTransform(gt) == CE_None)
4538 : {
4539 1 : if (gt.xrot != 0.0 || gt.yrot != 0.0)
4540 : {
4541 0 : CPLError(CE_Warning, CPLE_AppDefined,
4542 : "Cannot use EXTRA_RASTERS because main raster has a "
4543 : "rotated geotransform");
4544 0 : bUseExtraRasters = FALSE;
4545 : }
4546 : }
4547 : else
4548 : {
4549 0 : CPLError(CE_Warning, CPLE_AppDefined,
4550 : "Cannot use EXTRA_RASTERS because main raster has no "
4551 : "geotransform");
4552 0 : bUseExtraRasters = FALSE;
4553 : }
4554 1 : if (bUseExtraRasters && (pszClippingProjectionRef == nullptr ||
4555 1 : pszClippingProjectionRef[0] == '\0'))
4556 : {
4557 0 : CPLError(CE_Warning, CPLE_AppDefined,
4558 : "Cannot use EXTRA_RASTERS because main raster has no "
4559 : "projection");
4560 0 : bUseExtraRasters = FALSE;
4561 : }
4562 : }
4563 :
4564 56 : for (int i = 0; bRet && bUseExtraRasters && papszExtraRasters[i] != nullptr;
4565 : i++)
4566 : {
4567 : auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
4568 1 : papszExtraRasters[i], GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
4569 2 : nullptr, nullptr, nullptr));
4570 1 : if (poDS != nullptr)
4571 : {
4572 1 : GDALGeoTransform gt;
4573 1 : int bUseRaster = TRUE;
4574 1 : if (poDS->GetGeoTransform(gt) == CE_None)
4575 : {
4576 1 : if (gt.xrot != 0.0 || gt.yrot != 0.0)
4577 : {
4578 0 : CPLError(
4579 : CE_Warning, CPLE_AppDefined,
4580 : "Cannot use %s because it has a rotated geotransform",
4581 0 : papszExtraRasters[i]);
4582 0 : bUseRaster = FALSE;
4583 : }
4584 : }
4585 : else
4586 : {
4587 0 : CPLError(CE_Warning, CPLE_AppDefined,
4588 : "Cannot use %s because it has no geotransform",
4589 0 : papszExtraRasters[i]);
4590 0 : bUseRaster = FALSE;
4591 : }
4592 1 : const char *pszProjectionRef = poDS->GetProjectionRef();
4593 1 : if (bUseRaster &&
4594 1 : (pszProjectionRef == nullptr || pszProjectionRef[0] == '\0'))
4595 : {
4596 0 : CPLError(CE_Warning, CPLE_AppDefined,
4597 : "Cannot use %s because it has no projection",
4598 0 : papszExtraRasters[i]);
4599 0 : bUseRaster = FALSE;
4600 : }
4601 1 : if (bUseRaster)
4602 : {
4603 1 : if (pszClippingProjectionRef != nullptr &&
4604 1 : pszProjectionRef != nullptr &&
4605 1 : !EQUAL(pszClippingProjectionRef, pszProjectionRef))
4606 : {
4607 : OGRSpatialReferenceH hClippingSRS =
4608 1 : OSRNewSpatialReference(pszClippingProjectionRef);
4609 : OGRSpatialReferenceH hSRS =
4610 1 : OSRNewSpatialReference(pszProjectionRef);
4611 1 : if (!OSRIsSame(hClippingSRS, hSRS))
4612 : {
4613 0 : CPLError(CE_Warning, CPLE_AppDefined,
4614 : "Cannot use %s because it has a different "
4615 : "projection than main dataset",
4616 0 : papszExtraRasters[i]);
4617 0 : bUseRaster = FALSE;
4618 : }
4619 1 : OSRDestroySpatialReference(hClippingSRS);
4620 1 : OSRDestroySpatialReference(hSRS);
4621 : }
4622 : }
4623 1 : if (bUseRaster)
4624 : {
4625 2 : bRet = oWriter.WriteClippedImagery(
4626 : poDS.get(),
4627 1 : bUseExtraRastersLayerName ? papszExtraRastersLayerName[i]
4628 : : nullptr,
4629 : eCompressMethod, nPredictor, nJPEGQuality,
4630 : pszJPEG2000_DRIVER, nBlockXSize, nBlockYSize, nullptr,
4631 : nullptr);
4632 : }
4633 : }
4634 : }
4635 :
4636 55 : CSLDestroy(papszExtraRasters);
4637 55 : CSLDestroy(papszExtraRastersLayerName);
4638 :
4639 55 : if (bRet && pszOGRDataSource != nullptr)
4640 2 : oWriter.WriteOGRDataSource(pszOGRDataSource, pszOGRDisplayField,
4641 : pszOGRDisplayLayerNames, pszOGRLinkField,
4642 : bWriteOGRAttributes);
4643 :
4644 55 : if (bRet)
4645 53 : oWriter.EndPage(pszExtraImages, pszExtraStream, pszExtraLayerName,
4646 : pszOffLayers, pszExclusiveLayers);
4647 :
4648 55 : if (pszJavascript)
4649 1 : oWriter.WriteJavascript(pszJavascript);
4650 54 : else if (pszJavascriptFile)
4651 0 : oWriter.WriteJavascriptFile(pszJavascriptFile);
4652 :
4653 55 : oWriter.Close();
4654 :
4655 55 : if (poClippingDS != poSrcDS)
4656 1 : delete poClippingDS;
4657 :
4658 55 : if (!bRet)
4659 : {
4660 2 : VSIUnlink(pszFilename);
4661 2 : return nullptr;
4662 : }
4663 : else
4664 : {
4665 : #ifdef HAVE_PDF_READ_SUPPORT
4666 53 : GDALDataset *poDS = GDALPDFOpen(pszFilename, GA_ReadOnly);
4667 53 : if (poDS == nullptr)
4668 8 : return nullptr;
4669 45 : char **papszMD = CSLDuplicate(poSrcDS->GetMetadata());
4670 45 : papszMD = CSLMerge(papszMD, poDS->GetMetadata());
4671 45 : const char *pszAOP = CSLFetchNameValue(papszMD, GDALMD_AREA_OR_POINT);
4672 45 : if (pszAOP != nullptr && EQUAL(pszAOP, GDALMD_AOP_AREA))
4673 27 : papszMD = CSLSetNameValue(papszMD, GDALMD_AREA_OR_POINT, nullptr);
4674 45 : poDS->SetMetadata(papszMD);
4675 45 : if (EQUAL(pszGEO_ENCODING, "NONE"))
4676 : {
4677 2 : GDALGeoTransform gt;
4678 2 : if (poSrcDS->GetGeoTransform(gt) == CE_None)
4679 : {
4680 2 : poDS->SetGeoTransform(gt);
4681 : }
4682 2 : const char *pszProjectionRef = poSrcDS->GetProjectionRef();
4683 2 : if (pszProjectionRef != nullptr && pszProjectionRef[0] != '\0')
4684 : {
4685 2 : poDS->SetProjection(pszProjectionRef);
4686 : }
4687 : }
4688 45 : CSLDestroy(papszMD);
4689 45 : return poDS;
4690 : #else
4691 : return new GDALFakePDFDataset();
4692 : #endif
4693 : }
4694 : }
|