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

Generated by: LCOV version 1.14