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