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 : georeferencing.m_gt.data(), 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_gt[2]) <
763 6 : 1e-5 * std::fabs(georeferencing.m_gt[1]) &&
764 3 : std::fabs(georeferencing.m_gt[4]) <
765 3 : 1e-5 * std::fabs(georeferencing.m_gt[5]))
766 : {
767 3 : georeferencing.m_gt[2] = 0;
768 3 : georeferencing.m_gt[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 : GDALGeoTransform rasterGT;
1292 4 : GDALGeoTransform invGT; // 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_gt, dfX1, dfY1);
1312 1 : dfClippingMinY = APPLY_GT_Y(georeferencing.m_gt, dfX1, dfY1);
1313 1 : dfClippingMaxX = APPLY_GT_X(georeferencing.m_gt, dfX2, dfY2);
1314 1 : dfClippingMaxY = APPLY_GT_Y(georeferencing.m_gt, dfX2, dfY2);
1315 :
1316 2 : if (poDS->GetGeoTransform(rasterGT) != CE_None || rasterGT[2] != 0 ||
1317 2 : rasterGT[4] != 0 || rasterGT[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(georeferencing.m_gt.GetInverse(invGT));
1334 : }
1335 4 : const double dfRasterMinX = rasterGT[0];
1336 4 : const double dfRasterMaxY = rasterGT[3];
1337 :
1338 : /* Does the source image has a color table ? */
1339 4 : const auto nColorTableId = WriteColorTable(poDS.get());
1340 :
1341 : double dfIgnoredOpacity;
1342 4 : StartBlending(psNode, oPageContext, dfIgnoredOpacity);
1343 :
1344 8 : CPLString osGroupStream;
1345 8 : std::vector<GDALPDFObjectNum> anImageIds;
1346 :
1347 4 : const int nXBlocks = DIV_ROUND_UP(nWidth, nBlockXSize);
1348 4 : const int nYBlocks = DIV_ROUND_UP(nHeight, nBlockYSize);
1349 : int nBlockXOff, nBlockYOff;
1350 10 : for (nBlockYOff = 0; nBlockYOff < nYBlocks; nBlockYOff++)
1351 : {
1352 16 : for (nBlockXOff = 0; nBlockXOff < nXBlocks; nBlockXOff++)
1353 : {
1354 : int nReqWidth =
1355 10 : std::min(nBlockXSize, nWidth - nBlockXOff * nBlockXSize);
1356 : int nReqHeight =
1357 10 : std::min(nBlockYSize, nHeight - nBlockYOff * nBlockYSize);
1358 :
1359 10 : int nX = nBlockXOff * nBlockXSize;
1360 10 : int nY = nBlockYOff * nBlockYSize;
1361 :
1362 10 : double dfXPDFOff = nX * (dfX2 - dfX1) / nWidth + dfX1;
1363 10 : double dfYPDFOff =
1364 10 : (nHeight - nY - nReqHeight) * (dfY2 - dfY1) / nHeight + dfY1;
1365 10 : double dfXPDFSize = nReqWidth * (dfX2 - dfX1) / nWidth;
1366 10 : double dfYPDFSize = nReqHeight * (dfY2 - dfY1) / nHeight;
1367 :
1368 10 : if (bClip)
1369 : {
1370 : /* Compute extent of block to write */
1371 1 : double dfBlockMinX = rasterGT[0] + nX * rasterGT[1];
1372 : double dfBlockMaxX =
1373 1 : rasterGT[0] + (nX + nReqWidth) * rasterGT[1];
1374 : double dfBlockMinY =
1375 1 : rasterGT[3] + (nY + nReqHeight) * rasterGT[5];
1376 1 : double dfBlockMaxY = rasterGT[3] + nY * rasterGT[5];
1377 :
1378 : // Clip the extent of the block with the extent of the main
1379 : // raster.
1380 : const double dfIntersectMinX =
1381 1 : std::max(dfBlockMinX, dfClippingMinX);
1382 : const double dfIntersectMinY =
1383 1 : std::max(dfBlockMinY, dfClippingMinY);
1384 : const double dfIntersectMaxX =
1385 1 : std::min(dfBlockMaxX, dfClippingMaxX);
1386 : const double dfIntersectMaxY =
1387 1 : std::min(dfBlockMaxY, dfClippingMaxY);
1388 :
1389 1 : bool bOK = false;
1390 1 : if (dfIntersectMinX < dfIntersectMaxX &&
1391 : dfIntersectMinY < dfIntersectMaxY)
1392 : {
1393 : /* Re-compute (x,y,width,height) subwindow of current raster
1394 : * from */
1395 : /* the extent of the clipped block */
1396 1 : nX = static_cast<int>(
1397 1 : (dfIntersectMinX - dfRasterMinX) / rasterGT[1] + 0.5);
1398 2 : nY = static_cast<int>((dfRasterMaxY - dfIntersectMaxY) /
1399 1 : (-rasterGT[5]) +
1400 : 0.5);
1401 1 : nReqWidth =
1402 2 : static_cast<int>((dfIntersectMaxX - dfRasterMinX) /
1403 1 : rasterGT[1] +
1404 : 0.5) -
1405 : nX;
1406 1 : nReqHeight =
1407 2 : static_cast<int>((dfRasterMaxY - dfIntersectMinY) /
1408 1 : (-rasterGT[5]) +
1409 : 0.5) -
1410 : nY;
1411 :
1412 1 : if (nReqWidth > 0 && nReqHeight > 0)
1413 : {
1414 1 : dfBlockMinX = rasterGT[0] + nX * rasterGT[1];
1415 1 : dfBlockMaxX =
1416 1 : rasterGT[0] + (nX + nReqWidth) * rasterGT[1];
1417 1 : dfBlockMinY =
1418 1 : rasterGT[3] + (nY + nReqHeight) * rasterGT[5];
1419 1 : dfBlockMaxY = rasterGT[3] + nY * rasterGT[5];
1420 :
1421 : double dfPDFX1 =
1422 1 : APPLY_GT_X(invGT, dfBlockMinX, dfBlockMinY);
1423 : double dfPDFY1 =
1424 1 : APPLY_GT_Y(invGT, dfBlockMinX, dfBlockMinY);
1425 : double dfPDFX2 =
1426 1 : APPLY_GT_X(invGT, dfBlockMaxX, dfBlockMaxY);
1427 : double dfPDFY2 =
1428 1 : APPLY_GT_Y(invGT, dfBlockMaxX, dfBlockMaxY);
1429 :
1430 1 : dfXPDFOff = dfPDFX1;
1431 1 : dfYPDFOff = dfPDFY1;
1432 1 : dfXPDFSize = dfPDFX2 - dfPDFX1;
1433 1 : dfYPDFSize = dfPDFY2 - dfPDFY1;
1434 1 : bOK = true;
1435 : }
1436 : }
1437 1 : if (!bOK)
1438 : {
1439 0 : continue;
1440 : }
1441 : }
1442 :
1443 : const auto nImageId =
1444 : WriteBlock(poDS.get(), nX, nY, nReqWidth, nReqHeight,
1445 : nColorTableId, eCompressMethod, nPredictor,
1446 10 : nJPEGQuality, pszJPEG2000_DRIVER, nullptr, nullptr);
1447 :
1448 10 : if (!nImageId.toBool())
1449 0 : return false;
1450 :
1451 10 : anImageIds.push_back(nImageId);
1452 10 : osGroupStream += "q\n";
1453 10 : GDALPDFObjectRW *poXSize = GDALPDFObjectRW::CreateReal(dfXPDFSize);
1454 10 : GDALPDFObjectRW *poYSize = GDALPDFObjectRW::CreateReal(dfYPDFSize);
1455 10 : GDALPDFObjectRW *poXOff = GDALPDFObjectRW::CreateReal(dfXPDFOff);
1456 10 : GDALPDFObjectRW *poYOff = GDALPDFObjectRW::CreateReal(dfYPDFOff);
1457 50 : osGroupStream += CPLOPrintf(
1458 20 : "%s 0 0 %s %s %s cm\n", poXSize->Serialize().c_str(),
1459 30 : poYSize->Serialize().c_str(), poXOff->Serialize().c_str(),
1460 30 : poYOff->Serialize().c_str());
1461 10 : delete poXSize;
1462 10 : delete poYSize;
1463 10 : delete poXOff;
1464 10 : delete poYOff;
1465 10 : osGroupStream += CPLOPrintf("/Image%d Do\n", nImageId.toInt());
1466 10 : osGroupStream += "Q\n";
1467 : }
1468 : }
1469 :
1470 4 : if (anImageIds.size() <= 1 || CPLGetXMLNode(psNode, "Blending") == nullptr)
1471 : {
1472 4 : for (const auto &nImageId : anImageIds)
1473 : {
1474 2 : oPageContext.m_oXObjects[CPLOPrintf("Image%d", nImageId.toInt())] =
1475 : nImageId;
1476 : }
1477 2 : oPageContext.m_osDrawingStream += osGroupStream;
1478 : }
1479 : else
1480 : {
1481 : // In case several tiles are drawn with blending, use a transparency
1482 : // group to avoid edge effects.
1483 :
1484 2 : auto nGroupId = AllocNewObject();
1485 2 : GDALPDFDictionaryRW oDictGroup;
1486 2 : GDALPDFDictionaryRW *poGroup = new GDALPDFDictionaryRW();
1487 2 : poGroup->Add("Type", GDALPDFObjectRW::CreateName("Group"))
1488 2 : .Add("S", GDALPDFObjectRW::CreateName("Transparency"));
1489 :
1490 2 : GDALPDFDictionaryRW *poXObjects = new GDALPDFDictionaryRW();
1491 10 : for (const auto &nImageId : anImageIds)
1492 : {
1493 16 : poXObjects->Add(CPLOPrintf("Image%d", nImageId.toInt()), nImageId,
1494 8 : 0);
1495 : }
1496 2 : GDALPDFDictionaryRW *poResources = new GDALPDFDictionaryRW();
1497 2 : poResources->Add("XObject", poXObjects);
1498 :
1499 2 : oDictGroup.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
1500 2 : .Add("BBox", &((new GDALPDFArrayRW())->Add(0).Add(0))
1501 2 : .Add(oPageContext.m_dfWidthInUserUnit)
1502 2 : .Add(oPageContext.m_dfHeightInUserUnit))
1503 2 : .Add("Subtype", GDALPDFObjectRW::CreateName("Form"))
1504 2 : .Add("Group", poGroup)
1505 2 : .Add("Resources", poResources);
1506 :
1507 2 : StartObjWithStream(nGroupId, oDictGroup,
1508 2 : oPageContext.m_eStreamCompressMethod !=
1509 : COMPRESS_NONE);
1510 2 : VSIFPrintfL(m_fp, "%s", osGroupStream.c_str());
1511 2 : EndObjWithStream();
1512 :
1513 2 : oPageContext.m_oXObjects[CPLOPrintf("Group%d", nGroupId.toInt())] =
1514 : nGroupId;
1515 : oPageContext.m_osDrawingStream +=
1516 2 : CPLOPrintf("/Group%d Do\n", nGroupId.toInt());
1517 : }
1518 :
1519 4 : EndBlending(psNode, oPageContext);
1520 :
1521 4 : return true;
1522 : }
1523 :
1524 : /************************************************************************/
1525 : /* SetupVectorGeoreferencing() */
1526 : /************************************************************************/
1527 :
1528 4 : bool GDALPDFComposerWriter::SetupVectorGeoreferencing(
1529 : const char *pszGeoreferencingId, OGRLayer *poLayer,
1530 : const PageContext &oPageContext, double &dfClippingMinX,
1531 : double &dfClippingMinY, double &dfClippingMaxX, double &dfClippingMaxY,
1532 : double adfMatrix[4], std::unique_ptr<OGRCoordinateTransformation> &poCT)
1533 : {
1534 4 : CPLAssert(pszGeoreferencingId);
1535 :
1536 4 : auto iter = oPageContext.m_oMapGeoreferencedId.find(pszGeoreferencingId);
1537 4 : if (iter == oPageContext.m_oMapGeoreferencedId.end())
1538 : {
1539 0 : CPLError(CE_Failure, CPLE_AppDefined,
1540 : "Cannot find georeferencing of id %s", pszGeoreferencingId);
1541 0 : return false;
1542 : }
1543 4 : const auto &georeferencing = iter->second;
1544 4 : const double dfX1 = georeferencing.m_bboxX1;
1545 4 : const double dfY1 = georeferencing.m_bboxY1;
1546 4 : const double dfX2 = georeferencing.m_bboxX2;
1547 4 : const double dfY2 = georeferencing.m_bboxY2;
1548 :
1549 4 : dfClippingMinX = APPLY_GT_X(georeferencing.m_gt, dfX1, dfY1);
1550 4 : dfClippingMinY = APPLY_GT_Y(georeferencing.m_gt, dfX1, dfY1);
1551 4 : dfClippingMaxX = APPLY_GT_X(georeferencing.m_gt, dfX2, dfY2);
1552 4 : dfClippingMaxY = APPLY_GT_Y(georeferencing.m_gt, dfX2, dfY2);
1553 :
1554 4 : auto poSRS = poLayer->GetSpatialRef();
1555 4 : if (!poSRS)
1556 : {
1557 0 : CPLError(CE_Failure, CPLE_AppDefined, "Layer has no SRS");
1558 0 : return false;
1559 : }
1560 4 : if (!poSRS->IsSame(&georeferencing.m_oSRS))
1561 : {
1562 2 : poCT.reset(
1563 : OGRCreateCoordinateTransformation(poSRS, &georeferencing.m_oSRS));
1564 : }
1565 :
1566 4 : if (!poCT)
1567 : {
1568 2 : poLayer->SetSpatialFilterRect(dfClippingMinX, dfClippingMinY,
1569 : dfClippingMaxX, dfClippingMaxY);
1570 : }
1571 :
1572 4 : GDALGeoTransform invGT; // from georeferenced to PDF coordinates
1573 4 : CPL_IGNORE_RET_VAL(georeferencing.m_gt.GetInverse(invGT));
1574 4 : adfMatrix[0] = invGT[0];
1575 4 : adfMatrix[1] = invGT[1];
1576 4 : adfMatrix[2] = invGT[3];
1577 4 : adfMatrix[3] = invGT[5];
1578 :
1579 4 : return true;
1580 : }
1581 :
1582 : /************************************************************************/
1583 : /* WriteVector() */
1584 : /************************************************************************/
1585 :
1586 5 : bool GDALPDFComposerWriter::WriteVector(const CPLXMLNode *psNode,
1587 : PageContext &oPageContext)
1588 : {
1589 5 : const char *pszDataset = CPLGetXMLValue(psNode, "dataset", nullptr);
1590 5 : if (!pszDataset)
1591 : {
1592 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing dataset");
1593 0 : return false;
1594 : }
1595 5 : const char *pszLayer = CPLGetXMLValue(psNode, "layer", nullptr);
1596 5 : if (!pszLayer)
1597 : {
1598 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing layer");
1599 0 : return false;
1600 : }
1601 :
1602 : GDALDatasetUniquePtr poDS(
1603 : GDALDataset::Open(pszDataset, GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR,
1604 10 : nullptr, nullptr, nullptr));
1605 5 : if (!poDS)
1606 0 : return false;
1607 5 : OGRLayer *poLayer = poDS->GetLayerByName(pszLayer);
1608 5 : if (!poLayer)
1609 : {
1610 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannt find layer %s", pszLayer);
1611 0 : return false;
1612 : }
1613 : const bool bVisible =
1614 5 : CPLTestBool(CPLGetXMLValue(psNode, "visible", "true"));
1615 :
1616 5 : const auto psLogicalStructure = CPLGetXMLNode(psNode, "LogicalStructure");
1617 5 : const char *pszOGRDisplayField = nullptr;
1618 10 : std::vector<CPLString> aosIncludedFields;
1619 5 : const bool bLogicalStructure = psLogicalStructure != nullptr;
1620 5 : if (psLogicalStructure)
1621 : {
1622 : pszOGRDisplayField =
1623 5 : CPLGetXMLValue(psLogicalStructure, "fieldToDisplay", nullptr);
1624 9 : if (CPLGetXMLNode(psLogicalStructure, "ExcludeAllFields") != nullptr ||
1625 4 : CPLGetXMLNode(psLogicalStructure, "IncludeField") != nullptr)
1626 : {
1627 7 : for (const auto *psIter = psLogicalStructure->psChild; psIter;
1628 5 : psIter = psIter->psNext)
1629 : {
1630 5 : if (psIter->eType == CXT_Element &&
1631 3 : strcmp(psIter->pszValue, "IncludeField") == 0)
1632 : {
1633 2 : aosIncludedFields.push_back(
1634 : CPLGetXMLValue(psIter, nullptr, ""));
1635 : }
1636 : }
1637 : }
1638 : else
1639 : {
1640 6 : std::set<CPLString> oSetExcludedFields;
1641 5 : for (const auto *psIter = psLogicalStructure->psChild; psIter;
1642 2 : psIter = psIter->psNext)
1643 : {
1644 2 : if (psIter->eType == CXT_Element &&
1645 2 : strcmp(psIter->pszValue, "ExcludeField") == 0)
1646 : {
1647 : oSetExcludedFields.insert(
1648 2 : CPLGetXMLValue(psIter, nullptr, ""));
1649 : }
1650 : }
1651 3 : const auto poLayerDefn = poLayer->GetLayerDefn();
1652 8 : for (int i = 0; i < poLayerDefn->GetFieldCount(); i++)
1653 : {
1654 5 : const auto poFieldDefn = poLayerDefn->GetFieldDefn(i);
1655 5 : const char *pszName = poFieldDefn->GetNameRef();
1656 5 : if (oSetExcludedFields.find(pszName) ==
1657 10 : oSetExcludedFields.end())
1658 : {
1659 3 : aosIncludedFields.push_back(pszName);
1660 : }
1661 : }
1662 : }
1663 : }
1664 : const char *pszStyleString =
1665 5 : CPLGetXMLValue(psNode, "ogrStyleString", nullptr);
1666 : const char *pszOGRLinkField =
1667 5 : CPLGetXMLValue(psNode, "linkAttribute", nullptr);
1668 :
1669 : const char *pszGeoreferencingId =
1670 5 : CPLGetXMLValue(psNode, "georeferencingId", nullptr);
1671 5 : std::unique_ptr<OGRCoordinateTransformation> poCT;
1672 5 : double dfClippingMinX = 0;
1673 5 : double dfClippingMinY = 0;
1674 5 : double dfClippingMaxX = 0;
1675 5 : double dfClippingMaxY = 0;
1676 5 : double adfMatrix[4] = {0, 1, 0, 1};
1677 7 : if (pszGeoreferencingId &&
1678 2 : !SetupVectorGeoreferencing(
1679 : pszGeoreferencingId, poLayer, oPageContext, dfClippingMinX,
1680 : dfClippingMinY, dfClippingMaxX, dfClippingMaxY, adfMatrix, poCT))
1681 : {
1682 0 : return false;
1683 : }
1684 :
1685 5 : double dfOpacityFactor = 1.0;
1686 5 : if (!bVisible)
1687 : {
1688 2 : if (oPageContext.m_oExtGState.find("GSinvisible") ==
1689 4 : oPageContext.m_oExtGState.end())
1690 : {
1691 1 : auto nExtGState = AllocNewObject();
1692 1 : StartObj(nExtGState);
1693 : {
1694 1 : GDALPDFDictionaryRW gs;
1695 1 : gs.Add("Type", GDALPDFObjectRW::CreateName("ExtGState"));
1696 1 : gs.Add("ca", 0);
1697 1 : gs.Add("CA", 0);
1698 1 : VSIFPrintfL(m_fp, "%s\n", gs.Serialize().c_str());
1699 : }
1700 1 : EndObj();
1701 1 : oPageContext.m_oExtGState["GSinvisible"] = nExtGState;
1702 : }
1703 2 : oPageContext.m_osDrawingStream += "q\n";
1704 2 : oPageContext.m_osDrawingStream += "/GSinvisible gs\n";
1705 2 : oPageContext.m_osDrawingStream += "0 w\n";
1706 2 : dfOpacityFactor = 0;
1707 : }
1708 : else
1709 : {
1710 3 : StartBlending(psNode, oPageContext, dfOpacityFactor);
1711 : }
1712 :
1713 5 : if (!m_nStructTreeRootId.toBool())
1714 3 : m_nStructTreeRootId = AllocNewObject();
1715 :
1716 5 : GDALPDFObjectNum nFeatureLayerId;
1717 5 : if (bLogicalStructure)
1718 : {
1719 5 : nFeatureLayerId = AllocNewObject();
1720 5 : m_anFeatureLayerId.push_back(nFeatureLayerId);
1721 : }
1722 :
1723 5 : std::vector<GDALPDFObjectNum> anFeatureUserProperties;
1724 14 : for (auto &&poFeature : poLayer)
1725 : {
1726 9 : auto hFeat = OGRFeature::ToHandle(poFeature.get());
1727 9 : auto hGeom = OGR_F_GetGeometryRef(hFeat);
1728 9 : if (!hGeom || OGR_G_IsEmpty(hGeom))
1729 1 : continue;
1730 9 : if (poCT)
1731 : {
1732 2 : if (OGRGeometry::FromHandle(hGeom)->transform(poCT.get()) !=
1733 : OGRERR_NONE)
1734 1 : continue;
1735 :
1736 2 : OGREnvelope sEnvelope;
1737 2 : OGR_G_GetEnvelope(hGeom, &sEnvelope);
1738 2 : if (sEnvelope.MinX > dfClippingMaxX ||
1739 2 : sEnvelope.MaxX < dfClippingMinX ||
1740 1 : sEnvelope.MinY > dfClippingMaxY ||
1741 1 : sEnvelope.MaxY < dfClippingMinY)
1742 : {
1743 1 : continue;
1744 : }
1745 : }
1746 :
1747 8 : if (bLogicalStructure)
1748 : {
1749 8 : CPLString osOutFeatureName;
1750 8 : anFeatureUserProperties.push_back(
1751 8 : WriteAttributes(hFeat, aosIncludedFields, pszOGRDisplayField,
1752 : oPageContext.m_nMCID, nFeatureLayerId,
1753 8 : m_asPageId.back(), osOutFeatureName));
1754 : }
1755 :
1756 16 : ObjectStyle os;
1757 8 : GetObjectStyle(pszStyleString, hFeat, adfMatrix,
1758 8 : m_oMapSymbolFilenameToDesc, os);
1759 8 : os.nPenA = static_cast<int>(std::round(os.nPenA * dfOpacityFactor));
1760 8 : os.nBrushA = static_cast<int>(std::round(os.nBrushA * dfOpacityFactor));
1761 :
1762 8 : const double dfRadius = os.dfSymbolSize;
1763 :
1764 8 : if (os.nImageSymbolId.toBool())
1765 : {
1766 2 : oPageContext.m_oXObjects[CPLOPrintf(
1767 2 : "SymImage%d", os.nImageSymbolId.toInt())] = os.nImageSymbolId;
1768 : }
1769 :
1770 8 : if (pszOGRLinkField)
1771 : {
1772 1 : OGREnvelope sEnvelope;
1773 1 : OGR_G_GetEnvelope(hGeom, &sEnvelope);
1774 : int bboxXMin, bboxYMin, bboxXMax, bboxYMax;
1775 1 : ComputeIntBBox(hGeom, sEnvelope, adfMatrix, os, dfRadius, bboxXMin,
1776 : bboxYMin, bboxXMax, bboxYMax);
1777 :
1778 : auto nLinkId = WriteLink(hFeat, pszOGRLinkField, adfMatrix,
1779 1 : bboxXMin, bboxYMin, bboxXMax, bboxYMax);
1780 1 : if (nLinkId.toBool())
1781 1 : oPageContext.m_anAnnotationsId.push_back(nLinkId);
1782 : }
1783 :
1784 8 : if (bLogicalStructure)
1785 : {
1786 : oPageContext.m_osDrawingStream +=
1787 8 : CPLOPrintf("/feature <</MCID %d>> BDC\n", oPageContext.m_nMCID);
1788 : }
1789 :
1790 8 : if (bVisible || bLogicalStructure)
1791 : {
1792 8 : oPageContext.m_osDrawingStream += "q\n";
1793 8 : if (bVisible && (os.nPenA != 255 || os.nBrushA != 255))
1794 : {
1795 2 : CPLString osGSName;
1796 2 : osGSName.Printf("GS_CA_%d_ca_%d", os.nPenA, os.nBrushA);
1797 2 : if (oPageContext.m_oExtGState.find(osGSName) ==
1798 4 : oPageContext.m_oExtGState.end())
1799 : {
1800 2 : auto nExtGState = AllocNewObject();
1801 2 : StartObj(nExtGState);
1802 : {
1803 2 : GDALPDFDictionaryRW gs;
1804 : gs.Add("Type",
1805 2 : GDALPDFObjectRW::CreateName("ExtGState"));
1806 2 : if (os.nPenA != 255)
1807 1 : gs.Add("CA", (os.nPenA == 127 || os.nPenA == 128)
1808 : ? 0.5
1809 2 : : os.nPenA / 255.0);
1810 2 : if (os.nBrushA != 255)
1811 : gs.Add("ca",
1812 1 : (os.nBrushA == 127 || os.nBrushA == 128)
1813 : ? 0.5
1814 3 : : os.nBrushA / 255.0);
1815 2 : VSIFPrintfL(m_fp, "%s\n", gs.Serialize().c_str());
1816 : }
1817 2 : EndObj();
1818 2 : oPageContext.m_oExtGState[osGSName] = nExtGState;
1819 : }
1820 2 : oPageContext.m_osDrawingStream += "/" + osGSName + " gs\n";
1821 : }
1822 :
1823 : oPageContext.m_osDrawingStream +=
1824 8 : GenerateDrawingStream(hGeom, adfMatrix, os, dfRadius);
1825 :
1826 8 : oPageContext.m_osDrawingStream += "Q\n";
1827 : }
1828 :
1829 8 : if (bLogicalStructure)
1830 : {
1831 8 : oPageContext.m_osDrawingStream += "EMC\n";
1832 8 : oPageContext.m_nMCID++;
1833 : }
1834 : }
1835 :
1836 5 : if (bLogicalStructure)
1837 : {
1838 13 : for (const auto &num : anFeatureUserProperties)
1839 : {
1840 8 : oPageContext.m_anFeatureUserProperties.push_back(num);
1841 : }
1842 :
1843 : {
1844 5 : StartObj(nFeatureLayerId);
1845 :
1846 10 : GDALPDFDictionaryRW oDict;
1847 5 : GDALPDFDictionaryRW *poDictA = new GDALPDFDictionaryRW();
1848 5 : oDict.Add("A", poDictA);
1849 5 : poDictA->Add("O", GDALPDFObjectRW::CreateName("UserProperties"));
1850 5 : GDALPDFArrayRW *poArrayK = new GDALPDFArrayRW();
1851 13 : for (const auto &num : anFeatureUserProperties)
1852 8 : poArrayK->Add(num, 0);
1853 5 : oDict.Add("K", poArrayK);
1854 5 : oDict.Add("P", m_nStructTreeRootId, 0);
1855 5 : oDict.Add("S", GDALPDFObjectRW::CreateName("Layer"));
1856 :
1857 5 : const char *pszOGRDisplayName = CPLGetXMLValue(
1858 5 : psLogicalStructure, "displayLayerName", poLayer->GetName());
1859 5 : oDict.Add("T", pszOGRDisplayName);
1860 :
1861 5 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
1862 :
1863 5 : EndObj();
1864 : }
1865 : }
1866 :
1867 5 : if (!bVisible)
1868 : {
1869 2 : oPageContext.m_osDrawingStream += "Q\n";
1870 : }
1871 : else
1872 : {
1873 3 : EndBlending(psNode, oPageContext);
1874 : }
1875 :
1876 5 : return true;
1877 : }
1878 :
1879 : /************************************************************************/
1880 : /* WriteVectorLabel() */
1881 : /************************************************************************/
1882 :
1883 3 : bool GDALPDFComposerWriter::WriteVectorLabel(const CPLXMLNode *psNode,
1884 : PageContext &oPageContext)
1885 : {
1886 3 : const char *pszDataset = CPLGetXMLValue(psNode, "dataset", nullptr);
1887 3 : if (!pszDataset)
1888 : {
1889 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing dataset");
1890 0 : return false;
1891 : }
1892 3 : const char *pszLayer = CPLGetXMLValue(psNode, "layer", nullptr);
1893 3 : if (!pszLayer)
1894 : {
1895 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing layer");
1896 0 : return false;
1897 : }
1898 :
1899 : GDALDatasetUniquePtr poDS(
1900 : GDALDataset::Open(pszDataset, GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR,
1901 6 : nullptr, nullptr, nullptr));
1902 3 : if (!poDS)
1903 0 : return false;
1904 3 : OGRLayer *poLayer = poDS->GetLayerByName(pszLayer);
1905 3 : if (!poLayer)
1906 : {
1907 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannt find layer %s", pszLayer);
1908 0 : return false;
1909 : }
1910 :
1911 : const char *pszStyleString =
1912 3 : CPLGetXMLValue(psNode, "ogrStyleString", nullptr);
1913 :
1914 3 : double dfOpacityFactor = 1.0;
1915 3 : StartBlending(psNode, oPageContext, dfOpacityFactor);
1916 :
1917 : const char *pszGeoreferencingId =
1918 3 : CPLGetXMLValue(psNode, "georeferencingId", nullptr);
1919 3 : std::unique_ptr<OGRCoordinateTransformation> poCT;
1920 3 : double dfClippingMinX = 0;
1921 3 : double dfClippingMinY = 0;
1922 3 : double dfClippingMaxX = 0;
1923 3 : double dfClippingMaxY = 0;
1924 3 : double adfMatrix[4] = {0, 1, 0, 1};
1925 5 : if (pszGeoreferencingId &&
1926 2 : !SetupVectorGeoreferencing(
1927 : pszGeoreferencingId, poLayer, oPageContext, dfClippingMinX,
1928 : dfClippingMinY, dfClippingMaxX, dfClippingMaxY, adfMatrix, poCT))
1929 : {
1930 0 : return false;
1931 : }
1932 :
1933 7 : for (auto &&poFeature : poLayer)
1934 : {
1935 4 : auto hFeat = OGRFeature::ToHandle(poFeature.get());
1936 4 : auto hGeom = OGR_F_GetGeometryRef(hFeat);
1937 4 : if (!hGeom || OGR_G_IsEmpty(hGeom))
1938 1 : continue;
1939 4 : if (poCT)
1940 : {
1941 2 : if (OGRGeometry::FromHandle(hGeom)->transform(poCT.get()) !=
1942 : OGRERR_NONE)
1943 1 : continue;
1944 :
1945 2 : OGREnvelope sEnvelope;
1946 2 : OGR_G_GetEnvelope(hGeom, &sEnvelope);
1947 2 : if (sEnvelope.MinX > dfClippingMaxX ||
1948 2 : sEnvelope.MaxX < dfClippingMinX ||
1949 1 : sEnvelope.MinY > dfClippingMaxY ||
1950 1 : sEnvelope.MaxY < dfClippingMinY)
1951 : {
1952 1 : continue;
1953 : }
1954 : }
1955 :
1956 6 : ObjectStyle os;
1957 3 : GetObjectStyle(pszStyleString, hFeat, adfMatrix,
1958 3 : m_oMapSymbolFilenameToDesc, os);
1959 3 : os.nPenA = static_cast<int>(std::round(os.nPenA * dfOpacityFactor));
1960 3 : os.nBrushA = static_cast<int>(std::round(os.nBrushA * dfOpacityFactor));
1961 :
1962 6 : if (!os.osLabelText.empty() &&
1963 3 : wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint)
1964 : {
1965 : auto nObjectId = WriteLabel(hGeom, adfMatrix, os,
1966 : oPageContext.m_eStreamCompressMethod, 0,
1967 : 0, oPageContext.m_dfWidthInUserUnit,
1968 3 : oPageContext.m_dfHeightInUserUnit);
1969 : oPageContext.m_osDrawingStream +=
1970 3 : CPLOPrintf("/Label%d Do\n", nObjectId.toInt());
1971 3 : oPageContext.m_oXObjects[CPLOPrintf("Label%d", nObjectId.toInt())] =
1972 : nObjectId;
1973 : }
1974 : }
1975 :
1976 3 : EndBlending(psNode, oPageContext);
1977 :
1978 3 : return true;
1979 : }
1980 :
1981 : #ifdef HAVE_PDF_READ_SUPPORT
1982 :
1983 : /************************************************************************/
1984 : /* EmitNewObject() */
1985 : /************************************************************************/
1986 :
1987 : GDALPDFObjectNum
1988 10 : GDALPDFComposerWriter::EmitNewObject(GDALPDFObject *poObj,
1989 : RemapType &oRemapObjectRefs)
1990 : {
1991 10 : auto nId = AllocNewObject();
1992 10 : const auto nRefNum = poObj->GetRefNum();
1993 10 : if (nRefNum.toBool())
1994 : {
1995 10 : int nRefGen = poObj->GetRefGen();
1996 10 : std::pair<int, int> oKey(nRefNum.toInt(), nRefGen);
1997 10 : oRemapObjectRefs[oKey] = nId;
1998 : }
1999 20 : CPLString osStr;
2000 10 : if (!SerializeAndRenumberIgnoreRef(osStr, poObj, oRemapObjectRefs))
2001 0 : return GDALPDFObjectNum();
2002 10 : StartObj(nId);
2003 10 : VSIFWriteL(osStr.data(), 1, osStr.size(), m_fp);
2004 10 : VSIFPrintfL(m_fp, "\n");
2005 10 : EndObj();
2006 10 : return nId;
2007 : }
2008 :
2009 : /************************************************************************/
2010 : /* SerializeAndRenumber() */
2011 : /************************************************************************/
2012 :
2013 40 : bool GDALPDFComposerWriter::SerializeAndRenumber(CPLString &osStr,
2014 : GDALPDFObject *poObj,
2015 : RemapType &oRemapObjectRefs)
2016 : {
2017 40 : auto nRefNum = poObj->GetRefNum();
2018 40 : if (nRefNum.toBool())
2019 : {
2020 6 : int nRefGen = poObj->GetRefGen();
2021 :
2022 6 : std::pair<int, int> oKey(nRefNum.toInt(), nRefGen);
2023 6 : auto oIter = oRemapObjectRefs.find(oKey);
2024 6 : if (oIter != oRemapObjectRefs.end())
2025 : {
2026 0 : osStr.append(CPLSPrintf("%d 0 R", oIter->second.toInt()));
2027 0 : return true;
2028 : }
2029 : else
2030 : {
2031 6 : auto nId = EmitNewObject(poObj, oRemapObjectRefs);
2032 6 : osStr.append(CPLSPrintf("%d 0 R", nId.toInt()));
2033 6 : return nId.toBool();
2034 : }
2035 : }
2036 : else
2037 : {
2038 34 : return SerializeAndRenumberIgnoreRef(osStr, poObj, oRemapObjectRefs);
2039 : }
2040 : }
2041 :
2042 : /************************************************************************/
2043 : /* SerializeAndRenumberIgnoreRef() */
2044 : /************************************************************************/
2045 :
2046 44 : bool GDALPDFComposerWriter::SerializeAndRenumberIgnoreRef(
2047 : CPLString &osStr, GDALPDFObject *poObj, RemapType &oRemapObjectRefs)
2048 : {
2049 44 : switch (poObj->GetType())
2050 : {
2051 0 : case PDFObjectType_Array:
2052 : {
2053 0 : auto poArray = poObj->GetArray();
2054 0 : int nLength = poArray->GetLength();
2055 0 : osStr.append("[ ");
2056 0 : for (int i = 0; i < nLength; i++)
2057 : {
2058 0 : if (!SerializeAndRenumber(osStr, poArray->Get(i),
2059 : oRemapObjectRefs))
2060 0 : return false;
2061 0 : osStr.append(" ");
2062 : }
2063 0 : osStr.append("]");
2064 0 : break;
2065 : }
2066 12 : case PDFObjectType_Dictionary:
2067 : {
2068 12 : osStr.append("<< ");
2069 12 : auto poDict = poObj->GetDictionary();
2070 12 : auto &oMap = poDict->GetValues();
2071 52 : for (const auto &oIter : oMap)
2072 : {
2073 40 : const char *pszKey = oIter.first.c_str();
2074 40 : GDALPDFObject *poSubObj = oIter.second;
2075 40 : osStr.append("/");
2076 40 : osStr.append(pszKey);
2077 40 : osStr.append(" ");
2078 40 : if (!SerializeAndRenumber(osStr, poSubObj, oRemapObjectRefs))
2079 0 : return false;
2080 40 : osStr.append(" ");
2081 : }
2082 12 : osStr.append(">>");
2083 12 : auto poStream = poObj->GetStream();
2084 12 : if (poStream)
2085 : {
2086 : // CPLAssert( poObj->GetRefNum().toBool() ); // should be a top
2087 : // level object
2088 4 : osStr.append("\nstream\n");
2089 4 : auto pRawBytes = poStream->GetRawBytes();
2090 4 : if (!pRawBytes)
2091 : {
2092 0 : CPLError(CE_Failure, CPLE_AppDefined,
2093 : "Cannot get stream content");
2094 0 : return false;
2095 : }
2096 : osStr.append(pRawBytes,
2097 4 : static_cast<size_t>(poStream->GetRawLength()));
2098 4 : VSIFree(pRawBytes);
2099 4 : osStr.append("\nendstream\n");
2100 : }
2101 12 : break;
2102 : }
2103 0 : case PDFObjectType_Unknown:
2104 : {
2105 0 : CPLError(CE_Failure, CPLE_AppDefined, "Corrupted PDF");
2106 0 : return false;
2107 : }
2108 32 : default:
2109 : {
2110 32 : poObj->Serialize(osStr, false);
2111 32 : break;
2112 : }
2113 : }
2114 44 : return true;
2115 : }
2116 :
2117 : /************************************************************************/
2118 : /* SerializeAndRenumber() */
2119 : /************************************************************************/
2120 :
2121 : GDALPDFObjectNum
2122 4 : GDALPDFComposerWriter::SerializeAndRenumber(GDALPDFObject *poObj)
2123 : {
2124 8 : RemapType oRemapObjectRefs;
2125 8 : return EmitNewObject(poObj, oRemapObjectRefs);
2126 : }
2127 :
2128 : /************************************************************************/
2129 : /* WritePDF() */
2130 : /************************************************************************/
2131 :
2132 12 : bool GDALPDFComposerWriter::WritePDF(const CPLXMLNode *psNode,
2133 : PageContext &oPageContext)
2134 : {
2135 12 : const char *pszDataset = CPLGetXMLValue(psNode, "dataset", nullptr);
2136 12 : if (!pszDataset)
2137 : {
2138 2 : CPLError(CE_Failure, CPLE_AppDefined, "Missing dataset");
2139 2 : return false;
2140 : }
2141 :
2142 20 : GDALOpenInfo oOpenInfo(pszDataset, GA_ReadOnly);
2143 20 : std::unique_ptr<PDFDataset> poDS(PDFDataset::Open(&oOpenInfo));
2144 10 : if (!poDS)
2145 : {
2146 2 : CPLError(CE_Failure, CPLE_OpenFailed, "%s is not a valid PDF file",
2147 : pszDataset);
2148 2 : return false;
2149 : }
2150 16 : if (poDS->GetPageWidth() != oPageContext.m_dfWidthInUserUnit ||
2151 8 : poDS->GetPageHeight() != oPageContext.m_dfHeightInUserUnit)
2152 : {
2153 0 : CPLError(CE_Warning, CPLE_AppDefined,
2154 : "Dimensions of the inserted PDF page are %fx%f, which is "
2155 : "different from the output PDF page %fx%f",
2156 : poDS->GetPageWidth(), poDS->GetPageHeight(),
2157 : oPageContext.m_dfWidthInUserUnit,
2158 : oPageContext.m_dfHeightInUserUnit);
2159 : }
2160 8 : auto poPageObj = poDS->GetPageObj();
2161 8 : if (!poPageObj)
2162 0 : return false;
2163 8 : auto poPageDict = poPageObj->GetDictionary();
2164 8 : if (!poPageDict)
2165 0 : return false;
2166 8 : auto poContents = poPageDict->Get("Contents");
2167 8 : if (poContents != nullptr && poContents->GetType() == PDFObjectType_Array)
2168 : {
2169 0 : GDALPDFArray *poContentsArray = poContents->GetArray();
2170 0 : if (poContentsArray->GetLength() == 1)
2171 : {
2172 0 : poContents = poContentsArray->Get(0);
2173 : }
2174 : }
2175 14 : if (poContents == nullptr ||
2176 6 : poContents->GetType() != PDFObjectType_Dictionary)
2177 : {
2178 2 : CPLError(CE_Failure, CPLE_AppDefined, "Missing Contents");
2179 2 : return false;
2180 : }
2181 :
2182 6 : auto poResources = poPageDict->Get("Resources");
2183 6 : if (!poResources)
2184 : {
2185 2 : CPLError(CE_Failure, CPLE_AppDefined, "Missing Resources");
2186 2 : return false;
2187 : }
2188 :
2189 : // Serialize and renumber the Page Resources dictionary
2190 4 : auto nClonedResources = SerializeAndRenumber(poResources);
2191 4 : if (!nClonedResources.toBool())
2192 : {
2193 0 : return false;
2194 : }
2195 :
2196 : // Create a Transparency group using cloned Page Resources, and
2197 : // the Page Contents stream
2198 4 : auto nFormId = AllocNewObject();
2199 8 : GDALPDFDictionaryRW oDictGroup;
2200 4 : GDALPDFDictionaryRW *poGroup = new GDALPDFDictionaryRW();
2201 4 : poGroup->Add("Type", GDALPDFObjectRW::CreateName("Group"))
2202 4 : .Add("S", GDALPDFObjectRW::CreateName("Transparency"));
2203 :
2204 4 : oDictGroup.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
2205 4 : .Add("BBox", &((new GDALPDFArrayRW())->Add(0).Add(0))
2206 4 : .Add(oPageContext.m_dfWidthInUserUnit)
2207 4 : .Add(oPageContext.m_dfHeightInUserUnit))
2208 4 : .Add("Subtype", GDALPDFObjectRW::CreateName("Form"))
2209 4 : .Add("Group", poGroup)
2210 4 : .Add("Resources", nClonedResources, 0);
2211 :
2212 4 : auto poStream = poContents->GetStream();
2213 4 : if (!poStream)
2214 : {
2215 2 : CPLError(CE_Failure, CPLE_AppDefined, "Missing Contents stream");
2216 2 : return false;
2217 : }
2218 2 : auto pabyContents = poStream->GetBytes();
2219 2 : if (!pabyContents)
2220 : {
2221 0 : return false;
2222 : }
2223 2 : const auto nContentsLength = poStream->GetLength();
2224 :
2225 2 : StartObjWithStream(nFormId, oDictGroup,
2226 2 : oPageContext.m_eStreamCompressMethod != COMPRESS_NONE);
2227 2 : VSIFWriteL(pabyContents, 1, static_cast<size_t>(nContentsLength), m_fp);
2228 2 : VSIFree(pabyContents);
2229 2 : EndObjWithStream();
2230 :
2231 : // Paint the transparency group
2232 : double dfIgnoredOpacity;
2233 2 : StartBlending(psNode, oPageContext, dfIgnoredOpacity);
2234 :
2235 : oPageContext.m_osDrawingStream +=
2236 2 : CPLOPrintf("/Form%d Do\n", nFormId.toInt());
2237 2 : oPageContext.m_oXObjects[CPLOPrintf("Form%d", nFormId.toInt())] = nFormId;
2238 :
2239 2 : EndBlending(psNode, oPageContext);
2240 :
2241 2 : return true;
2242 : }
2243 :
2244 : #endif // HAVE_PDF_READ_SUPPORT
2245 :
2246 : /************************************************************************/
2247 : /* Generate() */
2248 : /************************************************************************/
2249 :
2250 38 : bool GDALPDFComposerWriter::Generate(const CPLXMLNode *psComposition)
2251 : {
2252 38 : m_osJPEG2000Driver = CPLGetXMLValue(psComposition, "JPEG2000Driver", "");
2253 :
2254 38 : auto psMetadata = CPLGetXMLNode(psComposition, "Metadata");
2255 38 : if (psMetadata)
2256 : {
2257 : SetInfo(CPLGetXMLValue(psMetadata, "Author", nullptr),
2258 : CPLGetXMLValue(psMetadata, "Producer", nullptr),
2259 : CPLGetXMLValue(psMetadata, "Creator", nullptr),
2260 : CPLGetXMLValue(psMetadata, "CreationDate", nullptr),
2261 : CPLGetXMLValue(psMetadata, "Subject", nullptr),
2262 : CPLGetXMLValue(psMetadata, "Title", nullptr),
2263 1 : CPLGetXMLValue(psMetadata, "Keywords", nullptr));
2264 1 : SetXMP(nullptr, CPLGetXMLValue(psMetadata, "XMP", nullptr));
2265 : }
2266 :
2267 : const char *pszJavascript =
2268 38 : CPLGetXMLValue(psComposition, "Javascript", nullptr);
2269 38 : if (pszJavascript)
2270 1 : WriteJavascript(pszJavascript, false);
2271 :
2272 38 : auto psLayerTree = CPLGetXMLNode(psComposition, "LayerTree");
2273 38 : if (psLayerTree)
2274 : {
2275 7 : m_bDisplayLayersOnlyOnVisiblePages = CPLTestBool(
2276 : CPLGetXMLValue(psLayerTree, "displayOnlyOnVisiblePages", "false"));
2277 7 : if (!CreateLayerTree(psLayerTree, GDALPDFObjectNum(), &m_oTreeOfOGC))
2278 3 : return false;
2279 : }
2280 :
2281 35 : bool bFoundPage = false;
2282 63 : for (const auto *psIter = psComposition->psChild; psIter;
2283 28 : psIter = psIter->psNext)
2284 : {
2285 48 : if (psIter->eType == CXT_Element &&
2286 48 : strcmp(psIter->pszValue, "Page") == 0)
2287 : {
2288 37 : if (!GeneratePage(psIter))
2289 20 : return false;
2290 17 : bFoundPage = true;
2291 : }
2292 : }
2293 15 : if (!bFoundPage)
2294 : {
2295 1 : CPLError(CE_Failure, CPLE_AppDefined,
2296 : "At least one page should be defined");
2297 1 : return false;
2298 : }
2299 :
2300 14 : auto psOutline = CPLGetXMLNode(psComposition, "Outline");
2301 14 : if (psOutline)
2302 : {
2303 5 : if (!CreateOutline(psOutline))
2304 4 : return false;
2305 : }
2306 :
2307 10 : return true;
2308 : }
2309 :
2310 : /************************************************************************/
2311 : /* GDALPDFErrorHandler() */
2312 : /************************************************************************/
2313 :
2314 19 : static void CPL_STDCALL GDALPDFErrorHandler(CPL_UNUSED CPLErr eErr,
2315 : CPL_UNUSED CPLErrorNum nType,
2316 : const char *pszMsg)
2317 : {
2318 : std::vector<CPLString> *paosErrors =
2319 19 : static_cast<std::vector<CPLString> *>(CPLGetErrorHandlerUserData());
2320 19 : paosErrors->push_back(pszMsg);
2321 19 : }
2322 :
2323 : /************************************************************************/
2324 : /* GDALPDFCreateFromCompositionFile() */
2325 : /************************************************************************/
2326 :
2327 39 : GDALDataset *GDALPDFCreateFromCompositionFile(const char *pszPDFFilename,
2328 : const char *pszXMLFilename)
2329 : {
2330 76 : CPLXMLTreeCloser oXML((pszXMLFilename[0] == '<' &&
2331 37 : strstr(pszXMLFilename, "<PDFComposition") != nullptr)
2332 37 : ? CPLParseXMLString(pszXMLFilename)
2333 115 : : CPLParseXMLFile(pszXMLFilename));
2334 39 : if (!oXML.get())
2335 1 : return nullptr;
2336 38 : auto psComposition = CPLGetXMLNode(oXML.get(), "=PDFComposition");
2337 38 : if (!psComposition)
2338 : {
2339 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find PDFComposition");
2340 0 : return nullptr;
2341 : }
2342 :
2343 : // XML Validation.
2344 38 : if (CPLTestBool(CPLGetConfigOption("GDAL_XML_VALIDATION", "YES")))
2345 : {
2346 : #ifdef EMBED_RESOURCE_FILES
2347 : std::string osTmpFilename;
2348 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
2349 : #endif
2350 : #ifdef USE_ONLY_EMBEDDED_RESOURCE_FILES
2351 : const char *pszXSD = nullptr;
2352 : #else
2353 38 : const char *pszXSD = CPLFindFile("gdal", "pdfcomposition.xsd");
2354 : #endif
2355 : #ifdef EMBED_RESOURCE_FILES
2356 : if (!pszXSD)
2357 : {
2358 : static const bool bOnce [[maybe_unused]] = []()
2359 : {
2360 : CPLDebug("PDF", "Using embedded pdfcomposition.xsd");
2361 : return true;
2362 : }();
2363 : osTmpFilename = VSIMemGenerateHiddenFilename("pdfcomposition.xsd");
2364 : pszXSD = osTmpFilename.c_str();
2365 : VSIFCloseL(VSIFileFromMemBuffer(
2366 : osTmpFilename.c_str(),
2367 : const_cast<GByte *>(
2368 : reinterpret_cast<const GByte *>(PDFGetCompositionXSD())),
2369 : static_cast<int>(strlen(PDFGetCompositionXSD())),
2370 : /* bTakeOwnership = */ false));
2371 : }
2372 : #else
2373 38 : if (pszXSD != nullptr)
2374 : #endif
2375 : {
2376 76 : std::vector<CPLString> aosErrors;
2377 38 : CPLPushErrorHandlerEx(GDALPDFErrorHandler, &aosErrors);
2378 38 : const int bRet = CPLValidateXML(pszXMLFilename, pszXSD, nullptr);
2379 38 : CPLPopErrorHandler();
2380 38 : if (!bRet)
2381 : {
2382 36 : if (!aosErrors.empty() &&
2383 18 : strstr(aosErrors[0].c_str(), "missing libxml2 support") ==
2384 : nullptr)
2385 : {
2386 37 : for (size_t i = 0; i < aosErrors.size(); i++)
2387 : {
2388 19 : CPLError(CE_Warning, CPLE_AppDefined, "%s",
2389 19 : aosErrors[i].c_str());
2390 : }
2391 : }
2392 : }
2393 38 : CPLErrorReset();
2394 : }
2395 :
2396 : #ifdef EMBED_RESOURCE_FILES
2397 : if (!osTmpFilename.empty())
2398 : VSIUnlink(osTmpFilename.c_str());
2399 : #endif
2400 : }
2401 :
2402 : /* -------------------------------------------------------------------- */
2403 : /* Create file. */
2404 : /* -------------------------------------------------------------------- */
2405 38 : VSILFILE *fp = VSIFOpenL(pszPDFFilename, "wb");
2406 38 : if (fp == nullptr)
2407 : {
2408 0 : CPLError(CE_Failure, CPLE_OpenFailed, "Unable to create PDF file %s.\n",
2409 : pszPDFFilename);
2410 0 : return nullptr;
2411 : }
2412 :
2413 76 : GDALPDFComposerWriter oWriter(fp);
2414 38 : if (!oWriter.Generate(psComposition))
2415 28 : return nullptr;
2416 :
2417 10 : return new GDALFakePDFDataset();
2418 : }
|