• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 13872211292

15 Mar 2025 11:00AM UTC coverage: 70.445% (+0.009%) from 70.436%
13872211292

Pull #11951

github

web-flow
Merge 643845942 into bb4e0ed67
Pull Request #11951: Doc: Build docs using CMake

553795 of 786140 relevant lines covered (70.44%)

221892.63 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.31
/frmts/pdf/pdfcreatecopy.cpp
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
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12

13
#include "gdal_pdf.h"
14
#include "pdfcreatecopy.h"
15

16
#include "cpl_vsi_virtual.h"
17
#include "cpl_conv.h"
18
#include "cpl_error.h"
19
#include "ogr_spatialref.h"
20
#include "ogr_geometry.h"
21
#include "memdataset.h"
22
#include "vrtdataset.h"
23

24
#include "pdfobject.h"
25

26
#include <cmath>
27
#include <algorithm>
28
#include <utility>
29
#include <vector>
30

31
// #define HACK_TO_GENERATE_OCMD can be set to produce a (single layer)
32
// non-structured vector PDF with a OCMD (Optional Content Group Membership
33
// Dictionary) similar to test case of https://github.com/OSGeo/gdal/issues/8372
34
// like with "ogr2ogr poly.pdf poly.shp -dsco STREAM_COMPRESS=NONE -limit 1"
35

36
/************************************************************************/
37
/*                        GDALPDFBaseWriter()                           */
38
/************************************************************************/
39

40
GDALPDFBaseWriter::GDALPDFBaseWriter(VSILFILE *fp) : m_fp(fp)
175✔
41
{
42
}
175✔
43

44
/************************************************************************/
45
/*                       ~GDALPDFBaseWriter()                           */
46
/************************************************************************/
47

48
GDALPDFBaseWriter::~GDALPDFBaseWriter()
175✔
49
{
50
    Close();
175✔
51
}
175✔
52

53
/************************************************************************/
54
/*                              ~Close()                                */
55
/************************************************************************/
56

57
void GDALPDFBaseWriter::Close()
487✔
58
{
59
    if (m_fp)
487✔
60
    {
61
        VSIFCloseL(m_fp);
175✔
62
        m_fp = nullptr;
175✔
63
    }
64
}
487✔
65

66
/************************************************************************/
67
/*                           GDALPDFUpdateWriter()                      */
68
/************************************************************************/
69

70
GDALPDFUpdateWriter::GDALPDFUpdateWriter(VSILFILE *fp) : GDALPDFBaseWriter(fp)
23✔
71
{
72
}
23✔
73

74
/************************************************************************/
75
/*                          ~GDALPDFUpdateWriter()                      */
76
/************************************************************************/
77

78
GDALPDFUpdateWriter::~GDALPDFUpdateWriter()
23✔
79
{
80
    Close();
23✔
81
}
23✔
82

83
/************************************************************************/
84
/*                              ~Close()                                */
85
/************************************************************************/
86

87
void GDALPDFUpdateWriter::Close()
46✔
88
{
89
    if (m_fp)
46✔
90
    {
91
        CPLAssert(!m_bInWriteObj);
23✔
92
        if (m_bUpdateNeeded)
23✔
93
        {
94
            WriteXRefTableAndTrailer(true, m_nLastStartXRef);
23✔
95
        }
96
    }
97
    GDALPDFBaseWriter::Close();
46✔
98
}
46✔
99

100
/************************************************************************/
101
/*                          StartNewDoc()                               */
102
/************************************************************************/
103

104
void GDALPDFBaseWriter::StartNewDoc()
152✔
105
{
106
    VSIFPrintfL(m_fp, "%%PDF-1.6\n");
152✔
107

108
    // See PDF 1.7 reference, page 92. Write 4 non-ASCII bytes to indicate
109
    // that the content will be binary.
110
    VSIFPrintfL(m_fp, "%%%c%c%c%c\n", 0xFF, 0xFF, 0xFF, 0xFF);
152✔
111

112
    m_nPageResourceId = AllocNewObject();
152✔
113
    m_nCatalogId = AllocNewObject();
152✔
114
}
152✔
115

116
/************************************************************************/
117
/*                         GDALPDFWriter()                              */
118
/************************************************************************/
119

120
GDALPDFWriter::GDALPDFWriter(VSILFILE *fpIn) : GDALPDFBaseWriter(fpIn)
114✔
121
{
122
    StartNewDoc();
114✔
123
}
114✔
124

125
/************************************************************************/
126
/*                         ~GDALPDFWriter()                             */
127
/************************************************************************/
128

129
GDALPDFWriter::~GDALPDFWriter()
114✔
130
{
131
    Close();
114✔
132
}
114✔
133

134
/************************************************************************/
135
/*                          ParseIndirectRef()                          */
136
/************************************************************************/
137

138
static int ParseIndirectRef(const char *pszStr, GDALPDFObjectNum &nNum,
27✔
139
                            int &nGen)
140
{
141
    while (*pszStr == ' ')
27✔
142
        pszStr++;
×
143

144
    nNum = atoi(pszStr);
27✔
145
    while (*pszStr >= '0' && *pszStr <= '9')
58✔
146
        pszStr++;
31✔
147
    if (*pszStr != ' ')
27✔
148
        return FALSE;
×
149

150
    while (*pszStr == ' ')
54✔
151
        pszStr++;
27✔
152

153
    nGen = atoi(pszStr);
27✔
154
    while (*pszStr >= '0' && *pszStr <= '9')
54✔
155
        pszStr++;
27✔
156
    if (*pszStr != ' ')
27✔
157
        return FALSE;
×
158

159
    while (*pszStr == ' ')
54✔
160
        pszStr++;
27✔
161

162
    return *pszStr == 'R';
27✔
163
}
164

165
/************************************************************************/
166
/*                       ParseTrailerAndXRef()                          */
167
/************************************************************************/
168

169
int GDALPDFUpdateWriter::ParseTrailerAndXRef()
23✔
170
{
171
    VSIFSeekL(m_fp, 0, SEEK_END);
23✔
172
    char szBuf[1024 + 1];
173
    vsi_l_offset nOffset = VSIFTellL(m_fp);
23✔
174

175
    if (nOffset > 128)
23✔
176
        nOffset -= 128;
23✔
177
    else
178
        nOffset = 0;
×
179

180
    /* Find startxref section */
181
    VSIFSeekL(m_fp, nOffset, SEEK_SET);
23✔
182
    int nRead = static_cast<int>(VSIFReadL(szBuf, 1, 128, m_fp));
23✔
183
    szBuf[nRead] = 0;
23✔
184
    if (nRead < 9)
23✔
185
        return FALSE;
×
186

187
    const char *pszStartXRef = nullptr;
23✔
188
    int i;
189
    for (i = nRead - 9; i >= 0; i--)
297✔
190
    {
191
        if (STARTS_WITH(szBuf + i, "startxref"))
297✔
192
        {
193
            pszStartXRef = szBuf + i;
23✔
194
            break;
23✔
195
        }
196
    }
197
    if (pszStartXRef == nullptr)
23✔
198
    {
199
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find startxref");
×
200
        return FALSE;
×
201
    }
202
    pszStartXRef += 9;
23✔
203
    while (*pszStartXRef == '\r' || *pszStartXRef == '\n')
46✔
204
        pszStartXRef++;
23✔
205
    if (*pszStartXRef == '\0')
23✔
206
    {
207
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find startxref");
×
208
        return FALSE;
×
209
    }
210

211
    m_nLastStartXRef = CPLScanUIntBig(pszStartXRef, 16);
23✔
212

213
    /* Skip to beginning of xref section */
214
    VSIFSeekL(m_fp, m_nLastStartXRef, SEEK_SET);
23✔
215

216
    /* And skip to trailer */
217
    const char *pszLine = nullptr;
23✔
218
    while ((pszLine = CPLReadLineL(m_fp)) != nullptr)
258✔
219
    {
220
        if (STARTS_WITH(pszLine, "trailer"))
258✔
221
            break;
23✔
222
    }
223

224
    if (pszLine == nullptr)
23✔
225
    {
226
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find trailer");
×
227
        return FALSE;
×
228
    }
229

230
    /* Read trailer content */
231
    nRead = static_cast<int>(VSIFReadL(szBuf, 1, 1024, m_fp));
23✔
232
    szBuf[nRead] = 0;
23✔
233

234
    /* Find XRef size */
235
    const char *pszSize = strstr(szBuf, "/Size");
23✔
236
    if (pszSize == nullptr)
23✔
237
    {
238
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find trailer /Size");
×
239
        return FALSE;
×
240
    }
241
    pszSize += 5;
23✔
242
    while (*pszSize == ' ')
46✔
243
        pszSize++;
23✔
244
    m_nLastXRefSize = atoi(pszSize);
23✔
245

246
    /* Find Root object */
247
    const char *pszRoot = strstr(szBuf, "/Root");
23✔
248
    if (pszRoot == nullptr)
23✔
249
    {
250
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find trailer /Root");
×
251
        return FALSE;
×
252
    }
253
    pszRoot += 5;
23✔
254
    while (*pszRoot == ' ')
46✔
255
        pszRoot++;
23✔
256

257
    if (!ParseIndirectRef(pszRoot, m_nCatalogId, m_nCatalogGen))
23✔
258
    {
259
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot parse trailer /Root");
×
260
        return FALSE;
×
261
    }
262

263
    /* Find Info object */
264
    const char *pszInfo = strstr(szBuf, "/Info");
23✔
265
    if (pszInfo != nullptr)
23✔
266
    {
267
        pszInfo += 5;
4✔
268
        while (*pszInfo == ' ')
8✔
269
            pszInfo++;
4✔
270

271
        if (!ParseIndirectRef(pszInfo, m_nInfoId, m_nInfoGen))
4✔
272
        {
273
            CPLError(CE_Failure, CPLE_AppDefined, "Cannot parse trailer /Info");
×
274
            m_nInfoId = 0;
×
275
            m_nInfoGen = 0;
×
276
        }
277
    }
278

279
    VSIFSeekL(m_fp, 0, SEEK_END);
23✔
280

281
    return TRUE;
23✔
282
}
283

284
/************************************************************************/
285
/*                              Close()                                 */
286
/************************************************************************/
287

288
void GDALPDFWriter::Close()
228✔
289
{
290
    if (m_fp)
228✔
291
    {
292
        CPLAssert(!m_bInWriteObj);
114✔
293
        if (m_nPageResourceId.toBool())
114✔
294
        {
295
            WritePages();
114✔
296
            WriteXRefTableAndTrailer(false, 0);
114✔
297
        }
298
    }
299
    GDALPDFBaseWriter::Close();
228✔
300
}
228✔
301

302
/************************************************************************/
303
/*                           UpdateProj()                               */
304
/************************************************************************/
305

306
void GDALPDFUpdateWriter::UpdateProj(GDALDataset *poSrcDS, double dfDPI,
11✔
307
                                     GDALPDFDictionaryRW *poPageDict,
308
                                     const GDALPDFObjectNum &nPageId,
309
                                     int nPageGen)
310
{
311
    m_bUpdateNeeded = true;
11✔
312
    if (static_cast<int>(m_asXRefEntries.size()) < m_nLastXRefSize - 1)
11✔
313
        m_asXRefEntries.resize(m_nLastXRefSize - 1);
11✔
314

315
    GDALPDFObjectNum nViewportId;
11✔
316
    GDALPDFObjectNum nLGIDictId;
11✔
317

318
    CPLAssert(nPageId.toBool());
11✔
319
    CPLAssert(poPageDict != nullptr);
11✔
320

321
    PDFMargins sMargins;
11✔
322

323
    const char *pszGEO_ENCODING =
324
        CPLGetConfigOption("GDAL_PDF_GEO_ENCODING", "ISO32000");
11✔
325
    if (EQUAL(pszGEO_ENCODING, "ISO32000") || EQUAL(pszGEO_ENCODING, "BOTH"))
11✔
326
        nViewportId = WriteSRS_ISO32000(poSrcDS, dfDPI * USER_UNIT_IN_INCH,
327
                                        nullptr, &sMargins, TRUE);
11✔
328

329
#ifdef invalidate_xref_entry
330
    GDALPDFObject *poVP = poPageDict->Get("VP");
331
    if (poVP)
332
    {
333
        if (poVP->GetType() == PDFObjectType_Array &&
334
            poVP->GetArray()->GetLength() == 1)
335
            poVP = poVP->GetArray()->Get(0);
336

337
        int nVPId = poVP->GetRefNum();
338
        if (nVPId)
339
        {
340
            m_asXRefEntries[nVPId - 1].bFree = TRUE;
341
            m_asXRefEntries[nVPId - 1].nGen++;
342
        }
343
    }
344
#endif
345

346
    poPageDict->Remove("VP");
11✔
347
    poPageDict->Remove("LGIDict");
11✔
348

349
    if (nViewportId.toBool())
11✔
350
    {
351
        poPageDict->Add("VP", &((new GDALPDFArrayRW())->Add(nViewportId, 0)));
9✔
352
    }
353

354
    if (nLGIDictId.toBool())
11✔
355
    {
356
        poPageDict->Add("LGIDict", nLGIDictId, 0);
×
357
    }
358

359
    StartObj(nPageId, nPageGen);
11✔
360
    VSIFPrintfL(m_fp, "%s\n", poPageDict->Serialize().c_str());
11✔
361
    EndObj();
11✔
362
}
11✔
363

364
/************************************************************************/
365
/*                           UpdateInfo()                               */
366
/************************************************************************/
367

368
void GDALPDFUpdateWriter::UpdateInfo(GDALDataset *poSrcDS)
6✔
369
{
370
    m_bUpdateNeeded = true;
6✔
371
    if (static_cast<int>(m_asXRefEntries.size()) < m_nLastXRefSize - 1)
6✔
372
        m_asXRefEntries.resize(m_nLastXRefSize - 1);
6✔
373

374
    auto nNewInfoId = SetInfo(poSrcDS, nullptr);
6✔
375
    /* Write empty info, because podofo driver will find the dangling info
376
     * instead */
377
    if (!nNewInfoId.toBool() && m_nInfoId.toBool())
6✔
378
    {
379
#ifdef invalidate_xref_entry
380
        m_asXRefEntries[m_nInfoId.toInt() - 1].bFree = TRUE;
381
        m_asXRefEntries[m_nInfoId.toInt() - 1].nGen++;
382
#else
383
        StartObj(m_nInfoId, m_nInfoGen);
2✔
384
        VSIFPrintfL(m_fp, "<< >>\n");
2✔
385
        EndObj();
2✔
386
#endif
387
    }
388
}
6✔
389

390
/************************************************************************/
391
/*                           UpdateXMP()                                */
392
/************************************************************************/
393

394
void GDALPDFUpdateWriter::UpdateXMP(GDALDataset *poSrcDS,
6✔
395
                                    GDALPDFDictionaryRW *poCatalogDict)
396
{
397
    m_bUpdateNeeded = true;
6✔
398
    if (static_cast<int>(m_asXRefEntries.size()) < m_nLastXRefSize - 1)
6✔
399
        m_asXRefEntries.resize(m_nLastXRefSize - 1);
6✔
400

401
    CPLAssert(m_nCatalogId.toBool());
6✔
402
    CPLAssert(poCatalogDict != nullptr);
6✔
403

404
    GDALPDFObject *poMetadata = poCatalogDict->Get("Metadata");
6✔
405
    if (poMetadata)
6✔
406
    {
407
        m_nXMPId = poMetadata->GetRefNum();
4✔
408
        m_nXMPGen = poMetadata->GetRefGen();
4✔
409
    }
410

411
    poCatalogDict->Remove("Metadata");
6✔
412
    auto nNewXMPId = SetXMP(poSrcDS, nullptr);
6✔
413

414
    /* Write empty metadata, because podofo driver will find the dangling info
415
     * instead */
416
    if (!nNewXMPId.toBool() && m_nXMPId.toBool())
6✔
417
    {
418
        StartObj(m_nXMPId, m_nXMPGen);
2✔
419
        VSIFPrintfL(m_fp, "<< >>\n");
2✔
420
        EndObj();
2✔
421
    }
422

423
    if (m_nXMPId.toBool())
6✔
424
        poCatalogDict->Add("Metadata", m_nXMPId, 0);
6✔
425

426
    StartObj(m_nCatalogId, m_nCatalogGen);
6✔
427
    VSIFPrintfL(m_fp, "%s\n", poCatalogDict->Serialize().c_str());
6✔
428
    EndObj();
6✔
429
}
6✔
430

431
/************************************************************************/
432
/*                           AllocNewObject()                           */
433
/************************************************************************/
434

435
GDALPDFObjectNum GDALPDFBaseWriter::AllocNewObject()
2,430✔
436
{
437
    m_asXRefEntries.push_back(GDALXRefEntry());
2,430✔
438
    return GDALPDFObjectNum(static_cast<int>(m_asXRefEntries.size()));
2,430✔
439
}
440

441
/************************************************************************/
442
/*                        WriteXRefTableAndTrailer()                    */
443
/************************************************************************/
444

445
void GDALPDFBaseWriter::WriteXRefTableAndTrailer(bool bUpdate,
175✔
446
                                                 vsi_l_offset nLastStartXRef)
447
{
448
    vsi_l_offset nOffsetXREF = VSIFTellL(m_fp);
175✔
449
    VSIFPrintfL(m_fp, "xref\n");
175✔
450

451
    char buffer[16];
452
    if (bUpdate)
175✔
453
    {
454
        VSIFPrintfL(m_fp, "0 1\n");
23✔
455
        VSIFPrintfL(m_fp, "0000000000 65535 f \n");
23✔
456
        for (size_t i = 0; i < m_asXRefEntries.size();)
326✔
457
        {
458
            if (m_asXRefEntries[i].nOffset != 0 || m_asXRefEntries[i].bFree)
303✔
459
            {
460
                /* Find number of consecutive objects */
461
                size_t nCount = 1;
38✔
462
                while (i + nCount < m_asXRefEntries.size() &&
91✔
463
                       (m_asXRefEntries[i + nCount].nOffset != 0 ||
35✔
464
                        m_asXRefEntries[i + nCount].bFree))
17✔
465
                    nCount++;
18✔
466

467
                VSIFPrintfL(m_fp, "%d %d\n", static_cast<int>(i) + 1,
38✔
468
                            static_cast<int>(nCount));
469
                size_t iEnd = i + nCount;
38✔
470
                for (; i < iEnd; i++)
94✔
471
                {
472
                    snprintf(buffer, sizeof(buffer),
56✔
473
                             "%010" CPL_FRMT_GB_WITHOUT_PREFIX "u",
474
                             m_asXRefEntries[i].nOffset);
56✔
475
                    VSIFPrintfL(m_fp, "%s %05d %c \n", buffer,
112✔
476
                                m_asXRefEntries[i].nGen,
56✔
477
                                m_asXRefEntries[i].bFree ? 'f' : 'n');
56✔
478
                }
479
            }
480
            else
481
            {
482
                i++;
265✔
483
            }
484
        }
485
    }
486
    else
487
    {
488
        VSIFPrintfL(m_fp, "%d %d\n", 0,
152✔
489
                    static_cast<int>(m_asXRefEntries.size()) + 1);
152✔
490
        VSIFPrintfL(m_fp, "0000000000 65535 f \n");
152✔
491
        for (size_t i = 0; i < m_asXRefEntries.size(); i++)
2,551✔
492
        {
493
            snprintf(buffer, sizeof(buffer),
2,399✔
494
                     "%010" CPL_FRMT_GB_WITHOUT_PREFIX "u",
495
                     m_asXRefEntries[i].nOffset);
2,399✔
496
            VSIFPrintfL(m_fp, "%s %05d n \n", buffer, m_asXRefEntries[i].nGen);
2,399✔
497
        }
498
    }
499

500
    VSIFPrintfL(m_fp, "trailer\n");
175✔
501
    GDALPDFDictionaryRW oDict;
350✔
502
    oDict.Add("Size", static_cast<int>(m_asXRefEntries.size()) + 1)
175✔
503
        .Add("Root", m_nCatalogId, m_nCatalogGen);
175✔
504
    if (m_nInfoId.toBool())
175✔
505
        oDict.Add("Info", m_nInfoId, m_nInfoGen);
11✔
506
    if (nLastStartXRef)
175✔
507
        oDict.Add("Prev", static_cast<double>(nLastStartXRef));
23✔
508
    VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
175✔
509

510
    VSIFPrintfL(m_fp,
175✔
511
                "startxref\n" CPL_FRMT_GUIB "\n"
512
                "%%%%EOF\n",
513
                nOffsetXREF);
514
}
175✔
515

516
/************************************************************************/
517
/*                              StartObj()                              */
518
/************************************************************************/
519

520
void GDALPDFBaseWriter::StartObj(const GDALPDFObjectNum &nObjectId, int nGen)
2,432✔
521
{
522
    CPLAssert(!m_bInWriteObj);
2,432✔
523
    CPLAssert(nObjectId.toInt() - 1 < static_cast<int>(m_asXRefEntries.size()));
2,432✔
524
    CPLAssert(m_asXRefEntries[nObjectId.toInt() - 1].nOffset == 0);
2,432✔
525
    m_asXRefEntries[nObjectId.toInt() - 1].nOffset = VSIFTellL(m_fp);
2,432✔
526
    m_asXRefEntries[nObjectId.toInt() - 1].nGen = nGen;
2,432✔
527
    VSIFPrintfL(m_fp, "%d %d obj\n", nObjectId.toInt(), nGen);
2,432✔
528
    m_bInWriteObj = true;
2,432✔
529
}
2,432✔
530

531
/************************************************************************/
532
/*                               EndObj()                               */
533
/************************************************************************/
534

535
void GDALPDFBaseWriter::EndObj()
2,432✔
536
{
537
    CPLAssert(m_bInWriteObj);
2,432✔
538
    CPLAssert(!m_fpBack);
2,432✔
539
    VSIFPrintfL(m_fp, "endobj\n");
2,432✔
540
    m_bInWriteObj = false;
2,432✔
541
}
2,432✔
542

543
/************************************************************************/
544
/*                         StartObjWithStream()                         */
545
/************************************************************************/
546

547
void GDALPDFBaseWriter::StartObjWithStream(const GDALPDFObjectNum &nObjectId,
562✔
548
                                           GDALPDFDictionaryRW &oDict,
549
                                           bool bDeflate)
550
{
551
    CPLAssert(!m_nContentLengthId.toBool());
562✔
552
    CPLAssert(!m_fpGZip);
562✔
553
    CPLAssert(!m_fpBack);
562✔
554
    CPLAssert(m_nStreamStart == 0);
562✔
555

556
    m_nContentLengthId = AllocNewObject();
562✔
557

558
    StartObj(nObjectId);
562✔
559
    {
560
        oDict.Add("Length", m_nContentLengthId, 0);
562✔
561
        if (bDeflate)
562✔
562
        {
563
            oDict.Add("Filter", GDALPDFObjectRW::CreateName("FlateDecode"));
425✔
564
        }
565
        VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
562✔
566
    }
567

568
    /* -------------------------------------------------------------- */
569
    /*  Write content stream                                          */
570
    /* -------------------------------------------------------------- */
571
    VSIFPrintfL(m_fp, "stream\n");
562✔
572
    m_nStreamStart = VSIFTellL(m_fp);
562✔
573

574
    m_fpGZip = nullptr;
562✔
575
    m_fpBack = m_fp;
562✔
576
    if (bDeflate)
562✔
577
    {
578
        m_fpGZip = VSICreateGZipWritable(m_fp, TRUE, FALSE);
425✔
579
        m_fp = m_fpGZip;
425✔
580
    }
581
}
562✔
582

583
/************************************************************************/
584
/*                          EndObjWithStream()                          */
585
/************************************************************************/
586

587
void GDALPDFBaseWriter::EndObjWithStream()
562✔
588
{
589
    if (m_fpGZip)
562✔
590
        VSIFCloseL(m_fpGZip);
425✔
591
    m_fp = m_fpBack;
562✔
592
    m_fpBack = nullptr;
562✔
593

594
    vsi_l_offset nStreamEnd = VSIFTellL(m_fp);
562✔
595
    if (m_fpGZip)
562✔
596
        VSIFPrintfL(m_fp, "\n");
425✔
597
    m_fpGZip = nullptr;
562✔
598
    VSIFPrintfL(m_fp, "endstream\n");
562✔
599
    EndObj();
562✔
600

601
    StartObj(m_nContentLengthId);
562✔
602
    VSIFPrintfL(m_fp, "   %ld\n",
562✔
603
                static_cast<long>(nStreamEnd - m_nStreamStart));
562✔
604
    EndObj();
562✔
605

606
    m_nContentLengthId = GDALPDFObjectNum();
562✔
607
    m_nStreamStart = 0;
562✔
608
}
562✔
609

610
/************************************************************************/
611
/*                         GDALPDFFind4Corners()                        */
612
/************************************************************************/
613

614
static void GDALPDFFind4Corners(const GDAL_GCP *pasGCPList, int &iUL, int &iUR,
18✔
615
                                int &iLR, int &iLL)
616
{
617
    double dfMeanX = 0.0;
18✔
618
    double dfMeanY = 0.0;
18✔
619
    int i;
620

621
    iUL = 0;
18✔
622
    iUR = 0;
18✔
623
    iLR = 0;
18✔
624
    iLL = 0;
18✔
625

626
    for (i = 0; i < 4; i++)
90✔
627
    {
628
        dfMeanX += pasGCPList[i].dfGCPPixel;
72✔
629
        dfMeanY += pasGCPList[i].dfGCPLine;
72✔
630
    }
631
    dfMeanX /= 4;
18✔
632
    dfMeanY /= 4;
18✔
633

634
    for (i = 0; i < 4; i++)
90✔
635
    {
636
        if (pasGCPList[i].dfGCPPixel < dfMeanX &&
72✔
637
            pasGCPList[i].dfGCPLine < dfMeanY)
36✔
638
            iUL = i;
18✔
639

640
        else if (pasGCPList[i].dfGCPPixel > dfMeanX &&
54✔
641
                 pasGCPList[i].dfGCPLine < dfMeanY)
36✔
642
            iUR = i;
18✔
643

644
        else if (pasGCPList[i].dfGCPPixel > dfMeanX &&
36✔
645
                 pasGCPList[i].dfGCPLine > dfMeanY)
18✔
646
            iLR = i;
18✔
647

648
        else if (pasGCPList[i].dfGCPPixel < dfMeanX &&
18✔
649
                 pasGCPList[i].dfGCPLine > dfMeanY)
18✔
650
            iLL = i;
18✔
651
    }
652
}
18✔
653

654
/************************************************************************/
655
/*                         WriteSRS_ISO32000()                          */
656
/************************************************************************/
657

658
GDALPDFObjectNum GDALPDFBaseWriter::WriteSRS_ISO32000(GDALDataset *poSrcDS,
121✔
659
                                                      double dfUserUnit,
660
                                                      const char *pszNEATLINE,
661
                                                      PDFMargins *psMargins,
662
                                                      int bWriteViewport)
663
{
664
    int nWidth = poSrcDS->GetRasterXSize();
121✔
665
    int nHeight = poSrcDS->GetRasterYSize();
121✔
666
    const char *pszWKT = poSrcDS->GetProjectionRef();
121✔
667
    double adfGeoTransform[6];
668

669
    int bHasGT = (poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None);
121✔
670
    const GDAL_GCP *pasGCPList =
671
        (poSrcDS->GetGCPCount() == 4) ? poSrcDS->GetGCPs() : nullptr;
121✔
672
    if (pasGCPList != nullptr)
121✔
673
        pszWKT = poSrcDS->GetGCPProjection();
2✔
674

675
    if (!bHasGT && pasGCPList == nullptr)
121✔
676
        return GDALPDFObjectNum();
20✔
677

678
    if (pszWKT == nullptr || EQUAL(pszWKT, ""))
101✔
679
        return GDALPDFObjectNum();
17✔
680

681
    double adfGPTS[8];
682

683
    double dfULPixel = 0;
84✔
684
    double dfULLine = 0;
84✔
685
    double dfLRPixel = nWidth;
84✔
686
    double dfLRLine = nHeight;
84✔
687

688
    std::vector<gdal::GCP> asNeatLineGCPs(4);
168✔
689
    if (pszNEATLINE == nullptr)
84✔
690
        pszNEATLINE = poSrcDS->GetMetadataItem("NEATLINE");
82✔
691
    if (bHasGT && pszNEATLINE != nullptr && pszNEATLINE[0] != '\0')
84✔
692
    {
693
        auto [poGeom, _] = OGRGeometryFactory::createFromWkt(pszNEATLINE);
16✔
694
        if (poGeom != nullptr &&
16✔
695
            wkbFlatten(poGeom->getGeometryType()) == wkbPolygon)
8✔
696
        {
697
            OGRLineString *poLS = poGeom->toPolygon()->getExteriorRing();
8✔
698
            double adfGeoTransformInv[6];
699
            if (poLS != nullptr && poLS->getNumPoints() == 5 &&
16✔
700
                GDALInvGeoTransform(adfGeoTransform, adfGeoTransformInv))
8✔
701
            {
702
                for (int i = 0; i < 4; i++)
40✔
703
                {
704
                    const double X = poLS->getX(i);
32✔
705
                    const double Y = poLS->getY(i);
32✔
706
                    asNeatLineGCPs[i].X() = X;
32✔
707
                    asNeatLineGCPs[i].Y() = Y;
32✔
708
                    const double x = adfGeoTransformInv[0] +
32✔
709
                                     X * adfGeoTransformInv[1] +
32✔
710
                                     Y * adfGeoTransformInv[2];
32✔
711
                    const double y = adfGeoTransformInv[3] +
32✔
712
                                     X * adfGeoTransformInv[4] +
32✔
713
                                     Y * adfGeoTransformInv[5];
32✔
714
                    asNeatLineGCPs[i].Pixel() = x;
32✔
715
                    asNeatLineGCPs[i].Line() = y;
32✔
716
                }
717

718
                int iUL = 0;
8✔
719
                int iUR = 0;
8✔
720
                int iLR = 0;
8✔
721
                int iLL = 0;
8✔
722
                GDALPDFFind4Corners(gdal::GCP::c_ptr(asNeatLineGCPs), iUL, iUR,
8✔
723
                                    iLR, iLL);
724

725
                if (fabs(asNeatLineGCPs[iUL].Pixel() -
8✔
726
                         asNeatLineGCPs[iLL].Pixel()) > .5 ||
8✔
727
                    fabs(asNeatLineGCPs[iUR].Pixel() -
8✔
728
                         asNeatLineGCPs[iLR].Pixel()) > .5 ||
8✔
729
                    fabs(asNeatLineGCPs[iUL].Line() -
8✔
730
                         asNeatLineGCPs[iUR].Line()) > .5 ||
24✔
731
                    fabs(asNeatLineGCPs[iLL].Line() -
8✔
732
                         asNeatLineGCPs[iLR].Line()) > .5)
8✔
733
                {
734
                    CPLError(CE_Warning, CPLE_NotSupported,
×
735
                             "Neatline coordinates should form a rectangle in "
736
                             "pixel space. Ignoring it");
737
                    for (int i = 0; i < 4; i++)
×
738
                    {
739
                        CPLDebug("PDF", "pixel[%d] = %.1f, line[%d] = %.1f", i,
×
740
                                 asNeatLineGCPs[i].Pixel(), i,
×
741
                                 asNeatLineGCPs[i].Line());
×
742
                    }
743
                }
744
                else
745
                {
746
                    pasGCPList = gdal::GCP::c_ptr(asNeatLineGCPs);
8✔
747
                }
748
            }
749
        }
750
    }
751

752
    if (pasGCPList)
84✔
753
    {
754
        int iUL = 0;
10✔
755
        int iUR = 0;
10✔
756
        int iLR = 0;
10✔
757
        int iLL = 0;
10✔
758
        GDALPDFFind4Corners(pasGCPList, iUL, iUR, iLR, iLL);
10✔
759

760
        if (fabs(pasGCPList[iUL].dfGCPPixel - pasGCPList[iLL].dfGCPPixel) >
10✔
761
                .5 ||
10✔
762
            fabs(pasGCPList[iUR].dfGCPPixel - pasGCPList[iLR].dfGCPPixel) >
10✔
763
                .5 ||
10✔
764
            fabs(pasGCPList[iUL].dfGCPLine - pasGCPList[iUR].dfGCPLine) > .5 ||
10✔
765
            fabs(pasGCPList[iLL].dfGCPLine - pasGCPList[iLR].dfGCPLine) > .5)
10✔
766
        {
767
            CPLError(CE_Failure, CPLE_NotSupported,
×
768
                     "GCPs should form a rectangle in pixel space");
769
            return GDALPDFObjectNum();
×
770
        }
771

772
        dfULPixel = pasGCPList[iUL].dfGCPPixel;
10✔
773
        dfULLine = pasGCPList[iUL].dfGCPLine;
10✔
774
        dfLRPixel = pasGCPList[iLR].dfGCPPixel;
10✔
775
        dfLRLine = pasGCPList[iLR].dfGCPLine;
10✔
776

777
        /* Upper-left */
778
        adfGPTS[0] = pasGCPList[iUL].dfGCPX;
10✔
779
        adfGPTS[1] = pasGCPList[iUL].dfGCPY;
10✔
780

781
        /* Lower-left */
782
        adfGPTS[2] = pasGCPList[iLL].dfGCPX;
10✔
783
        adfGPTS[3] = pasGCPList[iLL].dfGCPY;
10✔
784

785
        /* Lower-right */
786
        adfGPTS[4] = pasGCPList[iLR].dfGCPX;
10✔
787
        adfGPTS[5] = pasGCPList[iLR].dfGCPY;
10✔
788

789
        /* Upper-right */
790
        adfGPTS[6] = pasGCPList[iUR].dfGCPX;
10✔
791
        adfGPTS[7] = pasGCPList[iUR].dfGCPY;
10✔
792
    }
793
    else
794
    {
795
        /* Upper-left */
796
        adfGPTS[0] = APPLY_GT_X(adfGeoTransform, 0, 0);
74✔
797
        adfGPTS[1] = APPLY_GT_Y(adfGeoTransform, 0, 0);
74✔
798

799
        /* Lower-left */
800
        adfGPTS[2] = APPLY_GT_X(adfGeoTransform, 0, nHeight);
74✔
801
        adfGPTS[3] = APPLY_GT_Y(adfGeoTransform, 0, nHeight);
74✔
802

803
        /* Lower-right */
804
        adfGPTS[4] = APPLY_GT_X(adfGeoTransform, nWidth, nHeight);
74✔
805
        adfGPTS[5] = APPLY_GT_Y(adfGeoTransform, nWidth, nHeight);
74✔
806

807
        /* Upper-right */
808
        adfGPTS[6] = APPLY_GT_X(adfGeoTransform, nWidth, 0);
74✔
809
        adfGPTS[7] = APPLY_GT_Y(adfGeoTransform, nWidth, 0);
74✔
810
    }
811

812
    OGRSpatialReferenceH hSRS = OSRNewSpatialReference(pszWKT);
84✔
813
    if (hSRS == nullptr)
84✔
814
        return GDALPDFObjectNum();
×
815
    OSRSetAxisMappingStrategy(hSRS, OAMS_TRADITIONAL_GIS_ORDER);
84✔
816
    OGRSpatialReferenceH hSRSGeog = OSRCloneGeogCS(hSRS);
84✔
817
    if (hSRSGeog == nullptr)
84✔
818
    {
819
        OSRDestroySpatialReference(hSRS);
×
820
        return GDALPDFObjectNum();
×
821
    }
822
    OSRSetAxisMappingStrategy(hSRSGeog, OAMS_TRADITIONAL_GIS_ORDER);
84✔
823
    OGRCoordinateTransformationH hCT =
824
        OCTNewCoordinateTransformation(hSRS, hSRSGeog);
84✔
825
    if (hCT == nullptr)
84✔
826
    {
827
        OSRDestroySpatialReference(hSRS);
×
828
        OSRDestroySpatialReference(hSRSGeog);
×
829
        return GDALPDFObjectNum();
×
830
    }
831

832
    int bSuccess = TRUE;
84✔
833

834
    bSuccess &= (OCTTransform(hCT, 1, adfGPTS + 0, adfGPTS + 1, nullptr) == 1);
84✔
835
    bSuccess &= (OCTTransform(hCT, 1, adfGPTS + 2, adfGPTS + 3, nullptr) == 1);
84✔
836
    bSuccess &= (OCTTransform(hCT, 1, adfGPTS + 4, adfGPTS + 5, nullptr) == 1);
84✔
837
    bSuccess &= (OCTTransform(hCT, 1, adfGPTS + 6, adfGPTS + 7, nullptr) == 1);
84✔
838

839
    if (!bSuccess)
84✔
840
    {
841
        OSRDestroySpatialReference(hSRS);
×
842
        OSRDestroySpatialReference(hSRSGeog);
×
843
        OCTDestroyCoordinateTransformation(hCT);
×
844
        return GDALPDFObjectNum();
×
845
    }
846

847
    const char *pszAuthorityCode = OSRGetAuthorityCode(hSRS, nullptr);
84✔
848
    const char *pszAuthorityName = OSRGetAuthorityName(hSRS, nullptr);
84✔
849
    int nEPSGCode = 0;
84✔
850
    if (pszAuthorityName != nullptr && pszAuthorityCode != nullptr &&
153✔
851
        (EQUAL(pszAuthorityName, "EPSG") ||
69✔
852
         (EQUAL(pszAuthorityName, "ESRI") &&
×
853
          CPLTestBool(
×
854
              CPLGetConfigOption("GDAL_PDF_WRITE_ESRI_CODE_AS_EPSG", "NO")))))
855
    {
856
        nEPSGCode = atoi(pszAuthorityCode);
69✔
857
    }
858

859
    int bIsGeographic = OSRIsGeographic(hSRS);
84✔
860

861
    OSRMorphToESRI(hSRS);
84✔
862
    char *pszESRIWKT = nullptr;
84✔
863
    OSRExportToWkt(hSRS, &pszESRIWKT);
84✔
864

865
    OSRDestroySpatialReference(hSRS);
84✔
866
    OSRDestroySpatialReference(hSRSGeog);
84✔
867
    OCTDestroyCoordinateTransformation(hCT);
84✔
868
    hSRS = nullptr;
84✔
869
    hSRSGeog = nullptr;
84✔
870
    hCT = nullptr;
84✔
871

872
    if (pszESRIWKT == nullptr)
84✔
873
        return GDALPDFObjectNum();
×
874

875
    auto nViewportId = (bWriteViewport) ? AllocNewObject() : GDALPDFObjectNum();
84✔
876
    auto nMeasureId = AllocNewObject();
84✔
877
    auto nGCSId = AllocNewObject();
84✔
878

879
    if (nViewportId.toBool())
84✔
880
    {
881
        StartObj(nViewportId);
84✔
882
        GDALPDFDictionaryRW oViewPortDict;
168✔
883
        oViewPortDict.Add("Type", GDALPDFObjectRW::CreateName("Viewport"))
84✔
884
            .Add("Name", "Layer")
84✔
885
            .Add("BBox", &((new GDALPDFArrayRW())
84✔
886
                               ->Add(dfULPixel / dfUserUnit + psMargins->nLeft)
84✔
887
                               .Add((nHeight - dfLRLine) / dfUserUnit +
84✔
888
                                    psMargins->nBottom)
84✔
889
                               .Add(dfLRPixel / dfUserUnit + psMargins->nLeft)
84✔
890
                               .Add((nHeight - dfULLine) / dfUserUnit +
84✔
891
                                    psMargins->nBottom)))
84✔
892
            .Add("Measure", nMeasureId, 0);
84✔
893
        VSIFPrintfL(m_fp, "%s\n", oViewPortDict.Serialize().c_str());
84✔
894
        EndObj();
84✔
895
    }
896

897
    StartObj(nMeasureId);
84✔
898
    GDALPDFDictionaryRW oMeasureDict;
168✔
899
    oMeasureDict.Add("Type", GDALPDFObjectRW::CreateName("Measure"))
84✔
900
        .Add("Subtype", GDALPDFObjectRW::CreateName("GEO"))
84✔
901
        .Add("Bounds", &((new GDALPDFArrayRW())
84✔
902
                             ->Add(0)
84✔
903
                             .Add(1)
84✔
904
                             .Add(0)
84✔
905
                             .Add(0)
84✔
906
                             .Add(1)
84✔
907
                             .Add(0)
84✔
908
                             .Add(1)
84✔
909
                             .Add(1)))
84✔
910
        .Add("GPTS", &((new GDALPDFArrayRW())
84✔
911
                           ->Add(adfGPTS[1])
84✔
912
                           .Add(adfGPTS[0])
84✔
913
                           .Add(adfGPTS[3])
84✔
914
                           .Add(adfGPTS[2])
84✔
915
                           .Add(adfGPTS[5])
84✔
916
                           .Add(adfGPTS[4])
84✔
917
                           .Add(adfGPTS[7])
84✔
918
                           .Add(adfGPTS[6])))
84✔
919
        .Add("LPTS", &((new GDALPDFArrayRW())
84✔
920
                           ->Add(0)
84✔
921
                           .Add(1)
84✔
922
                           .Add(0)
84✔
923
                           .Add(0)
84✔
924
                           .Add(1)
84✔
925
                           .Add(0)
84✔
926
                           .Add(1)
84✔
927
                           .Add(1)))
84✔
928
        .Add("GCS", nGCSId, 0);
84✔
929
    VSIFPrintfL(m_fp, "%s\n", oMeasureDict.Serialize().c_str());
84✔
930
    EndObj();
84✔
931

932
    StartObj(nGCSId);
84✔
933
    GDALPDFDictionaryRW oGCSDict;
84✔
934
    oGCSDict
935
        .Add("Type",
936
             GDALPDFObjectRW::CreateName(bIsGeographic ? "GEOGCS" : "PROJCS"))
84✔
937
        .Add("WKT", pszESRIWKT);
84✔
938
    if (nEPSGCode)
84✔
939
        oGCSDict.Add("EPSG", nEPSGCode);
69✔
940
    VSIFPrintfL(m_fp, "%s\n", oGCSDict.Serialize().c_str());
84✔
941
    EndObj();
84✔
942

943
    CPLFree(pszESRIWKT);
84✔
944

945
    return nViewportId.toBool() ? nViewportId : nMeasureId;
84✔
946
}
947

948
/************************************************************************/
949
/*                     GDALPDFGetValueFromDSOrOption()                  */
950
/************************************************************************/
951

952
static const char *GDALPDFGetValueFromDSOrOption(GDALDataset *poSrcDS,
826✔
953
                                                 char **papszOptions,
954
                                                 const char *pszKey)
955
{
956
    const char *pszValue = CSLFetchNameValue(papszOptions, pszKey);
826✔
957
    if (pszValue == nullptr)
826✔
958
        pszValue = poSrcDS->GetMetadataItem(pszKey);
814✔
959
    if (pszValue != nullptr && pszValue[0] == '\0')
826✔
960
        return nullptr;
2✔
961
    else
962
        return pszValue;
824✔
963
}
964

965
/************************************************************************/
966
/*                             SetInfo()                                */
967
/************************************************************************/
968

969
GDALPDFObjectNum GDALPDFBaseWriter::SetInfo(GDALDataset *poSrcDS,
118✔
970
                                            char **papszOptions)
971
{
972
    const char *pszAUTHOR =
973
        GDALPDFGetValueFromDSOrOption(poSrcDS, papszOptions, "AUTHOR");
118✔
974
    const char *pszPRODUCER =
975
        GDALPDFGetValueFromDSOrOption(poSrcDS, papszOptions, "PRODUCER");
118✔
976
    const char *pszCREATOR =
977
        GDALPDFGetValueFromDSOrOption(poSrcDS, papszOptions, "CREATOR");
118✔
978
    const char *pszCREATION_DATE =
979
        GDALPDFGetValueFromDSOrOption(poSrcDS, papszOptions, "CREATION_DATE");
118✔
980
    const char *pszSUBJECT =
981
        GDALPDFGetValueFromDSOrOption(poSrcDS, papszOptions, "SUBJECT");
118✔
982
    const char *pszTITLE =
983
        GDALPDFGetValueFromDSOrOption(poSrcDS, papszOptions, "TITLE");
118✔
984
    const char *pszKEYWORDS =
985
        GDALPDFGetValueFromDSOrOption(poSrcDS, papszOptions, "KEYWORDS");
118✔
986
    return SetInfo(pszAUTHOR, pszPRODUCER, pszCREATOR, pszCREATION_DATE,
987
                   pszSUBJECT, pszTITLE, pszKEYWORDS);
118✔
988
}
989

990
/************************************************************************/
991
/*                             SetInfo()                                */
992
/************************************************************************/
993

994
GDALPDFObjectNum
995
GDALPDFBaseWriter::SetInfo(const char *pszAUTHOR, const char *pszPRODUCER,
119✔
996
                           const char *pszCREATOR, const char *pszCREATION_DATE,
997
                           const char *pszSUBJECT, const char *pszTITLE,
998
                           const char *pszKEYWORDS)
999
{
1000
    if (pszAUTHOR == nullptr && pszPRODUCER == nullptr &&
119✔
1001
        pszCREATOR == nullptr && pszCREATION_DATE == nullptr &&
110✔
1002
        pszSUBJECT == nullptr && pszTITLE == nullptr && pszKEYWORDS == nullptr)
110✔
1003
        return GDALPDFObjectNum();
110✔
1004

1005
    if (!m_nInfoId.toBool())
9✔
1006
        m_nInfoId = AllocNewObject();
7✔
1007
    StartObj(m_nInfoId, m_nInfoGen);
9✔
1008
    GDALPDFDictionaryRW oDict;
9✔
1009
    if (pszAUTHOR != nullptr)
9✔
1010
        oDict.Add("Author", pszAUTHOR);
9✔
1011
    if (pszPRODUCER != nullptr)
9✔
1012
        oDict.Add("Producer", pszPRODUCER);
4✔
1013
    if (pszCREATOR != nullptr)
9✔
1014
        oDict.Add("Creator", pszCREATOR);
4✔
1015
    if (pszCREATION_DATE != nullptr)
9✔
1016
        oDict.Add("CreationDate", pszCREATION_DATE);
×
1017
    if (pszSUBJECT != nullptr)
9✔
1018
        oDict.Add("Subject", pszSUBJECT);
4✔
1019
    if (pszTITLE != nullptr)
9✔
1020
        oDict.Add("Title", pszTITLE);
4✔
1021
    if (pszKEYWORDS != nullptr)
9✔
1022
        oDict.Add("Keywords", pszKEYWORDS);
4✔
1023
    VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
9✔
1024
    EndObj();
9✔
1025

1026
    return m_nInfoId;
9✔
1027
}
1028

1029
/************************************************************************/
1030
/*                             SetXMP()                                 */
1031
/************************************************************************/
1032

1033
GDALPDFObjectNum GDALPDFBaseWriter::SetXMP(GDALDataset *poSrcDS,
99✔
1034
                                           const char *pszXMP)
1035
{
1036
    if (pszXMP != nullptr && STARTS_WITH_CI(pszXMP, "NO"))
99✔
1037
        return GDALPDFObjectNum();
2✔
1038
    if (pszXMP != nullptr && pszXMP[0] == '\0')
97✔
1039
        return GDALPDFObjectNum();
×
1040

1041
    if (poSrcDS && pszXMP == nullptr)
97✔
1042
    {
1043
        char **papszXMP = poSrcDS->GetMetadata("xml:XMP");
96✔
1044
        if (papszXMP != nullptr && papszXMP[0] != nullptr)
96✔
1045
            pszXMP = papszXMP[0];
6✔
1046
    }
1047

1048
    if (pszXMP == nullptr)
97✔
1049
        return GDALPDFObjectNum();
90✔
1050

1051
    CPLXMLNode *psNode = CPLParseXMLString(pszXMP);
7✔
1052
    if (psNode == nullptr)
7✔
1053
        return GDALPDFObjectNum();
×
1054
    CPLDestroyXMLNode(psNode);
7✔
1055

1056
    if (!m_nXMPId.toBool())
7✔
1057
        m_nXMPId = AllocNewObject();
5✔
1058
    StartObj(m_nXMPId, m_nXMPGen);
7✔
1059
    GDALPDFDictionaryRW oDict;
7✔
1060
    oDict.Add("Type", GDALPDFObjectRW::CreateName("Metadata"))
7✔
1061
        .Add("Subtype", GDALPDFObjectRW::CreateName("XML"))
7✔
1062
        .Add("Length", static_cast<int>(strlen(pszXMP)));
7✔
1063
    VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
7✔
1064
    VSIFPrintfL(m_fp, "stream\n");
7✔
1065
    VSIFPrintfL(m_fp, "%s\n", pszXMP);
7✔
1066
    VSIFPrintfL(m_fp, "endstream\n");
7✔
1067
    EndObj();
7✔
1068
    return m_nXMPId;
7✔
1069
}
1070

1071
/************************************************************************/
1072
/*                              WriteOCG()                              */
1073
/************************************************************************/
1074

1075
GDALPDFObjectNum GDALPDFBaseWriter::WriteOCG(const char *pszLayerName,
267✔
1076
                                             const GDALPDFObjectNum &nParentId)
1077
{
1078
    if (pszLayerName == nullptr || pszLayerName[0] == '\0')
267✔
1079
        return GDALPDFObjectNum();
198✔
1080

1081
    auto nOCGId = AllocNewObject();
69✔
1082

1083
    GDALPDFOCGDesc oOCGDesc;
69✔
1084
    oOCGDesc.nId = nOCGId;
69✔
1085
    oOCGDesc.nParentId = nParentId;
69✔
1086
    oOCGDesc.osLayerName = pszLayerName;
69✔
1087

1088
    m_asOCGs.push_back(oOCGDesc);
69✔
1089

1090
    StartObj(nOCGId);
69✔
1091
    {
1092
        GDALPDFDictionaryRW oDict;
69✔
1093
        oDict.Add("Type", GDALPDFObjectRW::CreateName("OCG"));
69✔
1094
        oDict.Add("Name", pszLayerName);
69✔
1095
        VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
69✔
1096
    }
1097
    EndObj();
69✔
1098

1099
    return nOCGId;
69✔
1100
}
1101

1102
/************************************************************************/
1103
/*                              StartPage()                             */
1104
/************************************************************************/
1105

1106
bool GDALPDFWriter::StartPage(GDALDataset *poClippingDS, double dfDPI,
114✔
1107
                              bool bWriteUserUnit, const char *pszGEO_ENCODING,
1108
                              const char *pszNEATLINE, PDFMargins *psMargins,
1109
                              PDFCompressMethod eStreamCompressMethod,
1110
                              int bHasOGRData)
1111
{
1112
    int nWidth = poClippingDS->GetRasterXSize();
114✔
1113
    int nHeight = poClippingDS->GetRasterYSize();
114✔
1114
    int nBands = poClippingDS->GetRasterCount();
114✔
1115

1116
    double dfUserUnit = dfDPI * USER_UNIT_IN_INCH;
114✔
1117
    double dfWidthInUserUnit =
114✔
1118
        nWidth / dfUserUnit + psMargins->nLeft + psMargins->nRight;
114✔
1119
    double dfHeightInUserUnit =
114✔
1120
        nHeight / dfUserUnit + psMargins->nBottom + psMargins->nTop;
114✔
1121

1122
    auto nPageId = AllocNewObject();
114✔
1123
    m_asPageId.push_back(nPageId);
114✔
1124

1125
    auto nContentId = AllocNewObject();
114✔
1126
    auto nResourcesId = AllocNewObject();
114✔
1127

1128
    auto nAnnotsId = AllocNewObject();
114✔
1129

1130
    const bool bISO32000 =
114✔
1131
        EQUAL(pszGEO_ENCODING, "ISO32000") || EQUAL(pszGEO_ENCODING, "BOTH");
114✔
1132

1133
    GDALPDFObjectNum nViewportId;
114✔
1134
    if (bISO32000)
114✔
1135
        nViewportId = WriteSRS_ISO32000(poClippingDS, dfUserUnit, pszNEATLINE,
1136
                                        psMargins, TRUE);
110✔
1137

1138
    StartObj(nPageId);
114✔
1139
    GDALPDFDictionaryRW oDictPage;
114✔
1140
    oDictPage.Add("Type", GDALPDFObjectRW::CreateName("Page"))
114✔
1141
        .Add("Parent", m_nPageResourceId, 0)
114✔
1142
        .Add("MediaBox", &((new GDALPDFArrayRW())
114✔
1143
                               ->Add(0)
114✔
1144
                               .Add(0)
114✔
1145
                               .Add(dfWidthInUserUnit)
114✔
1146
                               .Add(dfHeightInUserUnit)));
114✔
1147
    if (bWriteUserUnit)
114✔
1148
        oDictPage.Add("UserUnit", dfUserUnit);
102✔
1149
    oDictPage.Add("Contents", nContentId, 0)
114✔
1150
        .Add("Resources", nResourcesId, 0)
114✔
1151
        .Add("Annots", nAnnotsId, 0);
114✔
1152

1153
    if (nBands == 4)
114✔
1154
    {
1155
        oDictPage.Add(
1156
            "Group",
1157
            &((new GDALPDFDictionaryRW())
7✔
1158
                  ->Add("Type", GDALPDFObjectRW::CreateName("Group"))
7✔
1159
                  .Add("S", GDALPDFObjectRW::CreateName("Transparency"))
7✔
1160
                  .Add("CS", GDALPDFObjectRW::CreateName("DeviceRGB"))));
7✔
1161
    }
1162
    if (nViewportId.toBool())
114✔
1163
    {
1164
        oDictPage.Add("VP", &((new GDALPDFArrayRW())->Add(nViewportId, 0)));
75✔
1165
    }
1166

1167
#ifndef HACK_TO_GENERATE_OCMD
1168
    if (bHasOGRData)
114✔
1169
        oDictPage.Add("StructParents", 0);
23✔
1170
#endif
1171

1172
    VSIFPrintfL(m_fp, "%s\n", oDictPage.Serialize().c_str());
114✔
1173
    EndObj();
114✔
1174

1175
    oPageContext.poClippingDS = poClippingDS;
114✔
1176
    oPageContext.nPageId = nPageId;
114✔
1177
    oPageContext.nContentId = nContentId;
114✔
1178
    oPageContext.nResourcesId = nResourcesId;
114✔
1179
    oPageContext.nAnnotsId = nAnnotsId;
114✔
1180
    oPageContext.dfDPI = dfDPI;
114✔
1181
    oPageContext.sMargins = *psMargins;
114✔
1182
    oPageContext.eStreamCompressMethod = eStreamCompressMethod;
114✔
1183

1184
    return true;
228✔
1185
}
1186

1187
/************************************************************************/
1188
/*                             WriteColorTable()                        */
1189
/************************************************************************/
1190

1191
GDALPDFObjectNum GDALPDFBaseWriter::WriteColorTable(GDALDataset *poSrcDS)
305✔
1192
{
1193
    /* Does the source image has a color table ? */
1194
    GDALColorTable *poCT = nullptr;
305✔
1195
    if (poSrcDS->GetRasterCount() > 0)
305✔
1196
        poCT = poSrcDS->GetRasterBand(1)->GetColorTable();
305✔
1197
    GDALPDFObjectNum nColorTableId;
305✔
1198
    if (poCT != nullptr && poCT->GetColorEntryCount() <= 256)
305✔
1199
    {
1200
        int nColors = poCT->GetColorEntryCount();
2✔
1201
        nColorTableId = AllocNewObject();
2✔
1202

1203
        auto nLookupTableId = AllocNewObject();
2✔
1204

1205
        /* Index object */
1206
        StartObj(nColorTableId);
2✔
1207
        {
1208
            GDALPDFArrayRW oArray;
2✔
1209
            oArray.Add(GDALPDFObjectRW::CreateName("Indexed"))
2✔
1210
                .Add(&((new GDALPDFArrayRW())
2✔
1211
                           ->Add(GDALPDFObjectRW::CreateName("DeviceRGB"))))
2✔
1212
                .Add(nColors - 1)
2✔
1213
                .Add(nLookupTableId, 0);
2✔
1214
            VSIFPrintfL(m_fp, "%s\n", oArray.Serialize().c_str());
2✔
1215
        }
1216
        EndObj();
2✔
1217

1218
        /* Lookup table object */
1219
        StartObj(nLookupTableId);
2✔
1220
        {
1221
            GDALPDFDictionaryRW oDict;
2✔
1222
            oDict.Add("Length", nColors * 3);
2✔
1223
            VSIFPrintfL(m_fp, "%s %% Lookup table\n",
2✔
1224
                        oDict.Serialize().c_str());
4✔
1225
        }
1226
        VSIFPrintfL(m_fp, "stream\n");
2✔
1227
        GByte pabyLookup[768];
1228
        for (int i = 0; i < nColors; i++)
514✔
1229
        {
1230
            const GDALColorEntry *poEntry = poCT->GetColorEntry(i);
512✔
1231
            pabyLookup[3 * i + 0] = static_cast<GByte>(poEntry->c1);
512✔
1232
            pabyLookup[3 * i + 1] = static_cast<GByte>(poEntry->c2);
512✔
1233
            pabyLookup[3 * i + 2] = static_cast<GByte>(poEntry->c3);
512✔
1234
        }
1235
        VSIFWriteL(pabyLookup, 3 * nColors, 1, m_fp);
2✔
1236
        VSIFPrintfL(m_fp, "\n");
2✔
1237
        VSIFPrintfL(m_fp, "endstream\n");
2✔
1238
        EndObj();
2✔
1239
    }
1240

1241
    return nColorTableId;
305✔
1242
}
1243

1244
/************************************************************************/
1245
/*                             WriteImagery()                           */
1246
/************************************************************************/
1247

1248
bool GDALPDFWriter::WriteImagery(GDALDataset *poDS, const char *pszLayerName,
90✔
1249
                                 PDFCompressMethod eCompressMethod,
1250
                                 int nPredictor, int nJPEGQuality,
1251
                                 const char *pszJPEG2000_DRIVER,
1252
                                 int nBlockXSize, int nBlockYSize,
1253
                                 GDALProgressFunc pfnProgress,
1254
                                 void *pProgressData)
1255
{
1256
    int nWidth = poDS->GetRasterXSize();
90✔
1257
    int nHeight = poDS->GetRasterYSize();
90✔
1258
    double dfUserUnit = oPageContext.dfDPI * USER_UNIT_IN_INCH;
90✔
1259

1260
    GDALPDFRasterDesc oRasterDesc;
180✔
1261

1262
    if (pfnProgress == nullptr)
90✔
1263
        pfnProgress = GDALDummyProgress;
×
1264

1265
    oRasterDesc.nOCGRasterId = WriteOCG(pszLayerName);
90✔
1266

1267
    /* Does the source image has a color table ? */
1268
    auto nColorTableId = WriteColorTable(poDS);
90✔
1269

1270
    int nXBlocks = DIV_ROUND_UP(nWidth, nBlockXSize);
90✔
1271
    int nYBlocks = DIV_ROUND_UP(nHeight, nBlockYSize);
90✔
1272
    int nBlocks = nXBlocks * nYBlocks;
90✔
1273
    int nBlockXOff, nBlockYOff;
1274
    for (nBlockYOff = 0; nBlockYOff < nYBlocks; nBlockYOff++)
194✔
1275
    {
1276
        for (nBlockXOff = 0; nBlockXOff < nXBlocks; nBlockXOff++)
288✔
1277
        {
1278
            const int nReqWidth =
1279
                std::min(nBlockXSize, nWidth - nBlockXOff * nBlockXSize);
184✔
1280
            const int nReqHeight =
1281
                std::min(nBlockYSize, nHeight - nBlockYOff * nBlockYSize);
184✔
1282
            int iImage = nBlockYOff * nXBlocks + nBlockXOff;
184✔
1283

1284
            void *pScaledData = GDALCreateScaledProgress(
368✔
1285
                iImage / double(nBlocks), (iImage + 1) / double(nBlocks),
184✔
1286
                pfnProgress, pProgressData);
1287
            int nX = nBlockXOff * nBlockXSize;
184✔
1288
            int nY = nBlockYOff * nBlockYSize;
184✔
1289

1290
            auto nImageId =
1291
                WriteBlock(poDS, nX, nY, nReqWidth, nReqHeight, nColorTableId,
1292
                           eCompressMethod, nPredictor, nJPEGQuality,
1293
                           pszJPEG2000_DRIVER, GDALScaledProgress, pScaledData);
184✔
1294

1295
            GDALDestroyScaledProgress(pScaledData);
184✔
1296

1297
            if (!nImageId.toBool())
184✔
1298
                return false;
2✔
1299

1300
            GDALPDFImageDesc oImageDesc;
182✔
1301
            oImageDesc.nImageId = nImageId;
182✔
1302
            oImageDesc.dfXOff = nX / dfUserUnit + oPageContext.sMargins.nLeft;
182✔
1303
            oImageDesc.dfYOff = (nHeight - nY - nReqHeight) / dfUserUnit +
182✔
1304
                                oPageContext.sMargins.nBottom;
182✔
1305
            oImageDesc.dfXSize = nReqWidth / dfUserUnit;
182✔
1306
            oImageDesc.dfYSize = nReqHeight / dfUserUnit;
182✔
1307

1308
            oRasterDesc.asImageDesc.push_back(oImageDesc);
182✔
1309
        }
1310
    }
1311

1312
    oPageContext.asRasterDesc.push_back(oRasterDesc);
88✔
1313

1314
    return true;
88✔
1315
}
1316

1317
/************************************************************************/
1318
/*                        WriteClippedImagery()                         */
1319
/************************************************************************/
1320

1321
bool GDALPDFWriter::WriteClippedImagery(
4✔
1322
    GDALDataset *poDS, const char *pszLayerName,
1323
    PDFCompressMethod eCompressMethod, int nPredictor, int nJPEGQuality,
1324
    const char *pszJPEG2000_DRIVER, int nBlockXSize, int nBlockYSize,
1325
    GDALProgressFunc pfnProgress, void *pProgressData)
1326
{
1327
    double dfUserUnit = oPageContext.dfDPI * USER_UNIT_IN_INCH;
4✔
1328

1329
    GDALPDFRasterDesc oRasterDesc;
8✔
1330

1331
    /* Get clipping dataset bounding-box */
1332
    double adfClippingGeoTransform[6];
1333
    GDALDataset *poClippingDS = oPageContext.poClippingDS;
4✔
1334
    poClippingDS->GetGeoTransform(adfClippingGeoTransform);
4✔
1335
    int nClippingWidth = poClippingDS->GetRasterXSize();
4✔
1336
    int nClippingHeight = poClippingDS->GetRasterYSize();
4✔
1337
    double dfClippingMinX = adfClippingGeoTransform[0];
4✔
1338
    double dfClippingMaxX =
4✔
1339
        dfClippingMinX + nClippingWidth * adfClippingGeoTransform[1];
4✔
1340
    double dfClippingMaxY = adfClippingGeoTransform[3];
4✔
1341
    double dfClippingMinY =
4✔
1342
        dfClippingMaxY + nClippingHeight * adfClippingGeoTransform[5];
4✔
1343

1344
    if (dfClippingMaxY < dfClippingMinY)
4✔
1345
    {
1346
        std::swap(dfClippingMinY, dfClippingMaxY);
×
1347
    }
1348

1349
    /* Get current dataset dataset bounding-box */
1350
    double adfGeoTransform[6];
1351
    poDS->GetGeoTransform(adfGeoTransform);
4✔
1352
    int nWidth = poDS->GetRasterXSize();
4✔
1353
    int nHeight = poDS->GetRasterYSize();
4✔
1354
    double dfRasterMinX = adfGeoTransform[0];
4✔
1355
    // double dfRasterMaxX = dfRasterMinX + nWidth * adfGeoTransform[1];
1356
    double dfRasterMaxY = adfGeoTransform[3];
4✔
1357
    double dfRasterMinY = dfRasterMaxY + nHeight * adfGeoTransform[5];
4✔
1358

1359
    if (dfRasterMaxY < dfRasterMinY)
4✔
1360
    {
1361
        std::swap(dfRasterMinY, dfRasterMaxY);
×
1362
    }
1363

1364
    if (pfnProgress == nullptr)
4✔
1365
        pfnProgress = GDALDummyProgress;
2✔
1366

1367
    oRasterDesc.nOCGRasterId = WriteOCG(pszLayerName);
4✔
1368

1369
    /* Does the source image has a color table ? */
1370
    auto nColorTableId = WriteColorTable(poDS);
4✔
1371

1372
    int nXBlocks = DIV_ROUND_UP(nWidth, nBlockXSize);
4✔
1373
    int nYBlocks = DIV_ROUND_UP(nHeight, nBlockYSize);
4✔
1374
    int nBlocks = nXBlocks * nYBlocks;
4✔
1375
    int nBlockXOff, nBlockYOff;
1376
    for (nBlockYOff = 0; nBlockYOff < nYBlocks; nBlockYOff++)
8✔
1377
    {
1378
        for (nBlockXOff = 0; nBlockXOff < nXBlocks; nBlockXOff++)
8✔
1379
        {
1380
            int nReqWidth =
1381
                std::min(nBlockXSize, nWidth - nBlockXOff * nBlockXSize);
4✔
1382
            int nReqHeight =
1383
                std::min(nBlockYSize, nHeight - nBlockYOff * nBlockYSize);
4✔
1384
            int iImage = nBlockYOff * nXBlocks + nBlockXOff;
4✔
1385

1386
            void *pScaledData = GDALCreateScaledProgress(
8✔
1387
                iImage / double(nBlocks), (iImage + 1) / double(nBlocks),
4✔
1388
                pfnProgress, pProgressData);
1389

1390
            int nX = nBlockXOff * nBlockXSize;
4✔
1391
            int nY = nBlockYOff * nBlockYSize;
4✔
1392

1393
            /* Compute extent of block to write */
1394
            double dfBlockMinX = adfGeoTransform[0] + nX * adfGeoTransform[1];
4✔
1395
            double dfBlockMaxX =
4✔
1396
                adfGeoTransform[0] + (nX + nReqWidth) * adfGeoTransform[1];
4✔
1397
            double dfBlockMinY =
4✔
1398
                adfGeoTransform[3] + (nY + nReqHeight) * adfGeoTransform[5];
4✔
1399
            double dfBlockMaxY = adfGeoTransform[3] + nY * adfGeoTransform[5];
4✔
1400

1401
            if (dfBlockMaxY < dfBlockMinY)
4✔
1402
            {
1403
                std::swap(dfBlockMinY, dfBlockMaxY);
×
1404
            }
1405

1406
            // Clip the extent of the block with the extent of the main raster.
1407
            const double dfIntersectMinX =
1408
                std::max(dfBlockMinX, dfClippingMinX);
4✔
1409
            const double dfIntersectMinY =
1410
                std::max(dfBlockMinY, dfClippingMinY);
4✔
1411
            const double dfIntersectMaxX =
1412
                std::min(dfBlockMaxX, dfClippingMaxX);
4✔
1413
            const double dfIntersectMaxY =
1414
                std::min(dfBlockMaxY, dfClippingMaxY);
4✔
1415

1416
            if (dfIntersectMinX < dfIntersectMaxX &&
4✔
1417
                dfIntersectMinY < dfIntersectMaxY)
1418
            {
1419
                /* Re-compute (x,y,width,height) subwindow of current raster
1420
                 * from */
1421
                /* the extent of the clipped block */
1422
                nX = static_cast<int>((dfIntersectMinX - dfRasterMinX) /
4✔
1423
                                          adfGeoTransform[1] +
4✔
1424
                                      0.5);
1425
                if (adfGeoTransform[5] < 0)
4✔
1426
                    nY = static_cast<int>((dfRasterMaxY - dfIntersectMaxY) /
4✔
1427
                                              (-adfGeoTransform[5]) +
4✔
1428
                                          0.5);
1429
                else
1430
                    nY = static_cast<int>((dfIntersectMinY - dfRasterMinY) /
×
1431
                                              adfGeoTransform[5] +
×
1432
                                          0.5);
1433
                nReqWidth = static_cast<int>((dfIntersectMaxX - dfRasterMinX) /
4✔
1434
                                                 adfGeoTransform[1] +
4✔
1435
                                             0.5) -
1436
                            nX;
1437
                if (adfGeoTransform[5] < 0)
4✔
1438
                    nReqHeight =
4✔
1439
                        static_cast<int>((dfRasterMaxY - dfIntersectMinY) /
4✔
1440
                                             (-adfGeoTransform[5]) +
4✔
1441
                                         0.5) -
1442
                        nY;
1443
                else
1444
                    nReqHeight =
×
1445
                        static_cast<int>((dfIntersectMaxY - dfRasterMinY) /
×
1446
                                             adfGeoTransform[5] +
×
1447
                                         0.5) -
1448
                        nY;
1449

1450
                if (nReqWidth > 0 && nReqHeight > 0)
4✔
1451
                {
1452
                    auto nImageId = WriteBlock(
1453
                        poDS, nX, nY, nReqWidth, nReqHeight, nColorTableId,
1454
                        eCompressMethod, nPredictor, nJPEGQuality,
1455
                        pszJPEG2000_DRIVER, GDALScaledProgress, pScaledData);
4✔
1456

1457
                    if (!nImageId.toBool())
4✔
1458
                    {
1459
                        GDALDestroyScaledProgress(pScaledData);
×
1460
                        return false;
×
1461
                    }
1462

1463
                    /* Compute the subwindow in image coordinates of the main
1464
                     * raster corresponding */
1465
                    /* to the extent of the clipped block */
1466
                    double dfXInClippingUnits, dfYInClippingUnits,
1467
                        dfReqWidthInClippingUnits, dfReqHeightInClippingUnits;
1468

1469
                    dfXInClippingUnits = (dfIntersectMinX - dfClippingMinX) /
4✔
1470
                                         adfClippingGeoTransform[1];
4✔
1471
                    if (adfClippingGeoTransform[5] < 0)
4✔
1472
                        dfYInClippingUnits =
4✔
1473
                            (dfClippingMaxY - dfIntersectMaxY) /
4✔
1474
                            (-adfClippingGeoTransform[5]);
4✔
1475
                    else
1476
                        dfYInClippingUnits =
×
1477
                            (dfIntersectMinY - dfClippingMinY) /
×
1478
                            adfClippingGeoTransform[5];
×
1479
                    dfReqWidthInClippingUnits =
4✔
1480
                        (dfIntersectMaxX - dfClippingMinX) /
4✔
1481
                            adfClippingGeoTransform[1] -
4✔
1482
                        dfXInClippingUnits;
1483
                    if (adfClippingGeoTransform[5] < 0)
4✔
1484
                        dfReqHeightInClippingUnits =
4✔
1485
                            (dfClippingMaxY - dfIntersectMinY) /
4✔
1486
                                (-adfClippingGeoTransform[5]) -
4✔
1487
                            dfYInClippingUnits;
1488
                    else
1489
                        dfReqHeightInClippingUnits =
×
1490
                            (dfIntersectMaxY - dfClippingMinY) /
×
1491
                                adfClippingGeoTransform[5] -
×
1492
                            dfYInClippingUnits;
1493

1494
                    GDALPDFImageDesc oImageDesc;
4✔
1495
                    oImageDesc.nImageId = nImageId;
4✔
1496
                    oImageDesc.dfXOff = dfXInClippingUnits / dfUserUnit +
4✔
1497
                                        oPageContext.sMargins.nLeft;
4✔
1498
                    oImageDesc.dfYOff = (nClippingHeight - dfYInClippingUnits -
4✔
1499
                                         dfReqHeightInClippingUnits) /
4✔
1500
                                            dfUserUnit +
4✔
1501
                                        oPageContext.sMargins.nBottom;
4✔
1502
                    oImageDesc.dfXSize = dfReqWidthInClippingUnits / dfUserUnit;
4✔
1503
                    oImageDesc.dfYSize =
4✔
1504
                        dfReqHeightInClippingUnits / dfUserUnit;
4✔
1505

1506
                    oRasterDesc.asImageDesc.push_back(oImageDesc);
4✔
1507
                }
1508
            }
1509

1510
            GDALDestroyScaledProgress(pScaledData);
4✔
1511
        }
1512
    }
1513

1514
    oPageContext.asRasterDesc.push_back(oRasterDesc);
4✔
1515

1516
    return true;
4✔
1517
}
1518

1519
/************************************************************************/
1520
/*                          WriteOGRDataSource()                        */
1521
/************************************************************************/
1522

1523
bool GDALPDFWriter::WriteOGRDataSource(const char *pszOGRDataSource,
4✔
1524
                                       const char *pszOGRDisplayField,
1525
                                       const char *pszOGRDisplayLayerNames,
1526
                                       const char *pszOGRLinkField,
1527
                                       int bWriteOGRAttributes)
1528
{
1529
    GDALDatasetH hDS =
1530
        GDALOpenEx(pszOGRDataSource, GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR,
4✔
1531
                   nullptr, nullptr, nullptr);
1532
    if (hDS == nullptr)
4✔
1533
        return false;
×
1534

1535
    int iObj = 0;
4✔
1536

1537
    int nLayers = GDALDatasetGetLayerCount(hDS);
4✔
1538

1539
    char **papszLayerNames =
1540
        CSLTokenizeString2(pszOGRDisplayLayerNames, ",", 0);
4✔
1541

1542
    for (int iLayer = 0; iLayer < nLayers; iLayer++)
8✔
1543
    {
1544
        CPLString osLayerName;
8✔
1545
        if (CSLCount(papszLayerNames) < nLayers)
4✔
1546
            osLayerName = OGR_L_GetName(GDALDatasetGetLayer(hDS, iLayer));
×
1547
        else
1548
            osLayerName = papszLayerNames[iLayer];
4✔
1549

1550
        WriteOGRLayer(hDS, iLayer, pszOGRDisplayField, pszOGRLinkField,
4✔
1551
                      osLayerName, bWriteOGRAttributes, iObj);
1552
    }
1553

1554
    GDALClose(hDS);
4✔
1555

1556
    CSLDestroy(papszLayerNames);
4✔
1557

1558
    return true;
4✔
1559
}
1560

1561
/************************************************************************/
1562
/*                           StartOGRLayer()                            */
1563
/************************************************************************/
1564

1565
GDALPDFLayerDesc GDALPDFWriter::StartOGRLayer(const std::string &osLayerName,
41✔
1566
                                              int bWriteOGRAttributes)
1567
{
1568
    GDALPDFLayerDesc osVectorDesc;
41✔
1569
    osVectorDesc.osLayerName = osLayerName;
41✔
1570
#ifdef HACK_TO_GENERATE_OCMD
1571
    osVectorDesc.bWriteOGRAttributes = false;
1572
    auto nParentOCGId = WriteOCG("parent");
1573
    osVectorDesc.nOCGId = WriteOCG(osLayerName.c_str(), nParentOCGId);
1574
#else
1575
    osVectorDesc.bWriteOGRAttributes = bWriteOGRAttributes;
41✔
1576
    osVectorDesc.nOCGId = WriteOCG(osLayerName.c_str());
41✔
1577
#endif
1578
    if (bWriteOGRAttributes)
41✔
1579
        osVectorDesc.nFeatureLayerId = AllocNewObject();
38✔
1580

1581
    return osVectorDesc;
41✔
1582
}
1583

1584
/************************************************************************/
1585
/*                           EndOGRLayer()                              */
1586
/************************************************************************/
1587

1588
void GDALPDFWriter::EndOGRLayer(GDALPDFLayerDesc &osVectorDesc)
41✔
1589
{
1590
    if (osVectorDesc.bWriteOGRAttributes)
41✔
1591
    {
1592
        StartObj(osVectorDesc.nFeatureLayerId);
38✔
1593

1594
        GDALPDFDictionaryRW oDict;
76✔
1595
        oDict.Add("A", &(new GDALPDFDictionaryRW())
38✔
1596
                            ->Add("O", GDALPDFObjectRW::CreateName(
38✔
1597
                                           "UserProperties")));
38✔
1598

1599
        GDALPDFArrayRW *poArray = new GDALPDFArrayRW();
38✔
1600
        oDict.Add("K", poArray);
38✔
1601

1602
        for (const auto &prop : osVectorDesc.aUserPropertiesIds)
145✔
1603
        {
1604
            poArray->Add(prop, 0);
107✔
1605
        }
1606

1607
        if (!m_nStructTreeRootId.toBool())
38✔
1608
            m_nStructTreeRootId = AllocNewObject();
23✔
1609

1610
        oDict.Add("P", m_nStructTreeRootId, 0);
38✔
1611
        oDict.Add("S", GDALPDFObjectRW::CreateName("Feature"));
38✔
1612
        oDict.Add("T", osVectorDesc.osLayerName);
38✔
1613

1614
        VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
38✔
1615

1616
        EndObj();
38✔
1617
    }
1618

1619
    oPageContext.asVectorDesc.push_back(osVectorDesc);
41✔
1620
}
41✔
1621

1622
/************************************************************************/
1623
/*                           WriteOGRLayer()                            */
1624
/************************************************************************/
1625

1626
int GDALPDFWriter::WriteOGRLayer(GDALDatasetH hDS, int iLayer,
41✔
1627
                                 const char *pszOGRDisplayField,
1628
                                 const char *pszOGRLinkField,
1629
                                 const std::string &osLayerName,
1630
                                 int bWriteOGRAttributes, int &iObj)
1631
{
1632
    GDALDataset *poClippingDS = oPageContext.poClippingDS;
41✔
1633
    double adfGeoTransform[6];
1634
    if (poClippingDS->GetGeoTransform(adfGeoTransform) != CE_None)
41✔
1635
        return FALSE;
×
1636

1637
    GDALPDFLayerDesc osVectorDesc =
1638
        StartOGRLayer(osLayerName, bWriteOGRAttributes);
41✔
1639
    OGRLayerH hLyr = GDALDatasetGetLayer(hDS, iLayer);
41✔
1640

1641
    const auto poLayerDefn = OGRLayer::FromHandle(hLyr)->GetLayerDefn();
41✔
1642
    for (int i = 0; i < poLayerDefn->GetFieldCount(); i++)
163✔
1643
    {
1644
        const auto poFieldDefn = poLayerDefn->GetFieldDefn(i);
122✔
1645
        const char *pszName = poFieldDefn->GetNameRef();
122✔
1646
        osVectorDesc.aosIncludedFields.push_back(pszName);
122✔
1647
    }
1648

1649
    OGRSpatialReferenceH hGDAL_SRS = OGRSpatialReference::ToHandle(
41✔
1650
        const_cast<OGRSpatialReference *>(poClippingDS->GetSpatialRef()));
41✔
1651
    OGRSpatialReferenceH hOGR_SRS = OGR_L_GetSpatialRef(hLyr);
41✔
1652
    OGRCoordinateTransformationH hCT = nullptr;
41✔
1653

1654
    if (hGDAL_SRS == nullptr && hOGR_SRS != nullptr)
41✔
1655
    {
1656
        CPLError(CE_Warning, CPLE_AppDefined,
×
1657
                 "Vector layer has a SRS set, but Raster layer has no SRS set. "
1658
                 "Assuming they are the same.");
1659
    }
1660
    else if (hGDAL_SRS != nullptr && hOGR_SRS == nullptr)
41✔
1661
    {
1662
        CPLError(CE_Warning, CPLE_AppDefined,
×
1663
                 "Vector layer has no SRS set, but Raster layer has a SRS set. "
1664
                 "Assuming they are the same.");
1665
    }
1666
    else if (hGDAL_SRS != nullptr && hOGR_SRS != nullptr)
41✔
1667
    {
1668
        if (!OSRIsSame(hGDAL_SRS, hOGR_SRS))
11✔
1669
        {
1670
            hCT = OCTNewCoordinateTransformation(hOGR_SRS, hGDAL_SRS);
2✔
1671
            if (hCT == nullptr)
2✔
1672
            {
1673
                CPLError(CE_Warning, CPLE_AppDefined,
×
1674
                         "Cannot compute coordinate transformation from vector "
1675
                         "SRS to raster SRS");
1676
            }
1677
        }
1678
    }
1679

1680
    if (hCT == nullptr)
41✔
1681
    {
1682
        double dfXMin = adfGeoTransform[0];
39✔
1683
        double dfYMin = adfGeoTransform[3] +
39✔
1684
                        poClippingDS->GetRasterYSize() * adfGeoTransform[5];
39✔
1685
        double dfXMax = adfGeoTransform[0] +
39✔
1686
                        poClippingDS->GetRasterXSize() * adfGeoTransform[1];
39✔
1687
        double dfYMax = adfGeoTransform[3];
39✔
1688
        OGR_L_SetSpatialFilterRect(hLyr, dfXMin, dfYMin, dfXMax, dfYMax);
39✔
1689
    }
1690

1691
    OGRFeatureH hFeat;
1692

1693
    while ((hFeat = OGR_L_GetNextFeature(hLyr)) != nullptr)
195✔
1694
    {
1695
        WriteOGRFeature(osVectorDesc, hFeat, hCT, pszOGRDisplayField,
154✔
1696
                        pszOGRLinkField, bWriteOGRAttributes, iObj);
1697

1698
        OGR_F_Destroy(hFeat);
154✔
1699
    }
1700

1701
    EndOGRLayer(osVectorDesc);
41✔
1702

1703
    if (hCT != nullptr)
41✔
1704
        OCTDestroyCoordinateTransformation(hCT);
2✔
1705

1706
    return TRUE;
41✔
1707
}
1708

1709
/************************************************************************/
1710
/*                             DrawGeometry()                           */
1711
/************************************************************************/
1712

1713
static void DrawGeometry(CPLString &osDS, OGRGeometryH hGeom,
118✔
1714
                         const double adfMatrix[4], bool bPaint = true)
1715
{
1716
    switch (wkbFlatten(OGR_G_GetGeometryType(hGeom)))
118✔
1717
    {
1718
        case wkbLineString:
61✔
1719
        {
1720
            int nPoints = OGR_G_GetPointCount(hGeom);
61✔
1721
            for (int i = 0; i < nPoints; i++)
300✔
1722
            {
1723
                double dfX = OGR_G_GetX(hGeom, i) * adfMatrix[1] + adfMatrix[0];
239✔
1724
                double dfY = OGR_G_GetY(hGeom, i) * adfMatrix[3] + adfMatrix[2];
239✔
1725
                osDS +=
1726
                    CPLOPrintf("%f %f %c\n", dfX, dfY, (i == 0) ? 'm' : 'l');
239✔
1727
            }
1728
            if (bPaint)
61✔
1729
                osDS += CPLOPrintf("S\n");
16✔
1730
            break;
61✔
1731
        }
1732

1733
        case wkbPolygon:
31✔
1734
        {
1735
            int nParts = OGR_G_GetGeometryCount(hGeom);
31✔
1736
            for (int i = 0; i < nParts; i++)
70✔
1737
            {
1738
                DrawGeometry(osDS, OGR_G_GetGeometryRef(hGeom, i), adfMatrix,
39✔
1739
                             false);
1740
                osDS += CPLOPrintf("h\n");
39✔
1741
            }
1742
            if (bPaint)
31✔
1743
                osDS += CPLOPrintf("b*\n");
21✔
1744
            break;
31✔
1745
        }
1746

1747
        case wkbMultiLineString:
6✔
1748
        {
1749
            int nParts = OGR_G_GetGeometryCount(hGeom);
6✔
1750
            for (int i = 0; i < nParts; i++)
12✔
1751
            {
1752
                DrawGeometry(osDS, OGR_G_GetGeometryRef(hGeom, i), adfMatrix,
6✔
1753
                             false);
1754
            }
1755
            if (bPaint)
6✔
1756
                osDS += CPLOPrintf("S\n");
6✔
1757
            break;
6✔
1758
        }
1759

1760
        case wkbMultiPolygon:
8✔
1761
        {
1762
            int nParts = OGR_G_GetGeometryCount(hGeom);
8✔
1763
            for (int i = 0; i < nParts; i++)
18✔
1764
            {
1765
                DrawGeometry(osDS, OGR_G_GetGeometryRef(hGeom, i), adfMatrix,
10✔
1766
                             false);
1767
            }
1768
            if (bPaint)
8✔
1769
                osDS += CPLOPrintf("b*\n");
8✔
1770
            break;
8✔
1771
        }
1772

1773
        default:
12✔
1774
            break;
12✔
1775
    }
1776
}
118✔
1777

1778
/************************************************************************/
1779
/*                           CalculateText()                            */
1780
/************************************************************************/
1781

1782
static void CalculateText(const CPLString &osText, CPLString &osFont,
11✔
1783
                          const double dfSize, const bool bBold,
1784
                          const bool bItalic, double &dfWidth, double &dfHeight)
1785
{
1786
    // Character widths of Helvetica, Win-1252 characters 32 to 255
1787
    // Helvetica bold, oblique and bold oblique have their own widths,
1788
    // but for now we will put up with these widths on all Helvetica variants
1789
    constexpr GUInt16 anHelveticaCharWidths[] = {
11✔
1790
        569,  569,  727,  1139, 1139, 1821, 1366, 391,  682,  682,  797,  1196,
1791
        569,  682,  569,  569,  1139, 1139, 1139, 1139, 1139, 1139, 1139, 1139,
1792
        1139, 1139, 569,  569,  1196, 1196, 1196, 1139, 2079, 1366, 1366, 1479,
1793
        1479, 1366, 1251, 1593, 1479, 569,  1024, 1366, 1139, 1706, 1479, 1593,
1794
        1366, 1593, 1479, 1366, 1251, 1479, 1366, 1933, 1366, 1366, 1251, 569,
1795
        569,  569,  961,  1139, 682,  1139, 1139, 1024, 1139, 1139, 569,  1139,
1796
        1139, 455,  455,  1024, 455,  1706, 1139, 1139, 1139, 1139, 682,  1024,
1797
        569,  1139, 1024, 1479, 1024, 1024, 1024, 684,  532,  684,  1196, 1536,
1798
        1139, 2048, 455,  1139, 682,  2048, 1139, 1139, 682,  2048, 1366, 682,
1799
        2048, 2048, 1251, 2048, 2048, 455,  455,  682,  682,  717,  1139, 2048,
1800
        682,  2048, 1024, 682,  1933, 2048, 1024, 1366, 569,  682,  1139, 1139,
1801
        1139, 1139, 532,  1139, 682,  1509, 758,  1139, 1196, 682,  1509, 1131,
1802
        819,  1124, 682,  682,  682,  1180, 1100, 682,  682,  682,  748,  1139,
1803
        1708, 1708, 1708, 1251, 1366, 1366, 1366, 1366, 1366, 1366, 2048, 1479,
1804
        1366, 1366, 1366, 1366, 569,  569,  569,  569,  1479, 1479, 1593, 1593,
1805
        1593, 1593, 1593, 1196, 1593, 1479, 1479, 1479, 1479, 1366, 1366, 1251,
1806
        1139, 1139, 1139, 1139, 1139, 1139, 1821, 1024, 1139, 1139, 1139, 1139,
1807
        569,  569,  569,  569,  1139, 1139, 1139, 1139, 1139, 1139, 1139, 1124,
1808
        1251, 1139, 1139, 1139, 1139, 1024, 1139, 1024};
1809

1810
    // Character widths of Times-Roman, Win-1252 characters 32 to 255
1811
    // Times bold, italic and bold italic have their own widths,
1812
    // but for now we will put up with these widths on all Times variants
1813
    constexpr GUInt16 anTimesCharWidths[] = {
11✔
1814
        512,  682,  836,  1024, 1024, 1706, 1593, 369,  682,  682,  1024, 1155,
1815
        512,  682,  512,  569,  1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024,
1816
        1024, 1024, 569,  569,  1155, 1155, 1155, 909,  1886, 1479, 1366, 1366,
1817
        1479, 1251, 1139, 1479, 1479, 682,  797,  1479, 1251, 1821, 1479, 1479,
1818
        1139, 1479, 1366, 1139, 1251, 1479, 1479, 1933, 1479, 1479, 1251, 682,
1819
        569,  682,  961,  1024, 682,  909,  1024, 909,  1024, 909,  682,  1024,
1820
        1024, 569,  569,  1024, 569,  1593, 1024, 1024, 1024, 1024, 682,  797,
1821
        569,  1024, 1024, 1479, 1024, 1024, 909,  983,  410,  983,  1108, 0,
1822
        1024, 2048, 682,  1024, 909,  2048, 1024, 1024, 682,  2048, 1139, 682,
1823
        1821, 2048, 1251, 2048, 2048, 682,  682,  909,  909,  717,  1024, 2048,
1824
        682,  2007, 797,  682,  1479, 2048, 909,  1479, 512,  682,  1024, 1024,
1825
        1024, 1024, 410,  1024, 682,  1556, 565,  1024, 1155, 682,  1556, 1024,
1826
        819,  1124, 614,  614,  682,  1180, 928,  682,  682,  614,  635,  1024,
1827
        1536, 1536, 1536, 909,  1479, 1479, 1479, 1479, 1479, 1479, 1821, 1366,
1828
        1251, 1251, 1251, 1251, 682,  682,  682,  682,  1479, 1479, 1479, 1479,
1829
        1479, 1479, 1479, 1155, 1479, 1479, 1479, 1479, 1479, 1479, 1139, 1024,
1830
        909,  909,  909,  909,  909,  909,  1366, 909,  909,  909,  909,  909,
1831
        569,  569,  569,  569,  1024, 1024, 1024, 1024, 1024, 1024, 1024, 1124,
1832
        1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024};
1833

1834
    const GUInt16 *panCharacterWidths = nullptr;
11✔
1835

1836
    if (STARTS_WITH_CI(osFont, "times") ||
22✔
1837
        osFont.find("Serif", 0) != std::string::npos)
11✔
1838
    {
1839
        if (bBold && bItalic)
×
1840
            osFont = "Times-BoldItalic";
×
1841
        else if (bBold)
×
1842
            osFont = "Times-Bold";
×
1843
        else if (bItalic)
×
1844
            osFont = "Times-Italic";
×
1845
        else
1846
            osFont = "Times-Roman";
×
1847

1848
        panCharacterWidths = anTimesCharWidths;
×
1849
        dfHeight = dfSize * 1356.0 / 2048;
×
1850
    }
1851
    else if (STARTS_WITH_CI(osFont, "courier") ||
22✔
1852
             osFont.find("Mono", 0) != std::string::npos)
11✔
1853
    {
1854
        if (bBold && bItalic)
×
1855
            osFont = "Courier-BoldOblique";
×
1856
        else if (bBold)
×
1857
            osFont = "Courier-Bold";
×
1858
        else if (bItalic)
×
1859
            osFont = "Courier-Oblique";
×
1860
        else
1861
            osFont = "Courier";
×
1862

1863
        dfHeight = dfSize * 1170.0 / 2048;
×
1864
    }
1865
    else
1866
    {
1867
        if (bBold && bItalic)
11✔
1868
            osFont = "Helvetica-BoldOblique";
×
1869
        else if (bBold)
11✔
1870
            osFont = "Helvetica-Bold";
×
1871
        else if (bItalic)
11✔
1872
            osFont = "Helvetica-Oblique";
×
1873
        else
1874
            osFont = "Helvetica";
11✔
1875

1876
        panCharacterWidths = anHelveticaCharWidths;
11✔
1877
        dfHeight = dfSize * 1467.0 / 2048;
11✔
1878
    }
1879

1880
    dfWidth = 0.0;
11✔
1881
    for (const char &ch : osText)
110✔
1882
    {
1883
        const int nCh = static_cast<int>(ch);
99✔
1884
        if (nCh < 32)
99✔
1885
            continue;
×
1886

1887
        dfWidth +=
198✔
1888
            (panCharacterWidths ? panCharacterWidths[nCh - 32]
99✔
1889
                                : 1229);  // Courier's fixed character width
1890
    }
1891
    dfWidth *= dfSize / 2048;
11✔
1892
}
11✔
1893

1894
/************************************************************************/
1895
/*                          GetObjectStyle()                            */
1896
/************************************************************************/
1897

1898
void GDALPDFBaseWriter::GetObjectStyle(
163✔
1899
    const char *pszStyleString, OGRFeatureH hFeat, const double adfMatrix[4],
1900
    std::map<CPLString, GDALPDFImageDesc> oMapSymbolFilenameToDesc,
1901
    ObjectStyle &os)
1902
{
1903
    OGRStyleMgrH hSM = OGR_SM_Create(nullptr);
163✔
1904
    if (pszStyleString)
163✔
1905
        OGR_SM_InitStyleString(hSM, pszStyleString);
×
1906
    else
1907
        OGR_SM_InitFromFeature(hSM, hFeat);
163✔
1908
    int nCount = OGR_SM_GetPartCount(hSM, nullptr);
163✔
1909
    for (int iPart = 0; iPart < nCount; iPart++)
257✔
1910
    {
1911
        OGRStyleToolH hTool = OGR_SM_GetPart(hSM, iPart, nullptr);
94✔
1912
        if (hTool)
94✔
1913
        {
1914
            // Figure out how to involve adfMatrix[3] here and below
1915
            OGR_ST_SetUnit(hTool, OGRSTUMM, 1000.0 / adfMatrix[1]);
92✔
1916
            if (OGR_ST_GetType(hTool) == OGRSTCPen)
92✔
1917
            {
1918
                os.bHasPenBrushOrSymbol = true;
4✔
1919

1920
                int bIsNull = TRUE;
4✔
1921
                const char *pszColor =
1922
                    OGR_ST_GetParamStr(hTool, OGRSTPenColor, &bIsNull);
4✔
1923
                if (pszColor && !bIsNull)
4✔
1924
                {
1925
                    unsigned int nRed = 0;
4✔
1926
                    unsigned int nGreen = 0;
4✔
1927
                    unsigned int nBlue = 0;
4✔
1928
                    unsigned int nAlpha = 255;
4✔
1929
                    int nVals = sscanf(pszColor, "#%2x%2x%2x%2x", &nRed,
4✔
1930
                                       &nGreen, &nBlue, &nAlpha);
1931
                    if (nVals >= 3)
4✔
1932
                    {
1933
                        os.nPenR = nRed;
4✔
1934
                        os.nPenG = nGreen;
4✔
1935
                        os.nPenB = nBlue;
4✔
1936
                        if (nVals == 4)
4✔
1937
                            os.nPenA = nAlpha;
×
1938
                    }
1939
                }
1940

1941
                const char *pszDash =
1942
                    OGR_ST_GetParamStr(hTool, OGRSTPenPattern, &bIsNull);
4✔
1943
                if (pszDash && !bIsNull)
4✔
1944
                {
1945
                    char **papszTokens = CSLTokenizeString2(pszDash, " ", 0);
2✔
1946
                    int nTokens = CSLCount(papszTokens);
2✔
1947
                    if ((nTokens % 2) == 0)
2✔
1948
                    {
1949
                        for (int i = 0; i < nTokens; i++)
6✔
1950
                        {
1951
                            double dfElement = CPLAtof(papszTokens[i]);
4✔
1952
                            dfElement *= adfMatrix[1];  // should involve
4✔
1953
                                                        // adfMatrix[3] too
1954
                            os.osDashArray += CPLSPrintf("%f ", dfElement);
4✔
1955
                        }
1956
                    }
1957
                    CSLDestroy(papszTokens);
2✔
1958
                }
1959

1960
                // OGRSTUnitId eUnit = OGR_ST_GetUnit(hTool);
1961
                double dfWidth =
1962
                    OGR_ST_GetParamDbl(hTool, OGRSTPenWidth, &bIsNull);
4✔
1963
                if (!bIsNull)
4✔
1964
                    os.dfPenWidth = dfWidth;
4✔
1965
            }
1966
            else if (OGR_ST_GetType(hTool) == OGRSTCBrush)
88✔
1967
            {
1968
                os.bHasPenBrushOrSymbol = true;
2✔
1969

1970
                int bIsNull;
1971
                const char *pszColor =
1972
                    OGR_ST_GetParamStr(hTool, OGRSTBrushFColor, &bIsNull);
2✔
1973
                if (pszColor)
2✔
1974
                {
1975
                    unsigned int nRed = 0;
2✔
1976
                    unsigned int nGreen = 0;
2✔
1977
                    unsigned int nBlue = 0;
2✔
1978
                    unsigned int nAlpha = 255;
2✔
1979
                    int nVals = sscanf(pszColor, "#%2x%2x%2x%2x", &nRed,
2✔
1980
                                       &nGreen, &nBlue, &nAlpha);
1981
                    if (nVals >= 3)
2✔
1982
                    {
1983
                        os.nBrushR = nRed;
2✔
1984
                        os.nBrushG = nGreen;
2✔
1985
                        os.nBrushB = nBlue;
2✔
1986
                        if (nVals == 4)
2✔
1987
                            os.nBrushA = nAlpha;
×
1988
                    }
1989
                }
1990
            }
1991
            else if (OGR_ST_GetType(hTool) == OGRSTCLabel)
86✔
1992
            {
1993
                int bIsNull;
1994
                const char *pszStr =
1995
                    OGR_ST_GetParamStr(hTool, OGRSTLabelTextString, &bIsNull);
13✔
1996
                if (pszStr)
13✔
1997
                {
1998
                    os.osLabelText = pszStr;
13✔
1999

2000
                    /* If the text is of the form {stuff}, then it means we want
2001
                     * to fetch */
2002
                    /* the value of the field "stuff" in the feature */
2003
                    if (!os.osLabelText.empty() && os.osLabelText[0] == '{' &&
19✔
2004
                        os.osLabelText.back() == '}')
6✔
2005
                    {
2006
                        os.osLabelText = pszStr + 1;
6✔
2007
                        os.osLabelText.resize(os.osLabelText.size() - 1);
6✔
2008

2009
                        int nIdxField =
2010
                            OGR_F_GetFieldIndex(hFeat, os.osLabelText);
6✔
2011
                        if (nIdxField >= 0)
6✔
2012
                            os.osLabelText =
2013
                                OGR_F_GetFieldAsString(hFeat, nIdxField);
6✔
2014
                        else
2015
                            os.osLabelText = "";
×
2016
                    }
2017
                }
2018

2019
                const char *pszColor =
2020
                    OGR_ST_GetParamStr(hTool, OGRSTLabelFColor, &bIsNull);
13✔
2021
                if (pszColor && !bIsNull)
13✔
2022
                {
2023
                    unsigned int nRed = 0;
2✔
2024
                    unsigned int nGreen = 0;
2✔
2025
                    unsigned int nBlue = 0;
2✔
2026
                    unsigned int nAlpha = 255;
2✔
2027
                    int nVals = sscanf(pszColor, "#%2x%2x%2x%2x", &nRed,
2✔
2028
                                       &nGreen, &nBlue, &nAlpha);
2029
                    if (nVals >= 3)
2✔
2030
                    {
2031
                        os.nTextR = nRed;
2✔
2032
                        os.nTextG = nGreen;
2✔
2033
                        os.nTextB = nBlue;
2✔
2034
                        if (nVals == 4)
2✔
2035
                            os.nTextA = nAlpha;
2✔
2036
                    }
2037
                }
2038

2039
                pszStr =
2040
                    OGR_ST_GetParamStr(hTool, OGRSTLabelFontName, &bIsNull);
13✔
2041
                if (pszStr && !bIsNull)
13✔
2042
                    os.osTextFont = pszStr;
2✔
2043

2044
                double dfVal =
2045
                    OGR_ST_GetParamDbl(hTool, OGRSTLabelSize, &bIsNull);
13✔
2046
                if (!bIsNull)
13✔
2047
                    os.dfTextSize = dfVal;
7✔
2048

2049
                dfVal = OGR_ST_GetParamDbl(hTool, OGRSTLabelAngle, &bIsNull);
13✔
2050
                if (!bIsNull)
13✔
2051
                    os.dfTextAngle = dfVal * M_PI / 180.0;
8✔
2052

2053
                dfVal = OGR_ST_GetParamDbl(hTool, OGRSTLabelStretch, &bIsNull);
13✔
2054
                if (!bIsNull)
13✔
2055
                    os.dfTextStretch = dfVal / 100.0;
×
2056

2057
                dfVal = OGR_ST_GetParamDbl(hTool, OGRSTLabelDx, &bIsNull);
13✔
2058
                if (!bIsNull)
13✔
2059
                    os.dfTextDx = dfVal;
6✔
2060

2061
                dfVal = OGR_ST_GetParamDbl(hTool, OGRSTLabelDy, &bIsNull);
13✔
2062
                if (!bIsNull)
13✔
2063
                    os.dfTextDy = dfVal;
6✔
2064

2065
                int nVal =
2066
                    OGR_ST_GetParamNum(hTool, OGRSTLabelAnchor, &bIsNull);
13✔
2067
                if (!bIsNull)
13✔
2068
                    os.nTextAnchor = nVal;
6✔
2069

2070
                nVal = OGR_ST_GetParamNum(hTool, OGRSTLabelBold, &bIsNull);
13✔
2071
                if (!bIsNull)
13✔
2072
                    os.bTextBold = (nVal != 0);
×
2073

2074
                nVal = OGR_ST_GetParamNum(hTool, OGRSTLabelItalic, &bIsNull);
13✔
2075
                if (!bIsNull)
13✔
2076
                    os.bTextItalic = (nVal != 0);
×
2077
            }
2078
            else if (OGR_ST_GetType(hTool) == OGRSTCSymbol)
73✔
2079
            {
2080
                os.bHasPenBrushOrSymbol = true;
73✔
2081

2082
                int bIsNull;
2083
                const char *pszSymbolId =
2084
                    OGR_ST_GetParamStr(hTool, OGRSTSymbolId, &bIsNull);
73✔
2085
                if (pszSymbolId && !bIsNull)
73✔
2086
                {
2087
                    os.osSymbolId = pszSymbolId;
73✔
2088

2089
                    if (strstr(pszSymbolId, "ogr-sym-") == nullptr)
73✔
2090
                    {
2091
                        if (oMapSymbolFilenameToDesc.find(os.osSymbolId) ==
7✔
2092
                            oMapSymbolFilenameToDesc.end())
14✔
2093
                        {
2094
                            CPLPushErrorHandler(CPLQuietErrorHandler);
7✔
2095
                            GDALDatasetH hImageDS =
2096
                                GDALOpen(os.osSymbolId, GA_ReadOnly);
7✔
2097
                            CPLPopErrorHandler();
7✔
2098
                            if (hImageDS != nullptr)
7✔
2099
                            {
2100
                                os.nImageWidth = GDALGetRasterXSize(hImageDS);
7✔
2101
                                os.nImageHeight = GDALGetRasterYSize(hImageDS);
7✔
2102

2103
                                os.nImageSymbolId = WriteBlock(
2104
                                    GDALDataset::FromHandle(hImageDS), 0, 0,
2105
                                    os.nImageWidth, os.nImageHeight,
2106
                                    GDALPDFObjectNum(), COMPRESS_DEFAULT, 0, -1,
7✔
2107
                                    nullptr, nullptr, nullptr);
7✔
2108
                                GDALClose(hImageDS);
7✔
2109
                            }
2110

2111
                            GDALPDFImageDesc oDesc;
7✔
2112
                            oDesc.nImageId = os.nImageSymbolId;
7✔
2113
                            oDesc.dfXOff = 0;
7✔
2114
                            oDesc.dfYOff = 0;
7✔
2115
                            oDesc.dfXSize = os.nImageWidth;
7✔
2116
                            oDesc.dfYSize = os.nImageHeight;
7✔
2117
                            oMapSymbolFilenameToDesc[os.osSymbolId] = oDesc;
7✔
2118
                        }
2119
                        else
2120
                        {
2121
                            const GDALPDFImageDesc &oDesc =
2122
                                oMapSymbolFilenameToDesc[os.osSymbolId];
×
2123
                            os.nImageSymbolId = oDesc.nImageId;
×
2124
                            os.nImageWidth = static_cast<int>(oDesc.dfXSize);
×
2125
                            os.nImageHeight = static_cast<int>(oDesc.dfYSize);
×
2126
                        }
2127
                    }
2128
                }
2129

2130
                double dfVal =
2131
                    OGR_ST_GetParamDbl(hTool, OGRSTSymbolSize, &bIsNull);
73✔
2132
                if (!bIsNull)
73✔
2133
                {
2134
                    os.dfSymbolSize = dfVal;
67✔
2135
                }
2136

2137
                const char *pszColor =
2138
                    OGR_ST_GetParamStr(hTool, OGRSTSymbolColor, &bIsNull);
73✔
2139
                if (pszColor && !bIsNull)
73✔
2140
                {
2141
                    unsigned int nRed = 0;
72✔
2142
                    unsigned int nGreen = 0;
72✔
2143
                    unsigned int nBlue = 0;
72✔
2144
                    unsigned int nAlpha = 255;
72✔
2145
                    int nVals = sscanf(pszColor, "#%2x%2x%2x%2x", &nRed,
72✔
2146
                                       &nGreen, &nBlue, &nAlpha);
2147
                    if (nVals >= 3)
72✔
2148
                    {
2149
                        os.bSymbolColorDefined = TRUE;
72✔
2150
                        os.nSymbolR = nRed;
72✔
2151
                        os.nSymbolG = nGreen;
72✔
2152
                        os.nSymbolB = nBlue;
72✔
2153
                        if (nVals == 4)
72✔
2154
                            os.nSymbolA = nAlpha;
1✔
2155
                    }
2156
                }
2157
            }
2158

2159
            OGR_ST_Destroy(hTool);
92✔
2160
        }
2161
    }
2162
    OGR_SM_Destroy(hSM);
163✔
2163

2164
    OGRGeometryH hGeom = OGR_F_GetGeometryRef(hFeat);
163✔
2165
    if (wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint &&
263✔
2166
        os.bSymbolColorDefined)
100✔
2167
    {
2168
        os.nPenR = os.nSymbolR;
72✔
2169
        os.nPenG = os.nSymbolG;
72✔
2170
        os.nPenB = os.nSymbolB;
72✔
2171
        os.nPenA = os.nSymbolA;
72✔
2172
        os.nBrushR = os.nSymbolR;
72✔
2173
        os.nBrushG = os.nSymbolG;
72✔
2174
        os.nBrushB = os.nSymbolB;
72✔
2175
        os.nBrushA = os.nSymbolA;
72✔
2176
    }
2177
}
163✔
2178

2179
/************************************************************************/
2180
/*                           ComputeIntBBox()                           */
2181
/************************************************************************/
2182

2183
void GDALPDFBaseWriter::ComputeIntBBox(
147✔
2184
    OGRGeometryH hGeom, const OGREnvelope &sEnvelope, const double adfMatrix[4],
2185
    const GDALPDFWriter::ObjectStyle &os, double dfRadius, int &bboxXMin,
2186
    int &bboxYMin, int &bboxXMax, int &bboxYMax)
2187
{
2188
    if (wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint &&
233✔
2189
        os.nImageSymbolId.toBool())
86✔
2190
    {
2191
        const double dfSemiWidth =
6✔
2192
            (os.nImageWidth >= os.nImageHeight)
6✔
2193
                ? dfRadius
6✔
2194
                : dfRadius * os.nImageWidth / os.nImageHeight;
×
2195
        const double dfSemiHeight =
6✔
2196
            (os.nImageWidth >= os.nImageHeight)
6✔
2197
                ? dfRadius * os.nImageHeight / os.nImageWidth
6✔
2198
                : dfRadius;
2199
        bboxXMin = static_cast<int>(
6✔
2200
            floor(sEnvelope.MinX * adfMatrix[1] + adfMatrix[0] - dfSemiWidth));
6✔
2201
        bboxYMin = static_cast<int>(
6✔
2202
            floor(sEnvelope.MinY * adfMatrix[3] + adfMatrix[2] - dfSemiHeight));
6✔
2203
        bboxXMax = static_cast<int>(
6✔
2204
            ceil(sEnvelope.MaxX * adfMatrix[1] + adfMatrix[0] + dfSemiWidth));
6✔
2205
        bboxYMax = static_cast<int>(
6✔
2206
            ceil(sEnvelope.MaxY * adfMatrix[3] + adfMatrix[2] + dfSemiHeight));
6✔
2207
    }
2208
    else
2209
    {
2210
        double dfMargin = os.dfPenWidth;
141✔
2211
        if (wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint)
141✔
2212
        {
2213
            if (os.osSymbolId == "ogr-sym-6" || os.osSymbolId == "ogr-sym-7")
80✔
2214
            {
2215
                const double dfSqrt3 = 1.73205080757;
12✔
2216
                dfMargin += dfRadius * 2 * dfSqrt3 / 3;
12✔
2217
            }
2218
            else
2219
                dfMargin += dfRadius;
68✔
2220
        }
2221
        bboxXMin = static_cast<int>(
141✔
2222
            floor(sEnvelope.MinX * adfMatrix[1] + adfMatrix[0] - dfMargin));
141✔
2223
        bboxYMin = static_cast<int>(
141✔
2224
            floor(sEnvelope.MinY * adfMatrix[3] + adfMatrix[2] - dfMargin));
141✔
2225
        bboxXMax = static_cast<int>(
141✔
2226
            ceil(sEnvelope.MaxX * adfMatrix[1] + adfMatrix[0] + dfMargin));
141✔
2227
        bboxYMax = static_cast<int>(
141✔
2228
            ceil(sEnvelope.MaxY * adfMatrix[3] + adfMatrix[2] + dfMargin));
141✔
2229
    }
2230
}
147✔
2231

2232
/************************************************************************/
2233
/*                              WriteLink()                             */
2234
/************************************************************************/
2235

2236
GDALPDFObjectNum GDALPDFBaseWriter::WriteLink(OGRFeatureH hFeat,
147✔
2237
                                              const char *pszOGRLinkField,
2238
                                              const double adfMatrix[4],
2239
                                              int bboxXMin, int bboxYMin,
2240
                                              int bboxXMax, int bboxYMax)
2241
{
2242
    GDALPDFObjectNum nAnnotId;
147✔
2243
    int iField = -1;
147✔
2244
    const char *pszLinkVal = nullptr;
147✔
2245
    if (pszOGRLinkField != nullptr &&
232✔
2246
        (iField = OGR_FD_GetFieldIndex(OGR_F_GetDefnRef(hFeat),
85✔
2247
                                       pszOGRLinkField)) >= 0 &&
85✔
2248
        OGR_F_IsFieldSetAndNotNull(hFeat, iField) &&
239✔
2249
        strcmp((pszLinkVal = OGR_F_GetFieldAsString(hFeat, iField)), "") != 0)
7✔
2250
    {
2251
        nAnnotId = AllocNewObject();
7✔
2252
        StartObj(nAnnotId);
7✔
2253
        {
2254
            GDALPDFDictionaryRW oDict;
7✔
2255
            oDict.Add("Type", GDALPDFObjectRW::CreateName("Annot"));
7✔
2256
            oDict.Add("Subtype", GDALPDFObjectRW::CreateName("Link"));
7✔
2257
            oDict.Add("Rect", &(new GDALPDFArrayRW())
7✔
2258
                                   ->Add(bboxXMin)
7✔
2259
                                   .Add(bboxYMin)
7✔
2260
                                   .Add(bboxXMax)
7✔
2261
                                   .Add(bboxYMax));
7✔
2262
            oDict.Add("A", &(new GDALPDFDictionaryRW())
7✔
2263
                                ->Add("S", GDALPDFObjectRW::CreateName("URI"))
7✔
2264
                                .Add("URI", pszLinkVal));
7✔
2265
            oDict.Add("BS",
2266
                      &(new GDALPDFDictionaryRW())
7✔
2267
                           ->Add("Type", GDALPDFObjectRW::CreateName("Border"))
7✔
2268
                           .Add("S", GDALPDFObjectRW::CreateName("S"))
7✔
2269
                           .Add("W", 0));
7✔
2270
            oDict.Add("Border", &(new GDALPDFArrayRW())->Add(0).Add(0).Add(0));
7✔
2271
            oDict.Add("H", GDALPDFObjectRW::CreateName("I"));
7✔
2272

2273
            OGRGeometryH hGeom = OGR_F_GetGeometryRef(hFeat);
7✔
2274
            if (wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPolygon &&
14✔
2275
                OGR_G_GetGeometryCount(hGeom) == 1)
7✔
2276
            {
2277
                OGRGeometryH hSubGeom = OGR_G_GetGeometryRef(hGeom, 0);
7✔
2278
                int nPoints = OGR_G_GetPointCount(hSubGeom);
7✔
2279
                if (nPoints == 4 || nPoints == 5)
7✔
2280
                {
2281
                    std::vector<double> adfX, adfY;
14✔
2282
                    for (int i = 0; i < nPoints; i++)
42✔
2283
                    {
2284
                        double dfX = OGR_G_GetX(hSubGeom, i) * adfMatrix[1] +
35✔
2285
                                     adfMatrix[0];
35✔
2286
                        double dfY = OGR_G_GetY(hSubGeom, i) * adfMatrix[3] +
35✔
2287
                                     adfMatrix[2];
35✔
2288
                        adfX.push_back(dfX);
35✔
2289
                        adfY.push_back(dfY);
35✔
2290
                    }
2291
                    if (nPoints == 4)
7✔
2292
                    {
2293
                        oDict.Add("QuadPoints", &(new GDALPDFArrayRW())
×
2294
                                                     ->Add(adfX[0])
×
2295
                                                     .Add(adfY[0])
×
2296
                                                     .Add(adfX[1])
×
2297
                                                     .Add(adfY[1])
×
2298
                                                     .Add(adfX[2])
×
2299
                                                     .Add(adfY[2])
×
2300
                                                     .Add(adfX[0])
×
2301
                                                     .Add(adfY[0]));
×
2302
                    }
2303
                    else if (nPoints == 5)
7✔
2304
                    {
2305
                        oDict.Add("QuadPoints", &(new GDALPDFArrayRW())
7✔
2306
                                                     ->Add(adfX[0])
7✔
2307
                                                     .Add(adfY[0])
7✔
2308
                                                     .Add(adfX[1])
7✔
2309
                                                     .Add(adfY[1])
7✔
2310
                                                     .Add(adfX[2])
7✔
2311
                                                     .Add(adfY[2])
7✔
2312
                                                     .Add(adfX[3])
7✔
2313
                                                     .Add(adfY[3]));
7✔
2314
                    }
2315
                }
2316
            }
2317

2318
            VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
7✔
2319
        }
2320
        EndObj();
7✔
2321
    }
2322
    return nAnnotId;
147✔
2323
}
2324

2325
/************************************************************************/
2326
/*                        GenerateDrawingStream()                       */
2327
/************************************************************************/
2328

2329
CPLString GDALPDFBaseWriter::GenerateDrawingStream(OGRGeometryH hGeom,
154✔
2330
                                                   const double adfMatrix[4],
2331
                                                   ObjectStyle &os,
2332
                                                   double dfRadius)
2333
{
2334
    CPLString osDS;
154✔
2335

2336
    if (!os.nImageSymbolId.toBool())
154✔
2337
    {
2338
        osDS += CPLOPrintf("%f w\n"
294✔
2339
                           "0 J\n"
2340
                           "0 j\n"
2341
                           "10 M\n"
2342
                           "[%s]0 d\n",
2343
                           os.dfPenWidth, os.osDashArray.c_str());
147✔
2344

2345
        osDS += CPLOPrintf("%f %f %f RG\n", os.nPenR / 255.0, os.nPenG / 255.0,
147✔
2346
                           os.nPenB / 255.0);
147✔
2347
        osDS += CPLOPrintf("%f %f %f rg\n", os.nBrushR / 255.0,
147✔
2348
                           os.nBrushG / 255.0, os.nBrushB / 255.0);
147✔
2349
    }
2350

2351
    if ((os.bHasPenBrushOrSymbol || os.osLabelText.empty()) &&
308✔
2352
        wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint)
154✔
2353
    {
2354
        double dfX = OGR_G_GetX(hGeom, 0) * adfMatrix[1] + adfMatrix[0];
91✔
2355
        double dfY = OGR_G_GetY(hGeom, 0) * adfMatrix[3] + adfMatrix[2];
91✔
2356

2357
        if (os.nImageSymbolId.toBool())
91✔
2358
        {
2359
            const double dfSemiWidth =
7✔
2360
                (os.nImageWidth >= os.nImageHeight)
7✔
2361
                    ? dfRadius
7✔
2362
                    : dfRadius * os.nImageWidth / os.nImageHeight;
×
2363
            const double dfSemiHeight =
7✔
2364
                (os.nImageWidth >= os.nImageHeight)
7✔
2365
                    ? dfRadius * os.nImageHeight / os.nImageWidth
7✔
2366
                    : dfRadius;
2367
            osDS += CPLOPrintf("%f 0 0 %f %f %f cm\n", 2 * dfSemiWidth,
14✔
2368
                               2 * dfSemiHeight, dfX - dfSemiWidth,
2369
                               dfY - dfSemiHeight);
7✔
2370
            osDS += CPLOPrintf("/SymImage%d Do\n", os.nImageSymbolId.toInt());
7✔
2371
        }
2372
        else if (os.osSymbolId == "")
84✔
2373
            os.osSymbolId = "ogr-sym-3"; /* symbol by default */
20✔
2374
        else if (!(os.osSymbolId == "ogr-sym-0" ||
122✔
2375
                   os.osSymbolId == "ogr-sym-1" ||
58✔
2376
                   os.osSymbolId == "ogr-sym-2" ||
48✔
2377
                   os.osSymbolId == "ogr-sym-3" ||
42✔
2378
                   os.osSymbolId == "ogr-sym-4" ||
36✔
2379
                   os.osSymbolId == "ogr-sym-5" ||
30✔
2380
                   os.osSymbolId == "ogr-sym-6" ||
24✔
2381
                   os.osSymbolId == "ogr-sym-7" ||
18✔
2382
                   os.osSymbolId == "ogr-sym-8" ||
12✔
2383
                   os.osSymbolId == "ogr-sym-9"))
6✔
2384
        {
2385
            CPLDebug("PDF", "Unhandled symbol id : %s. Using ogr-sym-3 instead",
×
2386
                     os.osSymbolId.c_str());
2387
            os.osSymbolId = "ogr-sym-3";
×
2388
        }
2389

2390
        if (os.osSymbolId == "ogr-sym-0") /* cross (+)  */
91✔
2391
        {
2392
            osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius, dfY);
6✔
2393
            osDS += CPLOPrintf("%f %f l\n", dfX + dfRadius, dfY);
6✔
2394
            osDS += CPLOPrintf("%f %f m\n", dfX, dfY - dfRadius);
6✔
2395
            osDS += CPLOPrintf("%f %f l\n", dfX, dfY + dfRadius);
6✔
2396
            osDS += CPLOPrintf("S\n");
6✔
2397
        }
2398
        else if (os.osSymbolId == "ogr-sym-1") /* diagcross (X) */
85✔
2399
        {
2400
            osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius, dfY - dfRadius);
10✔
2401
            osDS += CPLOPrintf("%f %f l\n", dfX + dfRadius, dfY + dfRadius);
10✔
2402
            osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius, dfY + dfRadius);
10✔
2403
            osDS += CPLOPrintf("%f %f l\n", dfX + dfRadius, dfY - dfRadius);
10✔
2404
            osDS += CPLOPrintf("S\n");
10✔
2405
        }
2406
        else if (os.osSymbolId == "ogr-sym-2" ||
144✔
2407
                 os.osSymbolId == "ogr-sym-3") /* circle */
69✔
2408
        {
2409
            /* See http://www.whizkidtech.redprince.net/bezier/circle/kappa/ */
2410
            const double dfKappa = 0.5522847498;
32✔
2411

2412
            osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius, dfY);
32✔
2413
            osDS +=
2414
                CPLOPrintf("%f %f %f %f %f %f c\n", dfX - dfRadius,
32✔
2415
                           dfY - dfRadius * dfKappa, dfX - dfRadius * dfKappa,
32✔
2416
                           dfY - dfRadius, dfX, dfY - dfRadius);
32✔
2417
            osDS +=
2418
                CPLOPrintf("%f %f %f %f %f %f c\n", dfX + dfRadius * dfKappa,
32✔
2419
                           dfY - dfRadius, dfX + dfRadius,
2420
                           dfY - dfRadius * dfKappa, dfX + dfRadius, dfY);
32✔
2421
            osDS +=
2422
                CPLOPrintf("%f %f %f %f %f %f c\n", dfX + dfRadius,
32✔
2423
                           dfY + dfRadius * dfKappa, dfX + dfRadius * dfKappa,
32✔
2424
                           dfY + dfRadius, dfX, dfY + dfRadius);
32✔
2425
            osDS +=
2426
                CPLOPrintf("%f %f %f %f %f %f c\n", dfX - dfRadius * dfKappa,
32✔
2427
                           dfY + dfRadius, dfX - dfRadius,
2428
                           dfY + dfRadius * dfKappa, dfX - dfRadius, dfY);
32✔
2429
            if (os.osSymbolId == "ogr-sym-2")
32✔
2430
                osDS += CPLOPrintf("s\n"); /* not filled */
6✔
2431
            else
2432
                osDS += CPLOPrintf("b*\n"); /* filled */
26✔
2433
        }
2434
        else if (os.osSymbolId == "ogr-sym-4" ||
80✔
2435
                 os.osSymbolId == "ogr-sym-5") /* square */
37✔
2436
        {
2437
            osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius, dfY + dfRadius);
12✔
2438
            osDS += CPLOPrintf("%f %f l\n", dfX + dfRadius, dfY + dfRadius);
12✔
2439
            osDS += CPLOPrintf("%f %f l\n", dfX + dfRadius, dfY - dfRadius);
12✔
2440
            osDS += CPLOPrintf("%f %f l\n", dfX - dfRadius, dfY - dfRadius);
12✔
2441
            if (os.osSymbolId == "ogr-sym-4")
12✔
2442
                osDS += CPLOPrintf("s\n"); /* not filled */
6✔
2443
            else
2444
                osDS += CPLOPrintf("b*\n"); /* filled */
6✔
2445
        }
2446
        else if (os.osSymbolId == "ogr-sym-6" ||
56✔
2447
                 os.osSymbolId == "ogr-sym-7") /* triangle */
25✔
2448
        {
2449
            const double dfSqrt3 = 1.73205080757;
12✔
2450
            osDS += CPLOPrintf("%f %f m\n", dfX - dfRadius,
12✔
2451
                               dfY - dfRadius * dfSqrt3 / 3);
12✔
2452
            osDS +=
2453
                CPLOPrintf("%f %f l\n", dfX, dfY + 2 * dfRadius * dfSqrt3 / 3);
12✔
2454
            osDS += CPLOPrintf("%f %f l\n", dfX + dfRadius,
12✔
2455
                               dfY - dfRadius * dfSqrt3 / 3);
12✔
2456
            if (os.osSymbolId == "ogr-sym-6")
12✔
2457
                osDS += CPLOPrintf("s\n"); /* not filled */
6✔
2458
            else
2459
                osDS += CPLOPrintf("b*\n"); /* filled */
6✔
2460
        }
2461
        else if (os.osSymbolId == "ogr-sym-8" ||
32✔
2462
                 os.osSymbolId == "ogr-sym-9") /* star */
13✔
2463
        {
2464
            const double dfSin18divSin126 = 0.38196601125;
12✔
2465
            osDS += CPLOPrintf("%f %f m\n", dfX, dfY + dfRadius);
12✔
2466
            for (int i = 1; i < 10; i++)
120✔
2467
            {
2468
                double dfFactor = ((i % 2) == 1) ? dfSin18divSin126 : 1.0;
108✔
2469
                osDS += CPLOPrintf("%f %f l\n",
108✔
2470
                                   dfX + cos(M_PI / 2 - i * M_PI * 36 / 180) *
108✔
2471
                                             dfRadius * dfFactor,
108✔
2472
                                   dfY + sin(M_PI / 2 - i * M_PI * 36 / 180) *
108✔
2473
                                             dfRadius * dfFactor);
108✔
2474
            }
2475
            if (os.osSymbolId == "ogr-sym-8")
12✔
2476
                osDS += CPLOPrintf("s\n"); /* not filled */
6✔
2477
            else
2478
                osDS += CPLOPrintf("b*\n"); /* filled */
6✔
2479
        }
2480
    }
2481
    else
2482
    {
2483
        DrawGeometry(osDS, hGeom, adfMatrix);
63✔
2484
    }
2485

2486
    return osDS;
154✔
2487
}
2488

2489
/************************************************************************/
2490
/*                          WriteAttributes()                           */
2491
/************************************************************************/
2492

2493
GDALPDFObjectNum GDALPDFBaseWriter::WriteAttributes(
115✔
2494
    OGRFeatureH hFeat, const std::vector<CPLString> &aosIncludedFields,
2495
    const char *pszOGRDisplayField, int nMCID, const GDALPDFObjectNum &oParent,
2496
    const GDALPDFObjectNum &oPage, CPLString &osOutFeatureName)
2497
{
2498

2499
    int iField = -1;
115✔
2500
    if (pszOGRDisplayField)
115✔
2501
        iField =
2502
            OGR_FD_GetFieldIndex(OGR_F_GetDefnRef(hFeat), pszOGRDisplayField);
17✔
2503
    if (iField >= 0)
115✔
2504
        osOutFeatureName = OGR_F_GetFieldAsString(hFeat, iField);
7✔
2505
    else
2506
        osOutFeatureName =
2507
            CPLSPrintf("feature" CPL_FRMT_GIB, OGR_F_GetFID(hFeat));
108✔
2508

2509
    auto nFeatureUserProperties = AllocNewObject();
115✔
2510
    StartObj(nFeatureUserProperties);
115✔
2511

2512
    GDALPDFDictionaryRW oDict;
115✔
2513

2514
    GDALPDFDictionaryRW *poDictA = new GDALPDFDictionaryRW();
115✔
2515
    oDict.Add("A", poDictA);
115✔
2516
    poDictA->Add("O", GDALPDFObjectRW::CreateName("UserProperties"));
115✔
2517

2518
    GDALPDFArrayRW *poArray = new GDALPDFArrayRW();
115✔
2519
    for (const auto &fieldName : aosIncludedFields)
495✔
2520
    {
2521
        int i = OGR_F_GetFieldIndex(hFeat, fieldName);
380✔
2522
        if (i >= 0 && OGR_F_IsFieldSetAndNotNull(hFeat, i))
380✔
2523
        {
2524
            OGRFieldDefnH hFDefn = OGR_F_GetFieldDefnRef(hFeat, i);
203✔
2525
            GDALPDFDictionaryRW *poKV = new GDALPDFDictionaryRW();
203✔
2526
            poKV->Add("N", OGR_Fld_GetNameRef(hFDefn));
203✔
2527
            if (OGR_Fld_GetType(hFDefn) == OFTInteger)
203✔
2528
                poKV->Add("V", OGR_F_GetFieldAsInteger(hFeat, i));
49✔
2529
            else if (OGR_Fld_GetType(hFDefn) == OFTReal)
154✔
2530
                poKV->Add("V", OGR_F_GetFieldAsDouble(hFeat, i));
33✔
2531
            else
2532
                poKV->Add("V", OGR_F_GetFieldAsString(hFeat, i));
121✔
2533
            poArray->Add(poKV);
203✔
2534
        }
2535
    }
2536

2537
    poDictA->Add("P", poArray);
115✔
2538

2539
    oDict.Add("K", nMCID);
115✔
2540
    oDict.Add("P", oParent, 0);
115✔
2541
    oDict.Add("Pg", oPage, 0);
115✔
2542
    oDict.Add("S", GDALPDFObjectRW::CreateName("feature"));
115✔
2543
    oDict.Add("T", osOutFeatureName);
115✔
2544

2545
    VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
115✔
2546

2547
    EndObj();
115✔
2548

2549
    return nFeatureUserProperties;
230✔
2550
}
2551

2552
/************************************************************************/
2553
/*                            WriteLabel()                              */
2554
/************************************************************************/
2555

2556
GDALPDFObjectNum GDALPDFBaseWriter::WriteLabel(
11✔
2557
    OGRGeometryH hGeom, const double adfMatrix[4], ObjectStyle &os,
2558
    PDFCompressMethod eStreamCompressMethod, double bboxXMin, double bboxYMin,
2559
    double bboxXMax, double bboxYMax)
2560
{
2561
    /* -------------------------------------------------------------- */
2562
    /*  Work out the text metrics for alignment purposes              */
2563
    /* -------------------------------------------------------------- */
2564
    double dfWidth, dfHeight;
2565
    CalculateText(os.osLabelText, os.osTextFont, os.dfTextSize, os.bTextBold,
11✔
2566
                  os.bTextItalic, dfWidth, dfHeight);
11✔
2567
    dfWidth *= os.dfTextStretch;
11✔
2568

2569
    if (os.nTextAnchor % 3 == 2)  // horizontal center
11✔
2570
    {
2571
        os.dfTextDx -= (dfWidth / 2) * cos(os.dfTextAngle);
×
2572
        os.dfTextDy -= (dfWidth / 2) * sin(os.dfTextAngle);
×
2573
    }
2574
    else if (os.nTextAnchor % 3 == 0)  // right
11✔
2575
    {
2576
        os.dfTextDx -= dfWidth * cos(os.dfTextAngle);
×
2577
        os.dfTextDy -= dfWidth * sin(os.dfTextAngle);
×
2578
    }
2579

2580
    if (os.nTextAnchor >= 4 && os.nTextAnchor <= 6)  // vertical center
11✔
2581
    {
2582
        os.dfTextDx += (dfHeight / 2) * sin(os.dfTextAngle);
6✔
2583
        os.dfTextDy -= (dfHeight / 2) * cos(os.dfTextAngle);
6✔
2584
    }
2585
    else if (os.nTextAnchor >= 7 && os.nTextAnchor <= 9)  // top
5✔
2586
    {
2587
        os.dfTextDx += dfHeight * sin(os.dfTextAngle);
×
2588
        os.dfTextDy -= dfHeight * cos(os.dfTextAngle);
×
2589
    }
2590
    // modes 10,11,12 (baseline) unsupported for the time being
2591

2592
    /* -------------------------------------------------------------- */
2593
    /*  Write object dictionary                                       */
2594
    /* -------------------------------------------------------------- */
2595
    auto nObjectId = AllocNewObject();
11✔
2596
    GDALPDFDictionaryRW oDict;
11✔
2597

2598
    oDict.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
11✔
2599
        .Add("BBox", &((new GDALPDFArrayRW())->Add(bboxXMin).Add(bboxYMin))
11✔
2600
                          .Add(bboxXMax)
11✔
2601
                          .Add(bboxYMax))
11✔
2602
        .Add("Subtype", GDALPDFObjectRW::CreateName("Form"));
11✔
2603

2604
    GDALPDFDictionaryRW *poResources = new GDALPDFDictionaryRW();
11✔
2605

2606
    if (os.nTextA != 255)
11✔
2607
    {
2608
        GDALPDFDictionaryRW *poGS1 = new GDALPDFDictionaryRW();
2✔
2609
        poGS1->Add("Type", GDALPDFObjectRW::CreateName("ExtGState"));
2✔
2610
        poGS1->Add("ca", (os.nTextA == 127 || os.nTextA == 128)
2✔
2611
                             ? 0.5
2612
                             : os.nTextA / 255.0);
4✔
2613

2614
        GDALPDFDictionaryRW *poExtGState = new GDALPDFDictionaryRW();
2✔
2615
        poExtGState->Add("GS1", poGS1);
2✔
2616

2617
        poResources->Add("ExtGState", poExtGState);
2✔
2618
    }
2619

2620
    GDALPDFDictionaryRW *poDictF1 = new GDALPDFDictionaryRW();
11✔
2621
    poDictF1->Add("Type", GDALPDFObjectRW::CreateName("Font"));
11✔
2622
    poDictF1->Add("BaseFont", GDALPDFObjectRW::CreateName(os.osTextFont));
11✔
2623
    poDictF1->Add("Encoding", GDALPDFObjectRW::CreateName("WinAnsiEncoding"));
11✔
2624
    poDictF1->Add("Subtype", GDALPDFObjectRW::CreateName("Type1"));
11✔
2625

2626
    GDALPDFDictionaryRW *poDictFont = new GDALPDFDictionaryRW();
11✔
2627
    poDictFont->Add("F1", poDictF1);
11✔
2628
    poResources->Add("Font", poDictFont);
11✔
2629

2630
    oDict.Add("Resources", poResources);
11✔
2631

2632
    StartObjWithStream(nObjectId, oDict,
11✔
2633
                       eStreamCompressMethod != COMPRESS_NONE);
2634

2635
    /* -------------------------------------------------------------- */
2636
    /*  Write object stream                                           */
2637
    /* -------------------------------------------------------------- */
2638

2639
    double dfX =
2640
        OGR_G_GetX(hGeom, 0) * adfMatrix[1] + adfMatrix[0] + os.dfTextDx;
11✔
2641
    double dfY =
2642
        OGR_G_GetY(hGeom, 0) * adfMatrix[3] + adfMatrix[2] + os.dfTextDy;
11✔
2643

2644
    VSIFPrintfL(m_fp, "q\n");
11✔
2645
    VSIFPrintfL(m_fp, "BT\n");
11✔
2646
    if (os.nTextA != 255)
11✔
2647
    {
2648
        VSIFPrintfL(m_fp, "/GS1 gs\n");
2✔
2649
    }
2650

2651
    VSIFPrintfL(m_fp, "%f %f %f %f %f %f Tm\n",
11✔
2652
                cos(os.dfTextAngle) * adfMatrix[1] * os.dfTextStretch,
11✔
2653
                sin(os.dfTextAngle) * adfMatrix[3] * os.dfTextStretch,
11✔
2654
                -sin(os.dfTextAngle) * adfMatrix[1],
11✔
2655
                cos(os.dfTextAngle) * adfMatrix[3], dfX, dfY);
11✔
2656

2657
    VSIFPrintfL(m_fp, "%f %f %f rg\n", os.nTextR / 255.0, os.nTextG / 255.0,
11✔
2658
                os.nTextB / 255.0);
11✔
2659
    // The factor of adfMatrix[1] is introduced in the call to SetUnit near the
2660
    // top of this function. Because we are handling the 2D stretch correctly in
2661
    // Tm above, we don't need that factor here
2662
    VSIFPrintfL(m_fp, "/F1 %f Tf\n", os.dfTextSize / adfMatrix[1]);
11✔
2663
    VSIFPrintfL(m_fp, "(");
11✔
2664
    for (size_t i = 0; i < os.osLabelText.size(); i++)
110✔
2665
    {
2666
        if (os.osLabelText[i] == '(' || os.osLabelText[i] == ')' ||
198✔
2667
            os.osLabelText[i] == '\\')
99✔
2668
        {
2669
            VSIFPrintfL(m_fp, "\\%c", os.osLabelText[i]);
×
2670
        }
2671
        else
2672
        {
2673
            VSIFPrintfL(m_fp, "%c", os.osLabelText[i]);
99✔
2674
        }
2675
    }
2676
    VSIFPrintfL(m_fp, ") Tj\n");
11✔
2677
    VSIFPrintfL(m_fp, "ET\n");
11✔
2678
    VSIFPrintfL(m_fp, "Q");
11✔
2679

2680
    EndObjWithStream();
11✔
2681

2682
    return nObjectId;
22✔
2683
}
2684

2685
/************************************************************************/
2686
/*                          WriteOGRFeature()                           */
2687
/************************************************************************/
2688

2689
int GDALPDFWriter::WriteOGRFeature(GDALPDFLayerDesc &osVectorDesc,
154✔
2690
                                   OGRFeatureH hFeat,
2691
                                   OGRCoordinateTransformationH hCT,
2692
                                   const char *pszOGRDisplayField,
2693
                                   const char *pszOGRLinkField,
2694
                                   int bWriteOGRAttributes, int &iObj)
2695
{
2696
    GDALDataset *const poClippingDS = oPageContext.poClippingDS;
154✔
2697
    const int nHeight = poClippingDS->GetRasterYSize();
154✔
2698
    const double dfUserUnit = oPageContext.dfDPI * USER_UNIT_IN_INCH;
154✔
2699
    double adfGeoTransform[6];
2700
    poClippingDS->GetGeoTransform(adfGeoTransform);
154✔
2701

2702
    double adfMatrix[4];
2703
    adfMatrix[0] = -adfGeoTransform[0] / (adfGeoTransform[1] * dfUserUnit) +
154✔
2704
                   oPageContext.sMargins.nLeft;
154✔
2705
    adfMatrix[1] = 1.0 / (adfGeoTransform[1] * dfUserUnit);
154✔
2706
    adfMatrix[2] = -(adfGeoTransform[3] + adfGeoTransform[5] * nHeight) /
154✔
2707
                       (-adfGeoTransform[5] * dfUserUnit) +
154✔
2708
                   oPageContext.sMargins.nBottom;
154✔
2709
    adfMatrix[3] = 1.0 / (-adfGeoTransform[5] * dfUserUnit);
154✔
2710

2711
    OGRGeometryH hGeom = OGR_F_GetGeometryRef(hFeat);
154✔
2712
    if (hGeom == nullptr)
154✔
2713
    {
2714
        return TRUE;
×
2715
    }
2716

2717
    OGREnvelope sEnvelope;
154✔
2718

2719
    if (hCT != nullptr)
154✔
2720
    {
2721
        /* Reproject */
2722
        if (OGR_G_Transform(hGeom, hCT) != OGRERR_NONE)
12✔
2723
        {
2724
            return TRUE;
2✔
2725
        }
2726

2727
        OGREnvelope sRasterEnvelope;
12✔
2728
        sRasterEnvelope.MinX = adfGeoTransform[0];
12✔
2729
        sRasterEnvelope.MinY =
12✔
2730
            adfGeoTransform[3] +
24✔
2731
            poClippingDS->GetRasterYSize() * adfGeoTransform[5];
12✔
2732
        sRasterEnvelope.MaxX =
12✔
2733
            adfGeoTransform[0] +
24✔
2734
            poClippingDS->GetRasterXSize() * adfGeoTransform[1];
12✔
2735
        sRasterEnvelope.MaxY = adfGeoTransform[3];
12✔
2736

2737
        // Check that the reprojected geometry intersects the raster envelope.
2738
        OGR_G_GetEnvelope(hGeom, &sEnvelope);
12✔
2739
        if (!(sRasterEnvelope.Intersects(sEnvelope)))
12✔
2740
        {
2741
            return TRUE;
2✔
2742
        }
2743
    }
2744
    else
2745
    {
2746
        OGR_G_GetEnvelope(hGeom, &sEnvelope);
142✔
2747
    }
2748

2749
    /* -------------------------------------------------------------- */
2750
    /*  Get style                                                     */
2751
    /* -------------------------------------------------------------- */
2752
    ObjectStyle os;
304✔
2753
    GetObjectStyle(nullptr, hFeat, adfMatrix, m_oMapSymbolFilenameToDesc, os);
152✔
2754

2755
    double dfRadius = os.dfSymbolSize * dfUserUnit;
152✔
2756

2757
    // For a POINT with only a LABEL style string and non-empty text, we do not
2758
    // output any geometry other than the text itself.
2759
    const bool bLabelOnly =
2760
        wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint &&
152✔
2761
        !os.bHasPenBrushOrSymbol && !os.osLabelText.empty();
152✔
2762

2763
    /* -------------------------------------------------------------- */
2764
    /*  Write object dictionary                                       */
2765
    /* -------------------------------------------------------------- */
2766
    if (!bLabelOnly)
152✔
2767
    {
2768
        auto nObjectId = AllocNewObject();
146✔
2769

2770
        osVectorDesc.aIds.push_back(nObjectId);
146✔
2771

2772
        int bboxXMin, bboxYMin, bboxXMax, bboxYMax;
2773
        ComputeIntBBox(hGeom, sEnvelope, adfMatrix, os, dfRadius, bboxXMin,
146✔
2774
                       bboxYMin, bboxXMax, bboxYMax);
2775

2776
        auto nLinkId = WriteLink(hFeat, pszOGRLinkField, adfMatrix, bboxXMin,
2777
                                 bboxYMin, bboxXMax, bboxYMax);
146✔
2778
        if (nLinkId.toBool())
146✔
2779
            oPageContext.anAnnotationsId.push_back(nLinkId);
6✔
2780

2781
        GDALPDFDictionaryRW oDict;
292✔
2782
        GDALPDFArrayRW *poBBOX = new GDALPDFArrayRW();
146✔
2783
        poBBOX->Add(bboxXMin).Add(bboxYMin).Add(bboxXMax).Add(bboxYMax);
146✔
2784
        oDict.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
146✔
2785
            .Add("BBox", poBBOX)
146✔
2786
            .Add("Subtype", GDALPDFObjectRW::CreateName("Form"));
146✔
2787

2788
        GDALPDFDictionaryRW *poGS1 = new GDALPDFDictionaryRW();
146✔
2789
        poGS1->Add("Type", GDALPDFObjectRW::CreateName("ExtGState"));
146✔
2790
        if (os.nPenA != 255)
146✔
2791
            poGS1->Add("CA", (os.nPenA == 127 || os.nPenA == 128)
×
2792
                                 ? 0.5
2793
                                 : os.nPenA / 255.0);
×
2794
        if (os.nBrushA != 255)
146✔
2795
            poGS1->Add("ca", (os.nBrushA == 127 || os.nBrushA == 128)
×
2796
                                 ? 0.5
2797
                                 : os.nBrushA / 255.0);
80✔
2798

2799
        GDALPDFDictionaryRW *poExtGState = new GDALPDFDictionaryRW();
146✔
2800
        poExtGState->Add("GS1", poGS1);
146✔
2801

2802
        GDALPDFDictionaryRW *poResources = new GDALPDFDictionaryRW();
146✔
2803
        poResources->Add("ExtGState", poExtGState);
146✔
2804

2805
        if (os.nImageSymbolId.toBool())
146✔
2806
        {
2807
            GDALPDFDictionaryRW *poDictXObject = new GDALPDFDictionaryRW();
6✔
2808
            poResources->Add("XObject", poDictXObject);
6✔
2809

2810
            poDictXObject->Add(
2811
                CPLSPrintf("SymImage%d", os.nImageSymbolId.toInt()),
2812
                os.nImageSymbolId, 0);
6✔
2813
        }
2814

2815
        oDict.Add("Resources", poResources);
146✔
2816

2817
        StartObjWithStream(nObjectId, oDict,
146✔
2818
                           oPageContext.eStreamCompressMethod != COMPRESS_NONE);
146✔
2819

2820
        /* -------------------------------------------------------------- */
2821
        /*  Write object stream                                           */
2822
        /* -------------------------------------------------------------- */
2823
        VSIFPrintfL(m_fp, "q\n");
146✔
2824

2825
        VSIFPrintfL(m_fp, "/GS1 gs\n");
146✔
2826

2827
        VSIFPrintfL(
146✔
2828
            m_fp, "%s",
2829
            GenerateDrawingStream(hGeom, adfMatrix, os, dfRadius).c_str());
292✔
2830

2831
        VSIFPrintfL(m_fp, "Q");
146✔
2832

2833
        EndObjWithStream();
146✔
2834
    }
2835
    else
2836
    {
2837
        osVectorDesc.aIds.push_back(GDALPDFObjectNum());
6✔
2838
    }
2839

2840
    /* -------------------------------------------------------------- */
2841
    /*  Write label                                                   */
2842
    /* -------------------------------------------------------------- */
2843
    if (!os.osLabelText.empty() &&
160✔
2844
        wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint)
8✔
2845
    {
2846
        if (!osVectorDesc.nOCGTextId.toBool())
8✔
2847
            osVectorDesc.nOCGTextId = WriteOCG("Text", osVectorDesc.nOCGId);
8✔
2848

2849
        int nWidth = poClippingDS->GetRasterXSize();
8✔
2850
        double dfWidthInUserUnit = nWidth / dfUserUnit +
8✔
2851
                                   oPageContext.sMargins.nLeft +
8✔
2852
                                   oPageContext.sMargins.nRight;
8✔
2853
        double dfHeightInUserUnit = nHeight / dfUserUnit +
8✔
2854
                                    oPageContext.sMargins.nBottom +
8✔
2855
                                    oPageContext.sMargins.nTop;
8✔
2856
        auto nObjectId =
2857
            WriteLabel(hGeom, adfMatrix, os, oPageContext.eStreamCompressMethod,
2858
                       0, 0, dfWidthInUserUnit, dfHeightInUserUnit);
8✔
2859

2860
        osVectorDesc.aIdsText.push_back(nObjectId);
8✔
2861
    }
2862
    else
2863
    {
2864
        osVectorDesc.aIdsText.push_back(GDALPDFObjectNum());
144✔
2865
    }
2866

2867
    /* -------------------------------------------------------------- */
2868
    /*  Write feature attributes                                      */
2869
    /* -------------------------------------------------------------- */
2870
    GDALPDFObjectNum nFeatureUserProperties;
152✔
2871

2872
    CPLString osFeatureName;
152✔
2873

2874
    if (bWriteOGRAttributes)
152✔
2875
    {
2876
        nFeatureUserProperties = WriteAttributes(
2877
            hFeat, osVectorDesc.aosIncludedFields, pszOGRDisplayField, iObj,
107✔
2878
            osVectorDesc.nFeatureLayerId, oPageContext.nPageId, osFeatureName);
107✔
2879
    }
2880

2881
    iObj++;
152✔
2882

2883
    osVectorDesc.aUserPropertiesIds.push_back(nFeatureUserProperties);
152✔
2884
    osVectorDesc.aFeatureNames.push_back(osFeatureName);
152✔
2885

2886
    return TRUE;
152✔
2887
}
2888

2889
/************************************************************************/
2890
/*                               EndPage()                              */
2891
/************************************************************************/
2892

2893
int GDALPDFWriter::EndPage(const char *pszExtraImages,
112✔
2894
                           const char *pszExtraStream,
2895
                           const char *pszExtraLayerName,
2896
                           const char *pszOffLayers,
2897
                           const char *pszExclusiveLayers)
2898
{
2899
    auto nLayerExtraId = WriteOCG(pszExtraLayerName);
112✔
2900
    if (pszOffLayers)
112✔
2901
        m_osOffLayers = pszOffLayers;
2✔
2902
    if (pszExclusiveLayers)
112✔
2903
        m_osExclusiveLayers = pszExclusiveLayers;
2✔
2904

2905
    /* -------------------------------------------------------------- */
2906
    /*  Write extra images                                            */
2907
    /* -------------------------------------------------------------- */
2908
    std::vector<GDALPDFImageDesc> asExtraImageDesc;
224✔
2909
    if (pszExtraImages)
112✔
2910
    {
2911
        if (GDALGetDriverCount() == 0)
2✔
2912
            GDALAllRegister();
×
2913

2914
        char **papszExtraImagesTokens =
2915
            CSLTokenizeString2(pszExtraImages, ",", 0);
2✔
2916
        double dfUserUnit = oPageContext.dfDPI * USER_UNIT_IN_INCH;
2✔
2917
        int nCount = CSLCount(papszExtraImagesTokens);
2✔
2918
        for (int i = 0; i + 4 <= nCount; /* */)
6✔
2919
        {
2920
            const char *pszImageFilename = papszExtraImagesTokens[i + 0];
4✔
2921
            double dfX = CPLAtof(papszExtraImagesTokens[i + 1]);
4✔
2922
            double dfY = CPLAtof(papszExtraImagesTokens[i + 2]);
4✔
2923
            double dfScale = CPLAtof(papszExtraImagesTokens[i + 3]);
4✔
2924
            const char *pszLinkVal = nullptr;
4✔
2925
            i += 4;
4✔
2926
            if (i < nCount &&
4✔
2927
                STARTS_WITH_CI(papszExtraImagesTokens[i], "link="))
2✔
2928
            {
2929
                pszLinkVal = papszExtraImagesTokens[i] + 5;
2✔
2930
                i++;
2✔
2931
            }
2932
            auto poImageDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
2933
                pszImageFilename, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
2934
                nullptr, nullptr, nullptr));
8✔
2935
            if (poImageDS)
4✔
2936
            {
2937
                auto nImageId = WriteBlock(
2938
                    poImageDS.get(), 0, 0, poImageDS->GetRasterXSize(),
2939
                    poImageDS->GetRasterYSize(), GDALPDFObjectNum(),
×
2940
                    COMPRESS_DEFAULT, 0, -1, nullptr, nullptr, nullptr);
4✔
2941

2942
                if (nImageId.toBool())
4✔
2943
                {
2944
                    GDALPDFImageDesc oImageDesc;
4✔
2945
                    oImageDesc.nImageId = nImageId;
4✔
2946
                    oImageDesc.dfXSize =
4✔
2947
                        poImageDS->GetRasterXSize() / dfUserUnit * dfScale;
4✔
2948
                    oImageDesc.dfYSize =
4✔
2949
                        poImageDS->GetRasterYSize() / dfUserUnit * dfScale;
4✔
2950
                    oImageDesc.dfXOff = dfX;
4✔
2951
                    oImageDesc.dfYOff = dfY;
4✔
2952

2953
                    asExtraImageDesc.push_back(oImageDesc);
4✔
2954

2955
                    if (pszLinkVal != nullptr)
4✔
2956
                    {
2957
                        auto nAnnotId = AllocNewObject();
2✔
2958
                        oPageContext.anAnnotationsId.push_back(nAnnotId);
2✔
2959
                        StartObj(nAnnotId);
2✔
2960
                        {
2961
                            GDALPDFDictionaryRW oDict;
2✔
2962
                            oDict.Add("Type",
2963
                                      GDALPDFObjectRW::CreateName("Annot"));
2✔
2964
                            oDict.Add("Subtype",
2965
                                      GDALPDFObjectRW::CreateName("Link"));
2✔
2966
                            oDict.Add("Rect", &(new GDALPDFArrayRW())
2✔
2967
                                                   ->Add(oImageDesc.dfXOff)
2✔
2968
                                                   .Add(oImageDesc.dfYOff)
2✔
2969
                                                   .Add(oImageDesc.dfXOff +
2✔
2970
                                                        oImageDesc.dfXSize)
2✔
2971
                                                   .Add(oImageDesc.dfYOff +
2✔
2972
                                                        oImageDesc.dfYSize));
2✔
2973
                            oDict.Add(
2974
                                "A",
2975
                                &(new GDALPDFDictionaryRW())
2✔
2976
                                     ->Add("S",
2977
                                           GDALPDFObjectRW::CreateName("URI"))
2✔
2978
                                     .Add("URI", pszLinkVal));
2✔
2979
                            oDict.Add(
2980
                                "BS",
2981
                                &(new GDALPDFDictionaryRW())
2✔
2982
                                     ->Add("Type", GDALPDFObjectRW::CreateName(
2✔
2983
                                                       "Border"))
2✔
2984
                                     .Add("S", GDALPDFObjectRW::CreateName("S"))
2✔
2985
                                     .Add("W", 0));
2✔
2986
                            oDict.Add(
2987
                                "Border",
2988
                                &(new GDALPDFArrayRW())->Add(0).Add(0).Add(0));
2✔
2989
                            oDict.Add("H", GDALPDFObjectRW::CreateName("I"));
2✔
2990

2991
                            VSIFPrintfL(m_fp, "%s\n",
2✔
2992
                                        oDict.Serialize().c_str());
4✔
2993
                        }
2994
                        EndObj();
2✔
2995
                    }
2996
                }
2997
            }
2998
        }
2999
        CSLDestroy(papszExtraImagesTokens);
2✔
3000
    }
3001

3002
    /* -------------------------------------------------------------- */
3003
    /*  Write content stream                                          */
3004
    /* -------------------------------------------------------------- */
3005
    GDALPDFDictionaryRW oDictContent;
112✔
3006
    StartObjWithStream(oPageContext.nContentId, oDictContent,
112✔
3007
                       oPageContext.eStreamCompressMethod != COMPRESS_NONE);
112✔
3008

3009
    /* -------------------------------------------------------------- */
3010
    /*  Write drawing instructions for raster blocks                  */
3011
    /* -------------------------------------------------------------- */
3012
    for (size_t iRaster = 0; iRaster < oPageContext.asRasterDesc.size();
204✔
3013
         iRaster++)
3014
    {
3015
        const GDALPDFRasterDesc &oDesc = oPageContext.asRasterDesc[iRaster];
92✔
3016
        if (oDesc.nOCGRasterId.toBool())
92✔
3017
            VSIFPrintfL(m_fp, "/OC /Lyr%d BDC\n", oDesc.nOCGRasterId.toInt());
6✔
3018

3019
        for (size_t iImage = 0; iImage < oDesc.asImageDesc.size(); iImage++)
278✔
3020
        {
3021
            VSIFPrintfL(m_fp, "q\n");
186✔
3022
            GDALPDFObjectRW *poXSize =
3023
                GDALPDFObjectRW::CreateReal(oDesc.asImageDesc[iImage].dfXSize);
186✔
3024
            GDALPDFObjectRW *poYSize =
3025
                GDALPDFObjectRW::CreateReal(oDesc.asImageDesc[iImage].dfYSize);
186✔
3026
            GDALPDFObjectRW *poXOff =
3027
                GDALPDFObjectRW::CreateReal(oDesc.asImageDesc[iImage].dfXOff);
186✔
3028
            GDALPDFObjectRW *poYOff =
3029
                GDALPDFObjectRW::CreateReal(oDesc.asImageDesc[iImage].dfYOff);
186✔
3030
            VSIFPrintfL(
744✔
3031
                m_fp, "%s 0 0 %s %s %s cm\n", poXSize->Serialize().c_str(),
372✔
3032
                poYSize->Serialize().c_str(), poXOff->Serialize().c_str(),
558✔
3033
                poYOff->Serialize().c_str());
372✔
3034
            delete poXSize;
186✔
3035
            delete poYSize;
186✔
3036
            delete poXOff;
186✔
3037
            delete poYOff;
186✔
3038
            VSIFPrintfL(m_fp, "/Image%d Do\n",
186✔
3039
                        oDesc.asImageDesc[iImage].nImageId.toInt());
186✔
3040
            VSIFPrintfL(m_fp, "Q\n");
186✔
3041
        }
3042

3043
        if (oDesc.nOCGRasterId.toBool())
92✔
3044
            VSIFPrintfL(m_fp, "EMC\n");
6✔
3045
    }
3046

3047
    /* -------------------------------------------------------------- */
3048
    /*  Write drawing instructions for vector features                */
3049
    /* -------------------------------------------------------------- */
3050
    int iObj = 0;
112✔
3051
    for (size_t iLayer = 0; iLayer < oPageContext.asVectorDesc.size(); iLayer++)
153✔
3052
    {
3053
        const GDALPDFLayerDesc &oLayerDesc = oPageContext.asVectorDesc[iLayer];
41✔
3054

3055
        VSIFPrintfL(m_fp, "/OC /Lyr%d BDC\n", oLayerDesc.nOCGId.toInt());
41✔
3056

3057
        for (size_t iVector = 0; iVector < oLayerDesc.aIds.size(); iVector++)
193✔
3058
        {
3059
            if (oLayerDesc.aIds[iVector].toBool())
152✔
3060
            {
3061
                CPLString osName = oLayerDesc.aFeatureNames[iVector];
292✔
3062
                if (!osName.empty())
146✔
3063
                {
3064
                    VSIFPrintfL(m_fp, "/feature <</MCID %d>> BDC\n", iObj);
104✔
3065
                }
3066

3067
                VSIFPrintfL(m_fp, "/Vector%d Do\n",
146✔
3068
                            oLayerDesc.aIds[iVector].toInt());
146✔
3069

3070
                if (!osName.empty())
146✔
3071
                {
3072
                    VSIFPrintfL(m_fp, "EMC\n");
104✔
3073
                }
3074
            }
3075

3076
            iObj++;
152✔
3077
        }
3078

3079
        VSIFPrintfL(m_fp, "EMC\n");
41✔
3080
    }
3081

3082
    /* -------------------------------------------------------------- */
3083
    /*  Write drawing instructions for labels of vector features      */
3084
    /* -------------------------------------------------------------- */
3085
    iObj = 0;
112✔
3086
    for (const GDALPDFLayerDesc &oLayerDesc : oPageContext.asVectorDesc)
153✔
3087
    {
3088
        if (oLayerDesc.nOCGTextId.toBool())
41✔
3089
        {
3090
            VSIFPrintfL(m_fp, "/OC /Lyr%d BDC\n", oLayerDesc.nOCGId.toInt());
8✔
3091
            VSIFPrintfL(m_fp, "/OC /Lyr%d BDC\n",
8✔
3092
                        oLayerDesc.nOCGTextId.toInt());
3093

3094
            for (size_t iVector = 0; iVector < oLayerDesc.aIdsText.size();
104✔
3095
                 iVector++)
3096
            {
3097
                if (oLayerDesc.aIdsText[iVector].toBool())
96✔
3098
                {
3099
                    CPLString osName = oLayerDesc.aFeatureNames[iVector];
16✔
3100
                    if (!osName.empty())
8✔
3101
                    {
3102
                        VSIFPrintfL(m_fp, "/feature <</MCID %d>> BDC\n", iObj);
5✔
3103
                    }
3104

3105
                    VSIFPrintfL(m_fp, "/Text%d Do\n",
8✔
3106
                                oLayerDesc.aIdsText[iVector].toInt());
8✔
3107

3108
                    if (!osName.empty())
8✔
3109
                    {
3110
                        VSIFPrintfL(m_fp, "EMC\n");
5✔
3111
                    }
3112
                }
3113

3114
                iObj++;
96✔
3115
            }
3116

3117
            VSIFPrintfL(m_fp, "EMC\n");
8✔
3118
            VSIFPrintfL(m_fp, "EMC\n");
8✔
3119
        }
3120
        else
3121
            iObj += static_cast<int>(oLayerDesc.aIds.size());
33✔
3122
    }
3123

3124
    /* -------------------------------------------------------------- */
3125
    /*  Write drawing instructions for extra content.                 */
3126
    /* -------------------------------------------------------------- */
3127
    if (pszExtraStream || !asExtraImageDesc.empty())
112✔
3128
    {
3129
        if (nLayerExtraId.toBool())
2✔
3130
            VSIFPrintfL(m_fp, "/OC /Lyr%d BDC\n", nLayerExtraId.toInt());
2✔
3131

3132
        /* -------------------------------------------------------------- */
3133
        /*  Write drawing instructions for extra images.                  */
3134
        /* -------------------------------------------------------------- */
3135
        for (size_t iImage = 0; iImage < asExtraImageDesc.size(); iImage++)
6✔
3136
        {
3137
            VSIFPrintfL(m_fp, "q\n");
4✔
3138
            GDALPDFObjectRW *poXSize =
3139
                GDALPDFObjectRW::CreateReal(asExtraImageDesc[iImage].dfXSize);
4✔
3140
            GDALPDFObjectRW *poYSize =
3141
                GDALPDFObjectRW::CreateReal(asExtraImageDesc[iImage].dfYSize);
4✔
3142
            GDALPDFObjectRW *poXOff =
3143
                GDALPDFObjectRW::CreateReal(asExtraImageDesc[iImage].dfXOff);
4✔
3144
            GDALPDFObjectRW *poYOff =
3145
                GDALPDFObjectRW::CreateReal(asExtraImageDesc[iImage].dfYOff);
4✔
3146
            VSIFPrintfL(
16✔
3147
                m_fp, "%s 0 0 %s %s %s cm\n", poXSize->Serialize().c_str(),
8✔
3148
                poYSize->Serialize().c_str(), poXOff->Serialize().c_str(),
12✔
3149
                poYOff->Serialize().c_str());
8✔
3150
            delete poXSize;
4✔
3151
            delete poYSize;
4✔
3152
            delete poXOff;
4✔
3153
            delete poYOff;
4✔
3154
            VSIFPrintfL(m_fp, "/Image%d Do\n",
4✔
3155
                        asExtraImageDesc[iImage].nImageId.toInt());
4✔
3156
            VSIFPrintfL(m_fp, "Q\n");
4✔
3157
        }
3158

3159
        if (pszExtraStream)
2✔
3160
            VSIFPrintfL(m_fp, "%s\n", pszExtraStream);
2✔
3161

3162
        if (nLayerExtraId.toBool())
2✔
3163
            VSIFPrintfL(m_fp, "EMC\n");
2✔
3164
    }
3165

3166
    EndObjWithStream();
112✔
3167

3168
    /* -------------------------------------------------------------- */
3169
    /*  Write objects for feature tree.                               */
3170
    /* -------------------------------------------------------------- */
3171
    if (m_nStructTreeRootId.toBool())
112✔
3172
    {
3173
        auto nParentTreeId = AllocNewObject();
23✔
3174
        StartObj(nParentTreeId);
23✔
3175
        VSIFPrintfL(m_fp, "<< /Nums [ 0 ");
23✔
3176
        VSIFPrintfL(m_fp, "[ ");
23✔
3177
        for (size_t iLayer = 0; iLayer < oPageContext.asVectorDesc.size();
61✔
3178
             iLayer++)
3179
        {
3180
            const GDALPDFLayerDesc &oLayerDesc =
3181
                oPageContext.asVectorDesc[iLayer];
38✔
3182
            for (size_t iVector = 0; iVector < oLayerDesc.aIds.size();
145✔
3183
                 iVector++)
3184
            {
3185
                const auto &nId = oLayerDesc.aUserPropertiesIds[iVector];
107✔
3186
                if (nId.toBool())
107✔
3187
                    VSIFPrintfL(m_fp, "%d 0 R ", nId.toInt());
107✔
3188
            }
3189
        }
3190
        VSIFPrintfL(m_fp, " ]\n");
23✔
3191
        VSIFPrintfL(m_fp, " ] >> \n");
23✔
3192
        EndObj();
23✔
3193

3194
        StartObj(m_nStructTreeRootId);
23✔
3195
        VSIFPrintfL(m_fp,
23✔
3196
                    "<< "
3197
                    "/Type /StructTreeRoot "
3198
                    "/ParentTree %d 0 R "
3199
                    "/K [ ",
3200
                    nParentTreeId.toInt());
3201
        for (size_t iLayer = 0; iLayer < oPageContext.asVectorDesc.size();
61✔
3202
             iLayer++)
3203
        {
3204
            VSIFPrintfL(
38✔
3205
                m_fp, "%d 0 R ",
3206
                oPageContext.asVectorDesc[iLayer].nFeatureLayerId.toInt());
38✔
3207
        }
3208
        VSIFPrintfL(m_fp, "] >>\n");
23✔
3209
        EndObj();
23✔
3210
    }
3211

3212
    /* -------------------------------------------------------------- */
3213
    /*  Write page resource dictionary.                               */
3214
    /* -------------------------------------------------------------- */
3215
    StartObj(oPageContext.nResourcesId);
112✔
3216
    {
3217
        GDALPDFDictionaryRW oDict;
112✔
3218
        GDALPDFDictionaryRW *poDictXObject = new GDALPDFDictionaryRW();
112✔
3219
        oDict.Add("XObject", poDictXObject);
112✔
3220
        size_t iImage;
3221
        for (const GDALPDFRasterDesc &oDesc : oPageContext.asRasterDesc)
204✔
3222
        {
3223
            for (iImage = 0; iImage < oDesc.asImageDesc.size(); iImage++)
278✔
3224
            {
3225
                poDictXObject->Add(
3226
                    CPLSPrintf("Image%d",
3227
                               oDesc.asImageDesc[iImage].nImageId.toInt()),
186✔
3228
                    oDesc.asImageDesc[iImage].nImageId, 0);
186✔
3229
            }
3230
        }
3231
        for (iImage = 0; iImage < asExtraImageDesc.size(); iImage++)
116✔
3232
        {
3233
            poDictXObject->Add(
3234
                CPLSPrintf("Image%d",
3235
                           asExtraImageDesc[iImage].nImageId.toInt()),
4✔
3236
                asExtraImageDesc[iImage].nImageId, 0);
4✔
3237
        }
3238
        for (const GDALPDFLayerDesc &oLayerDesc : oPageContext.asVectorDesc)
153✔
3239
        {
3240
            for (size_t iVector = 0; iVector < oLayerDesc.aIds.size();
193✔
3241
                 iVector++)
3242
            {
3243
                if (oLayerDesc.aIds[iVector].toBool())
152✔
3244
                    poDictXObject->Add(
3245
                        CPLSPrintf("Vector%d",
3246
                                   oLayerDesc.aIds[iVector].toInt()),
146✔
3247
                        oLayerDesc.aIds[iVector], 0);
292✔
3248
            }
3249
            for (size_t iVector = 0; iVector < oLayerDesc.aIdsText.size();
193✔
3250
                 iVector++)
3251
            {
3252
                if (oLayerDesc.aIdsText[iVector].toBool())
152✔
3253
                    poDictXObject->Add(
3254
                        CPLSPrintf("Text%d",
3255
                                   oLayerDesc.aIdsText[iVector].toInt()),
8✔
3256
                        oLayerDesc.aIdsText[iVector], 0);
16✔
3257
            }
3258
        }
3259

3260
        if (pszExtraStream)
112✔
3261
        {
3262
            std::vector<CPLString> aosNeededFonts;
4✔
3263
            if (strstr(pszExtraStream, "/FTimes"))
2✔
3264
            {
3265
                aosNeededFonts.push_back("Times-Roman");
2✔
3266
                aosNeededFonts.push_back("Times-Bold");
2✔
3267
                aosNeededFonts.push_back("Times-Italic");
2✔
3268
                aosNeededFonts.push_back("Times-BoldItalic");
2✔
3269
            }
3270
            if (strstr(pszExtraStream, "/FHelvetica"))
2✔
3271
            {
3272
                aosNeededFonts.push_back("Helvetica");
×
3273
                aosNeededFonts.push_back("Helvetica-Bold");
×
3274
                aosNeededFonts.push_back("Helvetica-Oblique");
×
3275
                aosNeededFonts.push_back("Helvetica-BoldOblique");
×
3276
            }
3277
            if (strstr(pszExtraStream, "/FCourier"))
2✔
3278
            {
3279
                aosNeededFonts.push_back("Courier");
×
3280
                aosNeededFonts.push_back("Courier-Bold");
×
3281
                aosNeededFonts.push_back("Courier-Oblique");
×
3282
                aosNeededFonts.push_back("Courier-BoldOblique");
×
3283
            }
3284
            if (strstr(pszExtraStream, "/FSymbol"))
2✔
3285
                aosNeededFonts.push_back("Symbol");
×
3286
            if (strstr(pszExtraStream, "/FZapfDingbats"))
2✔
3287
                aosNeededFonts.push_back("ZapfDingbats");
×
3288

3289
            if (!aosNeededFonts.empty())
2✔
3290
            {
3291
                GDALPDFDictionaryRW *poDictFont = new GDALPDFDictionaryRW();
2✔
3292

3293
                for (CPLString &osFont : aosNeededFonts)
10✔
3294
                {
3295
                    GDALPDFDictionaryRW *poDictFontInner =
3296
                        new GDALPDFDictionaryRW();
8✔
3297
                    poDictFontInner->Add("Type",
3298
                                         GDALPDFObjectRW::CreateName("Font"));
8✔
3299
                    poDictFontInner->Add("BaseFont",
3300
                                         GDALPDFObjectRW::CreateName(osFont));
8✔
3301
                    poDictFontInner->Add(
3302
                        "Encoding",
3303
                        GDALPDFObjectRW::CreateName("WinAnsiEncoding"));
8✔
3304
                    poDictFontInner->Add("Subtype",
3305
                                         GDALPDFObjectRW::CreateName("Type1"));
8✔
3306

3307
                    osFont = "F" + osFont;
8✔
3308
                    const size_t nHyphenPos = osFont.find('-');
8✔
3309
                    if (nHyphenPos != std::string::npos)
8✔
3310
                        osFont.erase(nHyphenPos, 1);
8✔
3311
                    poDictFont->Add(osFont, poDictFontInner);
8✔
3312
                }
3313

3314
                oDict.Add("Font", poDictFont);
2✔
3315
            }
3316
        }
3317

3318
        if (!m_asOCGs.empty())
112✔
3319
        {
3320
            GDALPDFDictionaryRW *poDictProperties = new GDALPDFDictionaryRW();
30✔
3321
#ifdef HACK_TO_GENERATE_OCMD
3322
            GDALPDFDictionaryRW *poOCMD = new GDALPDFDictionaryRW();
3323
            poOCMD->Add("Type", GDALPDFObjectRW::CreateName("OCMD"));
3324
            GDALPDFArrayRW *poArray = new GDALPDFArrayRW();
3325
            poArray->Add(m_asOCGs[0].nId, 0);
3326
            poArray->Add(m_asOCGs[1].nId, 0);
3327
            poOCMD->Add("OCGs", poArray);
3328
            poDictProperties->Add(CPLSPrintf("Lyr%d", m_asOCGs[1].nId.toInt()),
3329
                                  poOCMD);
3330
#else
3331
            for (size_t i = 0; i < m_asOCGs.size(); i++)
87✔
3332
                poDictProperties->Add(
3333
                    CPLSPrintf("Lyr%d", m_asOCGs[i].nId.toInt()),
57✔
3334
                    m_asOCGs[i].nId, 0);
57✔
3335
#endif
3336
            oDict.Add("Properties", poDictProperties);
30✔
3337
        }
3338

3339
        VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
112✔
3340
    }
3341
    EndObj();
112✔
3342

3343
    /* -------------------------------------------------------------- */
3344
    /*  Write annotation arrays.                                      */
3345
    /* -------------------------------------------------------------- */
3346
    StartObj(oPageContext.nAnnotsId);
112✔
3347
    {
3348
        GDALPDFArrayRW oArray;
112✔
3349
        for (size_t i = 0; i < oPageContext.anAnnotationsId.size(); i++)
120✔
3350
        {
3351
            oArray.Add(oPageContext.anAnnotationsId[i], 0);
8✔
3352
        }
3353
        VSIFPrintfL(m_fp, "%s\n", oArray.Serialize().c_str());
112✔
3354
    }
3355
    EndObj();
112✔
3356

3357
    return TRUE;
224✔
3358
}
3359

3360
/************************************************************************/
3361
/*                             WriteMask()                              */
3362
/************************************************************************/
3363

3364
GDALPDFObjectNum GDALPDFBaseWriter::WriteMask(GDALDataset *poSrcDS, int nXOff,
65✔
3365
                                              int nYOff, int nReqXSize,
3366
                                              int nReqYSize,
3367
                                              PDFCompressMethod eCompressMethod)
3368
{
3369
    int nMaskSize = nReqXSize * nReqYSize;
65✔
3370
    GByte *pabyMask = static_cast<GByte *>(VSIMalloc(nMaskSize));
65✔
3371
    if (pabyMask == nullptr)
65✔
3372
        return GDALPDFObjectNum();
×
3373

3374
    CPLErr eErr;
3375
    eErr = poSrcDS->GetRasterBand(4)->RasterIO(
65✔
3376
        GF_Read, nXOff, nYOff, nReqXSize, nReqYSize, pabyMask, nReqXSize,
3377
        nReqYSize, GDT_Byte, 0, 0, nullptr);
3378
    if (eErr != CE_None)
65✔
3379
    {
3380
        VSIFree(pabyMask);
×
3381
        return GDALPDFObjectNum();
×
3382
    }
3383

3384
    int bOnly0or255 = TRUE;
65✔
3385
    int bOnly255 = TRUE;
65✔
3386
    /* int bOnly0 = TRUE; */
3387
    int i;
3388
    for (i = 0; i < nReqXSize * nReqYSize; i++)
171,198✔
3389
    {
3390
        if (pabyMask[i] == 0)
171,180✔
3391
            bOnly255 = FALSE;
166,635✔
3392
        else if (pabyMask[i] == 255)
4,545✔
3393
        {
3394
            /* bOnly0 = FALSE; */
3395
        }
3396
        else
3397
        {
3398
            /* bOnly0 = FALSE; */
3399
            bOnly255 = FALSE;
47✔
3400
            bOnly0or255 = FALSE;
47✔
3401
            break;
47✔
3402
        }
3403
    }
3404

3405
    if (bOnly255)
65✔
3406
    {
3407
        CPLFree(pabyMask);
1✔
3408
        return GDALPDFObjectNum();
1✔
3409
    }
3410

3411
    if (bOnly0or255)
64✔
3412
    {
3413
        /* Translate to 1 bit */
3414
        int nReqXSize1 = (nReqXSize + 7) / 8;
17✔
3415
        GByte *pabyMask1 =
3416
            static_cast<GByte *>(VSICalloc(nReqXSize1, nReqYSize));
17✔
3417
        if (pabyMask1 == nullptr)
17✔
3418
        {
3419
            CPLFree(pabyMask);
×
3420
            return GDALPDFObjectNum();
×
3421
        }
3422
        for (int y = 0; y < nReqYSize; y++)
499✔
3423
        {
3424
            for (int x = 0; x < nReqXSize; x++)
6,686✔
3425
            {
3426
                if (pabyMask[y * nReqXSize + x])
6,204✔
3427
                    pabyMask1[y * nReqXSize1 + x / 8] |= 1 << (7 - (x % 8));
1,792✔
3428
            }
3429
        }
3430
        VSIFree(pabyMask);
17✔
3431
        pabyMask = pabyMask1;
17✔
3432
        nMaskSize = nReqXSize1 * nReqYSize;
17✔
3433
    }
3434

3435
    auto nMaskId = AllocNewObject();
64✔
3436

3437
    GDALPDFDictionaryRW oDict;
64✔
3438
    oDict.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
64✔
3439
        .Add("Subtype", GDALPDFObjectRW::CreateName("Image"))
64✔
3440
        .Add("Width", nReqXSize)
64✔
3441
        .Add("Height", nReqYSize)
64✔
3442
        .Add("ColorSpace", GDALPDFObjectRW::CreateName("DeviceGray"))
64✔
3443
        .Add("BitsPerComponent", (bOnly0or255) ? 1 : 8);
64✔
3444

3445
    StartObjWithStream(nMaskId, oDict, eCompressMethod != COMPRESS_NONE);
64✔
3446

3447
    VSIFWriteL(pabyMask, nMaskSize, 1, m_fp);
64✔
3448
    CPLFree(pabyMask);
64✔
3449

3450
    EndObjWithStream();
64✔
3451

3452
    return nMaskId;
64✔
3453
}
3454

3455
/************************************************************************/
3456
/*                             WriteBlock()                             */
3457
/************************************************************************/
3458

3459
GDALPDFObjectNum GDALPDFBaseWriter::WriteBlock(
209✔
3460
    GDALDataset *poSrcDS, int nXOff, int nYOff, int nReqXSize, int nReqYSize,
3461
    const GDALPDFObjectNum &nColorTableIdIn, PDFCompressMethod eCompressMethod,
3462
    int nPredictor, int nJPEGQuality, const char *pszJPEG2000_DRIVER,
3463
    GDALProgressFunc pfnProgress, void *pProgressData)
3464
{
3465
    int nBands = poSrcDS->GetRasterCount();
209✔
3466
    if (nBands == 0)
209✔
3467
        return GDALPDFObjectNum();
×
3468

3469
    GDALPDFObjectNum nColorTableId(nColorTableIdIn);
209✔
3470
    if (!nColorTableId.toBool())
209✔
3471
        nColorTableId = WriteColorTable(poSrcDS);
207✔
3472

3473
    CPLErr eErr = CE_None;
209✔
3474
    GDALDataset *poBlockSrcDS = nullptr;
209✔
3475
    std::unique_ptr<MEMDataset> poMEMDS;
209✔
3476
    GByte *pabyMEMDSBuffer = nullptr;
209✔
3477

3478
    if (eCompressMethod == COMPRESS_DEFAULT)
209✔
3479
    {
3480
        GDALDataset *poSrcDSToTest = poSrcDS;
175✔
3481

3482
        /* Test if we can directly copy original JPEG content */
3483
        /* if available */
3484
        if (VRTDataset *poVRTDS = dynamic_cast<VRTDataset *>(poSrcDS))
175✔
3485
        {
3486
            poSrcDSToTest = poVRTDS->GetSingleSimpleSource();
9✔
3487
        }
3488

3489
        if (poSrcDSToTest != nullptr && poSrcDSToTest->GetDriver() != nullptr &&
173✔
3490
            EQUAL(poSrcDSToTest->GetDriver()->GetDescription(), "JPEG") &&
173✔
3491
            nXOff == 0 && nYOff == 0 &&
4✔
3492
            nReqXSize == poSrcDSToTest->GetRasterXSize() &&
4✔
3493
            nReqYSize == poSrcDSToTest->GetRasterYSize() && nJPEGQuality < 0)
348✔
3494
        {
3495
            VSILFILE *fpSrc = VSIFOpenL(poSrcDSToTest->GetDescription(), "rb");
4✔
3496
            if (fpSrc != nullptr)
4✔
3497
            {
3498
                CPLDebug("PDF", "Copying directly original JPEG file");
4✔
3499

3500
                VSIFSeekL(fpSrc, 0, SEEK_END);
4✔
3501
                const int nLength = static_cast<int>(VSIFTellL(fpSrc));
4✔
3502
                VSIFSeekL(fpSrc, 0, SEEK_SET);
4✔
3503

3504
                auto nImageId = AllocNewObject();
4✔
3505

3506
                StartObj(nImageId);
4✔
3507

3508
                GDALPDFDictionaryRW oDict;
8✔
3509
                oDict.Add("Length", nLength)
4✔
3510
                    .Add("Type", GDALPDFObjectRW::CreateName("XObject"))
4✔
3511
                    .Add("Filter", GDALPDFObjectRW::CreateName("DCTDecode"))
4✔
3512
                    .Add("Subtype", GDALPDFObjectRW::CreateName("Image"))
4✔
3513
                    .Add("Width", nReqXSize)
4✔
3514
                    .Add("Height", nReqYSize)
4✔
3515
                    .Add("ColorSpace",
3516
                         (nBands == 1)
3517
                             ? GDALPDFObjectRW::CreateName("DeviceGray")
4✔
3518
                             : GDALPDFObjectRW::CreateName("DeviceRGB"))
8✔
3519
                    .Add("BitsPerComponent", 8);
4✔
3520
                VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
4✔
3521
                VSIFPrintfL(m_fp, "stream\n");
4✔
3522

3523
                GByte abyBuffer[1024];
3524
                for (int i = 0; i < nLength; i += 1024)
24✔
3525
                {
3526
                    const auto nRead = VSIFReadL(abyBuffer, 1, 1024, fpSrc);
20✔
3527
                    if (VSIFWriteL(abyBuffer, 1, nRead, m_fp) != nRead)
20✔
3528
                    {
3529
                        eErr = CE_Failure;
×
3530
                        break;
×
3531
                    }
3532

3533
                    if (eErr == CE_None && pfnProgress != nullptr &&
40✔
3534
                        !pfnProgress(double(i + nRead) / double(nLength),
20✔
3535
                                     nullptr, pProgressData))
3536
                    {
3537
                        CPLError(CE_Failure, CPLE_UserInterrupt,
×
3538
                                 "User terminated CreateCopy()");
3539
                        eErr = CE_Failure;
×
3540
                        break;
×
3541
                    }
3542
                }
3543

3544
                VSIFPrintfL(m_fp, "\nendstream\n");
4✔
3545

3546
                EndObj();
4✔
3547

3548
                VSIFCloseL(fpSrc);
4✔
3549

3550
                return eErr == CE_None ? nImageId : GDALPDFObjectNum();
4✔
3551
            }
3552
        }
3553

3554
        eCompressMethod = COMPRESS_DEFLATE;
171✔
3555
    }
3556

3557
    GDALPDFObjectNum nMaskId;
205✔
3558
    if (nBands == 4)
205✔
3559
    {
3560
        nMaskId = WriteMask(poSrcDS, nXOff, nYOff, nReqXSize, nReqYSize,
3561
                            eCompressMethod);
65✔
3562
    }
3563

3564
    if (nReqXSize == poSrcDS->GetRasterXSize() &&
205✔
3565
        nReqYSize == poSrcDS->GetRasterYSize() && nBands != 4)
205✔
3566
    {
3567
        poBlockSrcDS = poSrcDS;
89✔
3568
    }
3569
    else
3570
    {
3571
        if (nBands == 4)
116✔
3572
            nBands = 3;
65✔
3573

3574
        poMEMDS.reset(
116✔
3575
            MEMDataset::Create("", nReqXSize, nReqYSize, 0, GDT_Byte, nullptr));
3576

3577
        pabyMEMDSBuffer =
3578
            static_cast<GByte *>(VSIMalloc3(nReqXSize, nReqYSize, nBands));
116✔
3579
        if (pabyMEMDSBuffer == nullptr)
116✔
3580
        {
3581
            return GDALPDFObjectNum();
×
3582
        }
3583

3584
        eErr = poSrcDS->RasterIO(GF_Read, nXOff, nYOff, nReqXSize, nReqYSize,
116✔
3585
                                 pabyMEMDSBuffer, nReqXSize, nReqYSize,
3586
                                 GDT_Byte, nBands, nullptr, 0, 0, 0, nullptr);
3587

3588
        if (eErr != CE_None)
116✔
3589
        {
3590
            CPLFree(pabyMEMDSBuffer);
×
3591
            return GDALPDFObjectNum();
×
3592
        }
3593

3594
        int iBand;
3595
        for (iBand = 0; iBand < nBands; iBand++)
362✔
3596
        {
3597
            auto hBand = MEMCreateRasterBandEx(
246✔
3598
                poMEMDS.get(), iBand + 1,
246✔
3599
                pabyMEMDSBuffer + iBand * nReqXSize * nReqYSize, GDT_Byte, 0, 0,
246✔
3600
                false);
3601
            poMEMDS->AddMEMBand(hBand);
246✔
3602
        }
3603

3604
        poBlockSrcDS = poMEMDS.get();
116✔
3605
    }
3606

3607
    auto nImageId = AllocNewObject();
205✔
3608

3609
    GDALPDFObjectNum nMeasureId;
205✔
3610
    if (CPLTestBool(
205✔
3611
            CPLGetConfigOption("GDAL_PDF_WRITE_GEOREF_ON_IMAGE", "FALSE")) &&
×
3612
        nReqXSize == poSrcDS->GetRasterXSize() &&
205✔
3613
        nReqYSize == poSrcDS->GetRasterYSize())
×
3614
    {
3615
        PDFMargins sMargins;
×
3616
        nMeasureId = WriteSRS_ISO32000(poSrcDS, 1, nullptr, &sMargins, FALSE);
×
3617
    }
3618

3619
    GDALPDFDictionaryRW oDict;
410✔
3620
    oDict.Add("Type", GDALPDFObjectRW::CreateName("XObject"));
205✔
3621

3622
    if (eCompressMethod == COMPRESS_DEFLATE)
205✔
3623
    {
3624
        if (nPredictor == 2)
193✔
3625
            oDict.Add("DecodeParms", &((new GDALPDFDictionaryRW())
4✔
3626
                                           ->Add("Predictor", 2)
4✔
3627
                                           .Add("Colors", nBands)
4✔
3628
                                           .Add("Columns", nReqXSize)));
4✔
3629
    }
3630
    else if (eCompressMethod == COMPRESS_JPEG)
12✔
3631
    {
3632
        oDict.Add("Filter", GDALPDFObjectRW::CreateName("DCTDecode"));
6✔
3633
    }
3634
    else if (eCompressMethod == COMPRESS_JPEG2000)
6✔
3635
    {
3636
        oDict.Add("Filter", GDALPDFObjectRW::CreateName("JPXDecode"));
4✔
3637
    }
3638

3639
    oDict.Add("Subtype", GDALPDFObjectRW::CreateName("Image"))
205✔
3640
        .Add("Width", nReqXSize)
205✔
3641
        .Add("Height", nReqYSize)
205✔
3642
        .Add("ColorSpace",
3643
             (nColorTableId.toBool())
205✔
3644
                 ? GDALPDFObjectRW::CreateIndirect(nColorTableId, 0)
2✔
3645
             : (nBands == 1) ? GDALPDFObjectRW::CreateName("DeviceGray")
128✔
3646
                             : GDALPDFObjectRW::CreateName("DeviceRGB"))
335✔
3647
        .Add("BitsPerComponent", 8);
205✔
3648
    if (nMaskId.toBool())
205✔
3649
    {
3650
        oDict.Add("SMask", nMaskId, 0);
64✔
3651
    }
3652
    if (nMeasureId.toBool())
205✔
3653
    {
3654
        oDict.Add("Measure", nMeasureId, 0);
×
3655
    }
3656

3657
    StartObjWithStream(nImageId, oDict, eCompressMethod == COMPRESS_DEFLATE);
205✔
3658

3659
    if (eCompressMethod == COMPRESS_JPEG ||
205✔
3660
        eCompressMethod == COMPRESS_JPEG2000)
3661
    {
3662
        GDALDriver *poJPEGDriver = nullptr;
10✔
3663
        std::string osTmpfilename;
10✔
3664
        char **papszOptions = nullptr;
10✔
3665

3666
        bool bEcwEncodeKeyRequiredButNotFound = false;
10✔
3667
        if (eCompressMethod == COMPRESS_JPEG)
10✔
3668
        {
3669
            poJPEGDriver = GetGDALDriverManager()->GetDriverByName("JPEG");
6✔
3670
            if (poJPEGDriver != nullptr && nJPEGQuality > 0)
6✔
3671
                papszOptions = CSLAddString(
×
3672
                    papszOptions, CPLSPrintf("QUALITY=%d", nJPEGQuality));
3673
            osTmpfilename = VSIMemGenerateHiddenFilename("pdf_temp.jpg");
6✔
3674
        }
3675
        else
3676
        {
3677
            if (pszJPEG2000_DRIVER == nullptr ||
4✔
3678
                EQUAL(pszJPEG2000_DRIVER, "JP2KAK"))
3✔
3679
                poJPEGDriver =
3680
                    GetGDALDriverManager()->GetDriverByName("JP2KAK");
1✔
3681
            if (poJPEGDriver == nullptr)
4✔
3682
            {
3683
                if (pszJPEG2000_DRIVER == nullptr ||
4✔
3684
                    EQUAL(pszJPEG2000_DRIVER, "JP2ECW"))
3✔
3685
                {
3686
                    poJPEGDriver =
3687
                        GetGDALDriverManager()->GetDriverByName("JP2ECW");
3✔
3688
                    if (poJPEGDriver &&
6✔
3689
                        poJPEGDriver->GetMetadataItem(
3✔
3690
                            GDAL_DMD_CREATIONDATATYPES) == nullptr)
3✔
3691
                    {
3692
                        poJPEGDriver = nullptr;
×
3693
                    }
3694
                    else if (poJPEGDriver)
3✔
3695
                    {
3696
                        if (strstr(poJPEGDriver->GetMetadataItem(
6✔
3697
                                       GDAL_DMD_CREATIONOPTIONLIST),
3✔
3698
                                   "ECW_ENCODE_KEY"))
3✔
3699
                        {
3700
                            if (!CPLGetConfigOption("ECW_ENCODE_KEY", nullptr))
×
3701
                            {
3702
                                bEcwEncodeKeyRequiredButNotFound = true;
×
3703
                                poJPEGDriver = nullptr;
×
3704
                            }
3705
                        }
3706
                    }
3707
                }
3708
                if (poJPEGDriver)
4✔
3709
                {
3710
                    papszOptions = CSLAddString(papszOptions, "PROFILE=NPJE");
3✔
3711
                    papszOptions = CSLAddString(papszOptions, "LAYERS=1");
3✔
3712
                    papszOptions = CSLAddString(papszOptions, "GeoJP2=OFF");
3✔
3713
                    papszOptions = CSLAddString(papszOptions, "GMLJP2=OFF");
3✔
3714
                }
3715
            }
3716
            if (poJPEGDriver == nullptr)
4✔
3717
            {
3718
                if (pszJPEG2000_DRIVER == nullptr ||
1✔
3719
                    EQUAL(pszJPEG2000_DRIVER, "JP2OpenJPEG"))
1✔
3720
                    poJPEGDriver =
3721
                        GetGDALDriverManager()->GetDriverByName("JP2OpenJPEG");
1✔
3722
                if (poJPEGDriver)
1✔
3723
                {
3724
                    papszOptions = CSLAddString(papszOptions, "GeoJP2=OFF");
1✔
3725
                    papszOptions = CSLAddString(papszOptions, "GMLJP2=OFF");
1✔
3726
                }
3727
            }
3728
            osTmpfilename = VSIMemGenerateHiddenFilename("pdf_temp.jp2");
4✔
3729
        }
3730

3731
        if (poJPEGDriver == nullptr)
10✔
3732
        {
3733
            if (bEcwEncodeKeyRequiredButNotFound)
×
3734
            {
3735
                CPLError(CE_Failure, CPLE_NotSupported,
×
3736
                         "No JPEG2000 driver usable (JP2ECW detected but "
3737
                         "ECW_ENCODE_KEY configuration option not set");
3738
            }
3739
            else
3740
            {
3741
                CPLError(CE_Failure, CPLE_NotSupported, "No %s driver found",
×
3742
                         (eCompressMethod == COMPRESS_JPEG) ? "JPEG"
3743
                                                            : "JPEG2000");
3744
            }
3745
            eErr = CE_Failure;
×
3746
            goto end;
×
3747
        }
3748

3749
        GDALDataset *poJPEGDS =
3750
            poJPEGDriver->CreateCopy(osTmpfilename.c_str(), poBlockSrcDS, FALSE,
10✔
3751
                                     papszOptions, pfnProgress, pProgressData);
3752

3753
        CSLDestroy(papszOptions);
10✔
3754
        if (poJPEGDS == nullptr)
10✔
3755
        {
3756
            eErr = CE_Failure;
×
3757
            goto end;
×
3758
        }
3759

3760
        GDALClose(poJPEGDS);
10✔
3761

3762
        vsi_l_offset nJPEGDataSize = 0;
10✔
3763
        GByte *pabyJPEGData =
3764
            VSIGetMemFileBuffer(osTmpfilename.c_str(), &nJPEGDataSize, TRUE);
10✔
3765
        VSIFWriteL(pabyJPEGData, static_cast<size_t>(nJPEGDataSize), 1, m_fp);
10✔
3766
        CPLFree(pabyJPEGData);
20✔
3767
    }
3768
    else
3769
    {
3770
        GByte *pabyLine = static_cast<GByte *>(
3771
            CPLMalloc(static_cast<size_t>(nReqXSize) * nBands));
195✔
3772
        for (int iLine = 0; iLine < nReqYSize; iLine++)
93,132✔
3773
        {
3774
            /* Get pixel interleaved data */
3775
            eErr = poBlockSrcDS->RasterIO(
92,939✔
3776
                GF_Read, 0, iLine, nReqXSize, 1, pabyLine, nReqXSize, 1,
3777
                GDT_Byte, nBands, nullptr, nBands, 0, 1, nullptr);
3778
            if (eErr != CE_None)
92,939✔
3779
                break;
×
3780

3781
            /* Apply predictor if needed */
3782
            if (nPredictor == 2)
92,939✔
3783
            {
3784
                if (nBands == 1)
1,124✔
3785
                {
3786
                    int nPrevValue = pabyLine[0];
1,024✔
3787
                    for (int iPixel = 1; iPixel < nReqXSize; iPixel++)
524,288✔
3788
                    {
3789
                        int nCurValue = pabyLine[iPixel];
523,264✔
3790
                        pabyLine[iPixel] =
523,264✔
3791
                            static_cast<GByte>(nCurValue - nPrevValue);
523,264✔
3792
                        nPrevValue = nCurValue;
523,264✔
3793
                    }
3794
                }
3795
                else if (nBands == 3)
100✔
3796
                {
3797
                    int nPrevValueR = pabyLine[0];
100✔
3798
                    int nPrevValueG = pabyLine[1];
100✔
3799
                    int nPrevValueB = pabyLine[2];
100✔
3800
                    for (int iPixel = 1; iPixel < nReqXSize; iPixel++)
5,000✔
3801
                    {
3802
                        int nCurValueR = pabyLine[3 * iPixel + 0];
4,900✔
3803
                        int nCurValueG = pabyLine[3 * iPixel + 1];
4,900✔
3804
                        int nCurValueB = pabyLine[3 * iPixel + 2];
4,900✔
3805
                        pabyLine[3 * iPixel + 0] =
4,900✔
3806
                            static_cast<GByte>(nCurValueR - nPrevValueR);
4,900✔
3807
                        pabyLine[3 * iPixel + 1] =
4,900✔
3808
                            static_cast<GByte>(nCurValueG - nPrevValueG);
4,900✔
3809
                        pabyLine[3 * iPixel + 2] =
4,900✔
3810
                            static_cast<GByte>(nCurValueB - nPrevValueB);
4,900✔
3811
                        nPrevValueR = nCurValueR;
4,900✔
3812
                        nPrevValueG = nCurValueG;
4,900✔
3813
                        nPrevValueB = nCurValueB;
4,900✔
3814
                    }
3815
                }
3816
            }
3817

3818
            if (VSIFWriteL(pabyLine, static_cast<size_t>(nReqXSize) * nBands, 1,
92,939✔
3819
                           m_fp) != 1)
92,939✔
3820
            {
3821
                eErr = CE_Failure;
2✔
3822
                break;
2✔
3823
            }
3824

3825
            if (pfnProgress != nullptr &&
185,535✔
3826
                !pfnProgress((iLine + 1) / double(nReqYSize), nullptr,
92,598✔
3827
                             pProgressData))
3828
            {
3829
                CPLError(CE_Failure, CPLE_UserInterrupt,
×
3830
                         "User terminated CreateCopy()");
3831
                eErr = CE_Failure;
×
3832
                break;
×
3833
            }
3834
        }
3835

3836
        CPLFree(pabyLine);
195✔
3837
    }
3838

3839
end:
205✔
3840
    CPLFree(pabyMEMDSBuffer);
205✔
3841
    pabyMEMDSBuffer = nullptr;
205✔
3842

3843
    EndObjWithStream();
205✔
3844

3845
    return eErr == CE_None ? nImageId : GDALPDFObjectNum();
205✔
3846
}
3847

3848
/************************************************************************/
3849
/*                          WriteJavascript()                           */
3850
/************************************************************************/
3851

3852
GDALPDFObjectNum GDALPDFBaseWriter::WriteJavascript(const char *pszJavascript,
3✔
3853
                                                    bool bDeflate)
3854
{
3855
    auto nJSId = AllocNewObject();
3✔
3856
    {
3857
        GDALPDFDictionaryRW oDict;
6✔
3858
        StartObjWithStream(nJSId, oDict, bDeflate);
3✔
3859

3860
        VSIFWriteL(pszJavascript, strlen(pszJavascript), 1, m_fp);
3✔
3861
        VSIFPrintfL(m_fp, "\n");
3✔
3862

3863
        EndObjWithStream();
3✔
3864
    }
3865

3866
    m_nNamesId = AllocNewObject();
3✔
3867
    StartObj(m_nNamesId);
3✔
3868
    {
3869
        GDALPDFDictionaryRW oDict;
3✔
3870
        GDALPDFDictionaryRW *poJavaScriptDict = new GDALPDFDictionaryRW();
3✔
3871
        oDict.Add("JavaScript", poJavaScriptDict);
3✔
3872

3873
        GDALPDFArrayRW *poNamesArray = new GDALPDFArrayRW();
3✔
3874
        poJavaScriptDict->Add("Names", poNamesArray);
3✔
3875

3876
        poNamesArray->Add("GDAL");
3✔
3877

3878
        GDALPDFDictionaryRW *poJSDict = new GDALPDFDictionaryRW();
3✔
3879
        poNamesArray->Add(poJSDict);
3✔
3880

3881
        poJSDict->Add("JS", nJSId, 0);
3✔
3882
        poJSDict->Add("S", GDALPDFObjectRW::CreateName("JavaScript"));
3✔
3883

3884
        VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
3✔
3885
    }
3886
    EndObj();
3✔
3887

3888
    return m_nNamesId;
3✔
3889
}
3890

3891
GDALPDFObjectNum GDALPDFWriter::WriteJavascript(const char *pszJavascript)
2✔
3892
{
3893
    return GDALPDFBaseWriter::WriteJavascript(
3894
        pszJavascript, oPageContext.eStreamCompressMethod != COMPRESS_NONE);
2✔
3895
}
3896

3897
/************************************************************************/
3898
/*                        WriteJavascriptFile()                         */
3899
/************************************************************************/
3900

3901
GDALPDFObjectNum
3902
GDALPDFWriter::WriteJavascriptFile(const char *pszJavascriptFile)
×
3903
{
3904
    GDALPDFObjectNum nId;
×
3905
    char *pszJavascriptToFree = static_cast<char *>(CPLMalloc(65536));
×
3906
    VSILFILE *fpJS = VSIFOpenL(pszJavascriptFile, "rb");
×
3907
    if (fpJS != nullptr)
×
3908
    {
3909
        const int nRead =
3910
            static_cast<int>(VSIFReadL(pszJavascriptToFree, 1, 65536, fpJS));
×
3911
        if (nRead < 65536)
×
3912
        {
3913
            pszJavascriptToFree[nRead] = '\0';
×
3914
            nId = WriteJavascript(pszJavascriptToFree);
×
3915
        }
3916
        VSIFCloseL(fpJS);
×
3917
    }
3918
    CPLFree(pszJavascriptToFree);
×
3919
    return nId;
×
3920
}
3921

3922
/************************************************************************/
3923
/*                              WritePages()                            */
3924
/************************************************************************/
3925

3926
void GDALPDFWriter::WritePages()
114✔
3927
{
3928
    StartObj(m_nPageResourceId);
114✔
3929
    {
3930
        GDALPDFDictionaryRW oDict;
114✔
3931
        GDALPDFArrayRW *poKids = new GDALPDFArrayRW();
114✔
3932
        oDict.Add("Type", GDALPDFObjectRW::CreateName("Pages"))
114✔
3933
            .Add("Count", static_cast<int>(m_asPageId.size()))
114✔
3934
            .Add("Kids", poKids);
114✔
3935

3936
        for (size_t i = 0; i < m_asPageId.size(); i++)
228✔
3937
            poKids->Add(m_asPageId[i], 0);
114✔
3938

3939
        VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
114✔
3940
    }
3941
    EndObj();
114✔
3942

3943
    StartObj(m_nCatalogId);
114✔
3944
    {
3945
        GDALPDFDictionaryRW oDict;
114✔
3946
        oDict.Add("Type", GDALPDFObjectRW::CreateName("Catalog"))
114✔
3947
            .Add("Pages", m_nPageResourceId, 0);
114✔
3948
        if (m_nXMPId.toBool())
114✔
3949
            oDict.Add("Metadata", m_nXMPId, 0);
2✔
3950
        if (!m_asOCGs.empty())
114✔
3951
        {
3952
            GDALPDFDictionaryRW *poDictOCProperties = new GDALPDFDictionaryRW();
30✔
3953
            oDict.Add("OCProperties", poDictOCProperties);
30✔
3954

3955
            GDALPDFDictionaryRW *poDictD = new GDALPDFDictionaryRW();
30✔
3956
            poDictOCProperties->Add("D", poDictD);
30✔
3957

3958
            /* Build "Order" array of D dict */
3959
            GDALPDFArrayRW *poArrayOrder = new GDALPDFArrayRW();
30✔
3960
            for (size_t i = 0; i < m_asOCGs.size(); i++)
79✔
3961
            {
3962
                poArrayOrder->Add(m_asOCGs[i].nId, 0);
49✔
3963
                if (i + 1 < m_asOCGs.size() &&
76✔
3964
                    m_asOCGs[i + 1].nParentId == m_asOCGs[i].nId)
27✔
3965
                {
3966
                    GDALPDFArrayRW *poSubArrayOrder = new GDALPDFArrayRW();
8✔
3967
                    poSubArrayOrder->Add(m_asOCGs[i + 1].nId, 0);
8✔
3968
                    poArrayOrder->Add(poSubArrayOrder);
8✔
3969
                    i++;
8✔
3970
                }
3971
            }
3972
            poDictD->Add("Order", poArrayOrder);
30✔
3973

3974
            /* Build "OFF" array of D dict */
3975
            if (!m_osOffLayers.empty())
30✔
3976
            {
3977
                GDALPDFArrayRW *poArrayOFF = new GDALPDFArrayRW();
2✔
3978
                char **papszTokens = CSLTokenizeString2(m_osOffLayers, ",", 0);
2✔
3979
                for (int i = 0; papszTokens[i] != nullptr; i++)
4✔
3980
                {
3981
                    size_t j;
3982
                    int bFound = FALSE;
2✔
3983
                    for (j = 0; j < m_asOCGs.size(); j++)
6✔
3984
                    {
3985
                        if (strcmp(papszTokens[i], m_asOCGs[j].osLayerName) ==
4✔
3986
                            0)
3987
                        {
3988
                            poArrayOFF->Add(m_asOCGs[j].nId, 0);
2✔
3989
                            bFound = TRUE;
2✔
3990
                        }
3991
                        if (j + 1 < m_asOCGs.size() &&
6✔
3992
                            m_asOCGs[j + 1].nParentId == m_asOCGs[j].nId)
2✔
3993
                        {
3994
                            j++;
×
3995
                        }
3996
                    }
3997
                    if (!bFound)
2✔
3998
                    {
3999
                        CPLError(
×
4000
                            CE_Warning, CPLE_AppDefined,
4001
                            "Unknown layer name (%s) specified in OFF_LAYERS",
4002
                            papszTokens[i]);
×
4003
                    }
4004
                }
4005
                CSLDestroy(papszTokens);
2✔
4006

4007
                poDictD->Add("OFF", poArrayOFF);
2✔
4008
            }
4009

4010
            /* Build "RBGroups" array of D dict */
4011
            if (!m_osExclusiveLayers.empty())
30✔
4012
            {
4013
                GDALPDFArrayRW *poArrayRBGroups = new GDALPDFArrayRW();
2✔
4014
                char **papszTokens =
4015
                    CSLTokenizeString2(m_osExclusiveLayers, ",", 0);
2✔
4016
                for (int i = 0; papszTokens[i] != nullptr; i++)
6✔
4017
                {
4018
                    size_t j;
4019
                    int bFound = FALSE;
4✔
4020
                    for (j = 0; j < m_asOCGs.size(); j++)
12✔
4021
                    {
4022
                        if (strcmp(papszTokens[i], m_asOCGs[j].osLayerName) ==
8✔
4023
                            0)
4024
                        {
4025
                            poArrayRBGroups->Add(m_asOCGs[j].nId, 0);
4✔
4026
                            bFound = TRUE;
4✔
4027
                        }
4028
                        if (j + 1 < m_asOCGs.size() &&
12✔
4029
                            m_asOCGs[j + 1].nParentId == m_asOCGs[j].nId)
4✔
4030
                        {
4031
                            j++;
×
4032
                        }
4033
                    }
4034
                    if (!bFound)
4✔
4035
                    {
4036
                        CPLError(CE_Warning, CPLE_AppDefined,
×
4037
                                 "Unknown layer name (%s) specified in "
4038
                                 "EXCLUSIVE_LAYERS",
4039
                                 papszTokens[i]);
×
4040
                    }
4041
                }
4042
                CSLDestroy(papszTokens);
2✔
4043

4044
                if (poArrayRBGroups->GetLength())
2✔
4045
                {
4046
                    GDALPDFArrayRW *poMainArrayRBGroups = new GDALPDFArrayRW();
2✔
4047
                    poMainArrayRBGroups->Add(poArrayRBGroups);
2✔
4048
                    poDictD->Add("RBGroups", poMainArrayRBGroups);
2✔
4049
                }
4050
                else
4051
                    delete poArrayRBGroups;
×
4052
            }
4053

4054
            GDALPDFArrayRW *poArrayOGCs = new GDALPDFArrayRW();
30✔
4055
            for (size_t i = 0; i < m_asOCGs.size(); i++)
87✔
4056
                poArrayOGCs->Add(m_asOCGs[i].nId, 0);
57✔
4057
            poDictOCProperties->Add("OCGs", poArrayOGCs);
30✔
4058
        }
4059

4060
        if (m_nStructTreeRootId.toBool())
114✔
4061
        {
4062
            GDALPDFDictionaryRW *poDictMarkInfo = new GDALPDFDictionaryRW();
23✔
4063
            oDict.Add("MarkInfo", poDictMarkInfo);
23✔
4064
            poDictMarkInfo->Add("UserProperties",
4065
                                GDALPDFObjectRW::CreateBool(TRUE));
23✔
4066

4067
            oDict.Add("StructTreeRoot", m_nStructTreeRootId, 0);
23✔
4068
        }
4069

4070
        if (m_nNamesId.toBool())
114✔
4071
            oDict.Add("Names", m_nNamesId, 0);
2✔
4072

4073
        VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
114✔
4074
    }
4075
    EndObj();
114✔
4076
}
114✔
4077

4078
/************************************************************************/
4079
/*                        GDALPDFGetJPEGQuality()                       */
4080
/************************************************************************/
4081

4082
static int GDALPDFGetJPEGQuality(char **papszOptions)
95✔
4083
{
4084
    int nJpegQuality = -1;
95✔
4085
    const char *pszValue = CSLFetchNameValue(papszOptions, "JPEG_QUALITY");
95✔
4086
    if (pszValue != nullptr)
95✔
4087
    {
4088
        nJpegQuality = atoi(pszValue);
×
4089
        if (!(nJpegQuality >= 1 && nJpegQuality <= 100))
×
4090
        {
4091
            CPLError(CE_Warning, CPLE_IllegalArg,
×
4092
                     "JPEG_QUALITY=%s value not recognised, ignoring.",
4093
                     pszValue);
4094
            nJpegQuality = -1;
×
4095
        }
4096
    }
4097
    return nJpegQuality;
95✔
4098
}
4099

4100
/************************************************************************/
4101
/*                         GDALPDFClippingDataset                       */
4102
/************************************************************************/
4103

4104
class GDALPDFClippingDataset final : public GDALDataset
4105
{
4106
    GDALDataset *poSrcDS = nullptr;
4107
    double adfGeoTransform[6];
4108

4109
    CPL_DISALLOW_COPY_ASSIGN(GDALPDFClippingDataset)
4110

4111
  public:
4112
    GDALPDFClippingDataset(GDALDataset *poSrcDSIn, double adfClippingExtent[4])
2✔
4113
        : poSrcDS(poSrcDSIn)
2✔
4114
    {
4115
        double adfSrcGeoTransform[6];
4116
        poSrcDS->GetGeoTransform(adfSrcGeoTransform);
2✔
4117
        adfGeoTransform[0] = adfClippingExtent[0];
2✔
4118
        adfGeoTransform[1] = adfSrcGeoTransform[1];
2✔
4119
        adfGeoTransform[2] = 0.0;
2✔
4120
        adfGeoTransform[3] = adfSrcGeoTransform[5] < 0 ? adfClippingExtent[3]
2✔
4121
                                                       : adfClippingExtent[1];
4122
        adfGeoTransform[4] = 0.0;
2✔
4123
        adfGeoTransform[5] = adfSrcGeoTransform[5];
2✔
4124
        nRasterXSize =
2✔
4125
            static_cast<int>((adfClippingExtent[2] - adfClippingExtent[0]) /
2✔
4126
                             adfSrcGeoTransform[1]);
2✔
4127
        nRasterYSize =
2✔
4128
            static_cast<int>((adfClippingExtent[3] - adfClippingExtent[1]) /
2✔
4129
                             fabs(adfSrcGeoTransform[5]));
2✔
4130
    }
2✔
4131

4132
    virtual CPLErr GetGeoTransform(double *padfGeoTransform) override
6✔
4133
    {
4134
        memcpy(padfGeoTransform, adfGeoTransform, 6 * sizeof(double));
6✔
4135
        return CE_None;
6✔
4136
    }
4137

4138
    virtual const OGRSpatialReference *GetSpatialRef() const override
2✔
4139
    {
4140
        return poSrcDS->GetSpatialRef();
2✔
4141
    }
4142
};
4143

4144
/************************************************************************/
4145
/*                          GDALPDFCreateCopy()                         */
4146
/************************************************************************/
4147

4148
GDALDataset *GDALPDFCreateCopy(const char *pszFilename, GDALDataset *poSrcDS,
108✔
4149
                               int bStrict, char **papszOptions,
4150
                               GDALProgressFunc pfnProgress,
4151
                               void *pProgressData)
4152
{
4153
    int nBands = poSrcDS->GetRasterCount();
108✔
4154
    int nWidth = poSrcDS->GetRasterXSize();
108✔
4155
    int nHeight = poSrcDS->GetRasterYSize();
108✔
4156

4157
    if (!pfnProgress(0.0, nullptr, pProgressData))
108✔
4158
        return nullptr;
×
4159

4160
    /* -------------------------------------------------------------------- */
4161
    /*      Some some rudimentary checks                                    */
4162
    /* -------------------------------------------------------------------- */
4163
    if (nBands != 1 && nBands != 3 && nBands != 4)
108✔
4164
    {
4165
        CPLError(CE_Failure, CPLE_NotSupported,
3✔
4166
                 "PDF driver doesn't support %d bands.  Must be 1 (grey or "
4167
                 "with color table), "
4168
                 "3 (RGB) or 4 bands.\n",
4169
                 nBands);
4170

4171
        return nullptr;
3✔
4172
    }
4173

4174
    GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
105✔
4175
    if (eDT != GDT_Byte)
105✔
4176
    {
4177
        CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
10✔
4178
                 "PDF driver doesn't support data type %s. "
4179
                 "Only eight bit byte bands supported.\n",
4180
                 GDALGetDataTypeName(
4181
                     poSrcDS->GetRasterBand(1)->GetRasterDataType()));
4182

4183
        if (bStrict)
10✔
4184
            return nullptr;
10✔
4185
    }
4186

4187
    /* -------------------------------------------------------------------- */
4188
    /*     Read options.                                                    */
4189
    /* -------------------------------------------------------------------- */
4190
    PDFCompressMethod eCompressMethod = COMPRESS_DEFAULT;
95✔
4191
    const char *pszCompressMethod = CSLFetchNameValue(papszOptions, "COMPRESS");
95✔
4192
    if (pszCompressMethod)
95✔
4193
    {
4194
        if (EQUAL(pszCompressMethod, "NONE"))
14✔
4195
            eCompressMethod = COMPRESS_NONE;
2✔
4196
        else if (EQUAL(pszCompressMethod, "DEFLATE"))
12✔
4197
            eCompressMethod = COMPRESS_DEFLATE;
2✔
4198
        else if (EQUAL(pszCompressMethod, "JPEG"))
10✔
4199
            eCompressMethod = COMPRESS_JPEG;
6✔
4200
        else if (EQUAL(pszCompressMethod, "JPEG2000"))
4✔
4201
            eCompressMethod = COMPRESS_JPEG2000;
4✔
4202
        else
4203
        {
4204
            CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
×
4205
                     "Unsupported value for COMPRESS.");
4206

4207
            if (bStrict)
×
4208
                return nullptr;
×
4209
        }
4210
    }
4211

4212
    PDFCompressMethod eStreamCompressMethod = COMPRESS_DEFLATE;
95✔
4213
    const char *pszStreamCompressMethod =
4214
        CSLFetchNameValue(papszOptions, "STREAM_COMPRESS");
95✔
4215
    if (pszStreamCompressMethod)
95✔
4216
    {
4217
        if (EQUAL(pszStreamCompressMethod, "NONE"))
2✔
4218
            eStreamCompressMethod = COMPRESS_NONE;
2✔
4219
        else if (EQUAL(pszStreamCompressMethod, "DEFLATE"))
×
4220
            eStreamCompressMethod = COMPRESS_DEFLATE;
×
4221
        else
4222
        {
4223
            CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
×
4224
                     "Unsupported value for STREAM_COMPRESS.");
4225

4226
            if (bStrict)
×
4227
                return nullptr;
×
4228
        }
4229
    }
4230

4231
    if (nBands == 1 && poSrcDS->GetRasterBand(1)->GetColorTable() != nullptr &&
97✔
4232
        (eCompressMethod == COMPRESS_JPEG ||
2✔
4233
         eCompressMethod == COMPRESS_JPEG2000))
4234
    {
4235
        CPLError(CE_Warning, CPLE_AppDefined,
×
4236
                 "The source raster band has a color table, which is not "
4237
                 "appropriate with JPEG or JPEG2000 compression.\n"
4238
                 "You should rather consider using color table expansion "
4239
                 "(-expand option in gdal_translate)");
4240
    }
4241

4242
    int nBlockXSize = nWidth;
95✔
4243
    int nBlockYSize = nHeight;
95✔
4244

4245
    const bool bTiled = CPLFetchBool(papszOptions, "TILED", false);
95✔
4246
    if (bTiled)
95✔
4247
    {
4248
        nBlockXSize = 256;
2✔
4249
        nBlockYSize = 256;
2✔
4250
    }
4251

4252
    const char *pszValue = CSLFetchNameValue(papszOptions, "BLOCKXSIZE");
95✔
4253
    if (pszValue != nullptr)
95✔
4254
    {
4255
        nBlockXSize = atoi(pszValue);
4✔
4256
        if (nBlockXSize <= 0 || nBlockXSize >= nWidth)
4✔
4257
            nBlockXSize = nWidth;
×
4258
    }
4259

4260
    pszValue = CSLFetchNameValue(papszOptions, "BLOCKYSIZE");
95✔
4261
    if (pszValue != nullptr)
95✔
4262
    {
4263
        nBlockYSize = atoi(pszValue);
4✔
4264
        if (nBlockYSize <= 0 || nBlockYSize >= nHeight)
4✔
4265
            nBlockYSize = nHeight;
×
4266
    }
4267

4268
    int nJPEGQuality = GDALPDFGetJPEGQuality(papszOptions);
95✔
4269

4270
    const char *pszJPEG2000_DRIVER =
4271
        CSLFetchNameValue(papszOptions, "JPEG2000_DRIVER");
95✔
4272

4273
    const char *pszGEO_ENCODING =
4274
        CSLFetchNameValueDef(papszOptions, "GEO_ENCODING", "ISO32000");
95✔
4275
    if (EQUAL(pszGEO_ENCODING, "OGC_BP"))
95✔
4276
    {
4277
        CPLError(CE_Failure, CPLE_NotSupported,
×
4278
                 "GEO_ENCODING=OGC_BP is no longer supported. Switch to using "
4279
                 "ISO32000");
4280
        return nullptr;
×
4281
    }
4282
    else if (EQUAL(pszGEO_ENCODING, "BOTH"))
95✔
4283
    {
4284
        CPLError(CE_Warning, CPLE_NotSupported,
×
4285
                 "GEO_ENCODING=BOTH is no longer strictly supported. This now "
4286
                 "fallbacks to ISO32000");
4287
        pszGEO_ENCODING = "ISO32000";
×
4288
    }
4289

4290
    const char *pszXMP = CSLFetchNameValue(papszOptions, "XMP");
95✔
4291

4292
    const char *pszPredictor = CSLFetchNameValue(papszOptions, "PREDICTOR");
95✔
4293
    int nPredictor = 1;
95✔
4294
    if (pszPredictor)
95✔
4295
    {
4296
        if (eCompressMethod == COMPRESS_DEFAULT)
4✔
4297
            eCompressMethod = COMPRESS_DEFLATE;
4✔
4298

4299
        if (eCompressMethod != COMPRESS_DEFLATE)
4✔
4300
        {
4301
            CPLError(CE_Warning, CPLE_NotSupported,
×
4302
                     "PREDICTOR option is only taken into account for DEFLATE "
4303
                     "compression");
4304
        }
4305
        else
4306
        {
4307
            nPredictor = atoi(pszPredictor);
4✔
4308
            if (nPredictor != 1 && nPredictor != 2)
4✔
4309
            {
4310
                CPLError(CE_Warning, CPLE_NotSupported,
×
4311
                         "Supported PREDICTOR values are 1 or 2");
4312
                nPredictor = 1;
×
4313
            }
4314
        }
4315
    }
4316

4317
    const char *pszNEATLINE = CSLFetchNameValue(papszOptions, "NEATLINE");
95✔
4318

4319
    int nMargin = atoi(CSLFetchNameValueDef(papszOptions, "MARGIN", "0"));
95✔
4320

4321
    PDFMargins sMargins;
95✔
4322
    sMargins.nLeft = nMargin;
95✔
4323
    sMargins.nRight = nMargin;
95✔
4324
    sMargins.nTop = nMargin;
95✔
4325
    sMargins.nBottom = nMargin;
95✔
4326

4327
    const char *pszLeftMargin = CSLFetchNameValue(papszOptions, "LEFT_MARGIN");
95✔
4328
    if (pszLeftMargin)
95✔
4329
        sMargins.nLeft = atoi(pszLeftMargin);
4✔
4330

4331
    const char *pszRightMargin =
4332
        CSLFetchNameValue(papszOptions, "RIGHT_MARGIN");
95✔
4333
    if (pszRightMargin)
95✔
4334
        sMargins.nRight = atoi(pszRightMargin);
2✔
4335

4336
    const char *pszTopMargin = CSLFetchNameValue(papszOptions, "TOP_MARGIN");
95✔
4337
    if (pszTopMargin)
95✔
4338
        sMargins.nTop = atoi(pszTopMargin);
4✔
4339

4340
    const char *pszBottomMargin =
4341
        CSLFetchNameValue(papszOptions, "BOTTOM_MARGIN");
95✔
4342
    if (pszBottomMargin)
95✔
4343
        sMargins.nBottom = atoi(pszBottomMargin);
2✔
4344

4345
    const char *pszDPI = CSLFetchNameValue(papszOptions, "DPI");
95✔
4346
    double dfDPI = DEFAULT_DPI;
95✔
4347
    if (pszDPI != nullptr)
95✔
4348
        dfDPI = CPLAtof(pszDPI);
14✔
4349

4350
    const char *pszWriteUserUnit =
4351
        CSLFetchNameValue(papszOptions, "WRITE_USERUNIT");
95✔
4352
    bool bWriteUserUnit;
4353
    if (pszWriteUserUnit != nullptr)
95✔
4354
        bWriteUserUnit = CPLTestBool(pszWriteUserUnit);
2✔
4355
    else
4356
        bWriteUserUnit = (pszDPI == nullptr);
93✔
4357

4358
    double dfUserUnit = dfDPI * USER_UNIT_IN_INCH;
95✔
4359
    double dfWidthInUserUnit =
95✔
4360
        nWidth / dfUserUnit + sMargins.nLeft + sMargins.nRight;
95✔
4361
    double dfHeightInUserUnit =
95✔
4362
        nHeight / dfUserUnit + sMargins.nBottom + sMargins.nTop;
95✔
4363
    if (dfWidthInUserUnit > MAXIMUM_SIZE_IN_UNITS ||
95✔
4364
        dfHeightInUserUnit > MAXIMUM_SIZE_IN_UNITS)
4365
    {
4366
        if (pszDPI == nullptr)
12✔
4367
        {
4368
            if (sMargins.nLeft + sMargins.nRight >= MAXIMUM_SIZE_IN_UNITS ||
8✔
4369
                sMargins.nBottom + sMargins.nTop >= MAXIMUM_SIZE_IN_UNITS)
6✔
4370
            {
4371
                CPLError(
4✔
4372
                    CE_Warning, CPLE_AppDefined,
4373
                    "Margins too big compared to maximum page dimension (%d) "
4374
                    "in user units allowed by Acrobat",
4375
                    MAXIMUM_SIZE_IN_UNITS);
4376
            }
4377
            else
4378
            {
4379
                if (dfWidthInUserUnit >= dfHeightInUserUnit)
4✔
4380
                {
4381
                    dfDPI = ceil(double(nWidth) /
2✔
4382
                                 (MAXIMUM_SIZE_IN_UNITS -
2✔
4383
                                  (sMargins.nLeft + sMargins.nRight)) /
2✔
4384
                                 USER_UNIT_IN_INCH);
4385
                }
4386
                else
4387
                {
4388
                    dfDPI = ceil(double(nHeight) /
2✔
4389
                                 (MAXIMUM_SIZE_IN_UNITS -
2✔
4390
                                  (sMargins.nBottom + sMargins.nTop)) /
2✔
4391
                                 USER_UNIT_IN_INCH);
4392
                }
4393
                CPLDebug("PDF",
4✔
4394
                         "Adjusting DPI to %d so that page dimension in "
4395
                         "user units remain in what is accepted by Acrobat",
4396
                         static_cast<int>(dfDPI));
4397
            }
4398
        }
4399
        else
4400
        {
4401
            CPLError(CE_Warning, CPLE_AppDefined,
4✔
4402
                     "The page dimension in user units is %d x %d whereas the "
4403
                     "maximum allowed by Acrobat is %d x %d",
4404
                     static_cast<int>(dfWidthInUserUnit + 0.5),
4✔
4405
                     static_cast<int>(dfHeightInUserUnit + 0.5),
4✔
4406
                     MAXIMUM_SIZE_IN_UNITS, MAXIMUM_SIZE_IN_UNITS);
4407
        }
4408
    }
4409

4410
    if (dfDPI < DEFAULT_DPI)
95✔
4411
        dfDPI = DEFAULT_DPI;
×
4412

4413
    const char *pszClippingExtent =
4414
        CSLFetchNameValue(papszOptions, "CLIPPING_EXTENT");
95✔
4415
    int bUseClippingExtent = FALSE;
95✔
4416
    double adfClippingExtent[4] = {0.0, 0.0, 0.0, 0.0};
95✔
4417
    if (pszClippingExtent != nullptr)
95✔
4418
    {
4419
        char **papszTokens = CSLTokenizeString2(pszClippingExtent, ",", 0);
2✔
4420
        if (CSLCount(papszTokens) == 4)
2✔
4421
        {
4422
            bUseClippingExtent = TRUE;
2✔
4423
            adfClippingExtent[0] = CPLAtof(papszTokens[0]);
2✔
4424
            adfClippingExtent[1] = CPLAtof(papszTokens[1]);
2✔
4425
            adfClippingExtent[2] = CPLAtof(papszTokens[2]);
2✔
4426
            adfClippingExtent[3] = CPLAtof(papszTokens[3]);
2✔
4427
            if (adfClippingExtent[0] > adfClippingExtent[2] ||
2✔
4428
                adfClippingExtent[1] > adfClippingExtent[3])
2✔
4429
            {
4430
                CPLError(CE_Warning, CPLE_AppDefined,
×
4431
                         "Invalid value for CLIPPING_EXTENT. Should be "
4432
                         "xmin,ymin,xmax,ymax");
4433
                bUseClippingExtent = FALSE;
×
4434
            }
4435

4436
            if (bUseClippingExtent)
2✔
4437
            {
4438
                double adfGeoTransform[6];
4439
                if (poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None)
2✔
4440
                {
4441
                    if (adfGeoTransform[2] != 0.0 || adfGeoTransform[4] != 0.0)
2✔
4442
                    {
4443
                        CPLError(CE_Warning, CPLE_AppDefined,
×
4444
                                 "Cannot use CLIPPING_EXTENT because main "
4445
                                 "raster has a rotated geotransform");
4446
                        bUseClippingExtent = FALSE;
×
4447
                    }
4448
                }
4449
                else
4450
                {
4451
                    CPLError(CE_Warning, CPLE_AppDefined,
×
4452
                             "Cannot use CLIPPING_EXTENT because main raster "
4453
                             "has no geotransform");
4454
                    bUseClippingExtent = FALSE;
×
4455
                }
4456
            }
4457
        }
4458
        CSLDestroy(papszTokens);
2✔
4459
    }
4460

4461
    const char *pszLayerName = CSLFetchNameValue(papszOptions, "LAYER_NAME");
95✔
4462

4463
    const char *pszExtraImages =
4464
        CSLFetchNameValue(papszOptions, "EXTRA_IMAGES");
95✔
4465
    const char *pszExtraStream =
4466
        CSLFetchNameValue(papszOptions, "EXTRA_STREAM");
95✔
4467
    const char *pszExtraLayerName =
4468
        CSLFetchNameValue(papszOptions, "EXTRA_LAYER_NAME");
95✔
4469

4470
    const char *pszOGRDataSource =
4471
        CSLFetchNameValue(papszOptions, "OGR_DATASOURCE");
95✔
4472
    const char *pszOGRDisplayField =
4473
        CSLFetchNameValue(papszOptions, "OGR_DISPLAY_FIELD");
95✔
4474
    const char *pszOGRDisplayLayerNames =
4475
        CSLFetchNameValue(papszOptions, "OGR_DISPLAY_LAYER_NAMES");
95✔
4476
    const char *pszOGRLinkField =
4477
        CSLFetchNameValue(papszOptions, "OGR_LINK_FIELD");
95✔
4478
    const bool bWriteOGRAttributes =
4479
        CPLFetchBool(papszOptions, "OGR_WRITE_ATTRIBUTES", true);
95✔
4480

4481
    const char *pszExtraRasters =
4482
        CSLFetchNameValue(papszOptions, "EXTRA_RASTERS");
95✔
4483
    const char *pszExtraRastersLayerName =
4484
        CSLFetchNameValue(papszOptions, "EXTRA_RASTERS_LAYER_NAME");
95✔
4485

4486
    const char *pszOffLayers = CSLFetchNameValue(papszOptions, "OFF_LAYERS");
95✔
4487
    const char *pszExclusiveLayers =
4488
        CSLFetchNameValue(papszOptions, "EXCLUSIVE_LAYERS");
95✔
4489

4490
    const char *pszJavascript = CSLFetchNameValue(papszOptions, "JAVASCRIPT");
95✔
4491
    const char *pszJavascriptFile =
4492
        CSLFetchNameValue(papszOptions, "JAVASCRIPT_FILE");
95✔
4493

4494
    /* -------------------------------------------------------------------- */
4495
    /*      Create file.                                                    */
4496
    /* -------------------------------------------------------------------- */
4497
    VSILFILE *fp = VSIFOpenL(pszFilename, "wb");
95✔
4498
    if (fp == nullptr)
95✔
4499
    {
4500
        CPLError(CE_Failure, CPLE_OpenFailed, "Unable to create PDF file %s.\n",
3✔
4501
                 pszFilename);
4502
        return nullptr;
3✔
4503
    }
4504

4505
    GDALPDFWriter oWriter(fp);
184✔
4506

4507
    GDALDataset *poClippingDS = poSrcDS;
92✔
4508
    if (bUseClippingExtent)
92✔
4509
        poClippingDS = new GDALPDFClippingDataset(poSrcDS, adfClippingExtent);
2✔
4510

4511
    if (CPLFetchBool(papszOptions, "WRITE_INFO", true))
92✔
4512
        oWriter.SetInfo(poSrcDS, papszOptions);
90✔
4513
    oWriter.SetXMP(poClippingDS, pszXMP);
92✔
4514

4515
    oWriter.StartPage(poClippingDS, dfDPI, bWriteUserUnit, pszGEO_ENCODING,
92✔
4516
                      pszNEATLINE, &sMargins, eStreamCompressMethod,
4517
                      pszOGRDataSource != nullptr && bWriteOGRAttributes);
92✔
4518

4519
    int bRet;
4520

4521
    if (!bUseClippingExtent)
92✔
4522
    {
4523
        bRet = oWriter.WriteImagery(poSrcDS, pszLayerName, eCompressMethod,
90✔
4524
                                    nPredictor, nJPEGQuality,
4525
                                    pszJPEG2000_DRIVER, nBlockXSize,
4526
                                    nBlockYSize, pfnProgress, pProgressData);
4527
    }
4528
    else
4529
    {
4530
        bRet = oWriter.WriteClippedImagery(
2✔
4531
            poSrcDS, pszLayerName, eCompressMethod, nPredictor, nJPEGQuality,
4532
            pszJPEG2000_DRIVER, nBlockXSize, nBlockYSize, pfnProgress,
4533
            pProgressData);
4534
    }
4535

4536
    char **papszExtraRasters =
4537
        CSLTokenizeString2(pszExtraRasters ? pszExtraRasters : "", ",", 0);
92✔
4538
    char **papszExtraRastersLayerName = CSLTokenizeString2(
92✔
4539
        pszExtraRastersLayerName ? pszExtraRastersLayerName : "", ",", 0);
4540
    int bUseExtraRastersLayerName =
4541
        (CSLCount(papszExtraRasters) == CSLCount(papszExtraRastersLayerName));
92✔
4542
    int bUseExtraRasters = TRUE;
92✔
4543

4544
    const char *pszClippingProjectionRef = poSrcDS->GetProjectionRef();
92✔
4545
    if (CSLCount(papszExtraRasters) != 0)
92✔
4546
    {
4547
        double adfGeoTransform[6];
4548
        if (poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None)
2✔
4549
        {
4550
            if (adfGeoTransform[2] != 0.0 || adfGeoTransform[4] != 0.0)
2✔
4551
            {
4552
                CPLError(CE_Warning, CPLE_AppDefined,
×
4553
                         "Cannot use EXTRA_RASTERS because main raster has a "
4554
                         "rotated geotransform");
4555
                bUseExtraRasters = FALSE;
×
4556
            }
4557
        }
4558
        else
4559
        {
4560
            CPLError(CE_Warning, CPLE_AppDefined,
×
4561
                     "Cannot use EXTRA_RASTERS because main raster has no "
4562
                     "geotransform");
4563
            bUseExtraRasters = FALSE;
×
4564
        }
4565
        if (bUseExtraRasters && (pszClippingProjectionRef == nullptr ||
2✔
4566
                                 pszClippingProjectionRef[0] == '\0'))
2✔
4567
        {
4568
            CPLError(CE_Warning, CPLE_AppDefined,
×
4569
                     "Cannot use EXTRA_RASTERS because main raster has no "
4570
                     "projection");
4571
            bUseExtraRasters = FALSE;
×
4572
        }
4573
    }
4574

4575
    for (int i = 0; bRet && bUseExtraRasters && papszExtraRasters[i] != nullptr;
94✔
4576
         i++)
4577
    {
4578
        auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
4579
            papszExtraRasters[i], GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
2✔
4580
            nullptr, nullptr, nullptr));
4✔
4581
        if (poDS != nullptr)
2✔
4582
        {
4583
            double adfGeoTransform[6];
4584
            int bUseRaster = TRUE;
2✔
4585
            if (poDS->GetGeoTransform(adfGeoTransform) == CE_None)
2✔
4586
            {
4587
                if (adfGeoTransform[2] != 0.0 || adfGeoTransform[4] != 0.0)
2✔
4588
                {
4589
                    CPLError(
×
4590
                        CE_Warning, CPLE_AppDefined,
4591
                        "Cannot use %s because it has a rotated geotransform",
4592
                        papszExtraRasters[i]);
×
4593
                    bUseRaster = FALSE;
×
4594
                }
4595
            }
4596
            else
4597
            {
4598
                CPLError(CE_Warning, CPLE_AppDefined,
×
4599
                         "Cannot use %s because it has no geotransform",
4600
                         papszExtraRasters[i]);
×
4601
                bUseRaster = FALSE;
×
4602
            }
4603
            const char *pszProjectionRef = poDS->GetProjectionRef();
2✔
4604
            if (bUseRaster &&
2✔
4605
                (pszProjectionRef == nullptr || pszProjectionRef[0] == '\0'))
2✔
4606
            {
4607
                CPLError(CE_Warning, CPLE_AppDefined,
×
4608
                         "Cannot use %s because it has no projection",
4609
                         papszExtraRasters[i]);
×
4610
                bUseRaster = FALSE;
×
4611
            }
4612
            if (bUseRaster)
2✔
4613
            {
4614
                if (pszClippingProjectionRef != nullptr &&
2✔
4615
                    pszProjectionRef != nullptr &&
2✔
4616
                    !EQUAL(pszClippingProjectionRef, pszProjectionRef))
2✔
4617
                {
4618
                    OGRSpatialReferenceH hClippingSRS =
4619
                        OSRNewSpatialReference(pszClippingProjectionRef);
2✔
4620
                    OGRSpatialReferenceH hSRS =
4621
                        OSRNewSpatialReference(pszProjectionRef);
2✔
4622
                    if (!OSRIsSame(hClippingSRS, hSRS))
2✔
4623
                    {
4624
                        CPLError(CE_Warning, CPLE_AppDefined,
×
4625
                                 "Cannot use %s because it has a different "
4626
                                 "projection than main dataset",
4627
                                 papszExtraRasters[i]);
×
4628
                        bUseRaster = FALSE;
×
4629
                    }
4630
                    OSRDestroySpatialReference(hClippingSRS);
2✔
4631
                    OSRDestroySpatialReference(hSRS);
2✔
4632
                }
4633
            }
4634
            if (bUseRaster)
2✔
4635
            {
4636
                bRet = oWriter.WriteClippedImagery(
4✔
4637
                    poDS.get(),
4638
                    bUseExtraRastersLayerName ? papszExtraRastersLayerName[i]
2✔
4639
                                              : nullptr,
4640
                    eCompressMethod, nPredictor, nJPEGQuality,
4641
                    pszJPEG2000_DRIVER, nBlockXSize, nBlockYSize, nullptr,
4642
                    nullptr);
4643
            }
4644
        }
4645
    }
4646

4647
    CSLDestroy(papszExtraRasters);
92✔
4648
    CSLDestroy(papszExtraRastersLayerName);
92✔
4649

4650
    if (bRet && pszOGRDataSource != nullptr)
92✔
4651
        oWriter.WriteOGRDataSource(pszOGRDataSource, pszOGRDisplayField,
4✔
4652
                                   pszOGRDisplayLayerNames, pszOGRLinkField,
4653
                                   bWriteOGRAttributes);
4654

4655
    if (bRet)
92✔
4656
        oWriter.EndPage(pszExtraImages, pszExtraStream, pszExtraLayerName,
90✔
4657
                        pszOffLayers, pszExclusiveLayers);
4658

4659
    if (pszJavascript)
92✔
4660
        oWriter.WriteJavascript(pszJavascript);
2✔
4661
    else if (pszJavascriptFile)
90✔
4662
        oWriter.WriteJavascriptFile(pszJavascriptFile);
×
4663

4664
    oWriter.Close();
92✔
4665

4666
    if (poClippingDS != poSrcDS)
92✔
4667
        delete poClippingDS;
2✔
4668

4669
    if (!bRet)
92✔
4670
    {
4671
        VSIUnlink(pszFilename);
2✔
4672
        return nullptr;
2✔
4673
    }
4674
    else
4675
    {
4676
#ifdef HAVE_PDF_READ_SUPPORT
4677
        GDALDataset *poDS = GDALPDFOpen(pszFilename, GA_ReadOnly);
90✔
4678
        if (poDS == nullptr)
90✔
4679
            return nullptr;
8✔
4680
        char **papszMD = CSLDuplicate(poSrcDS->GetMetadata());
82✔
4681
        papszMD = CSLMerge(papszMD, poDS->GetMetadata());
82✔
4682
        const char *pszAOP = CSLFetchNameValue(papszMD, GDALMD_AREA_OR_POINT);
82✔
4683
        if (pszAOP != nullptr && EQUAL(pszAOP, GDALMD_AOP_AREA))
82✔
4684
            papszMD = CSLSetNameValue(papszMD, GDALMD_AREA_OR_POINT, nullptr);
50✔
4685
        poDS->SetMetadata(papszMD);
82✔
4686
        if (EQUAL(pszGEO_ENCODING, "NONE"))
82✔
4687
        {
4688
            double adfGeoTransform[6];
4689
            if (poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None)
4✔
4690
            {
4691
                poDS->SetGeoTransform(adfGeoTransform);
4✔
4692
            }
4693
            const char *pszProjectionRef = poSrcDS->GetProjectionRef();
4✔
4694
            if (pszProjectionRef != nullptr && pszProjectionRef[0] != '\0')
4✔
4695
            {
4696
                poDS->SetProjection(pszProjectionRef);
4✔
4697
            }
4698
        }
4699
        CSLDestroy(papszMD);
82✔
4700
        return poDS;
82✔
4701
#else
4702
        return new GDALFakePDFDataset();
4703
#endif
4704
    }
4705
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc