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