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

OSGeo / gdal / 15885686134

25 Jun 2025 07:44PM UTC coverage: 71.084%. Remained the same
15885686134

push

github

rouault
gdal_priv.h: fix C++11 compatibility

573814 of 807237 relevant lines covered (71.08%)

250621.56 hits per line

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

92.82
/frmts/pdf/pdfcreatefromcomposition.cpp
1
/******************************************************************************
2
 *
3
 * Project:  PDF driver
4
 * Purpose:  GDALDataset driver for PDF dataset.
5
 * Author:   Even Rouault, <even dot rouault at spatialys dot com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2019, Even Rouault <even dot rouault at spatialys dot com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12

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

16
#include <algorithm>
17
#include <cmath>
18
#include <cstdlib>
19

20
#include "pdfcreatefromcomposition.h"
21
#include "cpl_conv.h"
22
#include "cpl_minixml.h"
23
#include "cpl_vsi_virtual.h"
24
#include "ogr_geometry.h"
25

26
#ifdef EMBED_RESOURCE_FILES
27
#include "embedded_resources.h"
28
#endif
29

30
GDALPDFComposerWriter::Action::~Action() = default;
31
GDALPDFComposerWriter::GotoPageAction::~GotoPageAction() = default;
32
GDALPDFComposerWriter::SetLayerStateAction::~SetLayerStateAction() = default;
33
GDALPDFComposerWriter::JavascriptAction::~JavascriptAction() = default;
34

35
/************************************************************************/
36
/*                         GDALPDFComposerWriter()                      */
37
/************************************************************************/
38

39
GDALPDFComposerWriter::GDALPDFComposerWriter(VSILFILE *fp)
38✔
40
    : GDALPDFBaseWriter(fp)
38✔
41
{
42
    StartNewDoc();
38✔
43
}
38✔
44

45
/************************************************************************/
46
/*                        ~GDALPDFComposerWriter()                      */
47
/************************************************************************/
48

49
GDALPDFComposerWriter::~GDALPDFComposerWriter()
38✔
50
{
51
    Close();
38✔
52
}
38✔
53

54
/************************************************************************/
55
/*                                  Close()                             */
56
/************************************************************************/
57

58
void GDALPDFComposerWriter::Close()
38✔
59
{
60
    if (m_fp)
38✔
61
    {
62
        CPLAssert(!m_bInWriteObj);
38✔
63
        if (m_nPageResourceId.toBool())
38✔
64
        {
65
            WritePages();
38✔
66
            WriteXRefTableAndTrailer(false, 0);
38✔
67
        }
68
    }
69
    GDALPDFBaseWriter::Close();
38✔
70
}
38✔
71

72
/************************************************************************/
73
/*                          CreateOCGOrder()                            */
74
/************************************************************************/
75

76
GDALPDFArrayRW *GDALPDFComposerWriter::CreateOCGOrder(const TreeOfOCG *parent)
7✔
77
{
78
    auto poArrayOrder = new GDALPDFArrayRW();
7✔
79
    for (const auto &child : parent->m_children)
19✔
80
    {
81
        poArrayOrder->Add(child->m_nNum, 0);
12✔
82
        if (!child->m_children.empty())
12✔
83
        {
84
            poArrayOrder->Add(CreateOCGOrder(child.get()));
2✔
85
        }
86
    }
87
    return poArrayOrder;
7✔
88
}
89

90
/************************************************************************/
91
/*                          CollectOffOCG()                             */
92
/************************************************************************/
93

94
void GDALPDFComposerWriter::CollectOffOCG(std::vector<GDALPDFObjectNum> &ar,
17✔
95
                                          const TreeOfOCG *parent)
96
{
97
    if (!parent->m_bInitiallyVisible)
17✔
98
        ar.push_back(parent->m_nNum);
1✔
99
    for (const auto &child : parent->m_children)
29✔
100
    {
101
        CollectOffOCG(ar, child.get());
12✔
102
    }
103
}
17✔
104

105
/************************************************************************/
106
/*                              WritePages()                            */
107
/************************************************************************/
108

109
void GDALPDFComposerWriter::WritePages()
38✔
110
{
111
    StartObj(m_nPageResourceId);
38✔
112
    {
113
        GDALPDFDictionaryRW oDict;
38✔
114
        GDALPDFArrayRW *poKids = new GDALPDFArrayRW();
38✔
115
        oDict.Add("Type", GDALPDFObjectRW::CreateName("Pages"))
38✔
116
            .Add("Count", static_cast<int>(m_asPageId.size()))
38✔
117
            .Add("Kids", poKids);
38✔
118

119
        for (size_t i = 0; i < m_asPageId.size(); i++)
70✔
120
            poKids->Add(m_asPageId[i], 0);
32✔
121

122
        VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
38✔
123
    }
124
    EndObj();
38✔
125

126
    if (m_nStructTreeRootId.toBool())
38✔
127
    {
128
        auto nParentTreeId = AllocNewObject();
3✔
129
        StartObj(nParentTreeId);
3✔
130
        VSIFPrintfL(m_fp, "<< /Nums [ ");
3✔
131
        for (size_t i = 0; i < m_anParentElements.size(); i++)
6✔
132
        {
133
            VSIFPrintfL(m_fp, "%d %d 0 R ", static_cast<int>(i),
3✔
134
                        m_anParentElements[i].toInt());
3✔
135
        }
136
        VSIFPrintfL(m_fp, " ] >> \n");
3✔
137
        EndObj();
3✔
138

139
        StartObj(m_nStructTreeRootId);
3✔
140
        VSIFPrintfL(m_fp,
3✔
141
                    "<< "
142
                    "/Type /StructTreeRoot "
143
                    "/ParentTree %d 0 R "
144
                    "/K [ ",
145
                    nParentTreeId.toInt());
146
        for (const auto &num : m_anFeatureLayerId)
8✔
147
        {
148
            VSIFPrintfL(m_fp, "%d 0 R ", num.toInt());
5✔
149
        }
150
        VSIFPrintfL(m_fp, "] >>\n");
3✔
151
        EndObj();
3✔
152
    }
153

154
    StartObj(m_nCatalogId);
38✔
155
    {
156
        GDALPDFDictionaryRW oDict;
38✔
157
        oDict.Add("Type", GDALPDFObjectRW::CreateName("Catalog"))
38✔
158
            .Add("Pages", m_nPageResourceId, 0);
38✔
159
        if (m_nOutlinesId.toBool())
38✔
160
            oDict.Add("Outlines", m_nOutlinesId, 0);
1✔
161
        if (m_nXMPId.toBool())
38✔
162
            oDict.Add("Metadata", m_nXMPId, 0);
1✔
163
        if (!m_asOCGs.empty())
38✔
164
        {
165
            GDALPDFDictionaryRW *poDictOCProperties = new GDALPDFDictionaryRW();
5✔
166
            oDict.Add("OCProperties", poDictOCProperties);
5✔
167

168
            GDALPDFDictionaryRW *poDictD = new GDALPDFDictionaryRW();
5✔
169
            poDictOCProperties->Add("D", poDictD);
5✔
170

171
            if (m_bDisplayLayersOnlyOnVisiblePages)
5✔
172
            {
173
                poDictD->Add("ListMode",
174
                             GDALPDFObjectRW::CreateName("VisiblePages"));
1✔
175
            }
176

177
            /* Build "Order" array of D dict */
178
            GDALPDFArrayRW *poArrayOrder = CreateOCGOrder(&m_oTreeOfOGC);
5✔
179
            poDictD->Add("Order", poArrayOrder);
5✔
180

181
            /* Build "OFF" array of D dict */
182
            std::vector<GDALPDFObjectNum> offOCGs;
10✔
183
            CollectOffOCG(offOCGs, &m_oTreeOfOGC);
5✔
184
            if (!offOCGs.empty())
5✔
185
            {
186
                GDALPDFArrayRW *poArrayOFF = new GDALPDFArrayRW();
1✔
187
                for (const auto &num : offOCGs)
2✔
188
                {
189
                    poArrayOFF->Add(num, 0);
1✔
190
                }
191

192
                poDictD->Add("OFF", poArrayOFF);
1✔
193
            }
194

195
            /* Build "RBGroups" array of D dict */
196
            if (!m_oMapExclusiveOCGIdToOCGs.empty())
5✔
197
            {
198
                GDALPDFArrayRW *poArrayRBGroups = new GDALPDFArrayRW();
1✔
199
                for (const auto &group : m_oMapExclusiveOCGIdToOCGs)
2✔
200
                {
201
                    GDALPDFArrayRW *poGroup = new GDALPDFArrayRW();
1✔
202
                    for (const auto &num : group.second)
3✔
203
                    {
204
                        poGroup->Add(num, 0);
2✔
205
                    }
206
                    poArrayRBGroups->Add(poGroup);
1✔
207
                }
208

209
                poDictD->Add("RBGroups", poArrayRBGroups);
1✔
210
            }
211

212
            GDALPDFArrayRW *poArrayOGCs = new GDALPDFArrayRW();
5✔
213
            for (const auto &ocg : m_asOCGs)
17✔
214
                poArrayOGCs->Add(ocg.nId, 0);
12✔
215
            poDictOCProperties->Add("OCGs", poArrayOGCs);
5✔
216
        }
217

218
        if (m_nStructTreeRootId.toBool())
38✔
219
        {
220
            GDALPDFDictionaryRW *poDictMarkInfo = new GDALPDFDictionaryRW();
3✔
221
            oDict.Add("MarkInfo", poDictMarkInfo);
3✔
222
            poDictMarkInfo->Add("UserProperties",
223
                                GDALPDFObjectRW::CreateBool(TRUE));
3✔
224

225
            oDict.Add("StructTreeRoot", m_nStructTreeRootId, 0);
3✔
226
        }
227

228
        if (m_nNamesId.toBool())
38✔
229
            oDict.Add("Names", m_nNamesId, 0);
1✔
230

231
        VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
38✔
232
    }
233
    EndObj();
38✔
234
}
38✔
235

236
/************************************************************************/
237
/*                          CreateLayerTree()                           */
238
/************************************************************************/
239

240
bool GDALPDFComposerWriter::CreateLayerTree(const CPLXMLNode *psNode,
19✔
241
                                            const GDALPDFObjectNum &nParentId,
242
                                            TreeOfOCG *parent)
243
{
244
    for (const auto *psIter = psNode->psChild; psIter; psIter = psIter->psNext)
59✔
245
    {
246
        if (psIter->eType == CXT_Element &&
43✔
247
            strcmp(psIter->pszValue, "Layer") == 0)
15✔
248
        {
249
            const char *pszId = CPLGetXMLValue(psIter, "id", nullptr);
15✔
250
            if (!pszId)
15✔
251
            {
252
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
253
                         "Missing id attribute in Layer");
254
                return false;
3✔
255
            }
256
            const char *pszName = CPLGetXMLValue(psIter, "name", nullptr);
14✔
257
            if (!pszName)
14✔
258
            {
259
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
260
                         "Missing name attribute in Layer");
261
                return false;
1✔
262
            }
263
            if (m_oMapLayerIdToOCG.find(pszId) != m_oMapLayerIdToOCG.end())
13✔
264
            {
265
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
266
                         "Layer.id = %s is not unique", pszId);
267
                return false;
1✔
268
            }
269

270
            const bool bInitiallyVisible =
271
                CPLTestBool(CPLGetXMLValue(psIter, "initiallyVisible", "true"));
12✔
272

273
            const char *pszMutuallyExclusiveGroupId =
274
                CPLGetXMLValue(psIter, "mutuallyExclusiveGroupId", nullptr);
12✔
275

276
            auto nThisObjId = WriteOCG(pszName, nParentId);
12✔
277
            m_oMapLayerIdToOCG[pszId] = nThisObjId;
12✔
278

279
            auto newTreeOfOCG = std::make_unique<TreeOfOCG>();
12✔
280
            newTreeOfOCG->m_nNum = nThisObjId;
12✔
281
            newTreeOfOCG->m_bInitiallyVisible = bInitiallyVisible;
12✔
282
            parent->m_children.emplace_back(std::move(newTreeOfOCG));
12✔
283

284
            if (pszMutuallyExclusiveGroupId)
12✔
285
            {
286
                m_oMapExclusiveOCGIdToOCGs[pszMutuallyExclusiveGroupId]
4✔
287
                    .push_back(nThisObjId);
2✔
288
            }
289

290
            if (!CreateLayerTree(psIter, nThisObjId,
12✔
291
                                 parent->m_children.back().get()))
12✔
292
            {
293
                return false;
×
294
            }
295
        }
296
    }
297
    return true;
16✔
298
}
299

300
/************************************************************************/
301
/*                             ParseActions()                           */
302
/************************************************************************/
303

304
bool GDALPDFComposerWriter::ParseActions(
12✔
305
    const CPLXMLNode *psNode, std::vector<std::unique_ptr<Action>> &actions)
306
{
307
    std::set<GDALPDFObjectNum> anONLayers{};
24✔
308
    std::set<GDALPDFObjectNum> anOFFLayers{};
24✔
309
    for (const auto *psIter = psNode->psChild; psIter; psIter = psIter->psNext)
23✔
310
    {
311
        if (psIter->eType == CXT_Element &&
15✔
312
            strcmp(psIter->pszValue, "GotoPageAction") == 0)
15✔
313
        {
314
            auto poAction = std::make_unique<GotoPageAction>();
7✔
315
            const char *pszPageId = CPLGetXMLValue(psIter, "pageId", nullptr);
7✔
316
            if (!pszPageId)
7✔
317
            {
318
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
319
                         "Missing pageId attribute in GotoPageAction");
320
                return false;
1✔
321
            }
322

323
            auto oIter = m_oMapPageIdToObjectNum.find(pszPageId);
6✔
324
            if (oIter == m_oMapPageIdToObjectNum.end())
6✔
325
            {
326
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
327
                         "GotoPageAction.pageId = %s not pointing to a Page.id",
328
                         pszPageId);
329
                return false;
1✔
330
            }
331
            poAction->m_nPageDestId = oIter->second;
5✔
332
            poAction->m_dfX1 = CPLAtof(CPLGetXMLValue(psIter, "x1", "0"));
5✔
333
            poAction->m_dfX2 = CPLAtof(CPLGetXMLValue(psIter, "y1", "0"));
5✔
334
            poAction->m_dfY1 = CPLAtof(CPLGetXMLValue(psIter, "x2", "0"));
5✔
335
            poAction->m_dfY2 = CPLAtof(CPLGetXMLValue(psIter, "y2", "0"));
5✔
336
            actions.push_back(std::move(poAction));
10✔
337
        }
338
        else if (psIter->eType == CXT_Element &&
8✔
339
                 strcmp(psIter->pszValue, "SetAllLayersStateAction") == 0)
8✔
340
        {
341
            if (CPLTestBool(CPLGetXMLValue(psIter, "visible", "true")))
2✔
342
            {
343
                for (const auto &ocg : m_asOCGs)
3✔
344
                {
345
                    anOFFLayers.erase(ocg.nId);
2✔
346
                    anONLayers.insert(ocg.nId);
2✔
347
                }
348
            }
349
            else
350
            {
351
                for (const auto &ocg : m_asOCGs)
3✔
352
                {
353
                    anONLayers.erase(ocg.nId);
2✔
354
                    anOFFLayers.insert(ocg.nId);
2✔
355
                }
356
            }
2✔
357
        }
358
        else if (psIter->eType == CXT_Element &&
6✔
359
                 strcmp(psIter->pszValue, "SetLayerStateAction") == 0)
6✔
360
        {
361
            const char *pszLayerId = CPLGetXMLValue(psIter, "layerId", nullptr);
5✔
362
            if (!pszLayerId)
5✔
363
            {
364
                CPLError(CE_Failure, CPLE_AppDefined, "Missing layerId");
1✔
365
                return false;
2✔
366
            }
367
            auto oIter = m_oMapLayerIdToOCG.find(pszLayerId);
4✔
368
            if (oIter == m_oMapLayerIdToOCG.end())
4✔
369
            {
370
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
371
                         "Referencing layer of unknown id: %s", pszLayerId);
372
                return false;
1✔
373
            }
374
            const auto &ocg = oIter->second;
3✔
375

376
            if (CPLTestBool(CPLGetXMLValue(psIter, "visible", "true")))
3✔
377
            {
378
                anOFFLayers.erase(ocg);
2✔
379
                anONLayers.insert(ocg);
2✔
380
            }
381
            else
382
            {
383
                anONLayers.erase(ocg);
1✔
384
                anOFFLayers.insert(ocg);
1✔
385
            }
3✔
386
        }
387
        else if (psIter->eType == CXT_Element &&
1✔
388
                 strcmp(psIter->pszValue, "JavascriptAction") == 0)
1✔
389
        {
390
            auto poAction = std::make_unique<JavascriptAction>();
1✔
391
            poAction->m_osScript = CPLGetXMLValue(psIter, nullptr, "");
1✔
392
            actions.push_back(std::move(poAction));
1✔
393
        }
394
    }
395

396
    if (!anONLayers.empty() || !anOFFLayers.empty())
8✔
397
    {
398
        auto poAction = std::make_unique<SetLayerStateAction>();
4✔
399
        poAction->m_anONLayers = std::move(anONLayers);
4✔
400
        poAction->m_anOFFLayers = std::move(anOFFLayers);
4✔
401
        actions.push_back(std::move(poAction));
4✔
402
    }
403

404
    return true;
8✔
405
}
406

407
/************************************************************************/
408
/*                       CreateOutlineFirstPass()                       */
409
/************************************************************************/
410

411
bool GDALPDFComposerWriter::CreateOutlineFirstPass(const CPLXMLNode *psNode,
14✔
412
                                                   OutlineItem *poParentItem)
413
{
414
    for (const auto *psIter = psNode->psChild; psIter; psIter = psIter->psNext)
43✔
415
    {
416
        if (psIter->eType == CXT_Element &&
33✔
417
            strcmp(psIter->pszValue, "OutlineItem") == 0)
21✔
418
        {
419
            auto newItem = std::make_unique<OutlineItem>();
13✔
420
            const char *pszName = CPLGetXMLValue(psIter, "name", nullptr);
13✔
421
            if (!pszName)
13✔
422
            {
423
                CPLError(CE_Failure, CPLE_AppDefined,
×
424
                         "Missing name attribute in OutlineItem");
425
                return false;
×
426
            }
427
            newItem->m_osName = pszName;
13✔
428
            newItem->m_bOpen =
26✔
429
                CPLTestBool(CPLGetXMLValue(psIter, "open", "true"));
13✔
430
            if (CPLTestBool(CPLGetXMLValue(psIter, "italic", "false")))
13✔
431
                newItem->m_nFlags |= 1 << 0;
1✔
432
            if (CPLTestBool(CPLGetXMLValue(psIter, "bold", "false")))
13✔
433
                newItem->m_nFlags |= 1 << 1;
1✔
434

435
            const auto poActions = CPLGetXMLNode(psIter, "Actions");
13✔
436
            if (poActions)
13✔
437
            {
438
                if (!ParseActions(poActions, newItem->m_aoActions))
12✔
439
                    return false;
4✔
440
            }
441

442
            newItem->m_nObjId = AllocNewObject();
9✔
443
            if (!CreateOutlineFirstPass(psIter, newItem.get()))
9✔
444
            {
445
                return false;
×
446
            }
447
            poParentItem->m_nKidsRecCount += 1 + newItem->m_nKidsRecCount;
9✔
448
            poParentItem->m_aoKids.push_back(std::move(newItem));
9✔
449
        }
450
    }
451
    return true;
10✔
452
}
453

454
/************************************************************************/
455
/*                            SerializeActions()                        */
456
/************************************************************************/
457

458
GDALPDFDictionaryRW *GDALPDFComposerWriter::SerializeActions(
9✔
459
    GDALPDFDictionaryRW *poDictForDest,
460
    const std::vector<std::unique_ptr<Action>> &actions)
461
{
462
    GDALPDFDictionaryRW *poRetAction = nullptr;
9✔
463
    GDALPDFDictionaryRW *poLastActionDict = nullptr;
9✔
464
    for (const auto &poAction : actions)
19✔
465
    {
466
        GDALPDFDictionaryRW *poActionDict = nullptr;
10✔
467
        auto poGotoPageAction = dynamic_cast<GotoPageAction *>(poAction.get());
10✔
468
        if (poGotoPageAction)
10✔
469
        {
470
            GDALPDFArrayRW *poDest = new GDALPDFArrayRW;
5✔
471
            poDest->Add(poGotoPageAction->m_nPageDestId, 0);
5✔
472
            if (poGotoPageAction->m_dfX1 == 0.0 &&
5✔
473
                poGotoPageAction->m_dfX2 == 0.0 &&
4✔
474
                poGotoPageAction->m_dfY1 == 0.0 &&
4✔
475
                poGotoPageAction->m_dfY2 == 0.0)
4✔
476
            {
477
                poDest->Add(GDALPDFObjectRW::CreateName("XYZ"))
4✔
478
                    .Add(GDALPDFObjectRW::CreateNull())
4✔
479
                    .Add(GDALPDFObjectRW::CreateNull())
4✔
480
                    .Add(GDALPDFObjectRW::CreateNull());
4✔
481
            }
482
            else
483
            {
484
                poDest->Add(GDALPDFObjectRW::CreateName("FitR"))
1✔
485
                    .Add(poGotoPageAction->m_dfX1)
1✔
486
                    .Add(poGotoPageAction->m_dfY1)
1✔
487
                    .Add(poGotoPageAction->m_dfX2)
1✔
488
                    .Add(poGotoPageAction->m_dfY2);
1✔
489
            }
490
            if (poDictForDest && actions.size() == 1)
5✔
491
            {
492
                poDictForDest->Add("Dest", poDest);
4✔
493
            }
494
            else
495
            {
496
                poActionDict = new GDALPDFDictionaryRW();
1✔
497
                poActionDict->Add("Type",
498
                                  GDALPDFObjectRW::CreateName("Action"));
1✔
499
                poActionDict->Add("S", GDALPDFObjectRW::CreateName("GoTo"));
1✔
500
                poActionDict->Add("D", poDest);
1✔
501
            }
502
        }
503

504
        auto setLayerStateAction =
505
            dynamic_cast<SetLayerStateAction *>(poAction.get());
10✔
506
        if (poActionDict == nullptr && setLayerStateAction)
10✔
507
        {
508
            poActionDict = new GDALPDFDictionaryRW();
4✔
509
            poActionDict->Add("Type", GDALPDFObjectRW::CreateName("Action"));
4✔
510
            poActionDict->Add("S", GDALPDFObjectRW::CreateName("SetOCGState"));
4✔
511
            auto poStateArray = new GDALPDFArrayRW();
4✔
512
            if (!setLayerStateAction->m_anOFFLayers.empty())
4✔
513
            {
514
                poStateArray->Add(GDALPDFObjectRW::CreateName("OFF"));
2✔
515
                for (const auto &ocg : setLayerStateAction->m_anOFFLayers)
5✔
516
                    poStateArray->Add(ocg, 0);
3✔
517
            }
518
            if (!setLayerStateAction->m_anONLayers.empty())
4✔
519
            {
520
                poStateArray->Add(GDALPDFObjectRW::CreateName("ON"));
3✔
521
                for (const auto &ocg : setLayerStateAction->m_anONLayers)
7✔
522
                    poStateArray->Add(ocg, 0);
4✔
523
            }
524
            poActionDict->Add("State", poStateArray);
4✔
525
        }
526

527
        auto javascriptAction =
528
            dynamic_cast<JavascriptAction *>(poAction.get());
10✔
529
        if (poActionDict == nullptr && javascriptAction)
10✔
530
        {
531
            poActionDict = new GDALPDFDictionaryRW();
1✔
532
            poActionDict->Add("Type", GDALPDFObjectRW::CreateName("Action"));
1✔
533
            poActionDict->Add("S", GDALPDFObjectRW::CreateName("JavaScript"));
1✔
534
            poActionDict->Add("JS", javascriptAction->m_osScript);
1✔
535
        }
536

537
        if (poActionDict)
10✔
538
        {
539
            if (poLastActionDict == nullptr)
6✔
540
            {
541
                poRetAction = poActionDict;
4✔
542
            }
543
            else
544
            {
545
                poLastActionDict->Add("Next", poActionDict);
2✔
546
            }
547
            poLastActionDict = poActionDict;
6✔
548
        }
549
    }
550
    return poRetAction;
9✔
551
}
552

553
/************************************************************************/
554
/*                        SerializeOutlineKids()                        */
555
/************************************************************************/
556

557
bool GDALPDFComposerWriter::SerializeOutlineKids(
10✔
558
    const OutlineItem *poParentItem)
559
{
560
    for (size_t i = 0; i < poParentItem->m_aoKids.size(); i++)
19✔
561
    {
562
        const auto &poItem = poParentItem->m_aoKids[i];
9✔
563
        StartObj(poItem->m_nObjId);
9✔
564
        GDALPDFDictionaryRW oDict;
9✔
565
        oDict.Add("Title", poItem->m_osName);
9✔
566

567
        auto poActionDict = SerializeActions(&oDict, poItem->m_aoActions);
9✔
568
        if (poActionDict)
9✔
569
        {
570
            oDict.Add("A", poActionDict);
4✔
571
        }
572

573
        if (i > 0)
9✔
574
        {
575
            oDict.Add("Prev", poParentItem->m_aoKids[i - 1]->m_nObjId, 0);
6✔
576
        }
577
        if (i + 1 < poParentItem->m_aoKids.size())
9✔
578
        {
579
            oDict.Add("Next", poParentItem->m_aoKids[i + 1]->m_nObjId, 0);
6✔
580
        }
581
        if (poItem->m_nFlags)
9✔
582
            oDict.Add("F", poItem->m_nFlags);
2✔
583
        oDict.Add("Parent", poParentItem->m_nObjId, 0);
9✔
584
        if (!poItem->m_aoKids.empty())
9✔
585
        {
586
            oDict.Add("First", poItem->m_aoKids.front()->m_nObjId, 0);
2✔
587
            oDict.Add("Last", poItem->m_aoKids.back()->m_nObjId, 0);
2✔
588
            oDict.Add("Count", poItem->m_bOpen ? poItem->m_nKidsRecCount
3✔
589
                                               : -poItem->m_nKidsRecCount);
3✔
590
        }
591
        int ret = VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
9✔
592
        EndObj();
9✔
593
        if (ret == 0)
9✔
594
            return false;
×
595
        if (!SerializeOutlineKids(poItem.get()))
9✔
596
            return false;
×
597
    }
598
    return true;
10✔
599
}
600

601
/************************************************************************/
602
/*                           CreateOutline()                            */
603
/************************************************************************/
604

605
bool GDALPDFComposerWriter::CreateOutline(const CPLXMLNode *psNode)
5✔
606
{
607
    OutlineItem oRootOutlineItem;
10✔
608
    if (!CreateOutlineFirstPass(psNode, &oRootOutlineItem))
5✔
609
        return false;
4✔
610
    if (oRootOutlineItem.m_aoKids.empty())
1✔
611
        return true;
×
612

613
    m_nOutlinesId = AllocNewObject();
1✔
614
    StartObj(m_nOutlinesId);
1✔
615
    GDALPDFDictionaryRW oDict;
2✔
616
    oDict.Add("Type", GDALPDFObjectRW::CreateName("Outlines"))
1✔
617
        .Add("First", oRootOutlineItem.m_aoKids.front()->m_nObjId, 0)
1✔
618
        .Add("Last", oRootOutlineItem.m_aoKids.back()->m_nObjId, 0)
1✔
619
        .Add("Count", oRootOutlineItem.m_nKidsRecCount);
1✔
620
    VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
1✔
621
    EndObj();
1✔
622
    oRootOutlineItem.m_nObjId = m_nOutlinesId;
1✔
623
    return SerializeOutlineKids(&oRootOutlineItem);
1✔
624
}
625

626
/************************************************************************/
627
/*                        GenerateGeoreferencing()                      */
628
/************************************************************************/
629

630
bool GDALPDFComposerWriter::GenerateGeoreferencing(
8✔
631
    const CPLXMLNode *psGeoreferencing, double dfWidthInUserUnit,
632
    double dfHeightInUserUnit, GDALPDFObjectNum &nViewportId,
633
    Georeferencing &georeferencing)
634
{
635
    double bboxX1 = 0;
8✔
636
    double bboxY1 = 0;
8✔
637
    double bboxX2 = dfWidthInUserUnit;
8✔
638
    double bboxY2 = dfHeightInUserUnit;
8✔
639
    const auto psBoundingBox = CPLGetXMLNode(psGeoreferencing, "BoundingBox");
8✔
640
    if (psBoundingBox)
8✔
641
    {
642
        bboxX1 = CPLAtof(
7✔
643
            CPLGetXMLValue(psBoundingBox, "x1", CPLSPrintf("%.17g", bboxX1)));
644
        bboxY1 = CPLAtof(
7✔
645
            CPLGetXMLValue(psBoundingBox, "y1", CPLSPrintf("%.17g", bboxY1)));
646
        bboxX2 = CPLAtof(
7✔
647
            CPLGetXMLValue(psBoundingBox, "x2", CPLSPrintf("%.17g", bboxX2)));
648
        bboxY2 = CPLAtof(
7✔
649
            CPLGetXMLValue(psBoundingBox, "y2", CPLSPrintf("%.17g", bboxY2)));
650
        if (bboxX2 <= bboxX1 || bboxY2 <= bboxY1)
7✔
651
        {
652
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid BoundingBox");
1✔
653
            return false;
1✔
654
        }
655
    }
656

657
    std::vector<gdal::GCP> aGCPs;
14✔
658
    for (const auto *psIter = psGeoreferencing->psChild; psIter;
54✔
659
         psIter = psIter->psNext)
47✔
660
    {
661
        if (psIter->eType == CXT_Element &&
48✔
662
            strcmp(psIter->pszValue, "ControlPoint") == 0)
42✔
663
        {
664
            const char *pszx = CPLGetXMLValue(psIter, "x", nullptr);
27✔
665
            const char *pszy = CPLGetXMLValue(psIter, "y", nullptr);
27✔
666
            const char *pszX = CPLGetXMLValue(psIter, "GeoX", nullptr);
27✔
667
            const char *pszY = CPLGetXMLValue(psIter, "GeoY", nullptr);
27✔
668
            if (!pszx || !pszy || !pszX || !pszY)
27✔
669
            {
670
                CPLError(CE_Failure, CPLE_NotSupported,
1✔
671
                         "At least one of x, y, GeoX or GeoY attribute "
672
                         "missing on ControlPoint");
673
                return false;
1✔
674
            }
675
            aGCPs.emplace_back(nullptr, nullptr, CPLAtof(pszx), CPLAtof(pszy),
26✔
676
                               CPLAtof(pszX), CPLAtof(pszY));
52✔
677
        }
678
    }
679
    if (aGCPs.size() < 4)
6✔
680
    {
681
        CPLError(CE_Failure, CPLE_NotSupported,
1✔
682
                 "At least 4 ControlPoint are required");
683
        return false;
1✔
684
    }
685

686
    const char *pszBoundingPolygon =
687
        CPLGetXMLValue(psGeoreferencing, "BoundingPolygon", nullptr);
5✔
688
    std::vector<xyPair> aBoundingPolygon;
10✔
689
    if (pszBoundingPolygon)
5✔
690
    {
691
        auto [poGeom, _] =
2✔
692
            OGRGeometryFactory::createFromWkt(pszBoundingPolygon);
4✔
693
        if (poGeom && poGeom->getGeometryType() == wkbPolygon)
2✔
694
        {
695
            auto poPoly = poGeom->toPolygon();
2✔
696
            auto poRing = poPoly->getExteriorRing();
2✔
697
            if (poRing)
2✔
698
            {
699
                if (psBoundingBox == nullptr)
2✔
700
                {
701
                    OGREnvelope sEnvelope;
×
702
                    poRing->getEnvelope(&sEnvelope);
×
703
                    bboxX1 = sEnvelope.MinX;
×
704
                    bboxY1 = sEnvelope.MinY;
×
705
                    bboxX2 = sEnvelope.MaxX;
×
706
                    bboxY2 = sEnvelope.MaxY;
×
707
                }
708
                for (int i = 0; i < poRing->getNumPoints(); i++)
12✔
709
                {
710
                    aBoundingPolygon.emplace_back(
711
                        xyPair(poRing->getX(i), poRing->getY(i)));
10✔
712
                }
713
            }
714
        }
715
    }
716

717
    const auto pszSRS = CPLGetXMLValue(psGeoreferencing, "SRS", nullptr);
5✔
718
    if (!pszSRS)
5✔
719
    {
720
        CPLError(CE_Failure, CPLE_NotSupported, "Missing SRS");
1✔
721
        return false;
1✔
722
    }
723
    auto poSRS = std::make_unique<OGRSpatialReference>();
8✔
724
    if (poSRS->SetFromUserInput(pszSRS) != OGRERR_NONE)
4✔
725
    {
726
        return false;
×
727
    }
728
    poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
4✔
729

730
    if (CPLTestBool(CPLGetXMLValue(psGeoreferencing, "ISO32000ExtensionFormat",
4✔
731
                                   "true")))
732
    {
733
        nViewportId = GenerateISO32000_Georeferencing(
734
            OGRSpatialReference::ToHandle(poSRS.get()), bboxX1, bboxY1, bboxX2,
735
            bboxY2, aGCPs, aBoundingPolygon);
4✔
736
        if (!nViewportId.toBool())
4✔
737
        {
738
            return false;
×
739
        }
740
    }
741

742
    if (CPLTestBool(
4✔
743
            CPLGetXMLValue(psGeoreferencing, "OGCBestPracticeFormat", "false")))
744
    {
745
        CPLError(CE_Failure, CPLE_NotSupported,
×
746
                 "OGCBestPracticeFormat no longer supported. Use "
747
                 "ISO32000ExtensionFormat");
748
        return false;
×
749
    }
750

751
    const char *pszId = CPLGetXMLValue(psGeoreferencing, "id", nullptr);
4✔
752
    if (pszId)
4✔
753
    {
754
        if (!GDALGCPsToGeoTransform(static_cast<int>(aGCPs.size()),
3✔
755
                                    gdal::GCP::c_ptr(aGCPs),
756
                                    georeferencing.m_gt.data(), TRUE))
757
        {
758
            CPLError(CE_Failure, CPLE_AppDefined,
×
759
                     "Could not compute geotransform with approximate match.");
760
            return false;
×
761
        }
762
        if (std::fabs(georeferencing.m_gt[2]) <
3✔
763
                1e-5 * std::fabs(georeferencing.m_gt[1]) &&
6✔
764
            std::fabs(georeferencing.m_gt[4]) <
3✔
765
                1e-5 * std::fabs(georeferencing.m_gt[5]))
3✔
766
        {
767
            georeferencing.m_gt[2] = 0;
3✔
768
            georeferencing.m_gt[4] = 0;
3✔
769
        }
770

771
        georeferencing.m_osID = pszId;
3✔
772
        georeferencing.m_oSRS = *(poSRS.get());
3✔
773
        georeferencing.m_bboxX1 = bboxX1;
3✔
774
        georeferencing.m_bboxY1 = bboxY1;
3✔
775
        georeferencing.m_bboxX2 = bboxX2;
3✔
776
        georeferencing.m_bboxY2 = bboxY2;
3✔
777
    }
778

779
    return true;
4✔
780
}
781

782
/************************************************************************/
783
/*                      GenerateISO32000_Georeferencing()               */
784
/************************************************************************/
785

786
GDALPDFObjectNum GDALPDFComposerWriter::GenerateISO32000_Georeferencing(
4✔
787
    OGRSpatialReferenceH hSRS, double bboxX1, double bboxY1, double bboxX2,
788
    double bboxY2, const std::vector<gdal::GCP> &aGCPs,
789
    const std::vector<xyPair> &aBoundingPolygon)
790
{
791
    OGRSpatialReferenceH hSRSGeog = OSRCloneGeogCS(hSRS);
4✔
792
    if (hSRSGeog == nullptr)
4✔
793
    {
794
        return GDALPDFObjectNum();
×
795
    }
796
    OSRSetAxisMappingStrategy(hSRSGeog, OAMS_TRADITIONAL_GIS_ORDER);
4✔
797
    OGRCoordinateTransformationH hCT =
798
        OCTNewCoordinateTransformation(hSRS, hSRSGeog);
4✔
799
    if (hCT == nullptr)
4✔
800
    {
801
        OSRDestroySpatialReference(hSRSGeog);
×
802
        return GDALPDFObjectNum();
×
803
    }
804

805
    std::vector<gdal::GCP> aGCPReprojected;
8✔
806
    bool bSuccess = true;
4✔
807
    for (const auto &gcp : aGCPs)
20✔
808
    {
809
        double X = gcp.X();
16✔
810
        double Y = gcp.Y();
16✔
811
        bSuccess &= OCTTransform(hCT, 1, &X, &Y, nullptr) == 1;
16✔
812
        aGCPReprojected.emplace_back(nullptr, nullptr, gcp.Pixel(), gcp.Line(),
32✔
813
                                     X, Y);
16✔
814
    }
815
    if (!bSuccess)
4✔
816
    {
817
        OSRDestroySpatialReference(hSRSGeog);
×
818
        OCTDestroyCoordinateTransformation(hCT);
×
819

820
        return GDALPDFObjectNum();
×
821
    }
822

823
    const char *pszAuthorityCode = OSRGetAuthorityCode(hSRS, nullptr);
4✔
824
    const char *pszAuthorityName = OSRGetAuthorityName(hSRS, nullptr);
4✔
825
    int nEPSGCode = 0;
4✔
826
    if (pszAuthorityName != nullptr && EQUAL(pszAuthorityName, "EPSG") &&
4✔
827
        pszAuthorityCode != nullptr)
828
        nEPSGCode = atoi(pszAuthorityCode);
4✔
829

830
    int bIsGeographic = OSRIsGeographic(hSRS);
4✔
831

832
    char *pszESRIWKT = nullptr;
4✔
833
    const char *apszOptions[] = {"FORMAT=WKT1_ESRI", nullptr};
4✔
834
    OSRExportToWktEx(hSRS, &pszESRIWKT, apszOptions);
4✔
835

836
    OSRDestroySpatialReference(hSRSGeog);
4✔
837
    OCTDestroyCoordinateTransformation(hCT);
4✔
838

839
    auto nViewportId = AllocNewObject();
4✔
840
    auto nMeasureId = AllocNewObject();
4✔
841
    auto nGCSId = AllocNewObject();
4✔
842

843
    StartObj(nViewportId);
4✔
844
    GDALPDFDictionaryRW oViewPortDict;
8✔
845
    oViewPortDict.Add("Type", GDALPDFObjectRW::CreateName("Viewport"))
4✔
846
        .Add("Name", "Layer")
4✔
847
        .Add("BBox", &((new GDALPDFArrayRW())
4✔
848
                           ->Add(bboxX1)
4✔
849
                           .Add(bboxY1)
4✔
850
                           .Add(bboxX2)
4✔
851
                           .Add(bboxY2)))
4✔
852
        .Add("Measure", nMeasureId, 0);
4✔
853
    VSIFPrintfL(m_fp, "%s\n", oViewPortDict.Serialize().c_str());
4✔
854
    EndObj();
4✔
855

856
    GDALPDFArrayRW *poGPTS = new GDALPDFArrayRW();
4✔
857
    GDALPDFArrayRW *poLPTS = new GDALPDFArrayRW();
4✔
858

859
    const int nPrecision =
860
        atoi(CPLGetConfigOption("PDF_COORD_DOUBLE_PRECISION", "16"));
4✔
861
    for (const auto &gcp : aGCPReprojected)
20✔
862
    {
863
        poGPTS->AddWithPrecision(gcp.Y(), nPrecision)
16✔
864
            .AddWithPrecision(gcp.X(), nPrecision);  // Lat, long order
16✔
865
        poLPTS
866
            ->AddWithPrecision((gcp.Pixel() - bboxX1) / (bboxX2 - bboxX1),
16✔
867
                               nPrecision)
16✔
868
            .AddWithPrecision((gcp.Line() - bboxY1) / (bboxY2 - bboxY1),
16✔
869
                              nPrecision);
16✔
870
    }
871

872
    StartObj(nMeasureId);
4✔
873
    GDALPDFDictionaryRW oMeasureDict;
8✔
874
    oMeasureDict.Add("Type", GDALPDFObjectRW::CreateName("Measure"))
4✔
875
        .Add("Subtype", GDALPDFObjectRW::CreateName("GEO"))
4✔
876
        .Add("GPTS", poGPTS)
4✔
877
        .Add("LPTS", poLPTS)
4✔
878
        .Add("GCS", nGCSId, 0);
4✔
879
    if (!aBoundingPolygon.empty())
4✔
880
    {
881
        GDALPDFArrayRW *poBounds = new GDALPDFArrayRW();
1✔
882
        for (const auto &xy : aBoundingPolygon)
6✔
883
        {
884
            poBounds->Add((xy.x - bboxX1) / (bboxX2 - bboxX1))
5✔
885
                .Add((xy.y - bboxY1) / (bboxY2 - bboxY1));
5✔
886
        }
887
        oMeasureDict.Add("Bounds", poBounds);
1✔
888
    }
889
    VSIFPrintfL(m_fp, "%s\n", oMeasureDict.Serialize().c_str());
4✔
890
    EndObj();
4✔
891

892
    StartObj(nGCSId);
4✔
893
    GDALPDFDictionaryRW oGCSDict;
4✔
894
    oGCSDict
895
        .Add("Type",
896
             GDALPDFObjectRW::CreateName(bIsGeographic ? "GEOGCS" : "PROJCS"))
4✔
897
        .Add("WKT", pszESRIWKT);
4✔
898
    if (nEPSGCode)
4✔
899
        oGCSDict.Add("EPSG", nEPSGCode);
4✔
900
    VSIFPrintfL(m_fp, "%s\n", oGCSDict.Serialize().c_str());
4✔
901
    EndObj();
4✔
902

903
    CPLFree(pszESRIWKT);
4✔
904

905
    return nViewportId;
4✔
906
}
907

908
/************************************************************************/
909
/*                         GeneratePage()                               */
910
/************************************************************************/
911

912
bool GDALPDFComposerWriter::GeneratePage(const CPLXMLNode *psPage)
37✔
913
{
914
    double dfWidthInUserUnit = CPLAtof(CPLGetXMLValue(psPage, "Width", "-1"));
37✔
915
    double dfHeightInUserUnit = CPLAtof(CPLGetXMLValue(psPage, "Height", "-1"));
37✔
916
    if (dfWidthInUserUnit <= 0 || dfWidthInUserUnit >= MAXIMUM_SIZE_IN_UNITS ||
37✔
917
        dfHeightInUserUnit <= 0 || dfHeightInUserUnit >= MAXIMUM_SIZE_IN_UNITS)
36✔
918
    {
919
        CPLError(CE_Failure, CPLE_AppDefined,
1✔
920
                 "Missing or invalid Width and/or Height");
921
        return false;
1✔
922
    }
923
    double dfUserUnit =
924
        CPLAtof(CPLGetXMLValue(psPage, "DPI", CPLSPrintf("%f", DEFAULT_DPI))) *
36✔
925
        USER_UNIT_IN_INCH;
36✔
926

927
    std::vector<GDALPDFObjectNum> anViewportIds;
72✔
928

929
    PageContext oPageContext;
72✔
930
    for (const auto *psIter = psPage->psChild; psIter; psIter = psIter->psNext)
149✔
931
    {
932
        if (psIter->eType == CXT_Element &&
117✔
933
            strcmp(psIter->pszValue, "Georeferencing") == 0)
109✔
934
        {
935
            GDALPDFObjectNum nViewportId;
8✔
936
            Georeferencing georeferencing;
8✔
937
            if (!GenerateGeoreferencing(psIter, dfWidthInUserUnit,
8✔
938
                                        dfHeightInUserUnit, nViewportId,
939
                                        georeferencing))
940
            {
941
                return false;
4✔
942
            }
943
            if (nViewportId.toBool())
4✔
944
                anViewportIds.emplace_back(nViewportId);
4✔
945
            if (!georeferencing.m_osID.empty())
4✔
946
            {
947
                oPageContext.m_oMapGeoreferencedId[georeferencing.m_osID] =
3✔
948
                    georeferencing;
3✔
949
            }
950
        }
951
    }
952

953
    auto nPageId = AllocNewObject();
32✔
954
    m_asPageId.push_back(nPageId);
32✔
955

956
    const char *pszId = CPLGetXMLValue(psPage, "id", nullptr);
32✔
957
    if (pszId)
32✔
958
    {
959
        if (m_oMapPageIdToObjectNum.find(pszId) !=
8✔
960
            m_oMapPageIdToObjectNum.end())
16✔
961
        {
962
            CPLError(CE_Failure, CPLE_AppDefined, "Duplicated page id %s",
1✔
963
                     pszId);
964
            return false;
1✔
965
        }
966
        m_oMapPageIdToObjectNum[pszId] = nPageId;
7✔
967
    }
968

969
    const auto psContent = CPLGetXMLNode(psPage, "Content");
31✔
970
    if (!psContent)
31✔
971
    {
972
        CPLError(CE_Failure, CPLE_AppDefined, "Missing Content");
1✔
973
        return false;
1✔
974
    }
975

976
    const bool bDeflateStreamCompression = EQUAL(
30✔
977
        CPLGetXMLValue(psContent, "streamCompression", "DEFLATE"), "DEFLATE");
978

979
    oPageContext.m_dfWidthInUserUnit = dfWidthInUserUnit;
30✔
980
    oPageContext.m_dfHeightInUserUnit = dfHeightInUserUnit;
30✔
981
    oPageContext.m_eStreamCompressMethod =
30✔
982
        bDeflateStreamCompression ? COMPRESS_DEFLATE : COMPRESS_NONE;
30✔
983
    if (!ExploreContent(psContent, oPageContext))
30✔
984
        return false;
13✔
985

986
    int nStructParentsIdx = -1;
17✔
987
    if (!oPageContext.m_anFeatureUserProperties.empty())
17✔
988
    {
989
        nStructParentsIdx = static_cast<int>(m_anParentElements.size());
3✔
990
        auto nParentsElements = AllocNewObject();
3✔
991
        m_anParentElements.push_back(nParentsElements);
3✔
992
        {
993
            StartObj(nParentsElements);
3✔
994
            VSIFPrintfL(m_fp, "[ ");
3✔
995
            for (const auto &num : oPageContext.m_anFeatureUserProperties)
11✔
996
                VSIFPrintfL(m_fp, "%d 0 R ", num.toInt());
8✔
997
            VSIFPrintfL(m_fp, " ]\n");
3✔
998
            EndObj();
3✔
999
        }
1000
    }
1001

1002
    GDALPDFObjectNum nAnnotsId;
17✔
1003
    if (!oPageContext.m_anAnnotationsId.empty())
17✔
1004
    {
1005
        /* -------------------------------------------------------------- */
1006
        /*  Write annotation arrays.                                      */
1007
        /* -------------------------------------------------------------- */
1008
        nAnnotsId = AllocNewObject();
1✔
1009
        StartObj(nAnnotsId);
1✔
1010
        {
1011
            GDALPDFArrayRW oArray;
1✔
1012
            for (size_t i = 0; i < oPageContext.m_anAnnotationsId.size(); i++)
2✔
1013
            {
1014
                oArray.Add(oPageContext.m_anAnnotationsId[i], 0);
1✔
1015
            }
1016
            VSIFPrintfL(m_fp, "%s\n", oArray.Serialize().c_str());
1✔
1017
        }
1018
        EndObj();
1✔
1019
    }
1020

1021
    auto nContentId = AllocNewObject();
17✔
1022
    auto nResourcesId = AllocNewObject();
17✔
1023

1024
    StartObj(nPageId);
17✔
1025
    GDALPDFDictionaryRW oDictPage;
17✔
1026
    oDictPage.Add("Type", GDALPDFObjectRW::CreateName("Page"))
17✔
1027
        .Add("Parent", m_nPageResourceId, 0)
17✔
1028
        .Add("MediaBox", &((new GDALPDFArrayRW())
17✔
1029
                               ->Add(0)
17✔
1030
                               .Add(0)
17✔
1031
                               .Add(dfWidthInUserUnit)
17✔
1032
                               .Add(dfHeightInUserUnit)))
17✔
1033
        .Add("UserUnit", dfUserUnit)
17✔
1034
        .Add("Contents", nContentId, 0)
17✔
1035
        .Add("Resources", nResourcesId, 0);
17✔
1036

1037
    if (nAnnotsId.toBool())
17✔
1038
        oDictPage.Add("Annots", nAnnotsId, 0);
1✔
1039

1040
    oDictPage.Add("Group",
1041
                  &((new GDALPDFDictionaryRW())
17✔
1042
                        ->Add("Type", GDALPDFObjectRW::CreateName("Group"))
17✔
1043
                        .Add("S", GDALPDFObjectRW::CreateName("Transparency"))
17✔
1044
                        .Add("CS", GDALPDFObjectRW::CreateName("DeviceRGB"))));
17✔
1045
    if (!anViewportIds.empty())
17✔
1046
    {
1047
        auto poViewports = new GDALPDFArrayRW();
4✔
1048
        for (const auto &id : anViewportIds)
8✔
1049
            poViewports->Add(id, 0);
4✔
1050
        oDictPage.Add("VP", poViewports);
4✔
1051
    }
1052

1053
    if (nStructParentsIdx >= 0)
17✔
1054
    {
1055
        oDictPage.Add("StructParents", nStructParentsIdx);
3✔
1056
    }
1057

1058
    VSIFPrintfL(m_fp, "%s\n", oDictPage.Serialize().c_str());
17✔
1059
    EndObj();
17✔
1060

1061
    /* -------------------------------------------------------------- */
1062
    /*  Write content dictionary                                      */
1063
    /* -------------------------------------------------------------- */
1064
    {
1065
        GDALPDFDictionaryRW oDict;
34✔
1066
        StartObjWithStream(nContentId, oDict, bDeflateStreamCompression);
17✔
1067
        VSIFPrintfL(m_fp, "%s", oPageContext.m_osDrawingStream.c_str());
17✔
1068
        EndObjWithStream();
17✔
1069
    }
1070

1071
    /* -------------------------------------------------------------- */
1072
    /*  Write page resource dictionary.                               */
1073
    /* -------------------------------------------------------------- */
1074
    StartObj(nResourcesId);
17✔
1075
    {
1076
        GDALPDFDictionaryRW oDict;
17✔
1077
        if (!oPageContext.m_oXObjects.empty())
17✔
1078
        {
1079
            GDALPDFDictionaryRW *poDict = new GDALPDFDictionaryRW();
8✔
1080
            for (const auto &kv : oPageContext.m_oXObjects)
18✔
1081
            {
1082
                poDict->Add(kv.first, kv.second, 0);
10✔
1083
            }
1084
            oDict.Add("XObject", poDict);
8✔
1085
        }
1086

1087
        if (!oPageContext.m_oProperties.empty())
17✔
1088
        {
1089
            GDALPDFDictionaryRW *poDict = new GDALPDFDictionaryRW();
3✔
1090
            for (const auto &kv : oPageContext.m_oProperties)
6✔
1091
            {
1092
                poDict->Add(kv.first, kv.second, 0);
3✔
1093
            }
1094
            oDict.Add("Properties", poDict);
3✔
1095
        }
1096

1097
        if (!oPageContext.m_oExtGState.empty())
17✔
1098
        {
1099
            GDALPDFDictionaryRW *poDict = new GDALPDFDictionaryRW();
5✔
1100
            for (const auto &kv : oPageContext.m_oExtGState)
13✔
1101
            {
1102
                poDict->Add(kv.first, kv.second, 0);
8✔
1103
            }
1104
            oDict.Add("ExtGState", poDict);
5✔
1105
        }
1106

1107
        VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
17✔
1108
    }
1109
    EndObj();
17✔
1110

1111
    return true;
17✔
1112
}
1113

1114
/************************************************************************/
1115
/*                          ExploreContent()                            */
1116
/************************************************************************/
1117

1118
bool GDALPDFComposerWriter::ExploreContent(const CPLXMLNode *psNode,
33✔
1119
                                           PageContext &oPageContext)
1120
{
1121
    for (const auto *psIter = psNode->psChild; psIter; psIter = psIter->psNext)
70✔
1122
    {
1123
        if (psIter->eType == CXT_Element &&
50✔
1124
            strcmp(psIter->pszValue, "IfLayerOn") == 0)
30✔
1125
        {
1126
            const char *pszLayerId = CPLGetXMLValue(psIter, "layerId", nullptr);
4✔
1127
            if (!pszLayerId)
4✔
1128
            {
1129
                CPLError(CE_Failure, CPLE_AppDefined, "Missing layerId");
×
1130
                return false;
1✔
1131
            }
1132
            auto oIter = m_oMapLayerIdToOCG.find(pszLayerId);
4✔
1133
            if (oIter == m_oMapLayerIdToOCG.end())
4✔
1134
            {
1135
                CPLError(CE_Failure, CPLE_AppDefined,
1✔
1136
                         "Referencing layer of unknown id: %s", pszLayerId);
1137
                return false;
1✔
1138
            }
1139
            oPageContext
1140
                .m_oProperties[CPLOPrintf("Lyr%d", oIter->second.toInt())] =
3✔
1141
                oIter->second;
3✔
1142
            oPageContext.m_osDrawingStream +=
1143
                CPLOPrintf("/OC /Lyr%d BDC\n", oIter->second.toInt());
3✔
1144
            if (!ExploreContent(psIter, oPageContext))
3✔
1145
                return false;
×
1146
            oPageContext.m_osDrawingStream += "EMC\n";
3✔
1147
        }
1148

1149
        else if (psIter->eType == CXT_Element &&
46✔
1150
                 strcmp(psIter->pszValue, "Raster") == 0)
26✔
1151
        {
1152
            if (!WriteRaster(psIter, oPageContext))
6✔
1153
                return false;
2✔
1154
        }
1155

1156
        else if (psIter->eType == CXT_Element &&
40✔
1157
                 strcmp(psIter->pszValue, "Vector") == 0)
20✔
1158
        {
1159
            if (!WriteVector(psIter, oPageContext))
5✔
1160
                return false;
×
1161
        }
1162

1163
        else if (psIter->eType == CXT_Element &&
35✔
1164
                 strcmp(psIter->pszValue, "VectorLabel") == 0)
15✔
1165
        {
1166
            if (!WriteVectorLabel(psIter, oPageContext))
3✔
1167
                return false;
×
1168
        }
1169

1170
        else if (psIter->eType == CXT_Element &&
32✔
1171
                 strcmp(psIter->pszValue, "PDF") == 0)
12✔
1172
        {
1173
#ifdef HAVE_PDF_READ_SUPPORT
1174
            if (!WritePDF(psIter, oPageContext))
12✔
1175
                return false;
10✔
1176
#else
1177
            CPLError(CE_Failure, CPLE_NotSupported,
1178
                     "PDF node not supported due to missing PDF read support "
1179
                     "in this GDAL build");
1180
            return false;
1181
#endif
1182
        }
1183
    }
1184
    return true;
20✔
1185
}
1186

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

1191
void GDALPDFComposerWriter::StartBlending(const CPLXMLNode *psNode,
12✔
1192
                                          PageContext &oPageContext,
1193
                                          double &dfOpacity)
1194
{
1195
    dfOpacity = 1;
12✔
1196
    const auto psBlending = CPLGetXMLNode(psNode, "Blending");
12✔
1197
    if (psBlending)
12✔
1198
    {
1199
        auto nExtGState = AllocNewObject();
5✔
1200
        StartObj(nExtGState);
5✔
1201
        {
1202
            GDALPDFDictionaryRW gs;
5✔
1203
            gs.Add("Type", GDALPDFObjectRW::CreateName("ExtGState"));
5✔
1204
            dfOpacity = CPLAtof(CPLGetXMLValue(psBlending, "opacity", "1"));
5✔
1205
            gs.Add("ca", dfOpacity);
5✔
1206
            gs.Add("BM", GDALPDFObjectRW::CreateName(
5✔
1207
                             CPLGetXMLValue(psBlending, "function", "Normal")));
5✔
1208
            VSIFPrintfL(m_fp, "%s\n", gs.Serialize().c_str());
5✔
1209
        }
1210
        EndObj();
5✔
1211
        oPageContext.m_oExtGState[CPLOPrintf("GS%d", nExtGState.toInt())] =
5✔
1212
            nExtGState;
1213
        oPageContext.m_osDrawingStream += "q\n";
5✔
1214
        oPageContext.m_osDrawingStream +=
1215
            CPLOPrintf("/GS%d gs\n", nExtGState.toInt());
5✔
1216
    }
1217
}
12✔
1218

1219
/************************************************************************/
1220
/*                          EndBlending()                             */
1221
/************************************************************************/
1222

1223
void GDALPDFComposerWriter::EndBlending(const CPLXMLNode *psNode,
12✔
1224
                                        PageContext &oPageContext)
1225
{
1226
    const auto psBlending = CPLGetXMLNode(psNode, "Blending");
12✔
1227
    if (psBlending)
12✔
1228
    {
1229
        oPageContext.m_osDrawingStream += "Q\n";
5✔
1230
    }
1231
}
12✔
1232

1233
/************************************************************************/
1234
/*                           WriteRaster()                              */
1235
/************************************************************************/
1236

1237
bool GDALPDFComposerWriter::WriteRaster(const CPLXMLNode *psNode,
6✔
1238
                                        PageContext &oPageContext)
1239
{
1240
    const char *pszDataset = CPLGetXMLValue(psNode, "dataset", nullptr);
6✔
1241
    if (!pszDataset)
6✔
1242
    {
1243
        CPLError(CE_Failure, CPLE_AppDefined, "Missing dataset");
1✔
1244
        return false;
1✔
1245
    }
1246
    double dfX1 = CPLAtof(CPLGetXMLValue(psNode, "x1", "0"));
5✔
1247
    double dfY1 = CPLAtof(CPLGetXMLValue(psNode, "y1", "0"));
5✔
1248
    double dfX2 = CPLAtof(CPLGetXMLValue(
5✔
1249
        psNode, "x2", CPLSPrintf("%.17g", oPageContext.m_dfWidthInUserUnit)));
1250
    double dfY2 = CPLAtof(CPLGetXMLValue(
5✔
1251
        psNode, "y2", CPLSPrintf("%.17g", oPageContext.m_dfHeightInUserUnit)));
1252
    if (dfX2 <= dfX1 || dfY2 <= dfY1)
5✔
1253
    {
1254
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid x1,y1,x2,y2");
×
1255
        return false;
×
1256
    }
1257
    GDALDatasetUniquePtr poDS(
1258
        GDALDataset::Open(pszDataset, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
1259
                          nullptr, nullptr, nullptr));
10✔
1260
    if (!poDS)
5✔
1261
        return false;
1✔
1262
    const int nWidth = poDS->GetRasterXSize();
4✔
1263
    const int nHeight = poDS->GetRasterYSize();
4✔
1264
    const int nBlockXSize =
1265
        std::max(16, atoi(CPLGetXMLValue(psNode, "tileSize", "256")));
4✔
1266
    const int nBlockYSize = nBlockXSize;
4✔
1267
    const char *pszCompressMethod =
1268
        CPLGetXMLValue(psNode, "Compression.method", "DEFLATE");
4✔
1269
    PDFCompressMethod eCompressMethod = COMPRESS_DEFLATE;
4✔
1270
    if (EQUAL(pszCompressMethod, "JPEG"))
4✔
1271
        eCompressMethod = COMPRESS_JPEG;
×
1272
    else if (EQUAL(pszCompressMethod, "JPEG2000"))
4✔
1273
        eCompressMethod = COMPRESS_JPEG2000;
×
1274
    const int nPredictor =
1275
        CPLTestBool(CPLGetXMLValue(psNode, "Compression.predictor", "false"))
4✔
1276
            ? 2
4✔
1277
            : 0;
4✔
1278
    const int nJPEGQuality =
1279
        atoi(CPLGetXMLValue(psNode, "Compression.quality", "-1"));
4✔
1280
    const char *pszJPEG2000_DRIVER =
1281
        m_osJPEG2000Driver.empty() ? nullptr : m_osJPEG2000Driver.c_str();
4✔
1282
    ;
1283

1284
    const char *pszGeoreferencingId =
1285
        CPLGetXMLValue(psNode, "georeferencingId", nullptr);
4✔
1286
    double dfClippingMinX = 0;
4✔
1287
    double dfClippingMinY = 0;
4✔
1288
    double dfClippingMaxX = 0;
4✔
1289
    double dfClippingMaxY = 0;
4✔
1290
    bool bClip = false;
4✔
1291
    GDALGeoTransform rasterGT;
4✔
1292
    GDALGeoTransform invGT;  // from georeferenced to PDF coordinates
4✔
1293
    if (pszGeoreferencingId)
4✔
1294
    {
1295
        auto iter =
1296
            oPageContext.m_oMapGeoreferencedId.find(pszGeoreferencingId);
1✔
1297
        if (iter == oPageContext.m_oMapGeoreferencedId.end())
1✔
1298
        {
1299
            CPLError(CE_Failure, CPLE_AppDefined,
×
1300
                     "Cannot find georeferencing of id %s",
1301
                     pszGeoreferencingId);
1302
            return false;
×
1303
        }
1304
        const auto &georeferencing = iter->second;
1✔
1305
        dfX1 = georeferencing.m_bboxX1;
1✔
1306
        dfY1 = georeferencing.m_bboxY1;
1✔
1307
        dfX2 = georeferencing.m_bboxX2;
1✔
1308
        dfY2 = georeferencing.m_bboxY2;
1✔
1309

1310
        bClip = true;
1✔
1311
        dfClippingMinX = APPLY_GT_X(georeferencing.m_gt, dfX1, dfY1);
1✔
1312
        dfClippingMinY = APPLY_GT_Y(georeferencing.m_gt, dfX1, dfY1);
1✔
1313
        dfClippingMaxX = APPLY_GT_X(georeferencing.m_gt, dfX2, dfY2);
1✔
1314
        dfClippingMaxY = APPLY_GT_Y(georeferencing.m_gt, dfX2, dfY2);
1✔
1315

1316
        if (poDS->GetGeoTransform(rasterGT) != CE_None || rasterGT[2] != 0 ||
2✔
1317
            rasterGT[4] != 0 || rasterGT[5] > 0)
2✔
1318
        {
1319
            CPLError(CE_Failure, CPLE_AppDefined,
×
1320
                     "Raster has no geotransform or a rotated geotransform");
1321
            return false;
×
1322
        }
1323

1324
        auto poSRS = poDS->GetSpatialRef();
1✔
1325
        if (!poSRS || !poSRS->IsSame(&georeferencing.m_oSRS))
1✔
1326
        {
1327
            CPLError(CE_Failure, CPLE_AppDefined,
×
1328
                     "Raster has no projection, or different from the one "
1329
                     "of the georeferencing area");
1330
            return false;
×
1331
        }
1332

1333
        CPL_IGNORE_RET_VAL(georeferencing.m_gt.GetInverse(invGT));
1✔
1334
    }
1335
    const double dfRasterMinX = rasterGT[0];
4✔
1336
    const double dfRasterMaxY = rasterGT[3];
4✔
1337

1338
    /* Does the source image has a color table ? */
1339
    const auto nColorTableId = WriteColorTable(poDS.get());
4✔
1340

1341
    double dfIgnoredOpacity;
1342
    StartBlending(psNode, oPageContext, dfIgnoredOpacity);
4✔
1343

1344
    CPLString osGroupStream;
8✔
1345
    std::vector<GDALPDFObjectNum> anImageIds;
8✔
1346

1347
    const int nXBlocks = DIV_ROUND_UP(nWidth, nBlockXSize);
4✔
1348
    const int nYBlocks = DIV_ROUND_UP(nHeight, nBlockYSize);
4✔
1349
    int nBlockXOff, nBlockYOff;
1350
    for (nBlockYOff = 0; nBlockYOff < nYBlocks; nBlockYOff++)
10✔
1351
    {
1352
        for (nBlockXOff = 0; nBlockXOff < nXBlocks; nBlockXOff++)
16✔
1353
        {
1354
            int nReqWidth =
1355
                std::min(nBlockXSize, nWidth - nBlockXOff * nBlockXSize);
10✔
1356
            int nReqHeight =
1357
                std::min(nBlockYSize, nHeight - nBlockYOff * nBlockYSize);
10✔
1358

1359
            int nX = nBlockXOff * nBlockXSize;
10✔
1360
            int nY = nBlockYOff * nBlockYSize;
10✔
1361

1362
            double dfXPDFOff = nX * (dfX2 - dfX1) / nWidth + dfX1;
10✔
1363
            double dfYPDFOff =
10✔
1364
                (nHeight - nY - nReqHeight) * (dfY2 - dfY1) / nHeight + dfY1;
10✔
1365
            double dfXPDFSize = nReqWidth * (dfX2 - dfX1) / nWidth;
10✔
1366
            double dfYPDFSize = nReqHeight * (dfY2 - dfY1) / nHeight;
10✔
1367

1368
            if (bClip)
10✔
1369
            {
1370
                /* Compute extent of block to write */
1371
                double dfBlockMinX = rasterGT[0] + nX * rasterGT[1];
1✔
1372
                double dfBlockMaxX =
1373
                    rasterGT[0] + (nX + nReqWidth) * rasterGT[1];
1✔
1374
                double dfBlockMinY =
1375
                    rasterGT[3] + (nY + nReqHeight) * rasterGT[5];
1✔
1376
                double dfBlockMaxY = rasterGT[3] + nY * rasterGT[5];
1✔
1377

1378
                // Clip the extent of the block with the extent of the main
1379
                // raster.
1380
                const double dfIntersectMinX =
1381
                    std::max(dfBlockMinX, dfClippingMinX);
1✔
1382
                const double dfIntersectMinY =
1383
                    std::max(dfBlockMinY, dfClippingMinY);
1✔
1384
                const double dfIntersectMaxX =
1385
                    std::min(dfBlockMaxX, dfClippingMaxX);
1✔
1386
                const double dfIntersectMaxY =
1387
                    std::min(dfBlockMaxY, dfClippingMaxY);
1✔
1388

1389
                bool bOK = false;
1✔
1390
                if (dfIntersectMinX < dfIntersectMaxX &&
1✔
1391
                    dfIntersectMinY < dfIntersectMaxY)
1392
                {
1393
                    /* Re-compute (x,y,width,height) subwindow of current raster
1394
                     * from */
1395
                    /* the extent of the clipped block */
1396
                    nX = static_cast<int>(
1✔
1397
                        (dfIntersectMinX - dfRasterMinX) / rasterGT[1] + 0.5);
1✔
1398
                    nY = static_cast<int>((dfRasterMaxY - dfIntersectMaxY) /
2✔
1399
                                              (-rasterGT[5]) +
1✔
1400
                                          0.5);
1401
                    nReqWidth =
1✔
1402
                        static_cast<int>((dfIntersectMaxX - dfRasterMinX) /
2✔
1403
                                             rasterGT[1] +
1✔
1404
                                         0.5) -
1405
                        nX;
1406
                    nReqHeight =
1✔
1407
                        static_cast<int>((dfRasterMaxY - dfIntersectMinY) /
2✔
1408
                                             (-rasterGT[5]) +
1✔
1409
                                         0.5) -
1410
                        nY;
1411

1412
                    if (nReqWidth > 0 && nReqHeight > 0)
1✔
1413
                    {
1414
                        dfBlockMinX = rasterGT[0] + nX * rasterGT[1];
1✔
1415
                        dfBlockMaxX =
1✔
1416
                            rasterGT[0] + (nX + nReqWidth) * rasterGT[1];
1✔
1417
                        dfBlockMinY =
1✔
1418
                            rasterGT[3] + (nY + nReqHeight) * rasterGT[5];
1✔
1419
                        dfBlockMaxY = rasterGT[3] + nY * rasterGT[5];
1✔
1420

1421
                        double dfPDFX1 =
1422
                            APPLY_GT_X(invGT, dfBlockMinX, dfBlockMinY);
1✔
1423
                        double dfPDFY1 =
1424
                            APPLY_GT_Y(invGT, dfBlockMinX, dfBlockMinY);
1✔
1425
                        double dfPDFX2 =
1426
                            APPLY_GT_X(invGT, dfBlockMaxX, dfBlockMaxY);
1✔
1427
                        double dfPDFY2 =
1428
                            APPLY_GT_Y(invGT, dfBlockMaxX, dfBlockMaxY);
1✔
1429

1430
                        dfXPDFOff = dfPDFX1;
1✔
1431
                        dfYPDFOff = dfPDFY1;
1✔
1432
                        dfXPDFSize = dfPDFX2 - dfPDFX1;
1✔
1433
                        dfYPDFSize = dfPDFY2 - dfPDFY1;
1✔
1434
                        bOK = true;
1✔
1435
                    }
1436
                }
1437
                if (!bOK)
1✔
1438
                {
1439
                    continue;
×
1440
                }
1441
            }
1442

1443
            const auto nImageId =
1444
                WriteBlock(poDS.get(), nX, nY, nReqWidth, nReqHeight,
1445
                           nColorTableId, eCompressMethod, nPredictor,
1446
                           nJPEGQuality, pszJPEG2000_DRIVER, nullptr, nullptr);
10✔
1447

1448
            if (!nImageId.toBool())
10✔
1449
                return false;
×
1450

1451
            anImageIds.push_back(nImageId);
10✔
1452
            osGroupStream += "q\n";
10✔
1453
            GDALPDFObjectRW *poXSize = GDALPDFObjectRW::CreateReal(dfXPDFSize);
10✔
1454
            GDALPDFObjectRW *poYSize = GDALPDFObjectRW::CreateReal(dfYPDFSize);
10✔
1455
            GDALPDFObjectRW *poXOff = GDALPDFObjectRW::CreateReal(dfXPDFOff);
10✔
1456
            GDALPDFObjectRW *poYOff = GDALPDFObjectRW::CreateReal(dfYPDFOff);
10✔
1457
            osGroupStream += CPLOPrintf(
50✔
1458
                "%s 0 0 %s %s %s cm\n", poXSize->Serialize().c_str(),
20✔
1459
                poYSize->Serialize().c_str(), poXOff->Serialize().c_str(),
30✔
1460
                poYOff->Serialize().c_str());
30✔
1461
            delete poXSize;
10✔
1462
            delete poYSize;
10✔
1463
            delete poXOff;
10✔
1464
            delete poYOff;
10✔
1465
            osGroupStream += CPLOPrintf("/Image%d Do\n", nImageId.toInt());
10✔
1466
            osGroupStream += "Q\n";
10✔
1467
        }
1468
    }
1469

1470
    if (anImageIds.size() <= 1 || CPLGetXMLNode(psNode, "Blending") == nullptr)
4✔
1471
    {
1472
        for (const auto &nImageId : anImageIds)
4✔
1473
        {
1474
            oPageContext.m_oXObjects[CPLOPrintf("Image%d", nImageId.toInt())] =
2✔
1475
                nImageId;
1476
        }
1477
        oPageContext.m_osDrawingStream += osGroupStream;
2✔
1478
    }
1479
    else
1480
    {
1481
        // In case several tiles are drawn with blending, use a transparency
1482
        // group to avoid edge effects.
1483

1484
        auto nGroupId = AllocNewObject();
2✔
1485
        GDALPDFDictionaryRW oDictGroup;
2✔
1486
        GDALPDFDictionaryRW *poGroup = new GDALPDFDictionaryRW();
2✔
1487
        poGroup->Add("Type", GDALPDFObjectRW::CreateName("Group"))
2✔
1488
            .Add("S", GDALPDFObjectRW::CreateName("Transparency"));
2✔
1489

1490
        GDALPDFDictionaryRW *poXObjects = new GDALPDFDictionaryRW();
2✔
1491
        for (const auto &nImageId : anImageIds)
10✔
1492
        {
1493
            poXObjects->Add(CPLOPrintf("Image%d", nImageId.toInt()), nImageId,
16✔
1494
                            0);
8✔
1495
        }
1496
        GDALPDFDictionaryRW *poResources = new GDALPDFDictionaryRW();
2✔
1497
        poResources->Add("XObject", poXObjects);
2✔
1498

1499
        oDictGroup.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
2✔
1500
            .Add("BBox", &((new GDALPDFArrayRW())->Add(0).Add(0))
2✔
1501
                              .Add(oPageContext.m_dfWidthInUserUnit)
2✔
1502
                              .Add(oPageContext.m_dfHeightInUserUnit))
2✔
1503
            .Add("Subtype", GDALPDFObjectRW::CreateName("Form"))
2✔
1504
            .Add("Group", poGroup)
2✔
1505
            .Add("Resources", poResources);
2✔
1506

1507
        StartObjWithStream(nGroupId, oDictGroup,
2✔
1508
                           oPageContext.m_eStreamCompressMethod !=
2✔
1509
                               COMPRESS_NONE);
1510
        VSIFPrintfL(m_fp, "%s", osGroupStream.c_str());
2✔
1511
        EndObjWithStream();
2✔
1512

1513
        oPageContext.m_oXObjects[CPLOPrintf("Group%d", nGroupId.toInt())] =
2✔
1514
            nGroupId;
1515
        oPageContext.m_osDrawingStream +=
1516
            CPLOPrintf("/Group%d Do\n", nGroupId.toInt());
2✔
1517
    }
1518

1519
    EndBlending(psNode, oPageContext);
4✔
1520

1521
    return true;
4✔
1522
}
1523

1524
/************************************************************************/
1525
/*                     SetupVectorGeoreferencing()                      */
1526
/************************************************************************/
1527

1528
bool GDALPDFComposerWriter::SetupVectorGeoreferencing(
4✔
1529
    const char *pszGeoreferencingId, OGRLayer *poLayer,
1530
    const PageContext &oPageContext, double &dfClippingMinX,
1531
    double &dfClippingMinY, double &dfClippingMaxX, double &dfClippingMaxY,
1532
    double adfMatrix[4], std::unique_ptr<OGRCoordinateTransformation> &poCT)
1533
{
1534
    CPLAssert(pszGeoreferencingId);
4✔
1535

1536
    auto iter = oPageContext.m_oMapGeoreferencedId.find(pszGeoreferencingId);
4✔
1537
    if (iter == oPageContext.m_oMapGeoreferencedId.end())
4✔
1538
    {
1539
        CPLError(CE_Failure, CPLE_AppDefined,
×
1540
                 "Cannot find georeferencing of id %s", pszGeoreferencingId);
1541
        return false;
×
1542
    }
1543
    const auto &georeferencing = iter->second;
4✔
1544
    const double dfX1 = georeferencing.m_bboxX1;
4✔
1545
    const double dfY1 = georeferencing.m_bboxY1;
4✔
1546
    const double dfX2 = georeferencing.m_bboxX2;
4✔
1547
    const double dfY2 = georeferencing.m_bboxY2;
4✔
1548

1549
    dfClippingMinX = APPLY_GT_X(georeferencing.m_gt, dfX1, dfY1);
4✔
1550
    dfClippingMinY = APPLY_GT_Y(georeferencing.m_gt, dfX1, dfY1);
4✔
1551
    dfClippingMaxX = APPLY_GT_X(georeferencing.m_gt, dfX2, dfY2);
4✔
1552
    dfClippingMaxY = APPLY_GT_Y(georeferencing.m_gt, dfX2, dfY2);
4✔
1553

1554
    auto poSRS = poLayer->GetSpatialRef();
4✔
1555
    if (!poSRS)
4✔
1556
    {
1557
        CPLError(CE_Failure, CPLE_AppDefined, "Layer has no SRS");
×
1558
        return false;
×
1559
    }
1560
    if (!poSRS->IsSame(&georeferencing.m_oSRS))
4✔
1561
    {
1562
        poCT.reset(
2✔
1563
            OGRCreateCoordinateTransformation(poSRS, &georeferencing.m_oSRS));
1564
    }
1565

1566
    if (!poCT)
4✔
1567
    {
1568
        poLayer->SetSpatialFilterRect(dfClippingMinX, dfClippingMinY,
2✔
1569
                                      dfClippingMaxX, dfClippingMaxY);
1570
    }
1571

1572
    GDALGeoTransform invGT;  // from georeferenced to PDF coordinates
4✔
1573
    CPL_IGNORE_RET_VAL(georeferencing.m_gt.GetInverse(invGT));
4✔
1574
    adfMatrix[0] = invGT[0];
4✔
1575
    adfMatrix[1] = invGT[1];
4✔
1576
    adfMatrix[2] = invGT[3];
4✔
1577
    adfMatrix[3] = invGT[5];
4✔
1578

1579
    return true;
4✔
1580
}
1581

1582
/************************************************************************/
1583
/*                           WriteVector()                              */
1584
/************************************************************************/
1585

1586
bool GDALPDFComposerWriter::WriteVector(const CPLXMLNode *psNode,
5✔
1587
                                        PageContext &oPageContext)
1588
{
1589
    const char *pszDataset = CPLGetXMLValue(psNode, "dataset", nullptr);
5✔
1590
    if (!pszDataset)
5✔
1591
    {
1592
        CPLError(CE_Failure, CPLE_AppDefined, "Missing dataset");
×
1593
        return false;
×
1594
    }
1595
    const char *pszLayer = CPLGetXMLValue(psNode, "layer", nullptr);
5✔
1596
    if (!pszLayer)
5✔
1597
    {
1598
        CPLError(CE_Failure, CPLE_AppDefined, "Missing layer");
×
1599
        return false;
×
1600
    }
1601

1602
    GDALDatasetUniquePtr poDS(
1603
        GDALDataset::Open(pszDataset, GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR,
1604
                          nullptr, nullptr, nullptr));
10✔
1605
    if (!poDS)
5✔
1606
        return false;
×
1607
    OGRLayer *poLayer = poDS->GetLayerByName(pszLayer);
5✔
1608
    if (!poLayer)
5✔
1609
    {
1610
        CPLError(CE_Failure, CPLE_AppDefined, "Cannt find layer %s", pszLayer);
×
1611
        return false;
×
1612
    }
1613
    const bool bVisible =
1614
        CPLTestBool(CPLGetXMLValue(psNode, "visible", "true"));
5✔
1615

1616
    const auto psLogicalStructure = CPLGetXMLNode(psNode, "LogicalStructure");
5✔
1617
    const char *pszOGRDisplayField = nullptr;
5✔
1618
    std::vector<CPLString> aosIncludedFields;
10✔
1619
    const bool bLogicalStructure = psLogicalStructure != nullptr;
5✔
1620
    if (psLogicalStructure)
5✔
1621
    {
1622
        pszOGRDisplayField =
1623
            CPLGetXMLValue(psLogicalStructure, "fieldToDisplay", nullptr);
5✔
1624
        if (CPLGetXMLNode(psLogicalStructure, "ExcludeAllFields") != nullptr ||
9✔
1625
            CPLGetXMLNode(psLogicalStructure, "IncludeField") != nullptr)
4✔
1626
        {
1627
            for (const auto *psIter = psLogicalStructure->psChild; psIter;
7✔
1628
                 psIter = psIter->psNext)
5✔
1629
            {
1630
                if (psIter->eType == CXT_Element &&
5✔
1631
                    strcmp(psIter->pszValue, "IncludeField") == 0)
3✔
1632
                {
1633
                    aosIncludedFields.push_back(
2✔
1634
                        CPLGetXMLValue(psIter, nullptr, ""));
1635
                }
1636
            }
1637
        }
1638
        else
1639
        {
1640
            std::set<CPLString> oSetExcludedFields;
6✔
1641
            for (const auto *psIter = psLogicalStructure->psChild; psIter;
5✔
1642
                 psIter = psIter->psNext)
2✔
1643
            {
1644
                if (psIter->eType == CXT_Element &&
2✔
1645
                    strcmp(psIter->pszValue, "ExcludeField") == 0)
2✔
1646
                {
1647
                    oSetExcludedFields.insert(
1648
                        CPLGetXMLValue(psIter, nullptr, ""));
2✔
1649
                }
1650
            }
1651
            const auto poLayerDefn = poLayer->GetLayerDefn();
3✔
1652
            for (int i = 0; i < poLayerDefn->GetFieldCount(); i++)
8✔
1653
            {
1654
                const auto poFieldDefn = poLayerDefn->GetFieldDefn(i);
5✔
1655
                const char *pszName = poFieldDefn->GetNameRef();
5✔
1656
                if (oSetExcludedFields.find(pszName) ==
5✔
1657
                    oSetExcludedFields.end())
10✔
1658
                {
1659
                    aosIncludedFields.push_back(pszName);
3✔
1660
                }
1661
            }
1662
        }
1663
    }
1664
    const char *pszStyleString =
1665
        CPLGetXMLValue(psNode, "ogrStyleString", nullptr);
5✔
1666
    const char *pszOGRLinkField =
1667
        CPLGetXMLValue(psNode, "linkAttribute", nullptr);
5✔
1668

1669
    const char *pszGeoreferencingId =
1670
        CPLGetXMLValue(psNode, "georeferencingId", nullptr);
5✔
1671
    std::unique_ptr<OGRCoordinateTransformation> poCT;
5✔
1672
    double dfClippingMinX = 0;
5✔
1673
    double dfClippingMinY = 0;
5✔
1674
    double dfClippingMaxX = 0;
5✔
1675
    double dfClippingMaxY = 0;
5✔
1676
    double adfMatrix[4] = {0, 1, 0, 1};
5✔
1677
    if (pszGeoreferencingId &&
7✔
1678
        !SetupVectorGeoreferencing(
2✔
1679
            pszGeoreferencingId, poLayer, oPageContext, dfClippingMinX,
1680
            dfClippingMinY, dfClippingMaxX, dfClippingMaxY, adfMatrix, poCT))
1681
    {
1682
        return false;
×
1683
    }
1684

1685
    double dfOpacityFactor = 1.0;
5✔
1686
    if (!bVisible)
5✔
1687
    {
1688
        if (oPageContext.m_oExtGState.find("GSinvisible") ==
2✔
1689
            oPageContext.m_oExtGState.end())
4✔
1690
        {
1691
            auto nExtGState = AllocNewObject();
1✔
1692
            StartObj(nExtGState);
1✔
1693
            {
1694
                GDALPDFDictionaryRW gs;
1✔
1695
                gs.Add("Type", GDALPDFObjectRW::CreateName("ExtGState"));
1✔
1696
                gs.Add("ca", 0);
1✔
1697
                gs.Add("CA", 0);
1✔
1698
                VSIFPrintfL(m_fp, "%s\n", gs.Serialize().c_str());
1✔
1699
            }
1700
            EndObj();
1✔
1701
            oPageContext.m_oExtGState["GSinvisible"] = nExtGState;
1✔
1702
        }
1703
        oPageContext.m_osDrawingStream += "q\n";
2✔
1704
        oPageContext.m_osDrawingStream += "/GSinvisible gs\n";
2✔
1705
        oPageContext.m_osDrawingStream += "0 w\n";
2✔
1706
        dfOpacityFactor = 0;
2✔
1707
    }
1708
    else
1709
    {
1710
        StartBlending(psNode, oPageContext, dfOpacityFactor);
3✔
1711
    }
1712

1713
    if (!m_nStructTreeRootId.toBool())
5✔
1714
        m_nStructTreeRootId = AllocNewObject();
3✔
1715

1716
    GDALPDFObjectNum nFeatureLayerId;
5✔
1717
    if (bLogicalStructure)
5✔
1718
    {
1719
        nFeatureLayerId = AllocNewObject();
5✔
1720
        m_anFeatureLayerId.push_back(nFeatureLayerId);
5✔
1721
    }
1722

1723
    std::vector<GDALPDFObjectNum> anFeatureUserProperties;
5✔
1724
    for (auto &&poFeature : poLayer)
14✔
1725
    {
1726
        auto hFeat = OGRFeature::ToHandle(poFeature.get());
9✔
1727
        auto hGeom = OGR_F_GetGeometryRef(hFeat);
9✔
1728
        if (!hGeom || OGR_G_IsEmpty(hGeom))
9✔
1729
            continue;
1✔
1730
        if (poCT)
9✔
1731
        {
1732
            if (OGRGeometry::FromHandle(hGeom)->transform(poCT.get()) !=
2✔
1733
                OGRERR_NONE)
1734
                continue;
1✔
1735

1736
            OGREnvelope sEnvelope;
2✔
1737
            OGR_G_GetEnvelope(hGeom, &sEnvelope);
2✔
1738
            if (sEnvelope.MinX > dfClippingMaxX ||
2✔
1739
                sEnvelope.MaxX < dfClippingMinX ||
2✔
1740
                sEnvelope.MinY > dfClippingMaxY ||
1✔
1741
                sEnvelope.MaxY < dfClippingMinY)
1✔
1742
            {
1743
                continue;
1✔
1744
            }
1745
        }
1746

1747
        if (bLogicalStructure)
8✔
1748
        {
1749
            CPLString osOutFeatureName;
8✔
1750
            anFeatureUserProperties.push_back(
8✔
1751
                WriteAttributes(hFeat, aosIncludedFields, pszOGRDisplayField,
8✔
1752
                                oPageContext.m_nMCID, nFeatureLayerId,
1753
                                m_asPageId.back(), osOutFeatureName));
8✔
1754
        }
1755

1756
        ObjectStyle os;
16✔
1757
        GetObjectStyle(pszStyleString, hFeat, adfMatrix,
8✔
1758
                       m_oMapSymbolFilenameToDesc, os);
8✔
1759
        os.nPenA = static_cast<int>(std::round(os.nPenA * dfOpacityFactor));
8✔
1760
        os.nBrushA = static_cast<int>(std::round(os.nBrushA * dfOpacityFactor));
8✔
1761

1762
        const double dfRadius = os.dfSymbolSize;
8✔
1763

1764
        if (os.nImageSymbolId.toBool())
8✔
1765
        {
1766
            oPageContext.m_oXObjects[CPLOPrintf(
2✔
1767
                "SymImage%d", os.nImageSymbolId.toInt())] = os.nImageSymbolId;
2✔
1768
        }
1769

1770
        if (pszOGRLinkField)
8✔
1771
        {
1772
            OGREnvelope sEnvelope;
1✔
1773
            OGR_G_GetEnvelope(hGeom, &sEnvelope);
1✔
1774
            int bboxXMin, bboxYMin, bboxXMax, bboxYMax;
1775
            ComputeIntBBox(hGeom, sEnvelope, adfMatrix, os, dfRadius, bboxXMin,
1✔
1776
                           bboxYMin, bboxXMax, bboxYMax);
1777

1778
            auto nLinkId = WriteLink(hFeat, pszOGRLinkField, adfMatrix,
1779
                                     bboxXMin, bboxYMin, bboxXMax, bboxYMax);
1✔
1780
            if (nLinkId.toBool())
1✔
1781
                oPageContext.m_anAnnotationsId.push_back(nLinkId);
1✔
1782
        }
1783

1784
        if (bLogicalStructure)
8✔
1785
        {
1786
            oPageContext.m_osDrawingStream +=
1787
                CPLOPrintf("/feature <</MCID %d>> BDC\n", oPageContext.m_nMCID);
8✔
1788
        }
1789

1790
        if (bVisible || bLogicalStructure)
8✔
1791
        {
1792
            oPageContext.m_osDrawingStream += "q\n";
8✔
1793
            if (bVisible && (os.nPenA != 255 || os.nBrushA != 255))
8✔
1794
            {
1795
                CPLString osGSName;
2✔
1796
                osGSName.Printf("GS_CA_%d_ca_%d", os.nPenA, os.nBrushA);
2✔
1797
                if (oPageContext.m_oExtGState.find(osGSName) ==
2✔
1798
                    oPageContext.m_oExtGState.end())
4✔
1799
                {
1800
                    auto nExtGState = AllocNewObject();
2✔
1801
                    StartObj(nExtGState);
2✔
1802
                    {
1803
                        GDALPDFDictionaryRW gs;
2✔
1804
                        gs.Add("Type",
1805
                               GDALPDFObjectRW::CreateName("ExtGState"));
2✔
1806
                        if (os.nPenA != 255)
2✔
1807
                            gs.Add("CA", (os.nPenA == 127 || os.nPenA == 128)
1✔
1808
                                             ? 0.5
1809
                                             : os.nPenA / 255.0);
2✔
1810
                        if (os.nBrushA != 255)
2✔
1811
                            gs.Add("ca",
1812
                                   (os.nBrushA == 127 || os.nBrushA == 128)
1✔
1813
                                       ? 0.5
1814
                                       : os.nBrushA / 255.0);
3✔
1815
                        VSIFPrintfL(m_fp, "%s\n", gs.Serialize().c_str());
2✔
1816
                    }
1817
                    EndObj();
2✔
1818
                    oPageContext.m_oExtGState[osGSName] = nExtGState;
2✔
1819
                }
1820
                oPageContext.m_osDrawingStream += "/" + osGSName + " gs\n";
2✔
1821
            }
1822

1823
            oPageContext.m_osDrawingStream +=
1824
                GenerateDrawingStream(hGeom, adfMatrix, os, dfRadius);
8✔
1825

1826
            oPageContext.m_osDrawingStream += "Q\n";
8✔
1827
        }
1828

1829
        if (bLogicalStructure)
8✔
1830
        {
1831
            oPageContext.m_osDrawingStream += "EMC\n";
8✔
1832
            oPageContext.m_nMCID++;
8✔
1833
        }
1834
    }
1835

1836
    if (bLogicalStructure)
5✔
1837
    {
1838
        for (const auto &num : anFeatureUserProperties)
13✔
1839
        {
1840
            oPageContext.m_anFeatureUserProperties.push_back(num);
8✔
1841
        }
1842

1843
        {
1844
            StartObj(nFeatureLayerId);
5✔
1845

1846
            GDALPDFDictionaryRW oDict;
10✔
1847
            GDALPDFDictionaryRW *poDictA = new GDALPDFDictionaryRW();
5✔
1848
            oDict.Add("A", poDictA);
5✔
1849
            poDictA->Add("O", GDALPDFObjectRW::CreateName("UserProperties"));
5✔
1850
            GDALPDFArrayRW *poArrayK = new GDALPDFArrayRW();
5✔
1851
            for (const auto &num : anFeatureUserProperties)
13✔
1852
                poArrayK->Add(num, 0);
8✔
1853
            oDict.Add("K", poArrayK);
5✔
1854
            oDict.Add("P", m_nStructTreeRootId, 0);
5✔
1855
            oDict.Add("S", GDALPDFObjectRW::CreateName("Layer"));
5✔
1856

1857
            const char *pszOGRDisplayName = CPLGetXMLValue(
5✔
1858
                psLogicalStructure, "displayLayerName", poLayer->GetName());
5✔
1859
            oDict.Add("T", pszOGRDisplayName);
5✔
1860

1861
            VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
5✔
1862

1863
            EndObj();
5✔
1864
        }
1865
    }
1866

1867
    if (!bVisible)
5✔
1868
    {
1869
        oPageContext.m_osDrawingStream += "Q\n";
2✔
1870
    }
1871
    else
1872
    {
1873
        EndBlending(psNode, oPageContext);
3✔
1874
    }
1875

1876
    return true;
5✔
1877
}
1878

1879
/************************************************************************/
1880
/*                         WriteVectorLabel()                           */
1881
/************************************************************************/
1882

1883
bool GDALPDFComposerWriter::WriteVectorLabel(const CPLXMLNode *psNode,
3✔
1884
                                             PageContext &oPageContext)
1885
{
1886
    const char *pszDataset = CPLGetXMLValue(psNode, "dataset", nullptr);
3✔
1887
    if (!pszDataset)
3✔
1888
    {
1889
        CPLError(CE_Failure, CPLE_AppDefined, "Missing dataset");
×
1890
        return false;
×
1891
    }
1892
    const char *pszLayer = CPLGetXMLValue(psNode, "layer", nullptr);
3✔
1893
    if (!pszLayer)
3✔
1894
    {
1895
        CPLError(CE_Failure, CPLE_AppDefined, "Missing layer");
×
1896
        return false;
×
1897
    }
1898

1899
    GDALDatasetUniquePtr poDS(
1900
        GDALDataset::Open(pszDataset, GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR,
1901
                          nullptr, nullptr, nullptr));
6✔
1902
    if (!poDS)
3✔
1903
        return false;
×
1904
    OGRLayer *poLayer = poDS->GetLayerByName(pszLayer);
3✔
1905
    if (!poLayer)
3✔
1906
    {
1907
        CPLError(CE_Failure, CPLE_AppDefined, "Cannt find layer %s", pszLayer);
×
1908
        return false;
×
1909
    }
1910

1911
    const char *pszStyleString =
1912
        CPLGetXMLValue(psNode, "ogrStyleString", nullptr);
3✔
1913

1914
    double dfOpacityFactor = 1.0;
3✔
1915
    StartBlending(psNode, oPageContext, dfOpacityFactor);
3✔
1916

1917
    const char *pszGeoreferencingId =
1918
        CPLGetXMLValue(psNode, "georeferencingId", nullptr);
3✔
1919
    std::unique_ptr<OGRCoordinateTransformation> poCT;
3✔
1920
    double dfClippingMinX = 0;
3✔
1921
    double dfClippingMinY = 0;
3✔
1922
    double dfClippingMaxX = 0;
3✔
1923
    double dfClippingMaxY = 0;
3✔
1924
    double adfMatrix[4] = {0, 1, 0, 1};
3✔
1925
    if (pszGeoreferencingId &&
5✔
1926
        !SetupVectorGeoreferencing(
2✔
1927
            pszGeoreferencingId, poLayer, oPageContext, dfClippingMinX,
1928
            dfClippingMinY, dfClippingMaxX, dfClippingMaxY, adfMatrix, poCT))
1929
    {
1930
        return false;
×
1931
    }
1932

1933
    for (auto &&poFeature : poLayer)
7✔
1934
    {
1935
        auto hFeat = OGRFeature::ToHandle(poFeature.get());
4✔
1936
        auto hGeom = OGR_F_GetGeometryRef(hFeat);
4✔
1937
        if (!hGeom || OGR_G_IsEmpty(hGeom))
4✔
1938
            continue;
1✔
1939
        if (poCT)
4✔
1940
        {
1941
            if (OGRGeometry::FromHandle(hGeom)->transform(poCT.get()) !=
2✔
1942
                OGRERR_NONE)
1943
                continue;
1✔
1944

1945
            OGREnvelope sEnvelope;
2✔
1946
            OGR_G_GetEnvelope(hGeom, &sEnvelope);
2✔
1947
            if (sEnvelope.MinX > dfClippingMaxX ||
2✔
1948
                sEnvelope.MaxX < dfClippingMinX ||
2✔
1949
                sEnvelope.MinY > dfClippingMaxY ||
1✔
1950
                sEnvelope.MaxY < dfClippingMinY)
1✔
1951
            {
1952
                continue;
1✔
1953
            }
1954
        }
1955

1956
        ObjectStyle os;
6✔
1957
        GetObjectStyle(pszStyleString, hFeat, adfMatrix,
3✔
1958
                       m_oMapSymbolFilenameToDesc, os);
3✔
1959
        os.nPenA = static_cast<int>(std::round(os.nPenA * dfOpacityFactor));
3✔
1960
        os.nBrushA = static_cast<int>(std::round(os.nBrushA * dfOpacityFactor));
3✔
1961

1962
        if (!os.osLabelText.empty() &&
6✔
1963
            wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint)
3✔
1964
        {
1965
            auto nObjectId = WriteLabel(hGeom, adfMatrix, os,
1966
                                        oPageContext.m_eStreamCompressMethod, 0,
1967
                                        0, oPageContext.m_dfWidthInUserUnit,
1968
                                        oPageContext.m_dfHeightInUserUnit);
3✔
1969
            oPageContext.m_osDrawingStream +=
1970
                CPLOPrintf("/Label%d Do\n", nObjectId.toInt());
3✔
1971
            oPageContext.m_oXObjects[CPLOPrintf("Label%d", nObjectId.toInt())] =
3✔
1972
                nObjectId;
1973
        }
1974
    }
1975

1976
    EndBlending(psNode, oPageContext);
3✔
1977

1978
    return true;
3✔
1979
}
1980

1981
#ifdef HAVE_PDF_READ_SUPPORT
1982

1983
/************************************************************************/
1984
/*                            EmitNewObject()                           */
1985
/************************************************************************/
1986

1987
GDALPDFObjectNum
1988
GDALPDFComposerWriter::EmitNewObject(GDALPDFObject *poObj,
10✔
1989
                                     RemapType &oRemapObjectRefs)
1990
{
1991
    auto nId = AllocNewObject();
10✔
1992
    const auto nRefNum = poObj->GetRefNum();
10✔
1993
    if (nRefNum.toBool())
10✔
1994
    {
1995
        int nRefGen = poObj->GetRefGen();
10✔
1996
        std::pair<int, int> oKey(nRefNum.toInt(), nRefGen);
10✔
1997
        oRemapObjectRefs[oKey] = nId;
10✔
1998
    }
1999
    CPLString osStr;
20✔
2000
    if (!SerializeAndRenumberIgnoreRef(osStr, poObj, oRemapObjectRefs))
10✔
2001
        return GDALPDFObjectNum();
×
2002
    StartObj(nId);
10✔
2003
    VSIFWriteL(osStr.data(), 1, osStr.size(), m_fp);
10✔
2004
    VSIFPrintfL(m_fp, "\n");
10✔
2005
    EndObj();
10✔
2006
    return nId;
10✔
2007
}
2008

2009
/************************************************************************/
2010
/*                         SerializeAndRenumber()                       */
2011
/************************************************************************/
2012

2013
bool GDALPDFComposerWriter::SerializeAndRenumber(CPLString &osStr,
40✔
2014
                                                 GDALPDFObject *poObj,
2015
                                                 RemapType &oRemapObjectRefs)
2016
{
2017
    auto nRefNum = poObj->GetRefNum();
40✔
2018
    if (nRefNum.toBool())
40✔
2019
    {
2020
        int nRefGen = poObj->GetRefGen();
6✔
2021

2022
        std::pair<int, int> oKey(nRefNum.toInt(), nRefGen);
6✔
2023
        auto oIter = oRemapObjectRefs.find(oKey);
6✔
2024
        if (oIter != oRemapObjectRefs.end())
6✔
2025
        {
2026
            osStr.append(CPLSPrintf("%d 0 R", oIter->second.toInt()));
×
2027
            return true;
×
2028
        }
2029
        else
2030
        {
2031
            auto nId = EmitNewObject(poObj, oRemapObjectRefs);
6✔
2032
            osStr.append(CPLSPrintf("%d 0 R", nId.toInt()));
6✔
2033
            return nId.toBool();
6✔
2034
        }
2035
    }
2036
    else
2037
    {
2038
        return SerializeAndRenumberIgnoreRef(osStr, poObj, oRemapObjectRefs);
34✔
2039
    }
2040
}
2041

2042
/************************************************************************/
2043
/*                    SerializeAndRenumberIgnoreRef()                   */
2044
/************************************************************************/
2045

2046
bool GDALPDFComposerWriter::SerializeAndRenumberIgnoreRef(
44✔
2047
    CPLString &osStr, GDALPDFObject *poObj, RemapType &oRemapObjectRefs)
2048
{
2049
    switch (poObj->GetType())
44✔
2050
    {
2051
        case PDFObjectType_Array:
×
2052
        {
2053
            auto poArray = poObj->GetArray();
×
2054
            int nLength = poArray->GetLength();
×
2055
            osStr.append("[ ");
×
2056
            for (int i = 0; i < nLength; i++)
×
2057
            {
2058
                if (!SerializeAndRenumber(osStr, poArray->Get(i),
×
2059
                                          oRemapObjectRefs))
2060
                    return false;
×
2061
                osStr.append(" ");
×
2062
            }
2063
            osStr.append("]");
×
2064
            break;
×
2065
        }
2066
        case PDFObjectType_Dictionary:
12✔
2067
        {
2068
            osStr.append("<< ");
12✔
2069
            auto poDict = poObj->GetDictionary();
12✔
2070
            auto &oMap = poDict->GetValues();
12✔
2071
            for (const auto &oIter : oMap)
52✔
2072
            {
2073
                const char *pszKey = oIter.first.c_str();
40✔
2074
                GDALPDFObject *poSubObj = oIter.second;
40✔
2075
                osStr.append("/");
40✔
2076
                osStr.append(pszKey);
40✔
2077
                osStr.append(" ");
40✔
2078
                if (!SerializeAndRenumber(osStr, poSubObj, oRemapObjectRefs))
40✔
2079
                    return false;
×
2080
                osStr.append(" ");
40✔
2081
            }
2082
            osStr.append(">>");
12✔
2083
            auto poStream = poObj->GetStream();
12✔
2084
            if (poStream)
12✔
2085
            {
2086
                // CPLAssert( poObj->GetRefNum().toBool() ); // should be a top
2087
                // level object
2088
                osStr.append("\nstream\n");
4✔
2089
                auto pRawBytes = poStream->GetRawBytes();
4✔
2090
                if (!pRawBytes)
4✔
2091
                {
2092
                    CPLError(CE_Failure, CPLE_AppDefined,
×
2093
                             "Cannot get stream content");
2094
                    return false;
×
2095
                }
2096
                osStr.append(pRawBytes,
2097
                             static_cast<size_t>(poStream->GetRawLength()));
4✔
2098
                VSIFree(pRawBytes);
4✔
2099
                osStr.append("\nendstream\n");
4✔
2100
            }
2101
            break;
12✔
2102
        }
2103
        case PDFObjectType_Unknown:
×
2104
        {
2105
            CPLError(CE_Failure, CPLE_AppDefined, "Corrupted PDF");
×
2106
            return false;
×
2107
        }
2108
        default:
32✔
2109
        {
2110
            poObj->Serialize(osStr, false);
32✔
2111
            break;
32✔
2112
        }
2113
    }
2114
    return true;
44✔
2115
}
2116

2117
/************************************************************************/
2118
/*                         SerializeAndRenumber()                       */
2119
/************************************************************************/
2120

2121
GDALPDFObjectNum
2122
GDALPDFComposerWriter::SerializeAndRenumber(GDALPDFObject *poObj)
4✔
2123
{
2124
    RemapType oRemapObjectRefs;
8✔
2125
    return EmitNewObject(poObj, oRemapObjectRefs);
8✔
2126
}
2127

2128
/************************************************************************/
2129
/*                             WritePDF()                               */
2130
/************************************************************************/
2131

2132
bool GDALPDFComposerWriter::WritePDF(const CPLXMLNode *psNode,
12✔
2133
                                     PageContext &oPageContext)
2134
{
2135
    const char *pszDataset = CPLGetXMLValue(psNode, "dataset", nullptr);
12✔
2136
    if (!pszDataset)
12✔
2137
    {
2138
        CPLError(CE_Failure, CPLE_AppDefined, "Missing dataset");
2✔
2139
        return false;
2✔
2140
    }
2141

2142
    GDALOpenInfo oOpenInfo(pszDataset, GA_ReadOnly);
20✔
2143
    std::unique_ptr<PDFDataset> poDS(PDFDataset::Open(&oOpenInfo));
20✔
2144
    if (!poDS)
10✔
2145
    {
2146
        CPLError(CE_Failure, CPLE_OpenFailed, "%s is not a valid PDF file",
2✔
2147
                 pszDataset);
2148
        return false;
2✔
2149
    }
2150
    if (poDS->GetPageWidth() != oPageContext.m_dfWidthInUserUnit ||
16✔
2151
        poDS->GetPageHeight() != oPageContext.m_dfHeightInUserUnit)
8✔
2152
    {
2153
        CPLError(CE_Warning, CPLE_AppDefined,
×
2154
                 "Dimensions of the inserted PDF page are %fx%f, which is "
2155
                 "different from the output PDF page %fx%f",
2156
                 poDS->GetPageWidth(), poDS->GetPageHeight(),
2157
                 oPageContext.m_dfWidthInUserUnit,
2158
                 oPageContext.m_dfHeightInUserUnit);
2159
    }
2160
    auto poPageObj = poDS->GetPageObj();
8✔
2161
    if (!poPageObj)
8✔
2162
        return false;
×
2163
    auto poPageDict = poPageObj->GetDictionary();
8✔
2164
    if (!poPageDict)
8✔
2165
        return false;
×
2166
    auto poContents = poPageDict->Get("Contents");
8✔
2167
    if (poContents != nullptr && poContents->GetType() == PDFObjectType_Array)
8✔
2168
    {
2169
        GDALPDFArray *poContentsArray = poContents->GetArray();
×
2170
        if (poContentsArray->GetLength() == 1)
×
2171
        {
2172
            poContents = poContentsArray->Get(0);
×
2173
        }
2174
    }
2175
    if (poContents == nullptr ||
14✔
2176
        poContents->GetType() != PDFObjectType_Dictionary)
6✔
2177
    {
2178
        CPLError(CE_Failure, CPLE_AppDefined, "Missing Contents");
2✔
2179
        return false;
2✔
2180
    }
2181

2182
    auto poResources = poPageDict->Get("Resources");
6✔
2183
    if (!poResources)
6✔
2184
    {
2185
        CPLError(CE_Failure, CPLE_AppDefined, "Missing Resources");
2✔
2186
        return false;
2✔
2187
    }
2188

2189
    // Serialize and renumber the Page Resources dictionary
2190
    auto nClonedResources = SerializeAndRenumber(poResources);
4✔
2191
    if (!nClonedResources.toBool())
4✔
2192
    {
2193
        return false;
×
2194
    }
2195

2196
    // Create a Transparency group using cloned Page Resources, and
2197
    // the Page Contents stream
2198
    auto nFormId = AllocNewObject();
4✔
2199
    GDALPDFDictionaryRW oDictGroup;
8✔
2200
    GDALPDFDictionaryRW *poGroup = new GDALPDFDictionaryRW();
4✔
2201
    poGroup->Add("Type", GDALPDFObjectRW::CreateName("Group"))
4✔
2202
        .Add("S", GDALPDFObjectRW::CreateName("Transparency"));
4✔
2203

2204
    oDictGroup.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
4✔
2205
        .Add("BBox", &((new GDALPDFArrayRW())->Add(0).Add(0))
4✔
2206
                          .Add(oPageContext.m_dfWidthInUserUnit)
4✔
2207
                          .Add(oPageContext.m_dfHeightInUserUnit))
4✔
2208
        .Add("Subtype", GDALPDFObjectRW::CreateName("Form"))
4✔
2209
        .Add("Group", poGroup)
4✔
2210
        .Add("Resources", nClonedResources, 0);
4✔
2211

2212
    auto poStream = poContents->GetStream();
4✔
2213
    if (!poStream)
4✔
2214
    {
2215
        CPLError(CE_Failure, CPLE_AppDefined, "Missing Contents stream");
2✔
2216
        return false;
2✔
2217
    }
2218
    auto pabyContents = poStream->GetBytes();
2✔
2219
    if (!pabyContents)
2✔
2220
    {
2221
        return false;
×
2222
    }
2223
    const auto nContentsLength = poStream->GetLength();
2✔
2224

2225
    StartObjWithStream(nFormId, oDictGroup,
2✔
2226
                       oPageContext.m_eStreamCompressMethod != COMPRESS_NONE);
2✔
2227
    VSIFWriteL(pabyContents, 1, static_cast<size_t>(nContentsLength), m_fp);
2✔
2228
    VSIFree(pabyContents);
2✔
2229
    EndObjWithStream();
2✔
2230

2231
    // Paint the transparency group
2232
    double dfIgnoredOpacity;
2233
    StartBlending(psNode, oPageContext, dfIgnoredOpacity);
2✔
2234

2235
    oPageContext.m_osDrawingStream +=
2236
        CPLOPrintf("/Form%d Do\n", nFormId.toInt());
2✔
2237
    oPageContext.m_oXObjects[CPLOPrintf("Form%d", nFormId.toInt())] = nFormId;
2✔
2238

2239
    EndBlending(psNode, oPageContext);
2✔
2240

2241
    return true;
2✔
2242
}
2243

2244
#endif  // HAVE_PDF_READ_SUPPORT
2245

2246
/************************************************************************/
2247
/*                              Generate()                              */
2248
/************************************************************************/
2249

2250
bool GDALPDFComposerWriter::Generate(const CPLXMLNode *psComposition)
38✔
2251
{
2252
    m_osJPEG2000Driver = CPLGetXMLValue(psComposition, "JPEG2000Driver", "");
38✔
2253

2254
    auto psMetadata = CPLGetXMLNode(psComposition, "Metadata");
38✔
2255
    if (psMetadata)
38✔
2256
    {
2257
        SetInfo(CPLGetXMLValue(psMetadata, "Author", nullptr),
2258
                CPLGetXMLValue(psMetadata, "Producer", nullptr),
2259
                CPLGetXMLValue(psMetadata, "Creator", nullptr),
2260
                CPLGetXMLValue(psMetadata, "CreationDate", nullptr),
2261
                CPLGetXMLValue(psMetadata, "Subject", nullptr),
2262
                CPLGetXMLValue(psMetadata, "Title", nullptr),
2263
                CPLGetXMLValue(psMetadata, "Keywords", nullptr));
1✔
2264
        SetXMP(nullptr, CPLGetXMLValue(psMetadata, "XMP", nullptr));
1✔
2265
    }
2266

2267
    const char *pszJavascript =
2268
        CPLGetXMLValue(psComposition, "Javascript", nullptr);
38✔
2269
    if (pszJavascript)
38✔
2270
        WriteJavascript(pszJavascript, false);
1✔
2271

2272
    auto psLayerTree = CPLGetXMLNode(psComposition, "LayerTree");
38✔
2273
    if (psLayerTree)
38✔
2274
    {
2275
        m_bDisplayLayersOnlyOnVisiblePages = CPLTestBool(
7✔
2276
            CPLGetXMLValue(psLayerTree, "displayOnlyOnVisiblePages", "false"));
2277
        if (!CreateLayerTree(psLayerTree, GDALPDFObjectNum(), &m_oTreeOfOGC))
7✔
2278
            return false;
3✔
2279
    }
2280

2281
    bool bFoundPage = false;
35✔
2282
    for (const auto *psIter = psComposition->psChild; psIter;
63✔
2283
         psIter = psIter->psNext)
28✔
2284
    {
2285
        if (psIter->eType == CXT_Element &&
48✔
2286
            strcmp(psIter->pszValue, "Page") == 0)
48✔
2287
        {
2288
            if (!GeneratePage(psIter))
37✔
2289
                return false;
20✔
2290
            bFoundPage = true;
17✔
2291
        }
2292
    }
2293
    if (!bFoundPage)
15✔
2294
    {
2295
        CPLError(CE_Failure, CPLE_AppDefined,
1✔
2296
                 "At least one page should be defined");
2297
        return false;
1✔
2298
    }
2299

2300
    auto psOutline = CPLGetXMLNode(psComposition, "Outline");
14✔
2301
    if (psOutline)
14✔
2302
    {
2303
        if (!CreateOutline(psOutline))
5✔
2304
            return false;
4✔
2305
    }
2306

2307
    return true;
10✔
2308
}
2309

2310
/************************************************************************/
2311
/*                          GDALPDFErrorHandler()                       */
2312
/************************************************************************/
2313

2314
static void CPL_STDCALL GDALPDFErrorHandler(CPL_UNUSED CPLErr eErr,
19✔
2315
                                            CPL_UNUSED CPLErrorNum nType,
2316
                                            const char *pszMsg)
2317
{
2318
    std::vector<CPLString> *paosErrors =
2319
        static_cast<std::vector<CPLString> *>(CPLGetErrorHandlerUserData());
19✔
2320
    paosErrors->push_back(pszMsg);
19✔
2321
}
19✔
2322

2323
/************************************************************************/
2324
/*                      GDALPDFCreateFromCompositionFile()              */
2325
/************************************************************************/
2326

2327
GDALDataset *GDALPDFCreateFromCompositionFile(const char *pszPDFFilename,
39✔
2328
                                              const char *pszXMLFilename)
2329
{
2330
    CPLXMLTreeCloser oXML((pszXMLFilename[0] == '<' &&
76✔
2331
                           strstr(pszXMLFilename, "<PDFComposition") != nullptr)
37✔
2332
                              ? CPLParseXMLString(pszXMLFilename)
37✔
2333
                              : CPLParseXMLFile(pszXMLFilename));
115✔
2334
    if (!oXML.get())
39✔
2335
        return nullptr;
1✔
2336
    auto psComposition = CPLGetXMLNode(oXML.get(), "=PDFComposition");
38✔
2337
    if (!psComposition)
38✔
2338
    {
2339
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find PDFComposition");
×
2340
        return nullptr;
×
2341
    }
2342

2343
    // XML Validation.
2344
    if (CPLTestBool(CPLGetConfigOption("GDAL_XML_VALIDATION", "YES")))
38✔
2345
    {
2346
#ifdef EMBED_RESOURCE_FILES
2347
        std::string osTmpFilename;
2348
        CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
2349
#endif
2350
#ifdef USE_ONLY_EMBEDDED_RESOURCE_FILES
2351
        const char *pszXSD = nullptr;
2352
#else
2353
        const char *pszXSD = CPLFindFile("gdal", "pdfcomposition.xsd");
38✔
2354
#endif
2355
#ifdef EMBED_RESOURCE_FILES
2356
        if (!pszXSD)
2357
        {
2358
            static const bool bOnce [[maybe_unused]] = []()
2359
            {
2360
                CPLDebug("PDF", "Using embedded pdfcomposition.xsd");
2361
                return true;
2362
            }();
2363
            osTmpFilename = VSIMemGenerateHiddenFilename("pdfcomposition.xsd");
2364
            pszXSD = osTmpFilename.c_str();
2365
            VSIFCloseL(VSIFileFromMemBuffer(
2366
                osTmpFilename.c_str(),
2367
                const_cast<GByte *>(
2368
                    reinterpret_cast<const GByte *>(PDFGetCompositionXSD())),
2369
                static_cast<int>(strlen(PDFGetCompositionXSD())),
2370
                /* bTakeOwnership = */ false));
2371
        }
2372
#else
2373
        if (pszXSD != nullptr)
38✔
2374
#endif
2375
        {
2376
            std::vector<CPLString> aosErrors;
76✔
2377
            CPLPushErrorHandlerEx(GDALPDFErrorHandler, &aosErrors);
38✔
2378
            const int bRet = CPLValidateXML(pszXMLFilename, pszXSD, nullptr);
38✔
2379
            CPLPopErrorHandler();
38✔
2380
            if (!bRet)
38✔
2381
            {
2382
                if (!aosErrors.empty() &&
36✔
2383
                    strstr(aosErrors[0].c_str(), "missing libxml2 support") ==
18✔
2384
                        nullptr)
2385
                {
2386
                    for (size_t i = 0; i < aosErrors.size(); i++)
37✔
2387
                    {
2388
                        CPLError(CE_Warning, CPLE_AppDefined, "%s",
19✔
2389
                                 aosErrors[i].c_str());
19✔
2390
                    }
2391
                }
2392
            }
2393
            CPLErrorReset();
38✔
2394
        }
2395

2396
#ifdef EMBED_RESOURCE_FILES
2397
        if (!osTmpFilename.empty())
2398
            VSIUnlink(osTmpFilename.c_str());
2399
#endif
2400
    }
2401

2402
    /* -------------------------------------------------------------------- */
2403
    /*      Create file.                                                    */
2404
    /* -------------------------------------------------------------------- */
2405
    VSILFILE *fp = VSIFOpenL(pszPDFFilename, "wb");
38✔
2406
    if (fp == nullptr)
38✔
2407
    {
2408
        CPLError(CE_Failure, CPLE_OpenFailed, "Unable to create PDF file %s.\n",
×
2409
                 pszPDFFilename);
2410
        return nullptr;
×
2411
    }
2412

2413
    GDALPDFComposerWriter oWriter(fp);
76✔
2414
    if (!oWriter.Generate(psComposition))
38✔
2415
        return nullptr;
28✔
2416

2417
    return new GDALFakePDFDataset();
10✔
2418
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc