Line data Source code
1 : /******************************************************************************
2 : * $Id$
3 : *
4 : * Project: PDF driver
5 : * Purpose: GDALDataset driver for PDF dataset.
6 : * Author: Even Rouault, <even dot rouault at spatialys dot com>
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2019, Even Rouault <even dot rouault at spatialys dot com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #include "gdal_pdf.h"
15 : #include "pdfcreatecopy.h"
16 :
17 : #include <algorithm>
18 : #include <cmath>
19 : #include <cstdlib>
20 :
21 : #include "pdfcreatefromcomposition.h"
22 : #include "cpl_conv.h"
23 : #include "cpl_minixml.h"
24 : #include "cpl_vsi_virtual.h"
25 : #include "ogr_geometry.h"
26 :
27 : #ifdef EMBED_RESOURCE_FILES
28 : #include "embedded_resources.h"
29 : #endif
30 :
31 : /************************************************************************/
32 : /* GDALPDFComposerWriter() */
33 : /************************************************************************/
34 :
35 38 : GDALPDFComposerWriter::GDALPDFComposerWriter(VSILFILE *fp)
36 38 : : GDALPDFBaseWriter(fp)
37 : {
38 38 : StartNewDoc();
39 38 : }
40 :
41 : /************************************************************************/
42 : /* ~GDALPDFComposerWriter() */
43 : /************************************************************************/
44 :
45 38 : GDALPDFComposerWriter::~GDALPDFComposerWriter()
46 : {
47 38 : Close();
48 38 : }
49 :
50 : /************************************************************************/
51 : /* Close() */
52 : /************************************************************************/
53 :
54 38 : void GDALPDFComposerWriter::Close()
55 : {
56 38 : if (m_fp)
57 : {
58 38 : CPLAssert(!m_bInWriteObj);
59 38 : if (m_nPageResourceId.toBool())
60 : {
61 38 : WritePages();
62 38 : WriteXRefTableAndTrailer(false, 0);
63 : }
64 : }
65 38 : GDALPDFBaseWriter::Close();
66 38 : }
67 :
68 : /************************************************************************/
69 : /* CreateOCGOrder() */
70 : /************************************************************************/
71 :
72 7 : GDALPDFArrayRW *GDALPDFComposerWriter::CreateOCGOrder(const TreeOfOCG *parent)
73 : {
74 7 : auto poArrayOrder = new GDALPDFArrayRW();
75 19 : for (const auto &child : parent->m_children)
76 : {
77 12 : poArrayOrder->Add(child->m_nNum, 0);
78 12 : if (!child->m_children.empty())
79 : {
80 2 : poArrayOrder->Add(CreateOCGOrder(child.get()));
81 : }
82 : }
83 7 : return poArrayOrder;
84 : }
85 :
86 : /************************************************************************/
87 : /* CollectOffOCG() */
88 : /************************************************************************/
89 :
90 17 : void GDALPDFComposerWriter::CollectOffOCG(std::vector<GDALPDFObjectNum> &ar,
91 : const TreeOfOCG *parent)
92 : {
93 17 : if (!parent->m_bInitiallyVisible)
94 1 : ar.push_back(parent->m_nNum);
95 29 : for (const auto &child : parent->m_children)
96 : {
97 12 : CollectOffOCG(ar, child.get());
98 : }
99 17 : }
100 :
101 : /************************************************************************/
102 : /* WritePages() */
103 : /************************************************************************/
104 :
105 38 : void GDALPDFComposerWriter::WritePages()
106 : {
107 38 : StartObj(m_nPageResourceId);
108 : {
109 38 : GDALPDFDictionaryRW oDict;
110 38 : GDALPDFArrayRW *poKids = new GDALPDFArrayRW();
111 38 : oDict.Add("Type", GDALPDFObjectRW::CreateName("Pages"))
112 38 : .Add("Count", static_cast<int>(m_asPageId.size()))
113 38 : .Add("Kids", poKids);
114 :
115 70 : for (size_t i = 0; i < m_asPageId.size(); i++)
116 32 : poKids->Add(m_asPageId[i], 0);
117 :
118 38 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
119 : }
120 38 : EndObj();
121 :
122 38 : if (m_nStructTreeRootId.toBool())
123 : {
124 3 : auto nParentTreeId = AllocNewObject();
125 3 : StartObj(nParentTreeId);
126 3 : VSIFPrintfL(m_fp, "<< /Nums [ ");
127 6 : for (size_t i = 0; i < m_anParentElements.size(); i++)
128 : {
129 3 : VSIFPrintfL(m_fp, "%d %d 0 R ", static_cast<int>(i),
130 3 : m_anParentElements[i].toInt());
131 : }
132 3 : VSIFPrintfL(m_fp, " ] >> \n");
133 3 : EndObj();
134 :
135 3 : StartObj(m_nStructTreeRootId);
136 3 : VSIFPrintfL(m_fp,
137 : "<< "
138 : "/Type /StructTreeRoot "
139 : "/ParentTree %d 0 R "
140 : "/K [ ",
141 : nParentTreeId.toInt());
142 8 : for (const auto &num : m_anFeatureLayerId)
143 : {
144 5 : VSIFPrintfL(m_fp, "%d 0 R ", num.toInt());
145 : }
146 3 : VSIFPrintfL(m_fp, "] >>\n");
147 3 : EndObj();
148 : }
149 :
150 38 : StartObj(m_nCatalogId);
151 : {
152 38 : GDALPDFDictionaryRW oDict;
153 38 : oDict.Add("Type", GDALPDFObjectRW::CreateName("Catalog"))
154 38 : .Add("Pages", m_nPageResourceId, 0);
155 38 : if (m_nOutlinesId.toBool())
156 1 : oDict.Add("Outlines", m_nOutlinesId, 0);
157 38 : if (m_nXMPId.toBool())
158 1 : oDict.Add("Metadata", m_nXMPId, 0);
159 38 : if (!m_asOCGs.empty())
160 : {
161 5 : GDALPDFDictionaryRW *poDictOCProperties = new GDALPDFDictionaryRW();
162 5 : oDict.Add("OCProperties", poDictOCProperties);
163 :
164 5 : GDALPDFDictionaryRW *poDictD = new GDALPDFDictionaryRW();
165 5 : poDictOCProperties->Add("D", poDictD);
166 :
167 5 : if (m_bDisplayLayersOnlyOnVisiblePages)
168 : {
169 : poDictD->Add("ListMode",
170 1 : GDALPDFObjectRW::CreateName("VisiblePages"));
171 : }
172 :
173 : /* Build "Order" array of D dict */
174 5 : GDALPDFArrayRW *poArrayOrder = CreateOCGOrder(&m_oTreeOfOGC);
175 5 : poDictD->Add("Order", poArrayOrder);
176 :
177 : /* Build "OFF" array of D dict */
178 10 : std::vector<GDALPDFObjectNum> offOCGs;
179 5 : CollectOffOCG(offOCGs, &m_oTreeOfOGC);
180 5 : if (!offOCGs.empty())
181 : {
182 1 : GDALPDFArrayRW *poArrayOFF = new GDALPDFArrayRW();
183 2 : for (const auto &num : offOCGs)
184 : {
185 1 : poArrayOFF->Add(num, 0);
186 : }
187 :
188 1 : poDictD->Add("OFF", poArrayOFF);
189 : }
190 :
191 : /* Build "RBGroups" array of D dict */
192 5 : if (!m_oMapExclusiveOCGIdToOCGs.empty())
193 : {
194 1 : GDALPDFArrayRW *poArrayRBGroups = new GDALPDFArrayRW();
195 2 : for (const auto &group : m_oMapExclusiveOCGIdToOCGs)
196 : {
197 1 : GDALPDFArrayRW *poGroup = new GDALPDFArrayRW();
198 3 : for (const auto &num : group.second)
199 : {
200 2 : poGroup->Add(num, 0);
201 : }
202 1 : poArrayRBGroups->Add(poGroup);
203 : }
204 :
205 1 : poDictD->Add("RBGroups", poArrayRBGroups);
206 : }
207 :
208 5 : GDALPDFArrayRW *poArrayOGCs = new GDALPDFArrayRW();
209 17 : for (const auto &ocg : m_asOCGs)
210 12 : poArrayOGCs->Add(ocg.nId, 0);
211 5 : poDictOCProperties->Add("OCGs", poArrayOGCs);
212 : }
213 :
214 38 : if (m_nStructTreeRootId.toBool())
215 : {
216 3 : GDALPDFDictionaryRW *poDictMarkInfo = new GDALPDFDictionaryRW();
217 3 : oDict.Add("MarkInfo", poDictMarkInfo);
218 : poDictMarkInfo->Add("UserProperties",
219 3 : GDALPDFObjectRW::CreateBool(TRUE));
220 :
221 3 : oDict.Add("StructTreeRoot", m_nStructTreeRootId, 0);
222 : }
223 :
224 38 : if (m_nNamesId.toBool())
225 1 : oDict.Add("Names", m_nNamesId, 0);
226 :
227 38 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
228 : }
229 38 : EndObj();
230 38 : }
231 :
232 : /************************************************************************/
233 : /* CreateLayerTree() */
234 : /************************************************************************/
235 :
236 19 : bool GDALPDFComposerWriter::CreateLayerTree(const CPLXMLNode *psNode,
237 : const GDALPDFObjectNum &nParentId,
238 : TreeOfOCG *parent)
239 : {
240 59 : for (const auto *psIter = psNode->psChild; psIter; psIter = psIter->psNext)
241 : {
242 43 : if (psIter->eType == CXT_Element &&
243 15 : strcmp(psIter->pszValue, "Layer") == 0)
244 : {
245 15 : const char *pszId = CPLGetXMLValue(psIter, "id", nullptr);
246 15 : if (!pszId)
247 : {
248 1 : CPLError(CE_Failure, CPLE_AppDefined,
249 : "Missing id attribute in Layer");
250 3 : return false;
251 : }
252 14 : const char *pszName = CPLGetXMLValue(psIter, "name", nullptr);
253 14 : if (!pszName)
254 : {
255 1 : CPLError(CE_Failure, CPLE_AppDefined,
256 : "Missing name attribute in Layer");
257 1 : return false;
258 : }
259 13 : if (m_oMapLayerIdToOCG.find(pszId) != m_oMapLayerIdToOCG.end())
260 : {
261 1 : CPLError(CE_Failure, CPLE_AppDefined,
262 : "Layer.id = %s is not unique", pszId);
263 1 : return false;
264 : }
265 :
266 : const bool bInitiallyVisible =
267 12 : CPLTestBool(CPLGetXMLValue(psIter, "initiallyVisible", "true"));
268 :
269 : const char *pszMutuallyExclusiveGroupId =
270 12 : CPLGetXMLValue(psIter, "mutuallyExclusiveGroupId", nullptr);
271 :
272 12 : auto nThisObjId = WriteOCG(pszName, nParentId);
273 12 : m_oMapLayerIdToOCG[pszId] = nThisObjId;
274 :
275 12 : auto newTreeOfOCG = std::make_unique<TreeOfOCG>();
276 12 : newTreeOfOCG->m_nNum = nThisObjId;
277 12 : newTreeOfOCG->m_bInitiallyVisible = bInitiallyVisible;
278 12 : parent->m_children.emplace_back(std::move(newTreeOfOCG));
279 :
280 12 : if (pszMutuallyExclusiveGroupId)
281 : {
282 4 : m_oMapExclusiveOCGIdToOCGs[pszMutuallyExclusiveGroupId]
283 2 : .push_back(nThisObjId);
284 : }
285 :
286 12 : if (!CreateLayerTree(psIter, nThisObjId,
287 12 : parent->m_children.back().get()))
288 : {
289 0 : return false;
290 : }
291 : }
292 : }
293 16 : return true;
294 : }
295 :
296 : /************************************************************************/
297 : /* ParseActions() */
298 : /************************************************************************/
299 :
300 12 : bool GDALPDFComposerWriter::ParseActions(
301 : const CPLXMLNode *psNode, std::vector<std::unique_ptr<Action>> &actions)
302 : {
303 24 : std::set<GDALPDFObjectNum> anONLayers{};
304 24 : std::set<GDALPDFObjectNum> anOFFLayers{};
305 23 : for (const auto *psIter = psNode->psChild; psIter; psIter = psIter->psNext)
306 : {
307 15 : if (psIter->eType == CXT_Element &&
308 15 : strcmp(psIter->pszValue, "GotoPageAction") == 0)
309 : {
310 7 : auto poAction = std::make_unique<GotoPageAction>();
311 7 : const char *pszPageId = CPLGetXMLValue(psIter, "pageId", nullptr);
312 7 : if (!pszPageId)
313 : {
314 1 : CPLError(CE_Failure, CPLE_AppDefined,
315 : "Missing pageId attribute in GotoPageAction");
316 1 : return false;
317 : }
318 :
319 6 : auto oIter = m_oMapPageIdToObjectNum.find(pszPageId);
320 6 : if (oIter == m_oMapPageIdToObjectNum.end())
321 : {
322 1 : CPLError(CE_Failure, CPLE_AppDefined,
323 : "GotoPageAction.pageId = %s not pointing to a Page.id",
324 : pszPageId);
325 1 : return false;
326 : }
327 5 : poAction->m_nPageDestId = oIter->second;
328 5 : poAction->m_dfX1 = CPLAtof(CPLGetXMLValue(psIter, "x1", "0"));
329 5 : poAction->m_dfX2 = CPLAtof(CPLGetXMLValue(psIter, "y1", "0"));
330 5 : poAction->m_dfY1 = CPLAtof(CPLGetXMLValue(psIter, "x2", "0"));
331 5 : poAction->m_dfY2 = CPLAtof(CPLGetXMLValue(psIter, "y2", "0"));
332 10 : actions.push_back(std::move(poAction));
333 : }
334 8 : else if (psIter->eType == CXT_Element &&
335 8 : strcmp(psIter->pszValue, "SetAllLayersStateAction") == 0)
336 : {
337 2 : if (CPLTestBool(CPLGetXMLValue(psIter, "visible", "true")))
338 : {
339 3 : for (const auto &ocg : m_asOCGs)
340 : {
341 2 : anOFFLayers.erase(ocg.nId);
342 2 : anONLayers.insert(ocg.nId);
343 : }
344 : }
345 : else
346 : {
347 3 : for (const auto &ocg : m_asOCGs)
348 : {
349 2 : anONLayers.erase(ocg.nId);
350 2 : anOFFLayers.insert(ocg.nId);
351 : }
352 2 : }
353 : }
354 6 : else if (psIter->eType == CXT_Element &&
355 6 : strcmp(psIter->pszValue, "SetLayerStateAction") == 0)
356 : {
357 5 : const char *pszLayerId = CPLGetXMLValue(psIter, "layerId", nullptr);
358 5 : if (!pszLayerId)
359 : {
360 1 : CPLError(CE_Failure, CPLE_AppDefined, "Missing layerId");
361 2 : return false;
362 : }
363 4 : auto oIter = m_oMapLayerIdToOCG.find(pszLayerId);
364 4 : if (oIter == m_oMapLayerIdToOCG.end())
365 : {
366 1 : CPLError(CE_Failure, CPLE_AppDefined,
367 : "Referencing layer of unknown id: %s", pszLayerId);
368 1 : return false;
369 : }
370 3 : const auto &ocg = oIter->second;
371 :
372 3 : if (CPLTestBool(CPLGetXMLValue(psIter, "visible", "true")))
373 : {
374 2 : anOFFLayers.erase(ocg);
375 2 : anONLayers.insert(ocg);
376 : }
377 : else
378 : {
379 1 : anONLayers.erase(ocg);
380 1 : anOFFLayers.insert(ocg);
381 3 : }
382 : }
383 1 : else if (psIter->eType == CXT_Element &&
384 1 : strcmp(psIter->pszValue, "JavascriptAction") == 0)
385 : {
386 1 : auto poAction = std::make_unique<JavascriptAction>();
387 1 : poAction->m_osScript = CPLGetXMLValue(psIter, nullptr, "");
388 1 : actions.push_back(std::move(poAction));
389 : }
390 : }
391 :
392 8 : if (!anONLayers.empty() || !anOFFLayers.empty())
393 : {
394 4 : auto poAction = std::make_unique<SetLayerStateAction>();
395 4 : poAction->m_anONLayers = std::move(anONLayers);
396 4 : poAction->m_anOFFLayers = std::move(anOFFLayers);
397 4 : actions.push_back(std::move(poAction));
398 : }
399 :
400 8 : return true;
401 : }
402 :
403 : /************************************************************************/
404 : /* CreateOutlineFirstPass() */
405 : /************************************************************************/
406 :
407 14 : bool GDALPDFComposerWriter::CreateOutlineFirstPass(const CPLXMLNode *psNode,
408 : OutlineItem *poParentItem)
409 : {
410 43 : for (const auto *psIter = psNode->psChild; psIter; psIter = psIter->psNext)
411 : {
412 33 : if (psIter->eType == CXT_Element &&
413 21 : strcmp(psIter->pszValue, "OutlineItem") == 0)
414 : {
415 13 : auto newItem = std::make_unique<OutlineItem>();
416 13 : const char *pszName = CPLGetXMLValue(psIter, "name", nullptr);
417 13 : if (!pszName)
418 : {
419 0 : CPLError(CE_Failure, CPLE_AppDefined,
420 : "Missing name attribute in OutlineItem");
421 0 : return false;
422 : }
423 13 : newItem->m_osName = pszName;
424 26 : newItem->m_bOpen =
425 13 : CPLTestBool(CPLGetXMLValue(psIter, "open", "true"));
426 13 : if (CPLTestBool(CPLGetXMLValue(psIter, "italic", "false")))
427 1 : newItem->m_nFlags |= 1 << 0;
428 13 : if (CPLTestBool(CPLGetXMLValue(psIter, "bold", "false")))
429 1 : newItem->m_nFlags |= 1 << 1;
430 :
431 13 : const auto poActions = CPLGetXMLNode(psIter, "Actions");
432 13 : if (poActions)
433 : {
434 12 : if (!ParseActions(poActions, newItem->m_aoActions))
435 4 : return false;
436 : }
437 :
438 9 : newItem->m_nObjId = AllocNewObject();
439 9 : if (!CreateOutlineFirstPass(psIter, newItem.get()))
440 : {
441 0 : return false;
442 : }
443 9 : poParentItem->m_nKidsRecCount += 1 + newItem->m_nKidsRecCount;
444 9 : poParentItem->m_aoKids.push_back(std::move(newItem));
445 : }
446 : }
447 10 : return true;
448 : }
449 :
450 : /************************************************************************/
451 : /* SerializeActions() */
452 : /************************************************************************/
453 :
454 9 : GDALPDFDictionaryRW *GDALPDFComposerWriter::SerializeActions(
455 : GDALPDFDictionaryRW *poDictForDest,
456 : const std::vector<std::unique_ptr<Action>> &actions)
457 : {
458 9 : GDALPDFDictionaryRW *poRetAction = nullptr;
459 9 : GDALPDFDictionaryRW *poLastActionDict = nullptr;
460 19 : for (const auto &poAction : actions)
461 : {
462 10 : GDALPDFDictionaryRW *poActionDict = nullptr;
463 10 : auto poGotoPageAction = dynamic_cast<GotoPageAction *>(poAction.get());
464 10 : if (poGotoPageAction)
465 : {
466 5 : GDALPDFArrayRW *poDest = new GDALPDFArrayRW;
467 5 : poDest->Add(poGotoPageAction->m_nPageDestId, 0);
468 5 : if (poGotoPageAction->m_dfX1 == 0.0 &&
469 4 : poGotoPageAction->m_dfX2 == 0.0 &&
470 4 : poGotoPageAction->m_dfY1 == 0.0 &&
471 4 : poGotoPageAction->m_dfY2 == 0.0)
472 : {
473 4 : poDest->Add(GDALPDFObjectRW::CreateName("XYZ"))
474 4 : .Add(GDALPDFObjectRW::CreateNull())
475 4 : .Add(GDALPDFObjectRW::CreateNull())
476 4 : .Add(GDALPDFObjectRW::CreateNull());
477 : }
478 : else
479 : {
480 1 : poDest->Add(GDALPDFObjectRW::CreateName("FitR"))
481 1 : .Add(poGotoPageAction->m_dfX1)
482 1 : .Add(poGotoPageAction->m_dfY1)
483 1 : .Add(poGotoPageAction->m_dfX2)
484 1 : .Add(poGotoPageAction->m_dfY2);
485 : }
486 5 : if (poDictForDest && actions.size() == 1)
487 : {
488 4 : poDictForDest->Add("Dest", poDest);
489 : }
490 : else
491 : {
492 1 : poActionDict = new GDALPDFDictionaryRW();
493 : poActionDict->Add("Type",
494 1 : GDALPDFObjectRW::CreateName("Action"));
495 1 : poActionDict->Add("S", GDALPDFObjectRW::CreateName("GoTo"));
496 1 : poActionDict->Add("D", poDest);
497 : }
498 : }
499 :
500 : auto setLayerStateAction =
501 10 : dynamic_cast<SetLayerStateAction *>(poAction.get());
502 10 : if (poActionDict == nullptr && setLayerStateAction)
503 : {
504 4 : poActionDict = new GDALPDFDictionaryRW();
505 4 : poActionDict->Add("Type", GDALPDFObjectRW::CreateName("Action"));
506 4 : poActionDict->Add("S", GDALPDFObjectRW::CreateName("SetOCGState"));
507 4 : auto poStateArray = new GDALPDFArrayRW();
508 4 : if (!setLayerStateAction->m_anOFFLayers.empty())
509 : {
510 2 : poStateArray->Add(GDALPDFObjectRW::CreateName("OFF"));
511 5 : for (const auto &ocg : setLayerStateAction->m_anOFFLayers)
512 3 : poStateArray->Add(ocg, 0);
513 : }
514 4 : if (!setLayerStateAction->m_anONLayers.empty())
515 : {
516 3 : poStateArray->Add(GDALPDFObjectRW::CreateName("ON"));
517 7 : for (const auto &ocg : setLayerStateAction->m_anONLayers)
518 4 : poStateArray->Add(ocg, 0);
519 : }
520 4 : poActionDict->Add("State", poStateArray);
521 : }
522 :
523 : auto javascriptAction =
524 10 : dynamic_cast<JavascriptAction *>(poAction.get());
525 10 : if (poActionDict == nullptr && javascriptAction)
526 : {
527 1 : poActionDict = new GDALPDFDictionaryRW();
528 1 : poActionDict->Add("Type", GDALPDFObjectRW::CreateName("Action"));
529 1 : poActionDict->Add("S", GDALPDFObjectRW::CreateName("JavaScript"));
530 1 : poActionDict->Add("JS", javascriptAction->m_osScript);
531 : }
532 :
533 10 : if (poActionDict)
534 : {
535 6 : if (poLastActionDict == nullptr)
536 : {
537 4 : poRetAction = poActionDict;
538 : }
539 : else
540 : {
541 2 : poLastActionDict->Add("Next", poActionDict);
542 : }
543 6 : poLastActionDict = poActionDict;
544 : }
545 : }
546 9 : return poRetAction;
547 : }
548 :
549 : /************************************************************************/
550 : /* SerializeOutlineKids() */
551 : /************************************************************************/
552 :
553 10 : bool GDALPDFComposerWriter::SerializeOutlineKids(
554 : const OutlineItem *poParentItem)
555 : {
556 19 : for (size_t i = 0; i < poParentItem->m_aoKids.size(); i++)
557 : {
558 9 : const auto &poItem = poParentItem->m_aoKids[i];
559 9 : StartObj(poItem->m_nObjId);
560 9 : GDALPDFDictionaryRW oDict;
561 9 : oDict.Add("Title", poItem->m_osName);
562 :
563 9 : auto poActionDict = SerializeActions(&oDict, poItem->m_aoActions);
564 9 : if (poActionDict)
565 : {
566 4 : oDict.Add("A", poActionDict);
567 : }
568 :
569 9 : if (i > 0)
570 : {
571 6 : oDict.Add("Prev", poParentItem->m_aoKids[i - 1]->m_nObjId, 0);
572 : }
573 9 : if (i + 1 < poParentItem->m_aoKids.size())
574 : {
575 6 : oDict.Add("Next", poParentItem->m_aoKids[i + 1]->m_nObjId, 0);
576 : }
577 9 : if (poItem->m_nFlags)
578 2 : oDict.Add("F", poItem->m_nFlags);
579 9 : oDict.Add("Parent", poParentItem->m_nObjId, 0);
580 9 : if (!poItem->m_aoKids.empty())
581 : {
582 2 : oDict.Add("First", poItem->m_aoKids.front()->m_nObjId, 0);
583 2 : oDict.Add("Last", poItem->m_aoKids.back()->m_nObjId, 0);
584 3 : oDict.Add("Count", poItem->m_bOpen ? poItem->m_nKidsRecCount
585 3 : : -poItem->m_nKidsRecCount);
586 : }
587 9 : int ret = VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
588 9 : EndObj();
589 9 : if (ret == 0)
590 0 : return false;
591 9 : if (!SerializeOutlineKids(poItem.get()))
592 0 : return false;
593 : }
594 10 : return true;
595 : }
596 :
597 : /************************************************************************/
598 : /* CreateOutline() */
599 : /************************************************************************/
600 :
601 5 : bool GDALPDFComposerWriter::CreateOutline(const CPLXMLNode *psNode)
602 : {
603 10 : OutlineItem oRootOutlineItem;
604 5 : if (!CreateOutlineFirstPass(psNode, &oRootOutlineItem))
605 4 : return false;
606 1 : if (oRootOutlineItem.m_aoKids.empty())
607 0 : return true;
608 :
609 1 : m_nOutlinesId = AllocNewObject();
610 1 : StartObj(m_nOutlinesId);
611 2 : GDALPDFDictionaryRW oDict;
612 1 : oDict.Add("Type", GDALPDFObjectRW::CreateName("Outlines"))
613 1 : .Add("First", oRootOutlineItem.m_aoKids.front()->m_nObjId, 0)
614 1 : .Add("Last", oRootOutlineItem.m_aoKids.back()->m_nObjId, 0)
615 1 : .Add("Count", oRootOutlineItem.m_nKidsRecCount);
616 1 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
617 1 : EndObj();
618 1 : oRootOutlineItem.m_nObjId = m_nOutlinesId;
619 1 : return SerializeOutlineKids(&oRootOutlineItem);
620 : }
621 :
622 : /************************************************************************/
623 : /* GenerateGeoreferencing() */
624 : /************************************************************************/
625 :
626 8 : bool GDALPDFComposerWriter::GenerateGeoreferencing(
627 : const CPLXMLNode *psGeoreferencing, double dfWidthInUserUnit,
628 : double dfHeightInUserUnit, GDALPDFObjectNum &nViewportId,
629 : GDALPDFObjectNum &nLGIDictId, Georeferencing &georeferencing)
630 : {
631 8 : double bboxX1 = 0;
632 8 : double bboxY1 = 0;
633 8 : double bboxX2 = dfWidthInUserUnit;
634 8 : double bboxY2 = dfHeightInUserUnit;
635 8 : const auto psBoundingBox = CPLGetXMLNode(psGeoreferencing, "BoundingBox");
636 8 : if (psBoundingBox)
637 : {
638 7 : bboxX1 = CPLAtof(
639 : CPLGetXMLValue(psBoundingBox, "x1", CPLSPrintf("%.17g", bboxX1)));
640 7 : bboxY1 = CPLAtof(
641 : CPLGetXMLValue(psBoundingBox, "y1", CPLSPrintf("%.17g", bboxY1)));
642 7 : bboxX2 = CPLAtof(
643 : CPLGetXMLValue(psBoundingBox, "x2", CPLSPrintf("%.17g", bboxX2)));
644 7 : bboxY2 = CPLAtof(
645 : CPLGetXMLValue(psBoundingBox, "y2", CPLSPrintf("%.17g", bboxY2)));
646 7 : if (bboxX2 <= bboxX1 || bboxY2 <= bboxY1)
647 : {
648 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid BoundingBox");
649 1 : return false;
650 : }
651 : }
652 :
653 14 : std::vector<gdal::GCP> aGCPs;
654 55 : for (const auto *psIter = psGeoreferencing->psChild; psIter;
655 48 : psIter = psIter->psNext)
656 : {
657 49 : if (psIter->eType == CXT_Element &&
658 42 : strcmp(psIter->pszValue, "ControlPoint") == 0)
659 : {
660 27 : const char *pszx = CPLGetXMLValue(psIter, "x", nullptr);
661 27 : const char *pszy = CPLGetXMLValue(psIter, "y", nullptr);
662 27 : const char *pszX = CPLGetXMLValue(psIter, "GeoX", nullptr);
663 27 : const char *pszY = CPLGetXMLValue(psIter, "GeoY", nullptr);
664 27 : if (!pszx || !pszy || !pszX || !pszY)
665 : {
666 1 : CPLError(CE_Failure, CPLE_NotSupported,
667 : "At least one of x, y, GeoX or GeoY attribute "
668 : "missing on ControlPoint");
669 1 : return false;
670 : }
671 26 : aGCPs.emplace_back(nullptr, nullptr, CPLAtof(pszx), CPLAtof(pszy),
672 52 : CPLAtof(pszX), CPLAtof(pszY));
673 : }
674 : }
675 6 : if (aGCPs.size() < 4)
676 : {
677 1 : CPLError(CE_Failure, CPLE_NotSupported,
678 : "At least 4 ControlPoint are required");
679 1 : return false;
680 : }
681 :
682 : const char *pszBoundingPolygon =
683 5 : CPLGetXMLValue(psGeoreferencing, "BoundingPolygon", nullptr);
684 10 : std::vector<xyPair> aBoundingPolygon;
685 5 : if (pszBoundingPolygon)
686 : {
687 2 : OGRGeometry *poGeom = nullptr;
688 2 : OGRGeometryFactory::createFromWkt(pszBoundingPolygon, nullptr, &poGeom);
689 2 : if (poGeom && poGeom->getGeometryType() == wkbPolygon)
690 : {
691 2 : auto poPoly = poGeom->toPolygon();
692 2 : auto poRing = poPoly->getExteriorRing();
693 2 : if (poRing)
694 : {
695 2 : if (psBoundingBox == nullptr)
696 : {
697 0 : OGREnvelope sEnvelope;
698 0 : poRing->getEnvelope(&sEnvelope);
699 0 : bboxX1 = sEnvelope.MinX;
700 0 : bboxY1 = sEnvelope.MinY;
701 0 : bboxX2 = sEnvelope.MaxX;
702 0 : bboxY2 = sEnvelope.MaxY;
703 : }
704 12 : for (int i = 0; i < poRing->getNumPoints(); i++)
705 : {
706 : aBoundingPolygon.emplace_back(
707 10 : xyPair(poRing->getX(i), poRing->getY(i)));
708 : }
709 : }
710 : }
711 2 : delete poGeom;
712 : }
713 :
714 5 : const auto pszSRS = CPLGetXMLValue(psGeoreferencing, "SRS", nullptr);
715 5 : if (!pszSRS)
716 : {
717 1 : CPLError(CE_Failure, CPLE_NotSupported, "Missing SRS");
718 1 : return false;
719 : }
720 8 : auto poSRS = std::make_unique<OGRSpatialReference>();
721 4 : if (poSRS->SetFromUserInput(pszSRS) != OGRERR_NONE)
722 : {
723 0 : return false;
724 : }
725 4 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
726 :
727 4 : if (CPLTestBool(CPLGetXMLValue(psGeoreferencing, "ISO32000ExtensionFormat",
728 : "true")))
729 : {
730 : nViewportId = GenerateISO32000_Georeferencing(
731 : OGRSpatialReference::ToHandle(poSRS.get()), bboxX1, bboxY1, bboxX2,
732 4 : bboxY2, aGCPs, aBoundingPolygon);
733 4 : if (!nViewportId.toBool())
734 : {
735 0 : return false;
736 : }
737 : }
738 :
739 4 : if (CPLTestBool(
740 : CPLGetXMLValue(psGeoreferencing, "OGCBestPracticeFormat", "false")))
741 : {
742 : nLGIDictId = GenerateOGC_BP_Georeferencing(
743 : OGRSpatialReference::ToHandle(poSRS.get()), bboxX1, bboxY1, bboxX2,
744 1 : bboxY2, aGCPs, aBoundingPolygon);
745 1 : if (!nLGIDictId.toBool())
746 : {
747 0 : return false;
748 : }
749 : }
750 :
751 4 : const char *pszId = CPLGetXMLValue(psGeoreferencing, "id", nullptr);
752 4 : if (pszId)
753 : {
754 3 : if (!GDALGCPsToGeoTransform(static_cast<int>(aGCPs.size()),
755 : gdal::GCP::c_ptr(aGCPs),
756 3 : georeferencing.m_adfGT, TRUE))
757 : {
758 0 : CPLError(CE_Failure, CPLE_AppDefined,
759 : "Could not compute geotransform with approximate match.");
760 0 : return false;
761 : }
762 3 : if (std::fabs(georeferencing.m_adfGT[2]) <
763 3 : 1e-5 * std::fabs(georeferencing.m_adfGT[1]) &&
764 3 : std::fabs(georeferencing.m_adfGT[4]) <
765 3 : 1e-5 * std::fabs(georeferencing.m_adfGT[5]))
766 : {
767 3 : georeferencing.m_adfGT[2] = 0;
768 3 : georeferencing.m_adfGT[4] = 0;
769 : }
770 3 : if (georeferencing.m_adfGT[2] != 0 || georeferencing.m_adfGT[4] != 0 ||
771 3 : georeferencing.m_adfGT[5] < 0)
772 : {
773 0 : CPLError(CE_Failure, CPLE_AppDefined,
774 : "Geotransform should define a north-up non rotated area.");
775 0 : return false;
776 : }
777 3 : georeferencing.m_osID = pszId;
778 3 : georeferencing.m_oSRS = *(poSRS.get());
779 3 : georeferencing.m_bboxX1 = bboxX1;
780 3 : georeferencing.m_bboxY1 = bboxY1;
781 3 : georeferencing.m_bboxX2 = bboxX2;
782 3 : georeferencing.m_bboxY2 = bboxY2;
783 : }
784 :
785 4 : return true;
786 : }
787 :
788 : /************************************************************************/
789 : /* GenerateISO32000_Georeferencing() */
790 : /************************************************************************/
791 :
792 4 : GDALPDFObjectNum GDALPDFComposerWriter::GenerateISO32000_Georeferencing(
793 : OGRSpatialReferenceH hSRS, double bboxX1, double bboxY1, double bboxX2,
794 : double bboxY2, const std::vector<gdal::GCP> &aGCPs,
795 : const std::vector<xyPair> &aBoundingPolygon)
796 : {
797 4 : OGRSpatialReferenceH hSRSGeog = OSRCloneGeogCS(hSRS);
798 4 : if (hSRSGeog == nullptr)
799 : {
800 0 : return GDALPDFObjectNum();
801 : }
802 4 : OSRSetAxisMappingStrategy(hSRSGeog, OAMS_TRADITIONAL_GIS_ORDER);
803 : OGRCoordinateTransformationH hCT =
804 4 : OCTNewCoordinateTransformation(hSRS, hSRSGeog);
805 4 : if (hCT == nullptr)
806 : {
807 0 : OSRDestroySpatialReference(hSRSGeog);
808 0 : return GDALPDFObjectNum();
809 : }
810 :
811 8 : std::vector<gdal::GCP> aGCPReprojected;
812 4 : bool bSuccess = true;
813 20 : for (const auto &gcp : aGCPs)
814 : {
815 16 : double X = gcp.X();
816 16 : double Y = gcp.Y();
817 16 : bSuccess &= OCTTransform(hCT, 1, &X, &Y, nullptr) == 1;
818 32 : aGCPReprojected.emplace_back(nullptr, nullptr, gcp.Pixel(), gcp.Line(),
819 16 : X, Y);
820 : }
821 4 : if (!bSuccess)
822 : {
823 0 : OSRDestroySpatialReference(hSRSGeog);
824 0 : OCTDestroyCoordinateTransformation(hCT);
825 :
826 0 : return GDALPDFObjectNum();
827 : }
828 :
829 4 : const char *pszAuthorityCode = OSRGetAuthorityCode(hSRS, nullptr);
830 4 : const char *pszAuthorityName = OSRGetAuthorityName(hSRS, nullptr);
831 4 : int nEPSGCode = 0;
832 4 : if (pszAuthorityName != nullptr && EQUAL(pszAuthorityName, "EPSG") &&
833 : pszAuthorityCode != nullptr)
834 4 : nEPSGCode = atoi(pszAuthorityCode);
835 :
836 4 : int bIsGeographic = OSRIsGeographic(hSRS);
837 :
838 4 : char *pszESRIWKT = nullptr;
839 4 : const char *apszOptions[] = {"FORMAT=WKT1_ESRI", nullptr};
840 4 : OSRExportToWktEx(hSRS, &pszESRIWKT, apszOptions);
841 :
842 4 : OSRDestroySpatialReference(hSRSGeog);
843 4 : OCTDestroyCoordinateTransformation(hCT);
844 :
845 4 : auto nViewportId = AllocNewObject();
846 4 : auto nMeasureId = AllocNewObject();
847 4 : auto nGCSId = AllocNewObject();
848 :
849 4 : StartObj(nViewportId);
850 8 : GDALPDFDictionaryRW oViewPortDict;
851 4 : oViewPortDict.Add("Type", GDALPDFObjectRW::CreateName("Viewport"))
852 4 : .Add("Name", "Layer")
853 4 : .Add("BBox", &((new GDALPDFArrayRW())
854 4 : ->Add(bboxX1)
855 4 : .Add(bboxY1)
856 4 : .Add(bboxX2)
857 4 : .Add(bboxY2)))
858 4 : .Add("Measure", nMeasureId, 0);
859 4 : VSIFPrintfL(m_fp, "%s\n", oViewPortDict.Serialize().c_str());
860 4 : EndObj();
861 :
862 4 : GDALPDFArrayRW *poGPTS = new GDALPDFArrayRW();
863 4 : GDALPDFArrayRW *poLPTS = new GDALPDFArrayRW();
864 :
865 : const int nPrecision =
866 4 : atoi(CPLGetConfigOption("PDF_COORD_DOUBLE_PRECISION", "16"));
867 20 : for (const auto &gcp : aGCPReprojected)
868 : {
869 16 : poGPTS->AddWithPrecision(gcp.Y(), nPrecision)
870 16 : .AddWithPrecision(gcp.X(), nPrecision); // Lat, long order
871 : poLPTS
872 16 : ->AddWithPrecision((gcp.Pixel() - bboxX1) / (bboxX2 - bboxX1),
873 16 : nPrecision)
874 16 : .AddWithPrecision((gcp.Line() - bboxY1) / (bboxY2 - bboxY1),
875 16 : nPrecision);
876 : }
877 :
878 4 : StartObj(nMeasureId);
879 8 : GDALPDFDictionaryRW oMeasureDict;
880 4 : oMeasureDict.Add("Type", GDALPDFObjectRW::CreateName("Measure"))
881 4 : .Add("Subtype", GDALPDFObjectRW::CreateName("GEO"))
882 4 : .Add("GPTS", poGPTS)
883 4 : .Add("LPTS", poLPTS)
884 4 : .Add("GCS", nGCSId, 0);
885 4 : if (!aBoundingPolygon.empty())
886 : {
887 1 : GDALPDFArrayRW *poBounds = new GDALPDFArrayRW();
888 6 : for (const auto &xy : aBoundingPolygon)
889 : {
890 5 : poBounds->Add((xy.x - bboxX1) / (bboxX2 - bboxX1))
891 5 : .Add((xy.y - bboxY1) / (bboxY2 - bboxY1));
892 : }
893 1 : oMeasureDict.Add("Bounds", poBounds);
894 : }
895 4 : VSIFPrintfL(m_fp, "%s\n", oMeasureDict.Serialize().c_str());
896 4 : EndObj();
897 :
898 4 : StartObj(nGCSId);
899 4 : GDALPDFDictionaryRW oGCSDict;
900 : oGCSDict
901 : .Add("Type",
902 4 : GDALPDFObjectRW::CreateName(bIsGeographic ? "GEOGCS" : "PROJCS"))
903 4 : .Add("WKT", pszESRIWKT);
904 4 : if (nEPSGCode)
905 4 : oGCSDict.Add("EPSG", nEPSGCode);
906 4 : VSIFPrintfL(m_fp, "%s\n", oGCSDict.Serialize().c_str());
907 4 : EndObj();
908 :
909 4 : CPLFree(pszESRIWKT);
910 :
911 4 : return nViewportId;
912 : }
913 :
914 : /************************************************************************/
915 : /* GenerateOGC_BP_Georeferencing() */
916 : /************************************************************************/
917 :
918 1 : GDALPDFObjectNum GDALPDFComposerWriter::GenerateOGC_BP_Georeferencing(
919 : OGRSpatialReferenceH hSRS, double bboxX1, double bboxY1, double bboxX2,
920 : double bboxY2, const std::vector<gdal::GCP> &aGCPs,
921 : const std::vector<xyPair> &aBoundingPolygon)
922 : {
923 1 : const OGRSpatialReference *poSRS = OGRSpatialReference::FromHandle(hSRS);
924 : GDALPDFDictionaryRW *poProjectionDict =
925 1 : GDALPDFBuildOGC_BP_Projection(poSRS);
926 1 : if (poProjectionDict == nullptr)
927 : {
928 0 : OSRDestroySpatialReference(hSRS);
929 0 : return GDALPDFObjectNum();
930 : }
931 :
932 1 : GDALPDFArrayRW *poNeatLineArray = new GDALPDFArrayRW();
933 1 : if (!aBoundingPolygon.empty())
934 : {
935 6 : for (const auto &xy : aBoundingPolygon)
936 : {
937 5 : poNeatLineArray->Add(xy.x).Add(xy.y);
938 : }
939 : }
940 : else
941 : {
942 0 : poNeatLineArray->Add(bboxX1).Add(bboxY1).Add(bboxX2).Add(bboxY2);
943 : }
944 :
945 1 : GDALPDFArrayRW *poRegistration = new GDALPDFArrayRW();
946 :
947 5 : for (const auto &gcp : aGCPs)
948 : {
949 4 : GDALPDFArrayRW *poGCP = new GDALPDFArrayRW();
950 4 : poGCP->Add(gcp.Pixel(), TRUE)
951 4 : .Add(gcp.Line(), TRUE)
952 4 : .Add(gcp.X(), TRUE)
953 4 : .Add(gcp.Y(), TRUE);
954 4 : poRegistration->Add(poGCP);
955 : }
956 :
957 1 : auto nLGIDictId = AllocNewObject();
958 1 : StartObj(nLGIDictId);
959 1 : GDALPDFDictionaryRW oLGIDict;
960 1 : oLGIDict.Add("Type", GDALPDFObjectRW::CreateName("LGIDict"))
961 1 : .Add("Version", "2.1")
962 1 : .Add("Neatline", poNeatLineArray);
963 :
964 1 : oLGIDict.Add("Registration", poRegistration);
965 :
966 : /* GDAL extension */
967 1 : if (CPLTestBool(CPLGetConfigOption("GDAL_PDF_OGC_BP_WRITE_WKT", "TRUE")))
968 : {
969 1 : char *pszWKT = nullptr;
970 1 : OSRExportToWkt(hSRS, &pszWKT);
971 1 : if (pszWKT)
972 1 : poProjectionDict->Add("WKT", pszWKT);
973 1 : CPLFree(pszWKT);
974 : }
975 :
976 1 : oLGIDict.Add("Projection", poProjectionDict);
977 :
978 1 : VSIFPrintfL(m_fp, "%s\n", oLGIDict.Serialize().c_str());
979 1 : EndObj();
980 :
981 1 : return nLGIDictId;
982 : }
983 :
984 : /************************************************************************/
985 : /* GeneratePage() */
986 : /************************************************************************/
987 :
988 37 : bool GDALPDFComposerWriter::GeneratePage(const CPLXMLNode *psPage)
989 : {
990 37 : double dfWidthInUserUnit = CPLAtof(CPLGetXMLValue(psPage, "Width", "-1"));
991 37 : double dfHeightInUserUnit = CPLAtof(CPLGetXMLValue(psPage, "Height", "-1"));
992 37 : if (dfWidthInUserUnit <= 0 || dfWidthInUserUnit >= MAXIMUM_SIZE_IN_UNITS ||
993 36 : dfHeightInUserUnit <= 0 || dfHeightInUserUnit >= MAXIMUM_SIZE_IN_UNITS)
994 : {
995 1 : CPLError(CE_Failure, CPLE_AppDefined,
996 : "Missing or invalid Width and/or Height");
997 1 : return false;
998 : }
999 : double dfUserUnit =
1000 36 : CPLAtof(CPLGetXMLValue(psPage, "DPI", CPLSPrintf("%f", DEFAULT_DPI))) *
1001 36 : USER_UNIT_IN_INCH;
1002 :
1003 72 : std::vector<GDALPDFObjectNum> anViewportIds;
1004 72 : std::vector<GDALPDFObjectNum> anLGIDictIds;
1005 :
1006 72 : PageContext oPageContext;
1007 149 : for (const auto *psIter = psPage->psChild; psIter; psIter = psIter->psNext)
1008 : {
1009 117 : if (psIter->eType == CXT_Element &&
1010 109 : strcmp(psIter->pszValue, "Georeferencing") == 0)
1011 : {
1012 8 : GDALPDFObjectNum nViewportId;
1013 8 : GDALPDFObjectNum nLGIDictId;
1014 8 : Georeferencing georeferencing;
1015 8 : if (!GenerateGeoreferencing(psIter, dfWidthInUserUnit,
1016 : dfHeightInUserUnit, nViewportId,
1017 : nLGIDictId, georeferencing))
1018 : {
1019 4 : return false;
1020 : }
1021 4 : if (nViewportId.toBool())
1022 4 : anViewportIds.emplace_back(nViewportId);
1023 4 : if (nLGIDictId.toBool())
1024 1 : anLGIDictIds.emplace_back(nLGIDictId);
1025 4 : if (!georeferencing.m_osID.empty())
1026 : {
1027 3 : oPageContext.m_oMapGeoreferencedId[georeferencing.m_osID] =
1028 3 : georeferencing;
1029 : }
1030 : }
1031 : }
1032 :
1033 32 : auto nPageId = AllocNewObject();
1034 32 : m_asPageId.push_back(nPageId);
1035 :
1036 32 : const char *pszId = CPLGetXMLValue(psPage, "id", nullptr);
1037 32 : if (pszId)
1038 : {
1039 8 : if (m_oMapPageIdToObjectNum.find(pszId) !=
1040 16 : m_oMapPageIdToObjectNum.end())
1041 : {
1042 1 : CPLError(CE_Failure, CPLE_AppDefined, "Duplicated page id %s",
1043 : pszId);
1044 1 : return false;
1045 : }
1046 7 : m_oMapPageIdToObjectNum[pszId] = nPageId;
1047 : }
1048 :
1049 31 : const auto psContent = CPLGetXMLNode(psPage, "Content");
1050 31 : if (!psContent)
1051 : {
1052 1 : CPLError(CE_Failure, CPLE_AppDefined, "Missing Content");
1053 1 : return false;
1054 : }
1055 :
1056 30 : const bool bDeflateStreamCompression = EQUAL(
1057 : CPLGetXMLValue(psContent, "streamCompression", "DEFLATE"), "DEFLATE");
1058 :
1059 30 : oPageContext.m_dfWidthInUserUnit = dfWidthInUserUnit;
1060 30 : oPageContext.m_dfHeightInUserUnit = dfHeightInUserUnit;
1061 30 : oPageContext.m_eStreamCompressMethod =
1062 30 : bDeflateStreamCompression ? COMPRESS_DEFLATE : COMPRESS_NONE;
1063 30 : if (!ExploreContent(psContent, oPageContext))
1064 13 : return false;
1065 :
1066 17 : int nStructParentsIdx = -1;
1067 17 : if (!oPageContext.m_anFeatureUserProperties.empty())
1068 : {
1069 3 : nStructParentsIdx = static_cast<int>(m_anParentElements.size());
1070 3 : auto nParentsElements = AllocNewObject();
1071 3 : m_anParentElements.push_back(nParentsElements);
1072 : {
1073 3 : StartObj(nParentsElements);
1074 3 : VSIFPrintfL(m_fp, "[ ");
1075 11 : for (const auto &num : oPageContext.m_anFeatureUserProperties)
1076 8 : VSIFPrintfL(m_fp, "%d 0 R ", num.toInt());
1077 3 : VSIFPrintfL(m_fp, " ]\n");
1078 3 : EndObj();
1079 : }
1080 : }
1081 :
1082 17 : GDALPDFObjectNum nAnnotsId;
1083 17 : if (!oPageContext.m_anAnnotationsId.empty())
1084 : {
1085 : /* -------------------------------------------------------------- */
1086 : /* Write annotation arrays. */
1087 : /* -------------------------------------------------------------- */
1088 1 : nAnnotsId = AllocNewObject();
1089 1 : StartObj(nAnnotsId);
1090 : {
1091 1 : GDALPDFArrayRW oArray;
1092 2 : for (size_t i = 0; i < oPageContext.m_anAnnotationsId.size(); i++)
1093 : {
1094 1 : oArray.Add(oPageContext.m_anAnnotationsId[i], 0);
1095 : }
1096 1 : VSIFPrintfL(m_fp, "%s\n", oArray.Serialize().c_str());
1097 : }
1098 1 : EndObj();
1099 : }
1100 :
1101 17 : auto nContentId = AllocNewObject();
1102 17 : auto nResourcesId = AllocNewObject();
1103 :
1104 17 : StartObj(nPageId);
1105 17 : GDALPDFDictionaryRW oDictPage;
1106 17 : oDictPage.Add("Type", GDALPDFObjectRW::CreateName("Page"))
1107 17 : .Add("Parent", m_nPageResourceId, 0)
1108 17 : .Add("MediaBox", &((new GDALPDFArrayRW())
1109 17 : ->Add(0)
1110 17 : .Add(0)
1111 17 : .Add(dfWidthInUserUnit)
1112 17 : .Add(dfHeightInUserUnit)))
1113 17 : .Add("UserUnit", dfUserUnit)
1114 17 : .Add("Contents", nContentId, 0)
1115 17 : .Add("Resources", nResourcesId, 0);
1116 :
1117 17 : if (nAnnotsId.toBool())
1118 1 : oDictPage.Add("Annots", nAnnotsId, 0);
1119 :
1120 : oDictPage.Add("Group",
1121 17 : &((new GDALPDFDictionaryRW())
1122 17 : ->Add("Type", GDALPDFObjectRW::CreateName("Group"))
1123 17 : .Add("S", GDALPDFObjectRW::CreateName("Transparency"))
1124 17 : .Add("CS", GDALPDFObjectRW::CreateName("DeviceRGB"))));
1125 17 : if (!anViewportIds.empty())
1126 : {
1127 4 : auto poViewports = new GDALPDFArrayRW();
1128 8 : for (const auto &id : anViewportIds)
1129 4 : poViewports->Add(id, 0);
1130 4 : oDictPage.Add("VP", poViewports);
1131 : }
1132 :
1133 17 : if (anLGIDictIds.size() == 1)
1134 : {
1135 1 : oDictPage.Add("LGIDict", anLGIDictIds[0], 0);
1136 : }
1137 16 : else if (!anLGIDictIds.empty())
1138 : {
1139 0 : auto poLGIDict = new GDALPDFArrayRW();
1140 0 : for (const auto &id : anLGIDictIds)
1141 0 : poLGIDict->Add(id, 0);
1142 0 : oDictPage.Add("LGIDict", poLGIDict);
1143 : }
1144 :
1145 17 : if (nStructParentsIdx >= 0)
1146 : {
1147 3 : oDictPage.Add("StructParents", nStructParentsIdx);
1148 : }
1149 :
1150 17 : VSIFPrintfL(m_fp, "%s\n", oDictPage.Serialize().c_str());
1151 17 : EndObj();
1152 :
1153 : /* -------------------------------------------------------------- */
1154 : /* Write content dictionary */
1155 : /* -------------------------------------------------------------- */
1156 : {
1157 34 : GDALPDFDictionaryRW oDict;
1158 17 : StartObjWithStream(nContentId, oDict, bDeflateStreamCompression);
1159 17 : VSIFPrintfL(m_fp, "%s", oPageContext.m_osDrawingStream.c_str());
1160 17 : EndObjWithStream();
1161 : }
1162 :
1163 : /* -------------------------------------------------------------- */
1164 : /* Write page resource dictionary. */
1165 : /* -------------------------------------------------------------- */
1166 17 : StartObj(nResourcesId);
1167 : {
1168 17 : GDALPDFDictionaryRW oDict;
1169 17 : if (!oPageContext.m_oXObjects.empty())
1170 : {
1171 8 : GDALPDFDictionaryRW *poDict = new GDALPDFDictionaryRW();
1172 18 : for (const auto &kv : oPageContext.m_oXObjects)
1173 : {
1174 10 : poDict->Add(kv.first, kv.second, 0);
1175 : }
1176 8 : oDict.Add("XObject", poDict);
1177 : }
1178 :
1179 17 : if (!oPageContext.m_oProperties.empty())
1180 : {
1181 3 : GDALPDFDictionaryRW *poDict = new GDALPDFDictionaryRW();
1182 6 : for (const auto &kv : oPageContext.m_oProperties)
1183 : {
1184 3 : poDict->Add(kv.first, kv.second, 0);
1185 : }
1186 3 : oDict.Add("Properties", poDict);
1187 : }
1188 :
1189 17 : if (!oPageContext.m_oExtGState.empty())
1190 : {
1191 5 : GDALPDFDictionaryRW *poDict = new GDALPDFDictionaryRW();
1192 13 : for (const auto &kv : oPageContext.m_oExtGState)
1193 : {
1194 8 : poDict->Add(kv.first, kv.second, 0);
1195 : }
1196 5 : oDict.Add("ExtGState", poDict);
1197 : }
1198 :
1199 17 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
1200 : }
1201 17 : EndObj();
1202 :
1203 17 : return true;
1204 : }
1205 :
1206 : /************************************************************************/
1207 : /* ExploreContent() */
1208 : /************************************************************************/
1209 :
1210 33 : bool GDALPDFComposerWriter::ExploreContent(const CPLXMLNode *psNode,
1211 : PageContext &oPageContext)
1212 : {
1213 70 : for (const auto *psIter = psNode->psChild; psIter; psIter = psIter->psNext)
1214 : {
1215 50 : if (psIter->eType == CXT_Element &&
1216 30 : strcmp(psIter->pszValue, "IfLayerOn") == 0)
1217 : {
1218 4 : const char *pszLayerId = CPLGetXMLValue(psIter, "layerId", nullptr);
1219 4 : if (!pszLayerId)
1220 : {
1221 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing layerId");
1222 1 : return false;
1223 : }
1224 4 : auto oIter = m_oMapLayerIdToOCG.find(pszLayerId);
1225 4 : if (oIter == m_oMapLayerIdToOCG.end())
1226 : {
1227 1 : CPLError(CE_Failure, CPLE_AppDefined,
1228 : "Referencing layer of unknown id: %s", pszLayerId);
1229 1 : return false;
1230 : }
1231 : oPageContext
1232 3 : .m_oProperties[CPLOPrintf("Lyr%d", oIter->second.toInt())] =
1233 3 : oIter->second;
1234 : oPageContext.m_osDrawingStream +=
1235 3 : CPLOPrintf("/OC /Lyr%d BDC\n", oIter->second.toInt());
1236 3 : if (!ExploreContent(psIter, oPageContext))
1237 0 : return false;
1238 3 : oPageContext.m_osDrawingStream += "EMC\n";
1239 : }
1240 :
1241 46 : else if (psIter->eType == CXT_Element &&
1242 26 : strcmp(psIter->pszValue, "Raster") == 0)
1243 : {
1244 6 : if (!WriteRaster(psIter, oPageContext))
1245 2 : return false;
1246 : }
1247 :
1248 40 : else if (psIter->eType == CXT_Element &&
1249 20 : strcmp(psIter->pszValue, "Vector") == 0)
1250 : {
1251 5 : if (!WriteVector(psIter, oPageContext))
1252 0 : return false;
1253 : }
1254 :
1255 35 : else if (psIter->eType == CXT_Element &&
1256 15 : strcmp(psIter->pszValue, "VectorLabel") == 0)
1257 : {
1258 3 : if (!WriteVectorLabel(psIter, oPageContext))
1259 0 : return false;
1260 : }
1261 :
1262 32 : else if (psIter->eType == CXT_Element &&
1263 12 : strcmp(psIter->pszValue, "PDF") == 0)
1264 : {
1265 : #ifdef HAVE_PDF_READ_SUPPORT
1266 12 : if (!WritePDF(psIter, oPageContext))
1267 10 : return false;
1268 : #else
1269 : CPLError(CE_Failure, CPLE_NotSupported,
1270 : "PDF node not supported due to missing PDF read support "
1271 : "in this GDAL build");
1272 : return false;
1273 : #endif
1274 : }
1275 : }
1276 20 : return true;
1277 : }
1278 :
1279 : /************************************************************************/
1280 : /* StartBlending() */
1281 : /************************************************************************/
1282 :
1283 12 : void GDALPDFComposerWriter::StartBlending(const CPLXMLNode *psNode,
1284 : PageContext &oPageContext,
1285 : double &dfOpacity)
1286 : {
1287 12 : dfOpacity = 1;
1288 12 : const auto psBlending = CPLGetXMLNode(psNode, "Blending");
1289 12 : if (psBlending)
1290 : {
1291 5 : auto nExtGState = AllocNewObject();
1292 5 : StartObj(nExtGState);
1293 : {
1294 5 : GDALPDFDictionaryRW gs;
1295 5 : gs.Add("Type", GDALPDFObjectRW::CreateName("ExtGState"));
1296 5 : dfOpacity = CPLAtof(CPLGetXMLValue(psBlending, "opacity", "1"));
1297 5 : gs.Add("ca", dfOpacity);
1298 5 : gs.Add("BM", GDALPDFObjectRW::CreateName(
1299 5 : CPLGetXMLValue(psBlending, "function", "Normal")));
1300 5 : VSIFPrintfL(m_fp, "%s\n", gs.Serialize().c_str());
1301 : }
1302 5 : EndObj();
1303 5 : oPageContext.m_oExtGState[CPLOPrintf("GS%d", nExtGState.toInt())] =
1304 : nExtGState;
1305 5 : oPageContext.m_osDrawingStream += "q\n";
1306 : oPageContext.m_osDrawingStream +=
1307 5 : CPLOPrintf("/GS%d gs\n", nExtGState.toInt());
1308 : }
1309 12 : }
1310 :
1311 : /************************************************************************/
1312 : /* EndBlending() */
1313 : /************************************************************************/
1314 :
1315 12 : void GDALPDFComposerWriter::EndBlending(const CPLXMLNode *psNode,
1316 : PageContext &oPageContext)
1317 : {
1318 12 : const auto psBlending = CPLGetXMLNode(psNode, "Blending");
1319 12 : if (psBlending)
1320 : {
1321 5 : oPageContext.m_osDrawingStream += "Q\n";
1322 : }
1323 12 : }
1324 :
1325 : /************************************************************************/
1326 : /* WriteRaster() */
1327 : /************************************************************************/
1328 :
1329 6 : bool GDALPDFComposerWriter::WriteRaster(const CPLXMLNode *psNode,
1330 : PageContext &oPageContext)
1331 : {
1332 6 : const char *pszDataset = CPLGetXMLValue(psNode, "dataset", nullptr);
1333 6 : if (!pszDataset)
1334 : {
1335 1 : CPLError(CE_Failure, CPLE_AppDefined, "Missing dataset");
1336 1 : return false;
1337 : }
1338 5 : double dfX1 = CPLAtof(CPLGetXMLValue(psNode, "x1", "0"));
1339 5 : double dfY1 = CPLAtof(CPLGetXMLValue(psNode, "y1", "0"));
1340 5 : double dfX2 = CPLAtof(CPLGetXMLValue(
1341 : psNode, "x2", CPLSPrintf("%.17g", oPageContext.m_dfWidthInUserUnit)));
1342 5 : double dfY2 = CPLAtof(CPLGetXMLValue(
1343 : psNode, "y2", CPLSPrintf("%.17g", oPageContext.m_dfHeightInUserUnit)));
1344 5 : if (dfX2 <= dfX1 || dfY2 <= dfY1)
1345 : {
1346 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid x1,y1,x2,y2");
1347 0 : return false;
1348 : }
1349 : GDALDatasetUniquePtr poDS(
1350 : GDALDataset::Open(pszDataset, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
1351 10 : nullptr, nullptr, nullptr));
1352 5 : if (!poDS)
1353 1 : return false;
1354 4 : const int nWidth = poDS->GetRasterXSize();
1355 4 : const int nHeight = poDS->GetRasterYSize();
1356 : const int nBlockXSize =
1357 4 : std::max(16, atoi(CPLGetXMLValue(psNode, "tileSize", "256")));
1358 4 : const int nBlockYSize = nBlockXSize;
1359 : const char *pszCompressMethod =
1360 4 : CPLGetXMLValue(psNode, "Compression.method", "DEFLATE");
1361 4 : PDFCompressMethod eCompressMethod = COMPRESS_DEFLATE;
1362 4 : if (EQUAL(pszCompressMethod, "JPEG"))
1363 0 : eCompressMethod = COMPRESS_JPEG;
1364 4 : else if (EQUAL(pszCompressMethod, "JPEG2000"))
1365 0 : eCompressMethod = COMPRESS_JPEG2000;
1366 : const int nPredictor =
1367 4 : CPLTestBool(CPLGetXMLValue(psNode, "Compression.predictor", "false"))
1368 4 : ? 2
1369 4 : : 0;
1370 : const int nJPEGQuality =
1371 4 : atoi(CPLGetXMLValue(psNode, "Compression.quality", "-1"));
1372 : const char *pszJPEG2000_DRIVER =
1373 4 : m_osJPEG2000Driver.empty() ? nullptr : m_osJPEG2000Driver.c_str();
1374 : ;
1375 :
1376 : const char *pszGeoreferencingId =
1377 4 : CPLGetXMLValue(psNode, "georeferencingId", nullptr);
1378 4 : double dfClippingMinX = 0;
1379 4 : double dfClippingMinY = 0;
1380 4 : double dfClippingMaxX = 0;
1381 4 : double dfClippingMaxY = 0;
1382 4 : bool bClip = false;
1383 4 : double adfRasterGT[6] = {0, 1, 0, 0, 0, 1};
1384 : double adfInvGeoreferencingGT[6]; // from georeferenced to PDF coordinates
1385 4 : if (pszGeoreferencingId)
1386 : {
1387 : auto iter =
1388 1 : oPageContext.m_oMapGeoreferencedId.find(pszGeoreferencingId);
1389 1 : if (iter == oPageContext.m_oMapGeoreferencedId.end())
1390 : {
1391 0 : CPLError(CE_Failure, CPLE_AppDefined,
1392 : "Cannot find georeferencing of id %s",
1393 : pszGeoreferencingId);
1394 0 : return false;
1395 : }
1396 1 : const auto &georeferencing = iter->second;
1397 1 : dfX1 = georeferencing.m_bboxX1;
1398 1 : dfY1 = georeferencing.m_bboxY1;
1399 1 : dfX2 = georeferencing.m_bboxX2;
1400 1 : dfY2 = georeferencing.m_bboxY2;
1401 :
1402 1 : bClip = true;
1403 1 : dfClippingMinX = APPLY_GT_X(georeferencing.m_adfGT, dfX1, dfY1);
1404 1 : dfClippingMinY = APPLY_GT_Y(georeferencing.m_adfGT, dfX1, dfY1);
1405 1 : dfClippingMaxX = APPLY_GT_X(georeferencing.m_adfGT, dfX2, dfY2);
1406 1 : dfClippingMaxY = APPLY_GT_Y(georeferencing.m_adfGT, dfX2, dfY2);
1407 :
1408 1 : if (poDS->GetGeoTransform(adfRasterGT) != CE_None ||
1409 1 : adfRasterGT[2] != 0 || adfRasterGT[4] != 0 || adfRasterGT[5] > 0)
1410 : {
1411 0 : CPLError(CE_Failure, CPLE_AppDefined,
1412 : "Raster has no geotransform or a rotated geotransform");
1413 0 : return false;
1414 : }
1415 :
1416 1 : auto poSRS = poDS->GetSpatialRef();
1417 1 : if (!poSRS || !poSRS->IsSame(&georeferencing.m_oSRS))
1418 : {
1419 0 : CPLError(CE_Failure, CPLE_AppDefined,
1420 : "Raster has no projection, or different from the one "
1421 : "of the georeferencing area");
1422 0 : return false;
1423 : }
1424 :
1425 1 : CPL_IGNORE_RET_VAL(GDALInvGeoTransform(georeferencing.m_adfGT,
1426 : adfInvGeoreferencingGT));
1427 : }
1428 4 : const double dfRasterMinX = adfRasterGT[0];
1429 4 : const double dfRasterMaxY = adfRasterGT[3];
1430 :
1431 : /* Does the source image has a color table ? */
1432 4 : const auto nColorTableId = WriteColorTable(poDS.get());
1433 :
1434 : double dfIgnoredOpacity;
1435 4 : StartBlending(psNode, oPageContext, dfIgnoredOpacity);
1436 :
1437 8 : CPLString osGroupStream;
1438 8 : std::vector<GDALPDFObjectNum> anImageIds;
1439 :
1440 4 : const int nXBlocks = (nWidth + nBlockXSize - 1) / nBlockXSize;
1441 4 : const int nYBlocks = (nHeight + nBlockYSize - 1) / nBlockYSize;
1442 : int nBlockXOff, nBlockYOff;
1443 10 : for (nBlockYOff = 0; nBlockYOff < nYBlocks; nBlockYOff++)
1444 : {
1445 16 : for (nBlockXOff = 0; nBlockXOff < nXBlocks; nBlockXOff++)
1446 : {
1447 : int nReqWidth =
1448 10 : std::min(nBlockXSize, nWidth - nBlockXOff * nBlockXSize);
1449 : int nReqHeight =
1450 10 : std::min(nBlockYSize, nHeight - nBlockYOff * nBlockYSize);
1451 :
1452 10 : int nX = nBlockXOff * nBlockXSize;
1453 10 : int nY = nBlockYOff * nBlockYSize;
1454 :
1455 10 : double dfXPDFOff = nX * (dfX2 - dfX1) / nWidth + dfX1;
1456 10 : double dfYPDFOff =
1457 10 : (nHeight - nY - nReqHeight) * (dfY2 - dfY1) / nHeight + dfY1;
1458 10 : double dfXPDFSize = nReqWidth * (dfX2 - dfX1) / nWidth;
1459 10 : double dfYPDFSize = nReqHeight * (dfY2 - dfY1) / nHeight;
1460 :
1461 10 : if (bClip)
1462 : {
1463 : /* Compute extent of block to write */
1464 1 : double dfBlockMinX = adfRasterGT[0] + nX * adfRasterGT[1];
1465 1 : double dfBlockMaxX =
1466 1 : adfRasterGT[0] + (nX + nReqWidth) * adfRasterGT[1];
1467 1 : double dfBlockMinY =
1468 1 : adfRasterGT[3] + (nY + nReqHeight) * adfRasterGT[5];
1469 1 : double dfBlockMaxY = adfRasterGT[3] + nY * adfRasterGT[5];
1470 :
1471 : // Clip the extent of the block with the extent of the main
1472 : // raster.
1473 : const double dfIntersectMinX =
1474 1 : std::max(dfBlockMinX, dfClippingMinX);
1475 : const double dfIntersectMinY =
1476 1 : std::max(dfBlockMinY, dfClippingMinY);
1477 : const double dfIntersectMaxX =
1478 1 : std::min(dfBlockMaxX, dfClippingMaxX);
1479 : const double dfIntersectMaxY =
1480 1 : std::min(dfBlockMaxY, dfClippingMaxY);
1481 :
1482 1 : bool bOK = false;
1483 1 : if (dfIntersectMinX < dfIntersectMaxX &&
1484 : dfIntersectMinY < dfIntersectMaxY)
1485 : {
1486 : /* Re-compute (x,y,width,height) subwindow of current raster
1487 : * from */
1488 : /* the extent of the clipped block */
1489 1 : nX = static_cast<int>((dfIntersectMinX - dfRasterMinX) /
1490 1 : adfRasterGT[1] +
1491 : 0.5);
1492 1 : nY = static_cast<int>((dfRasterMaxY - dfIntersectMaxY) /
1493 1 : (-adfRasterGT[5]) +
1494 : 0.5);
1495 1 : nReqWidth =
1496 1 : static_cast<int>((dfIntersectMaxX - dfRasterMinX) /
1497 1 : adfRasterGT[1] +
1498 : 0.5) -
1499 : nX;
1500 1 : nReqHeight =
1501 1 : static_cast<int>((dfRasterMaxY - dfIntersectMinY) /
1502 1 : (-adfRasterGT[5]) +
1503 : 0.5) -
1504 : nY;
1505 :
1506 1 : if (nReqWidth > 0 && nReqHeight > 0)
1507 : {
1508 1 : dfBlockMinX = adfRasterGT[0] + nX * adfRasterGT[1];
1509 1 : dfBlockMaxX =
1510 1 : adfRasterGT[0] + (nX + nReqWidth) * adfRasterGT[1];
1511 1 : dfBlockMinY =
1512 1 : adfRasterGT[3] + (nY + nReqHeight) * adfRasterGT[5];
1513 1 : dfBlockMaxY = adfRasterGT[3] + nY * adfRasterGT[5];
1514 :
1515 1 : double dfPDFX1 = APPLY_GT_X(adfInvGeoreferencingGT,
1516 : dfBlockMinX, dfBlockMinY);
1517 1 : double dfPDFY1 = APPLY_GT_Y(adfInvGeoreferencingGT,
1518 : dfBlockMinX, dfBlockMinY);
1519 1 : double dfPDFX2 = APPLY_GT_X(adfInvGeoreferencingGT,
1520 : dfBlockMaxX, dfBlockMaxY);
1521 1 : double dfPDFY2 = APPLY_GT_Y(adfInvGeoreferencingGT,
1522 : dfBlockMaxX, dfBlockMaxY);
1523 :
1524 1 : dfXPDFOff = dfPDFX1;
1525 1 : dfYPDFOff = dfPDFY1;
1526 1 : dfXPDFSize = dfPDFX2 - dfPDFX1;
1527 1 : dfYPDFSize = dfPDFY2 - dfPDFY1;
1528 1 : bOK = true;
1529 : }
1530 : }
1531 1 : if (!bOK)
1532 : {
1533 0 : continue;
1534 : }
1535 : }
1536 :
1537 : const auto nImageId =
1538 : WriteBlock(poDS.get(), nX, nY, nReqWidth, nReqHeight,
1539 : nColorTableId, eCompressMethod, nPredictor,
1540 10 : nJPEGQuality, pszJPEG2000_DRIVER, nullptr, nullptr);
1541 :
1542 10 : if (!nImageId.toBool())
1543 0 : return false;
1544 :
1545 10 : anImageIds.push_back(nImageId);
1546 10 : osGroupStream += "q\n";
1547 10 : GDALPDFObjectRW *poXSize = GDALPDFObjectRW::CreateReal(dfXPDFSize);
1548 10 : GDALPDFObjectRW *poYSize = GDALPDFObjectRW::CreateReal(dfYPDFSize);
1549 10 : GDALPDFObjectRW *poXOff = GDALPDFObjectRW::CreateReal(dfXPDFOff);
1550 10 : GDALPDFObjectRW *poYOff = GDALPDFObjectRW::CreateReal(dfYPDFOff);
1551 50 : osGroupStream += CPLOPrintf(
1552 20 : "%s 0 0 %s %s %s cm\n", poXSize->Serialize().c_str(),
1553 30 : poYSize->Serialize().c_str(), poXOff->Serialize().c_str(),
1554 30 : poYOff->Serialize().c_str());
1555 10 : delete poXSize;
1556 10 : delete poYSize;
1557 10 : delete poXOff;
1558 10 : delete poYOff;
1559 10 : osGroupStream += CPLOPrintf("/Image%d Do\n", nImageId.toInt());
1560 10 : osGroupStream += "Q\n";
1561 : }
1562 : }
1563 :
1564 4 : if (anImageIds.size() <= 1 || CPLGetXMLNode(psNode, "Blending") == nullptr)
1565 : {
1566 4 : for (const auto &nImageId : anImageIds)
1567 : {
1568 2 : oPageContext.m_oXObjects[CPLOPrintf("Image%d", nImageId.toInt())] =
1569 : nImageId;
1570 : }
1571 2 : oPageContext.m_osDrawingStream += osGroupStream;
1572 : }
1573 : else
1574 : {
1575 : // In case several tiles are drawn with blending, use a transparency
1576 : // group to avoid edge effects.
1577 :
1578 2 : auto nGroupId = AllocNewObject();
1579 2 : GDALPDFDictionaryRW oDictGroup;
1580 2 : GDALPDFDictionaryRW *poGroup = new GDALPDFDictionaryRW();
1581 2 : poGroup->Add("Type", GDALPDFObjectRW::CreateName("Group"))
1582 2 : .Add("S", GDALPDFObjectRW::CreateName("Transparency"));
1583 :
1584 2 : GDALPDFDictionaryRW *poXObjects = new GDALPDFDictionaryRW();
1585 10 : for (const auto &nImageId : anImageIds)
1586 : {
1587 16 : poXObjects->Add(CPLOPrintf("Image%d", nImageId.toInt()), nImageId,
1588 8 : 0);
1589 : }
1590 2 : GDALPDFDictionaryRW *poResources = new GDALPDFDictionaryRW();
1591 2 : poResources->Add("XObject", poXObjects);
1592 :
1593 2 : oDictGroup.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
1594 2 : .Add("BBox", &((new GDALPDFArrayRW())->Add(0).Add(0))
1595 2 : .Add(oPageContext.m_dfWidthInUserUnit)
1596 2 : .Add(oPageContext.m_dfHeightInUserUnit))
1597 2 : .Add("Subtype", GDALPDFObjectRW::CreateName("Form"))
1598 2 : .Add("Group", poGroup)
1599 2 : .Add("Resources", poResources);
1600 :
1601 2 : StartObjWithStream(nGroupId, oDictGroup,
1602 2 : oPageContext.m_eStreamCompressMethod !=
1603 : COMPRESS_NONE);
1604 2 : VSIFPrintfL(m_fp, "%s", osGroupStream.c_str());
1605 2 : EndObjWithStream();
1606 :
1607 2 : oPageContext.m_oXObjects[CPLOPrintf("Group%d", nGroupId.toInt())] =
1608 : nGroupId;
1609 : oPageContext.m_osDrawingStream +=
1610 2 : CPLOPrintf("/Group%d Do\n", nGroupId.toInt());
1611 : }
1612 :
1613 4 : EndBlending(psNode, oPageContext);
1614 :
1615 4 : return true;
1616 : }
1617 :
1618 : /************************************************************************/
1619 : /* SetupVectorGeoreferencing() */
1620 : /************************************************************************/
1621 :
1622 4 : bool GDALPDFComposerWriter::SetupVectorGeoreferencing(
1623 : const char *pszGeoreferencingId, OGRLayer *poLayer,
1624 : const PageContext &oPageContext, double &dfClippingMinX,
1625 : double &dfClippingMinY, double &dfClippingMaxX, double &dfClippingMaxY,
1626 : double adfMatrix[4], std::unique_ptr<OGRCoordinateTransformation> &poCT)
1627 : {
1628 4 : CPLAssert(pszGeoreferencingId);
1629 :
1630 4 : auto iter = oPageContext.m_oMapGeoreferencedId.find(pszGeoreferencingId);
1631 4 : if (iter == oPageContext.m_oMapGeoreferencedId.end())
1632 : {
1633 0 : CPLError(CE_Failure, CPLE_AppDefined,
1634 : "Cannot find georeferencing of id %s", pszGeoreferencingId);
1635 0 : return false;
1636 : }
1637 4 : const auto &georeferencing = iter->second;
1638 4 : const double dfX1 = georeferencing.m_bboxX1;
1639 4 : const double dfY1 = georeferencing.m_bboxY1;
1640 4 : const double dfX2 = georeferencing.m_bboxX2;
1641 4 : const double dfY2 = georeferencing.m_bboxY2;
1642 :
1643 4 : dfClippingMinX = APPLY_GT_X(georeferencing.m_adfGT, dfX1, dfY1);
1644 4 : dfClippingMinY = APPLY_GT_Y(georeferencing.m_adfGT, dfX1, dfY1);
1645 4 : dfClippingMaxX = APPLY_GT_X(georeferencing.m_adfGT, dfX2, dfY2);
1646 4 : dfClippingMaxY = APPLY_GT_Y(georeferencing.m_adfGT, dfX2, dfY2);
1647 :
1648 4 : auto poSRS = poLayer->GetSpatialRef();
1649 4 : if (!poSRS)
1650 : {
1651 0 : CPLError(CE_Failure, CPLE_AppDefined, "Layer has no SRS");
1652 0 : return false;
1653 : }
1654 4 : if (!poSRS->IsSame(&georeferencing.m_oSRS))
1655 : {
1656 2 : poCT.reset(
1657 : OGRCreateCoordinateTransformation(poSRS, &georeferencing.m_oSRS));
1658 : }
1659 :
1660 4 : if (!poCT)
1661 : {
1662 2 : poLayer->SetSpatialFilterRect(dfClippingMinX, dfClippingMinY,
1663 2 : dfClippingMaxX, dfClippingMaxY);
1664 : }
1665 :
1666 : double adfInvGeoreferencingGT[6]; // from georeferenced to PDF coordinates
1667 4 : CPL_IGNORE_RET_VAL(GDALInvGeoTransform(
1668 4 : const_cast<double *>(georeferencing.m_adfGT), adfInvGeoreferencingGT));
1669 4 : adfMatrix[0] = adfInvGeoreferencingGT[0];
1670 4 : adfMatrix[1] = adfInvGeoreferencingGT[1];
1671 4 : adfMatrix[2] = adfInvGeoreferencingGT[3];
1672 4 : adfMatrix[3] = adfInvGeoreferencingGT[5];
1673 :
1674 4 : return true;
1675 : }
1676 :
1677 : /************************************************************************/
1678 : /* WriteVector() */
1679 : /************************************************************************/
1680 :
1681 5 : bool GDALPDFComposerWriter::WriteVector(const CPLXMLNode *psNode,
1682 : PageContext &oPageContext)
1683 : {
1684 5 : const char *pszDataset = CPLGetXMLValue(psNode, "dataset", nullptr);
1685 5 : if (!pszDataset)
1686 : {
1687 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing dataset");
1688 0 : return false;
1689 : }
1690 5 : const char *pszLayer = CPLGetXMLValue(psNode, "layer", nullptr);
1691 5 : if (!pszLayer)
1692 : {
1693 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing layer");
1694 0 : return false;
1695 : }
1696 :
1697 : GDALDatasetUniquePtr poDS(
1698 : GDALDataset::Open(pszDataset, GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR,
1699 10 : nullptr, nullptr, nullptr));
1700 5 : if (!poDS)
1701 0 : return false;
1702 5 : OGRLayer *poLayer = poDS->GetLayerByName(pszLayer);
1703 5 : if (!poLayer)
1704 : {
1705 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannt find layer %s", pszLayer);
1706 0 : return false;
1707 : }
1708 : const bool bVisible =
1709 5 : CPLTestBool(CPLGetXMLValue(psNode, "visible", "true"));
1710 :
1711 5 : const auto psLogicalStructure = CPLGetXMLNode(psNode, "LogicalStructure");
1712 5 : const char *pszOGRDisplayField = nullptr;
1713 10 : std::vector<CPLString> aosIncludedFields;
1714 5 : const bool bLogicalStructure = psLogicalStructure != nullptr;
1715 5 : if (psLogicalStructure)
1716 : {
1717 : pszOGRDisplayField =
1718 5 : CPLGetXMLValue(psLogicalStructure, "fieldToDisplay", nullptr);
1719 9 : if (CPLGetXMLNode(psLogicalStructure, "ExcludeAllFields") != nullptr ||
1720 4 : CPLGetXMLNode(psLogicalStructure, "IncludeField") != nullptr)
1721 : {
1722 7 : for (const auto *psIter = psLogicalStructure->psChild; psIter;
1723 5 : psIter = psIter->psNext)
1724 : {
1725 5 : if (psIter->eType == CXT_Element &&
1726 3 : strcmp(psIter->pszValue, "IncludeField") == 0)
1727 : {
1728 2 : aosIncludedFields.push_back(
1729 : CPLGetXMLValue(psIter, nullptr, ""));
1730 : }
1731 : }
1732 : }
1733 : else
1734 : {
1735 6 : std::set<CPLString> oSetExcludedFields;
1736 5 : for (const auto *psIter = psLogicalStructure->psChild; psIter;
1737 2 : psIter = psIter->psNext)
1738 : {
1739 2 : if (psIter->eType == CXT_Element &&
1740 2 : strcmp(psIter->pszValue, "ExcludeField") == 0)
1741 : {
1742 : oSetExcludedFields.insert(
1743 2 : CPLGetXMLValue(psIter, nullptr, ""));
1744 : }
1745 : }
1746 3 : const auto poLayerDefn = poLayer->GetLayerDefn();
1747 8 : for (int i = 0; i < poLayerDefn->GetFieldCount(); i++)
1748 : {
1749 5 : const auto poFieldDefn = poLayerDefn->GetFieldDefn(i);
1750 5 : const char *pszName = poFieldDefn->GetNameRef();
1751 5 : if (oSetExcludedFields.find(pszName) ==
1752 10 : oSetExcludedFields.end())
1753 : {
1754 3 : aosIncludedFields.push_back(pszName);
1755 : }
1756 : }
1757 : }
1758 : }
1759 : const char *pszStyleString =
1760 5 : CPLGetXMLValue(psNode, "ogrStyleString", nullptr);
1761 : const char *pszOGRLinkField =
1762 5 : CPLGetXMLValue(psNode, "linkAttribute", nullptr);
1763 :
1764 : const char *pszGeoreferencingId =
1765 5 : CPLGetXMLValue(psNode, "georeferencingId", nullptr);
1766 5 : std::unique_ptr<OGRCoordinateTransformation> poCT;
1767 5 : double dfClippingMinX = 0;
1768 5 : double dfClippingMinY = 0;
1769 5 : double dfClippingMaxX = 0;
1770 5 : double dfClippingMaxY = 0;
1771 5 : double adfMatrix[4] = {0, 1, 0, 1};
1772 7 : if (pszGeoreferencingId &&
1773 2 : !SetupVectorGeoreferencing(
1774 : pszGeoreferencingId, poLayer, oPageContext, dfClippingMinX,
1775 : dfClippingMinY, dfClippingMaxX, dfClippingMaxY, adfMatrix, poCT))
1776 : {
1777 0 : return false;
1778 : }
1779 :
1780 5 : double dfOpacityFactor = 1.0;
1781 5 : if (!bVisible)
1782 : {
1783 2 : if (oPageContext.m_oExtGState.find("GSinvisible") ==
1784 4 : oPageContext.m_oExtGState.end())
1785 : {
1786 1 : auto nExtGState = AllocNewObject();
1787 1 : StartObj(nExtGState);
1788 : {
1789 1 : GDALPDFDictionaryRW gs;
1790 1 : gs.Add("Type", GDALPDFObjectRW::CreateName("ExtGState"));
1791 1 : gs.Add("ca", 0);
1792 1 : gs.Add("CA", 0);
1793 1 : VSIFPrintfL(m_fp, "%s\n", gs.Serialize().c_str());
1794 : }
1795 1 : EndObj();
1796 1 : oPageContext.m_oExtGState["GSinvisible"] = nExtGState;
1797 : }
1798 2 : oPageContext.m_osDrawingStream += "q\n";
1799 2 : oPageContext.m_osDrawingStream += "/GSinvisible gs\n";
1800 2 : oPageContext.m_osDrawingStream += "0 w\n";
1801 2 : dfOpacityFactor = 0;
1802 : }
1803 : else
1804 : {
1805 3 : StartBlending(psNode, oPageContext, dfOpacityFactor);
1806 : }
1807 :
1808 5 : if (!m_nStructTreeRootId.toBool())
1809 3 : m_nStructTreeRootId = AllocNewObject();
1810 :
1811 5 : GDALPDFObjectNum nFeatureLayerId;
1812 5 : if (bLogicalStructure)
1813 : {
1814 5 : nFeatureLayerId = AllocNewObject();
1815 5 : m_anFeatureLayerId.push_back(nFeatureLayerId);
1816 : }
1817 :
1818 5 : std::vector<GDALPDFObjectNum> anFeatureUserProperties;
1819 14 : for (auto &&poFeature : poLayer)
1820 : {
1821 9 : auto hFeat = OGRFeature::ToHandle(poFeature.get());
1822 9 : auto hGeom = OGR_F_GetGeometryRef(hFeat);
1823 9 : if (!hGeom || OGR_G_IsEmpty(hGeom))
1824 1 : continue;
1825 9 : if (poCT)
1826 : {
1827 2 : if (OGRGeometry::FromHandle(hGeom)->transform(poCT.get()) !=
1828 : OGRERR_NONE)
1829 1 : continue;
1830 :
1831 2 : OGREnvelope sEnvelope;
1832 2 : OGR_G_GetEnvelope(hGeom, &sEnvelope);
1833 2 : if (sEnvelope.MinX > dfClippingMaxX ||
1834 2 : sEnvelope.MaxX < dfClippingMinX ||
1835 1 : sEnvelope.MinY > dfClippingMaxY ||
1836 1 : sEnvelope.MaxY < dfClippingMinY)
1837 : {
1838 1 : continue;
1839 : }
1840 : }
1841 :
1842 8 : if (bLogicalStructure)
1843 : {
1844 8 : CPLString osOutFeatureName;
1845 8 : anFeatureUserProperties.push_back(
1846 8 : WriteAttributes(hFeat, aosIncludedFields, pszOGRDisplayField,
1847 : oPageContext.m_nMCID, nFeatureLayerId,
1848 8 : m_asPageId.back(), osOutFeatureName));
1849 : }
1850 :
1851 16 : ObjectStyle os;
1852 8 : GetObjectStyle(pszStyleString, hFeat, adfMatrix,
1853 8 : m_oMapSymbolFilenameToDesc, os);
1854 8 : os.nPenA = static_cast<int>(std::round(os.nPenA * dfOpacityFactor));
1855 8 : os.nBrushA = static_cast<int>(std::round(os.nBrushA * dfOpacityFactor));
1856 :
1857 8 : const double dfRadius = os.dfSymbolSize;
1858 :
1859 8 : if (os.nImageSymbolId.toBool())
1860 : {
1861 2 : oPageContext.m_oXObjects[CPLOPrintf(
1862 2 : "SymImage%d", os.nImageSymbolId.toInt())] = os.nImageSymbolId;
1863 : }
1864 :
1865 8 : if (pszOGRLinkField)
1866 : {
1867 1 : OGREnvelope sEnvelope;
1868 1 : OGR_G_GetEnvelope(hGeom, &sEnvelope);
1869 : int bboxXMin, bboxYMin, bboxXMax, bboxYMax;
1870 1 : ComputeIntBBox(hGeom, sEnvelope, adfMatrix, os, dfRadius, bboxXMin,
1871 : bboxYMin, bboxXMax, bboxYMax);
1872 :
1873 : auto nLinkId = WriteLink(hFeat, pszOGRLinkField, adfMatrix,
1874 1 : bboxXMin, bboxYMin, bboxXMax, bboxYMax);
1875 1 : if (nLinkId.toBool())
1876 1 : oPageContext.m_anAnnotationsId.push_back(nLinkId);
1877 : }
1878 :
1879 8 : if (bLogicalStructure)
1880 : {
1881 : oPageContext.m_osDrawingStream +=
1882 8 : CPLOPrintf("/feature <</MCID %d>> BDC\n", oPageContext.m_nMCID);
1883 : }
1884 :
1885 8 : if (bVisible || bLogicalStructure)
1886 : {
1887 8 : oPageContext.m_osDrawingStream += "q\n";
1888 8 : if (bVisible && (os.nPenA != 255 || os.nBrushA != 255))
1889 : {
1890 2 : CPLString osGSName;
1891 2 : osGSName.Printf("GS_CA_%d_ca_%d", os.nPenA, os.nBrushA);
1892 2 : if (oPageContext.m_oExtGState.find(osGSName) ==
1893 4 : oPageContext.m_oExtGState.end())
1894 : {
1895 2 : auto nExtGState = AllocNewObject();
1896 2 : StartObj(nExtGState);
1897 : {
1898 2 : GDALPDFDictionaryRW gs;
1899 : gs.Add("Type",
1900 2 : GDALPDFObjectRW::CreateName("ExtGState"));
1901 2 : if (os.nPenA != 255)
1902 1 : gs.Add("CA", (os.nPenA == 127 || os.nPenA == 128)
1903 : ? 0.5
1904 2 : : os.nPenA / 255.0);
1905 2 : if (os.nBrushA != 255)
1906 : gs.Add("ca",
1907 1 : (os.nBrushA == 127 || os.nBrushA == 128)
1908 : ? 0.5
1909 3 : : os.nBrushA / 255.0);
1910 2 : VSIFPrintfL(m_fp, "%s\n", gs.Serialize().c_str());
1911 : }
1912 2 : EndObj();
1913 2 : oPageContext.m_oExtGState[osGSName] = nExtGState;
1914 : }
1915 2 : oPageContext.m_osDrawingStream += "/" + osGSName + " gs\n";
1916 : }
1917 :
1918 : oPageContext.m_osDrawingStream +=
1919 8 : GenerateDrawingStream(hGeom, adfMatrix, os, dfRadius);
1920 :
1921 8 : oPageContext.m_osDrawingStream += "Q\n";
1922 : }
1923 :
1924 8 : if (bLogicalStructure)
1925 : {
1926 8 : oPageContext.m_osDrawingStream += "EMC\n";
1927 8 : oPageContext.m_nMCID++;
1928 : }
1929 : }
1930 :
1931 5 : if (bLogicalStructure)
1932 : {
1933 13 : for (const auto &num : anFeatureUserProperties)
1934 : {
1935 8 : oPageContext.m_anFeatureUserProperties.push_back(num);
1936 : }
1937 :
1938 : {
1939 5 : StartObj(nFeatureLayerId);
1940 :
1941 10 : GDALPDFDictionaryRW oDict;
1942 5 : GDALPDFDictionaryRW *poDictA = new GDALPDFDictionaryRW();
1943 5 : oDict.Add("A", poDictA);
1944 5 : poDictA->Add("O", GDALPDFObjectRW::CreateName("UserProperties"));
1945 5 : GDALPDFArrayRW *poArrayK = new GDALPDFArrayRW();
1946 13 : for (const auto &num : anFeatureUserProperties)
1947 8 : poArrayK->Add(num, 0);
1948 5 : oDict.Add("K", poArrayK);
1949 5 : oDict.Add("P", m_nStructTreeRootId, 0);
1950 5 : oDict.Add("S", GDALPDFObjectRW::CreateName("Layer"));
1951 :
1952 5 : const char *pszOGRDisplayName = CPLGetXMLValue(
1953 5 : psLogicalStructure, "displayLayerName", poLayer->GetName());
1954 5 : oDict.Add("T", pszOGRDisplayName);
1955 :
1956 5 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
1957 :
1958 5 : EndObj();
1959 : }
1960 : }
1961 :
1962 5 : if (!bVisible)
1963 : {
1964 2 : oPageContext.m_osDrawingStream += "Q\n";
1965 : }
1966 : else
1967 : {
1968 3 : EndBlending(psNode, oPageContext);
1969 : }
1970 :
1971 5 : return true;
1972 : }
1973 :
1974 : /************************************************************************/
1975 : /* WriteVectorLabel() */
1976 : /************************************************************************/
1977 :
1978 3 : bool GDALPDFComposerWriter::WriteVectorLabel(const CPLXMLNode *psNode,
1979 : PageContext &oPageContext)
1980 : {
1981 3 : const char *pszDataset = CPLGetXMLValue(psNode, "dataset", nullptr);
1982 3 : if (!pszDataset)
1983 : {
1984 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing dataset");
1985 0 : return false;
1986 : }
1987 3 : const char *pszLayer = CPLGetXMLValue(psNode, "layer", nullptr);
1988 3 : if (!pszLayer)
1989 : {
1990 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing layer");
1991 0 : return false;
1992 : }
1993 :
1994 : GDALDatasetUniquePtr poDS(
1995 : GDALDataset::Open(pszDataset, GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR,
1996 6 : nullptr, nullptr, nullptr));
1997 3 : if (!poDS)
1998 0 : return false;
1999 3 : OGRLayer *poLayer = poDS->GetLayerByName(pszLayer);
2000 3 : if (!poLayer)
2001 : {
2002 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannt find layer %s", pszLayer);
2003 0 : return false;
2004 : }
2005 :
2006 : const char *pszStyleString =
2007 3 : CPLGetXMLValue(psNode, "ogrStyleString", nullptr);
2008 :
2009 3 : double dfOpacityFactor = 1.0;
2010 3 : StartBlending(psNode, oPageContext, dfOpacityFactor);
2011 :
2012 : const char *pszGeoreferencingId =
2013 3 : CPLGetXMLValue(psNode, "georeferencingId", nullptr);
2014 3 : std::unique_ptr<OGRCoordinateTransformation> poCT;
2015 3 : double dfClippingMinX = 0;
2016 3 : double dfClippingMinY = 0;
2017 3 : double dfClippingMaxX = 0;
2018 3 : double dfClippingMaxY = 0;
2019 3 : double adfMatrix[4] = {0, 1, 0, 1};
2020 5 : if (pszGeoreferencingId &&
2021 2 : !SetupVectorGeoreferencing(
2022 : pszGeoreferencingId, poLayer, oPageContext, dfClippingMinX,
2023 : dfClippingMinY, dfClippingMaxX, dfClippingMaxY, adfMatrix, poCT))
2024 : {
2025 0 : return false;
2026 : }
2027 :
2028 7 : for (auto &&poFeature : poLayer)
2029 : {
2030 4 : auto hFeat = OGRFeature::ToHandle(poFeature.get());
2031 4 : auto hGeom = OGR_F_GetGeometryRef(hFeat);
2032 4 : if (!hGeom || OGR_G_IsEmpty(hGeom))
2033 1 : continue;
2034 4 : if (poCT)
2035 : {
2036 2 : if (OGRGeometry::FromHandle(hGeom)->transform(poCT.get()) !=
2037 : OGRERR_NONE)
2038 1 : continue;
2039 :
2040 2 : OGREnvelope sEnvelope;
2041 2 : OGR_G_GetEnvelope(hGeom, &sEnvelope);
2042 2 : if (sEnvelope.MinX > dfClippingMaxX ||
2043 2 : sEnvelope.MaxX < dfClippingMinX ||
2044 1 : sEnvelope.MinY > dfClippingMaxY ||
2045 1 : sEnvelope.MaxY < dfClippingMinY)
2046 : {
2047 1 : continue;
2048 : }
2049 : }
2050 :
2051 6 : ObjectStyle os;
2052 3 : GetObjectStyle(pszStyleString, hFeat, adfMatrix,
2053 3 : m_oMapSymbolFilenameToDesc, os);
2054 3 : os.nPenA = static_cast<int>(std::round(os.nPenA * dfOpacityFactor));
2055 3 : os.nBrushA = static_cast<int>(std::round(os.nBrushA * dfOpacityFactor));
2056 :
2057 6 : if (!os.osLabelText.empty() &&
2058 3 : wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint)
2059 : {
2060 : auto nObjectId = WriteLabel(hGeom, adfMatrix, os,
2061 : oPageContext.m_eStreamCompressMethod, 0,
2062 : 0, oPageContext.m_dfWidthInUserUnit,
2063 3 : oPageContext.m_dfHeightInUserUnit);
2064 : oPageContext.m_osDrawingStream +=
2065 3 : CPLOPrintf("/Label%d Do\n", nObjectId.toInt());
2066 3 : oPageContext.m_oXObjects[CPLOPrintf("Label%d", nObjectId.toInt())] =
2067 : nObjectId;
2068 : }
2069 : }
2070 :
2071 3 : EndBlending(psNode, oPageContext);
2072 :
2073 3 : return true;
2074 : }
2075 :
2076 : #ifdef HAVE_PDF_READ_SUPPORT
2077 :
2078 : /************************************************************************/
2079 : /* EmitNewObject() */
2080 : /************************************************************************/
2081 :
2082 : GDALPDFObjectNum
2083 10 : GDALPDFComposerWriter::EmitNewObject(GDALPDFObject *poObj,
2084 : RemapType &oRemapObjectRefs)
2085 : {
2086 10 : auto nId = AllocNewObject();
2087 10 : const auto nRefNum = poObj->GetRefNum();
2088 10 : if (nRefNum.toBool())
2089 : {
2090 10 : int nRefGen = poObj->GetRefGen();
2091 10 : std::pair<int, int> oKey(nRefNum.toInt(), nRefGen);
2092 10 : oRemapObjectRefs[oKey] = nId;
2093 : }
2094 20 : CPLString osStr;
2095 10 : if (!SerializeAndRenumberIgnoreRef(osStr, poObj, oRemapObjectRefs))
2096 0 : return GDALPDFObjectNum();
2097 10 : StartObj(nId);
2098 10 : VSIFWriteL(osStr.data(), 1, osStr.size(), m_fp);
2099 10 : VSIFPrintfL(m_fp, "\n");
2100 10 : EndObj();
2101 10 : return nId;
2102 : }
2103 :
2104 : /************************************************************************/
2105 : /* SerializeAndRenumber() */
2106 : /************************************************************************/
2107 :
2108 40 : bool GDALPDFComposerWriter::SerializeAndRenumber(CPLString &osStr,
2109 : GDALPDFObject *poObj,
2110 : RemapType &oRemapObjectRefs)
2111 : {
2112 40 : auto nRefNum = poObj->GetRefNum();
2113 40 : if (nRefNum.toBool())
2114 : {
2115 6 : int nRefGen = poObj->GetRefGen();
2116 :
2117 6 : std::pair<int, int> oKey(nRefNum.toInt(), nRefGen);
2118 6 : auto oIter = oRemapObjectRefs.find(oKey);
2119 6 : if (oIter != oRemapObjectRefs.end())
2120 : {
2121 0 : osStr.append(CPLSPrintf("%d 0 R", oIter->second.toInt()));
2122 0 : return true;
2123 : }
2124 : else
2125 : {
2126 6 : auto nId = EmitNewObject(poObj, oRemapObjectRefs);
2127 6 : osStr.append(CPLSPrintf("%d 0 R", nId.toInt()));
2128 6 : return nId.toBool();
2129 : }
2130 : }
2131 : else
2132 : {
2133 34 : return SerializeAndRenumberIgnoreRef(osStr, poObj, oRemapObjectRefs);
2134 : }
2135 : }
2136 :
2137 : /************************************************************************/
2138 : /* SerializeAndRenumberIgnoreRef() */
2139 : /************************************************************************/
2140 :
2141 44 : bool GDALPDFComposerWriter::SerializeAndRenumberIgnoreRef(
2142 : CPLString &osStr, GDALPDFObject *poObj, RemapType &oRemapObjectRefs)
2143 : {
2144 44 : switch (poObj->GetType())
2145 : {
2146 0 : case PDFObjectType_Array:
2147 : {
2148 0 : auto poArray = poObj->GetArray();
2149 0 : int nLength = poArray->GetLength();
2150 0 : osStr.append("[ ");
2151 0 : for (int i = 0; i < nLength; i++)
2152 : {
2153 0 : if (!SerializeAndRenumber(osStr, poArray->Get(i),
2154 : oRemapObjectRefs))
2155 0 : return false;
2156 0 : osStr.append(" ");
2157 : }
2158 0 : osStr.append("]");
2159 0 : break;
2160 : }
2161 12 : case PDFObjectType_Dictionary:
2162 : {
2163 12 : osStr.append("<< ");
2164 12 : auto poDict = poObj->GetDictionary();
2165 12 : auto &oMap = poDict->GetValues();
2166 52 : for (const auto &oIter : oMap)
2167 : {
2168 40 : const char *pszKey = oIter.first.c_str();
2169 40 : GDALPDFObject *poSubObj = oIter.second;
2170 40 : osStr.append("/");
2171 40 : osStr.append(pszKey);
2172 40 : osStr.append(" ");
2173 40 : if (!SerializeAndRenumber(osStr, poSubObj, oRemapObjectRefs))
2174 0 : return false;
2175 40 : osStr.append(" ");
2176 : }
2177 12 : osStr.append(">>");
2178 12 : auto poStream = poObj->GetStream();
2179 12 : if (poStream)
2180 : {
2181 : // CPLAssert( poObj->GetRefNum().toBool() ); // should be a top
2182 : // level object
2183 4 : osStr.append("\nstream\n");
2184 4 : auto pRawBytes = poStream->GetRawBytes();
2185 4 : if (!pRawBytes)
2186 : {
2187 0 : CPLError(CE_Failure, CPLE_AppDefined,
2188 : "Cannot get stream content");
2189 0 : return false;
2190 : }
2191 : osStr.append(pRawBytes,
2192 4 : static_cast<size_t>(poStream->GetRawLength()));
2193 4 : VSIFree(pRawBytes);
2194 4 : osStr.append("\nendstream\n");
2195 : }
2196 12 : break;
2197 : }
2198 0 : case PDFObjectType_Unknown:
2199 : {
2200 0 : CPLError(CE_Failure, CPLE_AppDefined, "Corrupted PDF");
2201 0 : return false;
2202 : }
2203 32 : default:
2204 : {
2205 32 : poObj->Serialize(osStr, false);
2206 32 : break;
2207 : }
2208 : }
2209 44 : return true;
2210 : }
2211 :
2212 : /************************************************************************/
2213 : /* SerializeAndRenumber() */
2214 : /************************************************************************/
2215 :
2216 : GDALPDFObjectNum
2217 4 : GDALPDFComposerWriter::SerializeAndRenumber(GDALPDFObject *poObj)
2218 : {
2219 8 : RemapType oRemapObjectRefs;
2220 8 : return EmitNewObject(poObj, oRemapObjectRefs);
2221 : }
2222 :
2223 : /************************************************************************/
2224 : /* WritePDF() */
2225 : /************************************************************************/
2226 :
2227 12 : bool GDALPDFComposerWriter::WritePDF(const CPLXMLNode *psNode,
2228 : PageContext &oPageContext)
2229 : {
2230 12 : const char *pszDataset = CPLGetXMLValue(psNode, "dataset", nullptr);
2231 12 : if (!pszDataset)
2232 : {
2233 2 : CPLError(CE_Failure, CPLE_AppDefined, "Missing dataset");
2234 2 : return false;
2235 : }
2236 :
2237 20 : GDALOpenInfo oOpenInfo(pszDataset, GA_ReadOnly);
2238 20 : std::unique_ptr<PDFDataset> poDS(PDFDataset::Open(&oOpenInfo));
2239 10 : if (!poDS)
2240 : {
2241 2 : CPLError(CE_Failure, CPLE_OpenFailed, "%s is not a valid PDF file",
2242 : pszDataset);
2243 2 : return false;
2244 : }
2245 16 : if (poDS->GetPageWidth() != oPageContext.m_dfWidthInUserUnit ||
2246 8 : poDS->GetPageHeight() != oPageContext.m_dfHeightInUserUnit)
2247 : {
2248 0 : CPLError(CE_Warning, CPLE_AppDefined,
2249 : "Dimensions of the inserted PDF page are %fx%f, which is "
2250 : "different from the output PDF page %fx%f",
2251 : poDS->GetPageWidth(), poDS->GetPageHeight(),
2252 : oPageContext.m_dfWidthInUserUnit,
2253 : oPageContext.m_dfHeightInUserUnit);
2254 : }
2255 8 : auto poPageObj = poDS->GetPageObj();
2256 8 : if (!poPageObj)
2257 0 : return false;
2258 8 : auto poPageDict = poPageObj->GetDictionary();
2259 8 : if (!poPageDict)
2260 0 : return false;
2261 8 : auto poContents = poPageDict->Get("Contents");
2262 8 : if (poContents != nullptr && poContents->GetType() == PDFObjectType_Array)
2263 : {
2264 0 : GDALPDFArray *poContentsArray = poContents->GetArray();
2265 0 : if (poContentsArray->GetLength() == 1)
2266 : {
2267 0 : poContents = poContentsArray->Get(0);
2268 : }
2269 : }
2270 14 : if (poContents == nullptr ||
2271 6 : poContents->GetType() != PDFObjectType_Dictionary)
2272 : {
2273 2 : CPLError(CE_Failure, CPLE_AppDefined, "Missing Contents");
2274 2 : return false;
2275 : }
2276 :
2277 6 : auto poResources = poPageDict->Get("Resources");
2278 6 : if (!poResources)
2279 : {
2280 2 : CPLError(CE_Failure, CPLE_AppDefined, "Missing Resources");
2281 2 : return false;
2282 : }
2283 :
2284 : // Serialize and renumber the Page Resources dictionary
2285 4 : auto nClonedResources = SerializeAndRenumber(poResources);
2286 4 : if (!nClonedResources.toBool())
2287 : {
2288 0 : return false;
2289 : }
2290 :
2291 : // Create a Transparency group using cloned Page Resources, and
2292 : // the Page Contents stream
2293 4 : auto nFormId = AllocNewObject();
2294 8 : GDALPDFDictionaryRW oDictGroup;
2295 4 : GDALPDFDictionaryRW *poGroup = new GDALPDFDictionaryRW();
2296 4 : poGroup->Add("Type", GDALPDFObjectRW::CreateName("Group"))
2297 4 : .Add("S", GDALPDFObjectRW::CreateName("Transparency"));
2298 :
2299 4 : oDictGroup.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
2300 4 : .Add("BBox", &((new GDALPDFArrayRW())->Add(0).Add(0))
2301 4 : .Add(oPageContext.m_dfWidthInUserUnit)
2302 4 : .Add(oPageContext.m_dfHeightInUserUnit))
2303 4 : .Add("Subtype", GDALPDFObjectRW::CreateName("Form"))
2304 4 : .Add("Group", poGroup)
2305 4 : .Add("Resources", nClonedResources, 0);
2306 :
2307 4 : auto poStream = poContents->GetStream();
2308 4 : if (!poStream)
2309 : {
2310 2 : CPLError(CE_Failure, CPLE_AppDefined, "Missing Contents stream");
2311 2 : return false;
2312 : }
2313 2 : auto pabyContents = poStream->GetBytes();
2314 2 : if (!pabyContents)
2315 : {
2316 0 : return false;
2317 : }
2318 2 : const auto nContentsLength = poStream->GetLength();
2319 :
2320 2 : StartObjWithStream(nFormId, oDictGroup,
2321 2 : oPageContext.m_eStreamCompressMethod != COMPRESS_NONE);
2322 2 : VSIFWriteL(pabyContents, 1, static_cast<size_t>(nContentsLength), m_fp);
2323 2 : VSIFree(pabyContents);
2324 2 : EndObjWithStream();
2325 :
2326 : // Paint the transparency group
2327 : double dfIgnoredOpacity;
2328 2 : StartBlending(psNode, oPageContext, dfIgnoredOpacity);
2329 :
2330 : oPageContext.m_osDrawingStream +=
2331 2 : CPLOPrintf("/Form%d Do\n", nFormId.toInt());
2332 2 : oPageContext.m_oXObjects[CPLOPrintf("Form%d", nFormId.toInt())] = nFormId;
2333 :
2334 2 : EndBlending(psNode, oPageContext);
2335 :
2336 2 : return true;
2337 : }
2338 :
2339 : #endif // HAVE_PDF_READ_SUPPORT
2340 :
2341 : /************************************************************************/
2342 : /* Generate() */
2343 : /************************************************************************/
2344 :
2345 38 : bool GDALPDFComposerWriter::Generate(const CPLXMLNode *psComposition)
2346 : {
2347 38 : m_osJPEG2000Driver = CPLGetXMLValue(psComposition, "JPEG2000Driver", "");
2348 :
2349 38 : auto psMetadata = CPLGetXMLNode(psComposition, "Metadata");
2350 38 : if (psMetadata)
2351 : {
2352 : SetInfo(CPLGetXMLValue(psMetadata, "Author", nullptr),
2353 : CPLGetXMLValue(psMetadata, "Producer", nullptr),
2354 : CPLGetXMLValue(psMetadata, "Creator", nullptr),
2355 : CPLGetXMLValue(psMetadata, "CreationDate", nullptr),
2356 : CPLGetXMLValue(psMetadata, "Subject", nullptr),
2357 : CPLGetXMLValue(psMetadata, "Title", nullptr),
2358 1 : CPLGetXMLValue(psMetadata, "Keywords", nullptr));
2359 1 : SetXMP(nullptr, CPLGetXMLValue(psMetadata, "XMP", nullptr));
2360 : }
2361 :
2362 : const char *pszJavascript =
2363 38 : CPLGetXMLValue(psComposition, "Javascript", nullptr);
2364 38 : if (pszJavascript)
2365 1 : WriteJavascript(pszJavascript, false);
2366 :
2367 38 : auto psLayerTree = CPLGetXMLNode(psComposition, "LayerTree");
2368 38 : if (psLayerTree)
2369 : {
2370 7 : m_bDisplayLayersOnlyOnVisiblePages = CPLTestBool(
2371 : CPLGetXMLValue(psLayerTree, "displayOnlyOnVisiblePages", "false"));
2372 7 : if (!CreateLayerTree(psLayerTree, GDALPDFObjectNum(), &m_oTreeOfOGC))
2373 3 : return false;
2374 : }
2375 :
2376 35 : bool bFoundPage = false;
2377 63 : for (const auto *psIter = psComposition->psChild; psIter;
2378 28 : psIter = psIter->psNext)
2379 : {
2380 48 : if (psIter->eType == CXT_Element &&
2381 48 : strcmp(psIter->pszValue, "Page") == 0)
2382 : {
2383 37 : if (!GeneratePage(psIter))
2384 20 : return false;
2385 17 : bFoundPage = true;
2386 : }
2387 : }
2388 15 : if (!bFoundPage)
2389 : {
2390 1 : CPLError(CE_Failure, CPLE_AppDefined,
2391 : "At least one page should be defined");
2392 1 : return false;
2393 : }
2394 :
2395 14 : auto psOutline = CPLGetXMLNode(psComposition, "Outline");
2396 14 : if (psOutline)
2397 : {
2398 5 : if (!CreateOutline(psOutline))
2399 4 : return false;
2400 : }
2401 :
2402 10 : return true;
2403 : }
2404 :
2405 : /************************************************************************/
2406 : /* GDALPDFErrorHandler() */
2407 : /************************************************************************/
2408 :
2409 19 : static void CPL_STDCALL GDALPDFErrorHandler(CPL_UNUSED CPLErr eErr,
2410 : CPL_UNUSED CPLErrorNum nType,
2411 : const char *pszMsg)
2412 : {
2413 : std::vector<CPLString> *paosErrors =
2414 19 : static_cast<std::vector<CPLString> *>(CPLGetErrorHandlerUserData());
2415 19 : paosErrors->push_back(pszMsg);
2416 19 : }
2417 :
2418 : /************************************************************************/
2419 : /* GDALPDFCreateFromCompositionFile() */
2420 : /************************************************************************/
2421 :
2422 39 : GDALDataset *GDALPDFCreateFromCompositionFile(const char *pszPDFFilename,
2423 : const char *pszXMLFilename)
2424 : {
2425 76 : CPLXMLTreeCloser oXML((pszXMLFilename[0] == '<' &&
2426 37 : strstr(pszXMLFilename, "<PDFComposition") != nullptr)
2427 37 : ? CPLParseXMLString(pszXMLFilename)
2428 115 : : CPLParseXMLFile(pszXMLFilename));
2429 39 : if (!oXML.get())
2430 1 : return nullptr;
2431 38 : auto psComposition = CPLGetXMLNode(oXML.get(), "=PDFComposition");
2432 38 : if (!psComposition)
2433 : {
2434 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find PDFComposition");
2435 0 : return nullptr;
2436 : }
2437 :
2438 : // XML Validation.
2439 38 : if (CPLTestBool(CPLGetConfigOption("GDAL_XML_VALIDATION", "YES")))
2440 : {
2441 : #ifdef EMBED_RESOURCE_FILES
2442 : std::string osTmpFilename;
2443 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
2444 : #endif
2445 : #ifdef USE_ONLY_EMBEDDED_RESOURCE_FILES
2446 : const char *pszXSD = nullptr;
2447 : #else
2448 38 : const char *pszXSD = CPLFindFile("gdal", "pdfcomposition.xsd");
2449 : #endif
2450 : #ifdef EMBED_RESOURCE_FILES
2451 : if (!pszXSD)
2452 : {
2453 : static const bool bOnce [[maybe_unused]] = []()
2454 : {
2455 : CPLDebug("PDF", "Using embedded pdfcomposition.xsd");
2456 : return true;
2457 : }();
2458 : osTmpFilename = VSIMemGenerateHiddenFilename("pdfcomposition.xsd");
2459 : pszXSD = osTmpFilename.c_str();
2460 : VSIFCloseL(VSIFileFromMemBuffer(
2461 : osTmpFilename.c_str(),
2462 : const_cast<GByte *>(
2463 : reinterpret_cast<const GByte *>(PDFGetCompositionXSD())),
2464 : static_cast<int>(strlen(PDFGetCompositionXSD())),
2465 : /* bTakeOwnership = */ false));
2466 : }
2467 : #else
2468 38 : if (pszXSD != nullptr)
2469 : #endif
2470 : {
2471 76 : std::vector<CPLString> aosErrors;
2472 38 : CPLPushErrorHandlerEx(GDALPDFErrorHandler, &aosErrors);
2473 38 : const int bRet = CPLValidateXML(pszXMLFilename, pszXSD, nullptr);
2474 38 : CPLPopErrorHandler();
2475 38 : if (!bRet)
2476 : {
2477 36 : if (!aosErrors.empty() &&
2478 18 : strstr(aosErrors[0].c_str(), "missing libxml2 support") ==
2479 : nullptr)
2480 : {
2481 37 : for (size_t i = 0; i < aosErrors.size(); i++)
2482 : {
2483 19 : CPLError(CE_Warning, CPLE_AppDefined, "%s",
2484 19 : aosErrors[i].c_str());
2485 : }
2486 : }
2487 : }
2488 38 : CPLErrorReset();
2489 : }
2490 :
2491 : #ifdef EMBED_RESOURCE_FILES
2492 : if (!osTmpFilename.empty())
2493 : VSIUnlink(osTmpFilename.c_str());
2494 : #endif
2495 : }
2496 :
2497 : /* -------------------------------------------------------------------- */
2498 : /* Create file. */
2499 : /* -------------------------------------------------------------------- */
2500 38 : VSILFILE *fp = VSIFOpenL(pszPDFFilename, "wb");
2501 38 : if (fp == nullptr)
2502 : {
2503 0 : CPLError(CE_Failure, CPLE_OpenFailed, "Unable to create PDF file %s.\n",
2504 : pszPDFFilename);
2505 0 : return nullptr;
2506 : }
2507 :
2508 76 : GDALPDFComposerWriter oWriter(fp);
2509 38 : if (!oWriter.Generate(psComposition))
2510 28 : return nullptr;
2511 :
2512 10 : return new GDALFakePDFDataset();
2513 : }
|