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