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 : OGRGeometry *poGeom = nullptr;
687 2 : OGRGeometryFactory::createFromWkt(pszBoundingPolygon, nullptr, &poGeom);
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 2 : delete poGeom;
711 : }
712 :
713 5 : const auto pszSRS = CPLGetXMLValue(psGeoreferencing, "SRS", nullptr);
714 5 : if (!pszSRS)
715 : {
716 1 : CPLError(CE_Failure, CPLE_NotSupported, "Missing SRS");
717 1 : return false;
718 : }
719 8 : auto poSRS = std::make_unique<OGRSpatialReference>();
720 4 : if (poSRS->SetFromUserInput(pszSRS) != OGRERR_NONE)
721 : {
722 0 : return false;
723 : }
724 4 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
725 :
726 4 : if (CPLTestBool(CPLGetXMLValue(psGeoreferencing, "ISO32000ExtensionFormat",
727 : "true")))
728 : {
729 : nViewportId = GenerateISO32000_Georeferencing(
730 : OGRSpatialReference::ToHandle(poSRS.get()), bboxX1, bboxY1, bboxX2,
731 4 : bboxY2, aGCPs, aBoundingPolygon);
732 4 : if (!nViewportId.toBool())
733 : {
734 0 : return false;
735 : }
736 : }
737 :
738 4 : if (CPLTestBool(
739 : CPLGetXMLValue(psGeoreferencing, "OGCBestPracticeFormat", "false")))
740 : {
741 0 : CPLError(CE_Failure, CPLE_NotSupported,
742 : "OGCBestPracticeFormat no longer supported. Use "
743 : "ISO32000ExtensionFormat");
744 0 : return false;
745 : }
746 :
747 4 : const char *pszId = CPLGetXMLValue(psGeoreferencing, "id", nullptr);
748 4 : if (pszId)
749 : {
750 3 : if (!GDALGCPsToGeoTransform(static_cast<int>(aGCPs.size()),
751 : gdal::GCP::c_ptr(aGCPs),
752 3 : georeferencing.m_adfGT, TRUE))
753 : {
754 0 : CPLError(CE_Failure, CPLE_AppDefined,
755 : "Could not compute geotransform with approximate match.");
756 0 : return false;
757 : }
758 3 : if (std::fabs(georeferencing.m_adfGT[2]) <
759 3 : 1e-5 * std::fabs(georeferencing.m_adfGT[1]) &&
760 3 : std::fabs(georeferencing.m_adfGT[4]) <
761 3 : 1e-5 * std::fabs(georeferencing.m_adfGT[5]))
762 : {
763 3 : georeferencing.m_adfGT[2] = 0;
764 3 : georeferencing.m_adfGT[4] = 0;
765 : }
766 :
767 3 : georeferencing.m_osID = pszId;
768 3 : georeferencing.m_oSRS = *(poSRS.get());
769 3 : georeferencing.m_bboxX1 = bboxX1;
770 3 : georeferencing.m_bboxY1 = bboxY1;
771 3 : georeferencing.m_bboxX2 = bboxX2;
772 3 : georeferencing.m_bboxY2 = bboxY2;
773 : }
774 :
775 4 : return true;
776 : }
777 :
778 : /************************************************************************/
779 : /* GenerateISO32000_Georeferencing() */
780 : /************************************************************************/
781 :
782 4 : GDALPDFObjectNum GDALPDFComposerWriter::GenerateISO32000_Georeferencing(
783 : OGRSpatialReferenceH hSRS, double bboxX1, double bboxY1, double bboxX2,
784 : double bboxY2, const std::vector<gdal::GCP> &aGCPs,
785 : const std::vector<xyPair> &aBoundingPolygon)
786 : {
787 4 : OGRSpatialReferenceH hSRSGeog = OSRCloneGeogCS(hSRS);
788 4 : if (hSRSGeog == nullptr)
789 : {
790 0 : return GDALPDFObjectNum();
791 : }
792 4 : OSRSetAxisMappingStrategy(hSRSGeog, OAMS_TRADITIONAL_GIS_ORDER);
793 : OGRCoordinateTransformationH hCT =
794 4 : OCTNewCoordinateTransformation(hSRS, hSRSGeog);
795 4 : if (hCT == nullptr)
796 : {
797 0 : OSRDestroySpatialReference(hSRSGeog);
798 0 : return GDALPDFObjectNum();
799 : }
800 :
801 8 : std::vector<gdal::GCP> aGCPReprojected;
802 4 : bool bSuccess = true;
803 20 : for (const auto &gcp : aGCPs)
804 : {
805 16 : double X = gcp.X();
806 16 : double Y = gcp.Y();
807 16 : bSuccess &= OCTTransform(hCT, 1, &X, &Y, nullptr) == 1;
808 32 : aGCPReprojected.emplace_back(nullptr, nullptr, gcp.Pixel(), gcp.Line(),
809 16 : X, Y);
810 : }
811 4 : if (!bSuccess)
812 : {
813 0 : OSRDestroySpatialReference(hSRSGeog);
814 0 : OCTDestroyCoordinateTransformation(hCT);
815 :
816 0 : return GDALPDFObjectNum();
817 : }
818 :
819 4 : const char *pszAuthorityCode = OSRGetAuthorityCode(hSRS, nullptr);
820 4 : const char *pszAuthorityName = OSRGetAuthorityName(hSRS, nullptr);
821 4 : int nEPSGCode = 0;
822 4 : if (pszAuthorityName != nullptr && EQUAL(pszAuthorityName, "EPSG") &&
823 : pszAuthorityCode != nullptr)
824 4 : nEPSGCode = atoi(pszAuthorityCode);
825 :
826 4 : int bIsGeographic = OSRIsGeographic(hSRS);
827 :
828 4 : char *pszESRIWKT = nullptr;
829 4 : const char *apszOptions[] = {"FORMAT=WKT1_ESRI", nullptr};
830 4 : OSRExportToWktEx(hSRS, &pszESRIWKT, apszOptions);
831 :
832 4 : OSRDestroySpatialReference(hSRSGeog);
833 4 : OCTDestroyCoordinateTransformation(hCT);
834 :
835 4 : auto nViewportId = AllocNewObject();
836 4 : auto nMeasureId = AllocNewObject();
837 4 : auto nGCSId = AllocNewObject();
838 :
839 4 : StartObj(nViewportId);
840 8 : GDALPDFDictionaryRW oViewPortDict;
841 4 : oViewPortDict.Add("Type", GDALPDFObjectRW::CreateName("Viewport"))
842 4 : .Add("Name", "Layer")
843 4 : .Add("BBox", &((new GDALPDFArrayRW())
844 4 : ->Add(bboxX1)
845 4 : .Add(bboxY1)
846 4 : .Add(bboxX2)
847 4 : .Add(bboxY2)))
848 4 : .Add("Measure", nMeasureId, 0);
849 4 : VSIFPrintfL(m_fp, "%s\n", oViewPortDict.Serialize().c_str());
850 4 : EndObj();
851 :
852 4 : GDALPDFArrayRW *poGPTS = new GDALPDFArrayRW();
853 4 : GDALPDFArrayRW *poLPTS = new GDALPDFArrayRW();
854 :
855 : const int nPrecision =
856 4 : atoi(CPLGetConfigOption("PDF_COORD_DOUBLE_PRECISION", "16"));
857 20 : for (const auto &gcp : aGCPReprojected)
858 : {
859 16 : poGPTS->AddWithPrecision(gcp.Y(), nPrecision)
860 16 : .AddWithPrecision(gcp.X(), nPrecision); // Lat, long order
861 : poLPTS
862 16 : ->AddWithPrecision((gcp.Pixel() - bboxX1) / (bboxX2 - bboxX1),
863 16 : nPrecision)
864 16 : .AddWithPrecision((gcp.Line() - bboxY1) / (bboxY2 - bboxY1),
865 16 : nPrecision);
866 : }
867 :
868 4 : StartObj(nMeasureId);
869 8 : GDALPDFDictionaryRW oMeasureDict;
870 4 : oMeasureDict.Add("Type", GDALPDFObjectRW::CreateName("Measure"))
871 4 : .Add("Subtype", GDALPDFObjectRW::CreateName("GEO"))
872 4 : .Add("GPTS", poGPTS)
873 4 : .Add("LPTS", poLPTS)
874 4 : .Add("GCS", nGCSId, 0);
875 4 : if (!aBoundingPolygon.empty())
876 : {
877 1 : GDALPDFArrayRW *poBounds = new GDALPDFArrayRW();
878 6 : for (const auto &xy : aBoundingPolygon)
879 : {
880 5 : poBounds->Add((xy.x - bboxX1) / (bboxX2 - bboxX1))
881 5 : .Add((xy.y - bboxY1) / (bboxY2 - bboxY1));
882 : }
883 1 : oMeasureDict.Add("Bounds", poBounds);
884 : }
885 4 : VSIFPrintfL(m_fp, "%s\n", oMeasureDict.Serialize().c_str());
886 4 : EndObj();
887 :
888 4 : StartObj(nGCSId);
889 4 : GDALPDFDictionaryRW oGCSDict;
890 : oGCSDict
891 : .Add("Type",
892 4 : GDALPDFObjectRW::CreateName(bIsGeographic ? "GEOGCS" : "PROJCS"))
893 4 : .Add("WKT", pszESRIWKT);
894 4 : if (nEPSGCode)
895 4 : oGCSDict.Add("EPSG", nEPSGCode);
896 4 : VSIFPrintfL(m_fp, "%s\n", oGCSDict.Serialize().c_str());
897 4 : EndObj();
898 :
899 4 : CPLFree(pszESRIWKT);
900 :
901 4 : return nViewportId;
902 : }
903 :
904 : /************************************************************************/
905 : /* GeneratePage() */
906 : /************************************************************************/
907 :
908 37 : bool GDALPDFComposerWriter::GeneratePage(const CPLXMLNode *psPage)
909 : {
910 37 : double dfWidthInUserUnit = CPLAtof(CPLGetXMLValue(psPage, "Width", "-1"));
911 37 : double dfHeightInUserUnit = CPLAtof(CPLGetXMLValue(psPage, "Height", "-1"));
912 37 : if (dfWidthInUserUnit <= 0 || dfWidthInUserUnit >= MAXIMUM_SIZE_IN_UNITS ||
913 36 : dfHeightInUserUnit <= 0 || dfHeightInUserUnit >= MAXIMUM_SIZE_IN_UNITS)
914 : {
915 1 : CPLError(CE_Failure, CPLE_AppDefined,
916 : "Missing or invalid Width and/or Height");
917 1 : return false;
918 : }
919 : double dfUserUnit =
920 36 : CPLAtof(CPLGetXMLValue(psPage, "DPI", CPLSPrintf("%f", DEFAULT_DPI))) *
921 36 : USER_UNIT_IN_INCH;
922 :
923 72 : std::vector<GDALPDFObjectNum> anViewportIds;
924 :
925 72 : PageContext oPageContext;
926 149 : for (const auto *psIter = psPage->psChild; psIter; psIter = psIter->psNext)
927 : {
928 117 : if (psIter->eType == CXT_Element &&
929 109 : strcmp(psIter->pszValue, "Georeferencing") == 0)
930 : {
931 8 : GDALPDFObjectNum nViewportId;
932 8 : Georeferencing georeferencing;
933 8 : if (!GenerateGeoreferencing(psIter, dfWidthInUserUnit,
934 : dfHeightInUserUnit, nViewportId,
935 : georeferencing))
936 : {
937 4 : return false;
938 : }
939 4 : if (nViewportId.toBool())
940 4 : anViewportIds.emplace_back(nViewportId);
941 4 : if (!georeferencing.m_osID.empty())
942 : {
943 3 : oPageContext.m_oMapGeoreferencedId[georeferencing.m_osID] =
944 3 : georeferencing;
945 : }
946 : }
947 : }
948 :
949 32 : auto nPageId = AllocNewObject();
950 32 : m_asPageId.push_back(nPageId);
951 :
952 32 : const char *pszId = CPLGetXMLValue(psPage, "id", nullptr);
953 32 : if (pszId)
954 : {
955 8 : if (m_oMapPageIdToObjectNum.find(pszId) !=
956 16 : m_oMapPageIdToObjectNum.end())
957 : {
958 1 : CPLError(CE_Failure, CPLE_AppDefined, "Duplicated page id %s",
959 : pszId);
960 1 : return false;
961 : }
962 7 : m_oMapPageIdToObjectNum[pszId] = nPageId;
963 : }
964 :
965 31 : const auto psContent = CPLGetXMLNode(psPage, "Content");
966 31 : if (!psContent)
967 : {
968 1 : CPLError(CE_Failure, CPLE_AppDefined, "Missing Content");
969 1 : return false;
970 : }
971 :
972 30 : const bool bDeflateStreamCompression = EQUAL(
973 : CPLGetXMLValue(psContent, "streamCompression", "DEFLATE"), "DEFLATE");
974 :
975 30 : oPageContext.m_dfWidthInUserUnit = dfWidthInUserUnit;
976 30 : oPageContext.m_dfHeightInUserUnit = dfHeightInUserUnit;
977 30 : oPageContext.m_eStreamCompressMethod =
978 30 : bDeflateStreamCompression ? COMPRESS_DEFLATE : COMPRESS_NONE;
979 30 : if (!ExploreContent(psContent, oPageContext))
980 13 : return false;
981 :
982 17 : int nStructParentsIdx = -1;
983 17 : if (!oPageContext.m_anFeatureUserProperties.empty())
984 : {
985 3 : nStructParentsIdx = static_cast<int>(m_anParentElements.size());
986 3 : auto nParentsElements = AllocNewObject();
987 3 : m_anParentElements.push_back(nParentsElements);
988 : {
989 3 : StartObj(nParentsElements);
990 3 : VSIFPrintfL(m_fp, "[ ");
991 11 : for (const auto &num : oPageContext.m_anFeatureUserProperties)
992 8 : VSIFPrintfL(m_fp, "%d 0 R ", num.toInt());
993 3 : VSIFPrintfL(m_fp, " ]\n");
994 3 : EndObj();
995 : }
996 : }
997 :
998 17 : GDALPDFObjectNum nAnnotsId;
999 17 : if (!oPageContext.m_anAnnotationsId.empty())
1000 : {
1001 : /* -------------------------------------------------------------- */
1002 : /* Write annotation arrays. */
1003 : /* -------------------------------------------------------------- */
1004 1 : nAnnotsId = AllocNewObject();
1005 1 : StartObj(nAnnotsId);
1006 : {
1007 1 : GDALPDFArrayRW oArray;
1008 2 : for (size_t i = 0; i < oPageContext.m_anAnnotationsId.size(); i++)
1009 : {
1010 1 : oArray.Add(oPageContext.m_anAnnotationsId[i], 0);
1011 : }
1012 1 : VSIFPrintfL(m_fp, "%s\n", oArray.Serialize().c_str());
1013 : }
1014 1 : EndObj();
1015 : }
1016 :
1017 17 : auto nContentId = AllocNewObject();
1018 17 : auto nResourcesId = AllocNewObject();
1019 :
1020 17 : StartObj(nPageId);
1021 17 : GDALPDFDictionaryRW oDictPage;
1022 17 : oDictPage.Add("Type", GDALPDFObjectRW::CreateName("Page"))
1023 17 : .Add("Parent", m_nPageResourceId, 0)
1024 17 : .Add("MediaBox", &((new GDALPDFArrayRW())
1025 17 : ->Add(0)
1026 17 : .Add(0)
1027 17 : .Add(dfWidthInUserUnit)
1028 17 : .Add(dfHeightInUserUnit)))
1029 17 : .Add("UserUnit", dfUserUnit)
1030 17 : .Add("Contents", nContentId, 0)
1031 17 : .Add("Resources", nResourcesId, 0);
1032 :
1033 17 : if (nAnnotsId.toBool())
1034 1 : oDictPage.Add("Annots", nAnnotsId, 0);
1035 :
1036 : oDictPage.Add("Group",
1037 17 : &((new GDALPDFDictionaryRW())
1038 17 : ->Add("Type", GDALPDFObjectRW::CreateName("Group"))
1039 17 : .Add("S", GDALPDFObjectRW::CreateName("Transparency"))
1040 17 : .Add("CS", GDALPDFObjectRW::CreateName("DeviceRGB"))));
1041 17 : if (!anViewportIds.empty())
1042 : {
1043 4 : auto poViewports = new GDALPDFArrayRW();
1044 8 : for (const auto &id : anViewportIds)
1045 4 : poViewports->Add(id, 0);
1046 4 : oDictPage.Add("VP", poViewports);
1047 : }
1048 :
1049 17 : if (nStructParentsIdx >= 0)
1050 : {
1051 3 : oDictPage.Add("StructParents", nStructParentsIdx);
1052 : }
1053 :
1054 17 : VSIFPrintfL(m_fp, "%s\n", oDictPage.Serialize().c_str());
1055 17 : EndObj();
1056 :
1057 : /* -------------------------------------------------------------- */
1058 : /* Write content dictionary */
1059 : /* -------------------------------------------------------------- */
1060 : {
1061 34 : GDALPDFDictionaryRW oDict;
1062 17 : StartObjWithStream(nContentId, oDict, bDeflateStreamCompression);
1063 17 : VSIFPrintfL(m_fp, "%s", oPageContext.m_osDrawingStream.c_str());
1064 17 : EndObjWithStream();
1065 : }
1066 :
1067 : /* -------------------------------------------------------------- */
1068 : /* Write page resource dictionary. */
1069 : /* -------------------------------------------------------------- */
1070 17 : StartObj(nResourcesId);
1071 : {
1072 17 : GDALPDFDictionaryRW oDict;
1073 17 : if (!oPageContext.m_oXObjects.empty())
1074 : {
1075 8 : GDALPDFDictionaryRW *poDict = new GDALPDFDictionaryRW();
1076 18 : for (const auto &kv : oPageContext.m_oXObjects)
1077 : {
1078 10 : poDict->Add(kv.first, kv.second, 0);
1079 : }
1080 8 : oDict.Add("XObject", poDict);
1081 : }
1082 :
1083 17 : if (!oPageContext.m_oProperties.empty())
1084 : {
1085 3 : GDALPDFDictionaryRW *poDict = new GDALPDFDictionaryRW();
1086 6 : for (const auto &kv : oPageContext.m_oProperties)
1087 : {
1088 3 : poDict->Add(kv.first, kv.second, 0);
1089 : }
1090 3 : oDict.Add("Properties", poDict);
1091 : }
1092 :
1093 17 : if (!oPageContext.m_oExtGState.empty())
1094 : {
1095 5 : GDALPDFDictionaryRW *poDict = new GDALPDFDictionaryRW();
1096 13 : for (const auto &kv : oPageContext.m_oExtGState)
1097 : {
1098 8 : poDict->Add(kv.first, kv.second, 0);
1099 : }
1100 5 : oDict.Add("ExtGState", poDict);
1101 : }
1102 :
1103 17 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
1104 : }
1105 17 : EndObj();
1106 :
1107 17 : return true;
1108 : }
1109 :
1110 : /************************************************************************/
1111 : /* ExploreContent() */
1112 : /************************************************************************/
1113 :
1114 33 : bool GDALPDFComposerWriter::ExploreContent(const CPLXMLNode *psNode,
1115 : PageContext &oPageContext)
1116 : {
1117 70 : for (const auto *psIter = psNode->psChild; psIter; psIter = psIter->psNext)
1118 : {
1119 50 : if (psIter->eType == CXT_Element &&
1120 30 : strcmp(psIter->pszValue, "IfLayerOn") == 0)
1121 : {
1122 4 : const char *pszLayerId = CPLGetXMLValue(psIter, "layerId", nullptr);
1123 4 : if (!pszLayerId)
1124 : {
1125 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing layerId");
1126 1 : return false;
1127 : }
1128 4 : auto oIter = m_oMapLayerIdToOCG.find(pszLayerId);
1129 4 : if (oIter == m_oMapLayerIdToOCG.end())
1130 : {
1131 1 : CPLError(CE_Failure, CPLE_AppDefined,
1132 : "Referencing layer of unknown id: %s", pszLayerId);
1133 1 : return false;
1134 : }
1135 : oPageContext
1136 3 : .m_oProperties[CPLOPrintf("Lyr%d", oIter->second.toInt())] =
1137 3 : oIter->second;
1138 : oPageContext.m_osDrawingStream +=
1139 3 : CPLOPrintf("/OC /Lyr%d BDC\n", oIter->second.toInt());
1140 3 : if (!ExploreContent(psIter, oPageContext))
1141 0 : return false;
1142 3 : oPageContext.m_osDrawingStream += "EMC\n";
1143 : }
1144 :
1145 46 : else if (psIter->eType == CXT_Element &&
1146 26 : strcmp(psIter->pszValue, "Raster") == 0)
1147 : {
1148 6 : if (!WriteRaster(psIter, oPageContext))
1149 2 : return false;
1150 : }
1151 :
1152 40 : else if (psIter->eType == CXT_Element &&
1153 20 : strcmp(psIter->pszValue, "Vector") == 0)
1154 : {
1155 5 : if (!WriteVector(psIter, oPageContext))
1156 0 : return false;
1157 : }
1158 :
1159 35 : else if (psIter->eType == CXT_Element &&
1160 15 : strcmp(psIter->pszValue, "VectorLabel") == 0)
1161 : {
1162 3 : if (!WriteVectorLabel(psIter, oPageContext))
1163 0 : return false;
1164 : }
1165 :
1166 32 : else if (psIter->eType == CXT_Element &&
1167 12 : strcmp(psIter->pszValue, "PDF") == 0)
1168 : {
1169 : #ifdef HAVE_PDF_READ_SUPPORT
1170 12 : if (!WritePDF(psIter, oPageContext))
1171 10 : return false;
1172 : #else
1173 : CPLError(CE_Failure, CPLE_NotSupported,
1174 : "PDF node not supported due to missing PDF read support "
1175 : "in this GDAL build");
1176 : return false;
1177 : #endif
1178 : }
1179 : }
1180 20 : return true;
1181 : }
1182 :
1183 : /************************************************************************/
1184 : /* StartBlending() */
1185 : /************************************************************************/
1186 :
1187 12 : void GDALPDFComposerWriter::StartBlending(const CPLXMLNode *psNode,
1188 : PageContext &oPageContext,
1189 : double &dfOpacity)
1190 : {
1191 12 : dfOpacity = 1;
1192 12 : const auto psBlending = CPLGetXMLNode(psNode, "Blending");
1193 12 : if (psBlending)
1194 : {
1195 5 : auto nExtGState = AllocNewObject();
1196 5 : StartObj(nExtGState);
1197 : {
1198 5 : GDALPDFDictionaryRW gs;
1199 5 : gs.Add("Type", GDALPDFObjectRW::CreateName("ExtGState"));
1200 5 : dfOpacity = CPLAtof(CPLGetXMLValue(psBlending, "opacity", "1"));
1201 5 : gs.Add("ca", dfOpacity);
1202 5 : gs.Add("BM", GDALPDFObjectRW::CreateName(
1203 5 : CPLGetXMLValue(psBlending, "function", "Normal")));
1204 5 : VSIFPrintfL(m_fp, "%s\n", gs.Serialize().c_str());
1205 : }
1206 5 : EndObj();
1207 5 : oPageContext.m_oExtGState[CPLOPrintf("GS%d", nExtGState.toInt())] =
1208 : nExtGState;
1209 5 : oPageContext.m_osDrawingStream += "q\n";
1210 : oPageContext.m_osDrawingStream +=
1211 5 : CPLOPrintf("/GS%d gs\n", nExtGState.toInt());
1212 : }
1213 12 : }
1214 :
1215 : /************************************************************************/
1216 : /* EndBlending() */
1217 : /************************************************************************/
1218 :
1219 12 : void GDALPDFComposerWriter::EndBlending(const CPLXMLNode *psNode,
1220 : PageContext &oPageContext)
1221 : {
1222 12 : const auto psBlending = CPLGetXMLNode(psNode, "Blending");
1223 12 : if (psBlending)
1224 : {
1225 5 : oPageContext.m_osDrawingStream += "Q\n";
1226 : }
1227 12 : }
1228 :
1229 : /************************************************************************/
1230 : /* WriteRaster() */
1231 : /************************************************************************/
1232 :
1233 6 : bool GDALPDFComposerWriter::WriteRaster(const CPLXMLNode *psNode,
1234 : PageContext &oPageContext)
1235 : {
1236 6 : const char *pszDataset = CPLGetXMLValue(psNode, "dataset", nullptr);
1237 6 : if (!pszDataset)
1238 : {
1239 1 : CPLError(CE_Failure, CPLE_AppDefined, "Missing dataset");
1240 1 : return false;
1241 : }
1242 5 : double dfX1 = CPLAtof(CPLGetXMLValue(psNode, "x1", "0"));
1243 5 : double dfY1 = CPLAtof(CPLGetXMLValue(psNode, "y1", "0"));
1244 5 : double dfX2 = CPLAtof(CPLGetXMLValue(
1245 : psNode, "x2", CPLSPrintf("%.17g", oPageContext.m_dfWidthInUserUnit)));
1246 5 : double dfY2 = CPLAtof(CPLGetXMLValue(
1247 : psNode, "y2", CPLSPrintf("%.17g", oPageContext.m_dfHeightInUserUnit)));
1248 5 : if (dfX2 <= dfX1 || dfY2 <= dfY1)
1249 : {
1250 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid x1,y1,x2,y2");
1251 0 : return false;
1252 : }
1253 : GDALDatasetUniquePtr poDS(
1254 : GDALDataset::Open(pszDataset, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
1255 10 : nullptr, nullptr, nullptr));
1256 5 : if (!poDS)
1257 1 : return false;
1258 4 : const int nWidth = poDS->GetRasterXSize();
1259 4 : const int nHeight = poDS->GetRasterYSize();
1260 : const int nBlockXSize =
1261 4 : std::max(16, atoi(CPLGetXMLValue(psNode, "tileSize", "256")));
1262 4 : const int nBlockYSize = nBlockXSize;
1263 : const char *pszCompressMethod =
1264 4 : CPLGetXMLValue(psNode, "Compression.method", "DEFLATE");
1265 4 : PDFCompressMethod eCompressMethod = COMPRESS_DEFLATE;
1266 4 : if (EQUAL(pszCompressMethod, "JPEG"))
1267 0 : eCompressMethod = COMPRESS_JPEG;
1268 4 : else if (EQUAL(pszCompressMethod, "JPEG2000"))
1269 0 : eCompressMethod = COMPRESS_JPEG2000;
1270 : const int nPredictor =
1271 4 : CPLTestBool(CPLGetXMLValue(psNode, "Compression.predictor", "false"))
1272 4 : ? 2
1273 4 : : 0;
1274 : const int nJPEGQuality =
1275 4 : atoi(CPLGetXMLValue(psNode, "Compression.quality", "-1"));
1276 : const char *pszJPEG2000_DRIVER =
1277 4 : m_osJPEG2000Driver.empty() ? nullptr : m_osJPEG2000Driver.c_str();
1278 : ;
1279 :
1280 : const char *pszGeoreferencingId =
1281 4 : CPLGetXMLValue(psNode, "georeferencingId", nullptr);
1282 4 : double dfClippingMinX = 0;
1283 4 : double dfClippingMinY = 0;
1284 4 : double dfClippingMaxX = 0;
1285 4 : double dfClippingMaxY = 0;
1286 4 : bool bClip = false;
1287 4 : double adfRasterGT[6] = {0, 1, 0, 0, 0, 1};
1288 : double adfInvGeoreferencingGT[6]; // from georeferenced to PDF coordinates
1289 4 : if (pszGeoreferencingId)
1290 : {
1291 : auto iter =
1292 1 : oPageContext.m_oMapGeoreferencedId.find(pszGeoreferencingId);
1293 1 : if (iter == oPageContext.m_oMapGeoreferencedId.end())
1294 : {
1295 0 : CPLError(CE_Failure, CPLE_AppDefined,
1296 : "Cannot find georeferencing of id %s",
1297 : pszGeoreferencingId);
1298 0 : return false;
1299 : }
1300 1 : const auto &georeferencing = iter->second;
1301 1 : dfX1 = georeferencing.m_bboxX1;
1302 1 : dfY1 = georeferencing.m_bboxY1;
1303 1 : dfX2 = georeferencing.m_bboxX2;
1304 1 : dfY2 = georeferencing.m_bboxY2;
1305 :
1306 1 : bClip = true;
1307 1 : dfClippingMinX = APPLY_GT_X(georeferencing.m_adfGT, dfX1, dfY1);
1308 1 : dfClippingMinY = APPLY_GT_Y(georeferencing.m_adfGT, dfX1, dfY1);
1309 1 : dfClippingMaxX = APPLY_GT_X(georeferencing.m_adfGT, dfX2, dfY2);
1310 1 : dfClippingMaxY = APPLY_GT_Y(georeferencing.m_adfGT, dfX2, dfY2);
1311 :
1312 1 : if (poDS->GetGeoTransform(adfRasterGT) != CE_None ||
1313 1 : adfRasterGT[2] != 0 || adfRasterGT[4] != 0 || adfRasterGT[5] > 0)
1314 : {
1315 0 : CPLError(CE_Failure, CPLE_AppDefined,
1316 : "Raster has no geotransform or a rotated geotransform");
1317 0 : return false;
1318 : }
1319 :
1320 1 : auto poSRS = poDS->GetSpatialRef();
1321 1 : if (!poSRS || !poSRS->IsSame(&georeferencing.m_oSRS))
1322 : {
1323 0 : CPLError(CE_Failure, CPLE_AppDefined,
1324 : "Raster has no projection, or different from the one "
1325 : "of the georeferencing area");
1326 0 : return false;
1327 : }
1328 :
1329 1 : CPL_IGNORE_RET_VAL(GDALInvGeoTransform(georeferencing.m_adfGT,
1330 : adfInvGeoreferencingGT));
1331 : }
1332 4 : const double dfRasterMinX = adfRasterGT[0];
1333 4 : const double dfRasterMaxY = adfRasterGT[3];
1334 :
1335 : /* Does the source image has a color table ? */
1336 4 : const auto nColorTableId = WriteColorTable(poDS.get());
1337 :
1338 : double dfIgnoredOpacity;
1339 4 : StartBlending(psNode, oPageContext, dfIgnoredOpacity);
1340 :
1341 8 : CPLString osGroupStream;
1342 8 : std::vector<GDALPDFObjectNum> anImageIds;
1343 :
1344 4 : const int nXBlocks = (nWidth + nBlockXSize - 1) / nBlockXSize;
1345 4 : const int nYBlocks = (nHeight + nBlockYSize - 1) / nBlockYSize;
1346 : int nBlockXOff, nBlockYOff;
1347 10 : for (nBlockYOff = 0; nBlockYOff < nYBlocks; nBlockYOff++)
1348 : {
1349 16 : for (nBlockXOff = 0; nBlockXOff < nXBlocks; nBlockXOff++)
1350 : {
1351 : int nReqWidth =
1352 10 : std::min(nBlockXSize, nWidth - nBlockXOff * nBlockXSize);
1353 : int nReqHeight =
1354 10 : std::min(nBlockYSize, nHeight - nBlockYOff * nBlockYSize);
1355 :
1356 10 : int nX = nBlockXOff * nBlockXSize;
1357 10 : int nY = nBlockYOff * nBlockYSize;
1358 :
1359 10 : double dfXPDFOff = nX * (dfX2 - dfX1) / nWidth + dfX1;
1360 10 : double dfYPDFOff =
1361 10 : (nHeight - nY - nReqHeight) * (dfY2 - dfY1) / nHeight + dfY1;
1362 10 : double dfXPDFSize = nReqWidth * (dfX2 - dfX1) / nWidth;
1363 10 : double dfYPDFSize = nReqHeight * (dfY2 - dfY1) / nHeight;
1364 :
1365 10 : if (bClip)
1366 : {
1367 : /* Compute extent of block to write */
1368 1 : double dfBlockMinX = adfRasterGT[0] + nX * adfRasterGT[1];
1369 1 : double dfBlockMaxX =
1370 1 : adfRasterGT[0] + (nX + nReqWidth) * adfRasterGT[1];
1371 1 : double dfBlockMinY =
1372 1 : adfRasterGT[3] + (nY + nReqHeight) * adfRasterGT[5];
1373 1 : double dfBlockMaxY = adfRasterGT[3] + nY * adfRasterGT[5];
1374 :
1375 : // Clip the extent of the block with the extent of the main
1376 : // raster.
1377 : const double dfIntersectMinX =
1378 1 : std::max(dfBlockMinX, dfClippingMinX);
1379 : const double dfIntersectMinY =
1380 1 : std::max(dfBlockMinY, dfClippingMinY);
1381 : const double dfIntersectMaxX =
1382 1 : std::min(dfBlockMaxX, dfClippingMaxX);
1383 : const double dfIntersectMaxY =
1384 1 : std::min(dfBlockMaxY, dfClippingMaxY);
1385 :
1386 1 : bool bOK = false;
1387 1 : if (dfIntersectMinX < dfIntersectMaxX &&
1388 : dfIntersectMinY < dfIntersectMaxY)
1389 : {
1390 : /* Re-compute (x,y,width,height) subwindow of current raster
1391 : * from */
1392 : /* the extent of the clipped block */
1393 1 : nX = static_cast<int>((dfIntersectMinX - dfRasterMinX) /
1394 1 : adfRasterGT[1] +
1395 : 0.5);
1396 1 : nY = static_cast<int>((dfRasterMaxY - dfIntersectMaxY) /
1397 1 : (-adfRasterGT[5]) +
1398 : 0.5);
1399 1 : nReqWidth =
1400 1 : static_cast<int>((dfIntersectMaxX - dfRasterMinX) /
1401 1 : adfRasterGT[1] +
1402 : 0.5) -
1403 : nX;
1404 1 : nReqHeight =
1405 1 : static_cast<int>((dfRasterMaxY - dfIntersectMinY) /
1406 1 : (-adfRasterGT[5]) +
1407 : 0.5) -
1408 : nY;
1409 :
1410 1 : if (nReqWidth > 0 && nReqHeight > 0)
1411 : {
1412 1 : dfBlockMinX = adfRasterGT[0] + nX * adfRasterGT[1];
1413 1 : dfBlockMaxX =
1414 1 : adfRasterGT[0] + (nX + nReqWidth) * adfRasterGT[1];
1415 1 : dfBlockMinY =
1416 1 : adfRasterGT[3] + (nY + nReqHeight) * adfRasterGT[5];
1417 1 : dfBlockMaxY = adfRasterGT[3] + nY * adfRasterGT[5];
1418 :
1419 1 : double dfPDFX1 = APPLY_GT_X(adfInvGeoreferencingGT,
1420 : dfBlockMinX, dfBlockMinY);
1421 1 : double dfPDFY1 = APPLY_GT_Y(adfInvGeoreferencingGT,
1422 : dfBlockMinX, dfBlockMinY);
1423 1 : double dfPDFX2 = APPLY_GT_X(adfInvGeoreferencingGT,
1424 : dfBlockMaxX, dfBlockMaxY);
1425 1 : double dfPDFY2 = APPLY_GT_Y(adfInvGeoreferencingGT,
1426 : dfBlockMaxX, dfBlockMaxY);
1427 :
1428 1 : dfXPDFOff = dfPDFX1;
1429 1 : dfYPDFOff = dfPDFY1;
1430 1 : dfXPDFSize = dfPDFX2 - dfPDFX1;
1431 1 : dfYPDFSize = dfPDFY2 - dfPDFY1;
1432 1 : bOK = true;
1433 : }
1434 : }
1435 1 : if (!bOK)
1436 : {
1437 0 : continue;
1438 : }
1439 : }
1440 :
1441 : const auto nImageId =
1442 : WriteBlock(poDS.get(), nX, nY, nReqWidth, nReqHeight,
1443 : nColorTableId, eCompressMethod, nPredictor,
1444 10 : nJPEGQuality, pszJPEG2000_DRIVER, nullptr, nullptr);
1445 :
1446 10 : if (!nImageId.toBool())
1447 0 : return false;
1448 :
1449 10 : anImageIds.push_back(nImageId);
1450 10 : osGroupStream += "q\n";
1451 10 : GDALPDFObjectRW *poXSize = GDALPDFObjectRW::CreateReal(dfXPDFSize);
1452 10 : GDALPDFObjectRW *poYSize = GDALPDFObjectRW::CreateReal(dfYPDFSize);
1453 10 : GDALPDFObjectRW *poXOff = GDALPDFObjectRW::CreateReal(dfXPDFOff);
1454 10 : GDALPDFObjectRW *poYOff = GDALPDFObjectRW::CreateReal(dfYPDFOff);
1455 50 : osGroupStream += CPLOPrintf(
1456 20 : "%s 0 0 %s %s %s cm\n", poXSize->Serialize().c_str(),
1457 30 : poYSize->Serialize().c_str(), poXOff->Serialize().c_str(),
1458 30 : poYOff->Serialize().c_str());
1459 10 : delete poXSize;
1460 10 : delete poYSize;
1461 10 : delete poXOff;
1462 10 : delete poYOff;
1463 10 : osGroupStream += CPLOPrintf("/Image%d Do\n", nImageId.toInt());
1464 10 : osGroupStream += "Q\n";
1465 : }
1466 : }
1467 :
1468 4 : if (anImageIds.size() <= 1 || CPLGetXMLNode(psNode, "Blending") == nullptr)
1469 : {
1470 4 : for (const auto &nImageId : anImageIds)
1471 : {
1472 2 : oPageContext.m_oXObjects[CPLOPrintf("Image%d", nImageId.toInt())] =
1473 : nImageId;
1474 : }
1475 2 : oPageContext.m_osDrawingStream += osGroupStream;
1476 : }
1477 : else
1478 : {
1479 : // In case several tiles are drawn with blending, use a transparency
1480 : // group to avoid edge effects.
1481 :
1482 2 : auto nGroupId = AllocNewObject();
1483 2 : GDALPDFDictionaryRW oDictGroup;
1484 2 : GDALPDFDictionaryRW *poGroup = new GDALPDFDictionaryRW();
1485 2 : poGroup->Add("Type", GDALPDFObjectRW::CreateName("Group"))
1486 2 : .Add("S", GDALPDFObjectRW::CreateName("Transparency"));
1487 :
1488 2 : GDALPDFDictionaryRW *poXObjects = new GDALPDFDictionaryRW();
1489 10 : for (const auto &nImageId : anImageIds)
1490 : {
1491 16 : poXObjects->Add(CPLOPrintf("Image%d", nImageId.toInt()), nImageId,
1492 8 : 0);
1493 : }
1494 2 : GDALPDFDictionaryRW *poResources = new GDALPDFDictionaryRW();
1495 2 : poResources->Add("XObject", poXObjects);
1496 :
1497 2 : oDictGroup.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
1498 2 : .Add("BBox", &((new GDALPDFArrayRW())->Add(0).Add(0))
1499 2 : .Add(oPageContext.m_dfWidthInUserUnit)
1500 2 : .Add(oPageContext.m_dfHeightInUserUnit))
1501 2 : .Add("Subtype", GDALPDFObjectRW::CreateName("Form"))
1502 2 : .Add("Group", poGroup)
1503 2 : .Add("Resources", poResources);
1504 :
1505 2 : StartObjWithStream(nGroupId, oDictGroup,
1506 2 : oPageContext.m_eStreamCompressMethod !=
1507 : COMPRESS_NONE);
1508 2 : VSIFPrintfL(m_fp, "%s", osGroupStream.c_str());
1509 2 : EndObjWithStream();
1510 :
1511 2 : oPageContext.m_oXObjects[CPLOPrintf("Group%d", nGroupId.toInt())] =
1512 : nGroupId;
1513 : oPageContext.m_osDrawingStream +=
1514 2 : CPLOPrintf("/Group%d Do\n", nGroupId.toInt());
1515 : }
1516 :
1517 4 : EndBlending(psNode, oPageContext);
1518 :
1519 4 : return true;
1520 : }
1521 :
1522 : /************************************************************************/
1523 : /* SetupVectorGeoreferencing() */
1524 : /************************************************************************/
1525 :
1526 4 : bool GDALPDFComposerWriter::SetupVectorGeoreferencing(
1527 : const char *pszGeoreferencingId, OGRLayer *poLayer,
1528 : const PageContext &oPageContext, double &dfClippingMinX,
1529 : double &dfClippingMinY, double &dfClippingMaxX, double &dfClippingMaxY,
1530 : double adfMatrix[4], std::unique_ptr<OGRCoordinateTransformation> &poCT)
1531 : {
1532 4 : CPLAssert(pszGeoreferencingId);
1533 :
1534 4 : auto iter = oPageContext.m_oMapGeoreferencedId.find(pszGeoreferencingId);
1535 4 : if (iter == oPageContext.m_oMapGeoreferencedId.end())
1536 : {
1537 0 : CPLError(CE_Failure, CPLE_AppDefined,
1538 : "Cannot find georeferencing of id %s", pszGeoreferencingId);
1539 0 : return false;
1540 : }
1541 4 : const auto &georeferencing = iter->second;
1542 4 : const double dfX1 = georeferencing.m_bboxX1;
1543 4 : const double dfY1 = georeferencing.m_bboxY1;
1544 4 : const double dfX2 = georeferencing.m_bboxX2;
1545 4 : const double dfY2 = georeferencing.m_bboxY2;
1546 :
1547 4 : dfClippingMinX = APPLY_GT_X(georeferencing.m_adfGT, dfX1, dfY1);
1548 4 : dfClippingMinY = APPLY_GT_Y(georeferencing.m_adfGT, dfX1, dfY1);
1549 4 : dfClippingMaxX = APPLY_GT_X(georeferencing.m_adfGT, dfX2, dfY2);
1550 4 : dfClippingMaxY = APPLY_GT_Y(georeferencing.m_adfGT, dfX2, dfY2);
1551 :
1552 4 : auto poSRS = poLayer->GetSpatialRef();
1553 4 : if (!poSRS)
1554 : {
1555 0 : CPLError(CE_Failure, CPLE_AppDefined, "Layer has no SRS");
1556 0 : return false;
1557 : }
1558 4 : if (!poSRS->IsSame(&georeferencing.m_oSRS))
1559 : {
1560 2 : poCT.reset(
1561 : OGRCreateCoordinateTransformation(poSRS, &georeferencing.m_oSRS));
1562 : }
1563 :
1564 4 : if (!poCT)
1565 : {
1566 2 : poLayer->SetSpatialFilterRect(dfClippingMinX, dfClippingMinY,
1567 : dfClippingMaxX, dfClippingMaxY);
1568 : }
1569 :
1570 : double adfInvGeoreferencingGT[6]; // from georeferenced to PDF coordinates
1571 4 : CPL_IGNORE_RET_VAL(GDALInvGeoTransform(
1572 4 : const_cast<double *>(georeferencing.m_adfGT), adfInvGeoreferencingGT));
1573 4 : adfMatrix[0] = adfInvGeoreferencingGT[0];
1574 4 : adfMatrix[1] = adfInvGeoreferencingGT[1];
1575 4 : adfMatrix[2] = adfInvGeoreferencingGT[3];
1576 4 : adfMatrix[3] = adfInvGeoreferencingGT[5];
1577 :
1578 4 : return true;
1579 : }
1580 :
1581 : /************************************************************************/
1582 : /* WriteVector() */
1583 : /************************************************************************/
1584 :
1585 5 : bool GDALPDFComposerWriter::WriteVector(const CPLXMLNode *psNode,
1586 : PageContext &oPageContext)
1587 : {
1588 5 : const char *pszDataset = CPLGetXMLValue(psNode, "dataset", nullptr);
1589 5 : if (!pszDataset)
1590 : {
1591 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing dataset");
1592 0 : return false;
1593 : }
1594 5 : const char *pszLayer = CPLGetXMLValue(psNode, "layer", nullptr);
1595 5 : if (!pszLayer)
1596 : {
1597 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing layer");
1598 0 : return false;
1599 : }
1600 :
1601 : GDALDatasetUniquePtr poDS(
1602 : GDALDataset::Open(pszDataset, GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR,
1603 10 : nullptr, nullptr, nullptr));
1604 5 : if (!poDS)
1605 0 : return false;
1606 5 : OGRLayer *poLayer = poDS->GetLayerByName(pszLayer);
1607 5 : if (!poLayer)
1608 : {
1609 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannt find layer %s", pszLayer);
1610 0 : return false;
1611 : }
1612 : const bool bVisible =
1613 5 : CPLTestBool(CPLGetXMLValue(psNode, "visible", "true"));
1614 :
1615 5 : const auto psLogicalStructure = CPLGetXMLNode(psNode, "LogicalStructure");
1616 5 : const char *pszOGRDisplayField = nullptr;
1617 10 : std::vector<CPLString> aosIncludedFields;
1618 5 : const bool bLogicalStructure = psLogicalStructure != nullptr;
1619 5 : if (psLogicalStructure)
1620 : {
1621 : pszOGRDisplayField =
1622 5 : CPLGetXMLValue(psLogicalStructure, "fieldToDisplay", nullptr);
1623 9 : if (CPLGetXMLNode(psLogicalStructure, "ExcludeAllFields") != nullptr ||
1624 4 : CPLGetXMLNode(psLogicalStructure, "IncludeField") != nullptr)
1625 : {
1626 7 : for (const auto *psIter = psLogicalStructure->psChild; psIter;
1627 5 : psIter = psIter->psNext)
1628 : {
1629 5 : if (psIter->eType == CXT_Element &&
1630 3 : strcmp(psIter->pszValue, "IncludeField") == 0)
1631 : {
1632 2 : aosIncludedFields.push_back(
1633 : CPLGetXMLValue(psIter, nullptr, ""));
1634 : }
1635 : }
1636 : }
1637 : else
1638 : {
1639 6 : std::set<CPLString> oSetExcludedFields;
1640 5 : for (const auto *psIter = psLogicalStructure->psChild; psIter;
1641 2 : psIter = psIter->psNext)
1642 : {
1643 2 : if (psIter->eType == CXT_Element &&
1644 2 : strcmp(psIter->pszValue, "ExcludeField") == 0)
1645 : {
1646 : oSetExcludedFields.insert(
1647 2 : CPLGetXMLValue(psIter, nullptr, ""));
1648 : }
1649 : }
1650 3 : const auto poLayerDefn = poLayer->GetLayerDefn();
1651 8 : for (int i = 0; i < poLayerDefn->GetFieldCount(); i++)
1652 : {
1653 5 : const auto poFieldDefn = poLayerDefn->GetFieldDefn(i);
1654 5 : const char *pszName = poFieldDefn->GetNameRef();
1655 5 : if (oSetExcludedFields.find(pszName) ==
1656 10 : oSetExcludedFields.end())
1657 : {
1658 3 : aosIncludedFields.push_back(pszName);
1659 : }
1660 : }
1661 : }
1662 : }
1663 : const char *pszStyleString =
1664 5 : CPLGetXMLValue(psNode, "ogrStyleString", nullptr);
1665 : const char *pszOGRLinkField =
1666 5 : CPLGetXMLValue(psNode, "linkAttribute", nullptr);
1667 :
1668 : const char *pszGeoreferencingId =
1669 5 : CPLGetXMLValue(psNode, "georeferencingId", nullptr);
1670 5 : std::unique_ptr<OGRCoordinateTransformation> poCT;
1671 5 : double dfClippingMinX = 0;
1672 5 : double dfClippingMinY = 0;
1673 5 : double dfClippingMaxX = 0;
1674 5 : double dfClippingMaxY = 0;
1675 5 : double adfMatrix[4] = {0, 1, 0, 1};
1676 7 : if (pszGeoreferencingId &&
1677 2 : !SetupVectorGeoreferencing(
1678 : pszGeoreferencingId, poLayer, oPageContext, dfClippingMinX,
1679 : dfClippingMinY, dfClippingMaxX, dfClippingMaxY, adfMatrix, poCT))
1680 : {
1681 0 : return false;
1682 : }
1683 :
1684 5 : double dfOpacityFactor = 1.0;
1685 5 : if (!bVisible)
1686 : {
1687 2 : if (oPageContext.m_oExtGState.find("GSinvisible") ==
1688 4 : oPageContext.m_oExtGState.end())
1689 : {
1690 1 : auto nExtGState = AllocNewObject();
1691 1 : StartObj(nExtGState);
1692 : {
1693 1 : GDALPDFDictionaryRW gs;
1694 1 : gs.Add("Type", GDALPDFObjectRW::CreateName("ExtGState"));
1695 1 : gs.Add("ca", 0);
1696 1 : gs.Add("CA", 0);
1697 1 : VSIFPrintfL(m_fp, "%s\n", gs.Serialize().c_str());
1698 : }
1699 1 : EndObj();
1700 1 : oPageContext.m_oExtGState["GSinvisible"] = nExtGState;
1701 : }
1702 2 : oPageContext.m_osDrawingStream += "q\n";
1703 2 : oPageContext.m_osDrawingStream += "/GSinvisible gs\n";
1704 2 : oPageContext.m_osDrawingStream += "0 w\n";
1705 2 : dfOpacityFactor = 0;
1706 : }
1707 : else
1708 : {
1709 3 : StartBlending(psNode, oPageContext, dfOpacityFactor);
1710 : }
1711 :
1712 5 : if (!m_nStructTreeRootId.toBool())
1713 3 : m_nStructTreeRootId = AllocNewObject();
1714 :
1715 5 : GDALPDFObjectNum nFeatureLayerId;
1716 5 : if (bLogicalStructure)
1717 : {
1718 5 : nFeatureLayerId = AllocNewObject();
1719 5 : m_anFeatureLayerId.push_back(nFeatureLayerId);
1720 : }
1721 :
1722 5 : std::vector<GDALPDFObjectNum> anFeatureUserProperties;
1723 14 : for (auto &&poFeature : poLayer)
1724 : {
1725 9 : auto hFeat = OGRFeature::ToHandle(poFeature.get());
1726 9 : auto hGeom = OGR_F_GetGeometryRef(hFeat);
1727 9 : if (!hGeom || OGR_G_IsEmpty(hGeom))
1728 1 : continue;
1729 9 : if (poCT)
1730 : {
1731 2 : if (OGRGeometry::FromHandle(hGeom)->transform(poCT.get()) !=
1732 : OGRERR_NONE)
1733 1 : continue;
1734 :
1735 2 : OGREnvelope sEnvelope;
1736 2 : OGR_G_GetEnvelope(hGeom, &sEnvelope);
1737 2 : if (sEnvelope.MinX > dfClippingMaxX ||
1738 2 : sEnvelope.MaxX < dfClippingMinX ||
1739 1 : sEnvelope.MinY > dfClippingMaxY ||
1740 1 : sEnvelope.MaxY < dfClippingMinY)
1741 : {
1742 1 : continue;
1743 : }
1744 : }
1745 :
1746 8 : if (bLogicalStructure)
1747 : {
1748 8 : CPLString osOutFeatureName;
1749 8 : anFeatureUserProperties.push_back(
1750 8 : WriteAttributes(hFeat, aosIncludedFields, pszOGRDisplayField,
1751 : oPageContext.m_nMCID, nFeatureLayerId,
1752 8 : m_asPageId.back(), osOutFeatureName));
1753 : }
1754 :
1755 16 : ObjectStyle os;
1756 8 : GetObjectStyle(pszStyleString, hFeat, adfMatrix,
1757 8 : m_oMapSymbolFilenameToDesc, os);
1758 8 : os.nPenA = static_cast<int>(std::round(os.nPenA * dfOpacityFactor));
1759 8 : os.nBrushA = static_cast<int>(std::round(os.nBrushA * dfOpacityFactor));
1760 :
1761 8 : const double dfRadius = os.dfSymbolSize;
1762 :
1763 8 : if (os.nImageSymbolId.toBool())
1764 : {
1765 2 : oPageContext.m_oXObjects[CPLOPrintf(
1766 2 : "SymImage%d", os.nImageSymbolId.toInt())] = os.nImageSymbolId;
1767 : }
1768 :
1769 8 : if (pszOGRLinkField)
1770 : {
1771 1 : OGREnvelope sEnvelope;
1772 1 : OGR_G_GetEnvelope(hGeom, &sEnvelope);
1773 : int bboxXMin, bboxYMin, bboxXMax, bboxYMax;
1774 1 : ComputeIntBBox(hGeom, sEnvelope, adfMatrix, os, dfRadius, bboxXMin,
1775 : bboxYMin, bboxXMax, bboxYMax);
1776 :
1777 : auto nLinkId = WriteLink(hFeat, pszOGRLinkField, adfMatrix,
1778 1 : bboxXMin, bboxYMin, bboxXMax, bboxYMax);
1779 1 : if (nLinkId.toBool())
1780 1 : oPageContext.m_anAnnotationsId.push_back(nLinkId);
1781 : }
1782 :
1783 8 : if (bLogicalStructure)
1784 : {
1785 : oPageContext.m_osDrawingStream +=
1786 8 : CPLOPrintf("/feature <</MCID %d>> BDC\n", oPageContext.m_nMCID);
1787 : }
1788 :
1789 8 : if (bVisible || bLogicalStructure)
1790 : {
1791 8 : oPageContext.m_osDrawingStream += "q\n";
1792 8 : if (bVisible && (os.nPenA != 255 || os.nBrushA != 255))
1793 : {
1794 2 : CPLString osGSName;
1795 2 : osGSName.Printf("GS_CA_%d_ca_%d", os.nPenA, os.nBrushA);
1796 2 : if (oPageContext.m_oExtGState.find(osGSName) ==
1797 4 : oPageContext.m_oExtGState.end())
1798 : {
1799 2 : auto nExtGState = AllocNewObject();
1800 2 : StartObj(nExtGState);
1801 : {
1802 2 : GDALPDFDictionaryRW gs;
1803 : gs.Add("Type",
1804 2 : GDALPDFObjectRW::CreateName("ExtGState"));
1805 2 : if (os.nPenA != 255)
1806 1 : gs.Add("CA", (os.nPenA == 127 || os.nPenA == 128)
1807 : ? 0.5
1808 2 : : os.nPenA / 255.0);
1809 2 : if (os.nBrushA != 255)
1810 : gs.Add("ca",
1811 1 : (os.nBrushA == 127 || os.nBrushA == 128)
1812 : ? 0.5
1813 3 : : os.nBrushA / 255.0);
1814 2 : VSIFPrintfL(m_fp, "%s\n", gs.Serialize().c_str());
1815 : }
1816 2 : EndObj();
1817 2 : oPageContext.m_oExtGState[osGSName] = nExtGState;
1818 : }
1819 2 : oPageContext.m_osDrawingStream += "/" + osGSName + " gs\n";
1820 : }
1821 :
1822 : oPageContext.m_osDrawingStream +=
1823 8 : GenerateDrawingStream(hGeom, adfMatrix, os, dfRadius);
1824 :
1825 8 : oPageContext.m_osDrawingStream += "Q\n";
1826 : }
1827 :
1828 8 : if (bLogicalStructure)
1829 : {
1830 8 : oPageContext.m_osDrawingStream += "EMC\n";
1831 8 : oPageContext.m_nMCID++;
1832 : }
1833 : }
1834 :
1835 5 : if (bLogicalStructure)
1836 : {
1837 13 : for (const auto &num : anFeatureUserProperties)
1838 : {
1839 8 : oPageContext.m_anFeatureUserProperties.push_back(num);
1840 : }
1841 :
1842 : {
1843 5 : StartObj(nFeatureLayerId);
1844 :
1845 10 : GDALPDFDictionaryRW oDict;
1846 5 : GDALPDFDictionaryRW *poDictA = new GDALPDFDictionaryRW();
1847 5 : oDict.Add("A", poDictA);
1848 5 : poDictA->Add("O", GDALPDFObjectRW::CreateName("UserProperties"));
1849 5 : GDALPDFArrayRW *poArrayK = new GDALPDFArrayRW();
1850 13 : for (const auto &num : anFeatureUserProperties)
1851 8 : poArrayK->Add(num, 0);
1852 5 : oDict.Add("K", poArrayK);
1853 5 : oDict.Add("P", m_nStructTreeRootId, 0);
1854 5 : oDict.Add("S", GDALPDFObjectRW::CreateName("Layer"));
1855 :
1856 5 : const char *pszOGRDisplayName = CPLGetXMLValue(
1857 5 : psLogicalStructure, "displayLayerName", poLayer->GetName());
1858 5 : oDict.Add("T", pszOGRDisplayName);
1859 :
1860 5 : VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
1861 :
1862 5 : EndObj();
1863 : }
1864 : }
1865 :
1866 5 : if (!bVisible)
1867 : {
1868 2 : oPageContext.m_osDrawingStream += "Q\n";
1869 : }
1870 : else
1871 : {
1872 3 : EndBlending(psNode, oPageContext);
1873 : }
1874 :
1875 5 : return true;
1876 : }
1877 :
1878 : /************************************************************************/
1879 : /* WriteVectorLabel() */
1880 : /************************************************************************/
1881 :
1882 3 : bool GDALPDFComposerWriter::WriteVectorLabel(const CPLXMLNode *psNode,
1883 : PageContext &oPageContext)
1884 : {
1885 3 : const char *pszDataset = CPLGetXMLValue(psNode, "dataset", nullptr);
1886 3 : if (!pszDataset)
1887 : {
1888 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing dataset");
1889 0 : return false;
1890 : }
1891 3 : const char *pszLayer = CPLGetXMLValue(psNode, "layer", nullptr);
1892 3 : if (!pszLayer)
1893 : {
1894 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing layer");
1895 0 : return false;
1896 : }
1897 :
1898 : GDALDatasetUniquePtr poDS(
1899 : GDALDataset::Open(pszDataset, GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR,
1900 6 : nullptr, nullptr, nullptr));
1901 3 : if (!poDS)
1902 0 : return false;
1903 3 : OGRLayer *poLayer = poDS->GetLayerByName(pszLayer);
1904 3 : if (!poLayer)
1905 : {
1906 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannt find layer %s", pszLayer);
1907 0 : return false;
1908 : }
1909 :
1910 : const char *pszStyleString =
1911 3 : CPLGetXMLValue(psNode, "ogrStyleString", nullptr);
1912 :
1913 3 : double dfOpacityFactor = 1.0;
1914 3 : StartBlending(psNode, oPageContext, dfOpacityFactor);
1915 :
1916 : const char *pszGeoreferencingId =
1917 3 : CPLGetXMLValue(psNode, "georeferencingId", nullptr);
1918 3 : std::unique_ptr<OGRCoordinateTransformation> poCT;
1919 3 : double dfClippingMinX = 0;
1920 3 : double dfClippingMinY = 0;
1921 3 : double dfClippingMaxX = 0;
1922 3 : double dfClippingMaxY = 0;
1923 3 : double adfMatrix[4] = {0, 1, 0, 1};
1924 5 : if (pszGeoreferencingId &&
1925 2 : !SetupVectorGeoreferencing(
1926 : pszGeoreferencingId, poLayer, oPageContext, dfClippingMinX,
1927 : dfClippingMinY, dfClippingMaxX, dfClippingMaxY, adfMatrix, poCT))
1928 : {
1929 0 : return false;
1930 : }
1931 :
1932 7 : for (auto &&poFeature : poLayer)
1933 : {
1934 4 : auto hFeat = OGRFeature::ToHandle(poFeature.get());
1935 4 : auto hGeom = OGR_F_GetGeometryRef(hFeat);
1936 4 : if (!hGeom || OGR_G_IsEmpty(hGeom))
1937 1 : continue;
1938 4 : if (poCT)
1939 : {
1940 2 : if (OGRGeometry::FromHandle(hGeom)->transform(poCT.get()) !=
1941 : OGRERR_NONE)
1942 1 : continue;
1943 :
1944 2 : OGREnvelope sEnvelope;
1945 2 : OGR_G_GetEnvelope(hGeom, &sEnvelope);
1946 2 : if (sEnvelope.MinX > dfClippingMaxX ||
1947 2 : sEnvelope.MaxX < dfClippingMinX ||
1948 1 : sEnvelope.MinY > dfClippingMaxY ||
1949 1 : sEnvelope.MaxY < dfClippingMinY)
1950 : {
1951 1 : continue;
1952 : }
1953 : }
1954 :
1955 6 : ObjectStyle os;
1956 3 : GetObjectStyle(pszStyleString, hFeat, adfMatrix,
1957 3 : m_oMapSymbolFilenameToDesc, os);
1958 3 : os.nPenA = static_cast<int>(std::round(os.nPenA * dfOpacityFactor));
1959 3 : os.nBrushA = static_cast<int>(std::round(os.nBrushA * dfOpacityFactor));
1960 :
1961 6 : if (!os.osLabelText.empty() &&
1962 3 : wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint)
1963 : {
1964 : auto nObjectId = WriteLabel(hGeom, adfMatrix, os,
1965 : oPageContext.m_eStreamCompressMethod, 0,
1966 : 0, oPageContext.m_dfWidthInUserUnit,
1967 3 : oPageContext.m_dfHeightInUserUnit);
1968 : oPageContext.m_osDrawingStream +=
1969 3 : CPLOPrintf("/Label%d Do\n", nObjectId.toInt());
1970 3 : oPageContext.m_oXObjects[CPLOPrintf("Label%d", nObjectId.toInt())] =
1971 : nObjectId;
1972 : }
1973 : }
1974 :
1975 3 : EndBlending(psNode, oPageContext);
1976 :
1977 3 : return true;
1978 : }
1979 :
1980 : #ifdef HAVE_PDF_READ_SUPPORT
1981 :
1982 : /************************************************************************/
1983 : /* EmitNewObject() */
1984 : /************************************************************************/
1985 :
1986 : GDALPDFObjectNum
1987 10 : GDALPDFComposerWriter::EmitNewObject(GDALPDFObject *poObj,
1988 : RemapType &oRemapObjectRefs)
1989 : {
1990 10 : auto nId = AllocNewObject();
1991 10 : const auto nRefNum = poObj->GetRefNum();
1992 10 : if (nRefNum.toBool())
1993 : {
1994 10 : int nRefGen = poObj->GetRefGen();
1995 10 : std::pair<int, int> oKey(nRefNum.toInt(), nRefGen);
1996 10 : oRemapObjectRefs[oKey] = nId;
1997 : }
1998 20 : CPLString osStr;
1999 10 : if (!SerializeAndRenumberIgnoreRef(osStr, poObj, oRemapObjectRefs))
2000 0 : return GDALPDFObjectNum();
2001 10 : StartObj(nId);
2002 10 : VSIFWriteL(osStr.data(), 1, osStr.size(), m_fp);
2003 10 : VSIFPrintfL(m_fp, "\n");
2004 10 : EndObj();
2005 10 : return nId;
2006 : }
2007 :
2008 : /************************************************************************/
2009 : /* SerializeAndRenumber() */
2010 : /************************************************************************/
2011 :
2012 40 : bool GDALPDFComposerWriter::SerializeAndRenumber(CPLString &osStr,
2013 : GDALPDFObject *poObj,
2014 : RemapType &oRemapObjectRefs)
2015 : {
2016 40 : auto nRefNum = poObj->GetRefNum();
2017 40 : if (nRefNum.toBool())
2018 : {
2019 6 : int nRefGen = poObj->GetRefGen();
2020 :
2021 6 : std::pair<int, int> oKey(nRefNum.toInt(), nRefGen);
2022 6 : auto oIter = oRemapObjectRefs.find(oKey);
2023 6 : if (oIter != oRemapObjectRefs.end())
2024 : {
2025 0 : osStr.append(CPLSPrintf("%d 0 R", oIter->second.toInt()));
2026 0 : return true;
2027 : }
2028 : else
2029 : {
2030 6 : auto nId = EmitNewObject(poObj, oRemapObjectRefs);
2031 6 : osStr.append(CPLSPrintf("%d 0 R", nId.toInt()));
2032 6 : return nId.toBool();
2033 : }
2034 : }
2035 : else
2036 : {
2037 34 : return SerializeAndRenumberIgnoreRef(osStr, poObj, oRemapObjectRefs);
2038 : }
2039 : }
2040 :
2041 : /************************************************************************/
2042 : /* SerializeAndRenumberIgnoreRef() */
2043 : /************************************************************************/
2044 :
2045 44 : bool GDALPDFComposerWriter::SerializeAndRenumberIgnoreRef(
2046 : CPLString &osStr, GDALPDFObject *poObj, RemapType &oRemapObjectRefs)
2047 : {
2048 44 : switch (poObj->GetType())
2049 : {
2050 0 : case PDFObjectType_Array:
2051 : {
2052 0 : auto poArray = poObj->GetArray();
2053 0 : int nLength = poArray->GetLength();
2054 0 : osStr.append("[ ");
2055 0 : for (int i = 0; i < nLength; i++)
2056 : {
2057 0 : if (!SerializeAndRenumber(osStr, poArray->Get(i),
2058 : oRemapObjectRefs))
2059 0 : return false;
2060 0 : osStr.append(" ");
2061 : }
2062 0 : osStr.append("]");
2063 0 : break;
2064 : }
2065 12 : case PDFObjectType_Dictionary:
2066 : {
2067 12 : osStr.append("<< ");
2068 12 : auto poDict = poObj->GetDictionary();
2069 12 : auto &oMap = poDict->GetValues();
2070 52 : for (const auto &oIter : oMap)
2071 : {
2072 40 : const char *pszKey = oIter.first.c_str();
2073 40 : GDALPDFObject *poSubObj = oIter.second;
2074 40 : osStr.append("/");
2075 40 : osStr.append(pszKey);
2076 40 : osStr.append(" ");
2077 40 : if (!SerializeAndRenumber(osStr, poSubObj, oRemapObjectRefs))
2078 0 : return false;
2079 40 : osStr.append(" ");
2080 : }
2081 12 : osStr.append(">>");
2082 12 : auto poStream = poObj->GetStream();
2083 12 : if (poStream)
2084 : {
2085 : // CPLAssert( poObj->GetRefNum().toBool() ); // should be a top
2086 : // level object
2087 4 : osStr.append("\nstream\n");
2088 4 : auto pRawBytes = poStream->GetRawBytes();
2089 4 : if (!pRawBytes)
2090 : {
2091 0 : CPLError(CE_Failure, CPLE_AppDefined,
2092 : "Cannot get stream content");
2093 0 : return false;
2094 : }
2095 : osStr.append(pRawBytes,
2096 4 : static_cast<size_t>(poStream->GetRawLength()));
2097 4 : VSIFree(pRawBytes);
2098 4 : osStr.append("\nendstream\n");
2099 : }
2100 12 : break;
2101 : }
2102 0 : case PDFObjectType_Unknown:
2103 : {
2104 0 : CPLError(CE_Failure, CPLE_AppDefined, "Corrupted PDF");
2105 0 : return false;
2106 : }
2107 32 : default:
2108 : {
2109 32 : poObj->Serialize(osStr, false);
2110 32 : break;
2111 : }
2112 : }
2113 44 : return true;
2114 : }
2115 :
2116 : /************************************************************************/
2117 : /* SerializeAndRenumber() */
2118 : /************************************************************************/
2119 :
2120 : GDALPDFObjectNum
2121 4 : GDALPDFComposerWriter::SerializeAndRenumber(GDALPDFObject *poObj)
2122 : {
2123 8 : RemapType oRemapObjectRefs;
2124 8 : return EmitNewObject(poObj, oRemapObjectRefs);
2125 : }
2126 :
2127 : /************************************************************************/
2128 : /* WritePDF() */
2129 : /************************************************************************/
2130 :
2131 12 : bool GDALPDFComposerWriter::WritePDF(const CPLXMLNode *psNode,
2132 : PageContext &oPageContext)
2133 : {
2134 12 : const char *pszDataset = CPLGetXMLValue(psNode, "dataset", nullptr);
2135 12 : if (!pszDataset)
2136 : {
2137 2 : CPLError(CE_Failure, CPLE_AppDefined, "Missing dataset");
2138 2 : return false;
2139 : }
2140 :
2141 20 : GDALOpenInfo oOpenInfo(pszDataset, GA_ReadOnly);
2142 20 : std::unique_ptr<PDFDataset> poDS(PDFDataset::Open(&oOpenInfo));
2143 10 : if (!poDS)
2144 : {
2145 2 : CPLError(CE_Failure, CPLE_OpenFailed, "%s is not a valid PDF file",
2146 : pszDataset);
2147 2 : return false;
2148 : }
2149 16 : if (poDS->GetPageWidth() != oPageContext.m_dfWidthInUserUnit ||
2150 8 : poDS->GetPageHeight() != oPageContext.m_dfHeightInUserUnit)
2151 : {
2152 0 : CPLError(CE_Warning, CPLE_AppDefined,
2153 : "Dimensions of the inserted PDF page are %fx%f, which is "
2154 : "different from the output PDF page %fx%f",
2155 : poDS->GetPageWidth(), poDS->GetPageHeight(),
2156 : oPageContext.m_dfWidthInUserUnit,
2157 : oPageContext.m_dfHeightInUserUnit);
2158 : }
2159 8 : auto poPageObj = poDS->GetPageObj();
2160 8 : if (!poPageObj)
2161 0 : return false;
2162 8 : auto poPageDict = poPageObj->GetDictionary();
2163 8 : if (!poPageDict)
2164 0 : return false;
2165 8 : auto poContents = poPageDict->Get("Contents");
2166 8 : if (poContents != nullptr && poContents->GetType() == PDFObjectType_Array)
2167 : {
2168 0 : GDALPDFArray *poContentsArray = poContents->GetArray();
2169 0 : if (poContentsArray->GetLength() == 1)
2170 : {
2171 0 : poContents = poContentsArray->Get(0);
2172 : }
2173 : }
2174 14 : if (poContents == nullptr ||
2175 6 : poContents->GetType() != PDFObjectType_Dictionary)
2176 : {
2177 2 : CPLError(CE_Failure, CPLE_AppDefined, "Missing Contents");
2178 2 : return false;
2179 : }
2180 :
2181 6 : auto poResources = poPageDict->Get("Resources");
2182 6 : if (!poResources)
2183 : {
2184 2 : CPLError(CE_Failure, CPLE_AppDefined, "Missing Resources");
2185 2 : return false;
2186 : }
2187 :
2188 : // Serialize and renumber the Page Resources dictionary
2189 4 : auto nClonedResources = SerializeAndRenumber(poResources);
2190 4 : if (!nClonedResources.toBool())
2191 : {
2192 0 : return false;
2193 : }
2194 :
2195 : // Create a Transparency group using cloned Page Resources, and
2196 : // the Page Contents stream
2197 4 : auto nFormId = AllocNewObject();
2198 8 : GDALPDFDictionaryRW oDictGroup;
2199 4 : GDALPDFDictionaryRW *poGroup = new GDALPDFDictionaryRW();
2200 4 : poGroup->Add("Type", GDALPDFObjectRW::CreateName("Group"))
2201 4 : .Add("S", GDALPDFObjectRW::CreateName("Transparency"));
2202 :
2203 4 : oDictGroup.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
2204 4 : .Add("BBox", &((new GDALPDFArrayRW())->Add(0).Add(0))
2205 4 : .Add(oPageContext.m_dfWidthInUserUnit)
2206 4 : .Add(oPageContext.m_dfHeightInUserUnit))
2207 4 : .Add("Subtype", GDALPDFObjectRW::CreateName("Form"))
2208 4 : .Add("Group", poGroup)
2209 4 : .Add("Resources", nClonedResources, 0);
2210 :
2211 4 : auto poStream = poContents->GetStream();
2212 4 : if (!poStream)
2213 : {
2214 2 : CPLError(CE_Failure, CPLE_AppDefined, "Missing Contents stream");
2215 2 : return false;
2216 : }
2217 2 : auto pabyContents = poStream->GetBytes();
2218 2 : if (!pabyContents)
2219 : {
2220 0 : return false;
2221 : }
2222 2 : const auto nContentsLength = poStream->GetLength();
2223 :
2224 2 : StartObjWithStream(nFormId, oDictGroup,
2225 2 : oPageContext.m_eStreamCompressMethod != COMPRESS_NONE);
2226 2 : VSIFWriteL(pabyContents, 1, static_cast<size_t>(nContentsLength), m_fp);
2227 2 : VSIFree(pabyContents);
2228 2 : EndObjWithStream();
2229 :
2230 : // Paint the transparency group
2231 : double dfIgnoredOpacity;
2232 2 : StartBlending(psNode, oPageContext, dfIgnoredOpacity);
2233 :
2234 : oPageContext.m_osDrawingStream +=
2235 2 : CPLOPrintf("/Form%d Do\n", nFormId.toInt());
2236 2 : oPageContext.m_oXObjects[CPLOPrintf("Form%d", nFormId.toInt())] = nFormId;
2237 :
2238 2 : EndBlending(psNode, oPageContext);
2239 :
2240 2 : return true;
2241 : }
2242 :
2243 : #endif // HAVE_PDF_READ_SUPPORT
2244 :
2245 : /************************************************************************/
2246 : /* Generate() */
2247 : /************************************************************************/
2248 :
2249 38 : bool GDALPDFComposerWriter::Generate(const CPLXMLNode *psComposition)
2250 : {
2251 38 : m_osJPEG2000Driver = CPLGetXMLValue(psComposition, "JPEG2000Driver", "");
2252 :
2253 38 : auto psMetadata = CPLGetXMLNode(psComposition, "Metadata");
2254 38 : if (psMetadata)
2255 : {
2256 : SetInfo(CPLGetXMLValue(psMetadata, "Author", nullptr),
2257 : CPLGetXMLValue(psMetadata, "Producer", nullptr),
2258 : CPLGetXMLValue(psMetadata, "Creator", nullptr),
2259 : CPLGetXMLValue(psMetadata, "CreationDate", nullptr),
2260 : CPLGetXMLValue(psMetadata, "Subject", nullptr),
2261 : CPLGetXMLValue(psMetadata, "Title", nullptr),
2262 1 : CPLGetXMLValue(psMetadata, "Keywords", nullptr));
2263 1 : SetXMP(nullptr, CPLGetXMLValue(psMetadata, "XMP", nullptr));
2264 : }
2265 :
2266 : const char *pszJavascript =
2267 38 : CPLGetXMLValue(psComposition, "Javascript", nullptr);
2268 38 : if (pszJavascript)
2269 1 : WriteJavascript(pszJavascript, false);
2270 :
2271 38 : auto psLayerTree = CPLGetXMLNode(psComposition, "LayerTree");
2272 38 : if (psLayerTree)
2273 : {
2274 7 : m_bDisplayLayersOnlyOnVisiblePages = CPLTestBool(
2275 : CPLGetXMLValue(psLayerTree, "displayOnlyOnVisiblePages", "false"));
2276 7 : if (!CreateLayerTree(psLayerTree, GDALPDFObjectNum(), &m_oTreeOfOGC))
2277 3 : return false;
2278 : }
2279 :
2280 35 : bool bFoundPage = false;
2281 63 : for (const auto *psIter = psComposition->psChild; psIter;
2282 28 : psIter = psIter->psNext)
2283 : {
2284 48 : if (psIter->eType == CXT_Element &&
2285 48 : strcmp(psIter->pszValue, "Page") == 0)
2286 : {
2287 37 : if (!GeneratePage(psIter))
2288 20 : return false;
2289 17 : bFoundPage = true;
2290 : }
2291 : }
2292 15 : if (!bFoundPage)
2293 : {
2294 1 : CPLError(CE_Failure, CPLE_AppDefined,
2295 : "At least one page should be defined");
2296 1 : return false;
2297 : }
2298 :
2299 14 : auto psOutline = CPLGetXMLNode(psComposition, "Outline");
2300 14 : if (psOutline)
2301 : {
2302 5 : if (!CreateOutline(psOutline))
2303 4 : return false;
2304 : }
2305 :
2306 10 : return true;
2307 : }
2308 :
2309 : /************************************************************************/
2310 : /* GDALPDFErrorHandler() */
2311 : /************************************************************************/
2312 :
2313 19 : static void CPL_STDCALL GDALPDFErrorHandler(CPL_UNUSED CPLErr eErr,
2314 : CPL_UNUSED CPLErrorNum nType,
2315 : const char *pszMsg)
2316 : {
2317 : std::vector<CPLString> *paosErrors =
2318 19 : static_cast<std::vector<CPLString> *>(CPLGetErrorHandlerUserData());
2319 19 : paosErrors->push_back(pszMsg);
2320 19 : }
2321 :
2322 : /************************************************************************/
2323 : /* GDALPDFCreateFromCompositionFile() */
2324 : /************************************************************************/
2325 :
2326 39 : GDALDataset *GDALPDFCreateFromCompositionFile(const char *pszPDFFilename,
2327 : const char *pszXMLFilename)
2328 : {
2329 76 : CPLXMLTreeCloser oXML((pszXMLFilename[0] == '<' &&
2330 37 : strstr(pszXMLFilename, "<PDFComposition") != nullptr)
2331 37 : ? CPLParseXMLString(pszXMLFilename)
2332 115 : : CPLParseXMLFile(pszXMLFilename));
2333 39 : if (!oXML.get())
2334 1 : return nullptr;
2335 38 : auto psComposition = CPLGetXMLNode(oXML.get(), "=PDFComposition");
2336 38 : if (!psComposition)
2337 : {
2338 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find PDFComposition");
2339 0 : return nullptr;
2340 : }
2341 :
2342 : // XML Validation.
2343 38 : if (CPLTestBool(CPLGetConfigOption("GDAL_XML_VALIDATION", "YES")))
2344 : {
2345 : #ifdef EMBED_RESOURCE_FILES
2346 : std::string osTmpFilename;
2347 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
2348 : #endif
2349 : #ifdef USE_ONLY_EMBEDDED_RESOURCE_FILES
2350 : const char *pszXSD = nullptr;
2351 : #else
2352 38 : const char *pszXSD = CPLFindFile("gdal", "pdfcomposition.xsd");
2353 : #endif
2354 : #ifdef EMBED_RESOURCE_FILES
2355 : if (!pszXSD)
2356 : {
2357 : static const bool bOnce [[maybe_unused]] = []()
2358 : {
2359 : CPLDebug("PDF", "Using embedded pdfcomposition.xsd");
2360 : return true;
2361 : }();
2362 : osTmpFilename = VSIMemGenerateHiddenFilename("pdfcomposition.xsd");
2363 : pszXSD = osTmpFilename.c_str();
2364 : VSIFCloseL(VSIFileFromMemBuffer(
2365 : osTmpFilename.c_str(),
2366 : const_cast<GByte *>(
2367 : reinterpret_cast<const GByte *>(PDFGetCompositionXSD())),
2368 : static_cast<int>(strlen(PDFGetCompositionXSD())),
2369 : /* bTakeOwnership = */ false));
2370 : }
2371 : #else
2372 38 : if (pszXSD != nullptr)
2373 : #endif
2374 : {
2375 76 : std::vector<CPLString> aosErrors;
2376 38 : CPLPushErrorHandlerEx(GDALPDFErrorHandler, &aosErrors);
2377 38 : const int bRet = CPLValidateXML(pszXMLFilename, pszXSD, nullptr);
2378 38 : CPLPopErrorHandler();
2379 38 : if (!bRet)
2380 : {
2381 36 : if (!aosErrors.empty() &&
2382 18 : strstr(aosErrors[0].c_str(), "missing libxml2 support") ==
2383 : nullptr)
2384 : {
2385 37 : for (size_t i = 0; i < aosErrors.size(); i++)
2386 : {
2387 19 : CPLError(CE_Warning, CPLE_AppDefined, "%s",
2388 19 : aosErrors[i].c_str());
2389 : }
2390 : }
2391 : }
2392 38 : CPLErrorReset();
2393 : }
2394 :
2395 : #ifdef EMBED_RESOURCE_FILES
2396 : if (!osTmpFilename.empty())
2397 : VSIUnlink(osTmpFilename.c_str());
2398 : #endif
2399 : }
2400 :
2401 : /* -------------------------------------------------------------------- */
2402 : /* Create file. */
2403 : /* -------------------------------------------------------------------- */
2404 38 : VSILFILE *fp = VSIFOpenL(pszPDFFilename, "wb");
2405 38 : if (fp == nullptr)
2406 : {
2407 0 : CPLError(CE_Failure, CPLE_OpenFailed, "Unable to create PDF file %s.\n",
2408 : pszPDFFilename);
2409 0 : return nullptr;
2410 : }
2411 :
2412 76 : GDALPDFComposerWriter oWriter(fp);
2413 38 : if (!oWriter.Generate(psComposition))
2414 28 : return nullptr;
2415 :
2416 10 : return new GDALFakePDFDataset();
2417 : }
|