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

Generated by: LCOV version 1.14