Coverage Report

Created: 2026-05-16 08:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/pdf/pdfcreatefromcomposition.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  PDF driver
4
 * Purpose:  GDALDataset driver for PDF dataset.
5
 * Author:   Even Rouault, <even dot rouault at spatialys 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
0
GDALPDFComposerWriter::Action::~Action() = default;
31
GDALPDFComposerWriter::GotoPageAction::~GotoPageAction() = default;
32
0
GDALPDFComposerWriter::SetLayerStateAction::~SetLayerStateAction() = default;
33
0
GDALPDFComposerWriter::JavascriptAction::~JavascriptAction() = default;
34
35
/************************************************************************/
36
/*                       GDALPDFComposerWriter()                        */
37
/************************************************************************/
38
39
GDALPDFComposerWriter::GDALPDFComposerWriter(VSILFILE *fp)
40
0
    : GDALPDFBaseWriter(fp)
41
0
{
42
0
    StartNewDoc();
43
0
}
44
45
/************************************************************************/
46
/*                       ~GDALPDFComposerWriter()                       */
47
/************************************************************************/
48
49
GDALPDFComposerWriter::~GDALPDFComposerWriter()
50
0
{
51
0
    Close();
52
0
}
53
54
/************************************************************************/
55
/*                               Close()                                */
56
/************************************************************************/
57
58
void GDALPDFComposerWriter::Close()
59
0
{
60
0
    if (m_fp)
61
0
    {
62
0
        CPLAssert(!m_bInWriteObj);
63
0
        if (m_nPageResourceId.toBool())
64
0
        {
65
0
            WritePages();
66
0
            WriteXRefTableAndTrailer(false, 0);
67
0
        }
68
0
    }
69
0
    GDALPDFBaseWriter::Close();
70
0
}
71
72
/************************************************************************/
73
/*                           CreateOCGOrder()                           */
74
/************************************************************************/
75
76
GDALPDFArrayRW *GDALPDFComposerWriter::CreateOCGOrder(const TreeOfOCG *parent)
77
0
{
78
0
    auto poArrayOrder = new GDALPDFArrayRW();
79
0
    for (const auto &child : parent->m_children)
80
0
    {
81
0
        poArrayOrder->Add(child->m_nNum, 0);
82
0
        if (!child->m_children.empty())
83
0
        {
84
0
            poArrayOrder->Add(CreateOCGOrder(child.get()));
85
0
        }
86
0
    }
87
0
    return poArrayOrder;
88
0
}
89
90
/************************************************************************/
91
/*                           CollectOffOCG()                            */
92
/************************************************************************/
93
94
void GDALPDFComposerWriter::CollectOffOCG(std::vector<GDALPDFObjectNum> &ar,
95
                                          const TreeOfOCG *parent)
96
0
{
97
0
    if (!parent->m_bInitiallyVisible)
98
0
        ar.push_back(parent->m_nNum);
99
0
    for (const auto &child : parent->m_children)
100
0
    {
101
0
        CollectOffOCG(ar, child.get());
102
0
    }
103
0
}
104
105
/************************************************************************/
106
/*                             WritePages()                             */
107
/************************************************************************/
108
109
void GDALPDFComposerWriter::WritePages()
110
0
{
111
0
    StartObj(m_nPageResourceId);
112
0
    {
113
0
        GDALPDFDictionaryRW oDict;
114
0
        GDALPDFArrayRW *poKids = new GDALPDFArrayRW();
115
0
        oDict.Add("Type", GDALPDFObjectRW::CreateName("Pages"))
116
0
            .Add("Count", static_cast<int>(m_asPageId.size()))
117
0
            .Add("Kids", poKids);
118
119
0
        for (size_t i = 0; i < m_asPageId.size(); i++)
120
0
            poKids->Add(m_asPageId[i], 0);
121
122
0
        VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
123
0
    }
124
0
    EndObj();
125
126
0
    if (m_nStructTreeRootId.toBool())
127
0
    {
128
0
        auto nParentTreeId = AllocNewObject();
129
0
        StartObj(nParentTreeId);
130
0
        VSIFPrintfL(m_fp, "<< /Nums [ ");
131
0
        for (size_t i = 0; i < m_anParentElements.size(); i++)
132
0
        {
133
0
            VSIFPrintfL(m_fp, "%d %d 0 R ", static_cast<int>(i),
134
0
                        m_anParentElements[i].toInt());
135
0
        }
136
0
        VSIFPrintfL(m_fp, " ] >> \n");
137
0
        EndObj();
138
139
0
        StartObj(m_nStructTreeRootId);
140
0
        VSIFPrintfL(m_fp,
141
0
                    "<< "
142
0
                    "/Type /StructTreeRoot "
143
0
                    "/ParentTree %d 0 R "
144
0
                    "/K [ ",
145
0
                    nParentTreeId.toInt());
146
0
        for (const auto &num : m_anFeatureLayerId)
147
0
        {
148
0
            VSIFPrintfL(m_fp, "%d 0 R ", num.toInt());
149
0
        }
150
0
        VSIFPrintfL(m_fp, "] >>\n");
151
0
        EndObj();
152
0
    }
153
154
0
    StartObj(m_nCatalogId);
155
0
    {
156
0
        GDALPDFDictionaryRW oDict;
157
0
        oDict.Add("Type", GDALPDFObjectRW::CreateName("Catalog"))
158
0
            .Add("Pages", m_nPageResourceId, 0);
159
0
        if (m_nOutlinesId.toBool())
160
0
            oDict.Add("Outlines", m_nOutlinesId, 0);
161
0
        if (m_nXMPId.toBool())
162
0
            oDict.Add("Metadata", m_nXMPId, 0);
163
0
        if (!m_asOCGs.empty())
164
0
        {
165
0
            GDALPDFDictionaryRW *poDictOCProperties = new GDALPDFDictionaryRW();
166
0
            oDict.Add("OCProperties", poDictOCProperties);
167
168
0
            GDALPDFDictionaryRW *poDictD = new GDALPDFDictionaryRW();
169
0
            poDictOCProperties->Add("D", poDictD);
170
171
0
            if (m_bDisplayLayersOnlyOnVisiblePages)
172
0
            {
173
0
                poDictD->Add("ListMode",
174
0
                             GDALPDFObjectRW::CreateName("VisiblePages"));
175
0
            }
176
177
            /* Build "Order" array of D dict */
178
0
            GDALPDFArrayRW *poArrayOrder = CreateOCGOrder(&m_oTreeOfOGC);
179
0
            poDictD->Add("Order", poArrayOrder);
180
181
            /* Build "OFF" array of D dict */
182
0
            std::vector<GDALPDFObjectNum> offOCGs;
183
0
            CollectOffOCG(offOCGs, &m_oTreeOfOGC);
184
0
            if (!offOCGs.empty())
185
0
            {
186
0
                GDALPDFArrayRW *poArrayOFF = new GDALPDFArrayRW();
187
0
                for (const auto &num : offOCGs)
188
0
                {
189
0
                    poArrayOFF->Add(num, 0);
190
0
                }
191
192
0
                poDictD->Add("OFF", poArrayOFF);
193
0
            }
194
195
            /* Build "RBGroups" array of D dict */
196
0
            if (!m_oMapExclusiveOCGIdToOCGs.empty())
197
0
            {
198
0
                GDALPDFArrayRW *poArrayRBGroups = new GDALPDFArrayRW();
199
0
                for (const auto &group : m_oMapExclusiveOCGIdToOCGs)
200
0
                {
201
0
                    GDALPDFArrayRW *poGroup = new GDALPDFArrayRW();
202
0
                    for (const auto &num : group.second)
203
0
                    {
204
0
                        poGroup->Add(num, 0);
205
0
                    }
206
0
                    poArrayRBGroups->Add(poGroup);
207
0
                }
208
209
0
                poDictD->Add("RBGroups", poArrayRBGroups);
210
0
            }
211
212
0
            GDALPDFArrayRW *poArrayOGCs = new GDALPDFArrayRW();
213
0
            for (const auto &ocg : m_asOCGs)
214
0
                poArrayOGCs->Add(ocg.nId, 0);
215
0
            poDictOCProperties->Add("OCGs", poArrayOGCs);
216
0
        }
217
218
0
        if (m_nStructTreeRootId.toBool())
219
0
        {
220
0
            GDALPDFDictionaryRW *poDictMarkInfo = new GDALPDFDictionaryRW();
221
0
            oDict.Add("MarkInfo", poDictMarkInfo);
222
0
            poDictMarkInfo->Add("UserProperties",
223
0
                                GDALPDFObjectRW::CreateBool(TRUE));
224
225
0
            oDict.Add("StructTreeRoot", m_nStructTreeRootId, 0);
226
0
        }
227
228
0
        if (m_nNamesId.toBool())
229
0
            oDict.Add("Names", m_nNamesId, 0);
230
231
0
        VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
232
0
    }
233
0
    EndObj();
234
0
}
235
236
/************************************************************************/
237
/*                          CreateLayerTree()                           */
238
/************************************************************************/
239
240
bool GDALPDFComposerWriter::CreateLayerTree(const CPLXMLNode *psNode,
241
                                            const GDALPDFObjectNum &nParentId,
242
                                            TreeOfOCG *parent)
243
0
{
244
0
    for (const auto *psIter = psNode->psChild; psIter; psIter = psIter->psNext)
245
0
    {
246
0
        if (psIter->eType == CXT_Element &&
247
0
            strcmp(psIter->pszValue, "Layer") == 0)
248
0
        {
249
0
            const char *pszId = CPLGetXMLValue(psIter, "id", nullptr);
250
0
            if (!pszId)
251
0
            {
252
0
                CPLError(CE_Failure, CPLE_AppDefined,
253
0
                         "Missing id attribute in Layer");
254
0
                return false;
255
0
            }
256
0
            const char *pszName = CPLGetXMLValue(psIter, "name", nullptr);
257
0
            if (!pszName)
258
0
            {
259
0
                CPLError(CE_Failure, CPLE_AppDefined,
260
0
                         "Missing name attribute in Layer");
261
0
                return false;
262
0
            }
263
0
            if (m_oMapLayerIdToOCG.find(pszId) != m_oMapLayerIdToOCG.end())
264
0
            {
265
0
                CPLError(CE_Failure, CPLE_AppDefined,
266
0
                         "Layer.id = %s is not unique", pszId);
267
0
                return false;
268
0
            }
269
270
0
            const bool bInitiallyVisible =
271
0
                CPLTestBool(CPLGetXMLValue(psIter, "initiallyVisible", "true"));
272
273
0
            const char *pszMutuallyExclusiveGroupId =
274
0
                CPLGetXMLValue(psIter, "mutuallyExclusiveGroupId", nullptr);
275
276
0
            auto nThisObjId = WriteOCG(pszName, nParentId);
277
0
            m_oMapLayerIdToOCG[pszId] = nThisObjId;
278
279
0
            auto newTreeOfOCG = std::make_unique<TreeOfOCG>();
280
0
            newTreeOfOCG->m_nNum = nThisObjId;
281
0
            newTreeOfOCG->m_bInitiallyVisible = bInitiallyVisible;
282
0
            parent->m_children.emplace_back(std::move(newTreeOfOCG));
283
284
0
            if (pszMutuallyExclusiveGroupId)
285
0
            {
286
0
                m_oMapExclusiveOCGIdToOCGs[pszMutuallyExclusiveGroupId]
287
0
                    .push_back(nThisObjId);
288
0
            }
289
290
0
            if (!CreateLayerTree(psIter, nThisObjId,
291
0
                                 parent->m_children.back().get()))
292
0
            {
293
0
                return false;
294
0
            }
295
0
        }
296
0
    }
297
0
    return true;
298
0
}
299
300
/************************************************************************/
301
/*                            ParseActions()                            */
302
/************************************************************************/
303
304
bool GDALPDFComposerWriter::ParseActions(
305
    const CPLXMLNode *psNode, std::vector<std::unique_ptr<Action>> &actions)
306
0
{
307
0
    std::set<GDALPDFObjectNum> anONLayers{};
308
0
    std::set<GDALPDFObjectNum> anOFFLayers{};
309
0
    for (const auto *psIter = psNode->psChild; psIter; psIter = psIter->psNext)
310
0
    {
311
0
        if (psIter->eType == CXT_Element &&
312
0
            strcmp(psIter->pszValue, "GotoPageAction") == 0)
313
0
        {
314
0
            auto poAction = std::make_unique<GotoPageAction>();
315
0
            const char *pszPageId = CPLGetXMLValue(psIter, "pageId", nullptr);
316
0
            if (!pszPageId)
317
0
            {
318
0
                CPLError(CE_Failure, CPLE_AppDefined,
319
0
                         "Missing pageId attribute in GotoPageAction");
320
0
                return false;
321
0
            }
322
323
0
            auto oIter = m_oMapPageIdToObjectNum.find(pszPageId);
324
0
            if (oIter == m_oMapPageIdToObjectNum.end())
325
0
            {
326
0
                CPLError(CE_Failure, CPLE_AppDefined,
327
0
                         "GotoPageAction.pageId = %s not pointing to a Page.id",
328
0
                         pszPageId);
329
0
                return false;
330
0
            }
331
0
            poAction->m_nPageDestId = oIter->second;
332
0
            poAction->m_dfX1 = CPLAtof(CPLGetXMLValue(psIter, "x1", "0"));
333
0
            poAction->m_dfX2 = CPLAtof(CPLGetXMLValue(psIter, "y1", "0"));
334
0
            poAction->m_dfY1 = CPLAtof(CPLGetXMLValue(psIter, "x2", "0"));
335
0
            poAction->m_dfY2 = CPLAtof(CPLGetXMLValue(psIter, "y2", "0"));
336
0
            actions.push_back(std::move(poAction));
337
0
        }
338
0
        else if (psIter->eType == CXT_Element &&
339
0
                 strcmp(psIter->pszValue, "SetAllLayersStateAction") == 0)
340
0
        {
341
0
            if (CPLTestBool(CPLGetXMLValue(psIter, "visible", "true")))
342
0
            {
343
0
                for (const auto &ocg : m_asOCGs)
344
0
                {
345
0
                    anOFFLayers.erase(ocg.nId);
346
0
                    anONLayers.insert(ocg.nId);
347
0
                }
348
0
            }
349
0
            else
350
0
            {
351
0
                for (const auto &ocg : m_asOCGs)
352
0
                {
353
0
                    anONLayers.erase(ocg.nId);
354
0
                    anOFFLayers.insert(ocg.nId);
355
0
                }
356
0
            }
357
0
        }
358
0
        else if (psIter->eType == CXT_Element &&
359
0
                 strcmp(psIter->pszValue, "SetLayerStateAction") == 0)
360
0
        {
361
0
            const char *pszLayerId = CPLGetXMLValue(psIter, "layerId", nullptr);
362
0
            if (!pszLayerId)
363
0
            {
364
0
                CPLError(CE_Failure, CPLE_AppDefined, "Missing layerId");
365
0
                return false;
366
0
            }
367
0
            auto oIter = m_oMapLayerIdToOCG.find(pszLayerId);
368
0
            if (oIter == m_oMapLayerIdToOCG.end())
369
0
            {
370
0
                CPLError(CE_Failure, CPLE_AppDefined,
371
0
                         "Referencing layer of unknown id: %s", pszLayerId);
372
0
                return false;
373
0
            }
374
0
            const auto &ocg = oIter->second;
375
376
0
            if (CPLTestBool(CPLGetXMLValue(psIter, "visible", "true")))
377
0
            {
378
0
                anOFFLayers.erase(ocg);
379
0
                anONLayers.insert(ocg);
380
0
            }
381
0
            else
382
0
            {
383
0
                anONLayers.erase(ocg);
384
0
                anOFFLayers.insert(ocg);
385
0
            }
386
0
        }
387
0
        else if (psIter->eType == CXT_Element &&
388
0
                 strcmp(psIter->pszValue, "JavascriptAction") == 0)
389
0
        {
390
0
            auto poAction = std::make_unique<JavascriptAction>();
391
0
            poAction->m_osScript = CPLGetXMLValue(psIter, nullptr, "");
392
0
            actions.push_back(std::move(poAction));
393
0
        }
394
0
    }
395
396
0
    if (!anONLayers.empty() || !anOFFLayers.empty())
397
0
    {
398
0
        auto poAction = std::make_unique<SetLayerStateAction>();
399
0
        poAction->m_anONLayers = std::move(anONLayers);
400
0
        poAction->m_anOFFLayers = std::move(anOFFLayers);
401
0
        actions.push_back(std::move(poAction));
402
0
    }
403
404
0
    return true;
405
0
}
406
407
/************************************************************************/
408
/*                       CreateOutlineFirstPass()                       */
409
/************************************************************************/
410
411
bool GDALPDFComposerWriter::CreateOutlineFirstPass(const CPLXMLNode *psNode,
412
                                                   OutlineItem *poParentItem)
413
0
{
414
0
    for (const auto *psIter = psNode->psChild; psIter; psIter = psIter->psNext)
415
0
    {
416
0
        if (psIter->eType == CXT_Element &&
417
0
            strcmp(psIter->pszValue, "OutlineItem") == 0)
418
0
        {
419
0
            auto newItem = std::make_unique<OutlineItem>();
420
0
            const char *pszName = CPLGetXMLValue(psIter, "name", nullptr);
421
0
            if (!pszName)
422
0
            {
423
0
                CPLError(CE_Failure, CPLE_AppDefined,
424
0
                         "Missing name attribute in OutlineItem");
425
0
                return false;
426
0
            }
427
0
            newItem->m_osName = pszName;
428
0
            newItem->m_bOpen =
429
0
                CPLTestBool(CPLGetXMLValue(psIter, "open", "true"));
430
0
            if (CPLTestBool(CPLGetXMLValue(psIter, "italic", "false")))
431
0
                newItem->m_nFlags |= 1 << 0;
432
0
            if (CPLTestBool(CPLGetXMLValue(psIter, "bold", "false")))
433
0
                newItem->m_nFlags |= 1 << 1;
434
435
0
            const auto poActions = CPLGetXMLNode(psIter, "Actions");
436
0
            if (poActions)
437
0
            {
438
0
                if (!ParseActions(poActions, newItem->m_aoActions))
439
0
                    return false;
440
0
            }
441
442
0
            newItem->m_nObjId = AllocNewObject();
443
0
            if (!CreateOutlineFirstPass(psIter, newItem.get()))
444
0
            {
445
0
                return false;
446
0
            }
447
0
            poParentItem->m_nKidsRecCount += 1 + newItem->m_nKidsRecCount;
448
0
            poParentItem->m_aoKids.push_back(std::move(newItem));
449
0
        }
450
0
    }
451
0
    return true;
452
0
}
453
454
/************************************************************************/
455
/*                          SerializeActions()                          */
456
/************************************************************************/
457
458
GDALPDFDictionaryRW *GDALPDFComposerWriter::SerializeActions(
459
    GDALPDFDictionaryRW *poDictForDest,
460
    const std::vector<std::unique_ptr<Action>> &actions)
461
0
{
462
0
    GDALPDFDictionaryRW *poRetAction = nullptr;
463
0
    GDALPDFDictionaryRW *poLastActionDict = nullptr;
464
0
    for (const auto &poAction : actions)
465
0
    {
466
0
        GDALPDFDictionaryRW *poActionDict = nullptr;
467
0
        auto poGotoPageAction = dynamic_cast<GotoPageAction *>(poAction.get());
468
0
        if (poGotoPageAction)
469
0
        {
470
0
            GDALPDFArrayRW *poDest = new GDALPDFArrayRW;
471
0
            poDest->Add(poGotoPageAction->m_nPageDestId, 0);
472
0
            if (poGotoPageAction->m_dfX1 == 0.0 &&
473
0
                poGotoPageAction->m_dfX2 == 0.0 &&
474
0
                poGotoPageAction->m_dfY1 == 0.0 &&
475
0
                poGotoPageAction->m_dfY2 == 0.0)
476
0
            {
477
0
                poDest->Add(GDALPDFObjectRW::CreateName("XYZ"))
478
0
                    .Add(GDALPDFObjectRW::CreateNull())
479
0
                    .Add(GDALPDFObjectRW::CreateNull())
480
0
                    .Add(GDALPDFObjectRW::CreateNull());
481
0
            }
482
0
            else
483
0
            {
484
0
                poDest->Add(GDALPDFObjectRW::CreateName("FitR"))
485
0
                    .Add(poGotoPageAction->m_dfX1)
486
0
                    .Add(poGotoPageAction->m_dfY1)
487
0
                    .Add(poGotoPageAction->m_dfX2)
488
0
                    .Add(poGotoPageAction->m_dfY2);
489
0
            }
490
0
            if (poDictForDest && actions.size() == 1)
491
0
            {
492
0
                poDictForDest->Add("Dest", poDest);
493
0
            }
494
0
            else
495
0
            {
496
0
                poActionDict = new GDALPDFDictionaryRW();
497
0
                poActionDict->Add("Type",
498
0
                                  GDALPDFObjectRW::CreateName("Action"));
499
0
                poActionDict->Add("S", GDALPDFObjectRW::CreateName("GoTo"));
500
0
                poActionDict->Add("D", poDest);
501
0
            }
502
0
        }
503
504
0
        auto setLayerStateAction =
505
0
            dynamic_cast<SetLayerStateAction *>(poAction.get());
506
0
        if (poActionDict == nullptr && setLayerStateAction)
507
0
        {
508
0
            poActionDict = new GDALPDFDictionaryRW();
509
0
            poActionDict->Add("Type", GDALPDFObjectRW::CreateName("Action"));
510
0
            poActionDict->Add("S", GDALPDFObjectRW::CreateName("SetOCGState"));
511
0
            auto poStateArray = new GDALPDFArrayRW();
512
0
            if (!setLayerStateAction->m_anOFFLayers.empty())
513
0
            {
514
0
                poStateArray->Add(GDALPDFObjectRW::CreateName("OFF"));
515
0
                for (const auto &ocg : setLayerStateAction->m_anOFFLayers)
516
0
                    poStateArray->Add(ocg, 0);
517
0
            }
518
0
            if (!setLayerStateAction->m_anONLayers.empty())
519
0
            {
520
0
                poStateArray->Add(GDALPDFObjectRW::CreateName("ON"));
521
0
                for (const auto &ocg : setLayerStateAction->m_anONLayers)
522
0
                    poStateArray->Add(ocg, 0);
523
0
            }
524
0
            poActionDict->Add("State", poStateArray);
525
0
        }
526
527
0
        auto javascriptAction =
528
0
            dynamic_cast<JavascriptAction *>(poAction.get());
529
0
        if (poActionDict == nullptr && javascriptAction)
530
0
        {
531
0
            poActionDict = new GDALPDFDictionaryRW();
532
0
            poActionDict->Add("Type", GDALPDFObjectRW::CreateName("Action"));
533
0
            poActionDict->Add("S", GDALPDFObjectRW::CreateName("JavaScript"));
534
0
            poActionDict->Add("JS", javascriptAction->m_osScript);
535
0
        }
536
537
0
        if (poActionDict)
538
0
        {
539
0
            if (poLastActionDict == nullptr)
540
0
            {
541
0
                poRetAction = poActionDict;
542
0
            }
543
0
            else
544
0
            {
545
0
                poLastActionDict->Add("Next", poActionDict);
546
0
            }
547
0
            poLastActionDict = poActionDict;
548
0
        }
549
0
    }
550
0
    return poRetAction;
551
0
}
552
553
/************************************************************************/
554
/*                        SerializeOutlineKids()                        */
555
/************************************************************************/
556
557
bool GDALPDFComposerWriter::SerializeOutlineKids(
558
    const OutlineItem *poParentItem)
559
0
{
560
0
    for (size_t i = 0; i < poParentItem->m_aoKids.size(); i++)
561
0
    {
562
0
        const auto &poItem = poParentItem->m_aoKids[i];
563
0
        StartObj(poItem->m_nObjId);
564
0
        GDALPDFDictionaryRW oDict;
565
0
        oDict.Add("Title", poItem->m_osName);
566
567
0
        auto poActionDict = SerializeActions(&oDict, poItem->m_aoActions);
568
0
        if (poActionDict)
569
0
        {
570
0
            oDict.Add("A", poActionDict);
571
0
        }
572
573
0
        if (i > 0)
574
0
        {
575
0
            oDict.Add("Prev", poParentItem->m_aoKids[i - 1]->m_nObjId, 0);
576
0
        }
577
0
        if (i + 1 < poParentItem->m_aoKids.size())
578
0
        {
579
0
            oDict.Add("Next", poParentItem->m_aoKids[i + 1]->m_nObjId, 0);
580
0
        }
581
0
        if (poItem->m_nFlags)
582
0
            oDict.Add("F", poItem->m_nFlags);
583
0
        oDict.Add("Parent", poParentItem->m_nObjId, 0);
584
0
        if (!poItem->m_aoKids.empty())
585
0
        {
586
0
            oDict.Add("First", poItem->m_aoKids.front()->m_nObjId, 0);
587
0
            oDict.Add("Last", poItem->m_aoKids.back()->m_nObjId, 0);
588
0
            oDict.Add("Count", poItem->m_bOpen ? poItem->m_nKidsRecCount
589
0
                                               : -poItem->m_nKidsRecCount);
590
0
        }
591
0
        int ret = VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
592
0
        EndObj();
593
0
        if (ret == 0)
594
0
            return false;
595
0
        if (!SerializeOutlineKids(poItem.get()))
596
0
            return false;
597
0
    }
598
0
    return true;
599
0
}
600
601
/************************************************************************/
602
/*                           CreateOutline()                            */
603
/************************************************************************/
604
605
bool GDALPDFComposerWriter::CreateOutline(const CPLXMLNode *psNode)
606
0
{
607
0
    OutlineItem oRootOutlineItem;
608
0
    if (!CreateOutlineFirstPass(psNode, &oRootOutlineItem))
609
0
        return false;
610
0
    if (oRootOutlineItem.m_aoKids.empty())
611
0
        return true;
612
613
0
    m_nOutlinesId = AllocNewObject();
614
0
    StartObj(m_nOutlinesId);
615
0
    GDALPDFDictionaryRW oDict;
616
0
    oDict.Add("Type", GDALPDFObjectRW::CreateName("Outlines"))
617
0
        .Add("First", oRootOutlineItem.m_aoKids.front()->m_nObjId, 0)
618
0
        .Add("Last", oRootOutlineItem.m_aoKids.back()->m_nObjId, 0)
619
0
        .Add("Count", oRootOutlineItem.m_nKidsRecCount);
620
0
    VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
621
0
    EndObj();
622
0
    oRootOutlineItem.m_nObjId = m_nOutlinesId;
623
0
    return SerializeOutlineKids(&oRootOutlineItem);
624
0
}
625
626
/************************************************************************/
627
/*                       GenerateGeoreferencing()                       */
628
/************************************************************************/
629
630
bool GDALPDFComposerWriter::GenerateGeoreferencing(
631
    const CPLXMLNode *psGeoreferencing, double dfWidthInUserUnit,
632
    double dfHeightInUserUnit, GDALPDFObjectNum &nViewportId,
633
    Georeferencing &georeferencing)
634
0
{
635
0
    double bboxX1 = 0;
636
0
    double bboxY1 = 0;
637
0
    double bboxX2 = dfWidthInUserUnit;
638
0
    double bboxY2 = dfHeightInUserUnit;
639
0
    const auto psBoundingBox = CPLGetXMLNode(psGeoreferencing, "BoundingBox");
640
0
    if (psBoundingBox)
641
0
    {
642
0
        bboxX1 = CPLAtof(
643
0
            CPLGetXMLValue(psBoundingBox, "x1", CPLSPrintf("%.17g", bboxX1)));
644
0
        bboxY1 = CPLAtof(
645
0
            CPLGetXMLValue(psBoundingBox, "y1", CPLSPrintf("%.17g", bboxY1)));
646
0
        bboxX2 = CPLAtof(
647
0
            CPLGetXMLValue(psBoundingBox, "x2", CPLSPrintf("%.17g", bboxX2)));
648
0
        bboxY2 = CPLAtof(
649
0
            CPLGetXMLValue(psBoundingBox, "y2", CPLSPrintf("%.17g", bboxY2)));
650
0
        if (bboxX2 <= bboxX1 || bboxY2 <= bboxY1)
651
0
        {
652
0
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid BoundingBox");
653
0
            return false;
654
0
        }
655
0
    }
656
657
0
    std::vector<gdal::GCP> aGCPs;
658
0
    for (const auto *psIter = psGeoreferencing->psChild; psIter;
659
0
         psIter = psIter->psNext)
660
0
    {
661
0
        if (psIter->eType == CXT_Element &&
662
0
            strcmp(psIter->pszValue, "ControlPoint") == 0)
663
0
        {
664
0
            const char *pszx = CPLGetXMLValue(psIter, "x", nullptr);
665
0
            const char *pszy = CPLGetXMLValue(psIter, "y", nullptr);
666
0
            const char *pszX = CPLGetXMLValue(psIter, "GeoX", nullptr);
667
0
            const char *pszY = CPLGetXMLValue(psIter, "GeoY", nullptr);
668
0
            if (!pszx || !pszy || !pszX || !pszY)
669
0
            {
670
0
                CPLError(CE_Failure, CPLE_NotSupported,
671
0
                         "At least one of x, y, GeoX or GeoY attribute "
672
0
                         "missing on ControlPoint");
673
0
                return false;
674
0
            }
675
0
            aGCPs.emplace_back(nullptr, nullptr, CPLAtof(pszx), CPLAtof(pszy),
676
0
                               CPLAtof(pszX), CPLAtof(pszY));
677
0
        }
678
0
    }
679
0
    if (aGCPs.size() < 4)
680
0
    {
681
0
        CPLError(CE_Failure, CPLE_NotSupported,
682
0
                 "At least 4 ControlPoint are required");
683
0
        return false;
684
0
    }
685
686
0
    const char *pszBoundingPolygon =
687
0
        CPLGetXMLValue(psGeoreferencing, "BoundingPolygon", nullptr);
688
0
    std::vector<xyPair> aBoundingPolygon;
689
0
    if (pszBoundingPolygon)
690
0
    {
691
0
        auto [poGeom, _] =
692
0
            OGRGeometryFactory::createFromWkt(pszBoundingPolygon);
693
0
        if (poGeom && poGeom->getGeometryType() == wkbPolygon)
694
0
        {
695
0
            auto poPoly = poGeom->toPolygon();
696
0
            auto poRing = poPoly->getExteriorRing();
697
0
            if (poRing)
698
0
            {
699
0
                if (psBoundingBox == nullptr)
700
0
                {
701
0
                    OGREnvelope sEnvelope;
702
0
                    poRing->getEnvelope(&sEnvelope);
703
0
                    bboxX1 = sEnvelope.MinX;
704
0
                    bboxY1 = sEnvelope.MinY;
705
0
                    bboxX2 = sEnvelope.MaxX;
706
0
                    bboxY2 = sEnvelope.MaxY;
707
0
                }
708
0
                for (int i = 0; i < poRing->getNumPoints(); i++)
709
0
                {
710
0
                    aBoundingPolygon.emplace_back(
711
0
                        xyPair(poRing->getX(i), poRing->getY(i)));
712
0
                }
713
0
            }
714
0
        }
715
0
    }
716
717
0
    const auto pszSRS = CPLGetXMLValue(psGeoreferencing, "SRS", nullptr);
718
0
    if (!pszSRS)
719
0
    {
720
0
        CPLError(CE_Failure, CPLE_NotSupported, "Missing SRS");
721
0
        return false;
722
0
    }
723
0
    auto poSRS = std::make_unique<OGRSpatialReference>();
724
0
    if (poSRS->SetFromUserInput(pszSRS) != OGRERR_NONE)
725
0
    {
726
0
        return false;
727
0
    }
728
0
    poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
729
730
0
    if (CPLTestBool(CPLGetXMLValue(psGeoreferencing, "ISO32000ExtensionFormat",
731
0
                                   "true")))
732
0
    {
733
0
        nViewportId = GenerateISO32000_Georeferencing(
734
0
            OGRSpatialReference::ToHandle(poSRS.get()), bboxX1, bboxY1, bboxX2,
735
0
            bboxY2, aGCPs, aBoundingPolygon);
736
0
        if (!nViewportId.toBool())
737
0
        {
738
0
            return false;
739
0
        }
740
0
    }
741
742
0
    if (CPLTestBool(
743
0
            CPLGetXMLValue(psGeoreferencing, "OGCBestPracticeFormat", "false")))
744
0
    {
745
0
        CPLError(CE_Failure, CPLE_NotSupported,
746
0
                 "OGCBestPracticeFormat no longer supported. Use "
747
0
                 "ISO32000ExtensionFormat");
748
0
        return false;
749
0
    }
750
751
0
    const char *pszId = CPLGetXMLValue(psGeoreferencing, "id", nullptr);
752
0
    if (pszId)
753
0
    {
754
0
        if (!GDALGCPsToGeoTransform(static_cast<int>(aGCPs.size()),
755
0
                                    gdal::GCP::c_ptr(aGCPs),
756
0
                                    georeferencing.m_gt.data(), TRUE))
757
0
        {
758
0
            CPLError(CE_Failure, CPLE_AppDefined,
759
0
                     "Could not compute geotransform with approximate match.");
760
0
            return false;
761
0
        }
762
0
        if (std::fabs(georeferencing.m_gt.xrot) <
763
0
                1e-5 * std::fabs(georeferencing.m_gt.xscale) &&
764
0
            std::fabs(georeferencing.m_gt.yrot) <
765
0
                1e-5 * std::fabs(georeferencing.m_gt.yscale))
766
0
        {
767
0
            georeferencing.m_gt.xrot = 0;
768
0
            georeferencing.m_gt.yrot = 0;
769
0
        }
770
771
0
        georeferencing.m_osID = pszId;
772
0
        georeferencing.m_oSRS = *(poSRS.get());
773
0
        georeferencing.m_bboxX1 = bboxX1;
774
0
        georeferencing.m_bboxY1 = bboxY1;
775
0
        georeferencing.m_bboxX2 = bboxX2;
776
0
        georeferencing.m_bboxY2 = bboxY2;
777
0
    }
778
779
0
    return true;
780
0
}
781
782
/************************************************************************/
783
/*                  GenerateISO32000_Georeferencing()                   */
784
/************************************************************************/
785
786
GDALPDFObjectNum GDALPDFComposerWriter::GenerateISO32000_Georeferencing(
787
    OGRSpatialReferenceH hSRS, double bboxX1, double bboxY1, double bboxX2,
788
    double bboxY2, const std::vector<gdal::GCP> &aGCPs,
789
    const std::vector<xyPair> &aBoundingPolygon)
790
0
{
791
0
    OGRSpatialReferenceH hSRSGeog = OSRCloneGeogCS(hSRS);
792
0
    if (hSRSGeog == nullptr)
793
0
    {
794
0
        return GDALPDFObjectNum();
795
0
    }
796
0
    OSRSetAxisMappingStrategy(hSRSGeog, OAMS_TRADITIONAL_GIS_ORDER);
797
0
    OGRCoordinateTransformationH hCT =
798
0
        OCTNewCoordinateTransformation(hSRS, hSRSGeog);
799
0
    if (hCT == nullptr)
800
0
    {
801
0
        OSRDestroySpatialReference(hSRSGeog);
802
0
        return GDALPDFObjectNum();
803
0
    }
804
805
0
    std::vector<gdal::GCP> aGCPReprojected;
806
0
    bool bSuccess = true;
807
0
    for (const auto &gcp : aGCPs)
808
0
    {
809
0
        double X = gcp.X();
810
0
        double Y = gcp.Y();
811
0
        bSuccess &= OCTTransform(hCT, 1, &X, &Y, nullptr) == 1;
812
0
        aGCPReprojected.emplace_back(nullptr, nullptr, gcp.Pixel(), gcp.Line(),
813
0
                                     X, Y);
814
0
    }
815
0
    if (!bSuccess)
816
0
    {
817
0
        OSRDestroySpatialReference(hSRSGeog);
818
0
        OCTDestroyCoordinateTransformation(hCT);
819
820
0
        return GDALPDFObjectNum();
821
0
    }
822
823
0
    const char *pszAuthorityCode = OSRGetAuthorityCode(hSRS, nullptr);
824
0
    const char *pszAuthorityName = OSRGetAuthorityName(hSRS, nullptr);
825
0
    int nEPSGCode = 0;
826
0
    if (pszAuthorityName != nullptr && EQUAL(pszAuthorityName, "EPSG") &&
827
0
        pszAuthorityCode != nullptr)
828
0
        nEPSGCode = atoi(pszAuthorityCode);
829
830
0
    int bIsGeographic = OSRIsGeographic(hSRS);
831
832
0
    char *pszESRIWKT = nullptr;
833
0
    const char *apszOptions[] = {"FORMAT=WKT1_ESRI", nullptr};
834
0
    OSRExportToWktEx(hSRS, &pszESRIWKT, apszOptions);
835
836
0
    OSRDestroySpatialReference(hSRSGeog);
837
0
    OCTDestroyCoordinateTransformation(hCT);
838
839
0
    auto nViewportId = AllocNewObject();
840
0
    auto nMeasureId = AllocNewObject();
841
0
    auto nGCSId = AllocNewObject();
842
843
0
    StartObj(nViewportId);
844
0
    GDALPDFDictionaryRW oViewPortDict;
845
0
    oViewPortDict.Add("Type", GDALPDFObjectRW::CreateName("Viewport"))
846
0
        .Add("Name", "Layer")
847
0
        .Add("BBox", &((new GDALPDFArrayRW())
848
0
                           ->Add(bboxX1)
849
0
                           .Add(bboxY1)
850
0
                           .Add(bboxX2)
851
0
                           .Add(bboxY2)))
852
0
        .Add("Measure", nMeasureId, 0);
853
0
    VSIFPrintfL(m_fp, "%s\n", oViewPortDict.Serialize().c_str());
854
0
    EndObj();
855
856
0
    GDALPDFArrayRW *poGPTS = new GDALPDFArrayRW();
857
0
    GDALPDFArrayRW *poLPTS = new GDALPDFArrayRW();
858
859
0
    const int nPrecision =
860
0
        atoi(CPLGetConfigOption("PDF_COORD_DOUBLE_PRECISION", "16"));
861
0
    for (const auto &gcp : aGCPReprojected)
862
0
    {
863
0
        poGPTS->AddWithPrecision(gcp.Y(), nPrecision)
864
0
            .AddWithPrecision(gcp.X(), nPrecision);  // Lat, long order
865
0
        poLPTS
866
0
            ->AddWithPrecision((gcp.Pixel() - bboxX1) / (bboxX2 - bboxX1),
867
0
                               nPrecision)
868
0
            .AddWithPrecision((gcp.Line() - bboxY1) / (bboxY2 - bboxY1),
869
0
                              nPrecision);
870
0
    }
871
872
0
    StartObj(nMeasureId);
873
0
    GDALPDFDictionaryRW oMeasureDict;
874
0
    oMeasureDict.Add("Type", GDALPDFObjectRW::CreateName("Measure"))
875
0
        .Add("Subtype", GDALPDFObjectRW::CreateName("GEO"))
876
0
        .Add("GPTS", poGPTS)
877
0
        .Add("LPTS", poLPTS)
878
0
        .Add("GCS", nGCSId, 0);
879
0
    if (!aBoundingPolygon.empty())
880
0
    {
881
0
        GDALPDFArrayRW *poBounds = new GDALPDFArrayRW();
882
0
        for (const auto &xy : aBoundingPolygon)
883
0
        {
884
0
            poBounds->Add((xy.x - bboxX1) / (bboxX2 - bboxX1))
885
0
                .Add((xy.y - bboxY1) / (bboxY2 - bboxY1));
886
0
        }
887
0
        oMeasureDict.Add("Bounds", poBounds);
888
0
    }
889
0
    VSIFPrintfL(m_fp, "%s\n", oMeasureDict.Serialize().c_str());
890
0
    EndObj();
891
892
0
    StartObj(nGCSId);
893
0
    GDALPDFDictionaryRW oGCSDict;
894
0
    oGCSDict
895
0
        .Add("Type",
896
0
             GDALPDFObjectRW::CreateName(bIsGeographic ? "GEOGCS" : "PROJCS"))
897
0
        .Add("WKT", pszESRIWKT);
898
0
    if (nEPSGCode)
899
0
        oGCSDict.Add("EPSG", nEPSGCode);
900
0
    VSIFPrintfL(m_fp, "%s\n", oGCSDict.Serialize().c_str());
901
0
    EndObj();
902
903
0
    CPLFree(pszESRIWKT);
904
905
0
    return nViewportId;
906
0
}
907
908
/************************************************************************/
909
/*                            GeneratePage()                            */
910
/************************************************************************/
911
912
bool GDALPDFComposerWriter::GeneratePage(const CPLXMLNode *psPage)
913
0
{
914
0
    double dfWidthInUserUnit = CPLAtof(CPLGetXMLValue(psPage, "Width", "-1"));
915
0
    double dfHeightInUserUnit = CPLAtof(CPLGetXMLValue(psPage, "Height", "-1"));
916
0
    if (dfWidthInUserUnit <= 0 || dfWidthInUserUnit >= MAXIMUM_SIZE_IN_UNITS ||
917
0
        dfHeightInUserUnit <= 0 || dfHeightInUserUnit >= MAXIMUM_SIZE_IN_UNITS)
918
0
    {
919
0
        CPLError(CE_Failure, CPLE_AppDefined,
920
0
                 "Missing or invalid Width and/or Height");
921
0
        return false;
922
0
    }
923
0
    double dfUserUnit =
924
0
        CPLAtof(CPLGetXMLValue(psPage, "DPI", CPLSPrintf("%f", DEFAULT_DPI))) *
925
0
        USER_UNIT_IN_INCH;
926
927
0
    std::vector<GDALPDFObjectNum> anViewportIds;
928
929
0
    PageContext oPageContext;
930
0
    for (const auto *psIter = psPage->psChild; psIter; psIter = psIter->psNext)
931
0
    {
932
0
        if (psIter->eType == CXT_Element &&
933
0
            strcmp(psIter->pszValue, "Georeferencing") == 0)
934
0
        {
935
0
            GDALPDFObjectNum nViewportId;
936
0
            Georeferencing georeferencing;
937
0
            if (!GenerateGeoreferencing(psIter, dfWidthInUserUnit,
938
0
                                        dfHeightInUserUnit, nViewportId,
939
0
                                        georeferencing))
940
0
            {
941
0
                return false;
942
0
            }
943
0
            if (nViewportId.toBool())
944
0
                anViewportIds.emplace_back(nViewportId);
945
0
            if (!georeferencing.m_osID.empty())
946
0
            {
947
0
                oPageContext.m_oMapGeoreferencedId[georeferencing.m_osID] =
948
0
                    georeferencing;
949
0
            }
950
0
        }
951
0
    }
952
953
0
    auto nPageId = AllocNewObject();
954
0
    m_asPageId.push_back(nPageId);
955
956
0
    const char *pszId = CPLGetXMLValue(psPage, "id", nullptr);
957
0
    if (pszId)
958
0
    {
959
0
        if (m_oMapPageIdToObjectNum.find(pszId) !=
960
0
            m_oMapPageIdToObjectNum.end())
961
0
        {
962
0
            CPLError(CE_Failure, CPLE_AppDefined, "Duplicated page id %s",
963
0
                     pszId);
964
0
            return false;
965
0
        }
966
0
        m_oMapPageIdToObjectNum[pszId] = nPageId;
967
0
    }
968
969
0
    const auto psContent = CPLGetXMLNode(psPage, "Content");
970
0
    if (!psContent)
971
0
    {
972
0
        CPLError(CE_Failure, CPLE_AppDefined, "Missing Content");
973
0
        return false;
974
0
    }
975
976
0
    const bool bDeflateStreamCompression = EQUAL(
977
0
        CPLGetXMLValue(psContent, "streamCompression", "DEFLATE"), "DEFLATE");
978
979
0
    oPageContext.m_dfWidthInUserUnit = dfWidthInUserUnit;
980
0
    oPageContext.m_dfHeightInUserUnit = dfHeightInUserUnit;
981
0
    oPageContext.m_eStreamCompressMethod =
982
0
        bDeflateStreamCompression ? COMPRESS_DEFLATE : COMPRESS_NONE;
983
0
    if (!ExploreContent(psContent, oPageContext))
984
0
        return false;
985
986
0
    int nStructParentsIdx = -1;
987
0
    if (!oPageContext.m_anFeatureUserProperties.empty())
988
0
    {
989
0
        nStructParentsIdx = static_cast<int>(m_anParentElements.size());
990
0
        auto nParentsElements = AllocNewObject();
991
0
        m_anParentElements.push_back(nParentsElements);
992
0
        {
993
0
            StartObj(nParentsElements);
994
0
            VSIFPrintfL(m_fp, "[ ");
995
0
            for (const auto &num : oPageContext.m_anFeatureUserProperties)
996
0
                VSIFPrintfL(m_fp, "%d 0 R ", num.toInt());
997
0
            VSIFPrintfL(m_fp, " ]\n");
998
0
            EndObj();
999
0
        }
1000
0
    }
1001
1002
0
    GDALPDFObjectNum nAnnotsId;
1003
0
    if (!oPageContext.m_anAnnotationsId.empty())
1004
0
    {
1005
        /* -------------------------------------------------------------- */
1006
        /*  Write annotation arrays.                                      */
1007
        /* -------------------------------------------------------------- */
1008
0
        nAnnotsId = AllocNewObject();
1009
0
        StartObj(nAnnotsId);
1010
0
        {
1011
0
            GDALPDFArrayRW oArray;
1012
0
            for (size_t i = 0; i < oPageContext.m_anAnnotationsId.size(); i++)
1013
0
            {
1014
0
                oArray.Add(oPageContext.m_anAnnotationsId[i], 0);
1015
0
            }
1016
0
            VSIFPrintfL(m_fp, "%s\n", oArray.Serialize().c_str());
1017
0
        }
1018
0
        EndObj();
1019
0
    }
1020
1021
0
    auto nContentId = AllocNewObject();
1022
0
    auto nResourcesId = AllocNewObject();
1023
1024
0
    StartObj(nPageId);
1025
0
    GDALPDFDictionaryRW oDictPage;
1026
0
    oDictPage.Add("Type", GDALPDFObjectRW::CreateName("Page"))
1027
0
        .Add("Parent", m_nPageResourceId, 0)
1028
0
        .Add("MediaBox", &((new GDALPDFArrayRW())
1029
0
                               ->Add(0)
1030
0
                               .Add(0)
1031
0
                               .Add(dfWidthInUserUnit)
1032
0
                               .Add(dfHeightInUserUnit)))
1033
0
        .Add("UserUnit", dfUserUnit)
1034
0
        .Add("Contents", nContentId, 0)
1035
0
        .Add("Resources", nResourcesId, 0);
1036
1037
0
    if (nAnnotsId.toBool())
1038
0
        oDictPage.Add("Annots", nAnnotsId, 0);
1039
1040
0
    oDictPage.Add("Group",
1041
0
                  &((new GDALPDFDictionaryRW())
1042
0
                        ->Add("Type", GDALPDFObjectRW::CreateName("Group"))
1043
0
                        .Add("S", GDALPDFObjectRW::CreateName("Transparency"))
1044
0
                        .Add("CS", GDALPDFObjectRW::CreateName("DeviceRGB"))));
1045
0
    if (!anViewportIds.empty())
1046
0
    {
1047
0
        auto poViewports = new GDALPDFArrayRW();
1048
0
        for (const auto &id : anViewportIds)
1049
0
            poViewports->Add(id, 0);
1050
0
        oDictPage.Add("VP", poViewports);
1051
0
    }
1052
1053
0
    if (nStructParentsIdx >= 0)
1054
0
    {
1055
0
        oDictPage.Add("StructParents", nStructParentsIdx);
1056
0
    }
1057
1058
0
    VSIFPrintfL(m_fp, "%s\n", oDictPage.Serialize().c_str());
1059
0
    EndObj();
1060
1061
    /* -------------------------------------------------------------- */
1062
    /*  Write content dictionary                                      */
1063
    /* -------------------------------------------------------------- */
1064
0
    {
1065
0
        GDALPDFDictionaryRW oDict;
1066
0
        StartObjWithStream(nContentId, oDict, bDeflateStreamCompression);
1067
0
        VSIFPrintfL(m_fp, "%s", oPageContext.m_osDrawingStream.c_str());
1068
0
        EndObjWithStream();
1069
0
    }
1070
1071
    /* -------------------------------------------------------------- */
1072
    /*  Write page resource dictionary.                               */
1073
    /* -------------------------------------------------------------- */
1074
0
    StartObj(nResourcesId);
1075
0
    {
1076
0
        GDALPDFDictionaryRW oDict;
1077
0
        if (!oPageContext.m_oXObjects.empty())
1078
0
        {
1079
0
            GDALPDFDictionaryRW *poDict = new GDALPDFDictionaryRW();
1080
0
            for (const auto &kv : oPageContext.m_oXObjects)
1081
0
            {
1082
0
                poDict->Add(kv.first, kv.second, 0);
1083
0
            }
1084
0
            oDict.Add("XObject", poDict);
1085
0
        }
1086
1087
0
        if (!oPageContext.m_oProperties.empty())
1088
0
        {
1089
0
            GDALPDFDictionaryRW *poDict = new GDALPDFDictionaryRW();
1090
0
            for (const auto &kv : oPageContext.m_oProperties)
1091
0
            {
1092
0
                poDict->Add(kv.first, kv.second, 0);
1093
0
            }
1094
0
            oDict.Add("Properties", poDict);
1095
0
        }
1096
1097
0
        if (!oPageContext.m_oExtGState.empty())
1098
0
        {
1099
0
            GDALPDFDictionaryRW *poDict = new GDALPDFDictionaryRW();
1100
0
            for (const auto &kv : oPageContext.m_oExtGState)
1101
0
            {
1102
0
                poDict->Add(kv.first, kv.second, 0);
1103
0
            }
1104
0
            oDict.Add("ExtGState", poDict);
1105
0
        }
1106
1107
0
        VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
1108
0
    }
1109
0
    EndObj();
1110
1111
0
    return true;
1112
0
}
1113
1114
/************************************************************************/
1115
/*                           ExploreContent()                           */
1116
/************************************************************************/
1117
1118
bool GDALPDFComposerWriter::ExploreContent(const CPLXMLNode *psNode,
1119
                                           PageContext &oPageContext)
1120
0
{
1121
0
    for (const auto *psIter = psNode->psChild; psIter; psIter = psIter->psNext)
1122
0
    {
1123
0
        if (psIter->eType == CXT_Element &&
1124
0
            strcmp(psIter->pszValue, "IfLayerOn") == 0)
1125
0
        {
1126
0
            const char *pszLayerId = CPLGetXMLValue(psIter, "layerId", nullptr);
1127
0
            if (!pszLayerId)
1128
0
            {
1129
0
                CPLError(CE_Failure, CPLE_AppDefined, "Missing layerId");
1130
0
                return false;
1131
0
            }
1132
0
            auto oIter = m_oMapLayerIdToOCG.find(pszLayerId);
1133
0
            if (oIter == m_oMapLayerIdToOCG.end())
1134
0
            {
1135
0
                CPLError(CE_Failure, CPLE_AppDefined,
1136
0
                         "Referencing layer of unknown id: %s", pszLayerId);
1137
0
                return false;
1138
0
            }
1139
0
            oPageContext
1140
0
                .m_oProperties[CPLOPrintf("Lyr%d", oIter->second.toInt())] =
1141
0
                oIter->second;
1142
0
            oPageContext.m_osDrawingStream +=
1143
0
                CPLOPrintf("/OC /Lyr%d BDC\n", oIter->second.toInt());
1144
0
            if (!ExploreContent(psIter, oPageContext))
1145
0
                return false;
1146
0
            oPageContext.m_osDrawingStream += "EMC\n";
1147
0
        }
1148
1149
0
        else if (psIter->eType == CXT_Element &&
1150
0
                 strcmp(psIter->pszValue, "Raster") == 0)
1151
0
        {
1152
0
            if (!WriteRaster(psIter, oPageContext))
1153
0
                return false;
1154
0
        }
1155
1156
0
        else if (psIter->eType == CXT_Element &&
1157
0
                 strcmp(psIter->pszValue, "Vector") == 0)
1158
0
        {
1159
0
            if (!WriteVector(psIter, oPageContext))
1160
0
                return false;
1161
0
        }
1162
1163
0
        else if (psIter->eType == CXT_Element &&
1164
0
                 strcmp(psIter->pszValue, "VectorLabel") == 0)
1165
0
        {
1166
0
            if (!WriteVectorLabel(psIter, oPageContext))
1167
0
                return false;
1168
0
        }
1169
1170
0
        else if (psIter->eType == CXT_Element &&
1171
0
                 strcmp(psIter->pszValue, "PDF") == 0)
1172
0
        {
1173
0
#ifdef HAVE_PDF_READ_SUPPORT
1174
0
            if (!WritePDF(psIter, oPageContext))
1175
0
                return false;
1176
#else
1177
            CPLError(CE_Failure, CPLE_NotSupported,
1178
                     "PDF node not supported due to missing PDF read support "
1179
                     "in this GDAL build");
1180
            return false;
1181
#endif
1182
0
        }
1183
0
    }
1184
0
    return true;
1185
0
}
1186
1187
/************************************************************************/
1188
/*                           StartBlending()                            */
1189
/************************************************************************/
1190
1191
void GDALPDFComposerWriter::StartBlending(const CPLXMLNode *psNode,
1192
                                          PageContext &oPageContext,
1193
                                          double &dfOpacity)
1194
0
{
1195
0
    dfOpacity = 1;
1196
0
    const auto psBlending = CPLGetXMLNode(psNode, "Blending");
1197
0
    if (psBlending)
1198
0
    {
1199
0
        auto nExtGState = AllocNewObject();
1200
0
        StartObj(nExtGState);
1201
0
        {
1202
0
            GDALPDFDictionaryRW gs;
1203
0
            gs.Add("Type", GDALPDFObjectRW::CreateName("ExtGState"));
1204
0
            dfOpacity = CPLAtof(CPLGetXMLValue(psBlending, "opacity", "1"));
1205
0
            gs.Add("ca", dfOpacity);
1206
0
            gs.Add("BM", GDALPDFObjectRW::CreateName(
1207
0
                             CPLGetXMLValue(psBlending, "function", "Normal")));
1208
0
            VSIFPrintfL(m_fp, "%s\n", gs.Serialize().c_str());
1209
0
        }
1210
0
        EndObj();
1211
0
        oPageContext.m_oExtGState[CPLOPrintf("GS%d", nExtGState.toInt())] =
1212
0
            nExtGState;
1213
0
        oPageContext.m_osDrawingStream += "q\n";
1214
0
        oPageContext.m_osDrawingStream +=
1215
0
            CPLOPrintf("/GS%d gs\n", nExtGState.toInt());
1216
0
    }
1217
0
}
1218
1219
/************************************************************************/
1220
/*                            EndBlending()                             */
1221
/************************************************************************/
1222
1223
void GDALPDFComposerWriter::EndBlending(const CPLXMLNode *psNode,
1224
                                        PageContext &oPageContext)
1225
0
{
1226
0
    const auto psBlending = CPLGetXMLNode(psNode, "Blending");
1227
0
    if (psBlending)
1228
0
    {
1229
0
        oPageContext.m_osDrawingStream += "Q\n";
1230
0
    }
1231
0
}
1232
1233
/************************************************************************/
1234
/*                            WriteRaster()                             */
1235
/************************************************************************/
1236
1237
bool GDALPDFComposerWriter::WriteRaster(const CPLXMLNode *psNode,
1238
                                        PageContext &oPageContext)
1239
0
{
1240
0
    const char *pszDataset = CPLGetXMLValue(psNode, "dataset", nullptr);
1241
0
    if (!pszDataset)
1242
0
    {
1243
0
        CPLError(CE_Failure, CPLE_AppDefined, "Missing dataset");
1244
0
        return false;
1245
0
    }
1246
0
    double dfX1 = CPLAtof(CPLGetXMLValue(psNode, "x1", "0"));
1247
0
    double dfY1 = CPLAtof(CPLGetXMLValue(psNode, "y1", "0"));
1248
0
    double dfX2 = CPLAtof(CPLGetXMLValue(
1249
0
        psNode, "x2", CPLSPrintf("%.17g", oPageContext.m_dfWidthInUserUnit)));
1250
0
    double dfY2 = CPLAtof(CPLGetXMLValue(
1251
0
        psNode, "y2", CPLSPrintf("%.17g", oPageContext.m_dfHeightInUserUnit)));
1252
0
    if (dfX2 <= dfX1 || dfY2 <= dfY1)
1253
0
    {
1254
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid x1,y1,x2,y2");
1255
0
        return false;
1256
0
    }
1257
0
    GDALDatasetUniquePtr poDS(
1258
0
        GDALDataset::Open(pszDataset, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
1259
0
                          nullptr, nullptr, nullptr));
1260
0
    if (!poDS)
1261
0
        return false;
1262
0
    const int nWidth = poDS->GetRasterXSize();
1263
0
    const int nHeight = poDS->GetRasterYSize();
1264
0
    const int nBlockXSize =
1265
0
        std::max(16, atoi(CPLGetXMLValue(psNode, "tileSize", "256")));
1266
0
    const int nBlockYSize = nBlockXSize;
1267
0
    const char *pszCompressMethod =
1268
0
        CPLGetXMLValue(psNode, "Compression.method", "DEFLATE");
1269
0
    PDFCompressMethod eCompressMethod = COMPRESS_DEFLATE;
1270
0
    if (EQUAL(pszCompressMethod, "JPEG"))
1271
0
        eCompressMethod = COMPRESS_JPEG;
1272
0
    else if (EQUAL(pszCompressMethod, "JPEG2000"))
1273
0
        eCompressMethod = COMPRESS_JPEG2000;
1274
0
    const int nPredictor =
1275
0
        CPLTestBool(CPLGetXMLValue(psNode, "Compression.predictor", "false"))
1276
0
            ? 2
1277
0
            : 0;
1278
0
    const int nJPEGQuality =
1279
0
        atoi(CPLGetXMLValue(psNode, "Compression.quality", "-1"));
1280
0
    const char *pszJPEG2000_DRIVER =
1281
0
        m_osJPEG2000Driver.empty() ? nullptr : m_osJPEG2000Driver.c_str();
1282
0
    ;
1283
1284
0
    const char *pszGeoreferencingId =
1285
0
        CPLGetXMLValue(psNode, "georeferencingId", nullptr);
1286
0
    double dfClippingMinX = 0;
1287
0
    double dfClippingMinY = 0;
1288
0
    double dfClippingMaxX = 0;
1289
0
    double dfClippingMaxY = 0;
1290
0
    bool bClip = false;
1291
0
    GDALGeoTransform rasterGT;
1292
0
    GDALGeoTransform invGT;  // from georeferenced to PDF coordinates
1293
0
    if (pszGeoreferencingId)
1294
0
    {
1295
0
        auto iter =
1296
0
            oPageContext.m_oMapGeoreferencedId.find(pszGeoreferencingId);
1297
0
        if (iter == oPageContext.m_oMapGeoreferencedId.end())
1298
0
        {
1299
0
            CPLError(CE_Failure, CPLE_AppDefined,
1300
0
                     "Cannot find georeferencing of id %s",
1301
0
                     pszGeoreferencingId);
1302
0
            return false;
1303
0
        }
1304
0
        const auto &georeferencing = iter->second;
1305
0
        dfX1 = georeferencing.m_bboxX1;
1306
0
        dfY1 = georeferencing.m_bboxY1;
1307
0
        dfX2 = georeferencing.m_bboxX2;
1308
0
        dfY2 = georeferencing.m_bboxY2;
1309
1310
0
        bClip = true;
1311
0
        dfClippingMinX = APPLY_GT_X(georeferencing.m_gt, dfX1, dfY1);
1312
0
        dfClippingMinY = APPLY_GT_Y(georeferencing.m_gt, dfX1, dfY1);
1313
0
        dfClippingMaxX = APPLY_GT_X(georeferencing.m_gt, dfX2, dfY2);
1314
0
        dfClippingMaxY = APPLY_GT_Y(georeferencing.m_gt, dfX2, dfY2);
1315
1316
0
        if (poDS->GetGeoTransform(rasterGT) != CE_None || rasterGT[2] != 0 ||
1317
0
            rasterGT[4] != 0 || rasterGT[5] > 0)
1318
0
        {
1319
0
            CPLError(CE_Failure, CPLE_AppDefined,
1320
0
                     "Raster has no geotransform or a rotated geotransform");
1321
0
            return false;
1322
0
        }
1323
1324
0
        auto poSRS = poDS->GetSpatialRef();
1325
0
        if (!poSRS || !poSRS->IsSame(&georeferencing.m_oSRS))
1326
0
        {
1327
0
            CPLError(CE_Failure, CPLE_AppDefined,
1328
0
                     "Raster has no projection, or different from the one "
1329
0
                     "of the georeferencing area");
1330
0
            return false;
1331
0
        }
1332
1333
0
        CPL_IGNORE_RET_VAL(georeferencing.m_gt.GetInverse(invGT));
1334
0
    }
1335
0
    const double dfRasterMinX = rasterGT[0];
1336
0
    const double dfRasterMaxY = rasterGT[3];
1337
1338
    /* Does the source image has a color table ? */
1339
0
    const auto nColorTableId = WriteColorTable(poDS.get());
1340
1341
0
    double dfIgnoredOpacity;
1342
0
    StartBlending(psNode, oPageContext, dfIgnoredOpacity);
1343
1344
0
    CPLString osGroupStream;
1345
0
    std::vector<GDALPDFObjectNum> anImageIds;
1346
1347
0
    const int nXBlocks = DIV_ROUND_UP(nWidth, nBlockXSize);
1348
0
    const int nYBlocks = DIV_ROUND_UP(nHeight, nBlockYSize);
1349
0
    int nBlockXOff, nBlockYOff;
1350
0
    for (nBlockYOff = 0; nBlockYOff < nYBlocks; nBlockYOff++)
1351
0
    {
1352
0
        for (nBlockXOff = 0; nBlockXOff < nXBlocks; nBlockXOff++)
1353
0
        {
1354
0
            int nReqWidth =
1355
0
                std::min(nBlockXSize, nWidth - nBlockXOff * nBlockXSize);
1356
0
            int nReqHeight =
1357
0
                std::min(nBlockYSize, nHeight - nBlockYOff * nBlockYSize);
1358
1359
0
            int nX = nBlockXOff * nBlockXSize;
1360
0
            int nY = nBlockYOff * nBlockYSize;
1361
1362
0
            double dfXPDFOff = nX * (dfX2 - dfX1) / nWidth + dfX1;
1363
0
            double dfYPDFOff =
1364
0
                (nHeight - nY - nReqHeight) * (dfY2 - dfY1) / nHeight + dfY1;
1365
0
            double dfXPDFSize = nReqWidth * (dfX2 - dfX1) / nWidth;
1366
0
            double dfYPDFSize = nReqHeight * (dfY2 - dfY1) / nHeight;
1367
1368
0
            if (bClip)
1369
0
            {
1370
                /* Compute extent of block to write */
1371
0
                double dfBlockMinX = rasterGT[0] + nX * rasterGT[1];
1372
0
                double dfBlockMaxX =
1373
0
                    rasterGT[0] + (nX + nReqWidth) * rasterGT[1];
1374
0
                double dfBlockMinY =
1375
0
                    rasterGT[3] + (nY + nReqHeight) * rasterGT[5];
1376
0
                double dfBlockMaxY = rasterGT[3] + nY * rasterGT[5];
1377
1378
                // Clip the extent of the block with the extent of the main
1379
                // raster.
1380
0
                const double dfIntersectMinX =
1381
0
                    std::max(dfBlockMinX, dfClippingMinX);
1382
0
                const double dfIntersectMinY =
1383
0
                    std::max(dfBlockMinY, dfClippingMinY);
1384
0
                const double dfIntersectMaxX =
1385
0
                    std::min(dfBlockMaxX, dfClippingMaxX);
1386
0
                const double dfIntersectMaxY =
1387
0
                    std::min(dfBlockMaxY, dfClippingMaxY);
1388
1389
0
                bool bOK = false;
1390
0
                if (dfIntersectMinX < dfIntersectMaxX &&
1391
0
                    dfIntersectMinY < dfIntersectMaxY)
1392
0
                {
1393
                    /* Re-compute (x,y,width,height) subwindow of current raster
1394
                     * from */
1395
                    /* the extent of the clipped block */
1396
0
                    nX = static_cast<int>(
1397
0
                        (dfIntersectMinX - dfRasterMinX) / rasterGT[1] + 0.5);
1398
0
                    nY = static_cast<int>((dfRasterMaxY - dfIntersectMaxY) /
1399
0
                                              (-rasterGT[5]) +
1400
0
                                          0.5);
1401
0
                    nReqWidth =
1402
0
                        static_cast<int>((dfIntersectMaxX - dfRasterMinX) /
1403
0
                                             rasterGT[1] +
1404
0
                                         0.5) -
1405
0
                        nX;
1406
0
                    nReqHeight =
1407
0
                        static_cast<int>((dfRasterMaxY - dfIntersectMinY) /
1408
0
                                             (-rasterGT[5]) +
1409
0
                                         0.5) -
1410
0
                        nY;
1411
1412
0
                    if (nReqWidth > 0 && nReqHeight > 0)
1413
0
                    {
1414
0
                        dfBlockMinX = rasterGT[0] + nX * rasterGT[1];
1415
0
                        dfBlockMaxX =
1416
0
                            rasterGT[0] + (nX + nReqWidth) * rasterGT[1];
1417
0
                        dfBlockMinY =
1418
0
                            rasterGT[3] + (nY + nReqHeight) * rasterGT[5];
1419
0
                        dfBlockMaxY = rasterGT[3] + nY * rasterGT[5];
1420
1421
0
                        double dfPDFX1 =
1422
0
                            APPLY_GT_X(invGT, dfBlockMinX, dfBlockMinY);
1423
0
                        double dfPDFY1 =
1424
0
                            APPLY_GT_Y(invGT, dfBlockMinX, dfBlockMinY);
1425
0
                        double dfPDFX2 =
1426
0
                            APPLY_GT_X(invGT, dfBlockMaxX, dfBlockMaxY);
1427
0
                        double dfPDFY2 =
1428
0
                            APPLY_GT_Y(invGT, dfBlockMaxX, dfBlockMaxY);
1429
1430
0
                        dfXPDFOff = dfPDFX1;
1431
0
                        dfYPDFOff = dfPDFY1;
1432
0
                        dfXPDFSize = dfPDFX2 - dfPDFX1;
1433
0
                        dfYPDFSize = dfPDFY2 - dfPDFY1;
1434
0
                        bOK = true;
1435
0
                    }
1436
0
                }
1437
0
                if (!bOK)
1438
0
                {
1439
0
                    continue;
1440
0
                }
1441
0
            }
1442
1443
0
            const auto nImageId =
1444
0
                WriteBlock(poDS.get(), nX, nY, nReqWidth, nReqHeight,
1445
0
                           nColorTableId, eCompressMethod, nPredictor,
1446
0
                           nJPEGQuality, pszJPEG2000_DRIVER, nullptr, nullptr);
1447
1448
0
            if (!nImageId.toBool())
1449
0
                return false;
1450
1451
0
            anImageIds.push_back(nImageId);
1452
0
            osGroupStream += "q\n";
1453
0
            GDALPDFObjectRW *poXSize = GDALPDFObjectRW::CreateReal(dfXPDFSize);
1454
0
            GDALPDFObjectRW *poYSize = GDALPDFObjectRW::CreateReal(dfYPDFSize);
1455
0
            GDALPDFObjectRW *poXOff = GDALPDFObjectRW::CreateReal(dfXPDFOff);
1456
0
            GDALPDFObjectRW *poYOff = GDALPDFObjectRW::CreateReal(dfYPDFOff);
1457
0
            osGroupStream += CPLOPrintf(
1458
0
                "%s 0 0 %s %s %s cm\n", poXSize->Serialize().c_str(),
1459
0
                poYSize->Serialize().c_str(), poXOff->Serialize().c_str(),
1460
0
                poYOff->Serialize().c_str());
1461
0
            delete poXSize;
1462
0
            delete poYSize;
1463
0
            delete poXOff;
1464
0
            delete poYOff;
1465
0
            osGroupStream += CPLOPrintf("/Image%d Do\n", nImageId.toInt());
1466
0
            osGroupStream += "Q\n";
1467
0
        }
1468
0
    }
1469
1470
0
    if (anImageIds.size() <= 1 || CPLGetXMLNode(psNode, "Blending") == nullptr)
1471
0
    {
1472
0
        for (const auto &nImageId : anImageIds)
1473
0
        {
1474
0
            oPageContext.m_oXObjects[CPLOPrintf("Image%d", nImageId.toInt())] =
1475
0
                nImageId;
1476
0
        }
1477
0
        oPageContext.m_osDrawingStream += osGroupStream;
1478
0
    }
1479
0
    else
1480
0
    {
1481
        // In case several tiles are drawn with blending, use a transparency
1482
        // group to avoid edge effects.
1483
1484
0
        auto nGroupId = AllocNewObject();
1485
0
        GDALPDFDictionaryRW oDictGroup;
1486
0
        GDALPDFDictionaryRW *poGroup = new GDALPDFDictionaryRW();
1487
0
        poGroup->Add("Type", GDALPDFObjectRW::CreateName("Group"))
1488
0
            .Add("S", GDALPDFObjectRW::CreateName("Transparency"));
1489
1490
0
        GDALPDFDictionaryRW *poXObjects = new GDALPDFDictionaryRW();
1491
0
        for (const auto &nImageId : anImageIds)
1492
0
        {
1493
0
            poXObjects->Add(CPLOPrintf("Image%d", nImageId.toInt()), nImageId,
1494
0
                            0);
1495
0
        }
1496
0
        GDALPDFDictionaryRW *poResources = new GDALPDFDictionaryRW();
1497
0
        poResources->Add("XObject", poXObjects);
1498
1499
0
        oDictGroup.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
1500
0
            .Add("BBox", &((new GDALPDFArrayRW())->Add(0).Add(0))
1501
0
                              .Add(oPageContext.m_dfWidthInUserUnit)
1502
0
                              .Add(oPageContext.m_dfHeightInUserUnit))
1503
0
            .Add("Subtype", GDALPDFObjectRW::CreateName("Form"))
1504
0
            .Add("Group", poGroup)
1505
0
            .Add("Resources", poResources);
1506
1507
0
        StartObjWithStream(nGroupId, oDictGroup,
1508
0
                           oPageContext.m_eStreamCompressMethod !=
1509
0
                               COMPRESS_NONE);
1510
0
        VSIFPrintfL(m_fp, "%s", osGroupStream.c_str());
1511
0
        EndObjWithStream();
1512
1513
0
        oPageContext.m_oXObjects[CPLOPrintf("Group%d", nGroupId.toInt())] =
1514
0
            nGroupId;
1515
0
        oPageContext.m_osDrawingStream +=
1516
0
            CPLOPrintf("/Group%d Do\n", nGroupId.toInt());
1517
0
    }
1518
1519
0
    EndBlending(psNode, oPageContext);
1520
1521
0
    return true;
1522
0
}
1523
1524
/************************************************************************/
1525
/*                     SetupVectorGeoreferencing()                      */
1526
/************************************************************************/
1527
1528
bool GDALPDFComposerWriter::SetupVectorGeoreferencing(
1529
    const char *pszGeoreferencingId, OGRLayer *poLayer,
1530
    const PageContext &oPageContext, double &dfClippingMinX,
1531
    double &dfClippingMinY, double &dfClippingMaxX, double &dfClippingMaxY,
1532
    double adfMatrix[4], std::unique_ptr<OGRCoordinateTransformation> &poCT)
1533
0
{
1534
0
    CPLAssert(pszGeoreferencingId);
1535
1536
0
    auto iter = oPageContext.m_oMapGeoreferencedId.find(pszGeoreferencingId);
1537
0
    if (iter == oPageContext.m_oMapGeoreferencedId.end())
1538
0
    {
1539
0
        CPLError(CE_Failure, CPLE_AppDefined,
1540
0
                 "Cannot find georeferencing of id %s", pszGeoreferencingId);
1541
0
        return false;
1542
0
    }
1543
0
    const auto &georeferencing = iter->second;
1544
0
    const double dfX1 = georeferencing.m_bboxX1;
1545
0
    const double dfY1 = georeferencing.m_bboxY1;
1546
0
    const double dfX2 = georeferencing.m_bboxX2;
1547
0
    const double dfY2 = georeferencing.m_bboxY2;
1548
1549
0
    dfClippingMinX = APPLY_GT_X(georeferencing.m_gt, dfX1, dfY1);
1550
0
    dfClippingMinY = APPLY_GT_Y(georeferencing.m_gt, dfX1, dfY1);
1551
0
    dfClippingMaxX = APPLY_GT_X(georeferencing.m_gt, dfX2, dfY2);
1552
0
    dfClippingMaxY = APPLY_GT_Y(georeferencing.m_gt, dfX2, dfY2);
1553
1554
0
    auto poSRS = poLayer->GetSpatialRef();
1555
0
    if (!poSRS)
1556
0
    {
1557
0
        CPLError(CE_Failure, CPLE_AppDefined, "Layer has no SRS");
1558
0
        return false;
1559
0
    }
1560
0
    if (!poSRS->IsSame(&georeferencing.m_oSRS))
1561
0
    {
1562
0
        poCT.reset(
1563
0
            OGRCreateCoordinateTransformation(poSRS, &georeferencing.m_oSRS));
1564
0
    }
1565
1566
0
    if (!poCT)
1567
0
    {
1568
0
        poLayer->SetSpatialFilterRect(dfClippingMinX, dfClippingMinY,
1569
0
                                      dfClippingMaxX, dfClippingMaxY);
1570
0
    }
1571
1572
0
    GDALGeoTransform invGT;  // from georeferenced to PDF coordinates
1573
0
    CPL_IGNORE_RET_VAL(georeferencing.m_gt.GetInverse(invGT));
1574
0
    adfMatrix[0] = invGT[0];
1575
0
    adfMatrix[1] = invGT[1];
1576
0
    adfMatrix[2] = invGT[3];
1577
0
    adfMatrix[3] = invGT[5];
1578
1579
0
    return true;
1580
0
}
1581
1582
/************************************************************************/
1583
/*                            WriteVector()                             */
1584
/************************************************************************/
1585
1586
bool GDALPDFComposerWriter::WriteVector(const CPLXMLNode *psNode,
1587
                                        PageContext &oPageContext)
1588
0
{
1589
0
    const char *pszDataset = CPLGetXMLValue(psNode, "dataset", nullptr);
1590
0
    if (!pszDataset)
1591
0
    {
1592
0
        CPLError(CE_Failure, CPLE_AppDefined, "Missing dataset");
1593
0
        return false;
1594
0
    }
1595
0
    const char *pszLayer = CPLGetXMLValue(psNode, "layer", nullptr);
1596
0
    if (!pszLayer)
1597
0
    {
1598
0
        CPLError(CE_Failure, CPLE_AppDefined, "Missing layer");
1599
0
        return false;
1600
0
    }
1601
1602
0
    GDALDatasetUniquePtr poDS(
1603
0
        GDALDataset::Open(pszDataset, GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR,
1604
0
                          nullptr, nullptr, nullptr));
1605
0
    if (!poDS)
1606
0
        return false;
1607
0
    OGRLayer *poLayer = poDS->GetLayerByName(pszLayer);
1608
0
    if (!poLayer)
1609
0
    {
1610
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find layer %s", pszLayer);
1611
0
        return false;
1612
0
    }
1613
0
    const bool bVisible =
1614
0
        CPLTestBool(CPLGetXMLValue(psNode, "visible", "true"));
1615
1616
0
    const auto psLogicalStructure = CPLGetXMLNode(psNode, "LogicalStructure");
1617
0
    const char *pszOGRDisplayField = nullptr;
1618
0
    std::vector<CPLString> aosIncludedFields;
1619
0
    const bool bLogicalStructure = psLogicalStructure != nullptr;
1620
0
    if (psLogicalStructure)
1621
0
    {
1622
0
        pszOGRDisplayField =
1623
0
            CPLGetXMLValue(psLogicalStructure, "fieldToDisplay", nullptr);
1624
0
        if (CPLGetXMLNode(psLogicalStructure, "ExcludeAllFields") != nullptr ||
1625
0
            CPLGetXMLNode(psLogicalStructure, "IncludeField") != nullptr)
1626
0
        {
1627
0
            for (const auto *psIter = psLogicalStructure->psChild; psIter;
1628
0
                 psIter = psIter->psNext)
1629
0
            {
1630
0
                if (psIter->eType == CXT_Element &&
1631
0
                    strcmp(psIter->pszValue, "IncludeField") == 0)
1632
0
                {
1633
0
                    aosIncludedFields.push_back(
1634
0
                        CPLGetXMLValue(psIter, nullptr, ""));
1635
0
                }
1636
0
            }
1637
0
        }
1638
0
        else
1639
0
        {
1640
0
            std::set<CPLString> oSetExcludedFields;
1641
0
            for (const auto *psIter = psLogicalStructure->psChild; psIter;
1642
0
                 psIter = psIter->psNext)
1643
0
            {
1644
0
                if (psIter->eType == CXT_Element &&
1645
0
                    strcmp(psIter->pszValue, "ExcludeField") == 0)
1646
0
                {
1647
0
                    oSetExcludedFields.insert(
1648
0
                        CPLGetXMLValue(psIter, nullptr, ""));
1649
0
                }
1650
0
            }
1651
0
            const auto poLayerDefn = poLayer->GetLayerDefn();
1652
0
            for (int i = 0; i < poLayerDefn->GetFieldCount(); i++)
1653
0
            {
1654
0
                const auto poFieldDefn = poLayerDefn->GetFieldDefn(i);
1655
0
                const char *pszName = poFieldDefn->GetNameRef();
1656
0
                if (oSetExcludedFields.find(pszName) ==
1657
0
                    oSetExcludedFields.end())
1658
0
                {
1659
0
                    aosIncludedFields.push_back(pszName);
1660
0
                }
1661
0
            }
1662
0
        }
1663
0
    }
1664
0
    const char *pszStyleString =
1665
0
        CPLGetXMLValue(psNode, "ogrStyleString", nullptr);
1666
0
    const char *pszOGRLinkField =
1667
0
        CPLGetXMLValue(psNode, "linkAttribute", nullptr);
1668
1669
0
    const char *pszGeoreferencingId =
1670
0
        CPLGetXMLValue(psNode, "georeferencingId", nullptr);
1671
0
    std::unique_ptr<OGRCoordinateTransformation> poCT;
1672
0
    double dfClippingMinX = 0;
1673
0
    double dfClippingMinY = 0;
1674
0
    double dfClippingMaxX = 0;
1675
0
    double dfClippingMaxY = 0;
1676
0
    double adfMatrix[4] = {0, 1, 0, 1};
1677
0
    if (pszGeoreferencingId &&
1678
0
        !SetupVectorGeoreferencing(
1679
0
            pszGeoreferencingId, poLayer, oPageContext, dfClippingMinX,
1680
0
            dfClippingMinY, dfClippingMaxX, dfClippingMaxY, adfMatrix, poCT))
1681
0
    {
1682
0
        return false;
1683
0
    }
1684
1685
0
    double dfOpacityFactor = 1.0;
1686
0
    if (!bVisible)
1687
0
    {
1688
0
        if (oPageContext.m_oExtGState.find("GSinvisible") ==
1689
0
            oPageContext.m_oExtGState.end())
1690
0
        {
1691
0
            auto nExtGState = AllocNewObject();
1692
0
            StartObj(nExtGState);
1693
0
            {
1694
0
                GDALPDFDictionaryRW gs;
1695
0
                gs.Add("Type", GDALPDFObjectRW::CreateName("ExtGState"));
1696
0
                gs.Add("ca", 0);
1697
0
                gs.Add("CA", 0);
1698
0
                VSIFPrintfL(m_fp, "%s\n", gs.Serialize().c_str());
1699
0
            }
1700
0
            EndObj();
1701
0
            oPageContext.m_oExtGState["GSinvisible"] = nExtGState;
1702
0
        }
1703
0
        oPageContext.m_osDrawingStream += "q\n";
1704
0
        oPageContext.m_osDrawingStream += "/GSinvisible gs\n";
1705
0
        oPageContext.m_osDrawingStream += "0 w\n";
1706
0
        dfOpacityFactor = 0;
1707
0
    }
1708
0
    else
1709
0
    {
1710
0
        StartBlending(psNode, oPageContext, dfOpacityFactor);
1711
0
    }
1712
1713
0
    if (!m_nStructTreeRootId.toBool())
1714
0
        m_nStructTreeRootId = AllocNewObject();
1715
1716
0
    GDALPDFObjectNum nFeatureLayerId;
1717
0
    if (bLogicalStructure)
1718
0
    {
1719
0
        nFeatureLayerId = AllocNewObject();
1720
0
        m_anFeatureLayerId.push_back(nFeatureLayerId);
1721
0
    }
1722
1723
0
    std::vector<GDALPDFObjectNum> anFeatureUserProperties;
1724
0
    for (auto &&poFeature : poLayer)
1725
0
    {
1726
0
        auto hFeat = OGRFeature::ToHandle(poFeature.get());
1727
0
        auto hGeom = OGR_F_GetGeometryRef(hFeat);
1728
0
        if (!hGeom || OGR_G_IsEmpty(hGeom))
1729
0
            continue;
1730
0
        if (poCT)
1731
0
        {
1732
0
            if (OGRGeometry::FromHandle(hGeom)->transform(poCT.get()) !=
1733
0
                OGRERR_NONE)
1734
0
                continue;
1735
1736
0
            OGREnvelope sEnvelope;
1737
0
            OGR_G_GetEnvelope(hGeom, &sEnvelope);
1738
0
            if (sEnvelope.MinX > dfClippingMaxX ||
1739
0
                sEnvelope.MaxX < dfClippingMinX ||
1740
0
                sEnvelope.MinY > dfClippingMaxY ||
1741
0
                sEnvelope.MaxY < dfClippingMinY)
1742
0
            {
1743
0
                continue;
1744
0
            }
1745
0
        }
1746
1747
0
        if (bLogicalStructure)
1748
0
        {
1749
0
            CPLString osOutFeatureName;
1750
0
            anFeatureUserProperties.push_back(
1751
0
                WriteAttributes(hFeat, aosIncludedFields, pszOGRDisplayField,
1752
0
                                oPageContext.m_nMCID, nFeatureLayerId,
1753
0
                                m_asPageId.back(), osOutFeatureName));
1754
0
        }
1755
1756
0
        ObjectStyle os;
1757
0
        GetObjectStyle(pszStyleString, hFeat, adfMatrix,
1758
0
                       m_oMapSymbolFilenameToDesc, os);
1759
0
        os.nPenA = static_cast<int>(std::round(os.nPenA * dfOpacityFactor));
1760
0
        os.nBrushA = static_cast<int>(std::round(os.nBrushA * dfOpacityFactor));
1761
1762
0
        const double dfRadius = os.dfSymbolSize;
1763
1764
0
        if (os.nImageSymbolId.toBool())
1765
0
        {
1766
0
            oPageContext.m_oXObjects[CPLOPrintf(
1767
0
                "SymImage%d", os.nImageSymbolId.toInt())] = os.nImageSymbolId;
1768
0
        }
1769
1770
0
        if (pszOGRLinkField)
1771
0
        {
1772
0
            OGREnvelope sEnvelope;
1773
0
            OGR_G_GetEnvelope(hGeom, &sEnvelope);
1774
0
            int bboxXMin, bboxYMin, bboxXMax, bboxYMax;
1775
0
            ComputeIntBBox(hGeom, sEnvelope, adfMatrix, os, dfRadius, bboxXMin,
1776
0
                           bboxYMin, bboxXMax, bboxYMax);
1777
1778
0
            auto nLinkId = WriteLink(hFeat, pszOGRLinkField, adfMatrix,
1779
0
                                     bboxXMin, bboxYMin, bboxXMax, bboxYMax);
1780
0
            if (nLinkId.toBool())
1781
0
                oPageContext.m_anAnnotationsId.push_back(nLinkId);
1782
0
        }
1783
1784
0
        if (bLogicalStructure)
1785
0
        {
1786
0
            oPageContext.m_osDrawingStream +=
1787
0
                CPLOPrintf("/feature <</MCID %d>> BDC\n", oPageContext.m_nMCID);
1788
0
        }
1789
1790
0
        if (bVisible || bLogicalStructure)
1791
0
        {
1792
0
            oPageContext.m_osDrawingStream += "q\n";
1793
0
            if (bVisible && (os.nPenA != 255 || os.nBrushA != 255))
1794
0
            {
1795
0
                CPLString osGSName;
1796
0
                osGSName.Printf("GS_CA_%d_ca_%d", os.nPenA, os.nBrushA);
1797
0
                if (oPageContext.m_oExtGState.find(osGSName) ==
1798
0
                    oPageContext.m_oExtGState.end())
1799
0
                {
1800
0
                    auto nExtGState = AllocNewObject();
1801
0
                    StartObj(nExtGState);
1802
0
                    {
1803
0
                        GDALPDFDictionaryRW gs;
1804
0
                        gs.Add("Type",
1805
0
                               GDALPDFObjectRW::CreateName("ExtGState"));
1806
0
                        if (os.nPenA != 255)
1807
0
                            gs.Add("CA", (os.nPenA == 127 || os.nPenA == 128)
1808
0
                                             ? 0.5
1809
0
                                             : os.nPenA / 255.0);
1810
0
                        if (os.nBrushA != 255)
1811
0
                            gs.Add("ca",
1812
0
                                   (os.nBrushA == 127 || os.nBrushA == 128)
1813
0
                                       ? 0.5
1814
0
                                       : os.nBrushA / 255.0);
1815
0
                        VSIFPrintfL(m_fp, "%s\n", gs.Serialize().c_str());
1816
0
                    }
1817
0
                    EndObj();
1818
0
                    oPageContext.m_oExtGState[osGSName] = nExtGState;
1819
0
                }
1820
0
                oPageContext.m_osDrawingStream += "/" + osGSName + " gs\n";
1821
0
            }
1822
1823
0
            oPageContext.m_osDrawingStream +=
1824
0
                GenerateDrawingStream(hGeom, adfMatrix, os, dfRadius);
1825
1826
0
            oPageContext.m_osDrawingStream += "Q\n";
1827
0
        }
1828
1829
0
        if (bLogicalStructure)
1830
0
        {
1831
0
            oPageContext.m_osDrawingStream += "EMC\n";
1832
0
            oPageContext.m_nMCID++;
1833
0
        }
1834
0
    }
1835
1836
0
    if (bLogicalStructure)
1837
0
    {
1838
0
        for (const auto &num : anFeatureUserProperties)
1839
0
        {
1840
0
            oPageContext.m_anFeatureUserProperties.push_back(num);
1841
0
        }
1842
1843
0
        {
1844
0
            StartObj(nFeatureLayerId);
1845
1846
0
            GDALPDFDictionaryRW oDict;
1847
0
            GDALPDFDictionaryRW *poDictA = new GDALPDFDictionaryRW();
1848
0
            oDict.Add("A", poDictA);
1849
0
            poDictA->Add("O", GDALPDFObjectRW::CreateName("UserProperties"));
1850
0
            GDALPDFArrayRW *poArrayK = new GDALPDFArrayRW();
1851
0
            for (const auto &num : anFeatureUserProperties)
1852
0
                poArrayK->Add(num, 0);
1853
0
            oDict.Add("K", poArrayK);
1854
0
            oDict.Add("P", m_nStructTreeRootId, 0);
1855
0
            oDict.Add("S", GDALPDFObjectRW::CreateName("Layer"));
1856
1857
0
            const char *pszOGRDisplayName = CPLGetXMLValue(
1858
0
                psLogicalStructure, "displayLayerName", poLayer->GetName());
1859
0
            oDict.Add("T", pszOGRDisplayName);
1860
1861
0
            VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
1862
1863
0
            EndObj();
1864
0
        }
1865
0
    }
1866
1867
0
    if (!bVisible)
1868
0
    {
1869
0
        oPageContext.m_osDrawingStream += "Q\n";
1870
0
    }
1871
0
    else
1872
0
    {
1873
0
        EndBlending(psNode, oPageContext);
1874
0
    }
1875
1876
0
    return true;
1877
0
}
1878
1879
/************************************************************************/
1880
/*                          WriteVectorLabel()                          */
1881
/************************************************************************/
1882
1883
bool GDALPDFComposerWriter::WriteVectorLabel(const CPLXMLNode *psNode,
1884
                                             PageContext &oPageContext)
1885
0
{
1886
0
    const char *pszDataset = CPLGetXMLValue(psNode, "dataset", nullptr);
1887
0
    if (!pszDataset)
1888
0
    {
1889
0
        CPLError(CE_Failure, CPLE_AppDefined, "Missing dataset");
1890
0
        return false;
1891
0
    }
1892
0
    const char *pszLayer = CPLGetXMLValue(psNode, "layer", nullptr);
1893
0
    if (!pszLayer)
1894
0
    {
1895
0
        CPLError(CE_Failure, CPLE_AppDefined, "Missing layer");
1896
0
        return false;
1897
0
    }
1898
1899
0
    GDALDatasetUniquePtr poDS(
1900
0
        GDALDataset::Open(pszDataset, GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR,
1901
0
                          nullptr, nullptr, nullptr));
1902
0
    if (!poDS)
1903
0
        return false;
1904
0
    OGRLayer *poLayer = poDS->GetLayerByName(pszLayer);
1905
0
    if (!poLayer)
1906
0
    {
1907
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find layer %s", pszLayer);
1908
0
        return false;
1909
0
    }
1910
1911
0
    const char *pszStyleString =
1912
0
        CPLGetXMLValue(psNode, "ogrStyleString", nullptr);
1913
1914
0
    double dfOpacityFactor = 1.0;
1915
0
    StartBlending(psNode, oPageContext, dfOpacityFactor);
1916
1917
0
    const char *pszGeoreferencingId =
1918
0
        CPLGetXMLValue(psNode, "georeferencingId", nullptr);
1919
0
    std::unique_ptr<OGRCoordinateTransformation> poCT;
1920
0
    double dfClippingMinX = 0;
1921
0
    double dfClippingMinY = 0;
1922
0
    double dfClippingMaxX = 0;
1923
0
    double dfClippingMaxY = 0;
1924
0
    double adfMatrix[4] = {0, 1, 0, 1};
1925
0
    if (pszGeoreferencingId &&
1926
0
        !SetupVectorGeoreferencing(
1927
0
            pszGeoreferencingId, poLayer, oPageContext, dfClippingMinX,
1928
0
            dfClippingMinY, dfClippingMaxX, dfClippingMaxY, adfMatrix, poCT))
1929
0
    {
1930
0
        return false;
1931
0
    }
1932
1933
0
    for (auto &&poFeature : poLayer)
1934
0
    {
1935
0
        auto hFeat = OGRFeature::ToHandle(poFeature.get());
1936
0
        auto hGeom = OGR_F_GetGeometryRef(hFeat);
1937
0
        if (!hGeom || OGR_G_IsEmpty(hGeom))
1938
0
            continue;
1939
0
        if (poCT)
1940
0
        {
1941
0
            if (OGRGeometry::FromHandle(hGeom)->transform(poCT.get()) !=
1942
0
                OGRERR_NONE)
1943
0
                continue;
1944
1945
0
            OGREnvelope sEnvelope;
1946
0
            OGR_G_GetEnvelope(hGeom, &sEnvelope);
1947
0
            if (sEnvelope.MinX > dfClippingMaxX ||
1948
0
                sEnvelope.MaxX < dfClippingMinX ||
1949
0
                sEnvelope.MinY > dfClippingMaxY ||
1950
0
                sEnvelope.MaxY < dfClippingMinY)
1951
0
            {
1952
0
                continue;
1953
0
            }
1954
0
        }
1955
1956
0
        ObjectStyle os;
1957
0
        GetObjectStyle(pszStyleString, hFeat, adfMatrix,
1958
0
                       m_oMapSymbolFilenameToDesc, os);
1959
0
        os.nPenA = static_cast<int>(std::round(os.nPenA * dfOpacityFactor));
1960
0
        os.nBrushA = static_cast<int>(std::round(os.nBrushA * dfOpacityFactor));
1961
1962
0
        if (!os.osLabelText.empty() &&
1963
0
            wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint)
1964
0
        {
1965
0
            auto nObjectId = WriteLabel(hGeom, adfMatrix, os,
1966
0
                                        oPageContext.m_eStreamCompressMethod, 0,
1967
0
                                        0, oPageContext.m_dfWidthInUserUnit,
1968
0
                                        oPageContext.m_dfHeightInUserUnit);
1969
0
            oPageContext.m_osDrawingStream +=
1970
0
                CPLOPrintf("/Label%d Do\n", nObjectId.toInt());
1971
0
            oPageContext.m_oXObjects[CPLOPrintf("Label%d", nObjectId.toInt())] =
1972
0
                nObjectId;
1973
0
        }
1974
0
    }
1975
1976
0
    EndBlending(psNode, oPageContext);
1977
1978
0
    return true;
1979
0
}
1980
1981
#ifdef HAVE_PDF_READ_SUPPORT
1982
1983
/************************************************************************/
1984
/*                           EmitNewObject()                            */
1985
/************************************************************************/
1986
1987
GDALPDFObjectNum
1988
GDALPDFComposerWriter::EmitNewObject(GDALPDFObject *poObj,
1989
                                     RemapType &oRemapObjectRefs)
1990
0
{
1991
0
    auto nId = AllocNewObject();
1992
0
    const auto nRefNum = poObj->GetRefNum();
1993
0
    if (nRefNum.toBool())
1994
0
    {
1995
0
        int nRefGen = poObj->GetRefGen();
1996
0
        std::pair<int, int> oKey(nRefNum.toInt(), nRefGen);
1997
0
        oRemapObjectRefs[oKey] = nId;
1998
0
    }
1999
0
    CPLString osStr;
2000
0
    if (!SerializeAndRenumberIgnoreRef(osStr, poObj, oRemapObjectRefs))
2001
0
        return GDALPDFObjectNum();
2002
0
    StartObj(nId);
2003
0
    VSIFWriteL(osStr.data(), 1, osStr.size(), m_fp);
2004
0
    VSIFPrintfL(m_fp, "\n");
2005
0
    EndObj();
2006
0
    return nId;
2007
0
}
2008
2009
/************************************************************************/
2010
/*                        SerializeAndRenumber()                        */
2011
/************************************************************************/
2012
2013
bool GDALPDFComposerWriter::SerializeAndRenumber(CPLString &osStr,
2014
                                                 GDALPDFObject *poObj,
2015
                                                 RemapType &oRemapObjectRefs)
2016
0
{
2017
0
    auto nRefNum = poObj->GetRefNum();
2018
0
    if (nRefNum.toBool())
2019
0
    {
2020
0
        int nRefGen = poObj->GetRefGen();
2021
2022
0
        std::pair<int, int> oKey(nRefNum.toInt(), nRefGen);
2023
0
        auto oIter = oRemapObjectRefs.find(oKey);
2024
0
        if (oIter != oRemapObjectRefs.end())
2025
0
        {
2026
0
            osStr.append(CPLSPrintf("%d 0 R", oIter->second.toInt()));
2027
0
            return true;
2028
0
        }
2029
0
        else
2030
0
        {
2031
0
            auto nId = EmitNewObject(poObj, oRemapObjectRefs);
2032
0
            osStr.append(CPLSPrintf("%d 0 R", nId.toInt()));
2033
0
            return nId.toBool();
2034
0
        }
2035
0
    }
2036
0
    else
2037
0
    {
2038
0
        return SerializeAndRenumberIgnoreRef(osStr, poObj, oRemapObjectRefs);
2039
0
    }
2040
0
}
2041
2042
/************************************************************************/
2043
/*                   SerializeAndRenumberIgnoreRef()                    */
2044
/************************************************************************/
2045
2046
bool GDALPDFComposerWriter::SerializeAndRenumberIgnoreRef(
2047
    CPLString &osStr, GDALPDFObject *poObj, RemapType &oRemapObjectRefs)
2048
0
{
2049
0
    switch (poObj->GetType())
2050
0
    {
2051
0
        case PDFObjectType_Array:
2052
0
        {
2053
0
            auto poArray = poObj->GetArray();
2054
0
            int nLength = poArray->GetLength();
2055
0
            osStr.append("[ ");
2056
0
            for (int i = 0; i < nLength; i++)
2057
0
            {
2058
0
                if (!SerializeAndRenumber(osStr, poArray->Get(i),
2059
0
                                          oRemapObjectRefs))
2060
0
                    return false;
2061
0
                osStr.append(" ");
2062
0
            }
2063
0
            osStr.append("]");
2064
0
            break;
2065
0
        }
2066
0
        case PDFObjectType_Dictionary:
2067
0
        {
2068
0
            osStr.append("<< ");
2069
0
            auto poDict = poObj->GetDictionary();
2070
0
            auto &oMap = poDict->GetValues();
2071
0
            for (const auto &oIter : oMap)
2072
0
            {
2073
0
                const char *pszKey = oIter.first.c_str();
2074
0
                GDALPDFObject *poSubObj = oIter.second;
2075
0
                osStr.append("/");
2076
0
                osStr.append(pszKey);
2077
0
                osStr.append(" ");
2078
0
                if (!SerializeAndRenumber(osStr, poSubObj, oRemapObjectRefs))
2079
0
                    return false;
2080
0
                osStr.append(" ");
2081
0
            }
2082
0
            osStr.append(">>");
2083
0
            auto poStream = poObj->GetStream();
2084
0
            if (poStream)
2085
0
            {
2086
                // CPLAssert( poObj->GetRefNum().toBool() ); // should be a top
2087
                // level object
2088
0
                osStr.append("\nstream\n");
2089
0
                auto pRawBytes = poStream->GetRawBytes();
2090
0
                if (!pRawBytes)
2091
0
                {
2092
0
                    CPLError(CE_Failure, CPLE_AppDefined,
2093
0
                             "Cannot get stream content");
2094
0
                    return false;
2095
0
                }
2096
0
                osStr.append(pRawBytes,
2097
0
                             static_cast<size_t>(poStream->GetRawLength()));
2098
0
                VSIFree(pRawBytes);
2099
0
                osStr.append("\nendstream\n");
2100
0
            }
2101
0
            break;
2102
0
        }
2103
0
        case PDFObjectType_Unknown:
2104
0
        {
2105
0
            CPLError(CE_Failure, CPLE_AppDefined, "Corrupted PDF");
2106
0
            return false;
2107
0
        }
2108
0
        default:
2109
0
        {
2110
0
            poObj->Serialize(osStr, false);
2111
0
            break;
2112
0
        }
2113
0
    }
2114
0
    return true;
2115
0
}
2116
2117
/************************************************************************/
2118
/*                        SerializeAndRenumber()                        */
2119
/************************************************************************/
2120
2121
GDALPDFObjectNum
2122
GDALPDFComposerWriter::SerializeAndRenumber(GDALPDFObject *poObj)
2123
0
{
2124
0
    RemapType oRemapObjectRefs;
2125
0
    return EmitNewObject(poObj, oRemapObjectRefs);
2126
0
}
2127
2128
/************************************************************************/
2129
/*                              WritePDF()                              */
2130
/************************************************************************/
2131
2132
bool GDALPDFComposerWriter::WritePDF(const CPLXMLNode *psNode,
2133
                                     PageContext &oPageContext)
2134
0
{
2135
0
    const char *pszDataset = CPLGetXMLValue(psNode, "dataset", nullptr);
2136
0
    if (!pszDataset)
2137
0
    {
2138
0
        CPLError(CE_Failure, CPLE_AppDefined, "Missing dataset");
2139
0
        return false;
2140
0
    }
2141
2142
0
    GDALOpenInfo oOpenInfo(pszDataset, GA_ReadOnly);
2143
0
    std::unique_ptr<PDFDataset> poDS(PDFDataset::Open(&oOpenInfo));
2144
0
    if (!poDS)
2145
0
    {
2146
0
        CPLError(CE_Failure, CPLE_OpenFailed, "%s is not a valid PDF file",
2147
0
                 pszDataset);
2148
0
        return false;
2149
0
    }
2150
0
    if (poDS->GetPageWidth() != oPageContext.m_dfWidthInUserUnit ||
2151
0
        poDS->GetPageHeight() != oPageContext.m_dfHeightInUserUnit)
2152
0
    {
2153
0
        CPLError(CE_Warning, CPLE_AppDefined,
2154
0
                 "Dimensions of the inserted PDF page are %fx%f, which is "
2155
0
                 "different from the output PDF page %fx%f",
2156
0
                 poDS->GetPageWidth(), poDS->GetPageHeight(),
2157
0
                 oPageContext.m_dfWidthInUserUnit,
2158
0
                 oPageContext.m_dfHeightInUserUnit);
2159
0
    }
2160
0
    auto poPageObj = poDS->GetPageObj();
2161
0
    if (!poPageObj)
2162
0
        return false;
2163
0
    auto poPageDict = poPageObj->GetDictionary();
2164
0
    if (!poPageDict)
2165
0
        return false;
2166
0
    auto poContents = poPageDict->Get("Contents");
2167
0
    if (poContents != nullptr && poContents->GetType() == PDFObjectType_Array)
2168
0
    {
2169
0
        GDALPDFArray *poContentsArray = poContents->GetArray();
2170
0
        if (poContentsArray->GetLength() == 1)
2171
0
        {
2172
0
            poContents = poContentsArray->Get(0);
2173
0
        }
2174
0
    }
2175
0
    if (poContents == nullptr ||
2176
0
        poContents->GetType() != PDFObjectType_Dictionary)
2177
0
    {
2178
0
        CPLError(CE_Failure, CPLE_AppDefined, "Missing Contents");
2179
0
        return false;
2180
0
    }
2181
2182
0
    auto poResources = poPageDict->Get("Resources");
2183
0
    if (!poResources)
2184
0
    {
2185
0
        CPLError(CE_Failure, CPLE_AppDefined, "Missing Resources");
2186
0
        return false;
2187
0
    }
2188
2189
    // Serialize and renumber the Page Resources dictionary
2190
0
    auto nClonedResources = SerializeAndRenumber(poResources);
2191
0
    if (!nClonedResources.toBool())
2192
0
    {
2193
0
        return false;
2194
0
    }
2195
2196
    // Create a Transparency group using cloned Page Resources, and
2197
    // the Page Contents stream
2198
0
    auto nFormId = AllocNewObject();
2199
0
    GDALPDFDictionaryRW oDictGroup;
2200
0
    GDALPDFDictionaryRW *poGroup = new GDALPDFDictionaryRW();
2201
0
    poGroup->Add("Type", GDALPDFObjectRW::CreateName("Group"))
2202
0
        .Add("S", GDALPDFObjectRW::CreateName("Transparency"));
2203
2204
0
    oDictGroup.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
2205
0
        .Add("BBox", &((new GDALPDFArrayRW())->Add(0).Add(0))
2206
0
                          .Add(oPageContext.m_dfWidthInUserUnit)
2207
0
                          .Add(oPageContext.m_dfHeightInUserUnit))
2208
0
        .Add("Subtype", GDALPDFObjectRW::CreateName("Form"))
2209
0
        .Add("Group", poGroup)
2210
0
        .Add("Resources", nClonedResources, 0);
2211
2212
0
    auto poStream = poContents->GetStream();
2213
0
    if (!poStream)
2214
0
    {
2215
0
        CPLError(CE_Failure, CPLE_AppDefined, "Missing Contents stream");
2216
0
        return false;
2217
0
    }
2218
0
    auto pabyContents = poStream->GetBytes();
2219
0
    if (!pabyContents)
2220
0
    {
2221
0
        return false;
2222
0
    }
2223
0
    const auto nContentsLength = poStream->GetLength();
2224
2225
0
    StartObjWithStream(nFormId, oDictGroup,
2226
0
                       oPageContext.m_eStreamCompressMethod != COMPRESS_NONE);
2227
0
    VSIFWriteL(pabyContents, 1, static_cast<size_t>(nContentsLength), m_fp);
2228
0
    VSIFree(pabyContents);
2229
0
    EndObjWithStream();
2230
2231
    // Paint the transparency group
2232
0
    double dfIgnoredOpacity;
2233
0
    StartBlending(psNode, oPageContext, dfIgnoredOpacity);
2234
2235
0
    oPageContext.m_osDrawingStream +=
2236
0
        CPLOPrintf("/Form%d Do\n", nFormId.toInt());
2237
0
    oPageContext.m_oXObjects[CPLOPrintf("Form%d", nFormId.toInt())] = nFormId;
2238
2239
0
    EndBlending(psNode, oPageContext);
2240
2241
0
    return true;
2242
0
}
2243
2244
#endif  // HAVE_PDF_READ_SUPPORT
2245
2246
/************************************************************************/
2247
/*                              Generate()                              */
2248
/************************************************************************/
2249
2250
bool GDALPDFComposerWriter::Generate(const CPLXMLNode *psComposition)
2251
0
{
2252
0
    m_osJPEG2000Driver = CPLGetXMLValue(psComposition, "JPEG2000Driver", "");
2253
2254
0
    auto psMetadata = CPLGetXMLNode(psComposition, "Metadata");
2255
0
    if (psMetadata)
2256
0
    {
2257
0
        SetInfo(CPLGetXMLValue(psMetadata, "Author", nullptr),
2258
0
                CPLGetXMLValue(psMetadata, "Producer", nullptr),
2259
0
                CPLGetXMLValue(psMetadata, "Creator", nullptr),
2260
0
                CPLGetXMLValue(psMetadata, "CreationDate", nullptr),
2261
0
                CPLGetXMLValue(psMetadata, "Subject", nullptr),
2262
0
                CPLGetXMLValue(psMetadata, "Title", nullptr),
2263
0
                CPLGetXMLValue(psMetadata, "Keywords", nullptr));
2264
0
        SetXMP(nullptr, CPLGetXMLValue(psMetadata, "XMP", nullptr));
2265
0
    }
2266
2267
0
    const char *pszJavascript =
2268
0
        CPLGetXMLValue(psComposition, "Javascript", nullptr);
2269
0
    if (pszJavascript)
2270
0
        WriteJavascript(pszJavascript, false);
2271
2272
0
    auto psLayerTree = CPLGetXMLNode(psComposition, "LayerTree");
2273
0
    if (psLayerTree)
2274
0
    {
2275
0
        m_bDisplayLayersOnlyOnVisiblePages = CPLTestBool(
2276
0
            CPLGetXMLValue(psLayerTree, "displayOnlyOnVisiblePages", "false"));
2277
0
        if (!CreateLayerTree(psLayerTree, GDALPDFObjectNum(), &m_oTreeOfOGC))
2278
0
            return false;
2279
0
    }
2280
2281
0
    bool bFoundPage = false;
2282
0
    for (const auto *psIter = psComposition->psChild; psIter;
2283
0
         psIter = psIter->psNext)
2284
0
    {
2285
0
        if (psIter->eType == CXT_Element &&
2286
0
            strcmp(psIter->pszValue, "Page") == 0)
2287
0
        {
2288
0
            if (!GeneratePage(psIter))
2289
0
                return false;
2290
0
            bFoundPage = true;
2291
0
        }
2292
0
    }
2293
0
    if (!bFoundPage)
2294
0
    {
2295
0
        CPLError(CE_Failure, CPLE_AppDefined,
2296
0
                 "At least one page should be defined");
2297
0
        return false;
2298
0
    }
2299
2300
0
    auto psOutline = CPLGetXMLNode(psComposition, "Outline");
2301
0
    if (psOutline)
2302
0
    {
2303
0
        if (!CreateOutline(psOutline))
2304
0
            return false;
2305
0
    }
2306
2307
0
    return true;
2308
0
}
2309
2310
/************************************************************************/
2311
/*                        GDALPDFErrorHandler()                         */
2312
/************************************************************************/
2313
2314
static void CPL_STDCALL GDALPDFErrorHandler(CPL_UNUSED CPLErr eErr,
2315
                                            CPL_UNUSED CPLErrorNum nType,
2316
                                            const char *pszMsg)
2317
0
{
2318
0
    std::vector<CPLString> *paosErrors =
2319
0
        static_cast<std::vector<CPLString> *>(CPLGetErrorHandlerUserData());
2320
0
    paosErrors->push_back(pszMsg);
2321
0
}
2322
2323
/************************************************************************/
2324
/*                  GDALPDFCreateFromCompositionFile()                  */
2325
/************************************************************************/
2326
2327
GDALDataset *GDALPDFCreateFromCompositionFile(const char *pszPDFFilename,
2328
                                              const char *pszXMLFilename)
2329
0
{
2330
0
    CPLXMLTreeCloser oXML((pszXMLFilename[0] == '<' &&
2331
0
                           strstr(pszXMLFilename, "<PDFComposition") != nullptr)
2332
0
                              ? CPLParseXMLString(pszXMLFilename)
2333
0
                              : CPLParseXMLFile(pszXMLFilename));
2334
0
    if (!oXML.get())
2335
0
        return nullptr;
2336
0
    auto psComposition = CPLGetXMLNode(oXML.get(), "=PDFComposition");
2337
0
    if (!psComposition)
2338
0
    {
2339
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot find PDFComposition");
2340
0
        return nullptr;
2341
0
    }
2342
2343
    // XML Validation.
2344
0
    if (CPLTestBool(CPLGetConfigOption("GDAL_XML_VALIDATION", "YES")))
2345
0
    {
2346
0
#ifdef EMBED_RESOURCE_FILES
2347
0
        std::string osTmpFilename;
2348
0
        CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
2349
0
#endif
2350
#ifdef USE_ONLY_EMBEDDED_RESOURCE_FILES
2351
        const char *pszXSD = nullptr;
2352
#else
2353
0
        const char *pszXSD = CPLFindFile("gdal", "pdfcomposition.xsd");
2354
0
#endif
2355
0
#ifdef EMBED_RESOURCE_FILES
2356
0
        if (!pszXSD)
2357
0
        {
2358
0
            static const bool bOnce [[maybe_unused]] = []()
2359
0
            {
2360
0
                CPLDebug("PDF", "Using embedded pdfcomposition.xsd");
2361
0
                return true;
2362
0
            }();
2363
0
            osTmpFilename = VSIMemGenerateHiddenFilename("pdfcomposition.xsd");
2364
0
            pszXSD = osTmpFilename.c_str();
2365
0
            VSIFCloseL(VSIFileFromMemBuffer(
2366
0
                osTmpFilename.c_str(),
2367
0
                const_cast<GByte *>(
2368
0
                    reinterpret_cast<const GByte *>(PDFGetCompositionXSD())),
2369
0
                static_cast<int>(strlen(PDFGetCompositionXSD())),
2370
0
                /* bTakeOwnership = */ false));
2371
0
        }
2372
#else
2373
        if (pszXSD != nullptr)
2374
#endif
2375
0
        {
2376
0
            std::vector<CPLString> aosErrors;
2377
0
            CPLPushErrorHandlerEx(GDALPDFErrorHandler, &aosErrors);
2378
0
            const int bRet = CPLValidateXML(pszXMLFilename, pszXSD, nullptr);
2379
0
            CPLPopErrorHandler();
2380
0
            if (!bRet)
2381
0
            {
2382
0
                if (!aosErrors.empty() &&
2383
0
                    strstr(aosErrors[0].c_str(), "missing libxml2 support") ==
2384
0
                        nullptr)
2385
0
                {
2386
0
                    for (size_t i = 0; i < aosErrors.size(); i++)
2387
0
                    {
2388
0
                        CPLError(CE_Warning, CPLE_AppDefined, "%s",
2389
0
                                 aosErrors[i].c_str());
2390
0
                    }
2391
0
                }
2392
0
            }
2393
0
            CPLErrorReset();
2394
0
        }
2395
2396
0
#ifdef EMBED_RESOURCE_FILES
2397
0
        if (!osTmpFilename.empty())
2398
0
            VSIUnlink(osTmpFilename.c_str());
2399
0
#endif
2400
0
    }
2401
2402
    /* -------------------------------------------------------------------- */
2403
    /*      Create file.                                                    */
2404
    /* -------------------------------------------------------------------- */
2405
0
    VSILFILE *fp = VSIFOpenL(pszPDFFilename, "wb");
2406
0
    if (fp == nullptr)
2407
0
    {
2408
0
        CPLError(CE_Failure, CPLE_OpenFailed, "Unable to create PDF file %s.\n",
2409
0
                 pszPDFFilename);
2410
0
        return nullptr;
2411
0
    }
2412
2413
0
    GDALPDFComposerWriter oWriter(fp);
2414
0
    if (!oWriter.Generate(psComposition))
2415
0
        return nullptr;
2416
2417
0
    return new GDALFakePDFDataset();
2418
0
}