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