/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: */ |