LCOV - code coverage report
Current view: top level - frmts/pdf - pdfcreatefromcomposition.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1236 1336 92.5 %
Date: 2024-05-04 12:52:34 Functions: 31 31 100.0 %

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

Generated by: LCOV version 1.14