Coverage Report

Created: 2026-09-26 08:22

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