LCOV - code coverage report
Current view: top level - frmts/pdf - pdfcreatefromcomposition.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1192 1284 92.8 %
Date: 2025-06-08 21:14:40 Functions: 30 30 100.0 %

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

Generated by: LCOV version 1.14