Coverage Report

Created: 2025-12-08 09:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/source/pdf/pdfwriterimpl_utils.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
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <sal/types.h>
21
#include <rtl/character.hxx>
22
#include <rtl/math.hxx>
23
#include <osl/time.h>
24
#include <comphelper/hash.hxx>
25
26
#include <pdf/COSWriter.hxx>
27
28
#include "pdfwriter_utils.hxx"
29
30
#include <memory>
31
#include <stdlib.h>
32
33
namespace vcl::pdf
34
{
35
36
void appendObjectID(sal_Int32 nObjectID, OStringBuffer & aLine)
37
6
{
38
6
    aLine.append(nObjectID);
39
6
    aLine.append(" 0 obj\n");
40
6
}
41
42
void appendObjectReference(sal_Int32 nObjectID, OStringBuffer & aLine)
43
6
{
44
6
    aLine.append(nObjectID);
45
6
    aLine.append(" 0 R ");
46
6
}
47
48
/*
49
 * Convert a string before using it.
50
 *
51
 * This string conversion function is needed because the destination name
52
 * in a PDF file seen through an Internet browser should be
53
 * specially crafted, in order to be used directly by the browser.
54
 * In this way the fragment part of a hyperlink to a PDF file (e.g. something
55
 * as 'test1/test2/a-file.pdf\#thefragment) will be (hopefully) interpreted by the
56
 * PDF reader (currently only Adobe Reader plug-in seems to be working that way) called
57
 * from inside the Internet browser as: 'open the file test1/test2/a-file.pdf
58
 * and go to named destination thefragment using default zoom'.
59
 * The conversion is needed because in case of a fragment in the form: Slide%201
60
 * (meaning Slide 1) as it is converted obeying the Inet rules, it will become Slide25201
61
 * using this conversion, in both the generated named destinations, fragment and GoToR
62
 * destination.
63
 *
64
 * The names for destinations are name objects and so they don't need to be encrypted
65
 * even though they expose the content of PDF file (e.g. guessing the PDF content from the
66
 * destination name).
67
 *
68
 * Further limitation: it is advisable to use standard ASCII characters for
69
 * OOo bookmarks.
70
*/
71
void appendDestinationName( std::u16string_view rString, OStringBuffer& rBuffer )
72
0
{
73
0
    for( auto aChar: rString)
74
0
    {
75
0
        if( rtl::isAsciiAlphanumeric(aChar) || aChar == '-' )
76
0
        {
77
0
            rBuffer.append(static_cast<char>(aChar));
78
0
        }
79
0
        else
80
0
        {
81
0
            const sal_Int8 nValueHigh = sal_Int8(aChar >> 8);
82
83
0
            if (nValueHigh > 0)
84
0
                COSWriter::appendHex(nValueHigh, rBuffer);
85
86
0
            COSWriter::appendHex(static_cast<sal_Int8>(aChar & 255 ), rBuffer);
87
0
        }
88
0
    }
89
0
}
90
91
void appendFixedInt( sal_Int32 nValue, OStringBuffer& rBuffer )
92
1.31M
{
93
1.31M
    if( nValue < 0 )
94
103
    {
95
103
        rBuffer.append( '-' );
96
103
        nValue = -nValue;
97
103
    }
98
1.31M
    sal_Int32 nFactor = 1, nDiv = nLog10Divisor;
99
5.26M
    while( nDiv-- )
100
3.95M
        nFactor *= 10;
101
102
1.31M
    sal_Int32 nInt = nValue / nFactor;
103
1.31M
    rBuffer.append( nInt );
104
1.31M
    if (nFactor > 1 && nValue % nFactor)
105
883k
    {
106
883k
        rBuffer.append( '.' );
107
883k
        do
108
1.91M
        {
109
1.91M
            nFactor /= 10;
110
1.91M
            rBuffer.append((nValue / nFactor) % 10);
111
1.91M
        }
112
1.91M
        while (nFactor > 1 && nValue % nFactor); // omit trailing zeros
113
883k
    }
114
1.31M
}
115
116
// appends a double. PDF does not accept exponential format, only fixed point
117
void appendDouble(double fValue, OStringBuffer& rBuffer, sal_Int32 nPrecision)
118
1.69M
{
119
1.69M
    rtl::math::doubleToStringBuffer(rBuffer, fValue, rtl_math_StringFormat_F, nPrecision, '.', true);
120
1.69M
}
121
122
void appendColor( const Color& rColor, OStringBuffer& rBuffer, bool bConvertToGrey )
123
409k
{
124
409k
    if( rColor == COL_TRANSPARENT )
125
0
        return;
126
127
409k
    if( bConvertToGrey )
128
0
    {
129
0
        sal_uInt8 cByte = rColor.GetLuminance();
130
0
        appendDouble( cByte / 255.0, rBuffer );
131
0
    }
132
409k
    else
133
409k
    {
134
409k
        appendDouble( rColor.GetRed() / 255.0, rBuffer );
135
409k
        rBuffer.append( ' ' );
136
409k
        appendDouble( rColor.GetGreen() / 255.0, rBuffer );
137
409k
        rBuffer.append( ' ' );
138
409k
        appendDouble( rColor.GetBlue() / 255.0, rBuffer );
139
409k
    }
140
409k
}
141
142
void appendPdfTimeDate(OStringBuffer & rBuffer,
143
    sal_Int16 year, sal_uInt16 month, sal_uInt16 day, sal_uInt16 hours, sal_uInt16 minutes, sal_uInt16 seconds, sal_Int32 tzDelta)
144
4.26k
{
145
4.26k
    rBuffer.append("D:");
146
4.26k
    rBuffer.append(char('0' + ((year / 1000) % 10)));
147
4.26k
    rBuffer.append(char('0' + ((year / 100) % 10)));
148
4.26k
    rBuffer.append(char('0' + ((year / 10) % 10)));
149
4.26k
    rBuffer.append(char('0' + (year % 10)));
150
4.26k
    rBuffer.append(char('0' + ((month / 10) % 10)));
151
4.26k
    rBuffer.append(char('0' + (month % 10)));
152
4.26k
    rBuffer.append(char('0' + ((day / 10) % 10)));
153
4.26k
    rBuffer.append(char('0' + (day % 10)));
154
4.26k
    rBuffer.append(char('0' + ((hours / 10) % 10)));
155
4.26k
    rBuffer.append(char('0' + (hours % 10)));
156
4.26k
    rBuffer.append(char('0' + ((minutes / 10) % 10)));
157
4.26k
    rBuffer.append(char('0' + (minutes % 10)));
158
4.26k
    rBuffer.append(char('0' + ((seconds / 10) % 10)));
159
4.26k
    rBuffer.append(char('0' + (seconds % 10)));
160
161
4.26k
    if (tzDelta == 0)
162
4.26k
    {
163
4.26k
        rBuffer.append("Z");
164
4.26k
        return;
165
4.26k
    }
166
167
0
    if (tzDelta > 0 )
168
0
        rBuffer.append("+");
169
0
    else
170
0
    {
171
0
        rBuffer.append("-");
172
0
        tzDelta = -tzDelta;
173
0
    }
174
175
0
    rBuffer.append(char('0' + ((tzDelta / 36000) % 10)));
176
0
    rBuffer.append(char('0' + ((tzDelta / 3600) % 10)));
177
0
    rBuffer.append("'");
178
0
    rBuffer.append(char('0' + ((tzDelta / 600) % 6)));
179
0
    rBuffer.append(char('0' + ((tzDelta / 60) % 10)));
180
0
}
181
182
const char* getPDFVersionStr(PDFWriter::PDFVersion ePDFVersion)
183
8.52k
{
184
8.52k
    switch (ePDFVersion)
185
8.52k
    {
186
0
        case PDFWriter::PDFVersion::PDF_A_1:
187
0
        case PDFWriter::PDFVersion::PDF_1_4:
188
0
            return "1.4";
189
0
        case PDFWriter::PDFVersion::PDF_1_5:
190
0
            return "1.5";
191
0
        case PDFWriter::PDFVersion::PDF_1_6:
192
0
            return "1.6";
193
0
        default:
194
0
        case PDFWriter::PDFVersion::PDF_A_2:
195
0
        case PDFWriter::PDFVersion::PDF_A_3:
196
8.52k
        case PDFWriter::PDFVersion::PDF_1_7:
197
8.52k
            return "1.7";
198
        // PDF 2.0
199
0
        case PDFWriter::PDFVersion::PDF_A_4:
200
0
        case PDFWriter::PDFVersion::PDF_2_0:
201
0
            return "2.0";
202
8.52k
    }
203
8.52k
}
204
205
void computeDocumentIdentifier(std::vector<sal_uInt8>& o_rIdentifier,
206
                               const PDFWriter::PDFDocInfo& i_rDocInfo,
207
                               const OString& i_rCString1,
208
                               const css::util::DateTime& rCreationMetaDate, OString& o_rCString2)
209
4.26k
{
210
4.26k
    o_rIdentifier.clear();
211
212
    //build the document id
213
4.26k
    OString aInfoValuesOut;
214
4.26k
    OStringBuffer aID(1024);
215
4.26k
    if (!i_rDocInfo.Title.isEmpty())
216
393
        COSWriter::appendUnicodeTextString(i_rDocInfo.Title, aID);
217
4.26k
    if (!i_rDocInfo.Author.isEmpty())
218
297
        COSWriter::appendUnicodeTextString(i_rDocInfo.Author, aID);
219
4.26k
    if (!i_rDocInfo.Subject.isEmpty())
220
3
        COSWriter::appendUnicodeTextString(i_rDocInfo.Subject, aID);
221
4.26k
    if (!i_rDocInfo.Keywords.isEmpty())
222
295
        COSWriter::appendUnicodeTextString(i_rDocInfo.Keywords, aID);
223
4.26k
    if (!i_rDocInfo.Creator.isEmpty())
224
4.26k
        COSWriter::appendUnicodeTextString(i_rDocInfo.Creator, aID);
225
4.26k
    if (!i_rDocInfo.Producer.isEmpty())
226
0
        COSWriter::appendUnicodeTextString(i_rDocInfo.Producer, aID);
227
228
4.26k
    TimeValue aTVal, aGMT;
229
4.26k
    oslDateTime aDT;
230
4.26k
    aDT.NanoSeconds = rCreationMetaDate.NanoSeconds;
231
4.26k
    aDT.Seconds = rCreationMetaDate.Seconds;
232
4.26k
    aDT.Minutes = rCreationMetaDate.Minutes;
233
4.26k
    aDT.Hours = rCreationMetaDate.Hours;
234
4.26k
    aDT.Day = rCreationMetaDate.Day;
235
4.26k
    aDT.Month = rCreationMetaDate.Month;
236
4.26k
    aDT.Year = rCreationMetaDate.Year;
237
238
4.26k
    osl_getSystemTime(&aGMT);
239
4.26k
    osl_getLocalTimeFromSystemTime(&aGMT, &aTVal);
240
4.26k
    OStringBuffer aCreationMetaDateString(64);
241
242
    // i59651: we fill the Metadata date string as well, if PDF/A is requested
243
    // according to ISO 19005-1:2005 6.7.3 the date is corrected for
244
    // local time zone offset UTC only, whereas Acrobat 8 seems
245
    // to use the localtime notation only
246
    // according to a recommendation in XMP Specification (Jan 2004, page 75)
247
    // the Acrobat way seems the right approach
248
4.26k
    aCreationMetaDateString.append(
249
4.26k
        OStringChar(char('0' + ((aDT.Year / 1000) % 10)))
250
4.26k
        + OStringChar(char('0' + ((aDT.Year / 100) % 10)))
251
4.26k
        + OStringChar(char('0' + ((aDT.Year / 10) % 10)))
252
4.26k
        + OStringChar(char('0' + ((aDT.Year) % 10)))
253
4.26k
        + OStringChar('-')
254
4.26k
        + OStringChar(char('0' + ((aDT.Month / 10) % 10)))
255
4.26k
        + OStringChar(char('0' + ((aDT.Month) % 10)))
256
4.26k
        + OStringChar('-')
257
4.26k
        + OStringChar(char('0' + ((aDT.Day / 10) % 10)))
258
4.26k
        + OStringChar(char('0' + ((aDT.Day) % 10)))
259
4.26k
        + OStringChar('T')
260
4.26k
        + OStringChar(char('0' + ((aDT.Hours / 10) % 10)))
261
4.26k
        + OStringChar(char('0' + ((aDT.Hours) % 10)))
262
4.26k
        + OStringChar(':')
263
4.26k
        + OStringChar(char('0' + ((aDT.Minutes / 10) % 10)))
264
4.26k
        + OStringChar(char('0' + ((aDT.Minutes) % 10)))
265
4.26k
        + OStringChar(':')
266
4.26k
        + OStringChar(char('0' + ((aDT.Seconds / 10) % 10)))
267
4.26k
        + OStringChar(char('0' + ((aDT.Seconds) % 10))));
268
269
4.26k
    sal_uInt32 nDelta = 0;
270
4.26k
    if (aGMT.Seconds > aTVal.Seconds)
271
0
    {
272
0
        nDelta = aGMT.Seconds - aTVal.Seconds;
273
0
        aCreationMetaDateString.append("-");
274
0
    }
275
4.26k
    else if (aGMT.Seconds < aTVal.Seconds)
276
0
    {
277
0
        nDelta = aTVal.Seconds - aGMT.Seconds;
278
0
        aCreationMetaDateString.append("+");
279
0
    }
280
4.26k
    else
281
4.26k
    {
282
4.26k
        aCreationMetaDateString.append("Z");
283
4.26k
    }
284
4.26k
    if (nDelta)
285
0
    {
286
0
        aCreationMetaDateString.append(char('0' + ((nDelta / 36000) % 10)));
287
0
        aCreationMetaDateString.append(char('0' + ((nDelta / 3600) % 10)));
288
0
        aCreationMetaDateString.append(":");
289
0
        aCreationMetaDateString.append(char('0' + ((nDelta / 600) % 6)));
290
0
        aCreationMetaDateString.append(char('0' + ((nDelta / 60) % 10)));
291
0
    }
292
4.26k
    aID.append(i_rCString1.getStr(), i_rCString1.getLength());
293
294
4.26k
    aInfoValuesOut = aID.makeStringAndClear();
295
4.26k
    o_rCString2 = aCreationMetaDateString.makeStringAndClear();
296
297
4.26k
    ::comphelper::Hash aDigest(::comphelper::HashType::MD5);
298
4.26k
    aDigest.update(&aGMT, sizeof(aGMT));
299
4.26k
    aDigest.update(aInfoValuesOut.getStr(), aInfoValuesOut.getLength());
300
    //the binary form of the doc id is needed for encryption stuff
301
4.26k
    o_rIdentifier = aDigest.finalize();
302
4.26k
}
303
304
} // end namespace vcl::pdf
305
306
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */