Coverage Report

Created: 2026-06-30 11:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/source/pdf/XmpMetadata.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 */
10
11
#include <sal/config.h>
12
13
#include <string_view>
14
15
#include <pdf/XmpMetadata.hxx>
16
#include <tools/XmlWriter.hxx>
17
18
namespace vcl::pdf
19
{
20
namespace
21
{
22
constexpr const char* constPadding = "                                        "
23
                                     "                                        "
24
                                     "                                        "
25
                                     "                                        "
26
                                     "                                        "
27
                                     "\n";
28
}
29
30
4.01k
XmpMetadata::XmpMetadata() = default;
31
32
void XmpMetadata::write()
33
4.01k
{
34
4.01k
    mpMemoryStream = std::make_unique<SvMemoryStream>(4096 /*Initial*/, 64 /*Resize*/);
35
36
    // Header
37
4.01k
    mpMemoryStream->WriteOString(std::string_view(reinterpret_cast<char const*>(
38
4.01k
        u8"<?xpacket begin=\"\uFEFF\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n")));
39
40
4.01k
    {
41
4.01k
        tools::XmlWriter aXmlWriter(mpMemoryStream.get());
42
4.01k
        aXmlWriter.startDocument(2, false);
43
4.01k
        aXmlWriter.startElement("x"_ostr, "xmpmeta"_ostr, "adobe:ns:meta/"_ostr);
44
4.01k
        aXmlWriter.startElement("rdf"_ostr, "RDF"_ostr,
45
4.01k
                                "http://www.w3.org/1999/02/22-rdf-syntax-ns#"_ostr);
46
47
        // PDF/A part ( ISO 19005-1:2005 - 6.7.11 )
48
4.01k
        if (mnPDF_A > 0)
49
0
        {
50
0
            OString sPdfVersion = OString::number(mnPDF_A);
51
52
0
            aXmlWriter.startElement("rdf:Description");
53
0
            aXmlWriter.attribute("rdf:about", "");
54
0
            aXmlWriter.attribute("xmlns:pdfaid", "http://www.aiim.org/pdfa/ns/id/");
55
56
0
            aXmlWriter.startElement("pdfaid:part");
57
0
            aXmlWriter.content(sPdfVersion);
58
0
            aXmlWriter.endElement();
59
60
0
            if (mnPDF_A == 4)
61
0
            {
62
0
                aXmlWriter.startElement("pdfaid:rev");
63
0
                aXmlWriter.content("2020");
64
0
                aXmlWriter.endElement();
65
0
            }
66
67
0
            if (!msConformance.isEmpty())
68
0
            {
69
0
                aXmlWriter.startElement("pdfaid:conformance");
70
0
                aXmlWriter.content(msConformance);
71
0
                aXmlWriter.endElement();
72
0
            }
73
0
            aXmlWriter.endElement();
74
0
        }
75
76
        // Dublin Core properties
77
4.01k
        if (!msTitle.isEmpty() || !msAuthor.isEmpty() || !msSubject.isEmpty()
78
3.28k
            || !maContributor.empty() || !msCoverage.isEmpty() || !msIdentifier.isEmpty()
79
3.28k
            || !maPublisher.empty() || !maRelation.empty() || !msRights.isEmpty()
80
3.28k
            || !msSource.isEmpty() || !msType.isEmpty())
81
723
        {
82
723
            aXmlWriter.startElement("rdf:Description");
83
723
            aXmlWriter.attribute("rdf:about", "");
84
723
            aXmlWriter.attribute("xmlns:dc", "http://purl.org/dc/elements/1.1/");
85
86
723
            aXmlWriter.startElement("dc:format");
87
723
            aXmlWriter.content("application/pdf");
88
723
            aXmlWriter.endElement();
89
90
723
            aXmlWriter.startElement("dc:date");
91
723
            aXmlWriter.startElement("rdf:Seq");
92
723
            aXmlWriter.startElement("rdf:li");
93
723
            aXmlWriter.content(m_sCreateDate);
94
723
            aXmlWriter.endElement();
95
723
            aXmlWriter.endElement();
96
723
            aXmlWriter.endElement();
97
98
723
            if (!msTitle.isEmpty())
99
504
            {
100
                // this is according to PDF/A-1, technical corrigendum 1 (2007-04-01)
101
504
                aXmlWriter.startElement("dc:title");
102
504
                aXmlWriter.startElement("rdf:Alt");
103
504
                aXmlWriter.startElement("rdf:li");
104
504
                aXmlWriter.attribute("xml:lang", "x-default");
105
504
                aXmlWriter.content(msTitle);
106
504
                aXmlWriter.endElement();
107
504
                aXmlWriter.endElement();
108
504
                aXmlWriter.endElement();
109
504
            }
110
723
            if (!msAuthor.isEmpty())
111
333
            {
112
333
                aXmlWriter.startElement("dc:creator");
113
333
                aXmlWriter.startElement("rdf:Seq");
114
333
                aXmlWriter.startElement("rdf:li");
115
333
                aXmlWriter.content(msAuthor);
116
333
                aXmlWriter.endElement();
117
333
                aXmlWriter.endElement();
118
333
                aXmlWriter.endElement();
119
333
            }
120
723
            if (!msSubject.isEmpty())
121
3
            {
122
3
                aXmlWriter.startElement("dc:description");
123
3
                aXmlWriter.startElement("rdf:Alt");
124
3
                aXmlWriter.startElement("rdf:li");
125
3
                aXmlWriter.attribute("xml:lang", "x-default");
126
3
                aXmlWriter.content(msSubject);
127
3
                aXmlWriter.endElement();
128
3
                aXmlWriter.endElement();
129
3
                aXmlWriter.endElement();
130
3
            }
131
723
            if (!maContributor.empty())
132
0
            {
133
0
                aXmlWriter.startElement("dc:contributor");
134
0
                aXmlWriter.startElement("rdf:Bag");
135
0
                for (const OString& rContributor : maContributor)
136
0
                {
137
0
                    aXmlWriter.startElement("rdf:li");
138
0
                    aXmlWriter.content(rContributor);
139
0
                    aXmlWriter.endElement();
140
0
                }
141
0
                aXmlWriter.endElement();
142
0
                aXmlWriter.endElement();
143
0
            }
144
723
            if (!msCoverage.isEmpty())
145
0
            {
146
0
                aXmlWriter.startElement("dc:coverage");
147
0
                aXmlWriter.content(msCoverage);
148
0
                aXmlWriter.endElement();
149
0
            }
150
723
            if (!msIdentifier.isEmpty())
151
0
            {
152
0
                aXmlWriter.startElement("dc:identifier");
153
0
                aXmlWriter.content(msIdentifier);
154
0
                aXmlWriter.endElement();
155
0
            }
156
723
            if (!maPublisher.empty())
157
0
            {
158
0
                aXmlWriter.startElement("dc:publisher");
159
0
                aXmlWriter.startElement("rdf:Bag");
160
0
                for (const OString& rPublisher : maPublisher)
161
0
                {
162
0
                    aXmlWriter.startElement("rdf:li");
163
0
                    aXmlWriter.content(rPublisher);
164
0
                    aXmlWriter.endElement();
165
0
                }
166
0
                aXmlWriter.endElement();
167
0
                aXmlWriter.endElement();
168
0
            }
169
723
            if (!maRelation.empty())
170
0
            {
171
0
                aXmlWriter.startElement("dc:relation");
172
0
                aXmlWriter.startElement("rdf:Bag");
173
0
                for (const OString& rRelation : maRelation)
174
0
                {
175
0
                    aXmlWriter.startElement("rdf:li");
176
0
                    aXmlWriter.content(rRelation);
177
0
                    aXmlWriter.endElement();
178
0
                }
179
0
                aXmlWriter.endElement();
180
0
                aXmlWriter.endElement();
181
0
            }
182
723
            if (!msRights.isEmpty())
183
0
            {
184
0
                aXmlWriter.startElement("dc:rights");
185
0
                aXmlWriter.startElement("rdf:Alt");
186
0
                aXmlWriter.startElement("rdf:li");
187
0
                aXmlWriter.attribute("xml:lang", "x-default");
188
0
                aXmlWriter.content(msRights);
189
0
                aXmlWriter.endElement();
190
0
                aXmlWriter.endElement();
191
0
                aXmlWriter.endElement();
192
0
            }
193
723
            if (!msSource.isEmpty())
194
0
            {
195
0
                aXmlWriter.startElement("dc:source");
196
0
                aXmlWriter.content(msSource);
197
0
                aXmlWriter.endElement();
198
0
            }
199
723
            if (!msType.isEmpty())
200
0
            {
201
0
                aXmlWriter.startElement("dc:type");
202
0
                aXmlWriter.content(msType);
203
0
                aXmlWriter.endElement();
204
0
            }
205
723
            aXmlWriter.endElement();
206
723
        }
207
208
        // PDF/UA
209
4.01k
        if (mnPDF_UA > 0)
210
0
        {
211
0
            if (mnPDF_A != 0)
212
0
            { // tdf#157517 PDF/A extension schema is required
213
0
                aXmlWriter.startElement("rdf:Description");
214
0
                aXmlWriter.attribute("rdf:about", "");
215
0
                aXmlWriter.attribute("xmlns:pdfaExtension",
216
0
                                     "http://www.aiim.org/pdfa/ns/extension/");
217
0
                aXmlWriter.attribute("xmlns:pdfaSchema", "http://www.aiim.org/pdfa/ns/schema#");
218
0
                aXmlWriter.attribute("xmlns:pdfaProperty", "http://www.aiim.org/pdfa/ns/property#");
219
0
                aXmlWriter.startElement("pdfaExtension:schemas");
220
0
                aXmlWriter.startElement("rdf:Bag");
221
0
                aXmlWriter.startElement("rdf:li");
222
0
                aXmlWriter.attribute("rdf:parseType", "Resource");
223
0
                aXmlWriter.startElement("pdfaSchema:namespaceURI");
224
0
                aXmlWriter.content("http://www.aiim.org/pdfua/ns/id/");
225
0
                aXmlWriter.endElement();
226
0
                aXmlWriter.startElement("pdfaSchema:prefix");
227
0
                aXmlWriter.content("pdfuaid");
228
0
                aXmlWriter.endElement();
229
0
                aXmlWriter.startElement("pdfaSchema:schema");
230
0
                aXmlWriter.content("PDF/UA identification schema");
231
0
                aXmlWriter.endElement();
232
0
                aXmlWriter.startElement("pdfaSchema:property");
233
0
                aXmlWriter.startElement("rdf:Seq");
234
235
0
                aXmlWriter.startElement("rdf:li");
236
0
                aXmlWriter.attribute("rdf:parseType", "Resource");
237
0
                aXmlWriter.startElement("pdfaProperty:category");
238
0
                aXmlWriter.content("internal");
239
0
                aXmlWriter.endElement();
240
0
                aXmlWriter.startElement("pdfaProperty:description");
241
0
                aXmlWriter.content("PDF/UA version identifier");
242
0
                aXmlWriter.endElement();
243
0
                aXmlWriter.startElement("pdfaProperty:name");
244
0
                aXmlWriter.content("part");
245
0
                aXmlWriter.endElement();
246
0
                aXmlWriter.startElement("pdfaProperty:valueType");
247
0
                aXmlWriter.content("Integer");
248
0
                aXmlWriter.endElement();
249
0
                aXmlWriter.endElement(); // rdf:li
250
251
0
                aXmlWriter.startElement("rdf:li");
252
0
                aXmlWriter.attribute("rdf:parseType", "Resource");
253
0
                aXmlWriter.startElement("pdfaProperty:category");
254
0
                aXmlWriter.content("internal");
255
0
                aXmlWriter.endElement();
256
0
                aXmlWriter.startElement("pdfaProperty:description");
257
0
                aXmlWriter.content("PDF/UA amendment identifier");
258
0
                aXmlWriter.endElement();
259
0
                aXmlWriter.startElement("pdfaProperty:name");
260
0
                aXmlWriter.content("amd");
261
0
                aXmlWriter.endElement();
262
0
                aXmlWriter.startElement("pdfaProperty:valueType");
263
0
                aXmlWriter.content("Text");
264
0
                aXmlWriter.endElement();
265
0
                aXmlWriter.endElement(); // rdf:li
266
267
0
                aXmlWriter.startElement("rdf:li");
268
0
                aXmlWriter.attribute("rdf:parseType", "Resource");
269
0
                aXmlWriter.startElement("pdfaProperty:category");
270
0
                aXmlWriter.content("internal");
271
0
                aXmlWriter.endElement();
272
0
                aXmlWriter.startElement("pdfaProperty:description");
273
0
                aXmlWriter.content("PDF/UA corrigenda identifier");
274
0
                aXmlWriter.endElement();
275
0
                aXmlWriter.startElement("pdfaProperty:name");
276
0
                aXmlWriter.content("corr");
277
0
                aXmlWriter.endElement();
278
0
                aXmlWriter.startElement("pdfaProperty:valueType");
279
0
                aXmlWriter.content("Text");
280
0
                aXmlWriter.endElement();
281
0
                aXmlWriter.endElement(); // rdf:li
282
283
0
                aXmlWriter.endElement(); // rdf:Seq
284
0
                aXmlWriter.endElement(); // pdfaSchema:property
285
0
                aXmlWriter.endElement(); // rdf:li
286
0
                aXmlWriter.endElement(); // rdf:Bag
287
0
                aXmlWriter.endElement(); // pdfaExtension:schemas
288
0
                aXmlWriter.endElement(); // rdf:Description
289
0
            }
290
0
            OString sPdfUaVersion = OString::number(mnPDF_UA);
291
0
            aXmlWriter.startElement("rdf:Description");
292
0
            aXmlWriter.attribute("rdf:about", "");
293
0
            aXmlWriter.attribute("xmlns:pdfuaid", "http://www.aiim.org/pdfua/ns/id/");
294
295
0
            aXmlWriter.startElement("pdfuaid:part");
296
0
            aXmlWriter.content(sPdfUaVersion);
297
0
            aXmlWriter.endElement();
298
299
0
            if (mnPDF_UA == 2)
300
0
            {
301
0
                aXmlWriter.startElement("pdfuaid:rev");
302
0
                aXmlWriter.content("2024");
303
0
                aXmlWriter.endElement();
304
0
            }
305
0
            aXmlWriter.endElement();
306
0
        }
307
308
        // PDF properties
309
4.01k
        if (!msProducer.isEmpty() || !msKeywords.isEmpty() || !msPDFVersion.isEmpty())
310
4.01k
        {
311
4.01k
            aXmlWriter.startElement("rdf:Description");
312
4.01k
            aXmlWriter.attribute("rdf:about", "");
313
4.01k
            aXmlWriter.attribute("xmlns:pdf", "http://ns.adobe.com/pdf/1.3/");
314
4.01k
            if (!msProducer.isEmpty())
315
0
            {
316
0
                aXmlWriter.startElement("pdf:Producer");
317
0
                aXmlWriter.content(msProducer);
318
0
                aXmlWriter.endElement();
319
0
            }
320
4.01k
            if (!msKeywords.isEmpty())
321
255
            {
322
255
                aXmlWriter.startElement("pdf:Keywords");
323
255
                aXmlWriter.content(msKeywords);
324
255
                aXmlWriter.endElement();
325
255
            }
326
4.01k
            if (!msPDFVersion.isEmpty())
327
4.01k
            {
328
4.01k
                aXmlWriter.startElement("pdf:PDFVersion");
329
4.01k
                aXmlWriter.content(msPDFVersion);
330
4.01k
                aXmlWriter.endElement();
331
4.01k
            }
332
4.01k
            aXmlWriter.endElement();
333
4.01k
        }
334
335
        // XMP Basic schema
336
4.01k
        aXmlWriter.startElement("rdf:Description");
337
4.01k
        aXmlWriter.attribute("rdf:about", "");
338
4.01k
        aXmlWriter.attribute("xmlns:xmp", "http://ns.adobe.com/xap/1.0/");
339
4.01k
        if (!m_sCreatorTool.isEmpty())
340
4.01k
        {
341
4.01k
            aXmlWriter.startElement("xmp:CreatorTool");
342
4.01k
            aXmlWriter.content(m_sCreatorTool);
343
4.01k
            aXmlWriter.endElement();
344
4.01k
        }
345
4.01k
        aXmlWriter.startElement("xmp:CreateDate");
346
4.01k
        aXmlWriter.content(m_sCreateDate);
347
4.01k
        aXmlWriter.endElement();
348
349
4.01k
        aXmlWriter.startElement("xmp:ModifyDate");
350
4.01k
        aXmlWriter.content(m_sCreateDate);
351
4.01k
        aXmlWriter.endElement();
352
353
4.01k
        aXmlWriter.startElement("xmp:MetadataDate");
354
4.01k
        aXmlWriter.content(m_sCreateDate);
355
4.01k
        aXmlWriter.endElement();
356
357
4.01k
        aXmlWriter.endElement();
358
4.01k
        aXmlWriter.endElement();
359
4.01k
        aXmlWriter.endElement();
360
4.01k
        aXmlWriter.endDocument();
361
4.01k
    }
362
363
    // add padding (needed so the metadata can be changed in-place"
364
88.2k
    for (sal_Int32 nSpaces = 1; nSpaces <= 21; nSpaces++)
365
84.2k
        mpMemoryStream->WriteOString(constPadding);
366
367
4.01k
    mpMemoryStream->WriteOString("<?xpacket end=\"w\"?>\n");
368
4.01k
    mbWritten = true;
369
4.01k
}
370
371
sal_uInt64 XmpMetadata::getSize()
372
8.02k
{
373
8.02k
    if (!mbWritten)
374
4.01k
        write();
375
8.02k
    return mpMemoryStream->GetSize();
376
8.02k
}
377
378
const void* XmpMetadata::getData()
379
4.01k
{
380
4.01k
    if (!mbWritten)
381
0
        write();
382
4.01k
    return mpMemoryStream->GetData();
383
4.01k
}
384
385
} // end vcl::pdf
386
387
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */