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