LCOV - code coverage report
Current view: top level - frmts/pdf - pdfcreatecopy.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2399 2737 87.7 %
Date: 2024-05-04 12:52:34 Functions: 59 60 98.3 %

          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.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2012-2019, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * Permission is hereby granted, free of charge, to any person obtaining a
      11             :  * copy of this software and associated documentation files (the "Software"),
      12             :  * to deal in the Software without restriction, including without limitation
      13             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      14             :  * and/or sell copies of the Software, and to permit persons to whom the
      15             :  * Software is furnished to do so, subject to the following conditions:
      16             :  *
      17             :  * The above copyright notice and this permission notice shall be included
      18             :  * in all copies or substantial portions of the Software.
      19             :  *
      20             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      21             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      22             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      23             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      24             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      25             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      26             :  * DEALINGS IN THE SOFTWARE.
      27             :  ****************************************************************************/
      28             : 
      29             : #include "gdal_pdf.h"
      30             : #include "pdfcreatecopy.h"
      31             : 
      32             : #include "cpl_vsi_virtual.h"
      33             : #include "cpl_conv.h"
      34             : #include "cpl_error.h"
      35             : #include "ogr_spatialref.h"
      36             : #include "ogr_geometry.h"
      37             : #include "memdataset.h"
      38             : #include "vrtdataset.h"
      39             : 
      40             : #include "pdfobject.h"
      41             : 
      42             : #include <cmath>
      43             : #include <algorithm>
      44             : #include <utility>
      45             : #include <vector>
      46             : 
      47             : // #define HACK_TO_GENERATE_OCMD can be set to produce a (single layer)
      48             : // non-structured vector PDF with a OCMD (Optional Content Group Membership
      49             : // Dictionary) similar to test case of https://github.com/OSGeo/gdal/issues/8372
      50             : // like with "ogr2ogr poly.pdf poly.shp -dsco STREAM_COMPRESS=NONE -limit 1"
      51             : 
      52             : /************************************************************************/
      53             : /*                        GDALPDFBaseWriter()                           */
      54             : /************************************************************************/
      55             : 
      56         187 : GDALPDFBaseWriter::GDALPDFBaseWriter(VSILFILE *fp) : m_fp(fp)
      57             : {
      58         187 : }
      59             : 
      60             : /************************************************************************/
      61             : /*                       ~GDALPDFBaseWriter()                           */
      62             : /************************************************************************/
      63             : 
      64         187 : GDALPDFBaseWriter::~GDALPDFBaseWriter()
      65             : {
      66         187 :     Close();
      67         187 : }
      68             : 
      69             : /************************************************************************/
      70             : /*                              ~Close()                                */
      71             : /************************************************************************/
      72             : 
      73         523 : void GDALPDFBaseWriter::Close()
      74             : {
      75         523 :     if (m_fp)
      76             :     {
      77         187 :         VSIFCloseL(m_fp);
      78         187 :         m_fp = nullptr;
      79             :     }
      80         523 : }
      81             : 
      82             : /************************************************************************/
      83             : /*                           GDALPDFUpdateWriter()                      */
      84             : /************************************************************************/
      85             : 
      86          26 : GDALPDFUpdateWriter::GDALPDFUpdateWriter(VSILFILE *fp) : GDALPDFBaseWriter(fp)
      87             : {
      88          26 : }
      89             : 
      90             : /************************************************************************/
      91             : /*                          ~GDALPDFUpdateWriter()                      */
      92             : /************************************************************************/
      93             : 
      94          26 : GDALPDFUpdateWriter::~GDALPDFUpdateWriter()
      95             : {
      96          26 :     Close();
      97          26 : }
      98             : 
      99             : /************************************************************************/
     100             : /*                              ~Close()                                */
     101             : /************************************************************************/
     102             : 
     103          52 : void GDALPDFUpdateWriter::Close()
     104             : {
     105          52 :     if (m_fp)
     106             :     {
     107          26 :         CPLAssert(!m_bInWriteObj);
     108          26 :         if (m_bUpdateNeeded)
     109             :         {
     110          26 :             WriteXRefTableAndTrailer(true, m_nLastStartXRef);
     111             :         }
     112             :     }
     113          52 :     GDALPDFBaseWriter::Close();
     114          52 : }
     115             : 
     116             : /************************************************************************/
     117             : /*                          StartNewDoc()                               */
     118             : /************************************************************************/
     119             : 
     120         161 : void GDALPDFBaseWriter::StartNewDoc()
     121             : {
     122         161 :     VSIFPrintfL(m_fp, "%%PDF-1.6\n");
     123             : 
     124             :     // See PDF 1.7 reference, page 92. Write 4 non-ASCII bytes to indicate
     125             :     // that the content will be binary.
     126         161 :     VSIFPrintfL(m_fp, "%%%c%c%c%c\n", 0xFF, 0xFF, 0xFF, 0xFF);
     127             : 
     128         161 :     m_nPageResourceId = AllocNewObject();
     129         161 :     m_nCatalogId = AllocNewObject();
     130         161 : }
     131             : 
     132             : /************************************************************************/
     133             : /*                         GDALPDFWriter()                              */
     134             : /************************************************************************/
     135             : 
     136         123 : GDALPDFWriter::GDALPDFWriter(VSILFILE *fpIn) : GDALPDFBaseWriter(fpIn)
     137             : {
     138         123 :     StartNewDoc();
     139         123 : }
     140             : 
     141             : /************************************************************************/
     142             : /*                         ~GDALPDFWriter()                             */
     143             : /************************************************************************/
     144             : 
     145         123 : GDALPDFWriter::~GDALPDFWriter()
     146             : {
     147         123 :     Close();
     148         123 : }
     149             : 
     150             : /************************************************************************/
     151             : /*                          ParseIndirectRef()                          */
     152             : /************************************************************************/
     153             : 
     154          30 : static int ParseIndirectRef(const char *pszStr, GDALPDFObjectNum &nNum,
     155             :                             int &nGen)
     156             : {
     157          30 :     while (*pszStr == ' ')
     158           0 :         pszStr++;
     159             : 
     160          30 :     nNum = atoi(pszStr);
     161          64 :     while (*pszStr >= '0' && *pszStr <= '9')
     162          34 :         pszStr++;
     163          30 :     if (*pszStr != ' ')
     164           0 :         return FALSE;
     165             : 
     166          60 :     while (*pszStr == ' ')
     167          30 :         pszStr++;
     168             : 
     169          30 :     nGen = atoi(pszStr);
     170          60 :     while (*pszStr >= '0' && *pszStr <= '9')
     171          30 :         pszStr++;
     172          30 :     if (*pszStr != ' ')
     173           0 :         return FALSE;
     174             : 
     175          60 :     while (*pszStr == ' ')
     176          30 :         pszStr++;
     177             : 
     178          30 :     return *pszStr == 'R';
     179             : }
     180             : 
     181             : /************************************************************************/
     182             : /*                       ParseTrailerAndXRef()                          */
     183             : /************************************************************************/
     184             : 
     185          26 : int GDALPDFUpdateWriter::ParseTrailerAndXRef()
     186             : {
     187          26 :     VSIFSeekL(m_fp, 0, SEEK_END);
     188             :     char szBuf[1024 + 1];
     189          26 :     vsi_l_offset nOffset = VSIFTellL(m_fp);
     190             : 
     191          26 :     if (nOffset > 128)
     192          26 :         nOffset -= 128;
     193             :     else
     194           0 :         nOffset = 0;
     195             : 
     196             :     /* Find startxref section */
     197          26 :     VSIFSeekL(m_fp, nOffset, SEEK_SET);
     198          26 :     int nRead = (int)VSIFReadL(szBuf, 1, 128, m_fp);
     199          26 :     szBuf[nRead] = 0;
     200          26 :     if (nRead < 9)
     201           0 :         return FALSE;
     202             : 
     203          26 :     const char *pszStartXRef = nullptr;
     204             :     int i;
     205         334 :     for (i = nRead - 9; i >= 0; i--)
     206             :     {
     207         334 :         if (STARTS_WITH(szBuf + i, "startxref"))
     208             :         {
     209          26 :             pszStartXRef = szBuf + i;
     210          26 :             break;
     211             :         }
     212             :     }
     213          26 :     if (pszStartXRef == nullptr)
     214             :     {
     215           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find startxref");
     216           0 :         return FALSE;
     217             :     }
     218          26 :     pszStartXRef += 9;
     219          52 :     while (*pszStartXRef == '\r' || *pszStartXRef == '\n')
     220          26 :         pszStartXRef++;
     221          26 :     if (*pszStartXRef == '\0')
     222             :     {
     223           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find startxref");
     224           0 :         return FALSE;
     225             :     }
     226             : 
     227          26 :     m_nLastStartXRef = CPLScanUIntBig(pszStartXRef, 16);
     228             : 
     229             :     /* Skip to beginning of xref section */
     230          26 :     VSIFSeekL(m_fp, m_nLastStartXRef, SEEK_SET);
     231             : 
     232             :     /* And skip to trailer */
     233          26 :     const char *pszLine = nullptr;
     234         296 :     while ((pszLine = CPLReadLineL(m_fp)) != nullptr)
     235             :     {
     236         296 :         if (STARTS_WITH(pszLine, "trailer"))
     237          26 :             break;
     238             :     }
     239             : 
     240          26 :     if (pszLine == nullptr)
     241             :     {
     242           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find trailer");
     243           0 :         return FALSE;
     244             :     }
     245             : 
     246             :     /* Read trailer content */
     247          26 :     nRead = (int)VSIFReadL(szBuf, 1, 1024, m_fp);
     248          26 :     szBuf[nRead] = 0;
     249             : 
     250             :     /* Find XRef size */
     251          26 :     const char *pszSize = strstr(szBuf, "/Size");
     252          26 :     if (pszSize == nullptr)
     253             :     {
     254           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find trailer /Size");
     255           0 :         return FALSE;
     256             :     }
     257          26 :     pszSize += 5;
     258          52 :     while (*pszSize == ' ')
     259          26 :         pszSize++;
     260          26 :     m_nLastXRefSize = atoi(pszSize);
     261             : 
     262             :     /* Find Root object */
     263          26 :     const char *pszRoot = strstr(szBuf, "/Root");
     264          26 :     if (pszRoot == nullptr)
     265             :     {
     266           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find trailer /Root");
     267           0 :         return FALSE;
     268             :     }
     269          26 :     pszRoot += 5;
     270          52 :     while (*pszRoot == ' ')
     271          26 :         pszRoot++;
     272             : 
     273          26 :     if (!ParseIndirectRef(pszRoot, m_nCatalogId, m_nCatalogGen))
     274             :     {
     275           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot parse trailer /Root");
     276           0 :         return FALSE;
     277             :     }
     278             : 
     279             :     /* Find Info object */
     280          26 :     const char *pszInfo = strstr(szBuf, "/Info");
     281          26 :     if (pszInfo != nullptr)
     282             :     {
     283           4 :         pszInfo += 5;
     284           8 :         while (*pszInfo == ' ')
     285           4 :             pszInfo++;
     286             : 
     287           4 :         if (!ParseIndirectRef(pszInfo, m_nInfoId, m_nInfoGen))
     288             :         {
     289           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot parse trailer /Info");
     290           0 :             m_nInfoId = 0;
     291           0 :             m_nInfoGen = 0;
     292             :         }
     293             :     }
     294             : 
     295          26 :     VSIFSeekL(m_fp, 0, SEEK_END);
     296             : 
     297          26 :     return TRUE;
     298             : }
     299             : 
     300             : /************************************************************************/
     301             : /*                              Close()                                 */
     302             : /************************************************************************/
     303             : 
     304         246 : void GDALPDFWriter::Close()
     305             : {
     306         246 :     if (m_fp)
     307             :     {
     308         123 :         CPLAssert(!m_bInWriteObj);
     309         123 :         if (m_nPageResourceId.toBool())
     310             :         {
     311         123 :             WritePages();
     312         123 :             WriteXRefTableAndTrailer(false, 0);
     313             :         }
     314             :     }
     315         246 :     GDALPDFBaseWriter::Close();
     316         246 : }
     317             : 
     318             : /************************************************************************/
     319             : /*                           UpdateProj()                               */
     320             : /************************************************************************/
     321             : 
     322          14 : void GDALPDFUpdateWriter::UpdateProj(GDALDataset *poSrcDS, double dfDPI,
     323             :                                      GDALPDFDictionaryRW *poPageDict,
     324             :                                      const GDALPDFObjectNum &nPageId,
     325             :                                      int nPageGen)
     326             : {
     327          14 :     m_bUpdateNeeded = true;
     328          14 :     if ((int)m_asXRefEntries.size() < m_nLastXRefSize - 1)
     329          14 :         m_asXRefEntries.resize(m_nLastXRefSize - 1);
     330             : 
     331          14 :     GDALPDFObjectNum nViewportId;
     332          14 :     GDALPDFObjectNum nLGIDictId;
     333             : 
     334          14 :     CPLAssert(nPageId.toBool());
     335          14 :     CPLAssert(poPageDict != nullptr);
     336             : 
     337          14 :     PDFMargins sMargins;
     338             : 
     339             :     const char *pszGEO_ENCODING =
     340          14 :         CPLGetConfigOption("GDAL_PDF_GEO_ENCODING", "ISO32000");
     341          14 :     if (EQUAL(pszGEO_ENCODING, "ISO32000") || EQUAL(pszGEO_ENCODING, "BOTH"))
     342             :         nViewportId = WriteSRS_ISO32000(poSrcDS, dfDPI * USER_UNIT_IN_INCH,
     343          12 :                                         nullptr, &sMargins, TRUE);
     344          14 :     if (EQUAL(pszGEO_ENCODING, "OGC_BP") || EQUAL(pszGEO_ENCODING, "BOTH"))
     345             :         nLGIDictId = WriteSRS_OGC_BP(poSrcDS, dfDPI * USER_UNIT_IN_INCH,
     346           2 :                                      nullptr, &sMargins);
     347             : 
     348             : #ifdef invalidate_xref_entry
     349             :     GDALPDFObject *poVP = poPageDict->Get("VP");
     350             :     if (poVP)
     351             :     {
     352             :         if (poVP->GetType() == PDFObjectType_Array &&
     353             :             poVP->GetArray()->GetLength() == 1)
     354             :             poVP = poVP->GetArray()->Get(0);
     355             : 
     356             :         int nVPId = poVP->GetRefNum();
     357             :         if (nVPId)
     358             :         {
     359             :             m_asXRefEntries[nVPId - 1].bFree = TRUE;
     360             :             m_asXRefEntries[nVPId - 1].nGen++;
     361             :         }
     362             :     }
     363             : #endif
     364             : 
     365          14 :     poPageDict->Remove("VP");
     366          14 :     poPageDict->Remove("LGIDict");
     367             : 
     368          14 :     if (nViewportId.toBool())
     369             :     {
     370          10 :         poPageDict->Add("VP", &((new GDALPDFArrayRW())->Add(nViewportId, 0)));
     371             :     }
     372             : 
     373          14 :     if (nLGIDictId.toBool())
     374             :     {
     375           2 :         poPageDict->Add("LGIDict", nLGIDictId, 0);
     376             :     }
     377             : 
     378          14 :     StartObj(nPageId, nPageGen);
     379          14 :     VSIFPrintfL(m_fp, "%s\n", poPageDict->Serialize().c_str());
     380          14 :     EndObj();
     381          14 : }
     382             : 
     383             : /************************************************************************/
     384             : /*                           UpdateInfo()                               */
     385             : /************************************************************************/
     386             : 
     387           6 : void GDALPDFUpdateWriter::UpdateInfo(GDALDataset *poSrcDS)
     388             : {
     389           6 :     m_bUpdateNeeded = true;
     390           6 :     if ((int)m_asXRefEntries.size() < m_nLastXRefSize - 1)
     391           6 :         m_asXRefEntries.resize(m_nLastXRefSize - 1);
     392             : 
     393           6 :     auto nNewInfoId = SetInfo(poSrcDS, nullptr);
     394             :     /* Write empty info, because podofo driver will find the dangling info
     395             :      * instead */
     396           6 :     if (!nNewInfoId.toBool() && m_nInfoId.toBool())
     397             :     {
     398             : #ifdef invalidate_xref_entry
     399             :         m_asXRefEntries[m_nInfoId.toInt() - 1].bFree = TRUE;
     400             :         m_asXRefEntries[m_nInfoId.toInt() - 1].nGen++;
     401             : #else
     402           2 :         StartObj(m_nInfoId, m_nInfoGen);
     403           2 :         VSIFPrintfL(m_fp, "<< >>\n");
     404           2 :         EndObj();
     405             : #endif
     406             :     }
     407           6 : }
     408             : 
     409             : /************************************************************************/
     410             : /*                           UpdateXMP()                                */
     411             : /************************************************************************/
     412             : 
     413           6 : void GDALPDFUpdateWriter::UpdateXMP(GDALDataset *poSrcDS,
     414             :                                     GDALPDFDictionaryRW *poCatalogDict)
     415             : {
     416           6 :     m_bUpdateNeeded = true;
     417           6 :     if ((int)m_asXRefEntries.size() < m_nLastXRefSize - 1)
     418           6 :         m_asXRefEntries.resize(m_nLastXRefSize - 1);
     419             : 
     420           6 :     CPLAssert(m_nCatalogId.toBool());
     421           6 :     CPLAssert(poCatalogDict != nullptr);
     422             : 
     423           6 :     GDALPDFObject *poMetadata = poCatalogDict->Get("Metadata");
     424           6 :     if (poMetadata)
     425             :     {
     426           4 :         m_nXMPId = poMetadata->GetRefNum();
     427           4 :         m_nXMPGen = poMetadata->GetRefGen();
     428             :     }
     429             : 
     430           6 :     poCatalogDict->Remove("Metadata");
     431           6 :     auto nNewXMPId = SetXMP(poSrcDS, nullptr);
     432             : 
     433             :     /* Write empty metadata, because podofo driver will find the dangling info
     434             :      * instead */
     435           6 :     if (!nNewXMPId.toBool() && m_nXMPId.toBool())
     436             :     {
     437           2 :         StartObj(m_nXMPId, m_nXMPGen);
     438           2 :         VSIFPrintfL(m_fp, "<< >>\n");
     439           2 :         EndObj();
     440             :     }
     441             : 
     442           6 :     if (m_nXMPId.toBool())
     443           6 :         poCatalogDict->Add("Metadata", m_nXMPId, 0);
     444             : 
     445           6 :     StartObj(m_nCatalogId, m_nCatalogGen);
     446           6 :     VSIFPrintfL(m_fp, "%s\n", poCatalogDict->Serialize().c_str());
     447           6 :     EndObj();
     448           6 : }
     449             : 
     450             : /************************************************************************/
     451             : /*                           AllocNewObject()                           */
     452             : /************************************************************************/
     453             : 
     454        2350 : GDALPDFObjectNum GDALPDFBaseWriter::AllocNewObject()
     455             : {
     456        2350 :     m_asXRefEntries.push_back(GDALXRefEntry());
     457        2350 :     return GDALPDFObjectNum(static_cast<int>(m_asXRefEntries.size()));
     458             : }
     459             : 
     460             : /************************************************************************/
     461             : /*                        WriteXRefTableAndTrailer()                    */
     462             : /************************************************************************/
     463             : 
     464         187 : void GDALPDFBaseWriter::WriteXRefTableAndTrailer(bool bUpdate,
     465             :                                                  vsi_l_offset nLastStartXRef)
     466             : {
     467         187 :     vsi_l_offset nOffsetXREF = VSIFTellL(m_fp);
     468         187 :     VSIFPrintfL(m_fp, "xref\n");
     469             : 
     470             :     char buffer[16];
     471         187 :     if (bUpdate)
     472             :     {
     473          26 :         VSIFPrintfL(m_fp, "0 1\n");
     474          26 :         VSIFPrintfL(m_fp, "0000000000 65535 f \n");
     475         358 :         for (size_t i = 0; i < m_asXRefEntries.size();)
     476             :         {
     477         332 :             if (m_asXRefEntries[i].nOffset != 0 || m_asXRefEntries[i].bFree)
     478             :             {
     479             :                 /* Find number of consecutive objects */
     480          44 :                 size_t nCount = 1;
     481         104 :                 while (i + nCount < m_asXRefEntries.size() &&
     482          40 :                        (m_asXRefEntries[i + nCount].nOffset != 0 ||
     483          20 :                         m_asXRefEntries[i + nCount].bFree))
     484          20 :                     nCount++;
     485             : 
     486          44 :                 VSIFPrintfL(m_fp, "%d %d\n", (int)i + 1, (int)nCount);
     487          44 :                 size_t iEnd = i + nCount;
     488         108 :                 for (; i < iEnd; i++)
     489             :                 {
     490          64 :                     snprintf(buffer, sizeof(buffer),
     491             :                              "%010" CPL_FRMT_GB_WITHOUT_PREFIX "u",
     492          64 :                              m_asXRefEntries[i].nOffset);
     493         128 :                     VSIFPrintfL(m_fp, "%s %05d %c \n", buffer,
     494          64 :                                 m_asXRefEntries[i].nGen,
     495          64 :                                 m_asXRefEntries[i].bFree ? 'f' : 'n');
     496             :                 }
     497             :             }
     498             :             else
     499             :             {
     500         288 :                 i++;
     501             :             }
     502             :         }
     503             :     }
     504             :     else
     505             :     {
     506         161 :         VSIFPrintfL(m_fp, "%d %d\n", 0, (int)m_asXRefEntries.size() + 1);
     507         161 :         VSIFPrintfL(m_fp, "0000000000 65535 f \n");
     508        2475 :         for (size_t i = 0; i < m_asXRefEntries.size(); i++)
     509             :         {
     510        2314 :             snprintf(buffer, sizeof(buffer),
     511             :                      "%010" CPL_FRMT_GB_WITHOUT_PREFIX "u",
     512        2314 :                      m_asXRefEntries[i].nOffset);
     513        2314 :             VSIFPrintfL(m_fp, "%s %05d n \n", buffer, m_asXRefEntries[i].nGen);
     514             :         }
     515             :     }
     516             : 
     517         187 :     VSIFPrintfL(m_fp, "trailer\n");
     518         374 :     GDALPDFDictionaryRW oDict;
     519         187 :     oDict.Add("Size", (int)m_asXRefEntries.size() + 1)
     520         187 :         .Add("Root", m_nCatalogId, m_nCatalogGen);
     521         187 :     if (m_nInfoId.toBool())
     522          11 :         oDict.Add("Info", m_nInfoId, m_nInfoGen);
     523         187 :     if (nLastStartXRef)
     524          26 :         oDict.Add("Prev", (double)nLastStartXRef);
     525         187 :     VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
     526             : 
     527         187 :     VSIFPrintfL(m_fp,
     528             :                 "startxref\n" CPL_FRMT_GUIB "\n"
     529             :                 "%%%%EOF\n",
     530             :                 nOffsetXREF);
     531         187 : }
     532             : 
     533             : /************************************************************************/
     534             : /*                              StartObj()                              */
     535             : /************************************************************************/
     536             : 
     537        2355 : void GDALPDFBaseWriter::StartObj(const GDALPDFObjectNum &nObjectId, int nGen)
     538             : {
     539        2355 :     CPLAssert(!m_bInWriteObj);
     540        2355 :     CPLAssert(nObjectId.toInt() - 1 < (int)m_asXRefEntries.size());
     541        2355 :     CPLAssert(m_asXRefEntries[nObjectId.toInt() - 1].nOffset == 0);
     542        2355 :     m_asXRefEntries[nObjectId.toInt() - 1].nOffset = VSIFTellL(m_fp);
     543        2355 :     m_asXRefEntries[nObjectId.toInt() - 1].nGen = nGen;
     544        2355 :     VSIFPrintfL(m_fp, "%d %d obj\n", nObjectId.toInt(), nGen);
     545        2355 :     m_bInWriteObj = true;
     546        2355 : }
     547             : 
     548             : /************************************************************************/
     549             : /*                               EndObj()                               */
     550             : /************************************************************************/
     551             : 
     552        2355 : void GDALPDFBaseWriter::EndObj()
     553             : {
     554        2355 :     CPLAssert(m_bInWriteObj);
     555        2355 :     CPLAssert(!m_fpBack);
     556        2355 :     VSIFPrintfL(m_fp, "endobj\n");
     557        2355 :     m_bInWriteObj = false;
     558        2355 : }
     559             : 
     560             : /************************************************************************/
     561             : /*                         StartObjWithStream()                         */
     562             : /************************************************************************/
     563             : 
     564         520 : void GDALPDFBaseWriter::StartObjWithStream(const GDALPDFObjectNum &nObjectId,
     565             :                                            GDALPDFDictionaryRW &oDict,
     566             :                                            bool bDeflate)
     567             : {
     568         520 :     CPLAssert(!m_nContentLengthId.toBool());
     569         520 :     CPLAssert(!m_fpGZip);
     570         520 :     CPLAssert(!m_fpBack);
     571         520 :     CPLAssert(m_nStreamStart == 0);
     572             : 
     573         520 :     m_nContentLengthId = AllocNewObject();
     574             : 
     575         520 :     StartObj(nObjectId);
     576             :     {
     577         520 :         oDict.Add("Length", m_nContentLengthId, 0);
     578         520 :         if (bDeflate)
     579             :         {
     580         445 :             oDict.Add("Filter", GDALPDFObjectRW::CreateName("FlateDecode"));
     581             :         }
     582         520 :         VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
     583             :     }
     584             : 
     585             :     /* -------------------------------------------------------------- */
     586             :     /*  Write content stream                                          */
     587             :     /* -------------------------------------------------------------- */
     588         520 :     VSIFPrintfL(m_fp, "stream\n");
     589         520 :     m_nStreamStart = VSIFTellL(m_fp);
     590             : 
     591         520 :     m_fpGZip = nullptr;
     592         520 :     m_fpBack = m_fp;
     593         520 :     if (bDeflate)
     594             :     {
     595         445 :         m_fpGZip = VSICreateGZipWritable(m_fp, TRUE, FALSE);
     596         445 :         m_fp = m_fpGZip;
     597             :     }
     598         520 : }
     599             : 
     600             : /************************************************************************/
     601             : /*                          EndObjWithStream()                          */
     602             : /************************************************************************/
     603             : 
     604         520 : void GDALPDFBaseWriter::EndObjWithStream()
     605             : {
     606         520 :     if (m_fpGZip)
     607         445 :         VSIFCloseL(m_fpGZip);
     608         520 :     m_fp = m_fpBack;
     609         520 :     m_fpBack = nullptr;
     610             : 
     611         520 :     vsi_l_offset nStreamEnd = VSIFTellL(m_fp);
     612         520 :     if (m_fpGZip)
     613         445 :         VSIFPrintfL(m_fp, "\n");
     614         520 :     m_fpGZip = nullptr;
     615         520 :     VSIFPrintfL(m_fp, "endstream\n");
     616         520 :     EndObj();
     617             : 
     618         520 :     StartObj(m_nContentLengthId);
     619         520 :     VSIFPrintfL(m_fp, "   %ld\n",
     620         520 :                 static_cast<long>(nStreamEnd - m_nStreamStart));
     621         520 :     EndObj();
     622             : 
     623         520 :     m_nContentLengthId = GDALPDFObjectNum();
     624         520 :     m_nStreamStart = 0;
     625         520 : }
     626             : 
     627             : /************************************************************************/
     628             : /*                         GDALPDFFind4Corners()                        */
     629             : /************************************************************************/
     630             : 
     631          24 : static void GDALPDFFind4Corners(const GDAL_GCP *pasGCPList, int &iUL, int &iUR,
     632             :                                 int &iLR, int &iLL)
     633             : {
     634          24 :     double dfMeanX = 0.0;
     635          24 :     double dfMeanY = 0.0;
     636             :     int i;
     637             : 
     638          24 :     iUL = 0;
     639          24 :     iUR = 0;
     640          24 :     iLR = 0;
     641          24 :     iLL = 0;
     642             : 
     643         120 :     for (i = 0; i < 4; i++)
     644             :     {
     645          96 :         dfMeanX += pasGCPList[i].dfGCPPixel;
     646          96 :         dfMeanY += pasGCPList[i].dfGCPLine;
     647             :     }
     648          24 :     dfMeanX /= 4;
     649          24 :     dfMeanY /= 4;
     650             : 
     651         120 :     for (i = 0; i < 4; i++)
     652             :     {
     653          96 :         if (pasGCPList[i].dfGCPPixel < dfMeanX &&
     654          48 :             pasGCPList[i].dfGCPLine < dfMeanY)
     655          24 :             iUL = i;
     656             : 
     657          72 :         else if (pasGCPList[i].dfGCPPixel > dfMeanX &&
     658          48 :                  pasGCPList[i].dfGCPLine < dfMeanY)
     659          24 :             iUR = i;
     660             : 
     661          48 :         else if (pasGCPList[i].dfGCPPixel > dfMeanX &&
     662          24 :                  pasGCPList[i].dfGCPLine > dfMeanY)
     663          24 :             iLR = i;
     664             : 
     665          24 :         else if (pasGCPList[i].dfGCPPixel < dfMeanX &&
     666          24 :                  pasGCPList[i].dfGCPLine > dfMeanY)
     667          24 :             iLL = i;
     668             :     }
     669          24 : }
     670             : 
     671             : /************************************************************************/
     672             : /*                         WriteSRS_ISO32000()                          */
     673             : /************************************************************************/
     674             : 
     675         119 : GDALPDFObjectNum GDALPDFBaseWriter::WriteSRS_ISO32000(GDALDataset *poSrcDS,
     676             :                                                       double dfUserUnit,
     677             :                                                       const char *pszNEATLINE,
     678             :                                                       PDFMargins *psMargins,
     679             :                                                       int bWriteViewport)
     680             : {
     681         119 :     int nWidth = poSrcDS->GetRasterXSize();
     682         119 :     int nHeight = poSrcDS->GetRasterYSize();
     683         119 :     const char *pszWKT = poSrcDS->GetProjectionRef();
     684             :     double adfGeoTransform[6];
     685             : 
     686         119 :     int bHasGT = (poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None);
     687             :     const GDAL_GCP *pasGCPList =
     688         119 :         (poSrcDS->GetGCPCount() == 4) ? poSrcDS->GetGCPs() : nullptr;
     689         119 :     if (pasGCPList != nullptr)
     690           2 :         pszWKT = poSrcDS->GetGCPProjection();
     691             : 
     692         119 :     if (!bHasGT && pasGCPList == nullptr)
     693          20 :         return GDALPDFObjectNum();
     694             : 
     695          99 :     if (pszWKT == nullptr || EQUAL(pszWKT, ""))
     696          17 :         return GDALPDFObjectNum();
     697             : 
     698             :     double adfGPTS[8];
     699             : 
     700          82 :     double dfULPixel = 0;
     701          82 :     double dfULLine = 0;
     702          82 :     double dfLRPixel = nWidth;
     703          82 :     double dfLRLine = nHeight;
     704             : 
     705         164 :     std::vector<gdal::GCP> asNeatLineGCPs(4);
     706          82 :     if (pszNEATLINE == nullptr)
     707          80 :         pszNEATLINE = poSrcDS->GetMetadataItem("NEATLINE");
     708          82 :     if (bHasGT && pszNEATLINE != nullptr && pszNEATLINE[0] != '\0')
     709             :     {
     710          10 :         OGRGeometry *poGeom = nullptr;
     711          10 :         OGRGeometryFactory::createFromWkt(pszNEATLINE, nullptr, &poGeom);
     712          20 :         if (poGeom != nullptr &&
     713          10 :             wkbFlatten(poGeom->getGeometryType()) == wkbPolygon)
     714             :         {
     715          10 :             OGRLineString *poLS = poGeom->toPolygon()->getExteriorRing();
     716             :             double adfGeoTransformInv[6];
     717          20 :             if (poLS != nullptr && poLS->getNumPoints() == 5 &&
     718          10 :                 GDALInvGeoTransform(adfGeoTransform, adfGeoTransformInv))
     719             :             {
     720          50 :                 for (int i = 0; i < 4; i++)
     721             :                 {
     722          40 :                     const double X = poLS->getX(i);
     723          40 :                     const double Y = poLS->getY(i);
     724          40 :                     asNeatLineGCPs[i].X() = X;
     725          40 :                     asNeatLineGCPs[i].Y() = Y;
     726          40 :                     const double x = adfGeoTransformInv[0] +
     727          40 :                                      X * adfGeoTransformInv[1] +
     728          40 :                                      Y * adfGeoTransformInv[2];
     729          40 :                     const double y = adfGeoTransformInv[3] +
     730          40 :                                      X * adfGeoTransformInv[4] +
     731          40 :                                      Y * adfGeoTransformInv[5];
     732          40 :                     asNeatLineGCPs[i].Pixel() = x;
     733          40 :                     asNeatLineGCPs[i].Line() = y;
     734             :                 }
     735             : 
     736          10 :                 int iUL = 0;
     737          10 :                 int iUR = 0;
     738          10 :                 int iLR = 0;
     739          10 :                 int iLL = 0;
     740          10 :                 GDALPDFFind4Corners(gdal::GCP::c_ptr(asNeatLineGCPs), iUL, iUR,
     741             :                                     iLR, iLL);
     742             : 
     743          10 :                 if (fabs(asNeatLineGCPs[iUL].Pixel() -
     744          10 :                          asNeatLineGCPs[iLL].Pixel()) > .5 ||
     745          10 :                     fabs(asNeatLineGCPs[iUR].Pixel() -
     746          10 :                          asNeatLineGCPs[iLR].Pixel()) > .5 ||
     747          10 :                     fabs(asNeatLineGCPs[iUL].Line() -
     748          30 :                          asNeatLineGCPs[iUR].Line()) > .5 ||
     749          10 :                     fabs(asNeatLineGCPs[iLL].Line() -
     750          10 :                          asNeatLineGCPs[iLR].Line()) > .5)
     751             :                 {
     752           0 :                     CPLError(CE_Warning, CPLE_NotSupported,
     753             :                              "Neatline coordinates should form a rectangle in "
     754             :                              "pixel space. Ignoring it");
     755           0 :                     for (int i = 0; i < 4; i++)
     756             :                     {
     757           0 :                         CPLDebug("PDF", "pixel[%d] = %.1f, line[%d] = %.1f", i,
     758           0 :                                  asNeatLineGCPs[i].Pixel(), i,
     759           0 :                                  asNeatLineGCPs[i].Line());
     760             :                     }
     761             :                 }
     762             :                 else
     763             :                 {
     764          10 :                     pasGCPList = gdal::GCP::c_ptr(asNeatLineGCPs);
     765             :                 }
     766             :             }
     767             :         }
     768          10 :         delete poGeom;
     769             :     }
     770             : 
     771          82 :     if (pasGCPList)
     772             :     {
     773          12 :         int iUL = 0;
     774          12 :         int iUR = 0;
     775          12 :         int iLR = 0;
     776          12 :         int iLL = 0;
     777          12 :         GDALPDFFind4Corners(pasGCPList, iUL, iUR, iLR, iLL);
     778             : 
     779          12 :         if (fabs(pasGCPList[iUL].dfGCPPixel - pasGCPList[iLL].dfGCPPixel) >
     780          12 :                 .5 ||
     781          12 :             fabs(pasGCPList[iUR].dfGCPPixel - pasGCPList[iLR].dfGCPPixel) >
     782          12 :                 .5 ||
     783          12 :             fabs(pasGCPList[iUL].dfGCPLine - pasGCPList[iUR].dfGCPLine) > .5 ||
     784          12 :             fabs(pasGCPList[iLL].dfGCPLine - pasGCPList[iLR].dfGCPLine) > .5)
     785             :         {
     786           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     787             :                      "GCPs should form a rectangle in pixel space");
     788           0 :             return GDALPDFObjectNum();
     789             :         }
     790             : 
     791          12 :         dfULPixel = pasGCPList[iUL].dfGCPPixel;
     792          12 :         dfULLine = pasGCPList[iUL].dfGCPLine;
     793          12 :         dfLRPixel = pasGCPList[iLR].dfGCPPixel;
     794          12 :         dfLRLine = pasGCPList[iLR].dfGCPLine;
     795             : 
     796             :         /* Upper-left */
     797          12 :         adfGPTS[0] = pasGCPList[iUL].dfGCPX;
     798          12 :         adfGPTS[1] = pasGCPList[iUL].dfGCPY;
     799             : 
     800             :         /* Lower-left */
     801          12 :         adfGPTS[2] = pasGCPList[iLL].dfGCPX;
     802          12 :         adfGPTS[3] = pasGCPList[iLL].dfGCPY;
     803             : 
     804             :         /* Lower-right */
     805          12 :         adfGPTS[4] = pasGCPList[iLR].dfGCPX;
     806          12 :         adfGPTS[5] = pasGCPList[iLR].dfGCPY;
     807             : 
     808             :         /* Upper-right */
     809          12 :         adfGPTS[6] = pasGCPList[iUR].dfGCPX;
     810          12 :         adfGPTS[7] = pasGCPList[iUR].dfGCPY;
     811             :     }
     812             :     else
     813             :     {
     814             :         /* Upper-left */
     815          70 :         adfGPTS[0] = APPLY_GT_X(adfGeoTransform, 0, 0);
     816          70 :         adfGPTS[1] = APPLY_GT_Y(adfGeoTransform, 0, 0);
     817             : 
     818             :         /* Lower-left */
     819          70 :         adfGPTS[2] = APPLY_GT_X(adfGeoTransform, 0, nHeight);
     820          70 :         adfGPTS[3] = APPLY_GT_Y(adfGeoTransform, 0, nHeight);
     821             : 
     822             :         /* Lower-right */
     823          70 :         adfGPTS[4] = APPLY_GT_X(adfGeoTransform, nWidth, nHeight);
     824          70 :         adfGPTS[5] = APPLY_GT_Y(adfGeoTransform, nWidth, nHeight);
     825             : 
     826             :         /* Upper-right */
     827          70 :         adfGPTS[6] = APPLY_GT_X(adfGeoTransform, nWidth, 0);
     828          70 :         adfGPTS[7] = APPLY_GT_Y(adfGeoTransform, nWidth, 0);
     829             :     }
     830             : 
     831          82 :     OGRSpatialReferenceH hSRS = OSRNewSpatialReference(pszWKT);
     832          82 :     if (hSRS == nullptr)
     833           0 :         return GDALPDFObjectNum();
     834          82 :     OSRSetAxisMappingStrategy(hSRS, OAMS_TRADITIONAL_GIS_ORDER);
     835          82 :     OGRSpatialReferenceH hSRSGeog = OSRCloneGeogCS(hSRS);
     836          82 :     if (hSRSGeog == nullptr)
     837             :     {
     838           0 :         OSRDestroySpatialReference(hSRS);
     839           0 :         return GDALPDFObjectNum();
     840             :     }
     841          82 :     OSRSetAxisMappingStrategy(hSRSGeog, OAMS_TRADITIONAL_GIS_ORDER);
     842             :     OGRCoordinateTransformationH hCT =
     843          82 :         OCTNewCoordinateTransformation(hSRS, hSRSGeog);
     844          82 :     if (hCT == nullptr)
     845             :     {
     846           0 :         OSRDestroySpatialReference(hSRS);
     847           0 :         OSRDestroySpatialReference(hSRSGeog);
     848           0 :         return GDALPDFObjectNum();
     849             :     }
     850             : 
     851          82 :     int bSuccess = TRUE;
     852             : 
     853          82 :     bSuccess &= (OCTTransform(hCT, 1, adfGPTS + 0, adfGPTS + 1, nullptr) == 1);
     854          82 :     bSuccess &= (OCTTransform(hCT, 1, adfGPTS + 2, adfGPTS + 3, nullptr) == 1);
     855          82 :     bSuccess &= (OCTTransform(hCT, 1, adfGPTS + 4, adfGPTS + 5, nullptr) == 1);
     856          82 :     bSuccess &= (OCTTransform(hCT, 1, adfGPTS + 6, adfGPTS + 7, nullptr) == 1);
     857             : 
     858          82 :     if (!bSuccess)
     859             :     {
     860           0 :         OSRDestroySpatialReference(hSRS);
     861           0 :         OSRDestroySpatialReference(hSRSGeog);
     862           0 :         OCTDestroyCoordinateTransformation(hCT);
     863           0 :         return GDALPDFObjectNum();
     864             :     }
     865             : 
     866          82 :     const char *pszAuthorityCode = OSRGetAuthorityCode(hSRS, nullptr);
     867          82 :     const char *pszAuthorityName = OSRGetAuthorityName(hSRS, nullptr);
     868          82 :     int nEPSGCode = 0;
     869         147 :     if (pszAuthorityName != nullptr && pszAuthorityCode != nullptr &&
     870          65 :         (EQUAL(pszAuthorityName, "EPSG") ||
     871           0 :          (EQUAL(pszAuthorityName, "ESRI") &&
     872           0 :           CPLTestBool(
     873             :               CPLGetConfigOption("GDAL_PDF_WRITE_ESRI_CODE_AS_EPSG", "NO")))))
     874             :     {
     875          65 :         nEPSGCode = atoi(pszAuthorityCode);
     876             :     }
     877             : 
     878          82 :     int bIsGeographic = OSRIsGeographic(hSRS);
     879             : 
     880          82 :     OSRMorphToESRI(hSRS);
     881          82 :     char *pszESRIWKT = nullptr;
     882          82 :     OSRExportToWkt(hSRS, &pszESRIWKT);
     883             : 
     884          82 :     OSRDestroySpatialReference(hSRS);
     885          82 :     OSRDestroySpatialReference(hSRSGeog);
     886          82 :     OCTDestroyCoordinateTransformation(hCT);
     887          82 :     hSRS = nullptr;
     888          82 :     hSRSGeog = nullptr;
     889          82 :     hCT = nullptr;
     890             : 
     891          82 :     if (pszESRIWKT == nullptr)
     892           0 :         return GDALPDFObjectNum();
     893             : 
     894          82 :     auto nViewportId = (bWriteViewport) ? AllocNewObject() : GDALPDFObjectNum();
     895          82 :     auto nMeasureId = AllocNewObject();
     896          82 :     auto nGCSId = AllocNewObject();
     897             : 
     898          82 :     if (nViewportId.toBool())
     899             :     {
     900          82 :         StartObj(nViewportId);
     901         164 :         GDALPDFDictionaryRW oViewPortDict;
     902          82 :         oViewPortDict.Add("Type", GDALPDFObjectRW::CreateName("Viewport"))
     903          82 :             .Add("Name", "Layer")
     904          82 :             .Add("BBox", &((new GDALPDFArrayRW())
     905          82 :                                ->Add(dfULPixel / dfUserUnit + psMargins->nLeft)
     906          82 :                                .Add((nHeight - dfLRLine) / dfUserUnit +
     907          82 :                                     psMargins->nBottom)
     908          82 :                                .Add(dfLRPixel / dfUserUnit + psMargins->nLeft)
     909          82 :                                .Add((nHeight - dfULLine) / dfUserUnit +
     910          82 :                                     psMargins->nBottom)))
     911          82 :             .Add("Measure", nMeasureId, 0);
     912          82 :         VSIFPrintfL(m_fp, "%s\n", oViewPortDict.Serialize().c_str());
     913          82 :         EndObj();
     914             :     }
     915             : 
     916          82 :     StartObj(nMeasureId);
     917         164 :     GDALPDFDictionaryRW oMeasureDict;
     918          82 :     oMeasureDict.Add("Type", GDALPDFObjectRW::CreateName("Measure"))
     919          82 :         .Add("Subtype", GDALPDFObjectRW::CreateName("GEO"))
     920          82 :         .Add("Bounds", &((new GDALPDFArrayRW())
     921          82 :                              ->Add(0)
     922          82 :                              .Add(1)
     923          82 :                              .Add(0)
     924          82 :                              .Add(0)
     925          82 :                              .Add(1)
     926          82 :                              .Add(0)
     927          82 :                              .Add(1)
     928          82 :                              .Add(1)))
     929          82 :         .Add("GPTS", &((new GDALPDFArrayRW())
     930          82 :                            ->Add(adfGPTS[1])
     931          82 :                            .Add(adfGPTS[0])
     932          82 :                            .Add(adfGPTS[3])
     933          82 :                            .Add(adfGPTS[2])
     934          82 :                            .Add(adfGPTS[5])
     935          82 :                            .Add(adfGPTS[4])
     936          82 :                            .Add(adfGPTS[7])
     937          82 :                            .Add(adfGPTS[6])))
     938          82 :         .Add("LPTS", &((new GDALPDFArrayRW())
     939          82 :                            ->Add(0)
     940          82 :                            .Add(1)
     941          82 :                            .Add(0)
     942          82 :                            .Add(0)
     943          82 :                            .Add(1)
     944          82 :                            .Add(0)
     945          82 :                            .Add(1)
     946          82 :                            .Add(1)))
     947          82 :         .Add("GCS", nGCSId, 0);
     948          82 :     VSIFPrintfL(m_fp, "%s\n", oMeasureDict.Serialize().c_str());
     949          82 :     EndObj();
     950             : 
     951          82 :     StartObj(nGCSId);
     952          82 :     GDALPDFDictionaryRW oGCSDict;
     953             :     oGCSDict
     954             :         .Add("Type",
     955          82 :              GDALPDFObjectRW::CreateName(bIsGeographic ? "GEOGCS" : "PROJCS"))
     956          82 :         .Add("WKT", pszESRIWKT);
     957          82 :     if (nEPSGCode)
     958          65 :         oGCSDict.Add("EPSG", nEPSGCode);
     959          82 :     VSIFPrintfL(m_fp, "%s\n", oGCSDict.Serialize().c_str());
     960          82 :     EndObj();
     961             : 
     962          82 :     CPLFree(pszESRIWKT);
     963             : 
     964          82 :     return nViewportId.toBool() ? nViewportId : nMeasureId;
     965             : }
     966             : 
     967             : /************************************************************************/
     968             : /*                     GDALPDFBuildOGC_BP_Datum()                       */
     969             : /************************************************************************/
     970             : 
     971          13 : static GDALPDFObject *GDALPDFBuildOGC_BP_Datum(const OGRSpatialReference *poSRS)
     972             : {
     973          13 :     const OGR_SRSNode *poDatumNode = poSRS->GetAttrNode("DATUM");
     974          13 :     const char *pszDatumDescription = nullptr;
     975          13 :     if (poDatumNode && poDatumNode->GetChildCount() > 0)
     976          13 :         pszDatumDescription = poDatumNode->GetChild(0)->GetValue();
     977             : 
     978          13 :     GDALPDFObjectRW *poPDFDatum = nullptr;
     979             : 
     980          13 :     if (pszDatumDescription)
     981             :     {
     982          13 :         double dfSemiMajor = poSRS->GetSemiMajor();
     983          13 :         double dfInvFlattening = poSRS->GetInvFlattening();
     984          13 :         int nEPSGDatum = -1;
     985          13 :         const char *pszAuthority = poSRS->GetAuthorityName("DATUM");
     986          13 :         if (pszAuthority != nullptr && EQUAL(pszAuthority, "EPSG"))
     987          13 :             nEPSGDatum = atoi(poSRS->GetAuthorityCode("DATUM"));
     988             : 
     989          13 :         if (EQUAL(pszDatumDescription, SRS_DN_WGS84) || nEPSGDatum == 6326)
     990           3 :             poPDFDatum = GDALPDFObjectRW::CreateString("WGE");
     991          10 :         else if (EQUAL(pszDatumDescription, SRS_DN_NAD27) || nEPSGDatum == 6267)
     992          10 :             poPDFDatum = GDALPDFObjectRW::CreateString("NAS");
     993           0 :         else if (EQUAL(pszDatumDescription, SRS_DN_NAD83) || nEPSGDatum == 6269)
     994           0 :             poPDFDatum = GDALPDFObjectRW::CreateString("NAR");
     995           0 :         else if (nEPSGDatum == 6135)
     996           0 :             poPDFDatum = GDALPDFObjectRW::CreateString("OHA-M");
     997             :         else
     998             :         {
     999           0 :             CPLDebug("PDF",
    1000             :                      "Unhandled datum name (%s). Write datum parameters then.",
    1001             :                      pszDatumDescription);
    1002             : 
    1003           0 :             GDALPDFDictionaryRW *poPDFDatumDict = new GDALPDFDictionaryRW();
    1004           0 :             poPDFDatum = GDALPDFObjectRW::CreateDictionary(poPDFDatumDict);
    1005             : 
    1006           0 :             const OGR_SRSNode *poSpheroidNode = poSRS->GetAttrNode("SPHEROID");
    1007           0 :             if (poSpheroidNode && poSpheroidNode->GetChildCount() >= 3)
    1008             :             {
    1009           0 :                 poPDFDatumDict->Add("Description", pszDatumDescription);
    1010             : 
    1011             : #ifdef disabled_because_terrago_toolbar_does_not_like_it
    1012             :                 const char *pszEllipsoidCode = NULL;
    1013             :                 if (std::abs(dfSemiMajor - 6378249.145) < 0.01 &&
    1014             :                     std::abs(dfInvFlattening - 293.465) < 0.0001)
    1015             :                 {
    1016             :                     pszEllipsoidCode = "CD"; /* Clark 1880 */
    1017             :                 }
    1018             :                 else if (std::abs(dfSemiMajor - 6378245.0) < 0.01 x &&
    1019             :                          std::abs(dfInvFlattening - 298.3) < 0.0001)
    1020             :                 {
    1021             :                     pszEllipsoidCode = "KA"; /* Krassovsky */
    1022             :                 }
    1023             :                 else if (std::abs(dfSemiMajor - 6378388.0) < 0.01 &&
    1024             :                          std::abs(dfInvFlattening - 297.0) < 0.0001)
    1025             :                 {
    1026             :                     pszEllipsoidCode = "IN"; /* International 1924 */
    1027             :                 }
    1028             :                 else if (std::abs(dfSemiMajor - 6378160.0) < 0.01 &&
    1029             :                          std::abs(dfInvFlattening - 298.25) < 0.0001)
    1030             :                 {
    1031             :                     pszEllipsoidCode = "AN"; /* Australian */
    1032             :                 }
    1033             :                 else if (std::abs(dfSemiMajor - 6377397.155) < 0.01 &&
    1034             :                          std::abs(dfInvFlattening - 299.1528128) < 0.0001)
    1035             :                 {
    1036             :                     pszEllipsoidCode = "BR"; /* Bessel 1841 */
    1037             :                 }
    1038             :                 else if (std::abs(dfSemiMajor - 6377483.865) < 0.01 &&
    1039             :                          std::abs(dfInvFlattening - 299.1528128) < 0.0001)
    1040             :                 {
    1041             :                     pszEllipsoidCode =
    1042             :                         "BN"; /* Bessel 1841 (Namibia / Schwarzeck)*/
    1043             :                 }
    1044             : #if 0
    1045             :                 else if( std::abs(dfSemiMajor-6378160.0) < 0.01
    1046             :                          && std::abs(dfInvFlattening-298.247167427) < 0.0001 )
    1047             :                 {
    1048             :                     pszEllipsoidCode = "GRS67";      /* GRS 1967 */
    1049             :                 }
    1050             : #endif
    1051             :                 else if (std::abs(dfSemiMajor - 6378137) < 0.01 &&
    1052             :                          std::abs(dfInvFlattening - 298.257222101) < 0.000001)
    1053             :                 {
    1054             :                     pszEllipsoidCode = "RF"; /* GRS 1980 */
    1055             :                 }
    1056             :                 else if (std::abs(dfSemiMajor - 6378206.4) < 0.01 &&
    1057             :                          std::abs(dfInvFlattening - 294.9786982) < 0.0001)
    1058             :                 {
    1059             :                     pszEllipsoidCode = "CC"; /* Clarke 1866 */
    1060             :                 }
    1061             :                 else if (std::abs(dfSemiMajor - 6377340.189) < 0.01 &&
    1062             :                          std::abs(dfInvFlattening - 299.3249646) < 0.0001)
    1063             :                 {
    1064             :                     pszEllipsoidCode = "AM"; /* Modified Airy */
    1065             :                 }
    1066             :                 else if (std::abs(dfSemiMajor - 6377563.396) < 0.01 &&
    1067             :                          std::abs(dfInvFlattening - 299.3249646) < 0.0001)
    1068             :                 {
    1069             :                     pszEllipsoidCode = "AA"; /* Airy */
    1070             :                 }
    1071             :                 else if (std::abs(dfSemiMajor - 6378200) < 0.01 &&
    1072             :                          std::abs(dfInvFlattening - 298.3) < 0.0001)
    1073             :                 {
    1074             :                     pszEllipsoidCode = "HE"; /* Helmert 1906 */
    1075             :                 }
    1076             :                 else if (std::abs(dfSemiMajor - 6378155) < 0.01 &&
    1077             :                          std::abs(dfInvFlattening - 298.3) < 0.0001)
    1078             :                 {
    1079             :                     pszEllipsoidCode = "FA"; /* Modified Fischer 1960 */
    1080             :                 }
    1081             : #if 0
    1082             :                 else if( std::abs(dfSemiMajor-6377298.556) < 0.01
    1083             :                          && std::abs(dfInvFlattening-300.8017) < 0.0001 )
    1084             :                 {
    1085             :                     pszEllipsoidCode = "evrstSS";    /* Everest (Sabah & Sarawak) */
    1086             :                 }
    1087             :                 else if( std::abs(dfSemiMajor-6378165.0) < 0.01
    1088             :                          && std::abs(dfInvFlattening-298.3) < 0.0001 )
    1089             :                 {
    1090             :                     pszEllipsoidCode = "WGS60";
    1091             :                 }
    1092             :                 else if( std::abs(dfSemiMajor-6378145.0) < 0.01
    1093             :                          && std::abs(dfInvFlattening-298.25) < 0.0001 )
    1094             :                 {
    1095             :                     pszEllipsoidCode = "WGS66";
    1096             :                 }
    1097             : #endif
    1098             :                 else if (std::abs(dfSemiMajor - 6378135.0) < 0.01 &&
    1099             :                          std::abs(dfInvFlattening - 298.26) < 0.0001)
    1100             :                 {
    1101             :                     pszEllipsoidCode = "WD";
    1102             :                 }
    1103             :                 else if (std::abs(dfSemiMajor - 6378137.0) < 0.01 &&
    1104             :                          std::abs(dfInvFlattening - 298.257223563) < 0.000001)
    1105             :                 {
    1106             :                     pszEllipsoidCode = "WE";
    1107             :                 }
    1108             : 
    1109             :                 if (pszEllipsoidCode != NULL)
    1110             :                 {
    1111             :                     poPDFDatumDict->Add("Ellipsoid", pszEllipsoidCode);
    1112             :                 }
    1113             :                 else
    1114             : #endif /* disabled_because_terrago_toolbar_does_not_like_it */
    1115             :                 {
    1116             :                     const char *pszEllipsoidDescription =
    1117           0 :                         poSpheroidNode->GetChild(0)->GetValue();
    1118             : 
    1119           0 :                     CPLDebug("PDF",
    1120             :                              "Unhandled ellipsoid name (%s). Write ellipsoid "
    1121             :                              "parameters then.",
    1122             :                              pszEllipsoidDescription);
    1123             : 
    1124             :                     poPDFDatumDict->Add(
    1125             :                         "Ellipsoid",
    1126           0 :                         &((new GDALPDFDictionaryRW())
    1127           0 :                               ->Add("Description", pszEllipsoidDescription)
    1128           0 :                               .Add("SemiMajorAxis", dfSemiMajor, TRUE)
    1129           0 :                               .Add("InvFlattening", dfInvFlattening, TRUE)));
    1130             :                 }
    1131             : 
    1132           0 :                 const OGR_SRSNode *poTOWGS84 = poSRS->GetAttrNode("TOWGS84");
    1133           0 :                 if (poTOWGS84 != nullptr && poTOWGS84->GetChildCount() >= 3 &&
    1134           0 :                     (poTOWGS84->GetChildCount() < 7 ||
    1135           0 :                      (EQUAL(poTOWGS84->GetChild(3)->GetValue(), "") &&
    1136           0 :                       EQUAL(poTOWGS84->GetChild(4)->GetValue(), "") &&
    1137           0 :                       EQUAL(poTOWGS84->GetChild(5)->GetValue(), "") &&
    1138           0 :                       EQUAL(poTOWGS84->GetChild(6)->GetValue(), ""))))
    1139             :                 {
    1140             :                     poPDFDatumDict->Add(
    1141             :                         "ToWGS84",
    1142           0 :                         &((new GDALPDFDictionaryRW())
    1143           0 :                               ->Add("dx", poTOWGS84->GetChild(0)->GetValue())
    1144           0 :                               .Add("dy", poTOWGS84->GetChild(1)->GetValue())
    1145           0 :                               .Add("dz", poTOWGS84->GetChild(2)->GetValue())));
    1146             :                 }
    1147           0 :                 else if (poTOWGS84 != nullptr &&
    1148           0 :                          poTOWGS84->GetChildCount() >= 7)
    1149             :                 {
    1150             :                     poPDFDatumDict->Add(
    1151             :                         "ToWGS84",
    1152           0 :                         &((new GDALPDFDictionaryRW())
    1153           0 :                               ->Add("dx", poTOWGS84->GetChild(0)->GetValue())
    1154           0 :                               .Add("dy", poTOWGS84->GetChild(1)->GetValue())
    1155           0 :                               .Add("dz", poTOWGS84->GetChild(2)->GetValue())
    1156           0 :                               .Add("rx", poTOWGS84->GetChild(3)->GetValue())
    1157           0 :                               .Add("ry", poTOWGS84->GetChild(4)->GetValue())
    1158           0 :                               .Add("rz", poTOWGS84->GetChild(5)->GetValue())
    1159           0 :                               .Add("sf", poTOWGS84->GetChild(6)->GetValue())));
    1160             :                 }
    1161             :             }
    1162             :         }
    1163             :     }
    1164             :     else
    1165             :     {
    1166           0 :         CPLError(CE_Warning, CPLE_NotSupported,
    1167             :                  "No datum name. Defaulting to WGS84.");
    1168             :     }
    1169             : 
    1170          13 :     if (poPDFDatum == nullptr)
    1171           0 :         poPDFDatum = GDALPDFObjectRW::CreateString("WGE");
    1172             : 
    1173          13 :     return poPDFDatum;
    1174             : }
    1175             : 
    1176             : /************************************************************************/
    1177             : /*                   GDALPDFBuildOGC_BP_Projection()                    */
    1178             : /************************************************************************/
    1179             : 
    1180          13 : GDALPDFDictionaryRW *GDALPDFBaseWriter::GDALPDFBuildOGC_BP_Projection(
    1181             :     const OGRSpatialReference *poSRS)
    1182             : {
    1183             : 
    1184          13 :     const char *pszProjectionOGCBP = "GEOGRAPHIC";
    1185          13 :     const char *pszProjection = poSRS->GetAttrValue("PROJECTION");
    1186             : 
    1187          13 :     GDALPDFDictionaryRW *poProjectionDict = new GDALPDFDictionaryRW();
    1188          13 :     poProjectionDict->Add("Type", GDALPDFObjectRW::CreateName("Projection"));
    1189          13 :     poProjectionDict->Add("Datum", GDALPDFBuildOGC_BP_Datum(poSRS));
    1190             : 
    1191          13 :     if (pszProjection == nullptr)
    1192             :     {
    1193           3 :         if (poSRS->IsGeographic())
    1194           3 :             pszProjectionOGCBP = "GEOGRAPHIC";
    1195           0 :         else if (poSRS->IsLocal())
    1196           0 :             pszProjectionOGCBP = "LOCAL CARTESIAN";
    1197             :         else
    1198             :         {
    1199           0 :             CPLError(CE_Warning, CPLE_NotSupported, "Unsupported SRS type");
    1200           0 :             delete poProjectionDict;
    1201           0 :             return nullptr;
    1202             :         }
    1203             :     }
    1204          10 :     else if (EQUAL(pszProjection, SRS_PT_TRANSVERSE_MERCATOR))
    1205             :     {
    1206             :         int bNorth;
    1207          10 :         int nZone = poSRS->GetUTMZone(&bNorth);
    1208             : 
    1209          10 :         if (nZone != 0)
    1210             :         {
    1211          10 :             pszProjectionOGCBP = "UT";
    1212          10 :             poProjectionDict->Add("Hemisphere", (bNorth) ? "N" : "S");
    1213          10 :             poProjectionDict->Add("Zone", nZone);
    1214             :         }
    1215             :         else
    1216             :         {
    1217             :             double dfCenterLat =
    1218           0 :                 poSRS->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 90.L);
    1219             :             double dfCenterLong =
    1220           0 :                 poSRS->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
    1221           0 :             double dfScale = poSRS->GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0);
    1222             :             double dfFalseEasting =
    1223           0 :                 poSRS->GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
    1224             :             double dfFalseNorthing =
    1225           0 :                 poSRS->GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
    1226             : 
    1227             :             /* OGC_BP supports representing numbers as strings for better
    1228             :              * precision */
    1229             :             /* so use it */
    1230             : 
    1231           0 :             pszProjectionOGCBP = "TC";
    1232           0 :             poProjectionDict->Add("OriginLatitude", dfCenterLat, TRUE);
    1233           0 :             poProjectionDict->Add("CentralMeridian", dfCenterLong, TRUE);
    1234           0 :             poProjectionDict->Add("ScaleFactor", dfScale, TRUE);
    1235           0 :             poProjectionDict->Add("FalseEasting", dfFalseEasting, TRUE);
    1236           0 :             poProjectionDict->Add("FalseNorthing", dfFalseNorthing, TRUE);
    1237             :         }
    1238             :     }
    1239           0 :     else if (EQUAL(pszProjection, SRS_PT_POLAR_STEREOGRAPHIC))
    1240             :     {
    1241             :         double dfCenterLat =
    1242           0 :             poSRS->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
    1243             :         double dfCenterLong =
    1244           0 :             poSRS->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
    1245           0 :         double dfScale = poSRS->GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0);
    1246             :         double dfFalseEasting =
    1247           0 :             poSRS->GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
    1248             :         double dfFalseNorthing =
    1249           0 :             poSRS->GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
    1250             : 
    1251           0 :         if (fabs(dfCenterLat) == 90.0 && dfCenterLong == 0.0 &&
    1252           0 :             dfScale == 0.994 && dfFalseEasting == 200000.0 &&
    1253             :             dfFalseNorthing == 200000.0)
    1254             :         {
    1255           0 :             pszProjectionOGCBP = "UP";
    1256           0 :             poProjectionDict->Add("Hemisphere", (dfCenterLat > 0) ? "N" : "S");
    1257             :         }
    1258             :         else
    1259             :         {
    1260           0 :             pszProjectionOGCBP = "PG";
    1261           0 :             poProjectionDict->Add("LatitudeTrueScale", dfCenterLat, TRUE);
    1262           0 :             poProjectionDict->Add("LongitudeDownFromPole", dfCenterLong, TRUE);
    1263           0 :             poProjectionDict->Add("ScaleFactor", dfScale, TRUE);
    1264           0 :             poProjectionDict->Add("FalseEasting", dfFalseEasting, TRUE);
    1265           0 :             poProjectionDict->Add("FalseNorthing", dfFalseNorthing, TRUE);
    1266             :         }
    1267             :     }
    1268             : 
    1269           0 :     else if (EQUAL(pszProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP))
    1270             :     {
    1271             :         double dfStdP1 =
    1272           0 :             poSRS->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0);
    1273             :         double dfStdP2 =
    1274           0 :             poSRS->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2, 0.0);
    1275             :         double dfCenterLat =
    1276           0 :             poSRS->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
    1277             :         double dfCenterLong =
    1278           0 :             poSRS->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
    1279             :         double dfFalseEasting =
    1280           0 :             poSRS->GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
    1281             :         double dfFalseNorthing =
    1282           0 :             poSRS->GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
    1283             : 
    1284           0 :         pszProjectionOGCBP = "LE";
    1285           0 :         poProjectionDict->Add("StandardParallelOne", dfStdP1, TRUE);
    1286           0 :         poProjectionDict->Add("StandardParallelTwo", dfStdP2, TRUE);
    1287           0 :         poProjectionDict->Add("OriginLatitude", dfCenterLat, TRUE);
    1288           0 :         poProjectionDict->Add("CentralMeridian", dfCenterLong, TRUE);
    1289           0 :         poProjectionDict->Add("FalseEasting", dfFalseEasting, TRUE);
    1290           0 :         poProjectionDict->Add("FalseNorthing", dfFalseNorthing, TRUE);
    1291             :     }
    1292             : 
    1293           0 :     else if (EQUAL(pszProjection, SRS_PT_MERCATOR_1SP))
    1294             :     {
    1295             :         double dfCenterLong =
    1296           0 :             poSRS->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
    1297             :         double dfCenterLat =
    1298           0 :             poSRS->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
    1299           0 :         double dfScale = poSRS->GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0);
    1300             :         double dfFalseEasting =
    1301           0 :             poSRS->GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
    1302             :         double dfFalseNorthing =
    1303           0 :             poSRS->GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
    1304             : 
    1305           0 :         pszProjectionOGCBP = "MC";
    1306           0 :         poProjectionDict->Add("CentralMeridian", dfCenterLong, TRUE);
    1307           0 :         poProjectionDict->Add("OriginLatitude", dfCenterLat, TRUE);
    1308           0 :         poProjectionDict->Add("ScaleFactor", dfScale, TRUE);
    1309           0 :         poProjectionDict->Add("FalseEasting", dfFalseEasting, TRUE);
    1310           0 :         poProjectionDict->Add("FalseNorthing", dfFalseNorthing, TRUE);
    1311             :     }
    1312             : 
    1313             : #ifdef not_supported
    1314             :     else if (EQUAL(pszProjection, SRS_PT_MERCATOR_2SP))
    1315             :     {
    1316             :         double dfStdP1 =
    1317             :             poSRS->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0);
    1318             :         double dfCenterLong =
    1319             :             poSRS->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
    1320             :         double dfFalseEasting =
    1321             :             poSRS->GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
    1322             :         double dfFalseNorthing =
    1323             :             poSRS->GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
    1324             : 
    1325             :         pszProjectionOGCBP = "MC";
    1326             :         poProjectionDict->Add("StandardParallelOne", dfStdP1, TRUE);
    1327             :         poProjectionDict->Add("CentralMeridian", dfCenterLong, TRUE);
    1328             :         poProjectionDict->Add("FalseEasting", dfFalseEasting, TRUE);
    1329             :         poProjectionDict->Add("FalseNorthing", dfFalseNorthing, TRUE);
    1330             :     }
    1331             : #endif
    1332             : 
    1333             :     else
    1334             :     {
    1335           0 :         CPLError(CE_Warning, CPLE_NotSupported,
    1336             :                  "Unhandled projection type (%s) for now", pszProjection);
    1337             :     }
    1338             : 
    1339          13 :     poProjectionDict->Add("ProjectionType", pszProjectionOGCBP);
    1340             : 
    1341          13 :     if (poSRS->IsProjected())
    1342             :     {
    1343          10 :         const char *pszUnitName = nullptr;
    1344          10 :         double dfLinearUnits = poSRS->GetLinearUnits(&pszUnitName);
    1345          10 :         if (dfLinearUnits == 1.0)
    1346          10 :             poProjectionDict->Add("Units", "M");
    1347           0 :         else if (dfLinearUnits == 0.3048)
    1348           0 :             poProjectionDict->Add("Units", "FT");
    1349             :     }
    1350             : 
    1351          13 :     return poProjectionDict;
    1352             : }
    1353             : 
    1354             : /************************************************************************/
    1355             : /*                           WriteSRS_OGC_BP()                          */
    1356             : /************************************************************************/
    1357             : 
    1358          12 : GDALPDFObjectNum GDALPDFBaseWriter::WriteSRS_OGC_BP(GDALDataset *poSrcDS,
    1359             :                                                     double dfUserUnit,
    1360             :                                                     const char *pszNEATLINE,
    1361             :                                                     PDFMargins *psMargins)
    1362             : {
    1363          12 :     int nWidth = poSrcDS->GetRasterXSize();
    1364          12 :     int nHeight = poSrcDS->GetRasterYSize();
    1365          12 :     const char *pszWKT = poSrcDS->GetProjectionRef();
    1366             :     double adfGeoTransform[6];
    1367             : 
    1368          12 :     int bHasGT = (poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None);
    1369          12 :     int nGCPCount = poSrcDS->GetGCPCount();
    1370             :     const GDAL_GCP *pasGCPList =
    1371          12 :         (nGCPCount >= 4) ? poSrcDS->GetGCPs() : nullptr;
    1372          12 :     if (pasGCPList != nullptr)
    1373           4 :         pszWKT = poSrcDS->GetGCPProjection();
    1374             : 
    1375          12 :     if (!bHasGT && pasGCPList == nullptr)
    1376           0 :         return GDALPDFObjectNum();
    1377             : 
    1378          12 :     if (pszWKT == nullptr || EQUAL(pszWKT, ""))
    1379           0 :         return GDALPDFObjectNum();
    1380             : 
    1381          12 :     if (!bHasGT)
    1382             :     {
    1383           2 :         if (!GDALGCPsToGeoTransform(nGCPCount, pasGCPList, adfGeoTransform,
    1384             :                                     FALSE))
    1385             :         {
    1386           2 :             CPLDebug("PDF", "Could not compute GT with exact match. Writing "
    1387             :                             "Registration then");
    1388             :         }
    1389             :         else
    1390             :         {
    1391           0 :             bHasGT = TRUE;
    1392             :         }
    1393             :     }
    1394             : 
    1395          12 :     OGRSpatialReferenceH hSRS = OSRNewSpatialReference(pszWKT);
    1396          12 :     if (hSRS == nullptr)
    1397           0 :         return GDALPDFObjectNum();
    1398          12 :     OSRSetAxisMappingStrategy(hSRS, OAMS_TRADITIONAL_GIS_ORDER);
    1399             : 
    1400          12 :     const OGRSpatialReference *poSRS = OGRSpatialReference::FromHandle(hSRS);
    1401             :     GDALPDFDictionaryRW *poProjectionDict =
    1402          12 :         GDALPDFBuildOGC_BP_Projection(poSRS);
    1403          12 :     if (poProjectionDict == nullptr)
    1404             :     {
    1405           0 :         OSRDestroySpatialReference(hSRS);
    1406           0 :         return GDALPDFObjectNum();
    1407             :     }
    1408             : 
    1409          12 :     GDALPDFArrayRW *poNeatLineArray = nullptr;
    1410             : 
    1411          12 :     if (pszNEATLINE == nullptr)
    1412          10 :         pszNEATLINE = poSrcDS->GetMetadataItem("NEATLINE");
    1413          12 :     if (bHasGT && pszNEATLINE != nullptr && !EQUAL(pszNEATLINE, "NO") &&
    1414           2 :         pszNEATLINE[0] != '\0')
    1415             :     {
    1416           2 :         OGRGeometry *poGeom = nullptr;
    1417           2 :         OGRGeometryFactory::createFromWkt(pszNEATLINE, nullptr, &poGeom);
    1418           4 :         if (poGeom != nullptr &&
    1419           2 :             wkbFlatten(poGeom->getGeometryType()) == wkbPolygon)
    1420             :         {
    1421           2 :             OGRLineString *poLS = poGeom->toPolygon()->getExteriorRing();
    1422             :             double adfGeoTransformInv[6];
    1423           4 :             if (poLS != nullptr && poLS->getNumPoints() >= 5 &&
    1424           2 :                 GDALInvGeoTransform(adfGeoTransform, adfGeoTransformInv))
    1425             :             {
    1426           2 :                 poNeatLineArray = new GDALPDFArrayRW();
    1427             : 
    1428             :                 // FIXME : ensure that they are in clockwise order ?
    1429          12 :                 for (int i = 0; i < poLS->getNumPoints() - 1; i++)
    1430             :                 {
    1431          10 :                     double X = poLS->getX(i);
    1432          10 :                     double Y = poLS->getY(i);
    1433          10 :                     double x = adfGeoTransformInv[0] +
    1434          10 :                                X * adfGeoTransformInv[1] +
    1435          10 :                                Y * adfGeoTransformInv[2];
    1436          10 :                     double y = adfGeoTransformInv[3] +
    1437          10 :                                X * adfGeoTransformInv[4] +
    1438          10 :                                Y * adfGeoTransformInv[5];
    1439          10 :                     poNeatLineArray->Add(x / dfUserUnit + psMargins->nLeft,
    1440          10 :                                          TRUE);
    1441             :                     poNeatLineArray->Add(
    1442          10 :                         (nHeight - y) / dfUserUnit + psMargins->nBottom, TRUE);
    1443             :                 }
    1444             :             }
    1445             :         }
    1446           2 :         delete poGeom;
    1447             :     }
    1448             : 
    1449          12 :     if (pszNEATLINE != nullptr && EQUAL(pszNEATLINE, "NO"))
    1450             :     {
    1451             :         // Do nothing
    1452             :     }
    1453          12 :     else if (pasGCPList && poNeatLineArray == nullptr)
    1454             :     {
    1455           4 :         if (nGCPCount == 4)
    1456             :         {
    1457           2 :             int iUL = 0;
    1458           2 :             int iUR = 0;
    1459           2 :             int iLR = 0;
    1460           2 :             int iLL = 0;
    1461           2 :             GDALPDFFind4Corners(pasGCPList, iUL, iUR, iLR, iLL);
    1462             : 
    1463             :             double adfNL[8];
    1464           2 :             adfNL[0] =
    1465           2 :                 pasGCPList[iUL].dfGCPPixel / dfUserUnit + psMargins->nLeft;
    1466           2 :             adfNL[1] = (nHeight - pasGCPList[iUL].dfGCPLine) / dfUserUnit +
    1467           2 :                        psMargins->nBottom;
    1468           2 :             adfNL[2] =
    1469           2 :                 pasGCPList[iLL].dfGCPPixel / dfUserUnit + psMargins->nLeft;
    1470           2 :             adfNL[3] = (nHeight - pasGCPList[iLL].dfGCPLine) / dfUserUnit +
    1471           2 :                        psMargins->nBottom;
    1472           2 :             adfNL[4] =
    1473           2 :                 pasGCPList[iLR].dfGCPPixel / dfUserUnit + psMargins->nLeft;
    1474           2 :             adfNL[5] = (nHeight - pasGCPList[iLR].dfGCPLine) / dfUserUnit +
    1475           2 :                        psMargins->nBottom;
    1476           2 :             adfNL[6] =
    1477           2 :                 pasGCPList[iUR].dfGCPPixel / dfUserUnit + psMargins->nLeft;
    1478           2 :             adfNL[7] = (nHeight - pasGCPList[iUR].dfGCPLine) / dfUserUnit +
    1479           2 :                        psMargins->nBottom;
    1480             : 
    1481           2 :             poNeatLineArray = new GDALPDFArrayRW();
    1482           2 :             poNeatLineArray->Add(adfNL, 8, TRUE);
    1483             :         }
    1484             :         else
    1485             :         {
    1486           2 :             poNeatLineArray = new GDALPDFArrayRW();
    1487             : 
    1488             :             // FIXME : ensure that they are in clockwise order ?
    1489             :             int i;
    1490          12 :             for (i = 0; i < nGCPCount; i++)
    1491             :             {
    1492          10 :                 poNeatLineArray->Add(pasGCPList[i].dfGCPPixel / dfUserUnit +
    1493          10 :                                          psMargins->nLeft,
    1494          10 :                                      TRUE);
    1495          10 :                 poNeatLineArray->Add((nHeight - pasGCPList[i].dfGCPLine) /
    1496             :                                              dfUserUnit +
    1497          10 :                                          psMargins->nBottom,
    1498          10 :                                      TRUE);
    1499             :             }
    1500           4 :         }
    1501             :     }
    1502           8 :     else if (poNeatLineArray == nullptr)
    1503             :     {
    1504           6 :         poNeatLineArray = new GDALPDFArrayRW();
    1505             : 
    1506           6 :         poNeatLineArray->Add(0 / dfUserUnit + psMargins->nLeft, TRUE);
    1507           6 :         poNeatLineArray->Add((nHeight - 0) / dfUserUnit + psMargins->nBottom,
    1508           6 :                              TRUE);
    1509             : 
    1510           6 :         poNeatLineArray->Add(0 / dfUserUnit + psMargins->nLeft, TRUE);
    1511             :         poNeatLineArray->Add(
    1512           6 :             (/*nHeight -nHeight*/ 0) / dfUserUnit + psMargins->nBottom, TRUE);
    1513             : 
    1514           6 :         poNeatLineArray->Add(nWidth / dfUserUnit + psMargins->nLeft, TRUE);
    1515             :         poNeatLineArray->Add(
    1516           6 :             (/*nHeight -nHeight*/ 0) / dfUserUnit + psMargins->nBottom, TRUE);
    1517             : 
    1518           6 :         poNeatLineArray->Add(nWidth / dfUserUnit + psMargins->nLeft, TRUE);
    1519           6 :         poNeatLineArray->Add((nHeight - 0) / dfUserUnit + psMargins->nBottom,
    1520           6 :                              TRUE);
    1521             :     }
    1522             : 
    1523          12 :     auto nLGIDictId = AllocNewObject();
    1524          12 :     StartObj(nLGIDictId);
    1525          12 :     GDALPDFDictionaryRW oLGIDict;
    1526          12 :     oLGIDict.Add("Type", GDALPDFObjectRW::CreateName("LGIDict"))
    1527          12 :         .Add("Version", "2.1");
    1528          12 :     if (bHasGT)
    1529             :     {
    1530             :         double adfCTM[6];
    1531          10 :         double dfX1 = psMargins->nLeft;
    1532          10 :         double dfY2 = nHeight / dfUserUnit + psMargins->nBottom;
    1533             : 
    1534          10 :         adfCTM[0] = adfGeoTransform[1] * dfUserUnit;
    1535          10 :         adfCTM[1] = adfGeoTransform[2] * dfUserUnit;
    1536          10 :         adfCTM[2] = -adfGeoTransform[4] * dfUserUnit;
    1537          10 :         adfCTM[3] = -adfGeoTransform[5] * dfUserUnit;
    1538          10 :         adfCTM[4] = adfGeoTransform[0] - (adfCTM[0] * dfX1 + adfCTM[2] * dfY2);
    1539          10 :         adfCTM[5] = adfGeoTransform[3] - (adfCTM[1] * dfX1 + adfCTM[3] * dfY2);
    1540             : 
    1541          10 :         oLGIDict.Add("CTM", &((new GDALPDFArrayRW())->Add(adfCTM, 6, TRUE)));
    1542             :     }
    1543             :     else
    1544             :     {
    1545           2 :         GDALPDFArrayRW *poRegistrationArray = new GDALPDFArrayRW();
    1546             :         int i;
    1547          12 :         for (i = 0; i < nGCPCount; i++)
    1548             :         {
    1549          10 :             GDALPDFArrayRW *poPTArray = new GDALPDFArrayRW();
    1550             :             poPTArray->Add(
    1551          10 :                 pasGCPList[i].dfGCPPixel / dfUserUnit + psMargins->nLeft, TRUE);
    1552          10 :             poPTArray->Add((nHeight - pasGCPList[i].dfGCPLine) / dfUserUnit +
    1553          10 :                                psMargins->nBottom,
    1554          10 :                            TRUE);
    1555          10 :             poPTArray->Add(pasGCPList[i].dfGCPX, TRUE);
    1556          10 :             poPTArray->Add(pasGCPList[i].dfGCPY, TRUE);
    1557          10 :             poRegistrationArray->Add(poPTArray);
    1558             :         }
    1559           2 :         oLGIDict.Add("Registration", poRegistrationArray);
    1560             :     }
    1561          12 :     if (poNeatLineArray)
    1562             :     {
    1563          12 :         oLGIDict.Add("Neatline", poNeatLineArray);
    1564             :     }
    1565             : 
    1566          12 :     const OGR_SRSNode *poNode = poSRS->GetRoot();
    1567          12 :     if (poNode != nullptr)
    1568          12 :         poNode = poNode->GetChild(0);
    1569          12 :     const char *pszDescription = nullptr;
    1570          12 :     if (poNode != nullptr)
    1571          12 :         pszDescription = poNode->GetValue();
    1572          12 :     if (pszDescription)
    1573             :     {
    1574          12 :         oLGIDict.Add("Description", pszDescription);
    1575             :     }
    1576             : 
    1577          12 :     oLGIDict.Add("Projection", poProjectionDict);
    1578             : 
    1579             :     /* GDAL extension */
    1580          12 :     if (CPLTestBool(CPLGetConfigOption("GDAL_PDF_OGC_BP_WRITE_WKT", "TRUE")))
    1581           6 :         poProjectionDict->Add("WKT", pszWKT);
    1582             : 
    1583          12 :     VSIFPrintfL(m_fp, "%s\n", oLGIDict.Serialize().c_str());
    1584          12 :     EndObj();
    1585             : 
    1586          12 :     OSRDestroySpatialReference(hSRS);
    1587             : 
    1588          12 :     return nLGIDictId;
    1589             : }
    1590             : 
    1591             : /************************************************************************/
    1592             : /*                     GDALPDFGetValueFromDSOrOption()                  */
    1593             : /************************************************************************/
    1594             : 
    1595         889 : static const char *GDALPDFGetValueFromDSOrOption(GDALDataset *poSrcDS,
    1596             :                                                  char **papszOptions,
    1597             :                                                  const char *pszKey)
    1598             : {
    1599         889 :     const char *pszValue = CSLFetchNameValue(papszOptions, pszKey);
    1600         889 :     if (pszValue == nullptr)
    1601         877 :         pszValue = poSrcDS->GetMetadataItem(pszKey);
    1602         889 :     if (pszValue != nullptr && pszValue[0] == '\0')
    1603           2 :         return nullptr;
    1604             :     else
    1605         887 :         return pszValue;
    1606             : }
    1607             : 
    1608             : /************************************************************************/
    1609             : /*                             SetInfo()                                */
    1610             : /************************************************************************/
    1611             : 
    1612         127 : GDALPDFObjectNum GDALPDFBaseWriter::SetInfo(GDALDataset *poSrcDS,
    1613             :                                             char **papszOptions)
    1614             : {
    1615             :     const char *pszAUTHOR =
    1616         127 :         GDALPDFGetValueFromDSOrOption(poSrcDS, papszOptions, "AUTHOR");
    1617             :     const char *pszPRODUCER =
    1618         127 :         GDALPDFGetValueFromDSOrOption(poSrcDS, papszOptions, "PRODUCER");
    1619             :     const char *pszCREATOR =
    1620         127 :         GDALPDFGetValueFromDSOrOption(poSrcDS, papszOptions, "CREATOR");
    1621             :     const char *pszCREATION_DATE =
    1622         127 :         GDALPDFGetValueFromDSOrOption(poSrcDS, papszOptions, "CREATION_DATE");
    1623             :     const char *pszSUBJECT =
    1624         127 :         GDALPDFGetValueFromDSOrOption(poSrcDS, papszOptions, "SUBJECT");
    1625             :     const char *pszTITLE =
    1626         127 :         GDALPDFGetValueFromDSOrOption(poSrcDS, papszOptions, "TITLE");
    1627             :     const char *pszKEYWORDS =
    1628         127 :         GDALPDFGetValueFromDSOrOption(poSrcDS, papszOptions, "KEYWORDS");
    1629             :     return SetInfo(pszAUTHOR, pszPRODUCER, pszCREATOR, pszCREATION_DATE,
    1630         127 :                    pszSUBJECT, pszTITLE, pszKEYWORDS);
    1631             : }
    1632             : 
    1633             : /************************************************************************/
    1634             : /*                             SetInfo()                                */
    1635             : /************************************************************************/
    1636             : 
    1637             : GDALPDFObjectNum
    1638         128 : GDALPDFBaseWriter::SetInfo(const char *pszAUTHOR, const char *pszPRODUCER,
    1639             :                            const char *pszCREATOR, const char *pszCREATION_DATE,
    1640             :                            const char *pszSUBJECT, const char *pszTITLE,
    1641             :                            const char *pszKEYWORDS)
    1642             : {
    1643         128 :     if (pszAUTHOR == nullptr && pszPRODUCER == nullptr &&
    1644         119 :         pszCREATOR == nullptr && pszCREATION_DATE == nullptr &&
    1645         119 :         pszSUBJECT == nullptr && pszTITLE == nullptr && pszKEYWORDS == nullptr)
    1646         119 :         return GDALPDFObjectNum();
    1647             : 
    1648           9 :     if (!m_nInfoId.toBool())
    1649           7 :         m_nInfoId = AllocNewObject();
    1650           9 :     StartObj(m_nInfoId, m_nInfoGen);
    1651           9 :     GDALPDFDictionaryRW oDict;
    1652           9 :     if (pszAUTHOR != nullptr)
    1653           9 :         oDict.Add("Author", pszAUTHOR);
    1654           9 :     if (pszPRODUCER != nullptr)
    1655           4 :         oDict.Add("Producer", pszPRODUCER);
    1656           9 :     if (pszCREATOR != nullptr)
    1657           4 :         oDict.Add("Creator", pszCREATOR);
    1658           9 :     if (pszCREATION_DATE != nullptr)
    1659           0 :         oDict.Add("CreationDate", pszCREATION_DATE);
    1660           9 :     if (pszSUBJECT != nullptr)
    1661           4 :         oDict.Add("Subject", pszSUBJECT);
    1662           9 :     if (pszTITLE != nullptr)
    1663           4 :         oDict.Add("Title", pszTITLE);
    1664           9 :     if (pszKEYWORDS != nullptr)
    1665           4 :         oDict.Add("Keywords", pszKEYWORDS);
    1666           9 :     VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
    1667           9 :     EndObj();
    1668             : 
    1669           9 :     return m_nInfoId;
    1670             : }
    1671             : 
    1672             : /************************************************************************/
    1673             : /*                             SetXMP()                                 */
    1674             : /************************************************************************/
    1675             : 
    1676         112 : GDALPDFObjectNum GDALPDFBaseWriter::SetXMP(GDALDataset *poSrcDS,
    1677             :                                            const char *pszXMP)
    1678             : {
    1679         112 :     if (pszXMP != nullptr && STARTS_WITH_CI(pszXMP, "NO"))
    1680           2 :         return GDALPDFObjectNum();
    1681         110 :     if (pszXMP != nullptr && pszXMP[0] == '\0')
    1682           0 :         return GDALPDFObjectNum();
    1683             : 
    1684         110 :     if (poSrcDS && pszXMP == nullptr)
    1685             :     {
    1686         109 :         char **papszXMP = poSrcDS->GetMetadata("xml:XMP");
    1687         109 :         if (papszXMP != nullptr && papszXMP[0] != nullptr)
    1688           6 :             pszXMP = papszXMP[0];
    1689             :     }
    1690             : 
    1691         110 :     if (pszXMP == nullptr)
    1692         103 :         return GDALPDFObjectNum();
    1693             : 
    1694           7 :     CPLXMLNode *psNode = CPLParseXMLString(pszXMP);
    1695           7 :     if (psNode == nullptr)
    1696           0 :         return GDALPDFObjectNum();
    1697           7 :     CPLDestroyXMLNode(psNode);
    1698             : 
    1699           7 :     if (!m_nXMPId.toBool())
    1700           5 :         m_nXMPId = AllocNewObject();
    1701           7 :     StartObj(m_nXMPId, m_nXMPGen);
    1702           7 :     GDALPDFDictionaryRW oDict;
    1703           7 :     oDict.Add("Type", GDALPDFObjectRW::CreateName("Metadata"))
    1704           7 :         .Add("Subtype", GDALPDFObjectRW::CreateName("XML"))
    1705           7 :         .Add("Length", (int)strlen(pszXMP));
    1706           7 :     VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
    1707           7 :     VSIFPrintfL(m_fp, "stream\n");
    1708           7 :     VSIFPrintfL(m_fp, "%s\n", pszXMP);
    1709           7 :     VSIFPrintfL(m_fp, "endstream\n");
    1710           7 :     EndObj();
    1711           7 :     return m_nXMPId;
    1712             : }
    1713             : 
    1714             : /************************************************************************/
    1715             : /*                              WriteOCG()                              */
    1716             : /************************************************************************/
    1717             : 
    1718         281 : GDALPDFObjectNum GDALPDFBaseWriter::WriteOCG(const char *pszLayerName,
    1719             :                                              const GDALPDFObjectNum &nParentId)
    1720             : {
    1721         281 :     if (pszLayerName == nullptr || pszLayerName[0] == '\0')
    1722         220 :         return GDALPDFObjectNum();
    1723             : 
    1724          61 :     auto nOCGId = AllocNewObject();
    1725             : 
    1726          61 :     GDALPDFOCGDesc oOCGDesc;
    1727          61 :     oOCGDesc.nId = nOCGId;
    1728          61 :     oOCGDesc.nParentId = nParentId;
    1729          61 :     oOCGDesc.osLayerName = pszLayerName;
    1730             : 
    1731          61 :     m_asOCGs.push_back(oOCGDesc);
    1732             : 
    1733          61 :     StartObj(nOCGId);
    1734             :     {
    1735          61 :         GDALPDFDictionaryRW oDict;
    1736          61 :         oDict.Add("Type", GDALPDFObjectRW::CreateName("OCG"));
    1737          61 :         oDict.Add("Name", pszLayerName);
    1738          61 :         VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
    1739             :     }
    1740          61 :     EndObj();
    1741             : 
    1742          61 :     return nOCGId;
    1743             : }
    1744             : 
    1745             : /************************************************************************/
    1746             : /*                              StartPage()                             */
    1747             : /************************************************************************/
    1748             : 
    1749         123 : bool GDALPDFWriter::StartPage(GDALDataset *poClippingDS, double dfDPI,
    1750             :                               bool bWriteUserUnit, const char *pszGEO_ENCODING,
    1751             :                               const char *pszNEATLINE, PDFMargins *psMargins,
    1752             :                               PDFCompressMethod eStreamCompressMethod,
    1753             :                               int bHasOGRData)
    1754             : {
    1755         123 :     int nWidth = poClippingDS->GetRasterXSize();
    1756         123 :     int nHeight = poClippingDS->GetRasterYSize();
    1757         123 :     int nBands = poClippingDS->GetRasterCount();
    1758             : 
    1759         123 :     double dfUserUnit = dfDPI * USER_UNIT_IN_INCH;
    1760         123 :     double dfWidthInUserUnit =
    1761         123 :         nWidth / dfUserUnit + psMargins->nLeft + psMargins->nRight;
    1762         123 :     double dfHeightInUserUnit =
    1763         123 :         nHeight / dfUserUnit + psMargins->nBottom + psMargins->nTop;
    1764             : 
    1765         123 :     auto nPageId = AllocNewObject();
    1766         123 :     m_asPageId.push_back(nPageId);
    1767             : 
    1768         123 :     auto nContentId = AllocNewObject();
    1769         123 :     auto nResourcesId = AllocNewObject();
    1770             : 
    1771         123 :     auto nAnnotsId = AllocNewObject();
    1772             : 
    1773         123 :     const bool bISO32000 =
    1774         123 :         EQUAL(pszGEO_ENCODING, "ISO32000") || EQUAL(pszGEO_ENCODING, "BOTH");
    1775         123 :     const bool bOGC_BP =
    1776         123 :         EQUAL(pszGEO_ENCODING, "OGC_BP") || EQUAL(pszGEO_ENCODING, "BOTH");
    1777             : 
    1778         123 :     GDALPDFObjectNum nViewportId;
    1779         123 :     if (bISO32000)
    1780             :         nViewportId = WriteSRS_ISO32000(poClippingDS, dfUserUnit, pszNEATLINE,
    1781         107 :                                         psMargins, TRUE);
    1782             : 
    1783         123 :     GDALPDFObjectNum nLGIDictId;
    1784         123 :     if (bOGC_BP)
    1785             :         nLGIDictId =
    1786          10 :             WriteSRS_OGC_BP(poClippingDS, dfUserUnit, pszNEATLINE, psMargins);
    1787             : 
    1788         123 :     StartObj(nPageId);
    1789         123 :     GDALPDFDictionaryRW oDictPage;
    1790         123 :     oDictPage.Add("Type", GDALPDFObjectRW::CreateName("Page"))
    1791         123 :         .Add("Parent", m_nPageResourceId, 0)
    1792         123 :         .Add("MediaBox", &((new GDALPDFArrayRW())
    1793         123 :                                ->Add(0)
    1794         123 :                                .Add(0)
    1795         123 :                                .Add(dfWidthInUserUnit)
    1796         123 :                                .Add(dfHeightInUserUnit)));
    1797         123 :     if (bWriteUserUnit)
    1798         103 :         oDictPage.Add("UserUnit", dfUserUnit);
    1799         123 :     oDictPage.Add("Contents", nContentId, 0)
    1800         123 :         .Add("Resources", nResourcesId, 0)
    1801         123 :         .Add("Annots", nAnnotsId, 0);
    1802             : 
    1803         123 :     if (nBands == 4)
    1804             :     {
    1805             :         oDictPage.Add(
    1806             :             "Group",
    1807           7 :             &((new GDALPDFDictionaryRW())
    1808           7 :                   ->Add("Type", GDALPDFObjectRW::CreateName("Group"))
    1809           7 :                   .Add("S", GDALPDFObjectRW::CreateName("Transparency"))
    1810           7 :                   .Add("CS", GDALPDFObjectRW::CreateName("DeviceRGB"))));
    1811             :     }
    1812         123 :     if (nViewportId.toBool())
    1813             :     {
    1814          72 :         oDictPage.Add("VP", &((new GDALPDFArrayRW())->Add(nViewportId, 0)));
    1815             :     }
    1816         123 :     if (nLGIDictId.toBool())
    1817             :     {
    1818          10 :         oDictPage.Add("LGIDict", nLGIDictId, 0);
    1819             :     }
    1820             : 
    1821             : #ifndef HACK_TO_GENERATE_OCMD
    1822         123 :     if (bHasOGRData)
    1823          21 :         oDictPage.Add("StructParents", 0);
    1824             : #endif
    1825             : 
    1826         123 :     VSIFPrintfL(m_fp, "%s\n", oDictPage.Serialize().c_str());
    1827         123 :     EndObj();
    1828             : 
    1829         123 :     oPageContext.poClippingDS = poClippingDS;
    1830         123 :     oPageContext.nPageId = nPageId;
    1831         123 :     oPageContext.nContentId = nContentId;
    1832         123 :     oPageContext.nResourcesId = nResourcesId;
    1833         123 :     oPageContext.nAnnotsId = nAnnotsId;
    1834         123 :     oPageContext.dfDPI = dfDPI;
    1835         123 :     oPageContext.sMargins = *psMargins;
    1836         123 :     oPageContext.eStreamCompressMethod = eStreamCompressMethod;
    1837             : 
    1838         246 :     return true;
    1839             : }
    1840             : 
    1841             : /************************************************************************/
    1842             : /*                             WriteColorTable()                        */
    1843             : /************************************************************************/
    1844             : 
    1845         327 : GDALPDFObjectNum GDALPDFBaseWriter::WriteColorTable(GDALDataset *poSrcDS)
    1846             : {
    1847             :     /* Does the source image has a color table ? */
    1848         327 :     GDALColorTable *poCT = nullptr;
    1849         327 :     if (poSrcDS->GetRasterCount() > 0)
    1850         327 :         poCT = poSrcDS->GetRasterBand(1)->GetColorTable();
    1851         327 :     GDALPDFObjectNum nColorTableId;
    1852         327 :     if (poCT != nullptr && poCT->GetColorEntryCount() <= 256)
    1853             :     {
    1854           2 :         int nColors = poCT->GetColorEntryCount();
    1855           2 :         nColorTableId = AllocNewObject();
    1856             : 
    1857           2 :         auto nLookupTableId = AllocNewObject();
    1858             : 
    1859             :         /* Index object */
    1860           2 :         StartObj(nColorTableId);
    1861             :         {
    1862           2 :             GDALPDFArrayRW oArray;
    1863           2 :             oArray.Add(GDALPDFObjectRW::CreateName("Indexed"))
    1864           2 :                 .Add(&((new GDALPDFArrayRW())
    1865           2 :                            ->Add(GDALPDFObjectRW::CreateName("DeviceRGB"))))
    1866           2 :                 .Add(nColors - 1)
    1867           2 :                 .Add(nLookupTableId, 0);
    1868           2 :             VSIFPrintfL(m_fp, "%s\n", oArray.Serialize().c_str());
    1869             :         }
    1870           2 :         EndObj();
    1871             : 
    1872             :         /* Lookup table object */
    1873           2 :         StartObj(nLookupTableId);
    1874             :         {
    1875           2 :             GDALPDFDictionaryRW oDict;
    1876           2 :             oDict.Add("Length", nColors * 3);
    1877           2 :             VSIFPrintfL(m_fp, "%s %% Lookup table\n",
    1878           4 :                         oDict.Serialize().c_str());
    1879             :         }
    1880           2 :         VSIFPrintfL(m_fp, "stream\n");
    1881             :         GByte pabyLookup[768];
    1882         514 :         for (int i = 0; i < nColors; i++)
    1883             :         {
    1884         512 :             const GDALColorEntry *poEntry = poCT->GetColorEntry(i);
    1885         512 :             pabyLookup[3 * i + 0] = (GByte)poEntry->c1;
    1886         512 :             pabyLookup[3 * i + 1] = (GByte)poEntry->c2;
    1887         512 :             pabyLookup[3 * i + 2] = (GByte)poEntry->c3;
    1888             :         }
    1889           2 :         VSIFWriteL(pabyLookup, 3 * nColors, 1, m_fp);
    1890           2 :         VSIFPrintfL(m_fp, "\n");
    1891           2 :         VSIFPrintfL(m_fp, "endstream\n");
    1892           2 :         EndObj();
    1893             :     }
    1894             : 
    1895         327 :     return nColorTableId;
    1896             : }
    1897             : 
    1898             : /************************************************************************/
    1899             : /*                             WriteImagery()                           */
    1900             : /************************************************************************/
    1901             : 
    1902         103 : bool GDALPDFWriter::WriteImagery(GDALDataset *poDS, const char *pszLayerName,
    1903             :                                  PDFCompressMethod eCompressMethod,
    1904             :                                  int nPredictor, int nJPEGQuality,
    1905             :                                  const char *pszJPEG2000_DRIVER,
    1906             :                                  int nBlockXSize, int nBlockYSize,
    1907             :                                  GDALProgressFunc pfnProgress,
    1908             :                                  void *pProgressData)
    1909             : {
    1910         103 :     int nWidth = poDS->GetRasterXSize();
    1911         103 :     int nHeight = poDS->GetRasterYSize();
    1912         103 :     double dfUserUnit = oPageContext.dfDPI * USER_UNIT_IN_INCH;
    1913             : 
    1914         206 :     GDALPDFRasterDesc oRasterDesc;
    1915             : 
    1916         103 :     if (pfnProgress == nullptr)
    1917           0 :         pfnProgress = GDALDummyProgress;
    1918             : 
    1919         103 :     oRasterDesc.nOCGRasterId = WriteOCG(pszLayerName);
    1920             : 
    1921             :     /* Does the source image has a color table ? */
    1922         103 :     auto nColorTableId = WriteColorTable(poDS);
    1923             : 
    1924         103 :     int nXBlocks = DIV_ROUND_UP(nWidth, nBlockXSize);
    1925         103 :     int nYBlocks = DIV_ROUND_UP(nHeight, nBlockYSize);
    1926         103 :     int nBlocks = nXBlocks * nYBlocks;
    1927             :     int nBlockXOff, nBlockYOff;
    1928         220 :     for (nBlockYOff = 0; nBlockYOff < nYBlocks; nBlockYOff++)
    1929             :     {
    1930         314 :         for (nBlockXOff = 0; nBlockXOff < nXBlocks; nBlockXOff++)
    1931             :         {
    1932             :             const int nReqWidth =
    1933         197 :                 std::min(nBlockXSize, nWidth - nBlockXOff * nBlockXSize);
    1934             :             const int nReqHeight =
    1935         197 :                 std::min(nBlockYSize, nHeight - nBlockYOff * nBlockYSize);
    1936         197 :             int iImage = nBlockYOff * nXBlocks + nBlockXOff;
    1937             : 
    1938         394 :             void *pScaledData = GDALCreateScaledProgress(
    1939         197 :                 iImage / (double)nBlocks, (iImage + 1) / (double)nBlocks,
    1940             :                 pfnProgress, pProgressData);
    1941         197 :             int nX = nBlockXOff * nBlockXSize;
    1942         197 :             int nY = nBlockYOff * nBlockYSize;
    1943             : 
    1944             :             auto nImageId =
    1945             :                 WriteBlock(poDS, nX, nY, nReqWidth, nReqHeight, nColorTableId,
    1946             :                            eCompressMethod, nPredictor, nJPEGQuality,
    1947         197 :                            pszJPEG2000_DRIVER, GDALScaledProgress, pScaledData);
    1948             : 
    1949         197 :             GDALDestroyScaledProgress(pScaledData);
    1950             : 
    1951         197 :             if (!nImageId.toBool())
    1952           2 :                 return false;
    1953             : 
    1954         195 :             GDALPDFImageDesc oImageDesc;
    1955         195 :             oImageDesc.nImageId = nImageId;
    1956         195 :             oImageDesc.dfXOff = nX / dfUserUnit + oPageContext.sMargins.nLeft;
    1957         195 :             oImageDesc.dfYOff = (nHeight - nY - nReqHeight) / dfUserUnit +
    1958         195 :                                 oPageContext.sMargins.nBottom;
    1959         195 :             oImageDesc.dfXSize = nReqWidth / dfUserUnit;
    1960         195 :             oImageDesc.dfYSize = nReqHeight / dfUserUnit;
    1961             : 
    1962         195 :             oRasterDesc.asImageDesc.push_back(oImageDesc);
    1963             :         }
    1964             :     }
    1965             : 
    1966         101 :     oPageContext.asRasterDesc.push_back(oRasterDesc);
    1967             : 
    1968         101 :     return true;
    1969             : }
    1970             : 
    1971             : /************************************************************************/
    1972             : /*                        WriteClippedImagery()                         */
    1973             : /************************************************************************/
    1974             : 
    1975           4 : bool GDALPDFWriter::WriteClippedImagery(
    1976             :     GDALDataset *poDS, const char *pszLayerName,
    1977             :     PDFCompressMethod eCompressMethod, int nPredictor, int nJPEGQuality,
    1978             :     const char *pszJPEG2000_DRIVER, int nBlockXSize, int nBlockYSize,
    1979             :     GDALProgressFunc pfnProgress, void *pProgressData)
    1980             : {
    1981           4 :     double dfUserUnit = oPageContext.dfDPI * USER_UNIT_IN_INCH;
    1982             : 
    1983           8 :     GDALPDFRasterDesc oRasterDesc;
    1984             : 
    1985             :     /* Get clipping dataset bounding-box */
    1986             :     double adfClippingGeoTransform[6];
    1987           4 :     GDALDataset *poClippingDS = oPageContext.poClippingDS;
    1988           4 :     poClippingDS->GetGeoTransform(adfClippingGeoTransform);
    1989           4 :     int nClippingWidth = poClippingDS->GetRasterXSize();
    1990           4 :     int nClippingHeight = poClippingDS->GetRasterYSize();
    1991           4 :     double dfClippingMinX = adfClippingGeoTransform[0];
    1992           4 :     double dfClippingMaxX =
    1993           4 :         dfClippingMinX + nClippingWidth * adfClippingGeoTransform[1];
    1994           4 :     double dfClippingMaxY = adfClippingGeoTransform[3];
    1995           4 :     double dfClippingMinY =
    1996           4 :         dfClippingMaxY + nClippingHeight * adfClippingGeoTransform[5];
    1997             : 
    1998           4 :     if (dfClippingMaxY < dfClippingMinY)
    1999             :     {
    2000           0 :         std::swap(dfClippingMinY, dfClippingMaxY);
    2001             :     }
    2002             : 
    2003             :     /* Get current dataset dataset bounding-box */
    2004             :     double adfGeoTransform[6];
    2005           4 :     poDS->GetGeoTransform(adfGeoTransform);
    2006           4 :     int nWidth = poDS->GetRasterXSize();
    2007           4 :     int nHeight = poDS->GetRasterYSize();
    2008           4 :     double dfRasterMinX = adfGeoTransform[0];
    2009             :     // double dfRasterMaxX = dfRasterMinX + nWidth * adfGeoTransform[1];
    2010           4 :     double dfRasterMaxY = adfGeoTransform[3];
    2011           4 :     double dfRasterMinY = dfRasterMaxY + nHeight * adfGeoTransform[5];
    2012             : 
    2013           4 :     if (dfRasterMaxY < dfRasterMinY)
    2014             :     {
    2015           0 :         std::swap(dfRasterMinY, dfRasterMaxY);
    2016             :     }
    2017             : 
    2018           4 :     if (pfnProgress == nullptr)
    2019           2 :         pfnProgress = GDALDummyProgress;
    2020             : 
    2021           4 :     oRasterDesc.nOCGRasterId = WriteOCG(pszLayerName);
    2022             : 
    2023             :     /* Does the source image has a color table ? */
    2024           4 :     auto nColorTableId = WriteColorTable(poDS);
    2025             : 
    2026           4 :     int nXBlocks = DIV_ROUND_UP(nWidth, nBlockXSize);
    2027           4 :     int nYBlocks = DIV_ROUND_UP(nHeight, nBlockYSize);
    2028           4 :     int nBlocks = nXBlocks * nYBlocks;
    2029             :     int nBlockXOff, nBlockYOff;
    2030           8 :     for (nBlockYOff = 0; nBlockYOff < nYBlocks; nBlockYOff++)
    2031             :     {
    2032           8 :         for (nBlockXOff = 0; nBlockXOff < nXBlocks; nBlockXOff++)
    2033             :         {
    2034             :             int nReqWidth =
    2035           4 :                 std::min(nBlockXSize, nWidth - nBlockXOff * nBlockXSize);
    2036             :             int nReqHeight =
    2037           4 :                 std::min(nBlockYSize, nHeight - nBlockYOff * nBlockYSize);
    2038           4 :             int iImage = nBlockYOff * nXBlocks + nBlockXOff;
    2039             : 
    2040           8 :             void *pScaledData = GDALCreateScaledProgress(
    2041           4 :                 iImage / (double)nBlocks, (iImage + 1) / (double)nBlocks,
    2042             :                 pfnProgress, pProgressData);
    2043             : 
    2044           4 :             int nX = nBlockXOff * nBlockXSize;
    2045           4 :             int nY = nBlockYOff * nBlockYSize;
    2046             : 
    2047             :             /* Compute extent of block to write */
    2048           4 :             double dfBlockMinX = adfGeoTransform[0] + nX * adfGeoTransform[1];
    2049           4 :             double dfBlockMaxX =
    2050           4 :                 adfGeoTransform[0] + (nX + nReqWidth) * adfGeoTransform[1];
    2051           4 :             double dfBlockMinY =
    2052           4 :                 adfGeoTransform[3] + (nY + nReqHeight) * adfGeoTransform[5];
    2053           4 :             double dfBlockMaxY = adfGeoTransform[3] + nY * adfGeoTransform[5];
    2054             : 
    2055           4 :             if (dfBlockMaxY < dfBlockMinY)
    2056             :             {
    2057           0 :                 std::swap(dfBlockMinY, dfBlockMaxY);
    2058             :             }
    2059             : 
    2060             :             // Clip the extent of the block with the extent of the main raster.
    2061             :             const double dfIntersectMinX =
    2062           4 :                 std::max(dfBlockMinX, dfClippingMinX);
    2063             :             const double dfIntersectMinY =
    2064           4 :                 std::max(dfBlockMinY, dfClippingMinY);
    2065             :             const double dfIntersectMaxX =
    2066           4 :                 std::min(dfBlockMaxX, dfClippingMaxX);
    2067             :             const double dfIntersectMaxY =
    2068           4 :                 std::min(dfBlockMaxY, dfClippingMaxY);
    2069             : 
    2070           4 :             if (dfIntersectMinX < dfIntersectMaxX &&
    2071             :                 dfIntersectMinY < dfIntersectMaxY)
    2072             :             {
    2073             :                 /* Re-compute (x,y,width,height) subwindow of current raster
    2074             :                  * from */
    2075             :                 /* the extent of the clipped block */
    2076           4 :                 nX = (int)((dfIntersectMinX - dfRasterMinX) /
    2077           4 :                                adfGeoTransform[1] +
    2078             :                            0.5);
    2079           4 :                 if (adfGeoTransform[5] < 0)
    2080           4 :                     nY = (int)((dfRasterMaxY - dfIntersectMaxY) /
    2081           4 :                                    (-adfGeoTransform[5]) +
    2082             :                                0.5);
    2083             :                 else
    2084           0 :                     nY = (int)((dfIntersectMinY - dfRasterMinY) /
    2085           0 :                                    adfGeoTransform[5] +
    2086             :                                0.5);
    2087           4 :                 nReqWidth = (int)((dfIntersectMaxX - dfRasterMinX) /
    2088           4 :                                       adfGeoTransform[1] +
    2089             :                                   0.5) -
    2090             :                             nX;
    2091           4 :                 if (adfGeoTransform[5] < 0)
    2092           4 :                     nReqHeight = (int)((dfRasterMaxY - dfIntersectMinY) /
    2093           4 :                                            (-adfGeoTransform[5]) +
    2094             :                                        0.5) -
    2095             :                                  nY;
    2096             :                 else
    2097           0 :                     nReqHeight = (int)((dfIntersectMaxY - dfRasterMinY) /
    2098           0 :                                            adfGeoTransform[5] +
    2099             :                                        0.5) -
    2100             :                                  nY;
    2101             : 
    2102           4 :                 if (nReqWidth > 0 && nReqHeight > 0)
    2103             :                 {
    2104             :                     auto nImageId = WriteBlock(
    2105             :                         poDS, nX, nY, nReqWidth, nReqHeight, nColorTableId,
    2106             :                         eCompressMethod, nPredictor, nJPEGQuality,
    2107           4 :                         pszJPEG2000_DRIVER, GDALScaledProgress, pScaledData);
    2108             : 
    2109           4 :                     if (!nImageId.toBool())
    2110             :                     {
    2111           0 :                         GDALDestroyScaledProgress(pScaledData);
    2112           0 :                         return false;
    2113             :                     }
    2114             : 
    2115             :                     /* Compute the subwindow in image coordinates of the main
    2116             :                      * raster corresponding */
    2117             :                     /* to the extent of the clipped block */
    2118             :                     double dfXInClippingUnits, dfYInClippingUnits,
    2119             :                         dfReqWidthInClippingUnits, dfReqHeightInClippingUnits;
    2120             : 
    2121           4 :                     dfXInClippingUnits = (dfIntersectMinX - dfClippingMinX) /
    2122           4 :                                          adfClippingGeoTransform[1];
    2123           4 :                     if (adfClippingGeoTransform[5] < 0)
    2124           4 :                         dfYInClippingUnits =
    2125           4 :                             (dfClippingMaxY - dfIntersectMaxY) /
    2126           4 :                             (-adfClippingGeoTransform[5]);
    2127             :                     else
    2128           0 :                         dfYInClippingUnits =
    2129           0 :                             (dfIntersectMinY - dfClippingMinY) /
    2130           0 :                             adfClippingGeoTransform[5];
    2131           4 :                     dfReqWidthInClippingUnits =
    2132           4 :                         (dfIntersectMaxX - dfClippingMinX) /
    2133           4 :                             adfClippingGeoTransform[1] -
    2134             :                         dfXInClippingUnits;
    2135           4 :                     if (adfClippingGeoTransform[5] < 0)
    2136           4 :                         dfReqHeightInClippingUnits =
    2137           4 :                             (dfClippingMaxY - dfIntersectMinY) /
    2138           4 :                                 (-adfClippingGeoTransform[5]) -
    2139             :                             dfYInClippingUnits;
    2140             :                     else
    2141           0 :                         dfReqHeightInClippingUnits =
    2142           0 :                             (dfIntersectMaxY - dfClippingMinY) /
    2143           0 :                                 adfClippingGeoTransform[5] -
    2144             :                             dfYInClippingUnits;
    2145             : 
    2146           4 :                     GDALPDFImageDesc oImageDesc;
    2147           4 :                     oImageDesc.nImageId = nImageId;
    2148           4 :                     oImageDesc.dfXOff = dfXInClippingUnits / dfUserUnit +
    2149           4 :                                         oPageContext.sMargins.nLeft;
    2150           4 :                     oImageDesc.dfYOff = (nClippingHeight - dfYInClippingUnits -
    2151           4 :                                          dfReqHeightInClippingUnits) /
    2152           4 :                                             dfUserUnit +
    2153           4 :                                         oPageContext.sMargins.nBottom;
    2154           4 :                     oImageDesc.dfXSize = dfReqWidthInClippingUnits / dfUserUnit;
    2155           4 :                     oImageDesc.dfYSize =
    2156           4 :                         dfReqHeightInClippingUnits / dfUserUnit;
    2157             : 
    2158           4 :                     oRasterDesc.asImageDesc.push_back(oImageDesc);
    2159             :                 }
    2160             :             }
    2161             : 
    2162           4 :             GDALDestroyScaledProgress(pScaledData);
    2163             :         }
    2164             :     }
    2165             : 
    2166           4 :     oPageContext.asRasterDesc.push_back(oRasterDesc);
    2167             : 
    2168           4 :     return true;
    2169             : }
    2170             : 
    2171             : /************************************************************************/
    2172             : /*                          WriteOGRDataSource()                        */
    2173             : /************************************************************************/
    2174             : 
    2175           4 : bool GDALPDFWriter::WriteOGRDataSource(const char *pszOGRDataSource,
    2176             :                                        const char *pszOGRDisplayField,
    2177             :                                        const char *pszOGRDisplayLayerNames,
    2178             :                                        const char *pszOGRLinkField,
    2179             :                                        int bWriteOGRAttributes)
    2180             : {
    2181           4 :     OGRDataSourceH hDS = OGROpen(pszOGRDataSource, 0, nullptr);
    2182           4 :     if (hDS == nullptr)
    2183           0 :         return false;
    2184             : 
    2185           4 :     int iObj = 0;
    2186             : 
    2187           4 :     int nLayers = OGR_DS_GetLayerCount(hDS);
    2188             : 
    2189             :     char **papszLayerNames =
    2190           4 :         CSLTokenizeString2(pszOGRDisplayLayerNames, ",", 0);
    2191             : 
    2192           8 :     for (int iLayer = 0; iLayer < nLayers; iLayer++)
    2193             :     {
    2194           8 :         CPLString osLayerName;
    2195           4 :         if (CSLCount(papszLayerNames) < nLayers)
    2196           0 :             osLayerName = OGR_L_GetName(OGR_DS_GetLayer(hDS, iLayer));
    2197             :         else
    2198           4 :             osLayerName = papszLayerNames[iLayer];
    2199             : 
    2200           4 :         WriteOGRLayer(hDS, iLayer, pszOGRDisplayField, pszOGRLinkField,
    2201             :                       osLayerName, bWriteOGRAttributes, iObj);
    2202             :     }
    2203             : 
    2204           4 :     OGRReleaseDataSource(hDS);
    2205             : 
    2206           4 :     CSLDestroy(papszLayerNames);
    2207             : 
    2208           4 :     return true;
    2209             : }
    2210             : 
    2211             : /************************************************************************/
    2212             : /*                           StartOGRLayer()                            */
    2213             : /************************************************************************/
    2214             : 
    2215          37 : GDALPDFLayerDesc GDALPDFWriter::StartOGRLayer(const std::string &osLayerName,
    2216             :                                               int bWriteOGRAttributes)
    2217             : {
    2218          37 :     GDALPDFLayerDesc osVectorDesc;
    2219          37 :     osVectorDesc.osLayerName = osLayerName;
    2220             : #ifdef HACK_TO_GENERATE_OCMD
    2221             :     osVectorDesc.bWriteOGRAttributes = false;
    2222             :     auto nParentOCGId = WriteOCG("parent");
    2223             :     osVectorDesc.nOCGId = WriteOCG(osLayerName.c_str(), nParentOCGId);
    2224             : #else
    2225          37 :     osVectorDesc.bWriteOGRAttributes = bWriteOGRAttributes;
    2226          37 :     osVectorDesc.nOCGId = WriteOCG(osLayerName.c_str());
    2227             : #endif
    2228          37 :     if (bWriteOGRAttributes)
    2229          36 :         osVectorDesc.nFeatureLayerId = AllocNewObject();
    2230             : 
    2231          37 :     return osVectorDesc;
    2232             : }
    2233             : 
    2234             : /************************************************************************/
    2235             : /*                           EndOGRLayer()                              */
    2236             : /************************************************************************/
    2237             : 
    2238          37 : void GDALPDFWriter::EndOGRLayer(GDALPDFLayerDesc &osVectorDesc)
    2239             : {
    2240          37 :     if (osVectorDesc.bWriteOGRAttributes)
    2241             :     {
    2242          36 :         StartObj(osVectorDesc.nFeatureLayerId);
    2243             : 
    2244          72 :         GDALPDFDictionaryRW oDict;
    2245          36 :         oDict.Add("A", &(new GDALPDFDictionaryRW())
    2246          36 :                             ->Add("O", GDALPDFObjectRW::CreateName(
    2247          36 :                                            "UserProperties")));
    2248             : 
    2249          36 :         GDALPDFArrayRW *poArray = new GDALPDFArrayRW();
    2250          36 :         oDict.Add("K", poArray);
    2251             : 
    2252         113 :         for (int i = 0; i < (int)osVectorDesc.aUserPropertiesIds.size(); i++)
    2253             :         {
    2254          77 :             poArray->Add(osVectorDesc.aUserPropertiesIds[i], 0);
    2255             :         }
    2256             : 
    2257          36 :         if (!m_nStructTreeRootId.toBool())
    2258          21 :             m_nStructTreeRootId = AllocNewObject();
    2259             : 
    2260          36 :         oDict.Add("P", m_nStructTreeRootId, 0);
    2261          36 :         oDict.Add("S", GDALPDFObjectRW::CreateName("Feature"));
    2262          36 :         oDict.Add("T", osVectorDesc.osLayerName);
    2263             : 
    2264          36 :         VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
    2265             : 
    2266          36 :         EndObj();
    2267             :     }
    2268             : 
    2269          37 :     oPageContext.asVectorDesc.push_back(osVectorDesc);
    2270          37 : }
    2271             : 
    2272             : /************************************************************************/
    2273             : /*                           WriteOGRLayer()                            */
    2274             : /************************************************************************/
    2275             : 
    2276          37 : int GDALPDFWriter::WriteOGRLayer(OGRDataSourceH hDS, int iLayer,
    2277             :                                  const char *pszOGRDisplayField,
    2278             :                                  const char *pszOGRLinkField,
    2279             :                                  const std::string &osLayerName,
    2280             :                                  int bWriteOGRAttributes, int &iObj)
    2281             : {
    2282          37 :     GDALDataset *poClippingDS = oPageContext.poClippingDS;
    2283             :     double adfGeoTransform[6];
    2284          37 :     if (poClippingDS->GetGeoTransform(adfGeoTransform) != CE_None)
    2285           0 :         return FALSE;
    2286             : 
    2287             :     GDALPDFLayerDesc osVectorDesc =
    2288          37 :         StartOGRLayer(osLayerName, bWriteOGRAttributes);
    2289          37 :     OGRLayerH hLyr = OGR_DS_GetLayer(hDS, iLayer);
    2290             : 
    2291          37 :     const auto poLayerDefn = OGRLayer::FromHandle(hLyr)->GetLayerDefn();
    2292         143 :     for (int i = 0; i < poLayerDefn->GetFieldCount(); i++)
    2293             :     {
    2294         106 :         const auto poFieldDefn = poLayerDefn->GetFieldDefn(i);
    2295         106 :         const char *pszName = poFieldDefn->GetNameRef();
    2296         106 :         osVectorDesc.aosIncludedFields.push_back(pszName);
    2297             :     }
    2298             : 
    2299          37 :     OGRSpatialReferenceH hGDAL_SRS = OGRSpatialReference::ToHandle(
    2300          37 :         const_cast<OGRSpatialReference *>(poClippingDS->GetSpatialRef()));
    2301          37 :     OGRSpatialReferenceH hOGR_SRS = OGR_L_GetSpatialRef(hLyr);
    2302          37 :     OGRCoordinateTransformationH hCT = nullptr;
    2303             : 
    2304          37 :     if (hGDAL_SRS == nullptr && hOGR_SRS != nullptr)
    2305             :     {
    2306           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    2307             :                  "Vector layer has a SRS set, but Raster layer has no SRS set. "
    2308             :                  "Assuming they are the same.");
    2309             :     }
    2310          37 :     else if (hGDAL_SRS != nullptr && hOGR_SRS == nullptr)
    2311             :     {
    2312           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    2313             :                  "Vector layer has no SRS set, but Raster layer has a SRS set. "
    2314             :                  "Assuming they are the same.");
    2315             :     }
    2316          37 :     else if (hGDAL_SRS != nullptr && hOGR_SRS != nullptr)
    2317             :     {
    2318           7 :         if (!OSRIsSame(hGDAL_SRS, hOGR_SRS))
    2319             :         {
    2320           2 :             hCT = OCTNewCoordinateTransformation(hOGR_SRS, hGDAL_SRS);
    2321           2 :             if (hCT == nullptr)
    2322             :             {
    2323           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2324             :                          "Cannot compute coordinate transformation from vector "
    2325             :                          "SRS to raster SRS");
    2326             :             }
    2327             :         }
    2328             :     }
    2329             : 
    2330          37 :     if (hCT == nullptr)
    2331             :     {
    2332          35 :         double dfXMin = adfGeoTransform[0];
    2333          35 :         double dfYMin = adfGeoTransform[3] +
    2334          35 :                         poClippingDS->GetRasterYSize() * adfGeoTransform[5];
    2335          35 :         double dfXMax = adfGeoTransform[0] +
    2336          35 :                         poClippingDS->GetRasterXSize() * adfGeoTransform[1];
    2337          35 :         double dfYMax = adfGeoTransform[3];
    2338          35 :         OGR_L_SetSpatialFilterRect(hLyr, dfXMin, dfYMin, dfXMax, dfYMax);
    2339             :     }
    2340             : 
    2341             :     OGRFeatureH hFeat;
    2342             : 
    2343         131 :     while ((hFeat = OGR_L_GetNextFeature(hLyr)) != nullptr)
    2344             :     {
    2345          94 :         WriteOGRFeature(osVectorDesc, hFeat, hCT, pszOGRDisplayField,
    2346             :                         pszOGRLinkField, bWriteOGRAttributes, iObj);
    2347             : 
    2348          94 :         OGR_F_Destroy(hFeat);
    2349             :     }
    2350             : 
    2351          37 :     EndOGRLayer(osVectorDesc);
    2352             : 
    2353          37 :     if (hCT != nullptr)
    2354           2 :         OCTDestroyCoordinateTransformation(hCT);
    2355             : 
    2356          37 :     return TRUE;
    2357             : }
    2358             : 
    2359             : /************************************************************************/
    2360             : /*                             DrawGeometry()                           */
    2361             : /************************************************************************/
    2362             : 
    2363          94 : static void DrawGeometry(CPLString &osDS, OGRGeometryH hGeom,
    2364             :                          const double adfMatrix[4], bool bPaint = true)
    2365             : {
    2366          94 :     switch (wkbFlatten(OGR_G_GetGeometryType(hGeom)))
    2367             :     {
    2368          45 :         case wkbLineString:
    2369             :         {
    2370          45 :             int nPoints = OGR_G_GetPointCount(hGeom);
    2371         216 :             for (int i = 0; i < nPoints; i++)
    2372             :             {
    2373         171 :                 double dfX = OGR_G_GetX(hGeom, i) * adfMatrix[1] + adfMatrix[0];
    2374         171 :                 double dfY = OGR_G_GetY(hGeom, i) * adfMatrix[3] + adfMatrix[2];
    2375             :                 osDS +=
    2376         171 :                     CPLOPrintf("%f %f %c\n", dfX, dfY, (i == 0) ? 'm' : 'l');
    2377             :             }
    2378          45 :             if (bPaint)
    2379          12 :                 osDS += CPLOPrintf("S\n");
    2380          45 :             break;
    2381             :         }
    2382             : 
    2383          23 :         case wkbPolygon:
    2384             :         {
    2385          23 :             int nParts = OGR_G_GetGeometryCount(hGeom);
    2386          50 :             for (int i = 0; i < nParts; i++)
    2387             :             {
    2388          27 :                 DrawGeometry(osDS, OGR_G_GetGeometryRef(hGeom, i), adfMatrix,
    2389             :                              false);
    2390          27 :                 osDS += CPLOPrintf("h\n");
    2391             :             }
    2392          23 :             if (bPaint)
    2393          13 :                 osDS += CPLOPrintf("b*\n");
    2394          23 :             break;
    2395             :         }
    2396             : 
    2397           6 :         case wkbMultiLineString:
    2398             :         {
    2399           6 :             int nParts = OGR_G_GetGeometryCount(hGeom);
    2400          12 :             for (int i = 0; i < nParts; i++)
    2401             :             {
    2402           6 :                 DrawGeometry(osDS, OGR_G_GetGeometryRef(hGeom, i), adfMatrix,
    2403             :                              false);
    2404             :             }
    2405           6 :             if (bPaint)
    2406           6 :                 osDS += CPLOPrintf("S\n");
    2407           6 :             break;
    2408             :         }
    2409             : 
    2410           8 :         case wkbMultiPolygon:
    2411             :         {
    2412           8 :             int nParts = OGR_G_GetGeometryCount(hGeom);
    2413          18 :             for (int i = 0; i < nParts; i++)
    2414             :             {
    2415          10 :                 DrawGeometry(osDS, OGR_G_GetGeometryRef(hGeom, i), adfMatrix,
    2416             :                              false);
    2417             :             }
    2418           8 :             if (bPaint)
    2419           8 :                 osDS += CPLOPrintf("b*\n");
    2420           8 :             break;
    2421             :         }
    2422             : 
    2423          12 :         default:
    2424          12 :             break;
    2425             :     }
    2426          94 : }
    2427             : 
    2428             : /************************************************************************/
    2429             : /*                           CalculateText()                            */
    2430             : /************************************************************************/
    2431             : 
    2432           7 : static void CalculateText(const CPLString &osText, CPLString &osFont,
    2433             :                           const double dfSize, const bool bBold,
    2434             :                           const bool bItalic, double &dfWidth, double &dfHeight)
    2435             : {
    2436             :     // Character widths of Helvetica, Win-1252 characters 32 to 255
    2437             :     // Helvetica bold, oblique and bold oblique have their own widths,
    2438             :     // but for now we will put up with these widths on all Helvetica variants
    2439           7 :     constexpr GUInt16 anHelveticaCharWidths[] = {
    2440             :         569,  569,  727,  1139, 1139, 1821, 1366, 391,  682,  682,  797,  1196,
    2441             :         569,  682,  569,  569,  1139, 1139, 1139, 1139, 1139, 1139, 1139, 1139,
    2442             :         1139, 1139, 569,  569,  1196, 1196, 1196, 1139, 2079, 1366, 1366, 1479,
    2443             :         1479, 1366, 1251, 1593, 1479, 569,  1024, 1366, 1139, 1706, 1479, 1593,
    2444             :         1366, 1593, 1479, 1366, 1251, 1479, 1366, 1933, 1366, 1366, 1251, 569,
    2445             :         569,  569,  961,  1139, 682,  1139, 1139, 1024, 1139, 1139, 569,  1139,
    2446             :         1139, 455,  455,  1024, 455,  1706, 1139, 1139, 1139, 1139, 682,  1024,
    2447             :         569,  1139, 1024, 1479, 1024, 1024, 1024, 684,  532,  684,  1196, 1536,
    2448             :         1139, 2048, 455,  1139, 682,  2048, 1139, 1139, 682,  2048, 1366, 682,
    2449             :         2048, 2048, 1251, 2048, 2048, 455,  455,  682,  682,  717,  1139, 2048,
    2450             :         682,  2048, 1024, 682,  1933, 2048, 1024, 1366, 569,  682,  1139, 1139,
    2451             :         1139, 1139, 532,  1139, 682,  1509, 758,  1139, 1196, 682,  1509, 1131,
    2452             :         819,  1124, 682,  682,  682,  1180, 1100, 682,  682,  682,  748,  1139,
    2453             :         1708, 1708, 1708, 1251, 1366, 1366, 1366, 1366, 1366, 1366, 2048, 1479,
    2454             :         1366, 1366, 1366, 1366, 569,  569,  569,  569,  1479, 1479, 1593, 1593,
    2455             :         1593, 1593, 1593, 1196, 1593, 1479, 1479, 1479, 1479, 1366, 1366, 1251,
    2456             :         1139, 1139, 1139, 1139, 1139, 1139, 1821, 1024, 1139, 1139, 1139, 1139,
    2457             :         569,  569,  569,  569,  1139, 1139, 1139, 1139, 1139, 1139, 1139, 1124,
    2458             :         1251, 1139, 1139, 1139, 1139, 1024, 1139, 1024};
    2459             : 
    2460             :     // Character widths of Times-Roman, Win-1252 characters 32 to 255
    2461             :     // Times bold, italic and bold italic have their own widths,
    2462             :     // but for now we will put up with these widths on all Times variants
    2463           7 :     constexpr GUInt16 anTimesCharWidths[] = {
    2464             :         512,  682,  836,  1024, 1024, 1706, 1593, 369,  682,  682,  1024, 1155,
    2465             :         512,  682,  512,  569,  1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
    2466             :         1024, 1024, 569,  569,  1155, 1155, 1155, 909,  1886, 1479, 1366, 1366,
    2467             :         1479, 1251, 1139, 1479, 1479, 682,  797,  1479, 1251, 1821, 1479, 1479,
    2468             :         1139, 1479, 1366, 1139, 1251, 1479, 1479, 1933, 1479, 1479, 1251, 682,
    2469             :         569,  682,  961,  1024, 682,  909,  1024, 909,  1024, 909,  682,  1024,
    2470             :         1024, 569,  569,  1024, 569,  1593, 1024, 1024, 1024, 1024, 682,  797,
    2471             :         569,  1024, 1024, 1479, 1024, 1024, 909,  983,  410,  983,  1108, 0,
    2472             :         1024, 2048, 682,  1024, 909,  2048, 1024, 1024, 682,  2048, 1139, 682,
    2473             :         1821, 2048, 1251, 2048, 2048, 682,  682,  909,  909,  717,  1024, 2048,
    2474             :         682,  2007, 797,  682,  1479, 2048, 909,  1479, 512,  682,  1024, 1024,
    2475             :         1024, 1024, 410,  1024, 682,  1556, 565,  1024, 1155, 682,  1556, 1024,
    2476             :         819,  1124, 614,  614,  682,  1180, 928,  682,  682,  614,  635,  1024,
    2477             :         1536, 1536, 1536, 909,  1479, 1479, 1479, 1479, 1479, 1479, 1821, 1366,
    2478             :         1251, 1251, 1251, 1251, 682,  682,  682,  682,  1479, 1479, 1479, 1479,
    2479             :         1479, 1479, 1479, 1155, 1479, 1479, 1479, 1479, 1479, 1479, 1139, 1024,
    2480             :         909,  909,  909,  909,  909,  909,  1366, 909,  909,  909,  909,  909,
    2481             :         569,  569,  569,  569,  1024, 1024, 1024, 1024, 1024, 1024, 1024, 1124,
    2482             :         1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024};
    2483             : 
    2484           7 :     const GUInt16 *panCharacterWidths = nullptr;
    2485             : 
    2486          14 :     if (STARTS_WITH_CI(osFont, "times") ||
    2487           7 :         osFont.find("Serif", 0) != std::string::npos)
    2488             :     {
    2489           0 :         if (bBold && bItalic)
    2490           0 :             osFont = "Times-BoldItalic";
    2491           0 :         else if (bBold)
    2492           0 :             osFont = "Times-Bold";
    2493           0 :         else if (bItalic)
    2494           0 :             osFont = "Times-Italic";
    2495             :         else
    2496           0 :             osFont = "Times-Roman";
    2497             : 
    2498           0 :         panCharacterWidths = anTimesCharWidths;
    2499           0 :         dfHeight = dfSize * 1356.0 / 2048;
    2500             :     }
    2501          14 :     else if (STARTS_WITH_CI(osFont, "courier") ||
    2502           7 :              osFont.find("Mono", 0) != std::string::npos)
    2503             :     {
    2504           0 :         if (bBold && bItalic)
    2505           0 :             osFont = "Courier-BoldOblique";
    2506           0 :         else if (bBold)
    2507           0 :             osFont = "Courier-Bold";
    2508           0 :         else if (bItalic)
    2509           0 :             osFont = "Courier-Oblique";
    2510             :         else
    2511           0 :             osFont = "Courier";
    2512             : 
    2513           0 :         dfHeight = dfSize * 1170.0 / 2048;
    2514             :     }
    2515             :     else
    2516             :     {
    2517           7 :         if (bBold && bItalic)
    2518           0 :             osFont = "Helvetica-BoldOblique";
    2519           7 :         else if (bBold)
    2520           0 :             osFont = "Helvetica-Bold";
    2521           7 :         else if (bItalic)
    2522           0 :             osFont = "Helvetica-Oblique";
    2523             :         else
    2524           7 :             osFont = "Helvetica";
    2525             : 
    2526           7 :         panCharacterWidths = anHelveticaCharWidths;
    2527           7 :         dfHeight = dfSize * 1467.0 / 2048;
    2528             :     }
    2529             : 
    2530           7 :     dfWidth = 0.0;
    2531          62 :     for (const char &ch : osText)
    2532             :     {
    2533          55 :         const int nCh = static_cast<int>(ch);
    2534          55 :         if (nCh < 32)
    2535           0 :             continue;
    2536             : 
    2537         110 :         dfWidth +=
    2538          55 :             (panCharacterWidths ? panCharacterWidths[nCh - 32]
    2539             :                                 : 1229);  // Courier's fixed character width
    2540             :     }
    2541           7 :     dfWidth *= dfSize / 2048;
    2542           7 : }
    2543             : 
    2544             : /************************************************************************/
    2545             : /*                          GetObjectStyle()                            */
    2546             : /************************************************************************/
    2547             : 
    2548         103 : void GDALPDFBaseWriter::GetObjectStyle(
    2549             :     const char *pszStyleString, OGRFeatureH hFeat, const double adfMatrix[4],
    2550             :     std::map<CPLString, GDALPDFImageDesc> oMapSymbolFilenameToDesc,
    2551             :     ObjectStyle &os)
    2552             : {
    2553         103 :     OGRStyleMgrH hSM = OGR_SM_Create(nullptr);
    2554         103 :     if (pszStyleString)
    2555           0 :         OGR_SM_InitStyleString(hSM, pszStyleString);
    2556             :     else
    2557         103 :         OGR_SM_InitFromFeature(hSM, hFeat);
    2558         103 :     int nCount = OGR_SM_GetPartCount(hSM, nullptr);
    2559         149 :     for (int iPart = 0; iPart < nCount; iPart++)
    2560             :     {
    2561          46 :         OGRStyleToolH hTool = OGR_SM_GetPart(hSM, iPart, nullptr);
    2562          46 :         if (hTool)
    2563             :         {
    2564             :             // Figure out how to involve adfMatrix[3] here and below
    2565          44 :             OGR_ST_SetUnit(hTool, OGRSTUMM, 1000.0 / adfMatrix[1]);
    2566          44 :             if (OGR_ST_GetType(hTool) == OGRSTCPen)
    2567             :             {
    2568           4 :                 os.bHasPenBrushOrSymbol = true;
    2569             : 
    2570           4 :                 int bIsNull = TRUE;
    2571             :                 const char *pszColor =
    2572           4 :                     OGR_ST_GetParamStr(hTool, OGRSTPenColor, &bIsNull);
    2573           4 :                 if (pszColor && !bIsNull)
    2574             :                 {
    2575           4 :                     unsigned int nRed = 0;
    2576           4 :                     unsigned int nGreen = 0;
    2577           4 :                     unsigned int nBlue = 0;
    2578           4 :                     unsigned int nAlpha = 255;
    2579           4 :                     int nVals = sscanf(pszColor, "#%2x%2x%2x%2x", &nRed,
    2580             :                                        &nGreen, &nBlue, &nAlpha);
    2581           4 :                     if (nVals >= 3)
    2582             :                     {
    2583           4 :                         os.nPenR = nRed;
    2584           4 :                         os.nPenG = nGreen;
    2585           4 :                         os.nPenB = nBlue;
    2586           4 :                         if (nVals == 4)
    2587           0 :                             os.nPenA = nAlpha;
    2588             :                     }
    2589             :                 }
    2590             : 
    2591             :                 const char *pszDash =
    2592           4 :                     OGR_ST_GetParamStr(hTool, OGRSTPenPattern, &bIsNull);
    2593           4 :                 if (pszDash && !bIsNull)
    2594             :                 {
    2595           2 :                     char **papszTokens = CSLTokenizeString2(pszDash, " ", 0);
    2596           2 :                     int nTokens = CSLCount(papszTokens);
    2597           2 :                     if ((nTokens % 2) == 0)
    2598             :                     {
    2599           6 :                         for (int i = 0; i < nTokens; i++)
    2600             :                         {
    2601           4 :                             double dfElement = CPLAtof(papszTokens[i]);
    2602           4 :                             dfElement *= adfMatrix[1];  // should involve
    2603             :                                                         // adfMatrix[3] too
    2604           4 :                             os.osDashArray += CPLSPrintf("%f ", dfElement);
    2605             :                         }
    2606             :                     }
    2607           2 :                     CSLDestroy(papszTokens);
    2608             :                 }
    2609             : 
    2610             :                 // OGRSTUnitId eUnit = OGR_ST_GetUnit(hTool);
    2611             :                 double dfWidth =
    2612           4 :                     OGR_ST_GetParamDbl(hTool, OGRSTPenWidth, &bIsNull);
    2613           4 :                 if (!bIsNull)
    2614           4 :                     os.dfPenWidth = dfWidth;
    2615             :             }
    2616          40 :             else if (OGR_ST_GetType(hTool) == OGRSTCBrush)
    2617             :             {
    2618           2 :                 os.bHasPenBrushOrSymbol = true;
    2619             : 
    2620             :                 int bIsNull;
    2621             :                 const char *pszColor =
    2622           2 :                     OGR_ST_GetParamStr(hTool, OGRSTBrushFColor, &bIsNull);
    2623           2 :                 if (pszColor)
    2624             :                 {
    2625           2 :                     unsigned int nRed = 0;
    2626           2 :                     unsigned int nGreen = 0;
    2627           2 :                     unsigned int nBlue = 0;
    2628           2 :                     unsigned int nAlpha = 255;
    2629           2 :                     int nVals = sscanf(pszColor, "#%2x%2x%2x%2x", &nRed,
    2630             :                                        &nGreen, &nBlue, &nAlpha);
    2631           2 :                     if (nVals >= 3)
    2632             :                     {
    2633           2 :                         os.nBrushR = nRed;
    2634           2 :                         os.nBrushG = nGreen;
    2635           2 :                         os.nBrushB = nBlue;
    2636           2 :                         if (nVals == 4)
    2637           0 :                             os.nBrushA = nAlpha;
    2638             :                     }
    2639             :                 }
    2640             :             }
    2641          38 :             else if (OGR_ST_GetType(hTool) == OGRSTCLabel)
    2642             :             {
    2643             :                 int bIsNull;
    2644             :                 const char *pszStr =
    2645           9 :                     OGR_ST_GetParamStr(hTool, OGRSTLabelTextString, &bIsNull);
    2646           9 :                 if (pszStr)
    2647             :                 {
    2648           9 :                     os.osLabelText = pszStr;
    2649             : 
    2650             :                     /* If the text is of the form {stuff}, then it means we want
    2651             :                      * to fetch */
    2652             :                     /* the value of the field "stuff" in the feature */
    2653          11 :                     if (!os.osLabelText.empty() && os.osLabelText[0] == '{' &&
    2654           2 :                         os.osLabelText.back() == '}')
    2655             :                     {
    2656           2 :                         os.osLabelText = pszStr + 1;
    2657           2 :                         os.osLabelText.resize(os.osLabelText.size() - 1);
    2658             : 
    2659             :                         int nIdxField =
    2660           2 :                             OGR_F_GetFieldIndex(hFeat, os.osLabelText);
    2661           2 :                         if (nIdxField >= 0)
    2662             :                             os.osLabelText =
    2663           2 :                                 OGR_F_GetFieldAsString(hFeat, nIdxField);
    2664             :                         else
    2665           0 :                             os.osLabelText = "";
    2666             :                     }
    2667             :                 }
    2668             : 
    2669             :                 const char *pszColor =
    2670           9 :                     OGR_ST_GetParamStr(hTool, OGRSTLabelFColor, &bIsNull);
    2671           9 :                 if (pszColor && !bIsNull)
    2672             :                 {
    2673           2 :                     unsigned int nRed = 0;
    2674           2 :                     unsigned int nGreen = 0;
    2675           2 :                     unsigned int nBlue = 0;
    2676           2 :                     unsigned int nAlpha = 255;
    2677           2 :                     int nVals = sscanf(pszColor, "#%2x%2x%2x%2x", &nRed,
    2678             :                                        &nGreen, &nBlue, &nAlpha);
    2679           2 :                     if (nVals >= 3)
    2680             :                     {
    2681           2 :                         os.nTextR = nRed;
    2682           2 :                         os.nTextG = nGreen;
    2683           2 :                         os.nTextB = nBlue;
    2684           2 :                         if (nVals == 4)
    2685           2 :                             os.nTextA = nAlpha;
    2686             :                     }
    2687             :                 }
    2688             : 
    2689             :                 pszStr =
    2690           9 :                     OGR_ST_GetParamStr(hTool, OGRSTLabelFontName, &bIsNull);
    2691           9 :                 if (pszStr && !bIsNull)
    2692           2 :                     os.osTextFont = pszStr;
    2693             : 
    2694             :                 double dfVal =
    2695           9 :                     OGR_ST_GetParamDbl(hTool, OGRSTLabelSize, &bIsNull);
    2696           9 :                 if (!bIsNull)
    2697           7 :                     os.dfTextSize = dfVal;
    2698             : 
    2699           9 :                 dfVal = OGR_ST_GetParamDbl(hTool, OGRSTLabelAngle, &bIsNull);
    2700           9 :                 if (!bIsNull)
    2701           4 :                     os.dfTextAngle = dfVal * M_PI / 180.0;
    2702             : 
    2703           9 :                 dfVal = OGR_ST_GetParamDbl(hTool, OGRSTLabelStretch, &bIsNull);
    2704           9 :                 if (!bIsNull)
    2705           0 :                     os.dfTextStretch = dfVal / 100.0;
    2706             : 
    2707           9 :                 dfVal = OGR_ST_GetParamDbl(hTool, OGRSTLabelDx, &bIsNull);
    2708           9 :                 if (!bIsNull)
    2709           2 :                     os.dfTextDx = dfVal;
    2710             : 
    2711           9 :                 dfVal = OGR_ST_GetParamDbl(hTool, OGRSTLabelDy, &bIsNull);
    2712           9 :                 if (!bIsNull)
    2713           2 :                     os.dfTextDy = dfVal;
    2714             : 
    2715             :                 int nVal =
    2716           9 :                     OGR_ST_GetParamNum(hTool, OGRSTLabelAnchor, &bIsNull);
    2717           9 :                 if (!bIsNull)
    2718           2 :                     os.nTextAnchor = nVal;
    2719             : 
    2720           9 :                 nVal = OGR_ST_GetParamNum(hTool, OGRSTLabelBold, &bIsNull);
    2721           9 :                 if (!bIsNull)
    2722           0 :                     os.bTextBold = (nVal != 0);
    2723             : 
    2724           9 :                 nVal = OGR_ST_GetParamNum(hTool, OGRSTLabelItalic, &bIsNull);
    2725           9 :                 if (!bIsNull)
    2726           0 :                     os.bTextItalic = (nVal != 0);
    2727             :             }
    2728          29 :             else if (OGR_ST_GetType(hTool) == OGRSTCSymbol)
    2729             :             {
    2730          29 :                 os.bHasPenBrushOrSymbol = true;
    2731             : 
    2732             :                 int bIsNull;
    2733             :                 const char *pszSymbolId =
    2734          29 :                     OGR_ST_GetParamStr(hTool, OGRSTSymbolId, &bIsNull);
    2735          29 :                 if (pszSymbolId && !bIsNull)
    2736             :                 {
    2737          29 :                     os.osSymbolId = pszSymbolId;
    2738             : 
    2739          29 :                     if (strstr(pszSymbolId, "ogr-sym-") == nullptr)
    2740             :                     {
    2741           3 :                         if (oMapSymbolFilenameToDesc.find(os.osSymbolId) ==
    2742           6 :                             oMapSymbolFilenameToDesc.end())
    2743             :                         {
    2744           3 :                             CPLPushErrorHandler(CPLQuietErrorHandler);
    2745             :                             GDALDatasetH hImageDS =
    2746           3 :                                 GDALOpen(os.osSymbolId, GA_ReadOnly);
    2747           3 :                             CPLPopErrorHandler();
    2748           3 :                             if (hImageDS != nullptr)
    2749             :                             {
    2750           3 :                                 os.nImageWidth = GDALGetRasterXSize(hImageDS);
    2751           3 :                                 os.nImageHeight = GDALGetRasterYSize(hImageDS);
    2752             : 
    2753             :                                 os.nImageSymbolId = WriteBlock(
    2754             :                                     GDALDataset::FromHandle(hImageDS), 0, 0,
    2755             :                                     os.nImageWidth, os.nImageHeight,
    2756           3 :                                     GDALPDFObjectNum(), COMPRESS_DEFAULT, 0, -1,
    2757           3 :                                     nullptr, nullptr, nullptr);
    2758           3 :                                 GDALClose(hImageDS);
    2759             :                             }
    2760             : 
    2761           3 :                             GDALPDFImageDesc oDesc;
    2762           3 :                             oDesc.nImageId = os.nImageSymbolId;
    2763           3 :                             oDesc.dfXOff = 0;
    2764           3 :                             oDesc.dfYOff = 0;
    2765           3 :                             oDesc.dfXSize = os.nImageWidth;
    2766           3 :                             oDesc.dfYSize = os.nImageHeight;
    2767           3 :                             oMapSymbolFilenameToDesc[os.osSymbolId] = oDesc;
    2768             :                         }
    2769             :                         else
    2770             :                         {
    2771             :                             const GDALPDFImageDesc &oDesc =
    2772           0 :                                 oMapSymbolFilenameToDesc[os.osSymbolId];
    2773           0 :                             os.nImageSymbolId = oDesc.nImageId;
    2774           0 :                             os.nImageWidth = (int)oDesc.dfXSize;
    2775           0 :                             os.nImageHeight = (int)oDesc.dfYSize;
    2776             :                         }
    2777             :                     }
    2778             :                 }
    2779             : 
    2780             :                 double dfVal =
    2781          29 :                     OGR_ST_GetParamDbl(hTool, OGRSTSymbolSize, &bIsNull);
    2782          29 :                 if (!bIsNull)
    2783             :                 {
    2784          27 :                     os.dfSymbolSize = dfVal;
    2785             :                 }
    2786             : 
    2787             :                 const char *pszColor =
    2788          29 :                     OGR_ST_GetParamStr(hTool, OGRSTSymbolColor, &bIsNull);
    2789          29 :                 if (pszColor && !bIsNull)
    2790             :                 {
    2791          28 :                     unsigned int nRed = 0;
    2792          28 :                     unsigned int nGreen = 0;
    2793          28 :                     unsigned int nBlue = 0;
    2794          28 :                     unsigned int nAlpha = 255;
    2795          28 :                     int nVals = sscanf(pszColor, "#%2x%2x%2x%2x", &nRed,
    2796             :                                        &nGreen, &nBlue, &nAlpha);
    2797          28 :                     if (nVals >= 3)
    2798             :                     {
    2799          28 :                         os.bSymbolColorDefined = TRUE;
    2800          28 :                         os.nSymbolR = nRed;
    2801          28 :                         os.nSymbolG = nGreen;
    2802          28 :                         os.nSymbolB = nBlue;
    2803          28 :                         if (nVals == 4)
    2804           1 :                             os.nSymbolA = nAlpha;
    2805             :                     }
    2806             :                 }
    2807             :             }
    2808             : 
    2809          44 :             OGR_ST_Destroy(hTool);
    2810             :         }
    2811             :     }
    2812         103 :     OGR_SM_Destroy(hSM);
    2813             : 
    2814         103 :     OGRGeometryH hGeom = OGR_F_GetGeometryRef(hFeat);
    2815         155 :     if (wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint &&
    2816          52 :         os.bSymbolColorDefined)
    2817             :     {
    2818          28 :         os.nPenR = os.nSymbolR;
    2819          28 :         os.nPenG = os.nSymbolG;
    2820          28 :         os.nPenB = os.nSymbolB;
    2821          28 :         os.nPenA = os.nSymbolA;
    2822          28 :         os.nBrushR = os.nSymbolR;
    2823          28 :         os.nBrushG = os.nSymbolG;
    2824          28 :         os.nBrushB = os.nSymbolB;
    2825          28 :         os.nBrushA = os.nSymbolA;
    2826             :     }
    2827         103 : }
    2828             : 
    2829             : /************************************************************************/
    2830             : /*                           ComputeIntBBox()                           */
    2831             : /************************************************************************/
    2832             : 
    2833          91 : void GDALPDFBaseWriter::ComputeIntBBox(
    2834             :     OGRGeometryH hGeom, const OGREnvelope &sEnvelope, const double adfMatrix[4],
    2835             :     const GDALPDFWriter::ObjectStyle &os, double dfRadius, int &bboxXMin,
    2836             :     int &bboxYMin, int &bboxXMax, int &bboxYMax)
    2837             : {
    2838         133 :     if (wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint &&
    2839          42 :         os.nImageSymbolId.toBool())
    2840             :     {
    2841           2 :         const double dfSemiWidth =
    2842           2 :             (os.nImageWidth >= os.nImageHeight)
    2843           2 :                 ? dfRadius
    2844           0 :                 : dfRadius * os.nImageWidth / os.nImageHeight;
    2845           2 :         const double dfSemiHeight =
    2846           2 :             (os.nImageWidth >= os.nImageHeight)
    2847           2 :                 ? dfRadius * os.nImageHeight / os.nImageWidth
    2848             :                 : dfRadius;
    2849           2 :         bboxXMin = (int)floor(sEnvelope.MinX * adfMatrix[1] + adfMatrix[0] -
    2850             :                               dfSemiWidth);
    2851           2 :         bboxYMin = (int)floor(sEnvelope.MinY * adfMatrix[3] + adfMatrix[2] -
    2852             :                               dfSemiHeight);
    2853           2 :         bboxXMax = (int)ceil(sEnvelope.MaxX * adfMatrix[1] + adfMatrix[0] +
    2854             :                              dfSemiWidth);
    2855           2 :         bboxYMax = (int)ceil(sEnvelope.MaxY * adfMatrix[3] + adfMatrix[2] +
    2856             :                              dfSemiHeight);
    2857             :     }
    2858             :     else
    2859             :     {
    2860          89 :         double dfMargin = os.dfPenWidth;
    2861          89 :         if (wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint)
    2862             :         {
    2863          40 :             if (os.osSymbolId == "ogr-sym-6" || os.osSymbolId == "ogr-sym-7")
    2864             :             {
    2865           4 :                 const double dfSqrt3 = 1.73205080757;
    2866           4 :                 dfMargin += dfRadius * 2 * dfSqrt3 / 3;
    2867             :             }
    2868             :             else
    2869          36 :                 dfMargin += dfRadius;
    2870             :         }
    2871          89 :         bboxXMin =
    2872          89 :             (int)floor(sEnvelope.MinX * adfMatrix[1] + adfMatrix[0] - dfMargin);
    2873          89 :         bboxYMin =
    2874          89 :             (int)floor(sEnvelope.MinY * adfMatrix[3] + adfMatrix[2] - dfMargin);
    2875          89 :         bboxXMax =
    2876          89 :             (int)ceil(sEnvelope.MaxX * adfMatrix[1] + adfMatrix[0] + dfMargin);
    2877          89 :         bboxYMax =
    2878          89 :             (int)ceil(sEnvelope.MaxY * adfMatrix[3] + adfMatrix[2] + dfMargin);
    2879             :     }
    2880          91 : }
    2881             : 
    2882             : /************************************************************************/
    2883             : /*                              WriteLink()                             */
    2884             : /************************************************************************/
    2885             : 
    2886          91 : GDALPDFObjectNum GDALPDFBaseWriter::WriteLink(OGRFeatureH hFeat,
    2887             :                                               const char *pszOGRLinkField,
    2888             :                                               const double adfMatrix[4],
    2889             :                                               int bboxXMin, int bboxYMin,
    2890             :                                               int bboxXMax, int bboxYMax)
    2891             : {
    2892          91 :     GDALPDFObjectNum nAnnotId;
    2893          91 :     int iField = -1;
    2894          91 :     const char *pszLinkVal = nullptr;
    2895         120 :     if (pszOGRLinkField != nullptr &&
    2896          29 :         (iField = OGR_FD_GetFieldIndex(OGR_F_GetDefnRef(hFeat),
    2897          29 :                                        pszOGRLinkField)) >= 0 &&
    2898         123 :         OGR_F_IsFieldSetAndNotNull(hFeat, iField) &&
    2899           3 :         strcmp((pszLinkVal = OGR_F_GetFieldAsString(hFeat, iField)), "") != 0)
    2900             :     {
    2901           3 :         nAnnotId = AllocNewObject();
    2902           3 :         StartObj(nAnnotId);
    2903             :         {
    2904           3 :             GDALPDFDictionaryRW oDict;
    2905           3 :             oDict.Add("Type", GDALPDFObjectRW::CreateName("Annot"));
    2906           3 :             oDict.Add("Subtype", GDALPDFObjectRW::CreateName("Link"));
    2907           3 :             oDict.Add("Rect", &(new GDALPDFArrayRW())
    2908           3 :                                    ->Add(bboxXMin)
    2909           3 :                                    .Add(bboxYMin)
    2910           3 :                                    .Add(bboxXMax)
    2911           3 :                                    .Add(bboxYMax));
    2912           3 :             oDict.Add("A", &(new GDALPDFDictionaryRW())
    2913           3 :                                 ->Add("S", GDALPDFObjectRW::CreateName("URI"))
    2914           3 :                                 .Add("URI", pszLinkVal));
    2915             :             oDict.Add("BS",
    2916           3 :                       &(new GDALPDFDictionaryRW())
    2917           3 :                            ->Add("Type", GDALPDFObjectRW::CreateName("Border"))
    2918           3 :                            .Add("S", GDALPDFObjectRW::CreateName("S"))
    2919           3 :                            .Add("W", 0));
    2920           3 :             oDict.Add("Border", &(new GDALPDFArrayRW())->Add(0).Add(0).Add(0));
    2921           3 :             oDict.Add("H", GDALPDFObjectRW::CreateName("I"));
    2922             : 
    2923           3 :             OGRGeometryH hGeom = OGR_F_GetGeometryRef(hFeat);
    2924           6 :             if (wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPolygon &&
    2925           3 :                 OGR_G_GetGeometryCount(hGeom) == 1)
    2926             :             {
    2927           3 :                 OGRGeometryH hSubGeom = OGR_G_GetGeometryRef(hGeom, 0);
    2928           3 :                 int nPoints = OGR_G_GetPointCount(hSubGeom);
    2929           3 :                 if (nPoints == 4 || nPoints == 5)
    2930             :                 {
    2931           6 :                     std::vector<double> adfX, adfY;
    2932          18 :                     for (int i = 0; i < nPoints; i++)
    2933             :                     {
    2934          15 :                         double dfX = OGR_G_GetX(hSubGeom, i) * adfMatrix[1] +
    2935          15 :                                      adfMatrix[0];
    2936          15 :                         double dfY = OGR_G_GetY(hSubGeom, i) * adfMatrix[3] +
    2937          15 :                                      adfMatrix[2];
    2938          15 :                         adfX.push_back(dfX);
    2939          15 :                         adfY.push_back(dfY);
    2940             :                     }
    2941           3 :                     if (nPoints == 4)
    2942             :                     {
    2943           0 :                         oDict.Add("QuadPoints", &(new GDALPDFArrayRW())
    2944           0 :                                                      ->Add(adfX[0])
    2945           0 :                                                      .Add(adfY[0])
    2946           0 :                                                      .Add(adfX[1])
    2947           0 :                                                      .Add(adfY[1])
    2948           0 :                                                      .Add(adfX[2])
    2949           0 :                                                      .Add(adfY[2])
    2950           0 :                                                      .Add(adfX[0])
    2951           0 :                                                      .Add(adfY[0]));
    2952             :                     }
    2953           3 :                     else if (nPoints == 5)
    2954             :                     {
    2955           3 :                         oDict.Add("QuadPoints", &(new GDALPDFArrayRW())
    2956           3 :                                                      ->Add(adfX[0])
    2957           3 :                                                      .Add(adfY[0])
    2958           3 :                                                      .Add(adfX[1])
    2959           3 :                                                      .Add(adfY[1])
    2960           3 :                                                      .Add(adfX[2])
    2961           3 :                                                      .Add(adfY[2])
    2962           3 :                                                      .Add(adfX[3])
    2963           3 :                                                      .Add(adfY[3]));
    2964             :                     }
    2965             :                 }
    2966             :             }
    2967             : 
    2968           3 :             VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
    2969             :         }
    2970           3 :         EndObj();
    2971             :     }
    2972          91 :     return nAnnotId;
    2973             : }
    2974             : 
    2975             : /************************************************************************/
    2976             : /*                        GenerateDrawingStream()                       */
    2977             : /************************************************************************/
    2978             : 
    2979          98 : CPLString GDALPDFBaseWriter::GenerateDrawingStream(OGRGeometryH hGeom,
    2980             :                                                    const double adfMatrix[4],
    2981             :                                                    ObjectStyle &os,
    2982             :                                                    double dfRadius)
    2983             : {
    2984          98 :     CPLString osDS;
    2985             : 
    2986          98 :     if (!os.nImageSymbolId.toBool())
    2987             :     {
    2988         190 :         osDS += CPLOPrintf("%f w\n"
    2989             :                            "0 J\n"
    2990             :                            "0 j\n"
    2991             :                            "10 M\n"
    2992             :                            "[%s]0 d\n",
    2993          95 :                            os.dfPenWidth, os.osDashArray.c_str());
    2994             : 
    2995          95 :         osDS += CPLOPrintf("%f %f %f RG\n", os.nPenR / 255.0, os.nPenG / 255.0,
    2996          95 :                            os.nPenB / 255.0);
    2997          95 :         osDS += CPLOPrintf("%f %f %f rg\n", os.nBrushR / 255.0,
    2998          95 :                            os.nBrushG / 255.0, os.nBrushB / 255.0);
    2999             :     }
    3000             : 
    3001         196 :     if ((os.bHasPenBrushOrSymbol || os.osLabelText.empty()) &&
    3002          98 :         wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint)
    3003             :     {
    3004          47 :         double dfX = OGR_G_GetX(hGeom, 0) * adfMatrix[1] + adfMatrix[0];
    3005          47 :         double dfY = OGR_G_GetY(hGeom, 0) * adfMatrix[3] + adfMatrix[2];
    3006             : 
    3007          47 :         if (os.nImageSymbolId.toBool())
    3008             :         {
    3009           3 :             const double dfSemiWidth =
    3010           3 :                 (os.nImageWidth >= os.nImageHeight)
    3011           3 :                     ? dfRadius
    3012           0 :                     : dfRadius * os.nImageWidth / os.nImageHeight;
    3013           3 :             const double dfSemiHeight =
    3014           3 :                 (os.nImageWidth >= os.nImageHeight)
    3015           3 :                     ? dfRadius * os.nImageHeight / os.nImageWidth
    3016             :                     : dfRadius;
    3017           6 :             osDS += CPLOPrintf("%f 0 0 %f %f %f cm\n", 2 * dfSemiWidth,
    3018             :                                2 * dfSemiHeight, dfX - dfSemiWidth,
    3019           3 :                                dfY - dfSemiHeight);
    3020           3 :             osDS += CPLOPrintf("/SymImage%d Do\n", os.nImageSymbolId.toInt());
    3021             :         }
    3022          44 :         else if (os.osSymbolId == "")
    3023          20 :             os.osSymbolId = "ogr-sym-3"; /* symbol by default */
    3024          46 :         else if (!(os.osSymbolId == "ogr-sym-0" ||
    3025          22 :                    os.osSymbolId == "ogr-sym-1" ||
    3026          16 :                    os.osSymbolId == "ogr-sym-2" ||
    3027          14 :                    os.osSymbolId == "ogr-sym-3" ||
    3028          12 :                    os.osSymbolId == "ogr-sym-4" ||
    3029          10 :                    os.osSymbolId == "ogr-sym-5" ||
    3030           8 :                    os.osSymbolId == "ogr-sym-6" ||
    3031           6 :                    os.osSymbolId == "ogr-sym-7" ||
    3032           4 :                    os.osSymbolId == "ogr-sym-8" ||
    3033           2 :                    os.osSymbolId == "ogr-sym-9"))
    3034             :         {
    3035           0 :             CPLDebug("PDF", "Unhandled symbol id : %s. Using ogr-sym-3 instead",
    3036             :                      os.osSymbolId.c_str());
    3037           0 :             os.osSymbolId = "ogr-sym-3";
    3038             :         }
    3039             : 
    3040          47 :         if (os.osSymbolId == "ogr-sym-0") /* cross (+)  */
    3041             :         {
    3042           2 :             osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius, dfY);
    3043           2 :             osDS += CPLOPrintf("%f %f l\n", dfX + dfRadius, dfY);
    3044           2 :             osDS += CPLOPrintf("%f %f m\n", dfX, dfY - dfRadius);
    3045           2 :             osDS += CPLOPrintf("%f %f l\n", dfX, dfY + dfRadius);
    3046           2 :             osDS += CPLOPrintf("S\n");
    3047             :         }
    3048          45 :         else if (os.osSymbolId == "ogr-sym-1") /* diagcross (X) */
    3049             :         {
    3050           6 :             osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius, dfY - dfRadius);
    3051           6 :             osDS += CPLOPrintf("%f %f l\n", dfX + dfRadius, dfY + dfRadius);
    3052           6 :             osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius, dfY + dfRadius);
    3053           6 :             osDS += CPLOPrintf("%f %f l\n", dfX + dfRadius, dfY - dfRadius);
    3054           6 :             osDS += CPLOPrintf("S\n");
    3055             :         }
    3056          76 :         else if (os.osSymbolId == "ogr-sym-2" ||
    3057          37 :                  os.osSymbolId == "ogr-sym-3") /* circle */
    3058             :         {
    3059             :             /* See http://www.whizkidtech.redprince.net/bezier/circle/kappa/ */
    3060          24 :             const double dfKappa = 0.5522847498;
    3061             : 
    3062          24 :             osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius, dfY);
    3063             :             osDS +=
    3064          24 :                 CPLOPrintf("%f %f %f %f %f %f c\n", dfX - dfRadius,
    3065          24 :                            dfY - dfRadius * dfKappa, dfX - dfRadius * dfKappa,
    3066          24 :                            dfY - dfRadius, dfX, dfY - dfRadius);
    3067             :             osDS +=
    3068          24 :                 CPLOPrintf("%f %f %f %f %f %f c\n", dfX + dfRadius * dfKappa,
    3069             :                            dfY - dfRadius, dfX + dfRadius,
    3070          24 :                            dfY - dfRadius * dfKappa, dfX + dfRadius, dfY);
    3071             :             osDS +=
    3072          24 :                 CPLOPrintf("%f %f %f %f %f %f c\n", dfX + dfRadius,
    3073          24 :                            dfY + dfRadius * dfKappa, dfX + dfRadius * dfKappa,
    3074          24 :                            dfY + dfRadius, dfX, dfY + dfRadius);
    3075             :             osDS +=
    3076          24 :                 CPLOPrintf("%f %f %f %f %f %f c\n", dfX - dfRadius * dfKappa,
    3077             :                            dfY + dfRadius, dfX - dfRadius,
    3078          24 :                            dfY + dfRadius * dfKappa, dfX - dfRadius, dfY);
    3079          24 :             if (os.osSymbolId == "ogr-sym-2")
    3080           2 :                 osDS += CPLOPrintf("s\n"); /* not filled */
    3081             :             else
    3082          22 :                 osDS += CPLOPrintf("b*\n"); /* filled */
    3083             :         }
    3084          28 :         else if (os.osSymbolId == "ogr-sym-4" ||
    3085          13 :                  os.osSymbolId == "ogr-sym-5") /* square */
    3086             :         {
    3087           4 :             osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius, dfY + dfRadius);
    3088           4 :             osDS += CPLOPrintf("%f %f l\n", dfX + dfRadius, dfY + dfRadius);
    3089           4 :             osDS += CPLOPrintf("%f %f l\n", dfX + dfRadius, dfY - dfRadius);
    3090           4 :             osDS += CPLOPrintf("%f %f l\n", dfX - dfRadius, dfY - dfRadius);
    3091           4 :             if (os.osSymbolId == "ogr-sym-4")
    3092           2 :                 osDS += CPLOPrintf("s\n"); /* not filled */
    3093             :             else
    3094           2 :                 osDS += CPLOPrintf("b*\n"); /* filled */
    3095             :         }
    3096          20 :         else if (os.osSymbolId == "ogr-sym-6" ||
    3097           9 :                  os.osSymbolId == "ogr-sym-7") /* triangle */
    3098             :         {
    3099           4 :             const double dfSqrt3 = 1.73205080757;
    3100           4 :             osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius,
    3101           4 :                                dfY - dfRadius * dfSqrt3 / 3);
    3102             :             osDS +=
    3103           4 :                 CPLOPrintf("%f %f l\n", dfX, dfY + 2 * dfRadius * dfSqrt3 / 3);
    3104           4 :             osDS += CPLOPrintf("%f %f l\n", dfX + dfRadius,
    3105           4 :                                dfY - dfRadius * dfSqrt3 / 3);
    3106           4 :             if (os.osSymbolId == "ogr-sym-6")
    3107           2 :                 osDS += CPLOPrintf("s\n"); /* not filled */
    3108             :             else
    3109           2 :                 osDS += CPLOPrintf("b*\n"); /* filled */
    3110             :         }
    3111          12 :         else if (os.osSymbolId == "ogr-sym-8" ||
    3112           5 :                  os.osSymbolId == "ogr-sym-9") /* star */
    3113             :         {
    3114           4 :             const double dfSin18divSin126 = 0.38196601125;
    3115           4 :             osDS += CPLOPrintf("%f %f m\n", dfX, dfY + dfRadius);
    3116          40 :             for (int i = 1; i < 10; i++)
    3117             :             {
    3118          36 :                 double dfFactor = ((i % 2) == 1) ? dfSin18divSin126 : 1.0;
    3119          36 :                 osDS += CPLOPrintf("%f %f l\n",
    3120          36 :                                    dfX + cos(M_PI / 2 - i * M_PI * 36 / 180) *
    3121          36 :                                              dfRadius * dfFactor,
    3122          36 :                                    dfY + sin(M_PI / 2 - i * M_PI * 36 / 180) *
    3123          36 :                                              dfRadius * dfFactor);
    3124             :             }
    3125           4 :             if (os.osSymbolId == "ogr-sym-8")
    3126           2 :                 osDS += CPLOPrintf("s\n"); /* not filled */
    3127             :             else
    3128           2 :                 osDS += CPLOPrintf("b*\n"); /* filled */
    3129             :         }
    3130             :     }
    3131             :     else
    3132             :     {
    3133          51 :         DrawGeometry(osDS, hGeom, adfMatrix);
    3134             :     }
    3135             : 
    3136          98 :     return osDS;
    3137             : }
    3138             : 
    3139             : /************************************************************************/
    3140             : /*                          WriteAttributes()                           */
    3141             : /************************************************************************/
    3142             : 
    3143          85 : GDALPDFObjectNum GDALPDFBaseWriter::WriteAttributes(
    3144             :     OGRFeatureH hFeat, const std::vector<CPLString> &aosIncludedFields,
    3145             :     const char *pszOGRDisplayField, int nMCID, const GDALPDFObjectNum &oParent,
    3146             :     const GDALPDFObjectNum &oPage, CPLString &osOutFeatureName)
    3147             : {
    3148             : 
    3149          85 :     int iField = -1;
    3150          85 :     if (pszOGRDisplayField)
    3151             :         iField =
    3152          17 :             OGR_FD_GetFieldIndex(OGR_F_GetDefnRef(hFeat), pszOGRDisplayField);
    3153          85 :     if (iField >= 0)
    3154           7 :         osOutFeatureName = OGR_F_GetFieldAsString(hFeat, iField);
    3155             :     else
    3156             :         osOutFeatureName =
    3157          78 :             CPLSPrintf("feature" CPL_FRMT_GIB, OGR_F_GetFID(hFeat));
    3158             : 
    3159          85 :     auto nFeatureUserProperties = AllocNewObject();
    3160          85 :     StartObj(nFeatureUserProperties);
    3161             : 
    3162          85 :     GDALPDFDictionaryRW oDict;
    3163             : 
    3164          85 :     GDALPDFDictionaryRW *poDictA = new GDALPDFDictionaryRW();
    3165          85 :     oDict.Add("A", poDictA);
    3166          85 :     poDictA->Add("O", GDALPDFObjectRW::CreateName("UserProperties"));
    3167             : 
    3168          85 :     GDALPDFArrayRW *poArray = new GDALPDFArrayRW();
    3169         345 :     for (const auto &fieldName : aosIncludedFields)
    3170             :     {
    3171         260 :         int i = OGR_F_GetFieldIndex(hFeat, fieldName);
    3172         260 :         if (i >= 0 && OGR_F_IsFieldSetAndNotNull(hFeat, i))
    3173             :         {
    3174         191 :             OGRFieldDefnH hFDefn = OGR_F_GetFieldDefnRef(hFeat, i);
    3175         191 :             GDALPDFDictionaryRW *poKV = new GDALPDFDictionaryRW();
    3176         191 :             poKV->Add("N", OGR_Fld_GetNameRef(hFDefn));
    3177         191 :             if (OGR_Fld_GetType(hFDefn) == OFTInteger)
    3178          47 :                 poKV->Add("V", OGR_F_GetFieldAsInteger(hFeat, i));
    3179         144 :             else if (OGR_Fld_GetType(hFDefn) == OFTReal)
    3180          31 :                 poKV->Add("V", OGR_F_GetFieldAsDouble(hFeat, i));
    3181             :             else
    3182         113 :                 poKV->Add("V", OGR_F_GetFieldAsString(hFeat, i));
    3183         191 :             poArray->Add(poKV);
    3184             :         }
    3185             :     }
    3186             : 
    3187          85 :     poDictA->Add("P", poArray);
    3188             : 
    3189          85 :     oDict.Add("K", nMCID);
    3190          85 :     oDict.Add("P", oParent, 0);
    3191          85 :     oDict.Add("Pg", oPage, 0);
    3192          85 :     oDict.Add("S", GDALPDFObjectRW::CreateName("feature"));
    3193          85 :     oDict.Add("T", osOutFeatureName);
    3194             : 
    3195          85 :     VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
    3196             : 
    3197          85 :     EndObj();
    3198             : 
    3199         170 :     return nFeatureUserProperties;
    3200             : }
    3201             : 
    3202             : /************************************************************************/
    3203             : /*                            WriteLabel()                              */
    3204             : /************************************************************************/
    3205             : 
    3206           7 : GDALPDFObjectNum GDALPDFBaseWriter::WriteLabel(
    3207             :     OGRGeometryH hGeom, const double adfMatrix[4], ObjectStyle &os,
    3208             :     PDFCompressMethod eStreamCompressMethod, double bboxXMin, double bboxYMin,
    3209             :     double bboxXMax, double bboxYMax)
    3210             : {
    3211             :     /* -------------------------------------------------------------- */
    3212             :     /*  Work out the text metrics for alignment purposes              */
    3213             :     /* -------------------------------------------------------------- */
    3214             :     double dfWidth, dfHeight;
    3215           7 :     CalculateText(os.osLabelText, os.osTextFont, os.dfTextSize, os.bTextBold,
    3216           7 :                   os.bTextItalic, dfWidth, dfHeight);
    3217           7 :     dfWidth *= os.dfTextStretch;
    3218             : 
    3219           7 :     if (os.nTextAnchor % 3 == 2)  // horizontal center
    3220             :     {
    3221           0 :         os.dfTextDx -= (dfWidth / 2) * cos(os.dfTextAngle);
    3222           0 :         os.dfTextDy -= (dfWidth / 2) * sin(os.dfTextAngle);
    3223             :     }
    3224           7 :     else if (os.nTextAnchor % 3 == 0)  // right
    3225             :     {
    3226           0 :         os.dfTextDx -= dfWidth * cos(os.dfTextAngle);
    3227           0 :         os.dfTextDy -= dfWidth * sin(os.dfTextAngle);
    3228             :     }
    3229             : 
    3230           7 :     if (os.nTextAnchor >= 4 && os.nTextAnchor <= 6)  // vertical center
    3231             :     {
    3232           2 :         os.dfTextDx += (dfHeight / 2) * sin(os.dfTextAngle);
    3233           2 :         os.dfTextDy -= (dfHeight / 2) * cos(os.dfTextAngle);
    3234             :     }
    3235           5 :     else if (os.nTextAnchor >= 7 && os.nTextAnchor <= 9)  // top
    3236             :     {
    3237           0 :         os.dfTextDx += dfHeight * sin(os.dfTextAngle);
    3238           0 :         os.dfTextDy -= dfHeight * cos(os.dfTextAngle);
    3239             :     }
    3240             :     // modes 10,11,12 (baseline) unsupported for the time being
    3241             : 
    3242             :     /* -------------------------------------------------------------- */
    3243             :     /*  Write object dictionary                                       */
    3244             :     /* -------------------------------------------------------------- */
    3245           7 :     auto nObjectId = AllocNewObject();
    3246           7 :     GDALPDFDictionaryRW oDict;
    3247             : 
    3248           7 :     oDict.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
    3249           7 :         .Add("BBox", &((new GDALPDFArrayRW())->Add(bboxXMin).Add(bboxYMin))
    3250           7 :                           .Add(bboxXMax)
    3251           7 :                           .Add(bboxYMax))
    3252           7 :         .Add("Subtype", GDALPDFObjectRW::CreateName("Form"));
    3253             : 
    3254           7 :     GDALPDFDictionaryRW *poResources = new GDALPDFDictionaryRW();
    3255             : 
    3256           7 :     if (os.nTextA != 255)
    3257             :     {
    3258           2 :         GDALPDFDictionaryRW *poGS1 = new GDALPDFDictionaryRW();
    3259           2 :         poGS1->Add("Type", GDALPDFObjectRW::CreateName("ExtGState"));
    3260           2 :         poGS1->Add("ca", (os.nTextA == 127 || os.nTextA == 128)
    3261             :                              ? 0.5
    3262           4 :                              : os.nTextA / 255.0);
    3263             : 
    3264           2 :         GDALPDFDictionaryRW *poExtGState = new GDALPDFDictionaryRW();
    3265           2 :         poExtGState->Add("GS1", poGS1);
    3266             : 
    3267           2 :         poResources->Add("ExtGState", poExtGState);
    3268             :     }
    3269             : 
    3270           7 :     GDALPDFDictionaryRW *poDictF1 = new GDALPDFDictionaryRW();
    3271           7 :     poDictF1->Add("Type", GDALPDFObjectRW::CreateName("Font"));
    3272           7 :     poDictF1->Add("BaseFont", GDALPDFObjectRW::CreateName(os.osTextFont));
    3273           7 :     poDictF1->Add("Encoding", GDALPDFObjectRW::CreateName("WinAnsiEncoding"));
    3274           7 :     poDictF1->Add("Subtype", GDALPDFObjectRW::CreateName("Type1"));
    3275             : 
    3276           7 :     GDALPDFDictionaryRW *poDictFont = new GDALPDFDictionaryRW();
    3277           7 :     poDictFont->Add("F1", poDictF1);
    3278           7 :     poResources->Add("Font", poDictFont);
    3279             : 
    3280           7 :     oDict.Add("Resources", poResources);
    3281             : 
    3282           7 :     StartObjWithStream(nObjectId, oDict,
    3283             :                        eStreamCompressMethod != COMPRESS_NONE);
    3284             : 
    3285             :     /* -------------------------------------------------------------- */
    3286             :     /*  Write object stream                                           */
    3287             :     /* -------------------------------------------------------------- */
    3288             : 
    3289             :     double dfX =
    3290           7 :         OGR_G_GetX(hGeom, 0) * adfMatrix[1] + adfMatrix[0] + os.dfTextDx;
    3291             :     double dfY =
    3292           7 :         OGR_G_GetY(hGeom, 0) * adfMatrix[3] + adfMatrix[2] + os.dfTextDy;
    3293             : 
    3294           7 :     VSIFPrintfL(m_fp, "q\n");
    3295           7 :     VSIFPrintfL(m_fp, "BT\n");
    3296           7 :     if (os.nTextA != 255)
    3297             :     {
    3298           2 :         VSIFPrintfL(m_fp, "/GS1 gs\n");
    3299             :     }
    3300             : 
    3301           7 :     VSIFPrintfL(m_fp, "%f %f %f %f %f %f Tm\n",
    3302           7 :                 cos(os.dfTextAngle) * adfMatrix[1] * os.dfTextStretch,
    3303           7 :                 sin(os.dfTextAngle) * adfMatrix[3] * os.dfTextStretch,
    3304           7 :                 -sin(os.dfTextAngle) * adfMatrix[1],
    3305           7 :                 cos(os.dfTextAngle) * adfMatrix[3], dfX, dfY);
    3306             : 
    3307           7 :     VSIFPrintfL(m_fp, "%f %f %f rg\n", os.nTextR / 255.0, os.nTextG / 255.0,
    3308           7 :                 os.nTextB / 255.0);
    3309             :     // The factor of adfMatrix[1] is introduced in the call to SetUnit near the
    3310             :     // top of this function. Because we are handling the 2D stretch correctly in
    3311             :     // Tm above, we don't need that factor here
    3312           7 :     VSIFPrintfL(m_fp, "/F1 %f Tf\n", os.dfTextSize / adfMatrix[1]);
    3313           7 :     VSIFPrintfL(m_fp, "(");
    3314          62 :     for (size_t i = 0; i < os.osLabelText.size(); i++)
    3315             :     {
    3316         110 :         if (os.osLabelText[i] == '(' || os.osLabelText[i] == ')' ||
    3317          55 :             os.osLabelText[i] == '\\')
    3318             :         {
    3319           0 :             VSIFPrintfL(m_fp, "\\%c", os.osLabelText[i]);
    3320             :         }
    3321             :         else
    3322             :         {
    3323          55 :             VSIFPrintfL(m_fp, "%c", os.osLabelText[i]);
    3324             :         }
    3325             :     }
    3326           7 :     VSIFPrintfL(m_fp, ") Tj\n");
    3327           7 :     VSIFPrintfL(m_fp, "ET\n");
    3328           7 :     VSIFPrintfL(m_fp, "Q");
    3329             : 
    3330           7 :     EndObjWithStream();
    3331             : 
    3332          14 :     return nObjectId;
    3333             : }
    3334             : 
    3335             : /************************************************************************/
    3336             : /*                          WriteOGRFeature()                           */
    3337             : /************************************************************************/
    3338             : 
    3339          94 : int GDALPDFWriter::WriteOGRFeature(GDALPDFLayerDesc &osVectorDesc,
    3340             :                                    OGRFeatureH hFeat,
    3341             :                                    OGRCoordinateTransformationH hCT,
    3342             :                                    const char *pszOGRDisplayField,
    3343             :                                    const char *pszOGRLinkField,
    3344             :                                    int bWriteOGRAttributes, int &iObj)
    3345             : {
    3346          94 :     GDALDataset *const poClippingDS = oPageContext.poClippingDS;
    3347          94 :     const int nHeight = poClippingDS->GetRasterYSize();
    3348          94 :     const double dfUserUnit = oPageContext.dfDPI * USER_UNIT_IN_INCH;
    3349             :     double adfGeoTransform[6];
    3350          94 :     poClippingDS->GetGeoTransform(adfGeoTransform);
    3351             : 
    3352             :     double adfMatrix[4];
    3353          94 :     adfMatrix[0] = -adfGeoTransform[0] / (adfGeoTransform[1] * dfUserUnit) +
    3354          94 :                    oPageContext.sMargins.nLeft;
    3355          94 :     adfMatrix[1] = 1.0 / (adfGeoTransform[1] * dfUserUnit);
    3356          94 :     adfMatrix[2] = -(adfGeoTransform[3] + adfGeoTransform[5] * nHeight) /
    3357          94 :                        (-adfGeoTransform[5] * dfUserUnit) +
    3358          94 :                    oPageContext.sMargins.nBottom;
    3359          94 :     adfMatrix[3] = 1.0 / (-adfGeoTransform[5] * dfUserUnit);
    3360             : 
    3361          94 :     OGRGeometryH hGeom = OGR_F_GetGeometryRef(hFeat);
    3362          94 :     if (hGeom == nullptr)
    3363             :     {
    3364           0 :         return TRUE;
    3365             :     }
    3366             : 
    3367          94 :     OGREnvelope sEnvelope;
    3368             : 
    3369          94 :     if (hCT != nullptr)
    3370             :     {
    3371             :         /* Reproject */
    3372          12 :         if (OGR_G_Transform(hGeom, hCT) != OGRERR_NONE)
    3373             :         {
    3374           2 :             return TRUE;
    3375             :         }
    3376             : 
    3377          12 :         OGREnvelope sRasterEnvelope;
    3378          12 :         sRasterEnvelope.MinX = adfGeoTransform[0];
    3379          12 :         sRasterEnvelope.MinY =
    3380          24 :             adfGeoTransform[3] +
    3381          12 :             poClippingDS->GetRasterYSize() * adfGeoTransform[5];
    3382          12 :         sRasterEnvelope.MaxX =
    3383          24 :             adfGeoTransform[0] +
    3384          12 :             poClippingDS->GetRasterXSize() * adfGeoTransform[1];
    3385          12 :         sRasterEnvelope.MaxY = adfGeoTransform[3];
    3386             : 
    3387             :         // Check that the reprojected geometry intersects the raster envelope.
    3388          12 :         OGR_G_GetEnvelope(hGeom, &sEnvelope);
    3389          12 :         if (!(sRasterEnvelope.Intersects(sEnvelope)))
    3390             :         {
    3391           2 :             return TRUE;
    3392             :         }
    3393             :     }
    3394             :     else
    3395             :     {
    3396          82 :         OGR_G_GetEnvelope(hGeom, &sEnvelope);
    3397             :     }
    3398             : 
    3399             :     /* -------------------------------------------------------------- */
    3400             :     /*  Get style                                                     */
    3401             :     /* -------------------------------------------------------------- */
    3402         184 :     ObjectStyle os;
    3403          92 :     GetObjectStyle(nullptr, hFeat, adfMatrix, m_oMapSymbolFilenameToDesc, os);
    3404             : 
    3405          92 :     double dfRadius = os.dfSymbolSize * dfUserUnit;
    3406             : 
    3407             :     // For a POINT with only a LABEL style string and non-empty text, we do not
    3408             :     // output any geometry other than the text itself.
    3409             :     const bool bLabelOnly =
    3410          92 :         wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint &&
    3411          92 :         !os.bHasPenBrushOrSymbol && !os.osLabelText.empty();
    3412             : 
    3413             :     /* -------------------------------------------------------------- */
    3414             :     /*  Write object dictionary                                       */
    3415             :     /* -------------------------------------------------------------- */
    3416          92 :     if (!bLabelOnly)
    3417             :     {
    3418          90 :         auto nObjectId = AllocNewObject();
    3419             : 
    3420          90 :         osVectorDesc.aIds.push_back(nObjectId);
    3421             : 
    3422             :         int bboxXMin, bboxYMin, bboxXMax, bboxYMax;
    3423          90 :         ComputeIntBBox(hGeom, sEnvelope, adfMatrix, os, dfRadius, bboxXMin,
    3424             :                        bboxYMin, bboxXMax, bboxYMax);
    3425             : 
    3426             :         auto nLinkId = WriteLink(hFeat, pszOGRLinkField, adfMatrix, bboxXMin,
    3427          90 :                                  bboxYMin, bboxXMax, bboxYMax);
    3428          90 :         if (nLinkId.toBool())
    3429           2 :             oPageContext.anAnnotationsId.push_back(nLinkId);
    3430             : 
    3431         180 :         GDALPDFDictionaryRW oDict;
    3432          90 :         GDALPDFArrayRW *poBBOX = new GDALPDFArrayRW();
    3433          90 :         poBBOX->Add(bboxXMin).Add(bboxYMin).Add(bboxXMax).Add(bboxYMax);
    3434          90 :         oDict.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
    3435          90 :             .Add("BBox", poBBOX)
    3436          90 :             .Add("Subtype", GDALPDFObjectRW::CreateName("Form"));
    3437             : 
    3438          90 :         GDALPDFDictionaryRW *poGS1 = new GDALPDFDictionaryRW();
    3439          90 :         poGS1->Add("Type", GDALPDFObjectRW::CreateName("ExtGState"));
    3440          90 :         if (os.nPenA != 255)
    3441           0 :             poGS1->Add("CA", (os.nPenA == 127 || os.nPenA == 128)
    3442             :                                  ? 0.5
    3443           0 :                                  : os.nPenA / 255.0);
    3444          90 :         if (os.nBrushA != 255)
    3445           0 :             poGS1->Add("ca", (os.nBrushA == 127 || os.nBrushA == 128)
    3446             :                                  ? 0.5
    3447          68 :                                  : os.nBrushA / 255.0);
    3448             : 
    3449          90 :         GDALPDFDictionaryRW *poExtGState = new GDALPDFDictionaryRW();
    3450          90 :         poExtGState->Add("GS1", poGS1);
    3451             : 
    3452          90 :         GDALPDFDictionaryRW *poResources = new GDALPDFDictionaryRW();
    3453          90 :         poResources->Add("ExtGState", poExtGState);
    3454             : 
    3455          90 :         if (os.nImageSymbolId.toBool())
    3456             :         {
    3457           2 :             GDALPDFDictionaryRW *poDictXObject = new GDALPDFDictionaryRW();
    3458           2 :             poResources->Add("XObject", poDictXObject);
    3459             : 
    3460             :             poDictXObject->Add(
    3461             :                 CPLSPrintf("SymImage%d", os.nImageSymbolId.toInt()),
    3462           2 :                 os.nImageSymbolId, 0);
    3463             :         }
    3464             : 
    3465          90 :         oDict.Add("Resources", poResources);
    3466             : 
    3467          90 :         StartObjWithStream(nObjectId, oDict,
    3468          90 :                            oPageContext.eStreamCompressMethod != COMPRESS_NONE);
    3469             : 
    3470             :         /* -------------------------------------------------------------- */
    3471             :         /*  Write object stream                                           */
    3472             :         /* -------------------------------------------------------------- */
    3473          90 :         VSIFPrintfL(m_fp, "q\n");
    3474             : 
    3475          90 :         VSIFPrintfL(m_fp, "/GS1 gs\n");
    3476             : 
    3477          90 :         VSIFPrintfL(
    3478             :             m_fp, "%s",
    3479         180 :             GenerateDrawingStream(hGeom, adfMatrix, os, dfRadius).c_str());
    3480             : 
    3481          90 :         VSIFPrintfL(m_fp, "Q");
    3482             : 
    3483          90 :         EndObjWithStream();
    3484             :     }
    3485             :     else
    3486             :     {
    3487           2 :         osVectorDesc.aIds.push_back(GDALPDFObjectNum());
    3488             :     }
    3489             : 
    3490             :     /* -------------------------------------------------------------- */
    3491             :     /*  Write label                                                   */
    3492             :     /* -------------------------------------------------------------- */
    3493          96 :     if (!os.osLabelText.empty() &&
    3494           4 :         wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint)
    3495             :     {
    3496           4 :         if (!osVectorDesc.nOCGTextId.toBool())
    3497           4 :             osVectorDesc.nOCGTextId = WriteOCG("Text", osVectorDesc.nOCGId);
    3498             : 
    3499           4 :         int nWidth = poClippingDS->GetRasterXSize();
    3500           4 :         double dfWidthInUserUnit = nWidth / dfUserUnit +
    3501           4 :                                    oPageContext.sMargins.nLeft +
    3502           4 :                                    oPageContext.sMargins.nRight;
    3503           4 :         double dfHeightInUserUnit = nHeight / dfUserUnit +
    3504           4 :                                     oPageContext.sMargins.nBottom +
    3505           4 :                                     oPageContext.sMargins.nTop;
    3506             :         auto nObjectId =
    3507             :             WriteLabel(hGeom, adfMatrix, os, oPageContext.eStreamCompressMethod,
    3508           4 :                        0, 0, dfWidthInUserUnit, dfHeightInUserUnit);
    3509             : 
    3510           4 :         osVectorDesc.aIdsText.push_back(nObjectId);
    3511             :     }
    3512             :     else
    3513             :     {
    3514          88 :         osVectorDesc.aIdsText.push_back(GDALPDFObjectNum());
    3515             :     }
    3516             : 
    3517             :     /* -------------------------------------------------------------- */
    3518             :     /*  Write feature attributes                                      */
    3519             :     /* -------------------------------------------------------------- */
    3520          92 :     GDALPDFObjectNum nFeatureUserProperties;
    3521             : 
    3522          92 :     CPLString osFeatureName;
    3523             : 
    3524          92 :     if (bWriteOGRAttributes)
    3525             :     {
    3526             :         nFeatureUserProperties = WriteAttributes(
    3527          77 :             hFeat, osVectorDesc.aosIncludedFields, pszOGRDisplayField, iObj,
    3528          77 :             osVectorDesc.nFeatureLayerId, oPageContext.nPageId, osFeatureName);
    3529             :     }
    3530             : 
    3531          92 :     iObj++;
    3532             : 
    3533          92 :     osVectorDesc.aUserPropertiesIds.push_back(nFeatureUserProperties);
    3534          92 :     osVectorDesc.aFeatureNames.push_back(osFeatureName);
    3535             : 
    3536          92 :     return TRUE;
    3537             : }
    3538             : 
    3539             : /************************************************************************/
    3540             : /*                               EndPage()                              */
    3541             : /************************************************************************/
    3542             : 
    3543         121 : int GDALPDFWriter::EndPage(const char *pszExtraImages,
    3544             :                            const char *pszExtraStream,
    3545             :                            const char *pszExtraLayerName,
    3546             :                            const char *pszOffLayers,
    3547             :                            const char *pszExclusiveLayers)
    3548             : {
    3549         121 :     auto nLayerExtraId = WriteOCG(pszExtraLayerName);
    3550         121 :     if (pszOffLayers)
    3551           2 :         m_osOffLayers = pszOffLayers;
    3552         121 :     if (pszExclusiveLayers)
    3553           2 :         m_osExclusiveLayers = pszExclusiveLayers;
    3554             : 
    3555             :     /* -------------------------------------------------------------- */
    3556             :     /*  Write extra images                                            */
    3557             :     /* -------------------------------------------------------------- */
    3558         242 :     std::vector<GDALPDFImageDesc> asExtraImageDesc;
    3559         121 :     if (pszExtraImages)
    3560             :     {
    3561           2 :         if (GDALGetDriverCount() == 0)
    3562           0 :             GDALAllRegister();
    3563             : 
    3564             :         char **papszExtraImagesTokens =
    3565           2 :             CSLTokenizeString2(pszExtraImages, ",", 0);
    3566           2 :         double dfUserUnit = oPageContext.dfDPI * USER_UNIT_IN_INCH;
    3567           2 :         int nCount = CSLCount(papszExtraImagesTokens);
    3568           6 :         for (int i = 0; i + 4 <= nCount; /* */)
    3569             :         {
    3570           4 :             const char *pszImageFilename = papszExtraImagesTokens[i + 0];
    3571           4 :             double dfX = CPLAtof(papszExtraImagesTokens[i + 1]);
    3572           4 :             double dfY = CPLAtof(papszExtraImagesTokens[i + 2]);
    3573           4 :             double dfScale = CPLAtof(papszExtraImagesTokens[i + 3]);
    3574           4 :             const char *pszLinkVal = nullptr;
    3575           4 :             i += 4;
    3576           4 :             if (i < nCount &&
    3577           2 :                 STARTS_WITH_CI(papszExtraImagesTokens[i], "link="))
    3578             :             {
    3579           2 :                 pszLinkVal = papszExtraImagesTokens[i] + 5;
    3580           2 :                 i++;
    3581             :             }
    3582             :             GDALDataset *poImageDS =
    3583           4 :                 (GDALDataset *)GDALOpen(pszImageFilename, GA_ReadOnly);
    3584           4 :             if (poImageDS)
    3585             :             {
    3586             :                 auto nImageId = WriteBlock(
    3587             :                     poImageDS, 0, 0, poImageDS->GetRasterXSize(),
    3588           0 :                     poImageDS->GetRasterYSize(), GDALPDFObjectNum(),
    3589           4 :                     COMPRESS_DEFAULT, 0, -1, nullptr, nullptr, nullptr);
    3590             : 
    3591           4 :                 if (nImageId.toBool())
    3592             :                 {
    3593           4 :                     GDALPDFImageDesc oImageDesc;
    3594           4 :                     oImageDesc.nImageId = nImageId;
    3595           4 :                     oImageDesc.dfXSize =
    3596           4 :                         poImageDS->GetRasterXSize() / dfUserUnit * dfScale;
    3597           4 :                     oImageDesc.dfYSize =
    3598           4 :                         poImageDS->GetRasterYSize() / dfUserUnit * dfScale;
    3599           4 :                     oImageDesc.dfXOff = dfX;
    3600           4 :                     oImageDesc.dfYOff = dfY;
    3601             : 
    3602           4 :                     asExtraImageDesc.push_back(oImageDesc);
    3603             : 
    3604           4 :                     if (pszLinkVal != nullptr)
    3605             :                     {
    3606           2 :                         auto nAnnotId = AllocNewObject();
    3607           2 :                         oPageContext.anAnnotationsId.push_back(nAnnotId);
    3608           2 :                         StartObj(nAnnotId);
    3609             :                         {
    3610           2 :                             GDALPDFDictionaryRW oDict;
    3611             :                             oDict.Add("Type",
    3612           2 :                                       GDALPDFObjectRW::CreateName("Annot"));
    3613             :                             oDict.Add("Subtype",
    3614           2 :                                       GDALPDFObjectRW::CreateName("Link"));
    3615           2 :                             oDict.Add("Rect", &(new GDALPDFArrayRW())
    3616           2 :                                                    ->Add(oImageDesc.dfXOff)
    3617           2 :                                                    .Add(oImageDesc.dfYOff)
    3618           2 :                                                    .Add(oImageDesc.dfXOff +
    3619           2 :                                                         oImageDesc.dfXSize)
    3620           2 :                                                    .Add(oImageDesc.dfYOff +
    3621           2 :                                                         oImageDesc.dfYSize));
    3622             :                             oDict.Add(
    3623             :                                 "A",
    3624           2 :                                 &(new GDALPDFDictionaryRW())
    3625             :                                      ->Add("S",
    3626           2 :                                            GDALPDFObjectRW::CreateName("URI"))
    3627           2 :                                      .Add("URI", pszLinkVal));
    3628             :                             oDict.Add(
    3629             :                                 "BS",
    3630           2 :                                 &(new GDALPDFDictionaryRW())
    3631           2 :                                      ->Add("Type", GDALPDFObjectRW::CreateName(
    3632           2 :                                                        "Border"))
    3633           2 :                                      .Add("S", GDALPDFObjectRW::CreateName("S"))
    3634           2 :                                      .Add("W", 0));
    3635             :                             oDict.Add(
    3636             :                                 "Border",
    3637           2 :                                 &(new GDALPDFArrayRW())->Add(0).Add(0).Add(0));
    3638           2 :                             oDict.Add("H", GDALPDFObjectRW::CreateName("I"));
    3639             : 
    3640           2 :                             VSIFPrintfL(m_fp, "%s\n",
    3641           4 :                                         oDict.Serialize().c_str());
    3642             :                         }
    3643           2 :                         EndObj();
    3644             :                     }
    3645             :                 }
    3646             : 
    3647           4 :                 GDALClose(poImageDS);
    3648             :             }
    3649             :         }
    3650           2 :         CSLDestroy(papszExtraImagesTokens);
    3651             :     }
    3652             : 
    3653             :     /* -------------------------------------------------------------- */
    3654             :     /*  Write content stream                                          */
    3655             :     /* -------------------------------------------------------------- */
    3656         121 :     GDALPDFDictionaryRW oDictContent;
    3657         121 :     StartObjWithStream(oPageContext.nContentId, oDictContent,
    3658         121 :                        oPageContext.eStreamCompressMethod != COMPRESS_NONE);
    3659             : 
    3660             :     /* -------------------------------------------------------------- */
    3661             :     /*  Write drawing instructions for raster blocks                  */
    3662             :     /* -------------------------------------------------------------- */
    3663         226 :     for (size_t iRaster = 0; iRaster < oPageContext.asRasterDesc.size();
    3664             :          iRaster++)
    3665             :     {
    3666         105 :         const GDALPDFRasterDesc &oDesc = oPageContext.asRasterDesc[iRaster];
    3667         105 :         if (oDesc.nOCGRasterId.toBool())
    3668           6 :             VSIFPrintfL(m_fp, "/OC /Lyr%d BDC\n", oDesc.nOCGRasterId.toInt());
    3669             : 
    3670         304 :         for (size_t iImage = 0; iImage < oDesc.asImageDesc.size(); iImage++)
    3671             :         {
    3672         199 :             VSIFPrintfL(m_fp, "q\n");
    3673             :             GDALPDFObjectRW *poXSize =
    3674         199 :                 GDALPDFObjectRW::CreateReal(oDesc.asImageDesc[iImage].dfXSize);
    3675             :             GDALPDFObjectRW *poYSize =
    3676         199 :                 GDALPDFObjectRW::CreateReal(oDesc.asImageDesc[iImage].dfYSize);
    3677             :             GDALPDFObjectRW *poXOff =
    3678         199 :                 GDALPDFObjectRW::CreateReal(oDesc.asImageDesc[iImage].dfXOff);
    3679             :             GDALPDFObjectRW *poYOff =
    3680         199 :                 GDALPDFObjectRW::CreateReal(oDesc.asImageDesc[iImage].dfYOff);
    3681         796 :             VSIFPrintfL(
    3682         398 :                 m_fp, "%s 0 0 %s %s %s cm\n", poXSize->Serialize().c_str(),
    3683         597 :                 poYSize->Serialize().c_str(), poXOff->Serialize().c_str(),
    3684         398 :                 poYOff->Serialize().c_str());
    3685         199 :             delete poXSize;
    3686         199 :             delete poYSize;
    3687         199 :             delete poXOff;
    3688         199 :             delete poYOff;
    3689         199 :             VSIFPrintfL(m_fp, "/Image%d Do\n",
    3690         199 :                         oDesc.asImageDesc[iImage].nImageId.toInt());
    3691         199 :             VSIFPrintfL(m_fp, "Q\n");
    3692             :         }
    3693             : 
    3694         105 :         if (oDesc.nOCGRasterId.toBool())
    3695           6 :             VSIFPrintfL(m_fp, "EMC\n");
    3696             :     }
    3697             : 
    3698             :     /* -------------------------------------------------------------- */
    3699             :     /*  Write drawing instructions for vector features                */
    3700             :     /* -------------------------------------------------------------- */
    3701         121 :     int iObj = 0;
    3702         158 :     for (size_t iLayer = 0; iLayer < oPageContext.asVectorDesc.size(); iLayer++)
    3703             :     {
    3704          37 :         const GDALPDFLayerDesc &oLayerDesc = oPageContext.asVectorDesc[iLayer];
    3705             : 
    3706          37 :         VSIFPrintfL(m_fp, "/OC /Lyr%d BDC\n", oLayerDesc.nOCGId.toInt());
    3707             : 
    3708         129 :         for (size_t iVector = 0; iVector < oLayerDesc.aIds.size(); iVector++)
    3709             :         {
    3710          92 :             if (oLayerDesc.aIds[iVector].toBool())
    3711             :             {
    3712         180 :                 CPLString osName = oLayerDesc.aFeatureNames[iVector];
    3713          90 :                 if (!osName.empty())
    3714             :                 {
    3715          76 :                     VSIFPrintfL(m_fp, "/feature <</MCID %d>> BDC\n", iObj);
    3716             :                 }
    3717             : 
    3718          90 :                 VSIFPrintfL(m_fp, "/Vector%d Do\n",
    3719          90 :                             oLayerDesc.aIds[iVector].toInt());
    3720             : 
    3721          90 :                 if (!osName.empty())
    3722             :                 {
    3723          76 :                     VSIFPrintfL(m_fp, "EMC\n");
    3724             :                 }
    3725             :             }
    3726             : 
    3727          92 :             iObj++;
    3728             :         }
    3729             : 
    3730          37 :         VSIFPrintfL(m_fp, "EMC\n");
    3731             :     }
    3732             : 
    3733             :     /* -------------------------------------------------------------- */
    3734             :     /*  Write drawing instructions for labels of vector features      */
    3735             :     /* -------------------------------------------------------------- */
    3736         121 :     iObj = 0;
    3737         158 :     for (const GDALPDFLayerDesc &oLayerDesc : oPageContext.asVectorDesc)
    3738             :     {
    3739          37 :         if (oLayerDesc.nOCGTextId.toBool())
    3740             :         {
    3741           4 :             VSIFPrintfL(m_fp, "/OC /Lyr%d BDC\n", oLayerDesc.nOCGId.toInt());
    3742           4 :             VSIFPrintfL(m_fp, "/OC /Lyr%d BDC\n",
    3743             :                         oLayerDesc.nOCGTextId.toInt());
    3744             : 
    3745          40 :             for (size_t iVector = 0; iVector < oLayerDesc.aIdsText.size();
    3746             :                  iVector++)
    3747             :             {
    3748          36 :                 if (oLayerDesc.aIdsText[iVector].toBool())
    3749             :                 {
    3750           8 :                     CPLString osName = oLayerDesc.aFeatureNames[iVector];
    3751           4 :                     if (!osName.empty())
    3752             :                     {
    3753           3 :                         VSIFPrintfL(m_fp, "/feature <</MCID %d>> BDC\n", iObj);
    3754             :                     }
    3755             : 
    3756           4 :                     VSIFPrintfL(m_fp, "/Text%d Do\n",
    3757           4 :                                 oLayerDesc.aIdsText[iVector].toInt());
    3758             : 
    3759           4 :                     if (!osName.empty())
    3760             :                     {
    3761           3 :                         VSIFPrintfL(m_fp, "EMC\n");
    3762             :                     }
    3763             :                 }
    3764             : 
    3765          36 :                 iObj++;
    3766             :             }
    3767             : 
    3768           4 :             VSIFPrintfL(m_fp, "EMC\n");
    3769           4 :             VSIFPrintfL(m_fp, "EMC\n");
    3770             :         }
    3771             :         else
    3772          33 :             iObj += (int)oLayerDesc.aIds.size();
    3773             :     }
    3774             : 
    3775             :     /* -------------------------------------------------------------- */
    3776             :     /*  Write drawing instructions for extra content.                 */
    3777             :     /* -------------------------------------------------------------- */
    3778         121 :     if (pszExtraStream || !asExtraImageDesc.empty())
    3779             :     {
    3780           2 :         if (nLayerExtraId.toBool())
    3781           2 :             VSIFPrintfL(m_fp, "/OC /Lyr%d BDC\n", nLayerExtraId.toInt());
    3782             : 
    3783             :         /* -------------------------------------------------------------- */
    3784             :         /*  Write drawing instructions for extra images.                  */
    3785             :         /* -------------------------------------------------------------- */
    3786           6 :         for (size_t iImage = 0; iImage < asExtraImageDesc.size(); iImage++)
    3787             :         {
    3788           4 :             VSIFPrintfL(m_fp, "q\n");
    3789             :             GDALPDFObjectRW *poXSize =
    3790           4 :                 GDALPDFObjectRW::CreateReal(asExtraImageDesc[iImage].dfXSize);
    3791             :             GDALPDFObjectRW *poYSize =
    3792           4 :                 GDALPDFObjectRW::CreateReal(asExtraImageDesc[iImage].dfYSize);
    3793             :             GDALPDFObjectRW *poXOff =
    3794           4 :                 GDALPDFObjectRW::CreateReal(asExtraImageDesc[iImage].dfXOff);
    3795             :             GDALPDFObjectRW *poYOff =
    3796           4 :                 GDALPDFObjectRW::CreateReal(asExtraImageDesc[iImage].dfYOff);
    3797          16 :             VSIFPrintfL(
    3798           8 :                 m_fp, "%s 0 0 %s %s %s cm\n", poXSize->Serialize().c_str(),
    3799          12 :                 poYSize->Serialize().c_str(), poXOff->Serialize().c_str(),
    3800           8 :                 poYOff->Serialize().c_str());
    3801           4 :             delete poXSize;
    3802           4 :             delete poYSize;
    3803           4 :             delete poXOff;
    3804           4 :             delete poYOff;
    3805           4 :             VSIFPrintfL(m_fp, "/Image%d Do\n",
    3806           4 :                         asExtraImageDesc[iImage].nImageId.toInt());
    3807           4 :             VSIFPrintfL(m_fp, "Q\n");
    3808             :         }
    3809             : 
    3810           2 :         if (pszExtraStream)
    3811           2 :             VSIFPrintfL(m_fp, "%s\n", pszExtraStream);
    3812             : 
    3813           2 :         if (nLayerExtraId.toBool())
    3814           2 :             VSIFPrintfL(m_fp, "EMC\n");
    3815             :     }
    3816             : 
    3817         121 :     EndObjWithStream();
    3818             : 
    3819             :     /* -------------------------------------------------------------- */
    3820             :     /*  Write objects for feature tree.                               */
    3821             :     /* -------------------------------------------------------------- */
    3822         121 :     if (m_nStructTreeRootId.toBool())
    3823             :     {
    3824          21 :         auto nParentTreeId = AllocNewObject();
    3825          21 :         StartObj(nParentTreeId);
    3826          21 :         VSIFPrintfL(m_fp, "<< /Nums [ 0 ");
    3827          21 :         VSIFPrintfL(m_fp, "[ ");
    3828          57 :         for (size_t iLayer = 0; iLayer < oPageContext.asVectorDesc.size();
    3829             :              iLayer++)
    3830             :         {
    3831             :             const GDALPDFLayerDesc &oLayerDesc =
    3832          36 :                 oPageContext.asVectorDesc[iLayer];
    3833         113 :             for (size_t iVector = 0; iVector < oLayerDesc.aIds.size();
    3834             :                  iVector++)
    3835             :             {
    3836          77 :                 const auto &nId = oLayerDesc.aUserPropertiesIds[iVector];
    3837          77 :                 if (nId.toBool())
    3838          77 :                     VSIFPrintfL(m_fp, "%d 0 R ", nId.toInt());
    3839             :             }
    3840             :         }
    3841          21 :         VSIFPrintfL(m_fp, " ]\n");
    3842          21 :         VSIFPrintfL(m_fp, " ] >> \n");
    3843          21 :         EndObj();
    3844             : 
    3845          21 :         StartObj(m_nStructTreeRootId);
    3846          21 :         VSIFPrintfL(m_fp,
    3847             :                     "<< "
    3848             :                     "/Type /StructTreeRoot "
    3849             :                     "/ParentTree %d 0 R "
    3850             :                     "/K [ ",
    3851             :                     nParentTreeId.toInt());
    3852          57 :         for (size_t iLayer = 0; iLayer < oPageContext.asVectorDesc.size();
    3853             :              iLayer++)
    3854             :         {
    3855          36 :             VSIFPrintfL(
    3856             :                 m_fp, "%d 0 R ",
    3857          36 :                 oPageContext.asVectorDesc[iLayer].nFeatureLayerId.toInt());
    3858             :         }
    3859          21 :         VSIFPrintfL(m_fp, "] >>\n");
    3860          21 :         EndObj();
    3861             :     }
    3862             : 
    3863             :     /* -------------------------------------------------------------- */
    3864             :     /*  Write page resource dictionary.                               */
    3865             :     /* -------------------------------------------------------------- */
    3866         121 :     StartObj(oPageContext.nResourcesId);
    3867             :     {
    3868         121 :         GDALPDFDictionaryRW oDict;
    3869         121 :         GDALPDFDictionaryRW *poDictXObject = new GDALPDFDictionaryRW();
    3870         121 :         oDict.Add("XObject", poDictXObject);
    3871             :         size_t iImage;
    3872         226 :         for (const GDALPDFRasterDesc &oDesc : oPageContext.asRasterDesc)
    3873             :         {
    3874         304 :             for (iImage = 0; iImage < oDesc.asImageDesc.size(); iImage++)
    3875             :             {
    3876             :                 poDictXObject->Add(
    3877             :                     CPLSPrintf("Image%d",
    3878         199 :                                oDesc.asImageDesc[iImage].nImageId.toInt()),
    3879         199 :                     oDesc.asImageDesc[iImage].nImageId, 0);
    3880             :             }
    3881             :         }
    3882         125 :         for (iImage = 0; iImage < asExtraImageDesc.size(); iImage++)
    3883             :         {
    3884             :             poDictXObject->Add(
    3885             :                 CPLSPrintf("Image%d",
    3886           4 :                            asExtraImageDesc[iImage].nImageId.toInt()),
    3887           4 :                 asExtraImageDesc[iImage].nImageId, 0);
    3888             :         }
    3889         158 :         for (const GDALPDFLayerDesc &oLayerDesc : oPageContext.asVectorDesc)
    3890             :         {
    3891         129 :             for (size_t iVector = 0; iVector < oLayerDesc.aIds.size();
    3892             :                  iVector++)
    3893             :             {
    3894          92 :                 if (oLayerDesc.aIds[iVector].toBool())
    3895             :                     poDictXObject->Add(
    3896             :                         CPLSPrintf("Vector%d",
    3897          90 :                                    oLayerDesc.aIds[iVector].toInt()),
    3898         180 :                         oLayerDesc.aIds[iVector], 0);
    3899             :             }
    3900         129 :             for (size_t iVector = 0; iVector < oLayerDesc.aIdsText.size();
    3901             :                  iVector++)
    3902             :             {
    3903          92 :                 if (oLayerDesc.aIdsText[iVector].toBool())
    3904             :                     poDictXObject->Add(
    3905             :                         CPLSPrintf("Text%d",
    3906           4 :                                    oLayerDesc.aIdsText[iVector].toInt()),
    3907           8 :                         oLayerDesc.aIdsText[iVector], 0);
    3908             :             }
    3909             :         }
    3910             : 
    3911         121 :         if (pszExtraStream)
    3912             :         {
    3913           4 :             std::vector<CPLString> aosNeededFonts;
    3914           2 :             if (strstr(pszExtraStream, "/FTimes"))
    3915             :             {
    3916           2 :                 aosNeededFonts.push_back("Times-Roman");
    3917           2 :                 aosNeededFonts.push_back("Times-Bold");
    3918           2 :                 aosNeededFonts.push_back("Times-Italic");
    3919           2 :                 aosNeededFonts.push_back("Times-BoldItalic");
    3920             :             }
    3921           2 :             if (strstr(pszExtraStream, "/FHelvetica"))
    3922             :             {
    3923           0 :                 aosNeededFonts.push_back("Helvetica");
    3924           0 :                 aosNeededFonts.push_back("Helvetica-Bold");
    3925           0 :                 aosNeededFonts.push_back("Helvetica-Oblique");
    3926           0 :                 aosNeededFonts.push_back("Helvetica-BoldOblique");
    3927             :             }
    3928           2 :             if (strstr(pszExtraStream, "/FCourier"))
    3929             :             {
    3930           0 :                 aosNeededFonts.push_back("Courier");
    3931           0 :                 aosNeededFonts.push_back("Courier-Bold");
    3932           0 :                 aosNeededFonts.push_back("Courier-Oblique");
    3933           0 :                 aosNeededFonts.push_back("Courier-BoldOblique");
    3934             :             }
    3935           2 :             if (strstr(pszExtraStream, "/FSymbol"))
    3936           0 :                 aosNeededFonts.push_back("Symbol");
    3937           2 :             if (strstr(pszExtraStream, "/FZapfDingbats"))
    3938           0 :                 aosNeededFonts.push_back("ZapfDingbats");
    3939             : 
    3940           2 :             if (!aosNeededFonts.empty())
    3941             :             {
    3942           2 :                 GDALPDFDictionaryRW *poDictFont = new GDALPDFDictionaryRW();
    3943             : 
    3944          10 :                 for (CPLString &osFont : aosNeededFonts)
    3945             :                 {
    3946             :                     GDALPDFDictionaryRW *poDictFontInner =
    3947           8 :                         new GDALPDFDictionaryRW();
    3948             :                     poDictFontInner->Add("Type",
    3949           8 :                                          GDALPDFObjectRW::CreateName("Font"));
    3950             :                     poDictFontInner->Add("BaseFont",
    3951           8 :                                          GDALPDFObjectRW::CreateName(osFont));
    3952             :                     poDictFontInner->Add(
    3953             :                         "Encoding",
    3954           8 :                         GDALPDFObjectRW::CreateName("WinAnsiEncoding"));
    3955             :                     poDictFontInner->Add("Subtype",
    3956           8 :                                          GDALPDFObjectRW::CreateName("Type1"));
    3957             : 
    3958           8 :                     osFont = "F" + osFont;
    3959           8 :                     const size_t nHyphenPos = osFont.find('-');
    3960           8 :                     if (nHyphenPos != std::string::npos)
    3961           8 :                         osFont.erase(nHyphenPos, 1);
    3962           8 :                     poDictFont->Add(osFont, poDictFontInner);
    3963             :                 }
    3964             : 
    3965           2 :                 oDict.Add("Font", poDictFont);
    3966             :             }
    3967             :         }
    3968             : 
    3969         121 :         if (!m_asOCGs.empty())
    3970             :         {
    3971          26 :             GDALPDFDictionaryRW *poDictProperties = new GDALPDFDictionaryRW();
    3972             : #ifdef HACK_TO_GENERATE_OCMD
    3973             :             GDALPDFDictionaryRW *poOCMD = new GDALPDFDictionaryRW();
    3974             :             poOCMD->Add("Type", GDALPDFObjectRW::CreateName("OCMD"));
    3975             :             GDALPDFArrayRW *poArray = new GDALPDFArrayRW();
    3976             :             poArray->Add(m_asOCGs[0].nId, 0);
    3977             :             poArray->Add(m_asOCGs[1].nId, 0);
    3978             :             poOCMD->Add("OCGs", poArray);
    3979             :             poDictProperties->Add(CPLSPrintf("Lyr%d", m_asOCGs[1].nId.toInt()),
    3980             :                                   poOCMD);
    3981             : #else
    3982          75 :             for (size_t i = 0; i < m_asOCGs.size(); i++)
    3983             :                 poDictProperties->Add(
    3984          49 :                     CPLSPrintf("Lyr%d", m_asOCGs[i].nId.toInt()),
    3985          49 :                     m_asOCGs[i].nId, 0);
    3986             : #endif
    3987          26 :             oDict.Add("Properties", poDictProperties);
    3988             :         }
    3989             : 
    3990         121 :         VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
    3991             :     }
    3992         121 :     EndObj();
    3993             : 
    3994             :     /* -------------------------------------------------------------- */
    3995             :     /*  Write annotation arrays.                                      */
    3996             :     /* -------------------------------------------------------------- */
    3997         121 :     StartObj(oPageContext.nAnnotsId);
    3998             :     {
    3999         121 :         GDALPDFArrayRW oArray;
    4000         125 :         for (size_t i = 0; i < oPageContext.anAnnotationsId.size(); i++)
    4001             :         {
    4002           4 :             oArray.Add(oPageContext.anAnnotationsId[i], 0);
    4003             :         }
    4004         121 :         VSIFPrintfL(m_fp, "%s\n", oArray.Serialize().c_str());
    4005             :     }
    4006         121 :     EndObj();
    4007             : 
    4008         242 :     return TRUE;
    4009             : }
    4010             : 
    4011             : /************************************************************************/
    4012             : /*                             WriteMask()                              */
    4013             : /************************************************************************/
    4014             : 
    4015          65 : GDALPDFObjectNum GDALPDFBaseWriter::WriteMask(GDALDataset *poSrcDS, int nXOff,
    4016             :                                               int nYOff, int nReqXSize,
    4017             :                                               int nReqYSize,
    4018             :                                               PDFCompressMethod eCompressMethod)
    4019             : {
    4020          65 :     int nMaskSize = nReqXSize * nReqYSize;
    4021          65 :     GByte *pabyMask = (GByte *)VSIMalloc(nMaskSize);
    4022          65 :     if (pabyMask == nullptr)
    4023           0 :         return GDALPDFObjectNum();
    4024             : 
    4025             :     CPLErr eErr;
    4026          65 :     eErr = poSrcDS->GetRasterBand(4)->RasterIO(
    4027             :         GF_Read, nXOff, nYOff, nReqXSize, nReqYSize, pabyMask, nReqXSize,
    4028             :         nReqYSize, GDT_Byte, 0, 0, nullptr);
    4029          65 :     if (eErr != CE_None)
    4030             :     {
    4031           0 :         VSIFree(pabyMask);
    4032           0 :         return GDALPDFObjectNum();
    4033             :     }
    4034             : 
    4035          65 :     int bOnly0or255 = TRUE;
    4036          65 :     int bOnly255 = TRUE;
    4037             :     /* int bOnly0 = TRUE; */
    4038             :     int i;
    4039      171198 :     for (i = 0; i < nReqXSize * nReqYSize; i++)
    4040             :     {
    4041      171180 :         if (pabyMask[i] == 0)
    4042      166635 :             bOnly255 = FALSE;
    4043        4545 :         else if (pabyMask[i] == 255)
    4044             :         {
    4045             :             /* bOnly0 = FALSE; */
    4046             :         }
    4047             :         else
    4048             :         {
    4049             :             /* bOnly0 = FALSE; */
    4050          47 :             bOnly255 = FALSE;
    4051          47 :             bOnly0or255 = FALSE;
    4052          47 :             break;
    4053             :         }
    4054             :     }
    4055             : 
    4056          65 :     if (bOnly255)
    4057             :     {
    4058           1 :         CPLFree(pabyMask);
    4059           1 :         return GDALPDFObjectNum();
    4060             :     }
    4061             : 
    4062          64 :     if (bOnly0or255)
    4063             :     {
    4064             :         /* Translate to 1 bit */
    4065          17 :         int nReqXSize1 = (nReqXSize + 7) / 8;
    4066          17 :         GByte *pabyMask1 = (GByte *)VSICalloc(nReqXSize1, nReqYSize);
    4067          17 :         if (pabyMask1 == nullptr)
    4068             :         {
    4069           0 :             CPLFree(pabyMask);
    4070           0 :             return GDALPDFObjectNum();
    4071             :         }
    4072         499 :         for (int y = 0; y < nReqYSize; y++)
    4073             :         {
    4074        6686 :             for (int x = 0; x < nReqXSize; x++)
    4075             :             {
    4076        6204 :                 if (pabyMask[y * nReqXSize + x])
    4077        1792 :                     pabyMask1[y * nReqXSize1 + x / 8] |= 1 << (7 - (x % 8));
    4078             :             }
    4079             :         }
    4080          17 :         VSIFree(pabyMask);
    4081          17 :         pabyMask = pabyMask1;
    4082          17 :         nMaskSize = nReqXSize1 * nReqYSize;
    4083             :     }
    4084             : 
    4085          64 :     auto nMaskId = AllocNewObject();
    4086             : 
    4087          64 :     GDALPDFDictionaryRW oDict;
    4088          64 :     oDict.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
    4089          64 :         .Add("Subtype", GDALPDFObjectRW::CreateName("Image"))
    4090          64 :         .Add("Width", nReqXSize)
    4091          64 :         .Add("Height", nReqYSize)
    4092          64 :         .Add("ColorSpace", GDALPDFObjectRW::CreateName("DeviceGray"))
    4093          64 :         .Add("BitsPerComponent", (bOnly0or255) ? 1 : 8);
    4094             : 
    4095          64 :     StartObjWithStream(nMaskId, oDict, eCompressMethod != COMPRESS_NONE);
    4096             : 
    4097          64 :     VSIFWriteL(pabyMask, nMaskSize, 1, m_fp);
    4098          64 :     CPLFree(pabyMask);
    4099             : 
    4100          64 :     EndObjWithStream();
    4101             : 
    4102          64 :     return nMaskId;
    4103             : }
    4104             : 
    4105             : /************************************************************************/
    4106             : /*                             WriteBlock()                             */
    4107             : /************************************************************************/
    4108             : 
    4109         218 : GDALPDFObjectNum GDALPDFBaseWriter::WriteBlock(
    4110             :     GDALDataset *poSrcDS, int nXOff, int nYOff, int nReqXSize, int nReqYSize,
    4111             :     const GDALPDFObjectNum &nColorTableIdIn, PDFCompressMethod eCompressMethod,
    4112             :     int nPredictor, int nJPEGQuality, const char *pszJPEG2000_DRIVER,
    4113             :     GDALProgressFunc pfnProgress, void *pProgressData)
    4114             : {
    4115         218 :     int nBands = poSrcDS->GetRasterCount();
    4116         218 :     if (nBands == 0)
    4117           0 :         return GDALPDFObjectNum();
    4118             : 
    4119         218 :     GDALPDFObjectNum nColorTableId(nColorTableIdIn);
    4120         218 :     if (!nColorTableId.toBool())
    4121         216 :         nColorTableId = WriteColorTable(poSrcDS);
    4122             : 
    4123         218 :     CPLErr eErr = CE_None;
    4124         218 :     GDALDataset *poBlockSrcDS = nullptr;
    4125         218 :     std::unique_ptr<MEMDataset> poMEMDS;
    4126         218 :     GByte *pabyMEMDSBuffer = nullptr;
    4127             : 
    4128         218 :     if (eCompressMethod == COMPRESS_DEFAULT)
    4129             :     {
    4130         184 :         GDALDataset *poSrcDSToTest = poSrcDS;
    4131             : 
    4132             :         /* Test if we can directly copy original JPEG content */
    4133             :         /* if available */
    4134         368 :         if (poSrcDS->GetDriver() != nullptr &&
    4135         184 :             poSrcDS->GetDriver() == GDALGetDriverByName("VRT"))
    4136             :         {
    4137          12 :             VRTDataset *poVRTDS = (VRTDataset *)poSrcDS;
    4138          12 :             poSrcDSToTest = poVRTDS->GetSingleSimpleSource();
    4139             :         }
    4140             : 
    4141         182 :         if (poSrcDSToTest != nullptr && poSrcDSToTest->GetDriver() != nullptr &&
    4142         182 :             EQUAL(poSrcDSToTest->GetDriver()->GetDescription(), "JPEG") &&
    4143           4 :             nXOff == 0 && nYOff == 0 &&
    4144           4 :             nReqXSize == poSrcDSToTest->GetRasterXSize() &&
    4145         366 :             nReqYSize == poSrcDSToTest->GetRasterYSize() && nJPEGQuality < 0)
    4146             :         {
    4147           4 :             VSILFILE *fpSrc = VSIFOpenL(poSrcDSToTest->GetDescription(), "rb");
    4148           4 :             if (fpSrc != nullptr)
    4149             :             {
    4150           4 :                 CPLDebug("PDF", "Copying directly original JPEG file");
    4151             : 
    4152           4 :                 VSIFSeekL(fpSrc, 0, SEEK_END);
    4153           4 :                 int nLength = (int)VSIFTellL(fpSrc);
    4154           4 :                 VSIFSeekL(fpSrc, 0, SEEK_SET);
    4155             : 
    4156           4 :                 auto nImageId = AllocNewObject();
    4157             : 
    4158           4 :                 StartObj(nImageId);
    4159             : 
    4160           8 :                 GDALPDFDictionaryRW oDict;
    4161           4 :                 oDict.Add("Length", nLength)
    4162           4 :                     .Add("Type", GDALPDFObjectRW::CreateName("XObject"))
    4163           4 :                     .Add("Filter", GDALPDFObjectRW::CreateName("DCTDecode"))
    4164           4 :                     .Add("Subtype", GDALPDFObjectRW::CreateName("Image"))
    4165           4 :                     .Add("Width", nReqXSize)
    4166           4 :                     .Add("Height", nReqYSize)
    4167             :                     .Add("ColorSpace",
    4168             :                          (nBands == 1)
    4169           4 :                              ? GDALPDFObjectRW::CreateName("DeviceGray")
    4170           8 :                              : GDALPDFObjectRW::CreateName("DeviceRGB"))
    4171           4 :                     .Add("BitsPerComponent", 8);
    4172           4 :                 VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
    4173           4 :                 VSIFPrintfL(m_fp, "stream\n");
    4174             : 
    4175             :                 GByte abyBuffer[1024];
    4176          24 :                 for (int i = 0; i < nLength; i += 1024)
    4177             :                 {
    4178          20 :                     int nRead = (int)VSIFReadL(abyBuffer, 1, 1024, fpSrc);
    4179          20 :                     if ((int)VSIFWriteL(abyBuffer, 1, nRead, m_fp) != nRead)
    4180             :                     {
    4181           0 :                         eErr = CE_Failure;
    4182           0 :                         break;
    4183             :                     }
    4184             : 
    4185          40 :                     if (eErr == CE_None && pfnProgress != nullptr &&
    4186          20 :                         !pfnProgress((i + nRead) / (double)nLength, nullptr,
    4187             :                                      pProgressData))
    4188             :                     {
    4189           0 :                         CPLError(CE_Failure, CPLE_UserInterrupt,
    4190             :                                  "User terminated CreateCopy()");
    4191           0 :                         eErr = CE_Failure;
    4192           0 :                         break;
    4193             :                     }
    4194             :                 }
    4195             : 
    4196           4 :                 VSIFPrintfL(m_fp, "\nendstream\n");
    4197             : 
    4198           4 :                 EndObj();
    4199             : 
    4200           4 :                 VSIFCloseL(fpSrc);
    4201             : 
    4202           4 :                 return eErr == CE_None ? nImageId : GDALPDFObjectNum();
    4203             :             }
    4204             :         }
    4205             : 
    4206         180 :         eCompressMethod = COMPRESS_DEFLATE;
    4207             :     }
    4208             : 
    4209         214 :     GDALPDFObjectNum nMaskId;
    4210         214 :     if (nBands == 4)
    4211             :     {
    4212             :         nMaskId = WriteMask(poSrcDS, nXOff, nYOff, nReqXSize, nReqYSize,
    4213          65 :                             eCompressMethod);
    4214             :     }
    4215             : 
    4216         214 :     if (nReqXSize == poSrcDS->GetRasterXSize() &&
    4217         214 :         nReqYSize == poSrcDS->GetRasterYSize() && nBands != 4)
    4218             :     {
    4219          98 :         poBlockSrcDS = poSrcDS;
    4220             :     }
    4221             :     else
    4222             :     {
    4223         116 :         if (nBands == 4)
    4224          65 :             nBands = 3;
    4225             : 
    4226         116 :         poMEMDS.reset(
    4227             :             MEMDataset::Create("", nReqXSize, nReqYSize, 0, GDT_Byte, nullptr));
    4228             : 
    4229         116 :         pabyMEMDSBuffer = (GByte *)VSIMalloc3(nReqXSize, nReqYSize, nBands);
    4230         116 :         if (pabyMEMDSBuffer == nullptr)
    4231             :         {
    4232           0 :             return GDALPDFObjectNum();
    4233             :         }
    4234             : 
    4235         116 :         eErr = poSrcDS->RasterIO(GF_Read, nXOff, nYOff, nReqXSize, nReqYSize,
    4236             :                                  pabyMEMDSBuffer, nReqXSize, nReqYSize,
    4237             :                                  GDT_Byte, nBands, nullptr, 0, 0, 0, nullptr);
    4238             : 
    4239         116 :         if (eErr != CE_None)
    4240             :         {
    4241           0 :             CPLFree(pabyMEMDSBuffer);
    4242           0 :             return GDALPDFObjectNum();
    4243             :         }
    4244             : 
    4245             :         int iBand;
    4246         362 :         for (iBand = 0; iBand < nBands; iBand++)
    4247             :         {
    4248         246 :             auto hBand = MEMCreateRasterBandEx(
    4249         246 :                 poMEMDS.get(), iBand + 1,
    4250         246 :                 pabyMEMDSBuffer + iBand * nReqXSize * nReqYSize, GDT_Byte, 0, 0,
    4251             :                 false);
    4252         246 :             poMEMDS->AddMEMBand(hBand);
    4253             :         }
    4254             : 
    4255         116 :         poBlockSrcDS = poMEMDS.get();
    4256             :     }
    4257             : 
    4258         214 :     auto nImageId = AllocNewObject();
    4259             : 
    4260         214 :     GDALPDFObjectNum nMeasureId;
    4261         214 :     if (CPLTestBool(
    4262           0 :             CPLGetConfigOption("GDAL_PDF_WRITE_GEOREF_ON_IMAGE", "FALSE")) &&
    4263         214 :         nReqXSize == poSrcDS->GetRasterXSize() &&
    4264           0 :         nReqYSize == poSrcDS->GetRasterYSize())
    4265             :     {
    4266           0 :         PDFMargins sMargins;
    4267           0 :         nMeasureId = WriteSRS_ISO32000(poSrcDS, 1, nullptr, &sMargins, FALSE);
    4268             :     }
    4269             : 
    4270         428 :     GDALPDFDictionaryRW oDict;
    4271         214 :     oDict.Add("Type", GDALPDFObjectRW::CreateName("XObject"));
    4272             : 
    4273         214 :     if (eCompressMethod == COMPRESS_DEFLATE)
    4274             :     {
    4275         202 :         if (nPredictor == 2)
    4276           4 :             oDict.Add("DecodeParms", &((new GDALPDFDictionaryRW())
    4277           4 :                                            ->Add("Predictor", 2)
    4278           4 :                                            .Add("Colors", nBands)
    4279           4 :                                            .Add("Columns", nReqXSize)));
    4280             :     }
    4281          12 :     else if (eCompressMethod == COMPRESS_JPEG)
    4282             :     {
    4283           6 :         oDict.Add("Filter", GDALPDFObjectRW::CreateName("DCTDecode"));
    4284             :     }
    4285           6 :     else if (eCompressMethod == COMPRESS_JPEG2000)
    4286             :     {
    4287           4 :         oDict.Add("Filter", GDALPDFObjectRW::CreateName("JPXDecode"));
    4288             :     }
    4289             : 
    4290         214 :     oDict.Add("Subtype", GDALPDFObjectRW::CreateName("Image"))
    4291         214 :         .Add("Width", nReqXSize)
    4292         214 :         .Add("Height", nReqYSize)
    4293             :         .Add("ColorSpace",
    4294         214 :              (nColorTableId.toBool())
    4295           2 :                  ? GDALPDFObjectRW::CreateIndirect(nColorTableId, 0)
    4296         137 :              : (nBands == 1) ? GDALPDFObjectRW::CreateName("DeviceGray")
    4297         353 :                              : GDALPDFObjectRW::CreateName("DeviceRGB"))
    4298         214 :         .Add("BitsPerComponent", 8);
    4299         214 :     if (nMaskId.toBool())
    4300             :     {
    4301          64 :         oDict.Add("SMask", nMaskId, 0);
    4302             :     }
    4303         214 :     if (nMeasureId.toBool())
    4304             :     {
    4305           0 :         oDict.Add("Measure", nMeasureId, 0);
    4306             :     }
    4307             : 
    4308         214 :     StartObjWithStream(nImageId, oDict, eCompressMethod == COMPRESS_DEFLATE);
    4309             : 
    4310         214 :     if (eCompressMethod == COMPRESS_JPEG ||
    4311             :         eCompressMethod == COMPRESS_JPEG2000)
    4312             :     {
    4313          10 :         GDALDriver *poJPEGDriver = nullptr;
    4314             :         char szTmp[64];
    4315          10 :         char **papszOptions = nullptr;
    4316             : 
    4317          10 :         bool bEcwEncodeKeyRequiredButNotFound = false;
    4318          10 :         if (eCompressMethod == COMPRESS_JPEG)
    4319             :         {
    4320           6 :             poJPEGDriver = (GDALDriver *)GDALGetDriverByName("JPEG");
    4321           6 :             if (poJPEGDriver != nullptr && nJPEGQuality > 0)
    4322           0 :                 papszOptions = CSLAddString(
    4323             :                     papszOptions, CPLSPrintf("QUALITY=%d", nJPEGQuality));
    4324           6 :             snprintf(szTmp, sizeof(szTmp), "/vsimem/pdftemp/%p.jpg", this);
    4325             :         }
    4326             :         else
    4327             :         {
    4328           4 :             if (pszJPEG2000_DRIVER == nullptr ||
    4329           3 :                 EQUAL(pszJPEG2000_DRIVER, "JP2KAK"))
    4330           1 :                 poJPEGDriver = (GDALDriver *)GDALGetDriverByName("JP2KAK");
    4331           4 :             if (poJPEGDriver == nullptr)
    4332             :             {
    4333           4 :                 if (pszJPEG2000_DRIVER == nullptr ||
    4334           3 :                     EQUAL(pszJPEG2000_DRIVER, "JP2ECW"))
    4335             :                 {
    4336           3 :                     poJPEGDriver = (GDALDriver *)GDALGetDriverByName("JP2ECW");
    4337           6 :                     if (poJPEGDriver &&
    4338           3 :                         poJPEGDriver->GetMetadataItem(
    4339           3 :                             GDAL_DMD_CREATIONDATATYPES) == nullptr)
    4340             :                     {
    4341           0 :                         poJPEGDriver = nullptr;
    4342             :                     }
    4343           3 :                     else if (poJPEGDriver)
    4344             :                     {
    4345           6 :                         if (strstr(poJPEGDriver->GetMetadataItem(
    4346           3 :                                        GDAL_DMD_CREATIONOPTIONLIST),
    4347           3 :                                    "ECW_ENCODE_KEY"))
    4348             :                         {
    4349           0 :                             if (!CPLGetConfigOption("ECW_ENCODE_KEY", nullptr))
    4350             :                             {
    4351           0 :                                 bEcwEncodeKeyRequiredButNotFound = true;
    4352           0 :                                 poJPEGDriver = nullptr;
    4353             :                             }
    4354             :                         }
    4355             :                     }
    4356             :                 }
    4357           4 :                 if (poJPEGDriver)
    4358             :                 {
    4359           3 :                     papszOptions = CSLAddString(papszOptions, "PROFILE=NPJE");
    4360           3 :                     papszOptions = CSLAddString(papszOptions, "LAYERS=1");
    4361           3 :                     papszOptions = CSLAddString(papszOptions, "GeoJP2=OFF");
    4362           3 :                     papszOptions = CSLAddString(papszOptions, "GMLJP2=OFF");
    4363             :                 }
    4364             :             }
    4365           4 :             if (poJPEGDriver == nullptr)
    4366             :             {
    4367           1 :                 if (pszJPEG2000_DRIVER == nullptr ||
    4368           1 :                     EQUAL(pszJPEG2000_DRIVER, "JP2OpenJPEG"))
    4369             :                     poJPEGDriver =
    4370           1 :                         (GDALDriver *)GDALGetDriverByName("JP2OpenJPEG");
    4371           1 :                 if (poJPEGDriver)
    4372             :                 {
    4373           1 :                     papszOptions = CSLAddString(papszOptions, "GeoJP2=OFF");
    4374           1 :                     papszOptions = CSLAddString(papszOptions, "GMLJP2=OFF");
    4375             :                 }
    4376             :             }
    4377           4 :             snprintf(szTmp, sizeof(szTmp), "/vsimem/pdftemp/%p.jp2", this);
    4378             :         }
    4379             : 
    4380          10 :         if (poJPEGDriver == nullptr)
    4381             :         {
    4382           0 :             if (bEcwEncodeKeyRequiredButNotFound)
    4383             :             {
    4384           0 :                 CPLError(CE_Failure, CPLE_NotSupported,
    4385             :                          "No JPEG2000 driver usable (JP2ECW detected but "
    4386             :                          "ECW_ENCODE_KEY configuration option not set");
    4387             :             }
    4388             :             else
    4389             :             {
    4390           0 :                 CPLError(CE_Failure, CPLE_NotSupported, "No %s driver found",
    4391             :                          (eCompressMethod == COMPRESS_JPEG) ? "JPEG"
    4392             :                                                             : "JPEG2000");
    4393             :             }
    4394           0 :             eErr = CE_Failure;
    4395           0 :             goto end;
    4396             :         }
    4397             : 
    4398             :         GDALDataset *poJPEGDS =
    4399          10 :             poJPEGDriver->CreateCopy(szTmp, poBlockSrcDS, FALSE, papszOptions,
    4400             :                                      pfnProgress, pProgressData);
    4401             : 
    4402          10 :         CSLDestroy(papszOptions);
    4403          10 :         if (poJPEGDS == nullptr)
    4404             :         {
    4405           0 :             eErr = CE_Failure;
    4406           0 :             goto end;
    4407             :         }
    4408             : 
    4409          10 :         GDALClose(poJPEGDS);
    4410             : 
    4411          10 :         vsi_l_offset nJPEGDataSize = 0;
    4412          10 :         GByte *pabyJPEGData = VSIGetMemFileBuffer(szTmp, &nJPEGDataSize, TRUE);
    4413          10 :         VSIFWriteL(pabyJPEGData, static_cast<size_t>(nJPEGDataSize), 1, m_fp);
    4414          10 :         CPLFree(pabyJPEGData);
    4415             :     }
    4416             :     else
    4417             :     {
    4418             :         GByte *pabyLine =
    4419         204 :             (GByte *)CPLMalloc(static_cast<size_t>(nReqXSize) * nBands);
    4420       93175 :         for (int iLine = 0; iLine < nReqYSize; iLine++)
    4421             :         {
    4422             :             /* Get pixel interleaved data */
    4423       92973 :             eErr = poBlockSrcDS->RasterIO(
    4424             :                 GF_Read, 0, iLine, nReqXSize, 1, pabyLine, nReqXSize, 1,
    4425             :                 GDT_Byte, nBands, nullptr, nBands, 0, 1, nullptr);
    4426       92973 :             if (eErr != CE_None)
    4427           0 :                 break;
    4428             : 
    4429             :             /* Apply predictor if needed */
    4430       92973 :             if (nPredictor == 2)
    4431             :             {
    4432        1124 :                 if (nBands == 1)
    4433             :                 {
    4434        1024 :                     int nPrevValue = pabyLine[0];
    4435      524288 :                     for (int iPixel = 1; iPixel < nReqXSize; iPixel++)
    4436             :                     {
    4437      523264 :                         int nCurValue = pabyLine[iPixel];
    4438      523264 :                         pabyLine[iPixel] = (GByte)(nCurValue - nPrevValue);
    4439      523264 :                         nPrevValue = nCurValue;
    4440             :                     }
    4441             :                 }
    4442         100 :                 else if (nBands == 3)
    4443             :                 {
    4444         100 :                     int nPrevValueR = pabyLine[0];
    4445         100 :                     int nPrevValueG = pabyLine[1];
    4446         100 :                     int nPrevValueB = pabyLine[2];
    4447        5000 :                     for (int iPixel = 1; iPixel < nReqXSize; iPixel++)
    4448             :                     {
    4449        4900 :                         int nCurValueR = pabyLine[3 * iPixel + 0];
    4450        4900 :                         int nCurValueG = pabyLine[3 * iPixel + 1];
    4451        4900 :                         int nCurValueB = pabyLine[3 * iPixel + 2];
    4452        4900 :                         pabyLine[3 * iPixel + 0] =
    4453        4900 :                             (GByte)(nCurValueR - nPrevValueR);
    4454        4900 :                         pabyLine[3 * iPixel + 1] =
    4455        4900 :                             (GByte)(nCurValueG - nPrevValueG);
    4456        4900 :                         pabyLine[3 * iPixel + 2] =
    4457        4900 :                             (GByte)(nCurValueB - nPrevValueB);
    4458        4900 :                         nPrevValueR = nCurValueR;
    4459        4900 :                         nPrevValueG = nCurValueG;
    4460        4900 :                         nPrevValueB = nCurValueB;
    4461             :                     }
    4462             :                 }
    4463             :             }
    4464             : 
    4465       92973 :             if (VSIFWriteL(pabyLine, static_cast<size_t>(nReqXSize) * nBands, 1,
    4466       92973 :                            m_fp) != 1)
    4467             :             {
    4468           2 :                 eErr = CE_Failure;
    4469           2 :                 break;
    4470             :             }
    4471             : 
    4472      185683 :             if (pfnProgress != nullptr &&
    4473       92712 :                 !pfnProgress((iLine + 1) / (double)nReqYSize, nullptr,
    4474             :                              pProgressData))
    4475             :             {
    4476           0 :                 CPLError(CE_Failure, CPLE_UserInterrupt,
    4477             :                          "User terminated CreateCopy()");
    4478           0 :                 eErr = CE_Failure;
    4479           0 :                 break;
    4480             :             }
    4481             :         }
    4482             : 
    4483         204 :         CPLFree(pabyLine);
    4484             :     }
    4485             : 
    4486         214 : end:
    4487         214 :     CPLFree(pabyMEMDSBuffer);
    4488         214 :     pabyMEMDSBuffer = nullptr;
    4489             : 
    4490         214 :     EndObjWithStream();
    4491             : 
    4492         214 :     return eErr == CE_None ? nImageId : GDALPDFObjectNum();
    4493             : }
    4494             : 
    4495             : /************************************************************************/
    4496             : /*                          WriteJavascript()                           */
    4497             : /************************************************************************/
    4498             : 
    4499           3 : GDALPDFObjectNum GDALPDFBaseWriter::WriteJavascript(const char *pszJavascript,
    4500             :                                                     bool bDeflate)
    4501             : {
    4502           3 :     auto nJSId = AllocNewObject();
    4503             :     {
    4504           6 :         GDALPDFDictionaryRW oDict;
    4505           3 :         StartObjWithStream(nJSId, oDict, bDeflate);
    4506             : 
    4507           3 :         VSIFWriteL(pszJavascript, strlen(pszJavascript), 1, m_fp);
    4508           3 :         VSIFPrintfL(m_fp, "\n");
    4509             : 
    4510           3 :         EndObjWithStream();
    4511             :     }
    4512             : 
    4513           3 :     m_nNamesId = AllocNewObject();
    4514           3 :     StartObj(m_nNamesId);
    4515             :     {
    4516           3 :         GDALPDFDictionaryRW oDict;
    4517           3 :         GDALPDFDictionaryRW *poJavaScriptDict = new GDALPDFDictionaryRW();
    4518           3 :         oDict.Add("JavaScript", poJavaScriptDict);
    4519             : 
    4520           3 :         GDALPDFArrayRW *poNamesArray = new GDALPDFArrayRW();
    4521           3 :         poJavaScriptDict->Add("Names", poNamesArray);
    4522             : 
    4523           3 :         poNamesArray->Add("GDAL");
    4524             : 
    4525           3 :         GDALPDFDictionaryRW *poJSDict = new GDALPDFDictionaryRW();
    4526           3 :         poNamesArray->Add(poJSDict);
    4527             : 
    4528           3 :         poJSDict->Add("JS", nJSId, 0);
    4529           3 :         poJSDict->Add("S", GDALPDFObjectRW::CreateName("JavaScript"));
    4530             : 
    4531           3 :         VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
    4532             :     }
    4533           3 :     EndObj();
    4534             : 
    4535           3 :     return m_nNamesId;
    4536             : }
    4537             : 
    4538           2 : GDALPDFObjectNum GDALPDFWriter::WriteJavascript(const char *pszJavascript)
    4539             : {
    4540             :     return GDALPDFBaseWriter::WriteJavascript(
    4541           2 :         pszJavascript, oPageContext.eStreamCompressMethod != COMPRESS_NONE);
    4542             : }
    4543             : 
    4544             : /************************************************************************/
    4545             : /*                        WriteJavascriptFile()                         */
    4546             : /************************************************************************/
    4547             : 
    4548             : GDALPDFObjectNum
    4549           0 : GDALPDFWriter::WriteJavascriptFile(const char *pszJavascriptFile)
    4550             : {
    4551           0 :     GDALPDFObjectNum nId;
    4552           0 :     char *pszJavascriptToFree = (char *)CPLMalloc(65536);
    4553           0 :     VSILFILE *fpJS = VSIFOpenL(pszJavascriptFile, "rb");
    4554           0 :     if (fpJS != nullptr)
    4555             :     {
    4556           0 :         int nRead = (int)VSIFReadL(pszJavascriptToFree, 1, 65536, fpJS);
    4557           0 :         if (nRead < 65536)
    4558             :         {
    4559           0 :             pszJavascriptToFree[nRead] = '\0';
    4560           0 :             nId = WriteJavascript(pszJavascriptToFree);
    4561             :         }
    4562           0 :         VSIFCloseL(fpJS);
    4563             :     }
    4564           0 :     CPLFree(pszJavascriptToFree);
    4565           0 :     return nId;
    4566             : }
    4567             : 
    4568             : /************************************************************************/
    4569             : /*                              WritePages()                            */
    4570             : /************************************************************************/
    4571             : 
    4572         123 : void GDALPDFWriter::WritePages()
    4573             : {
    4574         123 :     StartObj(m_nPageResourceId);
    4575             :     {
    4576         123 :         GDALPDFDictionaryRW oDict;
    4577         123 :         GDALPDFArrayRW *poKids = new GDALPDFArrayRW();
    4578         123 :         oDict.Add("Type", GDALPDFObjectRW::CreateName("Pages"))
    4579         123 :             .Add("Count", (int)m_asPageId.size())
    4580         123 :             .Add("Kids", poKids);
    4581             : 
    4582         246 :         for (size_t i = 0; i < m_asPageId.size(); i++)
    4583         123 :             poKids->Add(m_asPageId[i], 0);
    4584             : 
    4585         123 :         VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
    4586             :     }
    4587         123 :     EndObj();
    4588             : 
    4589         123 :     StartObj(m_nCatalogId);
    4590             :     {
    4591         123 :         GDALPDFDictionaryRW oDict;
    4592         123 :         oDict.Add("Type", GDALPDFObjectRW::CreateName("Catalog"))
    4593         123 :             .Add("Pages", m_nPageResourceId, 0);
    4594         123 :         if (m_nXMPId.toBool())
    4595           2 :             oDict.Add("Metadata", m_nXMPId, 0);
    4596         123 :         if (!m_asOCGs.empty())
    4597             :         {
    4598          26 :             GDALPDFDictionaryRW *poDictOCProperties = new GDALPDFDictionaryRW();
    4599          26 :             oDict.Add("OCProperties", poDictOCProperties);
    4600             : 
    4601          26 :             GDALPDFDictionaryRW *poDictD = new GDALPDFDictionaryRW();
    4602          26 :             poDictOCProperties->Add("D", poDictD);
    4603             : 
    4604             :             /* Build "Order" array of D dict */
    4605          26 :             GDALPDFArrayRW *poArrayOrder = new GDALPDFArrayRW();
    4606          71 :             for (size_t i = 0; i < m_asOCGs.size(); i++)
    4607             :             {
    4608          45 :                 poArrayOrder->Add(m_asOCGs[i].nId, 0);
    4609          68 :                 if (i + 1 < m_asOCGs.size() &&
    4610          23 :                     m_asOCGs[i + 1].nParentId == m_asOCGs[i].nId)
    4611             :                 {
    4612           4 :                     GDALPDFArrayRW *poSubArrayOrder = new GDALPDFArrayRW();
    4613           4 :                     poSubArrayOrder->Add(m_asOCGs[i + 1].nId, 0);
    4614           4 :                     poArrayOrder->Add(poSubArrayOrder);
    4615           4 :                     i++;
    4616             :                 }
    4617             :             }
    4618          26 :             poDictD->Add("Order", poArrayOrder);
    4619             : 
    4620             :             /* Build "OFF" array of D dict */
    4621          26 :             if (!m_osOffLayers.empty())
    4622             :             {
    4623           2 :                 GDALPDFArrayRW *poArrayOFF = new GDALPDFArrayRW();
    4624           2 :                 char **papszTokens = CSLTokenizeString2(m_osOffLayers, ",", 0);
    4625           4 :                 for (int i = 0; papszTokens[i] != nullptr; i++)
    4626             :                 {
    4627             :                     size_t j;
    4628           2 :                     int bFound = FALSE;
    4629           6 :                     for (j = 0; j < m_asOCGs.size(); j++)
    4630             :                     {
    4631           4 :                         if (strcmp(papszTokens[i], m_asOCGs[j].osLayerName) ==
    4632             :                             0)
    4633             :                         {
    4634           2 :                             poArrayOFF->Add(m_asOCGs[j].nId, 0);
    4635           2 :                             bFound = TRUE;
    4636             :                         }
    4637           6 :                         if (j + 1 < m_asOCGs.size() &&
    4638           2 :                             m_asOCGs[j + 1].nParentId == m_asOCGs[j].nId)
    4639             :                         {
    4640           0 :                             j++;
    4641             :                         }
    4642             :                     }
    4643           2 :                     if (!bFound)
    4644             :                     {
    4645           0 :                         CPLError(
    4646             :                             CE_Warning, CPLE_AppDefined,
    4647             :                             "Unknown layer name (%s) specified in OFF_LAYERS",
    4648           0 :                             papszTokens[i]);
    4649             :                     }
    4650             :                 }
    4651           2 :                 CSLDestroy(papszTokens);
    4652             : 
    4653           2 :                 poDictD->Add("OFF", poArrayOFF);
    4654             :             }
    4655             : 
    4656             :             /* Build "RBGroups" array of D dict */
    4657          26 :             if (!m_osExclusiveLayers.empty())
    4658             :             {
    4659           2 :                 GDALPDFArrayRW *poArrayRBGroups = new GDALPDFArrayRW();
    4660             :                 char **papszTokens =
    4661           2 :                     CSLTokenizeString2(m_osExclusiveLayers, ",", 0);
    4662           6 :                 for (int i = 0; papszTokens[i] != nullptr; i++)
    4663             :                 {
    4664             :                     size_t j;
    4665           4 :                     int bFound = FALSE;
    4666          12 :                     for (j = 0; j < m_asOCGs.size(); j++)
    4667             :                     {
    4668           8 :                         if (strcmp(papszTokens[i], m_asOCGs[j].osLayerName) ==
    4669             :                             0)
    4670             :                         {
    4671           4 :                             poArrayRBGroups->Add(m_asOCGs[j].nId, 0);
    4672           4 :                             bFound = TRUE;
    4673             :                         }
    4674          12 :                         if (j + 1 < m_asOCGs.size() &&
    4675           4 :                             m_asOCGs[j + 1].nParentId == m_asOCGs[j].nId)
    4676             :                         {
    4677           0 :                             j++;
    4678             :                         }
    4679             :                     }
    4680           4 :                     if (!bFound)
    4681             :                     {
    4682           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
    4683             :                                  "Unknown layer name (%s) specified in "
    4684             :                                  "EXCLUSIVE_LAYERS",
    4685           0 :                                  papszTokens[i]);
    4686             :                     }
    4687             :                 }
    4688           2 :                 CSLDestroy(papszTokens);
    4689             : 
    4690           2 :                 if (poArrayRBGroups->GetLength())
    4691             :                 {
    4692           2 :                     GDALPDFArrayRW *poMainArrayRBGroups = new GDALPDFArrayRW();
    4693           2 :                     poMainArrayRBGroups->Add(poArrayRBGroups);
    4694           2 :                     poDictD->Add("RBGroups", poMainArrayRBGroups);
    4695             :                 }
    4696             :                 else
    4697           0 :                     delete poArrayRBGroups;
    4698             :             }
    4699             : 
    4700          26 :             GDALPDFArrayRW *poArrayOGCs = new GDALPDFArrayRW();
    4701          75 :             for (size_t i = 0; i < m_asOCGs.size(); i++)
    4702          49 :                 poArrayOGCs->Add(m_asOCGs[i].nId, 0);
    4703          26 :             poDictOCProperties->Add("OCGs", poArrayOGCs);
    4704             :         }
    4705             : 
    4706         123 :         if (m_nStructTreeRootId.toBool())
    4707             :         {
    4708          21 :             GDALPDFDictionaryRW *poDictMarkInfo = new GDALPDFDictionaryRW();
    4709          21 :             oDict.Add("MarkInfo", poDictMarkInfo);
    4710             :             poDictMarkInfo->Add("UserProperties",
    4711          21 :                                 GDALPDFObjectRW::CreateBool(TRUE));
    4712             : 
    4713          21 :             oDict.Add("StructTreeRoot", m_nStructTreeRootId, 0);
    4714             :         }
    4715             : 
    4716         123 :         if (m_nNamesId.toBool())
    4717           2 :             oDict.Add("Names", m_nNamesId, 0);
    4718             : 
    4719         123 :         VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
    4720             :     }
    4721         123 :     EndObj();
    4722         123 : }
    4723             : 
    4724             : /************************************************************************/
    4725             : /*                        GDALPDFGetJPEGQuality()                       */
    4726             : /************************************************************************/
    4727             : 
    4728         108 : static int GDALPDFGetJPEGQuality(char **papszOptions)
    4729             : {
    4730         108 :     int nJpegQuality = -1;
    4731         108 :     const char *pszValue = CSLFetchNameValue(papszOptions, "JPEG_QUALITY");
    4732         108 :     if (pszValue != nullptr)
    4733             :     {
    4734           0 :         nJpegQuality = atoi(pszValue);
    4735           0 :         if (!(nJpegQuality >= 1 && nJpegQuality <= 100))
    4736             :         {
    4737           0 :             CPLError(CE_Warning, CPLE_IllegalArg,
    4738             :                      "JPEG_QUALITY=%s value not recognised, ignoring.",
    4739             :                      pszValue);
    4740           0 :             nJpegQuality = -1;
    4741             :         }
    4742             :     }
    4743         108 :     return nJpegQuality;
    4744             : }
    4745             : 
    4746             : /************************************************************************/
    4747             : /*                         GDALPDFClippingDataset                       */
    4748             : /************************************************************************/
    4749             : 
    4750             : class GDALPDFClippingDataset final : public GDALDataset
    4751             : {
    4752             :     GDALDataset *poSrcDS;
    4753             :     double adfGeoTransform[6];
    4754             : 
    4755             :   public:
    4756           2 :     GDALPDFClippingDataset(GDALDataset *poSrcDSIn, double adfClippingExtent[4])
    4757           2 :         : poSrcDS(poSrcDSIn)
    4758             :     {
    4759             :         double adfSrcGeoTransform[6];
    4760           2 :         poSrcDS->GetGeoTransform(adfSrcGeoTransform);
    4761           2 :         adfGeoTransform[0] = adfClippingExtent[0];
    4762           2 :         adfGeoTransform[1] = adfSrcGeoTransform[1];
    4763           2 :         adfGeoTransform[2] = 0.0;
    4764           2 :         adfGeoTransform[3] = adfSrcGeoTransform[5] < 0 ? adfClippingExtent[3]
    4765             :                                                        : adfClippingExtent[1];
    4766           2 :         adfGeoTransform[4] = 0.0;
    4767           2 :         adfGeoTransform[5] = adfSrcGeoTransform[5];
    4768           2 :         nRasterXSize = (int)((adfClippingExtent[2] - adfClippingExtent[0]) /
    4769           2 :                              adfSrcGeoTransform[1]);
    4770           2 :         nRasterYSize = (int)((adfClippingExtent[3] - adfClippingExtent[1]) /
    4771           2 :                              fabs(adfSrcGeoTransform[5]));
    4772           2 :     }
    4773             : 
    4774           6 :     virtual CPLErr GetGeoTransform(double *padfGeoTransform) override
    4775             :     {
    4776           6 :         memcpy(padfGeoTransform, adfGeoTransform, 6 * sizeof(double));
    4777           6 :         return CE_None;
    4778             :     }
    4779             : 
    4780           2 :     virtual const OGRSpatialReference *GetSpatialRef() const override
    4781             :     {
    4782           2 :         return poSrcDS->GetSpatialRef();
    4783             :     }
    4784             : };
    4785             : 
    4786             : /************************************************************************/
    4787             : /*                          GDALPDFCreateCopy()                         */
    4788             : /************************************************************************/
    4789             : 
    4790         121 : GDALDataset *GDALPDFCreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
    4791             :                                int bStrict, char **papszOptions,
    4792             :                                GDALProgressFunc pfnProgress,
    4793             :                                void *pProgressData)
    4794             : {
    4795         121 :     int nBands = poSrcDS->GetRasterCount();
    4796         121 :     int nWidth = poSrcDS->GetRasterXSize();
    4797         121 :     int nHeight = poSrcDS->GetRasterYSize();
    4798             : 
    4799         121 :     if (!pfnProgress(0.0, nullptr, pProgressData))
    4800           0 :         return nullptr;
    4801             : 
    4802             :     /* -------------------------------------------------------------------- */
    4803             :     /*      Some some rudimentary checks                                    */
    4804             :     /* -------------------------------------------------------------------- */
    4805         121 :     if (nBands != 1 && nBands != 3 && nBands != 4)
    4806             :     {
    4807           3 :         CPLError(CE_Failure, CPLE_NotSupported,
    4808             :                  "PDF driver doesn't support %d bands.  Must be 1 (grey or "
    4809             :                  "with color table), "
    4810             :                  "3 (RGB) or 4 bands.\n",
    4811             :                  nBands);
    4812             : 
    4813           3 :         return nullptr;
    4814             :     }
    4815             : 
    4816         118 :     GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
    4817         118 :     if (eDT != GDT_Byte)
    4818             :     {
    4819          10 :         CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
    4820             :                  "PDF driver doesn't support data type %s. "
    4821             :                  "Only eight bit byte bands supported.\n",
    4822             :                  GDALGetDataTypeName(
    4823             :                      poSrcDS->GetRasterBand(1)->GetRasterDataType()));
    4824             : 
    4825          10 :         if (bStrict)
    4826          10 :             return nullptr;
    4827             :     }
    4828             : 
    4829             :     /* -------------------------------------------------------------------- */
    4830             :     /*     Read options.                                                    */
    4831             :     /* -------------------------------------------------------------------- */
    4832         108 :     PDFCompressMethod eCompressMethod = COMPRESS_DEFAULT;
    4833         108 :     const char *pszCompressMethod = CSLFetchNameValue(papszOptions, "COMPRESS");
    4834         108 :     if (pszCompressMethod)
    4835             :     {
    4836          14 :         if (EQUAL(pszCompressMethod, "NONE"))
    4837           2 :             eCompressMethod = COMPRESS_NONE;
    4838          12 :         else if (EQUAL(pszCompressMethod, "DEFLATE"))
    4839           2 :             eCompressMethod = COMPRESS_DEFLATE;
    4840          10 :         else if (EQUAL(pszCompressMethod, "JPEG"))
    4841           6 :             eCompressMethod = COMPRESS_JPEG;
    4842           4 :         else if (EQUAL(pszCompressMethod, "JPEG2000"))
    4843           4 :             eCompressMethod = COMPRESS_JPEG2000;
    4844             :         else
    4845             :         {
    4846           0 :             CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
    4847             :                      "Unsupported value for COMPRESS.");
    4848             : 
    4849           0 :             if (bStrict)
    4850           0 :                 return nullptr;
    4851             :         }
    4852             :     }
    4853             : 
    4854         108 :     PDFCompressMethod eStreamCompressMethod = COMPRESS_DEFLATE;
    4855             :     const char *pszStreamCompressMethod =
    4856         108 :         CSLFetchNameValue(papszOptions, "STREAM_COMPRESS");
    4857         108 :     if (pszStreamCompressMethod)
    4858             :     {
    4859           4 :         if (EQUAL(pszStreamCompressMethod, "NONE"))
    4860           4 :             eStreamCompressMethod = COMPRESS_NONE;
    4861           0 :         else if (EQUAL(pszStreamCompressMethod, "DEFLATE"))
    4862           0 :             eStreamCompressMethod = COMPRESS_DEFLATE;
    4863             :         else
    4864             :         {
    4865           0 :             CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
    4866             :                      "Unsupported value for STREAM_COMPRESS.");
    4867             : 
    4868           0 :             if (bStrict)
    4869           0 :                 return nullptr;
    4870             :         }
    4871             :     }
    4872             : 
    4873         110 :     if (nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr &&
    4874           2 :         (eCompressMethod == COMPRESS_JPEG ||
    4875             :          eCompressMethod == COMPRESS_JPEG2000))
    4876             :     {
    4877           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    4878             :                  "The source raster band has a color table, which is not "
    4879             :                  "appropriate with JPEG or JPEG2000 compression.\n"
    4880             :                  "You should rather consider using color table expansion "
    4881             :                  "(-expand option in gdal_translate)");
    4882             :     }
    4883             : 
    4884         108 :     int nBlockXSize = nWidth;
    4885         108 :     int nBlockYSize = nHeight;
    4886             : 
    4887         108 :     const bool bTiled = CPLFetchBool(papszOptions, "TILED", false);
    4888         108 :     if (bTiled)
    4889             :     {
    4890           2 :         nBlockXSize = 256;
    4891           2 :         nBlockYSize = 256;
    4892             :     }
    4893             : 
    4894         108 :     const char *pszValue = CSLFetchNameValue(papszOptions, "BLOCKXSIZE");
    4895         108 :     if (pszValue != nullptr)
    4896             :     {
    4897           4 :         nBlockXSize = atoi(pszValue);
    4898           4 :         if (nBlockXSize <= 0 || nBlockXSize >= nWidth)
    4899           0 :             nBlockXSize = nWidth;
    4900             :     }
    4901             : 
    4902         108 :     pszValue = CSLFetchNameValue(papszOptions, "BLOCKYSIZE");
    4903         108 :     if (pszValue != nullptr)
    4904             :     {
    4905           4 :         nBlockYSize = atoi(pszValue);
    4906           4 :         if (nBlockYSize <= 0 || nBlockYSize >= nHeight)
    4907           0 :             nBlockYSize = nHeight;
    4908             :     }
    4909             : 
    4910         108 :     int nJPEGQuality = GDALPDFGetJPEGQuality(papszOptions);
    4911             : 
    4912             :     const char *pszJPEG2000_DRIVER =
    4913         108 :         CSLFetchNameValue(papszOptions, "JPEG2000_DRIVER");
    4914             : 
    4915             :     const char *pszGEO_ENCODING =
    4916         108 :         CSLFetchNameValueDef(papszOptions, "GEO_ENCODING", "ISO32000");
    4917             : 
    4918         108 :     const char *pszXMP = CSLFetchNameValue(papszOptions, "XMP");
    4919             : 
    4920         108 :     const char *pszPredictor = CSLFetchNameValue(papszOptions, "PREDICTOR");
    4921         108 :     int nPredictor = 1;
    4922         108 :     if (pszPredictor)
    4923             :     {
    4924           4 :         if (eCompressMethod == COMPRESS_DEFAULT)
    4925           4 :             eCompressMethod = COMPRESS_DEFLATE;
    4926             : 
    4927           4 :         if (eCompressMethod != COMPRESS_DEFLATE)
    4928             :         {
    4929           0 :             CPLError(CE_Warning, CPLE_NotSupported,
    4930             :                      "PREDICTOR option is only taken into account for DEFLATE "
    4931             :                      "compression");
    4932             :         }
    4933             :         else
    4934             :         {
    4935           4 :             nPredictor = atoi(pszPredictor);
    4936           4 :             if (nPredictor != 1 && nPredictor != 2)
    4937             :             {
    4938           0 :                 CPLError(CE_Warning, CPLE_NotSupported,
    4939             :                          "Supported PREDICTOR values are 1 or 2");
    4940           0 :                 nPredictor = 1;
    4941             :             }
    4942             :         }
    4943             :     }
    4944             : 
    4945         108 :     const char *pszNEATLINE = CSLFetchNameValue(papszOptions, "NEATLINE");
    4946             : 
    4947         108 :     int nMargin = atoi(CSLFetchNameValueDef(papszOptions, "MARGIN", "0"));
    4948             : 
    4949         108 :     PDFMargins sMargins;
    4950         108 :     sMargins.nLeft = nMargin;
    4951         108 :     sMargins.nRight = nMargin;
    4952         108 :     sMargins.nTop = nMargin;
    4953         108 :     sMargins.nBottom = nMargin;
    4954             : 
    4955         108 :     const char *pszLeftMargin = CSLFetchNameValue(papszOptions, "LEFT_MARGIN");
    4956         108 :     if (pszLeftMargin)
    4957           4 :         sMargins.nLeft = atoi(pszLeftMargin);
    4958             : 
    4959             :     const char *pszRightMargin =
    4960         108 :         CSLFetchNameValue(papszOptions, "RIGHT_MARGIN");
    4961         108 :     if (pszRightMargin)
    4962           2 :         sMargins.nRight = atoi(pszRightMargin);
    4963             : 
    4964         108 :     const char *pszTopMargin = CSLFetchNameValue(papszOptions, "TOP_MARGIN");
    4965         108 :     if (pszTopMargin)
    4966           4 :         sMargins.nTop = atoi(pszTopMargin);
    4967             : 
    4968             :     const char *pszBottomMargin =
    4969         108 :         CSLFetchNameValue(papszOptions, "BOTTOM_MARGIN");
    4970         108 :     if (pszBottomMargin)
    4971           2 :         sMargins.nBottom = atoi(pszBottomMargin);
    4972             : 
    4973         108 :     const char *pszDPI = CSLFetchNameValue(papszOptions, "DPI");
    4974         108 :     double dfDPI = DEFAULT_DPI;
    4975         108 :     if (pszDPI != nullptr)
    4976          22 :         dfDPI = CPLAtof(pszDPI);
    4977             : 
    4978             :     const char *pszWriteUserUnit =
    4979         108 :         CSLFetchNameValue(papszOptions, "WRITE_USERUNIT");
    4980             :     bool bWriteUserUnit;
    4981         108 :     if (pszWriteUserUnit != nullptr)
    4982           2 :         bWriteUserUnit = CPLTestBool(pszWriteUserUnit);
    4983             :     else
    4984         106 :         bWriteUserUnit = (pszDPI == nullptr);
    4985             : 
    4986         108 :     double dfUserUnit = dfDPI * USER_UNIT_IN_INCH;
    4987         108 :     double dfWidthInUserUnit =
    4988         108 :         nWidth / dfUserUnit + sMargins.nLeft + sMargins.nRight;
    4989         108 :     double dfHeightInUserUnit =
    4990         108 :         nHeight / dfUserUnit + sMargins.nBottom + sMargins.nTop;
    4991         108 :     if (dfWidthInUserUnit > MAXIMUM_SIZE_IN_UNITS ||
    4992             :         dfHeightInUserUnit > MAXIMUM_SIZE_IN_UNITS)
    4993             :     {
    4994          12 :         if (pszDPI == nullptr)
    4995             :         {
    4996           8 :             if (sMargins.nLeft + sMargins.nRight >= MAXIMUM_SIZE_IN_UNITS ||
    4997           6 :                 sMargins.nBottom + sMargins.nTop >= MAXIMUM_SIZE_IN_UNITS)
    4998             :             {
    4999           4 :                 CPLError(
    5000             :                     CE_Warning, CPLE_AppDefined,
    5001             :                     "Margins too big compared to maximum page dimension (%d) "
    5002             :                     "in user units allowed by Acrobat",
    5003             :                     MAXIMUM_SIZE_IN_UNITS);
    5004             :             }
    5005             :             else
    5006             :             {
    5007           4 :                 if (dfWidthInUserUnit >= dfHeightInUserUnit)
    5008             :                 {
    5009           2 :                     dfDPI = ceil((double)nWidth /
    5010           2 :                                  (MAXIMUM_SIZE_IN_UNITS -
    5011           2 :                                   (sMargins.nLeft + sMargins.nRight)) /
    5012             :                                  USER_UNIT_IN_INCH);
    5013             :                 }
    5014             :                 else
    5015             :                 {
    5016           2 :                     dfDPI = ceil((double)nHeight /
    5017           2 :                                  (MAXIMUM_SIZE_IN_UNITS -
    5018           2 :                                   (sMargins.nBottom + sMargins.nTop)) /
    5019             :                                  USER_UNIT_IN_INCH);
    5020             :                 }
    5021           4 :                 CPLDebug("PDF",
    5022             :                          "Adjusting DPI to %d so that page dimension in "
    5023             :                          "user units remain in what is accepted by Acrobat",
    5024             :                          (int)dfDPI);
    5025             :             }
    5026             :         }
    5027             :         else
    5028             :         {
    5029           4 :             CPLError(CE_Warning, CPLE_AppDefined,
    5030             :                      "The page dimension in user units is %d x %d whereas the "
    5031             :                      "maximum allowed by Acrobat is %d x %d",
    5032           4 :                      (int)(dfWidthInUserUnit + 0.5),
    5033           4 :                      (int)(dfHeightInUserUnit + 0.5), MAXIMUM_SIZE_IN_UNITS,
    5034             :                      MAXIMUM_SIZE_IN_UNITS);
    5035             :         }
    5036             :     }
    5037             : 
    5038         108 :     if (dfDPI < DEFAULT_DPI)
    5039           0 :         dfDPI = DEFAULT_DPI;
    5040             : 
    5041             :     const char *pszClippingExtent =
    5042         108 :         CSLFetchNameValue(papszOptions, "CLIPPING_EXTENT");
    5043         108 :     int bUseClippingExtent = FALSE;
    5044         108 :     double adfClippingExtent[4] = {0.0, 0.0, 0.0, 0.0};
    5045         108 :     if (pszClippingExtent != nullptr)
    5046             :     {
    5047           2 :         char **papszTokens = CSLTokenizeString2(pszClippingExtent, ",", 0);
    5048           2 :         if (CSLCount(papszTokens) == 4)
    5049             :         {
    5050           2 :             bUseClippingExtent = TRUE;
    5051           2 :             adfClippingExtent[0] = CPLAtof(papszTokens[0]);
    5052           2 :             adfClippingExtent[1] = CPLAtof(papszTokens[1]);
    5053           2 :             adfClippingExtent[2] = CPLAtof(papszTokens[2]);
    5054           2 :             adfClippingExtent[3] = CPLAtof(papszTokens[3]);
    5055           2 :             if (adfClippingExtent[0] > adfClippingExtent[2] ||
    5056           2 :                 adfClippingExtent[1] > adfClippingExtent[3])
    5057             :             {
    5058           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    5059             :                          "Invalid value for CLIPPING_EXTENT. Should be "
    5060             :                          "xmin,ymin,xmax,ymax");
    5061           0 :                 bUseClippingExtent = FALSE;
    5062             :             }
    5063             : 
    5064           2 :             if (bUseClippingExtent)
    5065             :             {
    5066             :                 double adfGeoTransform[6];
    5067           2 :                 if (poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None)
    5068             :                 {
    5069           2 :                     if (adfGeoTransform[2] != 0.0 || adfGeoTransform[4] != 0.0)
    5070             :                     {
    5071           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
    5072             :                                  "Cannot use CLIPPING_EXTENT because main "
    5073             :                                  "raster has a rotated geotransform");
    5074           0 :                         bUseClippingExtent = FALSE;
    5075             :                     }
    5076             :                 }
    5077             :                 else
    5078             :                 {
    5079           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    5080             :                              "Cannot use CLIPPING_EXTENT because main raster "
    5081             :                              "has no geotransform");
    5082           0 :                     bUseClippingExtent = FALSE;
    5083             :                 }
    5084             :             }
    5085             :         }
    5086           2 :         CSLDestroy(papszTokens);
    5087             :     }
    5088             : 
    5089         108 :     const char *pszLayerName = CSLFetchNameValue(papszOptions, "LAYER_NAME");
    5090             : 
    5091             :     const char *pszExtraImages =
    5092         108 :         CSLFetchNameValue(papszOptions, "EXTRA_IMAGES");
    5093             :     const char *pszExtraStream =
    5094         108 :         CSLFetchNameValue(papszOptions, "EXTRA_STREAM");
    5095             :     const char *pszExtraLayerName =
    5096         108 :         CSLFetchNameValue(papszOptions, "EXTRA_LAYER_NAME");
    5097             : 
    5098             :     const char *pszOGRDataSource =
    5099         108 :         CSLFetchNameValue(papszOptions, "OGR_DATASOURCE");
    5100             :     const char *pszOGRDisplayField =
    5101         108 :         CSLFetchNameValue(papszOptions, "OGR_DISPLAY_FIELD");
    5102             :     const char *pszOGRDisplayLayerNames =
    5103         108 :         CSLFetchNameValue(papszOptions, "OGR_DISPLAY_LAYER_NAMES");
    5104             :     const char *pszOGRLinkField =
    5105         108 :         CSLFetchNameValue(papszOptions, "OGR_LINK_FIELD");
    5106             :     const bool bWriteOGRAttributes =
    5107         108 :         CPLFetchBool(papszOptions, "OGR_WRITE_ATTRIBUTES", true);
    5108             : 
    5109             :     const char *pszExtraRasters =
    5110         108 :         CSLFetchNameValue(papszOptions, "EXTRA_RASTERS");
    5111             :     const char *pszExtraRastersLayerName =
    5112         108 :         CSLFetchNameValue(papszOptions, "EXTRA_RASTERS_LAYER_NAME");
    5113             : 
    5114         108 :     const char *pszOffLayers = CSLFetchNameValue(papszOptions, "OFF_LAYERS");
    5115             :     const char *pszExclusiveLayers =
    5116         108 :         CSLFetchNameValue(papszOptions, "EXCLUSIVE_LAYERS");
    5117             : 
    5118         108 :     const char *pszJavascript = CSLFetchNameValue(papszOptions, "JAVASCRIPT");
    5119             :     const char *pszJavascriptFile =
    5120         108 :         CSLFetchNameValue(papszOptions, "JAVASCRIPT_FILE");
    5121             : 
    5122             :     /* -------------------------------------------------------------------- */
    5123             :     /*      Create file.                                                    */
    5124             :     /* -------------------------------------------------------------------- */
    5125         108 :     VSILFILE *fp = VSIFOpenL(pszFilename, "wb");
    5126         108 :     if (fp == nullptr)
    5127             :     {
    5128           3 :         CPLError(CE_Failure, CPLE_OpenFailed, "Unable to create PDF file %s.\n",
    5129             :                  pszFilename);
    5130           3 :         return nullptr;
    5131             :     }
    5132             : 
    5133         210 :     GDALPDFWriter oWriter(fp);
    5134             : 
    5135         105 :     GDALDataset *poClippingDS = poSrcDS;
    5136         105 :     if (bUseClippingExtent)
    5137           2 :         poClippingDS = new GDALPDFClippingDataset(poSrcDS, adfClippingExtent);
    5138             : 
    5139         105 :     if (CPLFetchBool(papszOptions, "WRITE_INFO", true))
    5140         103 :         oWriter.SetInfo(poSrcDS, papszOptions);
    5141         105 :     oWriter.SetXMP(poClippingDS, pszXMP);
    5142             : 
    5143         105 :     oWriter.StartPage(poClippingDS, dfDPI, bWriteUserUnit, pszGEO_ENCODING,
    5144             :                       pszNEATLINE, &sMargins, eStreamCompressMethod,
    5145         105 :                       pszOGRDataSource != nullptr && bWriteOGRAttributes);
    5146             : 
    5147             :     int bRet;
    5148             : 
    5149         105 :     if (!bUseClippingExtent)
    5150             :     {
    5151         103 :         bRet = oWriter.WriteImagery(poSrcDS, pszLayerName, eCompressMethod,
    5152             :                                     nPredictor, nJPEGQuality,
    5153             :                                     pszJPEG2000_DRIVER, nBlockXSize,
    5154             :                                     nBlockYSize, pfnProgress, pProgressData);
    5155             :     }
    5156             :     else
    5157             :     {
    5158           2 :         bRet = oWriter.WriteClippedImagery(
    5159             :             poSrcDS, pszLayerName, eCompressMethod, nPredictor, nJPEGQuality,
    5160             :             pszJPEG2000_DRIVER, nBlockXSize, nBlockYSize, pfnProgress,
    5161             :             pProgressData);
    5162             :     }
    5163             : 
    5164             :     char **papszExtraRasters =
    5165         105 :         CSLTokenizeString2(pszExtraRasters ? pszExtraRasters : "", ",", 0);
    5166         105 :     char **papszExtraRastersLayerName = CSLTokenizeString2(
    5167             :         pszExtraRastersLayerName ? pszExtraRastersLayerName : "", ",", 0);
    5168             :     int bUseExtraRastersLayerName =
    5169         105 :         (CSLCount(papszExtraRasters) == CSLCount(papszExtraRastersLayerName));
    5170         105 :     int bUseExtraRasters = TRUE;
    5171             : 
    5172         105 :     const char *pszClippingProjectionRef = poSrcDS->GetProjectionRef();
    5173         105 :     if (CSLCount(papszExtraRasters) != 0)
    5174             :     {
    5175             :         double adfGeoTransform[6];
    5176           2 :         if (poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None)
    5177             :         {
    5178           2 :             if (adfGeoTransform[2] != 0.0 || adfGeoTransform[4] != 0.0)
    5179             :             {
    5180           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    5181             :                          "Cannot use EXTRA_RASTERS because main raster has a "
    5182             :                          "rotated geotransform");
    5183           0 :                 bUseExtraRasters = FALSE;
    5184             :             }
    5185             :         }
    5186             :         else
    5187             :         {
    5188           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    5189             :                      "Cannot use EXTRA_RASTERS because main raster has no "
    5190             :                      "geotransform");
    5191           0 :             bUseExtraRasters = FALSE;
    5192             :         }
    5193           2 :         if (bUseExtraRasters && (pszClippingProjectionRef == nullptr ||
    5194           2 :                                  pszClippingProjectionRef[0] == '\0'))
    5195             :         {
    5196           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    5197             :                      "Cannot use EXTRA_RASTERS because main raster has no "
    5198             :                      "projection");
    5199           0 :             bUseExtraRasters = FALSE;
    5200             :         }
    5201             :     }
    5202             : 
    5203         107 :     for (int i = 0; bRet && bUseExtraRasters && papszExtraRasters[i] != nullptr;
    5204             :          i++)
    5205             :     {
    5206             :         GDALDataset *poDS =
    5207           2 :             (GDALDataset *)GDALOpen(papszExtraRasters[i], GA_ReadOnly);
    5208           2 :         if (poDS != nullptr)
    5209             :         {
    5210             :             double adfGeoTransform[6];
    5211           2 :             int bUseRaster = TRUE;
    5212           2 :             if (poDS->GetGeoTransform(adfGeoTransform) == CE_None)
    5213             :             {
    5214           2 :                 if (adfGeoTransform[2] != 0.0 || adfGeoTransform[4] != 0.0)
    5215             :                 {
    5216           0 :                     CPLError(
    5217             :                         CE_Warning, CPLE_AppDefined,
    5218             :                         "Cannot use %s because it has a rotated geotransform",
    5219           0 :                         papszExtraRasters[i]);
    5220           0 :                     bUseRaster = FALSE;
    5221             :                 }
    5222             :             }
    5223             :             else
    5224             :             {
    5225           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    5226             :                          "Cannot use %s because it has no geotransform",
    5227           0 :                          papszExtraRasters[i]);
    5228           0 :                 bUseRaster = FALSE;
    5229             :             }
    5230           2 :             const char *pszProjectionRef = poDS->GetProjectionRef();
    5231           2 :             if (bUseRaster &&
    5232           2 :                 (pszProjectionRef == nullptr || pszProjectionRef[0] == '\0'))
    5233             :             {
    5234           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    5235             :                          "Cannot use %s because it has no projection",
    5236           0 :                          papszExtraRasters[i]);
    5237           0 :                 bUseRaster = FALSE;
    5238             :             }
    5239           2 :             if (bUseRaster)
    5240             :             {
    5241           2 :                 if (pszClippingProjectionRef != nullptr &&
    5242           2 :                     pszProjectionRef != nullptr &&
    5243           2 :                     !EQUAL(pszClippingProjectionRef, pszProjectionRef))
    5244             :                 {
    5245             :                     OGRSpatialReferenceH hClippingSRS =
    5246           2 :                         OSRNewSpatialReference(pszClippingProjectionRef);
    5247             :                     OGRSpatialReferenceH hSRS =
    5248           2 :                         OSRNewSpatialReference(pszProjectionRef);
    5249           2 :                     if (!OSRIsSame(hClippingSRS, hSRS))
    5250             :                     {
    5251           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
    5252             :                                  "Cannot use %s because it has a different "
    5253             :                                  "projection than main dataset",
    5254           0 :                                  papszExtraRasters[i]);
    5255           0 :                         bUseRaster = FALSE;
    5256             :                     }
    5257           2 :                     OSRDestroySpatialReference(hClippingSRS);
    5258           2 :                     OSRDestroySpatialReference(hSRS);
    5259             :                 }
    5260             :             }
    5261           2 :             if (bUseRaster)
    5262             :             {
    5263           4 :                 bRet = oWriter.WriteClippedImagery(
    5264             :                     poDS,
    5265           2 :                     bUseExtraRastersLayerName ? papszExtraRastersLayerName[i]
    5266             :                                               : nullptr,
    5267             :                     eCompressMethod, nPredictor, nJPEGQuality,
    5268             :                     pszJPEG2000_DRIVER, nBlockXSize, nBlockYSize, nullptr,
    5269             :                     nullptr);
    5270             :             }
    5271             : 
    5272           2 :             GDALClose(poDS);
    5273             :         }
    5274             :     }
    5275             : 
    5276         105 :     CSLDestroy(papszExtraRasters);
    5277         105 :     CSLDestroy(papszExtraRastersLayerName);
    5278             : 
    5279         105 :     if (bRet && pszOGRDataSource != nullptr)
    5280           4 :         oWriter.WriteOGRDataSource(pszOGRDataSource, pszOGRDisplayField,
    5281             :                                    pszOGRDisplayLayerNames, pszOGRLinkField,
    5282             :                                    bWriteOGRAttributes);
    5283             : 
    5284         105 :     if (bRet)
    5285         103 :         oWriter.EndPage(pszExtraImages, pszExtraStream, pszExtraLayerName,
    5286             :                         pszOffLayers, pszExclusiveLayers);
    5287             : 
    5288         105 :     if (pszJavascript)
    5289           2 :         oWriter.WriteJavascript(pszJavascript);
    5290         103 :     else if (pszJavascriptFile)
    5291           0 :         oWriter.WriteJavascriptFile(pszJavascriptFile);
    5292             : 
    5293         105 :     oWriter.Close();
    5294             : 
    5295         105 :     if (poClippingDS != poSrcDS)
    5296           2 :         delete poClippingDS;
    5297             : 
    5298         105 :     if (!bRet)
    5299             :     {
    5300           2 :         VSIUnlink(pszFilename);
    5301           2 :         return nullptr;
    5302             :     }
    5303             :     else
    5304             :     {
    5305             : #ifdef HAVE_PDF_READ_SUPPORT
    5306         103 :         GDALDataset *poDS = GDALPDFOpen(pszFilename, GA_ReadOnly);
    5307         103 :         if (poDS == nullptr)
    5308           8 :             return nullptr;
    5309          95 :         char **papszMD = CSLDuplicate(poSrcDS->GetMetadata());
    5310          95 :         papszMD = CSLMerge(papszMD, poDS->GetMetadata());
    5311          95 :         const char *pszAOP = CSLFetchNameValue(papszMD, GDALMD_AREA_OR_POINT);
    5312          95 :         if (pszAOP != nullptr && EQUAL(pszAOP, GDALMD_AOP_AREA))
    5313          58 :             papszMD = CSLSetNameValue(papszMD, GDALMD_AREA_OR_POINT, nullptr);
    5314          95 :         poDS->SetMetadata(papszMD);
    5315          95 :         if (EQUAL(pszGEO_ENCODING, "NONE"))
    5316             :         {
    5317             :             double adfGeoTransform[6];
    5318           6 :             if (poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None)
    5319             :             {
    5320           6 :                 poDS->SetGeoTransform(adfGeoTransform);
    5321             :             }
    5322           6 :             const char *pszProjectionRef = poSrcDS->GetProjectionRef();
    5323           6 :             if (pszProjectionRef != nullptr && pszProjectionRef[0] != '\0')
    5324             :             {
    5325           6 :                 poDS->SetProjection(pszProjectionRef);
    5326             :             }
    5327             :         }
    5328          95 :         CSLDestroy(papszMD);
    5329          95 :         return poDS;
    5330             : #else
    5331             :         return new GDALFakePDFDataset();
    5332             : #endif
    5333             :     }
    5334             : }

Generated by: LCOV version 1.14