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

OSGeo / gdal / 13872211292

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

Pull #11951

github

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

553795 of 786140 relevant lines covered (70.44%)

221892.63 hits per line

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

92.83
/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
/************************************************************************/
31
/*                         GDALPDFComposerWriter()                      */
32
/************************************************************************/
33

34
GDALPDFComposerWriter::GDALPDFComposerWriter(VSILFILE *fp)
38✔
35
    : GDALPDFBaseWriter(fp)
38✔
36
{
37
    StartNewDoc();
38✔
38
}
38✔
39

40
/************************************************************************/
41
/*                        ~GDALPDFComposerWriter()                      */
42
/************************************************************************/
43

44
GDALPDFComposerWriter::~GDALPDFComposerWriter()
38✔
45
{
46
    Close();
38✔
47
}
38✔
48

49
/************************************************************************/
50
/*                                  Close()                             */
51
/************************************************************************/
52

53
void GDALPDFComposerWriter::Close()
38✔
54
{
55
    if (m_fp)
38✔
56
    {
57
        CPLAssert(!m_bInWriteObj);
38✔
58
        if (m_nPageResourceId.toBool())
38✔
59
        {
60
            WritePages();
38✔
61
            WriteXRefTableAndTrailer(false, 0);
38✔
62
        }
63
    }
64
    GDALPDFBaseWriter::Close();
38✔
65
}
38✔
66

67
/************************************************************************/
68
/*                          CreateOCGOrder()                            */
69
/************************************************************************/
70

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

85
/************************************************************************/
86
/*                          CollectOffOCG()                             */
87
/************************************************************************/
88

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

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

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

114
        for (size_t i = 0; i < m_asPageId.size(); i++)
70✔
115
            poKids->Add(m_asPageId[i], 0);
32✔
116

117
        VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
38✔
118
    }
119
    EndObj();
38✔
120

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

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

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

163
            GDALPDFDictionaryRW *poDictD = new GDALPDFDictionaryRW();
5✔
164
            poDictOCProperties->Add("D", poDictD);
5✔
165

166
            if (m_bDisplayLayersOnlyOnVisiblePages)
5✔
167
            {
168
                poDictD->Add("ListMode",
169
                             GDALPDFObjectRW::CreateName("VisiblePages"));
1✔
170
            }
171

172
            /* Build "Order" array of D dict */
173
            GDALPDFArrayRW *poArrayOrder = CreateOCGOrder(&m_oTreeOfOGC);
5✔
174
            poDictD->Add("Order", poArrayOrder);
5✔
175

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

187
                poDictD->Add("OFF", poArrayOFF);
1✔
188
            }
189

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

204
                poDictD->Add("RBGroups", poArrayRBGroups);
1✔
205
            }
206

207
            GDALPDFArrayRW *poArrayOGCs = new GDALPDFArrayRW();
5✔
208
            for (const auto &ocg : m_asOCGs)
17✔
209
                poArrayOGCs->Add(ocg.nId, 0);
12✔
210
            poDictOCProperties->Add("OCGs", poArrayOGCs);
5✔
211
        }
212

213
        if (m_nStructTreeRootId.toBool())
38✔
214
        {
215
            GDALPDFDictionaryRW *poDictMarkInfo = new GDALPDFDictionaryRW();
3✔
216
            oDict.Add("MarkInfo", poDictMarkInfo);
3✔
217
            poDictMarkInfo->Add("UserProperties",
218
                                GDALPDFObjectRW::CreateBool(TRUE));
3✔
219

220
            oDict.Add("StructTreeRoot", m_nStructTreeRootId, 0);
3✔
221
        }
222

223
        if (m_nNamesId.toBool())
38✔
224
            oDict.Add("Names", m_nNamesId, 0);
1✔
225

226
        VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
38✔
227
    }
228
    EndObj();
38✔
229
}
38✔
230

231
/************************************************************************/
232
/*                          CreateLayerTree()                           */
233
/************************************************************************/
234

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

265
            const bool bInitiallyVisible =
266
                CPLTestBool(CPLGetXMLValue(psIter, "initiallyVisible", "true"));
12✔
267

268
            const char *pszMutuallyExclusiveGroupId =
269
                CPLGetXMLValue(psIter, "mutuallyExclusiveGroupId", nullptr);
12✔
270

271
            auto nThisObjId = WriteOCG(pszName, nParentId);
12✔
272
            m_oMapLayerIdToOCG[pszId] = nThisObjId;
12✔
273

274
            auto newTreeOfOCG = std::make_unique<TreeOfOCG>();
12✔
275
            newTreeOfOCG->m_nNum = nThisObjId;
12✔
276
            newTreeOfOCG->m_bInitiallyVisible = bInitiallyVisible;
12✔
277
            parent->m_children.emplace_back(std::move(newTreeOfOCG));
12✔
278

279
            if (pszMutuallyExclusiveGroupId)
12✔
280
            {
281
                m_oMapExclusiveOCGIdToOCGs[pszMutuallyExclusiveGroupId]
4✔
282
                    .push_back(nThisObjId);
2✔
283
            }
284

285
            if (!CreateLayerTree(psIter, nThisObjId,
12✔
286
                                 parent->m_children.back().get()))
12✔
287
            {
288
                return false;
×
289
            }
290
        }
291
    }
292
    return true;
16✔
293
}
294

295
/************************************************************************/
296
/*                             ParseActions()                           */
297
/************************************************************************/
298

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

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

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

391
    if (!anONLayers.empty() || !anOFFLayers.empty())
8✔
392
    {
393
        auto poAction = std::make_unique<SetLayerStateAction>();
4✔
394
        poAction->m_anONLayers = std::move(anONLayers);
4✔
395
        poAction->m_anOFFLayers = std::move(anOFFLayers);
4✔
396
        actions.push_back(std::move(poAction));
4✔
397
    }
398

399
    return true;
8✔
400
}
401

402
/************************************************************************/
403
/*                       CreateOutlineFirstPass()                       */
404
/************************************************************************/
405

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

430
            const auto poActions = CPLGetXMLNode(psIter, "Actions");
13✔
431
            if (poActions)
13✔
432
            {
433
                if (!ParseActions(poActions, newItem->m_aoActions))
12✔
434
                    return false;
4✔
435
            }
436

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

449
/************************************************************************/
450
/*                            SerializeActions()                        */
451
/************************************************************************/
452

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

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

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

532
        if (poActionDict)
10✔
533
        {
534
            if (poLastActionDict == nullptr)
6✔
535
            {
536
                poRetAction = poActionDict;
4✔
537
            }
538
            else
539
            {
540
                poLastActionDict->Add("Next", poActionDict);
2✔
541
            }
542
            poLastActionDict = poActionDict;
6✔
543
        }
544
    }
545
    return poRetAction;
9✔
546
}
547

548
/************************************************************************/
549
/*                        SerializeOutlineKids()                        */
550
/************************************************************************/
551

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

562
        auto poActionDict = SerializeActions(&oDict, poItem->m_aoActions);
9✔
563
        if (poActionDict)
9✔
564
        {
565
            oDict.Add("A", poActionDict);
4✔
566
        }
567

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

596
/************************************************************************/
597
/*                           CreateOutline()                            */
598
/************************************************************************/
599

600
bool GDALPDFComposerWriter::CreateOutline(const CPLXMLNode *psNode)
5✔
601
{
602
    OutlineItem oRootOutlineItem;
10✔
603
    if (!CreateOutlineFirstPass(psNode, &oRootOutlineItem))
5✔
604
        return false;
4✔
605
    if (oRootOutlineItem.m_aoKids.empty())
1✔
606
        return true;
×
607

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

621
/************************************************************************/
622
/*                        GenerateGeoreferencing()                      */
623
/************************************************************************/
624

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

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

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

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

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

737
    if (CPLTestBool(
4✔
738
            CPLGetXMLValue(psGeoreferencing, "OGCBestPracticeFormat", "false")))
739
    {
740
        CPLError(CE_Failure, CPLE_NotSupported,
×
741
                 "OGCBestPracticeFormat no longer supported. Use "
742
                 "ISO32000ExtensionFormat");
743
        return false;
×
744
    }
745

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

766
        georeferencing.m_osID = pszId;
3✔
767
        georeferencing.m_oSRS = *(poSRS.get());
3✔
768
        georeferencing.m_bboxX1 = bboxX1;
3✔
769
        georeferencing.m_bboxY1 = bboxY1;
3✔
770
        georeferencing.m_bboxX2 = bboxX2;
3✔
771
        georeferencing.m_bboxY2 = bboxY2;
3✔
772
    }
773

774
    return true;
4✔
775
}
776

777
/************************************************************************/
778
/*                      GenerateISO32000_Georeferencing()               */
779
/************************************************************************/
780

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

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

815
        return GDALPDFObjectNum();
×
816
    }
817

818
    const char *pszAuthorityCode = OSRGetAuthorityCode(hSRS, nullptr);
4✔
819
    const char *pszAuthorityName = OSRGetAuthorityName(hSRS, nullptr);
4✔
820
    int nEPSGCode = 0;
4✔
821
    if (pszAuthorityName != nullptr && EQUAL(pszAuthorityName, "EPSG") &&
4✔
822
        pszAuthorityCode != nullptr)
823
        nEPSGCode = atoi(pszAuthorityCode);
4✔
824

825
    int bIsGeographic = OSRIsGeographic(hSRS);
4✔
826

827
    char *pszESRIWKT = nullptr;
4✔
828
    const char *apszOptions[] = {"FORMAT=WKT1_ESRI", nullptr};
4✔
829
    OSRExportToWktEx(hSRS, &pszESRIWKT, apszOptions);
4✔
830

831
    OSRDestroySpatialReference(hSRSGeog);
4✔
832
    OCTDestroyCoordinateTransformation(hCT);
4✔
833

834
    auto nViewportId = AllocNewObject();
4✔
835
    auto nMeasureId = AllocNewObject();
4✔
836
    auto nGCSId = AllocNewObject();
4✔
837

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

851
    GDALPDFArrayRW *poGPTS = new GDALPDFArrayRW();
4✔
852
    GDALPDFArrayRW *poLPTS = new GDALPDFArrayRW();
4✔
853

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

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

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

898
    CPLFree(pszESRIWKT);
4✔
899

900
    return nViewportId;
4✔
901
}
902

903
/************************************************************************/
904
/*                         GeneratePage()                               */
905
/************************************************************************/
906

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

922
    std::vector<GDALPDFObjectNum> anViewportIds;
72✔
923

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

948
    auto nPageId = AllocNewObject();
32✔
949
    m_asPageId.push_back(nPageId);
32✔
950

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

964
    const auto psContent = CPLGetXMLNode(psPage, "Content");
31✔
965
    if (!psContent)
31✔
966
    {
967
        CPLError(CE_Failure, CPLE_AppDefined, "Missing Content");
1✔
968
        return false;
1✔
969
    }
970

971
    const bool bDeflateStreamCompression = EQUAL(
30✔
972
        CPLGetXMLValue(psContent, "streamCompression", "DEFLATE"), "DEFLATE");
973

974
    oPageContext.m_dfWidthInUserUnit = dfWidthInUserUnit;
30✔
975
    oPageContext.m_dfHeightInUserUnit = dfHeightInUserUnit;
30✔
976
    oPageContext.m_eStreamCompressMethod =
30✔
977
        bDeflateStreamCompression ? COMPRESS_DEFLATE : COMPRESS_NONE;
30✔
978
    if (!ExploreContent(psContent, oPageContext))
30✔
979
        return false;
13✔
980

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

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

1016
    auto nContentId = AllocNewObject();
17✔
1017
    auto nResourcesId = AllocNewObject();
17✔
1018

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

1032
    if (nAnnotsId.toBool())
17✔
1033
        oDictPage.Add("Annots", nAnnotsId, 0);
1✔
1034

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

1048
    if (nStructParentsIdx >= 0)
17✔
1049
    {
1050
        oDictPage.Add("StructParents", nStructParentsIdx);
3✔
1051
    }
1052

1053
    VSIFPrintfL(m_fp, "%s\n", oDictPage.Serialize().c_str());
17✔
1054
    EndObj();
17✔
1055

1056
    /* -------------------------------------------------------------- */
1057
    /*  Write content dictionary                                      */
1058
    /* -------------------------------------------------------------- */
1059
    {
1060
        GDALPDFDictionaryRW oDict;
34✔
1061
        StartObjWithStream(nContentId, oDict, bDeflateStreamCompression);
17✔
1062
        VSIFPrintfL(m_fp, "%s", oPageContext.m_osDrawingStream.c_str());
17✔
1063
        EndObjWithStream();
17✔
1064
    }
1065

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

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

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

1102
        VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
17✔
1103
    }
1104
    EndObj();
17✔
1105

1106
    return true;
17✔
1107
}
1108

1109
/************************************************************************/
1110
/*                          ExploreContent()                            */
1111
/************************************************************************/
1112

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

1144
        else if (psIter->eType == CXT_Element &&
46✔
1145
                 strcmp(psIter->pszValue, "Raster") == 0)
26✔
1146
        {
1147
            if (!WriteRaster(psIter, oPageContext))
6✔
1148
                return false;
2✔
1149
        }
1150

1151
        else if (psIter->eType == CXT_Element &&
40✔
1152
                 strcmp(psIter->pszValue, "Vector") == 0)
20✔
1153
        {
1154
            if (!WriteVector(psIter, oPageContext))
5✔
1155
                return false;
×
1156
        }
1157

1158
        else if (psIter->eType == CXT_Element &&
35✔
1159
                 strcmp(psIter->pszValue, "VectorLabel") == 0)
15✔
1160
        {
1161
            if (!WriteVectorLabel(psIter, oPageContext))
3✔
1162
                return false;
×
1163
        }
1164

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

1182
/************************************************************************/
1183
/*                          StartBlending()                             */
1184
/************************************************************************/
1185

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

1214
/************************************************************************/
1215
/*                          EndBlending()                             */
1216
/************************************************************************/
1217

1218
void GDALPDFComposerWriter::EndBlending(const CPLXMLNode *psNode,
12✔
1219
                                        PageContext &oPageContext)
1220
{
1221
    const auto psBlending = CPLGetXMLNode(psNode, "Blending");
12✔
1222
    if (psBlending)
12✔
1223
    {
1224
        oPageContext.m_osDrawingStream += "Q\n";
5✔
1225
    }
1226
}
12✔
1227

1228
/************************************************************************/
1229
/*                           WriteRaster()                              */
1230
/************************************************************************/
1231

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

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

1305
        bClip = true;
1✔
1306
        dfClippingMinX = APPLY_GT_X(georeferencing.m_adfGT, dfX1, dfY1);
1✔
1307
        dfClippingMinY = APPLY_GT_Y(georeferencing.m_adfGT, dfX1, dfY1);
1✔
1308
        dfClippingMaxX = APPLY_GT_X(georeferencing.m_adfGT, dfX2, dfY2);
1✔
1309
        dfClippingMaxY = APPLY_GT_Y(georeferencing.m_adfGT, dfX2, dfY2);
1✔
1310

1311
        if (poDS->GetGeoTransform(adfRasterGT) != CE_None ||
1✔
1312
            adfRasterGT[2] != 0 || adfRasterGT[4] != 0 || adfRasterGT[5] > 0)
1✔
1313
        {
1314
            CPLError(CE_Failure, CPLE_AppDefined,
×
1315
                     "Raster has no geotransform or a rotated geotransform");
1316
            return false;
×
1317
        }
1318

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

1328
        CPL_IGNORE_RET_VAL(GDALInvGeoTransform(georeferencing.m_adfGT,
1✔
1329
                                               adfInvGeoreferencingGT));
1330
    }
1331
    const double dfRasterMinX = adfRasterGT[0];
4✔
1332
    const double dfRasterMaxY = adfRasterGT[3];
4✔
1333

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

1337
    double dfIgnoredOpacity;
1338
    StartBlending(psNode, oPageContext, dfIgnoredOpacity);
4✔
1339

1340
    CPLString osGroupStream;
8✔
1341
    std::vector<GDALPDFObjectNum> anImageIds;
8✔
1342

1343
    const int nXBlocks = (nWidth + nBlockXSize - 1) / nBlockXSize;
4✔
1344
    const int nYBlocks = (nHeight + nBlockYSize - 1) / nBlockYSize;
4✔
1345
    int nBlockXOff, nBlockYOff;
1346
    for (nBlockYOff = 0; nBlockYOff < nYBlocks; nBlockYOff++)
10✔
1347
    {
1348
        for (nBlockXOff = 0; nBlockXOff < nXBlocks; nBlockXOff++)
16✔
1349
        {
1350
            int nReqWidth =
1351
                std::min(nBlockXSize, nWidth - nBlockXOff * nBlockXSize);
10✔
1352
            int nReqHeight =
1353
                std::min(nBlockYSize, nHeight - nBlockYOff * nBlockYSize);
10✔
1354

1355
            int nX = nBlockXOff * nBlockXSize;
10✔
1356
            int nY = nBlockYOff * nBlockYSize;
10✔
1357

1358
            double dfXPDFOff = nX * (dfX2 - dfX1) / nWidth + dfX1;
10✔
1359
            double dfYPDFOff =
10✔
1360
                (nHeight - nY - nReqHeight) * (dfY2 - dfY1) / nHeight + dfY1;
10✔
1361
            double dfXPDFSize = nReqWidth * (dfX2 - dfX1) / nWidth;
10✔
1362
            double dfYPDFSize = nReqHeight * (dfY2 - dfY1) / nHeight;
10✔
1363

1364
            if (bClip)
10✔
1365
            {
1366
                /* Compute extent of block to write */
1367
                double dfBlockMinX = adfRasterGT[0] + nX * adfRasterGT[1];
1✔
1368
                double dfBlockMaxX =
1✔
1369
                    adfRasterGT[0] + (nX + nReqWidth) * adfRasterGT[1];
1✔
1370
                double dfBlockMinY =
1✔
1371
                    adfRasterGT[3] + (nY + nReqHeight) * adfRasterGT[5];
1✔
1372
                double dfBlockMaxY = adfRasterGT[3] + nY * adfRasterGT[5];
1✔
1373

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

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

1409
                    if (nReqWidth > 0 && nReqHeight > 0)
1✔
1410
                    {
1411
                        dfBlockMinX = adfRasterGT[0] + nX * adfRasterGT[1];
1✔
1412
                        dfBlockMaxX =
1✔
1413
                            adfRasterGT[0] + (nX + nReqWidth) * adfRasterGT[1];
1✔
1414
                        dfBlockMinY =
1✔
1415
                            adfRasterGT[3] + (nY + nReqHeight) * adfRasterGT[5];
1✔
1416
                        dfBlockMaxY = adfRasterGT[3] + nY * adfRasterGT[5];
1✔
1417

1418
                        double dfPDFX1 = APPLY_GT_X(adfInvGeoreferencingGT,
1✔
1419
                                                    dfBlockMinX, dfBlockMinY);
1420
                        double dfPDFY1 = APPLY_GT_Y(adfInvGeoreferencingGT,
1✔
1421
                                                    dfBlockMinX, dfBlockMinY);
1422
                        double dfPDFX2 = APPLY_GT_X(adfInvGeoreferencingGT,
1✔
1423
                                                    dfBlockMaxX, dfBlockMaxY);
1424
                        double dfPDFY2 = APPLY_GT_Y(adfInvGeoreferencingGT,
1✔
1425
                                                    dfBlockMaxX, dfBlockMaxY);
1426

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

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

1445
            if (!nImageId.toBool())
10✔
1446
                return false;
×
1447

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

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

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

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

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

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

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

1516
    EndBlending(psNode, oPageContext);
4✔
1517

1518
    return true;
4✔
1519
}
1520

1521
/************************************************************************/
1522
/*                     SetupVectorGeoreferencing()                      */
1523
/************************************************************************/
1524

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

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

1546
    dfClippingMinX = APPLY_GT_X(georeferencing.m_adfGT, dfX1, dfY1);
4✔
1547
    dfClippingMinY = APPLY_GT_Y(georeferencing.m_adfGT, dfX1, dfY1);
4✔
1548
    dfClippingMaxX = APPLY_GT_X(georeferencing.m_adfGT, dfX2, dfY2);
4✔
1549
    dfClippingMaxY = APPLY_GT_Y(georeferencing.m_adfGT, dfX2, dfY2);
4✔
1550

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

1563
    if (!poCT)
4✔
1564
    {
1565
        poLayer->SetSpatialFilterRect(dfClippingMinX, dfClippingMinY,
2✔
1566
                                      dfClippingMaxX, dfClippingMaxY);
1567
    }
1568

1569
    double adfInvGeoreferencingGT[6];  // from georeferenced to PDF coordinates
1570
    CPL_IGNORE_RET_VAL(GDALInvGeoTransform(
4✔
1571
        const_cast<double *>(georeferencing.m_adfGT), adfInvGeoreferencingGT));
4✔
1572
    adfMatrix[0] = adfInvGeoreferencingGT[0];
4✔
1573
    adfMatrix[1] = adfInvGeoreferencingGT[1];
4✔
1574
    adfMatrix[2] = adfInvGeoreferencingGT[3];
4✔
1575
    adfMatrix[3] = adfInvGeoreferencingGT[5];
4✔
1576

1577
    return true;
4✔
1578
}
1579

1580
/************************************************************************/
1581
/*                           WriteVector()                              */
1582
/************************************************************************/
1583

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

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

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

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

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

1711
    if (!m_nStructTreeRootId.toBool())
5✔
1712
        m_nStructTreeRootId = AllocNewObject();
3✔
1713

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

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

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

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

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

1760
        const double dfRadius = os.dfSymbolSize;
8✔
1761

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

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

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

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

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

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

1824
            oPageContext.m_osDrawingStream += "Q\n";
8✔
1825
        }
1826

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

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

1841
        {
1842
            StartObj(nFeatureLayerId);
5✔
1843

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

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

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

1861
            EndObj();
5✔
1862
        }
1863
    }
1864

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

1874
    return true;
5✔
1875
}
1876

1877
/************************************************************************/
1878
/*                         WriteVectorLabel()                           */
1879
/************************************************************************/
1880

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

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

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

1912
    double dfOpacityFactor = 1.0;
3✔
1913
    StartBlending(psNode, oPageContext, dfOpacityFactor);
3✔
1914

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

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

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

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

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

1974
    EndBlending(psNode, oPageContext);
3✔
1975

1976
    return true;
3✔
1977
}
1978

1979
#ifdef HAVE_PDF_READ_SUPPORT
1980

1981
/************************************************************************/
1982
/*                            EmitNewObject()                           */
1983
/************************************************************************/
1984

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

2007
/************************************************************************/
2008
/*                         SerializeAndRenumber()                       */
2009
/************************************************************************/
2010

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

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

2040
/************************************************************************/
2041
/*                    SerializeAndRenumberIgnoreRef()                   */
2042
/************************************************************************/
2043

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

2115
/************************************************************************/
2116
/*                         SerializeAndRenumber()                       */
2117
/************************************************************************/
2118

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

2126
/************************************************************************/
2127
/*                             WritePDF()                               */
2128
/************************************************************************/
2129

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

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

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

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

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

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

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

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

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

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

2237
    EndBlending(psNode, oPageContext);
2✔
2238

2239
    return true;
2✔
2240
}
2241

2242
#endif  // HAVE_PDF_READ_SUPPORT
2243

2244
/************************************************************************/
2245
/*                              Generate()                              */
2246
/************************************************************************/
2247

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

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

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

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

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

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

2305
    return true;
10✔
2306
}
2307

2308
/************************************************************************/
2309
/*                          GDALPDFErrorHandler()                       */
2310
/************************************************************************/
2311

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

2321
/************************************************************************/
2322
/*                      GDALPDFCreateFromCompositionFile()              */
2323
/************************************************************************/
2324

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

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

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

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

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

2415
    return new GDALFakePDFDataset();
10✔
2416
}
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