/src/libreoffice/vcl/source/pdf/pdfwriter_impl.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/config.h> |
21 | | #include <config_crypto.h> |
22 | | |
23 | | #include <sal/types.h> |
24 | | |
25 | | #include <math.h> |
26 | | #include <algorithm> |
27 | | #include <ranges> |
28 | | #include <string_view> |
29 | | |
30 | | #include <lcms2.h> |
31 | | |
32 | | #include <basegfx/matrix/b2dhommatrix.hxx> |
33 | | #include <basegfx/polygon/b2dpolygon.hxx> |
34 | | #include <basegfx/polygon/b2dpolygontools.hxx> |
35 | | #include <basegfx/polygon/b2dpolypolygon.hxx> |
36 | | #include <basegfx/polygon/b2dpolypolygoncutter.hxx> |
37 | | #include <basegfx/polygon/b2dpolypolygontools.hxx> |
38 | | #include <basegfx/vector/b2dsize.hxx> |
39 | | #include <memory> |
40 | | #include <com/sun/star/awt/GradientStyle.hpp> |
41 | | #include <com/sun/star/io/XOutputStream.hpp> |
42 | | #include <com/sun/star/util/URL.hpp> |
43 | | #include <com/sun/star/util/URLTransformer.hpp> |
44 | | #include <comphelper/processfactory.hxx> |
45 | | #include <comphelper/string.hxx> |
46 | | #include <comphelper/xmlencode.hxx> |
47 | | #include <cppuhelper/implbase.hxx> |
48 | | #include <i18nlangtag/languagetag.hxx> |
49 | | #include <o3tl/numeric.hxx> |
50 | | #include <o3tl/safeint.hxx> |
51 | | #include <o3tl/temporary.hxx> |
52 | | #include <officecfg/Office/Common.hxx> |
53 | | #include <osl/diagnose.h> |
54 | | #include <osl/file.hxx> |
55 | | #include <osl/thread.h> |
56 | | #include <rtl/crc.h> |
57 | | #include <o3tl/string_view.hxx> |
58 | | #include <rtl/character.hxx> |
59 | | #include <rtl/digest.h> |
60 | | #include <rtl/uri.hxx> |
61 | | #include <rtl/ustrbuf.hxx> |
62 | | #include <svl/cryptosign.hxx> |
63 | | #include <sal/log.hxx> |
64 | | #include <svl/urihelper.hxx> |
65 | | #include <tools/fract.hxx> |
66 | | #include <tools/stream.hxx> |
67 | | #include <tools/helpers.hxx> |
68 | | #include <tools/urlobj.hxx> |
69 | | #include <tools/UnitConversion.hxx> |
70 | | #include <tools/zcodec.hxx> |
71 | | #include <unotools/configmgr.hxx> |
72 | | #include <vcl/bitmap.hxx> |
73 | | #include <vcl/canvastools.hxx> |
74 | | #include <vcl/cvtgrf.hxx> |
75 | | #include <vcl/fontcharmap.hxx> |
76 | | #include <vcl/fntstyle.hxx> |
77 | | #include <vcl/glyphitemcache.hxx> |
78 | | #include <vcl/kernarray.hxx> |
79 | | #include <vcl/lineinfo.hxx> |
80 | | #include <vcl/metric.hxx> |
81 | | #include <vcl/mnemonic.hxx> |
82 | | #include <vcl/pdfread.hxx> |
83 | | #include <vcl/rendercontext/DrawModeFlags.hxx> |
84 | | #include <vcl/settings.hxx> |
85 | | #include <strhelper.hxx> |
86 | | #include <vcl/svapp.hxx> |
87 | | #include <vcl/vectorgraphicdata.hxx> |
88 | | #include <vcl/virdev.hxx> |
89 | | #include <vcl/filter/pdfdocument.hxx> |
90 | | #include <vcl/filter/PngImageReader.hxx> |
91 | | #include <comphelper/hash.hxx> |
92 | | #include <vcl/pdf/PDFNote.hxx> |
93 | | |
94 | | #include <svdata.hxx> |
95 | | #include <vcl/BitmapWriteAccess.hxx> |
96 | | #include <pdf/COSWriter.hxx> |
97 | | #include <fontsubset.hxx> |
98 | | #include <font/EmphasisMark.hxx> |
99 | | #include <font/PhysicalFontFace.hxx> |
100 | | #include <salgdi.hxx> |
101 | | #include <textlayout.hxx> |
102 | | #include <textlineinfo.hxx> |
103 | | #include <impglyphitem.hxx> |
104 | | #include <pdf/XmpMetadata.hxx> |
105 | | #include <pdf/objectcopier.hxx> |
106 | | #include <pdf/pdfwriter_impl.hxx> |
107 | | #include <pdf/PdfConfig.hxx> |
108 | | #include <pdf/PDFEncryptorR6.hxx> |
109 | | #include <pdf/PDFEncryptor.hxx> |
110 | | #include <o3tl/sorted_vector.hxx> |
111 | | #include <frozen/bits/defines.h> |
112 | | #include <frozen/bits/elsa_std.h> |
113 | | #include <frozen/unordered_map.h> |
114 | | |
115 | | #include "pdfwriter_utils.hxx" |
116 | | |
117 | | using namespace::com::sun::star; |
118 | | |
119 | | static bool g_bDebugDisableCompression = getenv("VCL_DEBUG_DISABLE_PDFCOMPRESSION"); |
120 | | |
121 | | namespace vcl |
122 | | { |
123 | | |
124 | | constexpr sal_Int32 PDFWRITER_IMPL_BUFFERSIZE = 1024; |
125 | | |
126 | | namespace |
127 | | { |
128 | | |
129 | | void removePlaceholderSE(std::vector<PDFStructureElement> & rStructure, PDFStructureElement& rEle); |
130 | | |
131 | | // a reader inflates a whole object stream to reach any one object in it |
132 | | constexpr size_t nMaxObjectsPerStream = 200; |
133 | | |
134 | | // deflates a buffer for a /FlateDecode stream whose /Length has to be known before it is written |
135 | | std::vector<sal_uInt8> deflateBuffer(std::string_view aData) |
136 | 0 | { |
137 | 0 | if (g_bDebugDisableCompression) |
138 | 0 | return { aData.begin(), aData.end() }; |
139 | | |
140 | 0 | SvMemoryStream aInput(const_cast<char*>(aData.data()), aData.size(), StreamMode::READ); |
141 | 0 | SvMemoryStream aOutput(aData.size(), 0x4000); |
142 | 0 | ZCodec aCodec(0x4000, 0x4000); |
143 | 0 | aCodec.BeginCompression(); |
144 | 0 | aCodec.Compress(aInput, aOutput); |
145 | | // it answers with the bytes it took, and a short count would leave a stream that does not |
146 | | // hold what its /Length claims |
147 | 0 | if (aCodec.EndCompression() != static_cast<tools::Long>(aData.size())) |
148 | 0 | return {}; |
149 | | |
150 | 0 | const sal_uInt8* pBegin = static_cast<const sal_uInt8*>(aOutput.GetData()); |
151 | 0 | return { pBegin, pBegin + aOutput.Tell() }; |
152 | 0 | } |
153 | | |
154 | | // the types ISO 32000-2 14.8.6 left out of the PDF 2.0 standard structure namespace |
155 | | bool isPDF17OnlyType(StructElement eType) |
156 | 0 | { |
157 | 0 | switch (eType) |
158 | 0 | { |
159 | 0 | case StructElement::Article: |
160 | 0 | case StructElement::BlockQuote: |
161 | 0 | case StructElement::TOC: |
162 | 0 | case StructElement::TOCI: |
163 | 0 | case StructElement::Index: |
164 | 0 | case StructElement::Quote: |
165 | 0 | case StructElement::Note: |
166 | 0 | case StructElement::Reference: |
167 | 0 | case StructElement::BibEntry: |
168 | 0 | case StructElement::Code: |
169 | 0 | return true; |
170 | 0 | default: |
171 | 0 | return false; |
172 | 0 | } |
173 | 0 | } |
174 | | |
175 | | // the attributes this writer can set that ISO 32000-2 14.8.5.4 measures in user space units; |
176 | | // every numerical layout attribute prints as thousandths of a point, so a new one belongs here |
177 | | bool isLengthAttribute(PDFWriter::StructAttribute eAttr) |
178 | 0 | { |
179 | 0 | switch (eAttr) |
180 | 0 | { |
181 | 0 | case PDFWriter::SpaceBefore: |
182 | 0 | case PDFWriter::SpaceAfter: |
183 | 0 | case PDFWriter::StartIndent: |
184 | 0 | case PDFWriter::EndIndent: |
185 | 0 | case PDFWriter::TextIndent: |
186 | 0 | case PDFWriter::Width: |
187 | 0 | case PDFWriter::Height: |
188 | 0 | case PDFWriter::LineHeight: |
189 | 0 | case PDFWriter::BaselineShift: |
190 | 0 | return true; |
191 | 0 | default: |
192 | 0 | return false; |
193 | 0 | } |
194 | 0 | } |
195 | | |
196 | | constexpr auto constTagStrings = frozen::make_unordered_map<StructElement, const char*>({ |
197 | | { StructElement::NonStructElement, "NonStruct" }, |
198 | | { StructElement::Document, "Document" }, |
199 | | { StructElement::Part, "Part" }, |
200 | | { StructElement::Article, "Art" }, |
201 | | { StructElement::Section, "Sect" }, |
202 | | { StructElement::Division, "Div" }, |
203 | | { StructElement::BlockQuote, "BlockQuote" }, |
204 | | { StructElement::Caption, "Caption" }, |
205 | | { StructElement::TOC, "TOC" }, |
206 | | { StructElement::TOCI, "TOCI" }, |
207 | | { StructElement::Index, "Index" }, |
208 | | { StructElement::Paragraph, "P" }, |
209 | | { StructElement::Heading, "H" }, |
210 | | { StructElement::H1, "H1" }, |
211 | | { StructElement::H2, "H2" }, |
212 | | { StructElement::H3, "H3" }, |
213 | | { StructElement::H4, "H4" }, |
214 | | { StructElement::H5, "H5" }, |
215 | | { StructElement::H6, "H6" }, |
216 | | { StructElement::List, "L" }, |
217 | | { StructElement::ListItem, "LI" }, |
218 | | { StructElement::LILabel, "Lbl" }, |
219 | | { StructElement::LIBody, "LBody" }, |
220 | | { StructElement::Table, "Table" }, |
221 | | { StructElement::TableRow, "TR" }, |
222 | | { StructElement::TableHeader, "TH" }, |
223 | | { StructElement::TableData, "TD" }, |
224 | | { StructElement::Span, "Span" }, |
225 | | { StructElement::Quote, "Quote" }, |
226 | | { StructElement::Note, "Note" }, |
227 | | { StructElement::Reference, "Reference" }, |
228 | | { StructElement::BibEntry, "BibEntry" }, |
229 | | { StructElement::Code, "Code" }, |
230 | | { StructElement::Link, "Link" }, |
231 | | { StructElement::Annot, "Annot" }, |
232 | | { StructElement::Ruby, "Ruby" }, |
233 | | { StructElement::RB, "RB" }, |
234 | | { StructElement::RT, "RT" }, |
235 | | { StructElement::RP, "RP" }, |
236 | | { StructElement::Warichu, "Warichu" }, |
237 | | { StructElement::WT, "WT" }, |
238 | | { StructElement::WP, "WP" }, |
239 | | { StructElement::Figure, "Figure" }, |
240 | | { StructElement::Formula, "Formula" }, |
241 | | { StructElement::Form, "Form" }, |
242 | | { StructElement::Title, "Title" }, |
243 | | { StructElement::Emphasis, "Em" }, |
244 | | { StructElement::Strong, "Strong" }, |
245 | | { StructElement::FENote, "FENote" }, |
246 | | }); |
247 | | |
248 | | // standard structure types getStructureTag never returns, so the table above lacks them: |
249 | | // ISO 32000-1 14.8.4, and the ones ISO 32000-2 adds |
250 | | // Annot is standard from PDF 1.5, and below that getStructureTag answers Figure for it |
251 | | constexpr std::string_view constUnusedTagStrings[]{ "Annot", "Artifact", "Private", |
252 | | "THead", "TBody", "TFoot" }; |
253 | | constexpr std::string_view constUnusedTagStrings20[]{ "Aside", "DocumentFragment", "Sub" }; |
254 | | |
255 | | // PDF 2.0 has heading levels without a limit, so H7 and up are standard there as well; |
256 | | // H1 to H6 are the table's, and must not answer here, or the level could be claimed wrong |
257 | | bool isDeepHeading(std::string_view aName) |
258 | 0 | { |
259 | 0 | return aName.size() > 1 && aName[0] == 'H' && aName[1] != '0' |
260 | 0 | && std::ranges::all_of( |
261 | 0 | aName.substr(1), |
262 | 0 | [](char c) { return rtl::isAsciiDigit(static_cast<sal_uInt8>(c)); }) |
263 | 0 | && o3tl::toInt32(aName.substr(1)) > 6; |
264 | 0 | } |
265 | | |
266 | | } // end anonymous namespace |
267 | | |
268 | | void PDFWriterImpl::createWidgetFieldName( sal_Int32 i_nWidgetIndex, const PDFWriter::AnyWidget& i_rControl ) |
269 | 0 | { |
270 | | /* #i80258# previously we use appendName here |
271 | | however we need a slightly different coding scheme than the normal |
272 | | name encoding for field names |
273 | | */ |
274 | 0 | const OUString& rName = i_rControl.Name; |
275 | 0 | OString aStr( OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ) ); |
276 | 0 | int nLen = aStr.getLength(); |
277 | |
|
278 | 0 | OStringBuffer aBuffer( rName.getLength()+64 ); |
279 | 0 | for( int i = 0; i < nLen; i++ ) |
280 | 0 | { |
281 | | /* #i16920# PDF recommendation: output UTF8, any byte |
282 | | * outside the interval [32(=ASCII' ');126(=ASCII'~')] |
283 | | * should be escaped hexadecimal |
284 | | */ |
285 | 0 | if( aStr[i] >= 32 && aStr[i] <= 126 ) |
286 | 0 | aBuffer.append( aStr[i] ); |
287 | 0 | else |
288 | 0 | { |
289 | 0 | aBuffer.append( '#' ); |
290 | 0 | COSWriter::appendHex(static_cast<sal_Int8>(aStr[i]), aBuffer); |
291 | 0 | } |
292 | 0 | } |
293 | |
|
294 | 0 | OString aFullName( aBuffer.makeStringAndClear() ); |
295 | | |
296 | | /* #i82785# create hierarchical fields down to the for each dot in i_rName */ |
297 | 0 | sal_Int32 nTokenIndex = 0, nLastTokenIndex = 0; |
298 | 0 | OString aPartialName; |
299 | 0 | OString aDomain; |
300 | 0 | do |
301 | 0 | { |
302 | 0 | nLastTokenIndex = nTokenIndex; |
303 | 0 | aPartialName = aFullName.getToken( 0, '.', nTokenIndex ); |
304 | 0 | if( nTokenIndex != -1 ) |
305 | 0 | { |
306 | | // find or create a hierarchical field |
307 | | // first find the fully qualified name up to this field |
308 | 0 | aDomain = aFullName.copy( 0, nTokenIndex-1 ); |
309 | 0 | std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain ); |
310 | 0 | if( it == m_aFieldNameMap.end() ) |
311 | 0 | { |
312 | | // create new hierarchy field |
313 | 0 | sal_Int32 nNewWidget = m_aWidgets.size(); |
314 | 0 | m_aWidgets.emplace_back( ); |
315 | 0 | m_aWidgets[nNewWidget].m_nObject = createObject(); |
316 | 0 | m_aWidgets[nNewWidget].m_eType = PDFWriter::Hierarchy; |
317 | 0 | m_aWidgets[nNewWidget].m_aName = aPartialName; |
318 | 0 | m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject; |
319 | 0 | m_aFieldNameMap[aDomain] = nNewWidget; |
320 | 0 | m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject; |
321 | 0 | if( nLastTokenIndex > 0 ) |
322 | 0 | { |
323 | | // this field is not a root field and |
324 | | // needs to be inserted to its parent |
325 | 0 | OString aParentDomain( aDomain.copy( 0, nLastTokenIndex-1 ) ); |
326 | 0 | it = m_aFieldNameMap.find( aParentDomain ); |
327 | 0 | OSL_ENSURE( it != m_aFieldNameMap.end(), "field name not found" ); |
328 | 0 | if( it != m_aFieldNameMap.end() ) |
329 | 0 | { |
330 | 0 | OSL_ENSURE( it->second < sal_Int32(m_aWidgets.size()), "invalid field number entry" ); |
331 | 0 | if( it->second < sal_Int32(m_aWidgets.size()) ) |
332 | 0 | { |
333 | 0 | PDFWidget& rParentField( m_aWidgets[it->second] ); |
334 | 0 | rParentField.m_aKids.push_back( m_aWidgets[nNewWidget].m_nObject ); |
335 | 0 | rParentField.m_aKidsIndex.push_back( nNewWidget ); |
336 | 0 | m_aWidgets[nNewWidget].m_nParent = rParentField.m_nObject; |
337 | 0 | } |
338 | 0 | } |
339 | 0 | } |
340 | 0 | } |
341 | 0 | else if( m_aWidgets[it->second].m_eType != PDFWriter::Hierarchy ) |
342 | 0 | { |
343 | | // this is invalid, someone tries to have a terminal field as parent |
344 | | // example: a button with the name foo.bar exists and |
345 | | // another button is named foo.bar.no |
346 | | // workaround: put the second terminal field as much up in the hierarchy as |
347 | | // necessary to have a non-terminal field as parent (or none at all) |
348 | | // since it->second already is terminal, we just need to use its parent |
349 | 0 | aDomain.clear(); |
350 | 0 | aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 ); |
351 | 0 | if( nLastTokenIndex > 0 ) |
352 | 0 | { |
353 | 0 | aDomain = aFullName.copy( 0, nLastTokenIndex-1 ); |
354 | 0 | aFullName = aDomain + "." + aPartialName; |
355 | 0 | } |
356 | 0 | else |
357 | 0 | aFullName = aPartialName; |
358 | 0 | break; |
359 | 0 | } |
360 | 0 | } |
361 | 0 | } while( nTokenIndex != -1 ); |
362 | | |
363 | | // insert widget into its hierarchy field |
364 | 0 | if( !aDomain.isEmpty() ) |
365 | 0 | { |
366 | 0 | std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain ); |
367 | 0 | if( it != m_aFieldNameMap.end() ) |
368 | 0 | { |
369 | 0 | OSL_ENSURE( it->second >= 0 && o3tl::make_unsigned(it->second) < m_aWidgets.size(), "invalid field index" ); |
370 | 0 | if( it->second >= 0 && o3tl::make_unsigned(it->second) < m_aWidgets.size() ) |
371 | 0 | { |
372 | 0 | m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[it->second].m_nObject; |
373 | 0 | m_aWidgets[it->second].m_aKids.push_back( m_aWidgets[i_nWidgetIndex].m_nObject); |
374 | 0 | m_aWidgets[it->second].m_aKidsIndex.push_back( i_nWidgetIndex ); |
375 | 0 | } |
376 | 0 | } |
377 | 0 | } |
378 | |
|
379 | 0 | if( aPartialName.isEmpty() ) |
380 | 0 | { |
381 | | // how funny, an empty field name |
382 | 0 | if( i_rControl.getType() == PDFWriter::RadioButton ) |
383 | 0 | { |
384 | 0 | aPartialName = "RadioGroup" + |
385 | 0 | OString::number( static_cast<const PDFWriter::RadioButtonWidget&>(i_rControl).RadioGroup ); |
386 | 0 | } |
387 | 0 | else |
388 | 0 | aPartialName = "Widget"_ostr; |
389 | 0 | } |
390 | |
|
391 | 0 | if( ! m_aContext.AllowDuplicateFieldNames ) |
392 | 0 | { |
393 | 0 | std::unordered_map<OString, sal_Int32>::iterator it = m_aFieldNameMap.find( aFullName ); |
394 | |
|
395 | 0 | if( it != m_aFieldNameMap.end() ) // not unique |
396 | 0 | { |
397 | 0 | std::unordered_map< OString, sal_Int32 >::const_iterator check_it; |
398 | 0 | OString aTry; |
399 | 0 | sal_Int32 nTry = 2; |
400 | 0 | do |
401 | 0 | { |
402 | 0 | aTry = aFullName + "_" + OString::number(nTry++); |
403 | 0 | check_it = m_aFieldNameMap.find( aTry ); |
404 | 0 | } while( check_it != m_aFieldNameMap.end() ); |
405 | 0 | aFullName = aTry; |
406 | 0 | m_aFieldNameMap[ aFullName ] = i_nWidgetIndex; |
407 | 0 | aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 ); |
408 | 0 | } |
409 | 0 | else |
410 | 0 | m_aFieldNameMap[ aFullName ] = i_nWidgetIndex; |
411 | 0 | } |
412 | | |
413 | | // finally |
414 | 0 | m_aWidgets[i_nWidgetIndex].m_aName = aPartialName; |
415 | 0 | } |
416 | | |
417 | | void PDFWriterImpl::appendStrokingColor( const Color& rColor, OStringBuffer& rBuffer ) |
418 | 15.4k | { |
419 | 15.4k | if( rColor != COL_TRANSPARENT ) |
420 | 15.4k | { |
421 | 15.4k | const bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale; |
422 | 15.4k | appendColor( rColor, rBuffer, bGrey ); |
423 | 15.4k | rBuffer.append( bGrey ? " G" : " RG" ); |
424 | 15.4k | } |
425 | 15.4k | } |
426 | | |
427 | | void PDFWriterImpl::appendNonStrokingColor( const Color& rColor, OStringBuffer& rBuffer ) |
428 | 669k | { |
429 | 669k | if( rColor != COL_TRANSPARENT ) |
430 | 669k | { |
431 | 669k | const bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale; |
432 | 669k | appendColor( rColor, rBuffer, bGrey ); |
433 | 669k | rBuffer.append( bGrey ? " g" : " rg" ); |
434 | 669k | } |
435 | 669k | } |
436 | | |
437 | | PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext, |
438 | | const css::uno::Reference< css::beans::XMaterialHolder >& xEncryptionMaterialHolder, |
439 | | PDFWriter& i_rOuterFace) |
440 | 8.87k | : VirtualDevice(Application::GetDefaultDevice(), DeviceFormat::WITHOUT_ALPHA, OUTDEV_PDF), |
441 | 8.87k | m_aMapMode( MapUnit::MapPoint, Point(), 1.0 / pointToPixel(1), 1.0 / pointToPixel(1) ), |
442 | 8.87k | m_aWidgetStyleSettings(Application::GetSettings().GetStyleSettings()), |
443 | 8.87k | m_nCurrentStructElement( 0 ), |
444 | 8.87k | m_bEmitStructure( true ), |
445 | 8.87k | m_nNextFID( 1 ), |
446 | 8.87k | m_aPDFBmpCache(comphelper::IsFuzzing() ? 15 : |
447 | 8.87k | officecfg::Office::Common::VCL::PDFExportImageCacheSize::get()), |
448 | 8.87k | m_nCurrentPage( -1 ), |
449 | 8.87k | m_nCatalogObject(0), |
450 | 8.87k | m_nSignatureObject( -1 ), |
451 | 8.87k | m_nSignatureContentOffset( 0 ), |
452 | 8.87k | m_nSignatureLastByteRangeNoOffset( 0 ), |
453 | 8.87k | m_nResourceDict( -1 ), |
454 | 8.87k | m_nFontDictObject( -1 ), |
455 | 8.87k | m_aContext(rContext), |
456 | 8.87k | m_aFile(m_aContext.URL), |
457 | 8.87k | m_bOpen(false), |
458 | 8.87k | m_DocDigest(::comphelper::HashType::MD5), |
459 | 8.87k | m_rOuterFace( i_rOuterFace ) |
460 | 8.87k | { |
461 | 8.87k | m_aStructure.emplace_back( ); |
462 | 8.87k | m_aStructure[0].m_nOwnElement = 0; |
463 | 8.87k | m_aStructure[0].m_nParentElement = 0; |
464 | | //m_StructElementStack.push(0); |
465 | | |
466 | | // tdf#150786 use the same settings for widgets regardless of theme |
467 | 8.87k | m_aWidgetStyleSettings.SetStandardStyles(); |
468 | | |
469 | 8.87k | GraphicsState aState; |
470 | 8.87k | aState.m_aMapMode = m_aMapMode; |
471 | 8.87k | aState.m_aFont.SetFamilyName( u"Times"_ustr ); |
472 | 8.87k | aState.m_aFont.SetFontSize( Size( 0, 12 ) ); |
473 | | |
474 | 8.87k | m_aGraphicsStack.push_front( aState ); |
475 | | |
476 | 8.87k | osl::File::RC aError = m_aFile.open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create); |
477 | 8.87k | if (aError != osl::File::E_None) |
478 | 8.87k | { |
479 | 8.87k | if (aError == osl::File::E_EXIST) |
480 | 8.87k | { |
481 | 8.87k | aError = m_aFile.open(osl_File_OpenFlag_Write); |
482 | 8.87k | if (aError == osl::File::E_None) |
483 | 8.87k | aError = m_aFile.setSize(0); |
484 | 8.87k | } |
485 | 8.87k | } |
486 | 8.87k | if (aError != osl::File::E_None) |
487 | 0 | return; |
488 | | |
489 | 8.87k | m_bOpen = true; |
490 | | |
491 | | // setup DocInfo |
492 | 8.87k | setupDocInfo(); |
493 | | |
494 | 8.87k | if (xEncryptionMaterialHolder.is()) |
495 | 0 | { |
496 | 0 | if (m_aContext.Version == PDFWriter::PDFVersion::PDF_2_0 || m_aContext.Version == PDFWriter::PDFVersion::PDF_A_4) |
497 | 0 | m_pPDFEncryptor.reset(new PDFEncryptorR6); |
498 | 0 | else |
499 | 0 | m_pPDFEncryptor.reset(new PDFEncryptor); |
500 | 0 | m_pPDFEncryptor->prepareEncryption(xEncryptionMaterialHolder, m_aContext.Encryption); |
501 | 0 | } |
502 | | |
503 | 8.87k | if (m_pPDFEncryptor && m_aContext.Encryption.canEncrypt()) |
504 | 0 | { |
505 | 0 | m_pPDFEncryptor->setupKeysAndCheck(m_aContext.Encryption); |
506 | 0 | } |
507 | | |
508 | | // write header |
509 | 8.87k | if (!writeBuffer(Concat2View(OString::Concat("%PDF-") + getPDFVersionStr(m_aContext.Version) |
510 | 8.87k | + "\n%\303\244\303\274\303\266\303\237\n"))) |
511 | 0 | { |
512 | 0 | m_aFile.close(); |
513 | 0 | m_bOpen = false; |
514 | 0 | return; |
515 | 0 | } |
516 | | |
517 | | // insert outline root |
518 | 8.87k | m_aOutline.emplace_back( ); |
519 | | |
520 | 8.87k | switch (m_aContext.Version) |
521 | 8.87k | { |
522 | 0 | case PDFWriter::PDFVersion::PDF_A_1: |
523 | 0 | m_nPDFA_Version = 1; |
524 | 0 | m_bIsPDF_A1 = true; |
525 | 0 | m_aContext.Version = PDFWriter::PDFVersion::PDF_1_4; //meaning we need PDF 1.4, PDF/A flavour |
526 | 0 | break; |
527 | 0 | case PDFWriter::PDFVersion::PDF_A_2: |
528 | 0 | m_nPDFA_Version = 2; |
529 | 0 | m_bIsPDF_A2 = true; |
530 | 0 | m_aContext.Version = PDFWriter::PDFVersion::PDF_1_7; |
531 | 0 | break; |
532 | 0 | case PDFWriter::PDFVersion::PDF_A_3: |
533 | 0 | m_nPDFA_Version = 3; |
534 | 0 | m_bIsPDF_A3 = true; |
535 | 0 | m_aContext.Version = PDFWriter::PDFVersion::PDF_1_7; |
536 | 0 | break; |
537 | 0 | case PDFWriter::PDFVersion::PDF_A_4: |
538 | 0 | m_nPDFA_Version = 4; |
539 | 0 | m_bIsPDF_A4 = true; |
540 | 0 | m_aContext.Version = PDFWriter::PDFVersion::PDF_2_0; |
541 | 0 | break; |
542 | 8.87k | default: |
543 | 8.87k | break; |
544 | 8.87k | } |
545 | | |
546 | | // PDF/UA can only be enabled if PDF version is 1.7 (PDF/UA-1) and 2.0 (PDF/UA-2) |
547 | 8.87k | if (m_aContext.UniversalAccessibilityCompliance && m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_7) |
548 | 0 | { |
549 | 0 | m_bIsPDF_UA = true; |
550 | 0 | m_aContext.Tagged = true; |
551 | 0 | } |
552 | | |
553 | | // Add common PDF 2.0 namespace when we are using PDF 2.0 |
554 | 8.87k | if (m_aContext.Version == PDFWriter::PDFVersion::PDF_2_0) |
555 | 0 | { |
556 | 0 | m_aNamespacesMap.emplace(constNamespacePDF2, createObject()); |
557 | | // some types exist only in the PDF 1.7 namespace |
558 | 0 | m_aNamespacesMap.emplace(constNamespacePDF17, createObject()); |
559 | 0 | } |
560 | | |
561 | 8.87k | if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 ) |
562 | 8.87k | SetReferenceDevice( VirtualDevice::RefDevMode::PDF1 ); |
563 | 0 | else |
564 | 0 | SetReferenceDevice( m_aContext.DPIx, m_aContext.DPIy ); |
565 | | |
566 | 8.87k | SetOutputSizePixel( Size( 640, 480 ) ); |
567 | 8.87k | SetMapMode(MapMode(MapUnit::MapMM)); |
568 | 8.87k | } Unexecuted instantiation: vcl::PDFWriterImpl::PDFWriterImpl(vcl::pdf::PDFWriter::PDFWriterContext const&, com::sun::star::uno::Reference<com::sun::star::beans::XMaterialHolder> const&, vcl::pdf::PDFWriter&) vcl::PDFWriterImpl::PDFWriterImpl(vcl::pdf::PDFWriter::PDFWriterContext const&, com::sun::star::uno::Reference<com::sun::star::beans::XMaterialHolder> const&, vcl::pdf::PDFWriter&) Line | Count | Source | 440 | 8.87k | : VirtualDevice(Application::GetDefaultDevice(), DeviceFormat::WITHOUT_ALPHA, OUTDEV_PDF), | 441 | 8.87k | m_aMapMode( MapUnit::MapPoint, Point(), 1.0 / pointToPixel(1), 1.0 / pointToPixel(1) ), | 442 | 8.87k | m_aWidgetStyleSettings(Application::GetSettings().GetStyleSettings()), | 443 | 8.87k | m_nCurrentStructElement( 0 ), | 444 | 8.87k | m_bEmitStructure( true ), | 445 | 8.87k | m_nNextFID( 1 ), | 446 | 8.87k | m_aPDFBmpCache(comphelper::IsFuzzing() ? 15 : | 447 | 8.87k | officecfg::Office::Common::VCL::PDFExportImageCacheSize::get()), | 448 | 8.87k | m_nCurrentPage( -1 ), | 449 | 8.87k | m_nCatalogObject(0), | 450 | 8.87k | m_nSignatureObject( -1 ), | 451 | 8.87k | m_nSignatureContentOffset( 0 ), | 452 | 8.87k | m_nSignatureLastByteRangeNoOffset( 0 ), | 453 | 8.87k | m_nResourceDict( -1 ), | 454 | 8.87k | m_nFontDictObject( -1 ), | 455 | 8.87k | m_aContext(rContext), | 456 | 8.87k | m_aFile(m_aContext.URL), | 457 | 8.87k | m_bOpen(false), | 458 | 8.87k | m_DocDigest(::comphelper::HashType::MD5), | 459 | 8.87k | m_rOuterFace( i_rOuterFace ) | 460 | 8.87k | { | 461 | 8.87k | m_aStructure.emplace_back( ); | 462 | 8.87k | m_aStructure[0].m_nOwnElement = 0; | 463 | 8.87k | m_aStructure[0].m_nParentElement = 0; | 464 | | //m_StructElementStack.push(0); | 465 | | | 466 | | // tdf#150786 use the same settings for widgets regardless of theme | 467 | 8.87k | m_aWidgetStyleSettings.SetStandardStyles(); | 468 | | | 469 | 8.87k | GraphicsState aState; | 470 | 8.87k | aState.m_aMapMode = m_aMapMode; | 471 | 8.87k | aState.m_aFont.SetFamilyName( u"Times"_ustr ); | 472 | 8.87k | aState.m_aFont.SetFontSize( Size( 0, 12 ) ); | 473 | | | 474 | 8.87k | m_aGraphicsStack.push_front( aState ); | 475 | | | 476 | 8.87k | osl::File::RC aError = m_aFile.open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create); | 477 | 8.87k | if (aError != osl::File::E_None) | 478 | 8.87k | { | 479 | 8.87k | if (aError == osl::File::E_EXIST) | 480 | 8.87k | { | 481 | 8.87k | aError = m_aFile.open(osl_File_OpenFlag_Write); | 482 | 8.87k | if (aError == osl::File::E_None) | 483 | 8.87k | aError = m_aFile.setSize(0); | 484 | 8.87k | } | 485 | 8.87k | } | 486 | 8.87k | if (aError != osl::File::E_None) | 487 | 0 | return; | 488 | | | 489 | 8.87k | m_bOpen = true; | 490 | | | 491 | | // setup DocInfo | 492 | 8.87k | setupDocInfo(); | 493 | | | 494 | 8.87k | if (xEncryptionMaterialHolder.is()) | 495 | 0 | { | 496 | 0 | if (m_aContext.Version == PDFWriter::PDFVersion::PDF_2_0 || m_aContext.Version == PDFWriter::PDFVersion::PDF_A_4) | 497 | 0 | m_pPDFEncryptor.reset(new PDFEncryptorR6); | 498 | 0 | else | 499 | 0 | m_pPDFEncryptor.reset(new PDFEncryptor); | 500 | 0 | m_pPDFEncryptor->prepareEncryption(xEncryptionMaterialHolder, m_aContext.Encryption); | 501 | 0 | } | 502 | | | 503 | 8.87k | if (m_pPDFEncryptor && m_aContext.Encryption.canEncrypt()) | 504 | 0 | { | 505 | 0 | m_pPDFEncryptor->setupKeysAndCheck(m_aContext.Encryption); | 506 | 0 | } | 507 | | | 508 | | // write header | 509 | 8.87k | if (!writeBuffer(Concat2View(OString::Concat("%PDF-") + getPDFVersionStr(m_aContext.Version) | 510 | 8.87k | + "\n%\303\244\303\274\303\266\303\237\n"))) | 511 | 0 | { | 512 | 0 | m_aFile.close(); | 513 | 0 | m_bOpen = false; | 514 | 0 | return; | 515 | 0 | } | 516 | | | 517 | | // insert outline root | 518 | 8.87k | m_aOutline.emplace_back( ); | 519 | | | 520 | 8.87k | switch (m_aContext.Version) | 521 | 8.87k | { | 522 | 0 | case PDFWriter::PDFVersion::PDF_A_1: | 523 | 0 | m_nPDFA_Version = 1; | 524 | 0 | m_bIsPDF_A1 = true; | 525 | 0 | m_aContext.Version = PDFWriter::PDFVersion::PDF_1_4; //meaning we need PDF 1.4, PDF/A flavour | 526 | 0 | break; | 527 | 0 | case PDFWriter::PDFVersion::PDF_A_2: | 528 | 0 | m_nPDFA_Version = 2; | 529 | 0 | m_bIsPDF_A2 = true; | 530 | 0 | m_aContext.Version = PDFWriter::PDFVersion::PDF_1_7; | 531 | 0 | break; | 532 | 0 | case PDFWriter::PDFVersion::PDF_A_3: | 533 | 0 | m_nPDFA_Version = 3; | 534 | 0 | m_bIsPDF_A3 = true; | 535 | 0 | m_aContext.Version = PDFWriter::PDFVersion::PDF_1_7; | 536 | 0 | break; | 537 | 0 | case PDFWriter::PDFVersion::PDF_A_4: | 538 | 0 | m_nPDFA_Version = 4; | 539 | 0 | m_bIsPDF_A4 = true; | 540 | 0 | m_aContext.Version = PDFWriter::PDFVersion::PDF_2_0; | 541 | 0 | break; | 542 | 8.87k | default: | 543 | 8.87k | break; | 544 | 8.87k | } | 545 | | | 546 | | // PDF/UA can only be enabled if PDF version is 1.7 (PDF/UA-1) and 2.0 (PDF/UA-2) | 547 | 8.87k | if (m_aContext.UniversalAccessibilityCompliance && m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_7) | 548 | 0 | { | 549 | 0 | m_bIsPDF_UA = true; | 550 | 0 | m_aContext.Tagged = true; | 551 | 0 | } | 552 | | | 553 | | // Add common PDF 2.0 namespace when we are using PDF 2.0 | 554 | 8.87k | if (m_aContext.Version == PDFWriter::PDFVersion::PDF_2_0) | 555 | 0 | { | 556 | 0 | m_aNamespacesMap.emplace(constNamespacePDF2, createObject()); | 557 | | // some types exist only in the PDF 1.7 namespace | 558 | 0 | m_aNamespacesMap.emplace(constNamespacePDF17, createObject()); | 559 | 0 | } | 560 | | | 561 | 8.87k | if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 ) | 562 | 8.87k | SetReferenceDevice( VirtualDevice::RefDevMode::PDF1 ); | 563 | 0 | else | 564 | 0 | SetReferenceDevice( m_aContext.DPIx, m_aContext.DPIy ); | 565 | | | 566 | 8.87k | SetOutputSizePixel( Size( 640, 480 ) ); | 567 | 8.87k | SetMapMode(MapMode(MapUnit::MapMM)); | 568 | 8.87k | } |
|
569 | | |
570 | | PDFWriterImpl::~PDFWriterImpl() |
571 | 8.87k | { |
572 | 8.87k | disposeOnce(); |
573 | 8.87k | } |
574 | | |
575 | | void PDFWriterImpl::dispose() |
576 | 8.87k | { |
577 | 8.87k | m_aPages.clear(); |
578 | 8.87k | VirtualDevice::dispose(); |
579 | 8.87k | } |
580 | | |
581 | | bool PDFWriterImpl::ImplNewFont() const |
582 | 6.03M | { |
583 | 6.03M | const ImplSVData* pSVData = ImplGetSVData(); |
584 | | |
585 | 6.03M | if( mxFontCollection == pSVData->maGDIData.mxScreenFontList |
586 | 6.03M | || mxFontCache == pSVData->maGDIData.mxScreenFontCache ) |
587 | 0 | { |
588 | 0 | const_cast<vcl::PDFWriterImpl&>(*this).ImplUpdateFontData(); |
589 | 0 | } |
590 | | |
591 | 6.03M | return OutputDevice::ImplNewFont(); |
592 | 6.03M | } |
593 | | |
594 | | void PDFWriterImpl::setupDocInfo() |
595 | 8.87k | { |
596 | 8.87k | std::vector< sal_uInt8 > aId; |
597 | 8.87k | m_aCreationDateString = PDFWriter::GetDateTime(); |
598 | 8.87k | computeDocumentIdentifier(aId, m_aContext.DocumentInfo, m_aCreationDateString, m_aContext.DocumentInfo.ModificationDate, m_aCreationMetaDateString); |
599 | 8.87k | if( m_aContext.Encryption.DocumentIdentifier.empty() ) |
600 | 8.87k | m_aContext.Encryption.DocumentIdentifier = std::move(aId); |
601 | 8.87k | } |
602 | | |
603 | | OString PDFWriter::GetDateTime(svl::crypto::SigningContext* pSigningContext) |
604 | 8.87k | { |
605 | 8.87k | OStringBuffer aRet; |
606 | | |
607 | 8.87k | TimeValue aTVal, aGMT; |
608 | 8.87k | oslDateTime aDT; |
609 | 8.87k | osl_getSystemTime(&aGMT); |
610 | | |
611 | 8.87k | if (pSigningContext) |
612 | 0 | { |
613 | | // The context unit is milliseconds, TimeValue is seconds + nanoseconds. |
614 | 0 | if (pSigningContext->m_nSignatureTime) |
615 | 0 | { |
616 | 0 | aGMT = std::chrono::milliseconds(pSigningContext->m_nSignatureTime); |
617 | 0 | } |
618 | 0 | else |
619 | 0 | { |
620 | 0 | pSigningContext->m_nSignatureTime = static_cast<sal_Int64>(aGMT.Seconds) * 1000 + aGMT.Nanosec / 1000000; |
621 | 0 | } |
622 | 0 | } |
623 | | |
624 | 8.87k | osl_getLocalTimeFromSystemTime(&aGMT, &aTVal); |
625 | 8.87k | osl_getDateTimeFromTimeValue(&aTVal, &aDT); |
626 | | |
627 | 8.87k | sal_Int32 nDelta = aTVal.Seconds-aGMT.Seconds; |
628 | | |
629 | 8.87k | appendPdfTimeDate(aRet, aDT.Year, aDT.Month, aDT.Day, aDT.Hours, aDT.Minutes, aDT.Seconds, nDelta); |
630 | | |
631 | 8.87k | aRet.append("'"); |
632 | 8.87k | return aRet.makeStringAndClear(); |
633 | 8.87k | } |
634 | | |
635 | | void PDFWriterImpl::emitComment( const char* pComment ) |
636 | 0 | { |
637 | 0 | OString aLine = OString::Concat("% ") + pComment + "\n"; |
638 | 0 | writeBuffer( aLine ); |
639 | 0 | } |
640 | | |
641 | | bool PDFWriterImpl::compressStream( SvMemoryStream* pStream ) |
642 | 6 | { |
643 | 6 | if (!g_bDebugDisableCompression) |
644 | 6 | { |
645 | 6 | sal_uInt64 nEndPos = pStream->TellEnd(); |
646 | 6 | pStream->Seek( STREAM_SEEK_TO_BEGIN ); |
647 | 6 | ZCodec aCodec( 0x4000, 0x4000 ); |
648 | 6 | SvMemoryStream aStream; |
649 | 6 | aCodec.BeginCompression(); |
650 | 6 | aCodec.Write( aStream, static_cast<const sal_uInt8*>(pStream->GetData()), nEndPos ); |
651 | 6 | aCodec.EndCompression(); |
652 | 6 | nEndPos = aStream.Tell(); |
653 | 6 | pStream->Seek( STREAM_SEEK_TO_BEGIN ); |
654 | 6 | aStream.Seek( STREAM_SEEK_TO_BEGIN ); |
655 | 6 | pStream->SetStreamSize( nEndPos ); |
656 | 6 | pStream->WriteBytes( aStream.GetData(), nEndPos ); |
657 | 6 | return true; |
658 | 6 | } |
659 | 0 | else |
660 | 0 | return false; |
661 | 6 | } |
662 | | |
663 | | void PDFWriterImpl::beginCompression() |
664 | 90.1k | { |
665 | 90.1k | if (!g_bDebugDisableCompression) |
666 | 90.1k | { |
667 | 90.1k | m_pCodec = std::make_unique<ZCodec>( 0x4000, 0x4000 ); |
668 | 90.1k | m_pMemStream = std::make_unique<SvMemoryStream>(); |
669 | 90.1k | m_pCodec->BeginCompression(); |
670 | 90.1k | } |
671 | 90.1k | } |
672 | | |
673 | | void PDFWriterImpl::endCompression() |
674 | 90.1k | { |
675 | 90.1k | if (!g_bDebugDisableCompression && m_pCodec) |
676 | 90.1k | { |
677 | 90.1k | m_pCodec->EndCompression(); |
678 | 90.1k | m_pCodec.reset(); |
679 | 90.1k | sal_uInt64 nLen = m_pMemStream->Tell(); |
680 | 90.1k | m_pMemStream->Seek( 0 ); |
681 | 90.1k | (void)writeBufferBytes( m_pMemStream->GetData(), nLen ); |
682 | 90.1k | m_pMemStream.reset(); |
683 | 90.1k | } |
684 | 90.1k | } |
685 | | |
686 | | bool PDFWriterImpl::writeBufferBytes( const void* pBuffer, sal_uInt64 nBytes ) |
687 | 2.13M | { |
688 | 2.13M | if( ! m_bOpen ) // we are already down the drain |
689 | 0 | return false; |
690 | | |
691 | 2.13M | if( ! nBytes ) // huh ? |
692 | 0 | return true; |
693 | | |
694 | 2.13M | if( !m_aOutputStreams.empty() ) |
695 | 6 | { |
696 | 6 | m_aOutputStreams.front().m_pStream->Seek( STREAM_SEEK_TO_END ); |
697 | 6 | m_aOutputStreams.front().m_pStream->WriteBytes( |
698 | 6 | pBuffer, nBytes); |
699 | 6 | return true; |
700 | 6 | } |
701 | | |
702 | 2.13M | sal_uInt64 nWritten; |
703 | 2.13M | sal_uInt64 nActualSize = nBytes; |
704 | | |
705 | | // we are compressing the stream |
706 | 2.13M | if (m_pCodec) |
707 | 1.03M | { |
708 | 1.03M | m_pCodec->Write( *m_pMemStream, static_cast<const sal_uInt8*>(pBuffer), nBytes ); |
709 | 1.03M | nWritten = nBytes; |
710 | 1.03M | } |
711 | 1.10M | else |
712 | 1.10M | { |
713 | | // is it encrypted? |
714 | 1.10M | bool bStreamEncryption = m_pPDFEncryptor && m_pPDFEncryptor->isStreamEncryptionEnabled(); |
715 | 1.10M | if (bStreamEncryption) |
716 | 0 | { |
717 | 0 | nActualSize = m_pPDFEncryptor->calculateSizeIncludingHeader(nActualSize); |
718 | 0 | m_vEncryptionBuffer.resize(nActualSize); |
719 | 0 | m_pPDFEncryptor->encrypt(pBuffer, nBytes, m_vEncryptionBuffer, nActualSize); |
720 | 0 | } |
721 | | |
722 | 1.10M | const void* pWriteBuffer = bStreamEncryption ? m_vEncryptionBuffer.data() : pBuffer; |
723 | 1.10M | m_DocDigest.update(pWriteBuffer, sal_uInt32(nActualSize)); |
724 | | |
725 | | |
726 | 1.10M | if (m_aFile.write(pWriteBuffer, nActualSize, nWritten) != osl::File::E_None) |
727 | 0 | nWritten = 0; |
728 | | |
729 | 1.10M | if (nWritten != nActualSize) |
730 | 0 | { |
731 | 0 | m_aFile.close(); |
732 | 0 | m_bOpen = false; |
733 | 0 | return false; |
734 | 0 | } |
735 | 1.10M | return true; |
736 | 1.10M | } |
737 | | |
738 | 1.03M | return nWritten == nActualSize; |
739 | 2.13M | } |
740 | | |
741 | | void PDFWriterImpl::newPage( double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation ) |
742 | 73.7k | { |
743 | 73.7k | endPage(); |
744 | 73.7k | m_nCurrentPage = m_aPages.size(); |
745 | 73.7k | m_aPages.emplace_back(this, nPageWidth, nPageHeight, eOrientation ); |
746 | | |
747 | 73.7k | const double frac = double(m_aPages.back().m_nUserUnit) / pointToPixel(1); |
748 | 73.7k | m_aMapMode = MapMode(MapUnit::MapPoint, Point(), frac, frac); |
749 | | |
750 | 73.7k | m_aPages.back().beginStream(); |
751 | | |
752 | | // setup global graphics state |
753 | | // linewidth is "1 pixel" by default |
754 | 73.7k | OStringBuffer aBuf( 16 ); |
755 | 73.7k | appendDouble( 72.0/GetDPIX(), aBuf ); |
756 | 73.7k | aBuf.append( " w\n" ); |
757 | 73.7k | writeBuffer( aBuf ); |
758 | 73.7k | } |
759 | | |
760 | | void PDFWriterImpl::endPage() |
761 | 82.6k | { |
762 | 82.6k | if( m_aPages.empty() ) |
763 | 8.85k | return; |
764 | | |
765 | | // close eventual MC sequence |
766 | 73.7k | endStructureElementMCSeq(); |
767 | | |
768 | | // sanity check |
769 | 73.7k | if( !m_aOutputStreams.empty() ) |
770 | 0 | { |
771 | 0 | OSL_FAIL( "redirection across pages !!!" ); |
772 | 0 | m_aOutputStreams.clear(); // leak ! |
773 | 0 | m_aMapMode.SetOrigin( Point() ); |
774 | 0 | } |
775 | | |
776 | 73.7k | m_aGraphicsStack.clear(); |
777 | 73.7k | m_aGraphicsStack.emplace_back( ); |
778 | | |
779 | | // this should pop the PDF graphics stack if necessary |
780 | 73.7k | updateGraphicsState(); |
781 | | |
782 | 73.7k | m_aPages.back().endStream(); |
783 | | |
784 | | // reset the default font |
785 | 73.7k | Font aFont; |
786 | 73.7k | aFont.SetFamilyName( u"Times"_ustr ); |
787 | 73.7k | aFont.SetFontSize( Size( 0, 12 ) ); |
788 | | |
789 | 73.7k | m_aCurrentPDFState = m_aGraphicsStack.front(); |
790 | 73.7k | m_aGraphicsStack.front().m_aFont = std::move(aFont); |
791 | | |
792 | 73.7k | for (auto & bitmap : m_aBitmaps) |
793 | 0 | { |
794 | 0 | if( ! bitmap.m_aBitmap.IsEmpty() ) |
795 | 0 | { |
796 | 0 | writeBitmapObject(bitmap); |
797 | 0 | bitmap.m_aBitmap = Bitmap(); |
798 | 0 | } |
799 | 0 | } |
800 | 73.7k | for (auto & jpeg : m_aJPGs) |
801 | 0 | { |
802 | 0 | if( jpeg.m_pStream ) |
803 | 0 | { |
804 | 0 | writeJPG( jpeg ); |
805 | 0 | jpeg.m_pStream.reset(); |
806 | 0 | jpeg.m_aAlphaMask = AlphaMask(); |
807 | 0 | } |
808 | 0 | } |
809 | 73.7k | for (auto & item : m_aTransparentObjects) |
810 | 6 | { |
811 | 6 | if( item.m_pContentStream ) |
812 | 6 | { |
813 | 6 | writeTransparentObject(item); |
814 | 6 | item.m_pContentStream.reset(); |
815 | 6 | } |
816 | 6 | } |
817 | | |
818 | 73.7k | } |
819 | | |
820 | | sal_Int32 PDFWriterImpl::createObject() |
821 | 394k | { |
822 | 394k | m_aObjects.emplace_back(); |
823 | 394k | return m_aObjects.size(); |
824 | 394k | } |
825 | | |
826 | | bool PDFWriterImpl::updateObject( sal_Int32 n ) |
827 | 394k | { |
828 | 394k | if( ! m_bOpen ) |
829 | 0 | return false; |
830 | | |
831 | 394k | sal_uInt64 nOffset = SAL_MAX_UINT64; |
832 | 394k | osl::File::RC aError = m_aFile.getPos(nOffset); |
833 | 394k | SAL_WARN_IF( aError != osl::File::E_None, "vcl.pdfwriter", "could not register object" ); |
834 | 394k | if (aError != osl::File::E_None) |
835 | 0 | { |
836 | 0 | m_aFile.close(); |
837 | 0 | m_bOpen = false; |
838 | 0 | } |
839 | 394k | m_aObjects[n - 1].m_nOffset = nOffset; |
840 | 394k | return aError == osl::File::E_None; |
841 | 394k | } |
842 | | |
843 | | bool PDFWriterImpl::useObjectStreams() const |
844 | 0 | { |
845 | 0 | if (m_aContext.Version < PDFWriter::PDFVersion::PDF_1_5) |
846 | 0 | return false; |
847 | | // sdext reads the embedded original document out of a trailer, and a cross-reference |
848 | | // stream leaves none |
849 | 0 | return m_aDocumentAttachedFiles.empty(); |
850 | 0 | } |
851 | | |
852 | | std::shared_ptr<pdf::IPDFEncryptor> PDFWriterImpl::getStringEncryptor(sal_Int32 nObject) |
853 | 0 | { |
854 | 0 | if (!useObjectStreams()) |
855 | 0 | return m_pPDFEncryptor; |
856 | | |
857 | | #ifndef NDEBUG |
858 | | m_aPlainStringObjects.insert(nObject); |
859 | | #else |
860 | 0 | (void)nObject; |
861 | 0 | #endif |
862 | | // ISO 32000-2 7.5.7: a string in an object stream is not encrypted on its own, the |
863 | | // stream carrying it is |
864 | 0 | return nullptr; |
865 | 0 | } |
866 | | |
867 | | bool PDFWriterImpl::writeObject(sal_Int32 nObject, std::string_view aBody) |
868 | 0 | { |
869 | 0 | if (useObjectStreams()) |
870 | 0 | return writeCompressedObject(nObject, aBody); |
871 | | |
872 | 0 | return updateObject(nObject) && writeBuffer(OString::number(nObject) + " 0 obj\n") |
873 | 0 | && writeBuffer(aBody) && writeBuffer("\nendobj\n\n"); |
874 | 0 | } |
875 | | |
876 | | bool PDFWriterImpl::writeCompressedObject(sal_Int32 nObject, std::string_view aBody) |
877 | 0 | { |
878 | 0 | m_aCompressedObjects.emplace_back(nObject, OString(aBody)); |
879 | |
|
880 | 0 | return m_aCompressedObjects.size() < nMaxObjectsPerStream || emitObjectStream(); |
881 | 0 | } |
882 | | |
883 | | bool PDFWriterImpl::emitObjectStream() |
884 | 0 | { |
885 | 0 | if (m_aCompressedObjects.empty()) |
886 | 0 | return true; |
887 | | |
888 | | // ISO 32000-2 7.5.7: /N pairs of object number and offset, then the bodies at /First |
889 | 0 | OStringBuffer aPairs(12 * m_aCompressedObjects.size()); |
890 | 0 | OStringBuffer aBodies(4096); |
891 | 0 | const sal_Int32 nStreamObject = createObject(); |
892 | 0 | for (const auto& rPending : m_aCompressedObjects) |
893 | 0 | { |
894 | 0 | aPairs.append(OString::number(rPending.m_nObject) + " " |
895 | 0 | + OString::number(aBodies.getLength()) + " "); |
896 | 0 | aBodies.append(rPending.m_aBody + "\n"); |
897 | 0 | } |
898 | |
|
899 | 0 | const sal_Int32 nFirst = aPairs.getLength(); |
900 | 0 | aPairs.append(aBodies.makeStringAndClear()); |
901 | 0 | const std::vector<sal_uInt8> aStream(deflateBuffer(aPairs)); |
902 | 0 | if (aStream.empty()) |
903 | 0 | return false; |
904 | | |
905 | 0 | OStringBuffer aLine(256); |
906 | 0 | aLine.append(OString::number(nStreamObject) + " 0 obj\n<</Type/ObjStm/N " |
907 | 0 | + OString::number(static_cast<sal_Int32>(m_aCompressedObjects.size())) + "/First " |
908 | 0 | + OString::number(nFirst) + "/Length " |
909 | 0 | + OString::number(calculateStreamSize(aStream.size()))); |
910 | 0 | if (!g_bDebugDisableCompression) |
911 | 0 | aLine.append("/Filter/FlateDecode"); |
912 | 0 | aLine.append(">>\nstream\n"); |
913 | |
|
914 | 0 | if (!updateObject(nStreamObject) || !writeBuffer(aLine)) |
915 | 0 | return false; |
916 | | |
917 | | // ISO 32000-2 7.5.7: a string in here is not encrypted on its own, the stream holding |
918 | | // it is, with the cipher set up for the stream's own object number |
919 | 0 | checkAndEnableStreamEncryption(nStreamObject); |
920 | | // the strings inside are covered by nothing else, so stream encryption must really be on |
921 | 0 | assert((!m_aContext.Encryption.canEncrypt() |
922 | 0 | || (m_pPDFEncryptor && m_pPDFEncryptor->isStreamEncryptionEnabled())) |
923 | 0 | && "an object stream is going out unencrypted"); |
924 | 0 | const bool bBodies = writeBufferBytes(aStream.data(), aStream.size()); |
925 | 0 | disableStreamEncryption(); |
926 | 0 | if (!bBodies || !writeBuffer("\nendstream\nendobj\n\n")) |
927 | 0 | return false; |
928 | | |
929 | | // only now can an entry name the stream, which is in the file |
930 | 0 | sal_Int32 nIndex = 0; |
931 | 0 | for (const auto& rPending : m_aCompressedObjects) |
932 | 0 | { |
933 | 0 | m_aObjects[rPending.m_nObject - 1] |
934 | 0 | = { .m_nObjectStream = nStreamObject, .m_nIndex = nIndex }; |
935 | 0 | ++nIndex; |
936 | 0 | } |
937 | 0 | m_aCompressedObjects.clear(); |
938 | 0 | return true; |
939 | 0 | } |
940 | | |
941 | | sal_Int32 PDFWriterImpl::emitStructParentTree( sal_Int32 nObject ) |
942 | 0 | { |
943 | 0 | if( nObject > 0 ) |
944 | 0 | { |
945 | 0 | OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE ); |
946 | |
|
947 | 0 | aLine.append("<</Nums[\n"); |
948 | 0 | sal_Int32 nTreeItems = m_aStructParentTree.size(); |
949 | 0 | for( sal_Int32 n = 0; n < nTreeItems; n++ ) |
950 | 0 | { |
951 | 0 | aLine.append( OString::number(n) + " " |
952 | 0 | + m_aStructParentTree[n] |
953 | 0 | + "\n" ); |
954 | 0 | } |
955 | 0 | aLine.append("]>>"); |
956 | 0 | if (!writeObject(nObject, aLine)) |
957 | 0 | return 0; |
958 | 0 | } |
959 | 0 | return nObject; |
960 | 0 | } |
961 | | |
962 | | // every structure element already has a unique object id - just use it for ID |
963 | | static OString GenerateID(sal_Int32 const nObjectId) |
964 | 0 | { |
965 | 0 | return "id" + OString::number(nObjectId); |
966 | 0 | } |
967 | | |
968 | | sal_Int32 PDFWriterImpl::emitStructIDTree(sal_Int32 const nObject) |
969 | 0 | { |
970 | | // loosely following PDF 1.7, 10.6.5 Example of Logical Structure, Example 10.15 |
971 | 0 | if (nObject < 0) |
972 | 0 | { |
973 | 0 | return nObject; |
974 | 0 | } |
975 | | // the name tree entries must be sorted lexicographically. |
976 | 0 | std::map<OString, sal_Int32> ids; |
977 | 0 | for (auto n : m_StructElemObjsWithID) |
978 | 0 | { |
979 | 0 | ids.emplace(GenerateID(n), n); |
980 | 0 | } |
981 | 0 | OStringBuffer buf; |
982 | 0 | COSWriter aWriter(buf, m_aContext.Encryption.getParams(), getStringEncryptor(nObject)); |
983 | 0 | buf.append("<</Names [\n"); |
984 | 0 | for (auto const& it : ids) |
985 | 0 | { |
986 | 0 | aWriter.writeLiteralEncrypt(it.first, nObject); |
987 | 0 | buf.append(" "); |
988 | 0 | appendObjectReference(it.second, buf); |
989 | 0 | buf.append("\n"); |
990 | 0 | } |
991 | 0 | buf.append("] >>"); |
992 | |
|
993 | 0 | if (!writeObject(nObject, buf)) |
994 | 0 | return 0; |
995 | | |
996 | 0 | return nObject; |
997 | 0 | } |
998 | | |
999 | | const char* PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr ) |
1000 | 0 | { |
1001 | 0 | static constexpr auto aAttributeStrings = frozen::make_unordered_map<PDFWriter::StructAttribute, const char*>({ |
1002 | 0 | { PDFWriter::Placement, "Placement" }, |
1003 | 0 | { PDFWriter::WritingMode, "WritingMode" }, |
1004 | 0 | { PDFWriter::SpaceBefore, "SpaceBefore" }, |
1005 | 0 | { PDFWriter::SpaceAfter, "SpaceAfter" }, |
1006 | 0 | { PDFWriter::StartIndent, "StartIndent" }, |
1007 | 0 | { PDFWriter::EndIndent, "EndIndent" }, |
1008 | 0 | { PDFWriter::TextIndent, "TextIndent" }, |
1009 | 0 | { PDFWriter::TextAlign, "TextAlign" }, |
1010 | 0 | { PDFWriter::Width, "Width" }, |
1011 | 0 | { PDFWriter::Height, "Height" }, |
1012 | 0 | { PDFWriter::BlockAlign, "BlockAlign" }, |
1013 | 0 | { PDFWriter::InlineAlign, "InlineAlign" }, |
1014 | 0 | { PDFWriter::LineHeight, "LineHeight" }, |
1015 | 0 | { PDFWriter::BaselineShift, "BaselineShift" }, |
1016 | 0 | { PDFWriter::TextDecorationType,"TextDecorationType" }, |
1017 | 0 | { PDFWriter::ListNumbering, "ListNumbering" }, |
1018 | 0 | { PDFWriter::RowSpan, "RowSpan" }, |
1019 | 0 | { PDFWriter::ColSpan, "ColSpan" }, |
1020 | 0 | { PDFWriter::Scope, "Scope" }, |
1021 | 0 | { PDFWriter::Role, "Role" }, |
1022 | 0 | { PDFWriter::RubyAlign, "RubyAlign" }, |
1023 | 0 | { PDFWriter::RubyPosition, "RubyPosition" }, |
1024 | 0 | { PDFWriter::Type, "Type" }, |
1025 | 0 | { PDFWriter::Subtype, "Subtype" }, |
1026 | 0 | { PDFWriter::LinkAnnotation, "LinkAnnotation" }, |
1027 | 0 | { PDFWriter::NoteAnnotation, "NoteAnnotation" } |
1028 | 0 | }); |
1029 | |
|
1030 | 0 | auto it = aAttributeStrings.find( eAttr ); |
1031 | |
|
1032 | 0 | if( it == aAttributeStrings.end() ) |
1033 | 0 | SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttribute " << eAttr); |
1034 | | |
1035 | 0 | return it != aAttributeStrings.end() ? it->second : ""; |
1036 | 0 | } |
1037 | | |
1038 | | const char* PDFWriterImpl::getAttributeValueTag( PDFWriter::StructAttributeValue eVal ) |
1039 | 0 | { |
1040 | 0 | static constexpr auto aValueStrings = frozen::make_unordered_map<PDFWriter::StructAttributeValue, const char*>({ |
1041 | 0 | { PDFWriter::NONE, "None" }, |
1042 | 0 | { PDFWriter::Block, "Block" }, |
1043 | 0 | { PDFWriter::Inline, "Inline" }, |
1044 | 0 | { PDFWriter::Before, "Before" }, |
1045 | 0 | { PDFWriter::After, "After" }, |
1046 | 0 | { PDFWriter::Start, "Start" }, |
1047 | 0 | { PDFWriter::End, "End" }, |
1048 | 0 | { PDFWriter::LrTb, "LrTb" }, |
1049 | 0 | { PDFWriter::RlTb, "RlTb" }, |
1050 | 0 | { PDFWriter::TbRl, "TbRl" }, |
1051 | 0 | { PDFWriter::Center, "Center" }, |
1052 | 0 | { PDFWriter::Justify, "Justify" }, |
1053 | 0 | { PDFWriter::Auto, "Auto" }, |
1054 | 0 | { PDFWriter::Middle, "Middle" }, |
1055 | 0 | { PDFWriter::Normal, "Normal" }, |
1056 | 0 | { PDFWriter::Underline, "Underline" }, |
1057 | 0 | { PDFWriter::Overline, "Overline" }, |
1058 | 0 | { PDFWriter::LineThrough,"LineThrough" }, |
1059 | 0 | { PDFWriter::Row, "Row" }, |
1060 | 0 | { PDFWriter::Column, "Column" }, |
1061 | 0 | { PDFWriter::Both, "Both" }, |
1062 | 0 | { PDFWriter::Pagination, "Pagination" }, |
1063 | 0 | { PDFWriter::Layout, "Layout" }, |
1064 | 0 | { PDFWriter::Page, "Page" }, |
1065 | 0 | { PDFWriter::Background, "Background" }, |
1066 | 0 | { PDFWriter::Header, "Header" }, |
1067 | 0 | { PDFWriter::Footer, "Footer" }, |
1068 | 0 | { PDFWriter::Watermark, "Watermark" }, |
1069 | 0 | { PDFWriter::Rb, "rb" }, |
1070 | 0 | { PDFWriter::Cb, "cb" }, |
1071 | 0 | { PDFWriter::Pb, "pb" }, |
1072 | 0 | { PDFWriter::Tv, "tv" }, |
1073 | 0 | { PDFWriter::RStart, "Start" }, |
1074 | 0 | { PDFWriter::RCenter, "Center" }, |
1075 | 0 | { PDFWriter::REnd, "End" }, |
1076 | 0 | { PDFWriter::RJustify, "Justify" }, |
1077 | 0 | { PDFWriter::RDistribute,"Distribute" }, |
1078 | 0 | { PDFWriter::RBefore, "Before" }, |
1079 | 0 | { PDFWriter::RAfter, "After" }, |
1080 | 0 | { PDFWriter::RWarichu, "Warichu" }, |
1081 | 0 | { PDFWriter::RInline, "Inline" }, |
1082 | 0 | { PDFWriter::Disc, "Disc" }, |
1083 | 0 | { PDFWriter::Circle, "Circle" }, |
1084 | 0 | { PDFWriter::Square, "Square" }, |
1085 | 0 | { PDFWriter::Decimal, "Decimal" }, |
1086 | 0 | { PDFWriter::UpperRoman, "UpperRoman" }, |
1087 | 0 | { PDFWriter::LowerRoman, "LowerRoman" }, |
1088 | 0 | { PDFWriter::UpperAlpha, "UpperAlpha" }, |
1089 | 0 | { PDFWriter::LowerAlpha, "LowerAlpha" } |
1090 | 0 | }); |
1091 | |
|
1092 | 0 | auto it = aValueStrings.find( eVal ); |
1093 | |
|
1094 | 0 | if( it == aValueStrings.end() ) |
1095 | 0 | SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttributeValue " << eVal); |
1096 | | |
1097 | 0 | return it != aValueStrings.end() ? it->second : ""; |
1098 | 0 | } |
1099 | | |
1100 | | static void appendStructureAttributeLine( PDFWriter::StructAttribute i_eAttr, const PDFStructureAttribute& i_rVal, OStringBuffer& o_rLine, bool i_bIsFixedInt ) |
1101 | 0 | { |
1102 | 0 | o_rLine.append( "/" ); |
1103 | 0 | o_rLine.append( PDFWriterImpl::getAttributeTag( i_eAttr ) ); |
1104 | |
|
1105 | 0 | if( i_rVal.eValue != PDFWriter::Invalid ) |
1106 | 0 | { |
1107 | 0 | o_rLine.append( "/" ); |
1108 | 0 | o_rLine.append( PDFWriterImpl::getAttributeValueTag( i_rVal.eValue ) ); |
1109 | 0 | } |
1110 | 0 | else |
1111 | 0 | { |
1112 | | // numerical value |
1113 | 0 | o_rLine.append( " " ); |
1114 | 0 | if( i_bIsFixedInt ) |
1115 | 0 | appendFixedInt( i_rVal.nValue, o_rLine ); |
1116 | 0 | else |
1117 | 0 | o_rLine.append( i_rVal.nValue ); |
1118 | 0 | } |
1119 | 0 | o_rLine.append( "\n" ); |
1120 | 0 | } |
1121 | | |
1122 | | template<typename T> |
1123 | | void PDFWriterImpl::AppendAnnotKid(PDFStructureElement& i_rEle, T & rAnnot) |
1124 | 0 | { |
1125 | | // update struct parent of link |
1126 | 0 | OString const aStructParentEntry(OString::number(i_rEle.m_nObject) + " 0 R"); |
1127 | 0 | m_aStructParentTree.push_back( aStructParentEntry ); |
1128 | 0 | rAnnot.m_nStructParent = m_aStructParentTree.size()-1; |
1129 | 0 | sal_Int32 const nAnnotObj(rAnnot.m_nObject); |
1130 | 0 | i_rEle.m_aKids.emplace_back(ObjReferenceObj{nAnnotObj}); |
1131 | 0 | } Unexecuted instantiation: void vcl::PDFWriterImpl::AppendAnnotKid<vcl::pdf::PDFNoteEntry>(vcl::pdf::PDFStructureElement&, vcl::pdf::PDFNoteEntry&) Unexecuted instantiation: void vcl::PDFWriterImpl::AppendAnnotKid<vcl::pdf::PDFWidget>(vcl::pdf::PDFStructureElement&, vcl::pdf::PDFWidget&) Unexecuted instantiation: void vcl::PDFWriterImpl::AppendAnnotKid<vcl::pdf::PDFScreen>(vcl::pdf::PDFStructureElement&, vcl::pdf::PDFScreen&) Unexecuted instantiation: void vcl::PDFWriterImpl::AppendAnnotKid<vcl::pdf::PDFLink>(vcl::pdf::PDFStructureElement&, vcl::pdf::PDFLink&) |
1132 | | |
1133 | | OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle ) |
1134 | 0 | { |
1135 | | // create layout, list and table attribute sets |
1136 | 0 | OStringBuffer aLayout(256), aList(64), aTable(64); |
1137 | 0 | OStringBuffer aPrintField; |
1138 | 0 | for (auto const& attribute : i_rEle.m_aAttributes) |
1139 | 0 | { |
1140 | 0 | if( attribute.first == PDFWriter::ListNumbering ) |
1141 | 0 | appendStructureAttributeLine( attribute.first, attribute.second, aList, true ); |
1142 | 0 | else if (attribute.first == PDFWriter::Role) |
1143 | 0 | { |
1144 | 0 | appendStructureAttributeLine(attribute.first, attribute.second, aPrintField, true); |
1145 | 0 | } |
1146 | 0 | else if( attribute.first == PDFWriter::RowSpan || |
1147 | 0 | attribute.first == PDFWriter::ColSpan || |
1148 | 0 | attribute.first == PDFWriter::Scope) |
1149 | 0 | { |
1150 | 0 | appendStructureAttributeLine( attribute.first, attribute.second, aTable, false ); |
1151 | 0 | } |
1152 | 0 | else if (attribute.first == PDFWriter::NoteAnnotation) |
1153 | 0 | { |
1154 | 0 | sal_Int32 nNote = attribute.second.nValue; |
1155 | 0 | std::map<sal_Int32, sal_Int32>::const_iterator link_it = m_aLinkPropertyMap.find(nNote); |
1156 | 0 | if (link_it != m_aLinkPropertyMap.end()) |
1157 | 0 | nNote = link_it->second; |
1158 | 0 | if (nNote >= 0 && o3tl::make_unsigned(nNote) < m_aNotes.size()) |
1159 | 0 | { |
1160 | 0 | AppendAnnotKid(i_rEle, m_aNotes[nNote]); |
1161 | 0 | } |
1162 | 0 | else |
1163 | 0 | { |
1164 | 0 | OSL_FAIL("unresolved note id for Note structure"); |
1165 | 0 | SAL_INFO("vcl.pdfwriter", "unresolved note id " << nNote << " for Note structure"); |
1166 | 0 | if (g_bDebugDisableCompression) |
1167 | 0 | { |
1168 | 0 | OString aLine |
1169 | 0 | = "unresolved note id " + OString::number(nNote) + " for Note structure"; |
1170 | 0 | emitComment(aLine.getStr()); |
1171 | 0 | } |
1172 | 0 | } |
1173 | 0 | } |
1174 | 0 | else |
1175 | 0 | appendStructureAttributeLine( attribute.first, attribute.second, aLayout, true ); |
1176 | 0 | } |
1177 | 0 | if( ! i_rEle.m_aBBox.IsEmpty() ) |
1178 | 0 | { |
1179 | 0 | aLayout.append( "/BBox[" ); |
1180 | 0 | appendFixedInt( i_rEle.m_aBBox.Left(), aLayout ); |
1181 | 0 | aLayout.append( " " ); |
1182 | 0 | appendFixedInt( i_rEle.m_aBBox.Top(), aLayout ); |
1183 | 0 | aLayout.append( " " ); |
1184 | 0 | appendFixedInt( i_rEle.m_aBBox.Right(), aLayout ); |
1185 | 0 | aLayout.append( " " ); |
1186 | 0 | appendFixedInt( i_rEle.m_aBBox.Bottom(), aLayout ); |
1187 | 0 | aLayout.append( "]\n" ); |
1188 | 0 | } |
1189 | |
|
1190 | 0 | OStringBuffer aRet(256); |
1191 | 0 | bool isArray(false); |
1192 | 0 | if (1 < (aLayout.isEmpty() ? 0 : 1) + (aList.isEmpty() ? 0 : 1) |
1193 | 0 | + (aPrintField.isEmpty() ? 0 : 1) + (aTable.isEmpty() ? 0 : 1)) |
1194 | 0 | { |
1195 | 0 | isArray = true; |
1196 | 0 | aRet.append(" ["); |
1197 | 0 | } |
1198 | 0 | auto const WriteAttrs = [&](char const*const pName, OStringBuffer & rBuf) |
1199 | 0 | { |
1200 | 0 | aRet.append(OString::Concat(" <</O") |
1201 | 0 | + pName |
1202 | 0 | + rBuf |
1203 | 0 | + ">>"); |
1204 | 0 | }; |
1205 | 0 | if( !aLayout.isEmpty() ) |
1206 | 0 | { |
1207 | 0 | WriteAttrs("/Layout", aLayout); |
1208 | 0 | } |
1209 | 0 | if( !aList.isEmpty() ) |
1210 | 0 | { |
1211 | 0 | WriteAttrs("/List", aList); |
1212 | 0 | } |
1213 | 0 | if (!aPrintField.isEmpty()) |
1214 | 0 | { |
1215 | 0 | WriteAttrs("/PrintField", aPrintField); |
1216 | 0 | } |
1217 | 0 | if( !aTable.isEmpty() ) |
1218 | 0 | { |
1219 | 0 | WriteAttrs("/Table", aTable); |
1220 | 0 | } |
1221 | |
|
1222 | 0 | if (isArray) |
1223 | 0 | { |
1224 | 0 | aRet.append( " ]" ); |
1225 | 0 | } |
1226 | 0 | return aRet.makeStringAndClear(); |
1227 | 0 | } |
1228 | | |
1229 | | // Write the namespace objects to the stream |
1230 | | void PDFWriterImpl::emitNamespaces() |
1231 | 8.85k | { |
1232 | 8.85k | if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0) |
1233 | 8.85k | return; |
1234 | | |
1235 | 0 | for (auto&[sNamespace, nObject] : m_aNamespacesMap) |
1236 | 0 | { |
1237 | 0 | if (!updateObject(nObject)) |
1238 | 0 | return; |
1239 | | |
1240 | 0 | OStringBuffer aLine; |
1241 | 0 | COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor); |
1242 | 0 | aWriter.startObject(nObject); |
1243 | 0 | aWriter.startDict(); |
1244 | 0 | aWriter.write("/Type", "/Namespace"); |
1245 | 0 | aWriter.writeKeyAndLiteral("/NS", sNamespace); |
1246 | | // the 1.7 namespace needs none: for it the structure tree root's RoleMap is the fallback |
1247 | 0 | if (sNamespace == constNamespacePDF2 && !m_aRoleMap.empty()) |
1248 | 0 | { |
1249 | 0 | aLine.append( "/RoleMapNS<<" ); |
1250 | 0 | for (auto const& role : m_aRoleMap) |
1251 | 0 | { |
1252 | 0 | aLine.append("/" + role.first + "/" + role.second.m_aTag + "\n"); |
1253 | 0 | } |
1254 | 0 | aLine.append( ">>\n" ); |
1255 | 0 | } |
1256 | 0 | aWriter.endDict(); |
1257 | 0 | aWriter.endObject(); |
1258 | |
|
1259 | 0 | if (!writeBuffer(aWriter.getLine())) |
1260 | 0 | m_aNamespacesMap[sNamespace] = 0; |
1261 | 0 | } |
1262 | 0 | } |
1263 | | |
1264 | | sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle ) |
1265 | 0 | { |
1266 | 0 | assert(rEle.m_nOwnElement == 0 || rEle.m_oType); |
1267 | 0 | if (rEle.m_nOwnElement != rEle.m_nParentElement // emit the struct tree root |
1268 | | // do not emit NonStruct and its children |
1269 | 0 | && *rEle.m_oType == StructElement::NonStructElement) |
1270 | 0 | { |
1271 | 0 | return 0; |
1272 | 0 | } |
1273 | | |
1274 | 0 | for (auto const& child : rEle.m_aChildren) |
1275 | 0 | { |
1276 | 0 | if( child > 0 && o3tl::make_unsigned(child) < m_aStructure.size() ) |
1277 | 0 | { |
1278 | 0 | PDFStructureElement& rChild = m_aStructure[ child ]; |
1279 | 0 | if (*rChild.m_oType != StructElement::NonStructElement) |
1280 | 0 | { |
1281 | 0 | if( rChild.m_nParentElement == rEle.m_nOwnElement ) |
1282 | 0 | emitStructure( rChild ); |
1283 | 0 | else |
1284 | 0 | { |
1285 | 0 | OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure element" ); |
1286 | 0 | SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure element with id " << child); |
1287 | 0 | } |
1288 | 0 | } |
1289 | 0 | } |
1290 | 0 | else |
1291 | 0 | { |
1292 | 0 | OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" ); |
1293 | 0 | SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure id " << child); |
1294 | 0 | } |
1295 | 0 | } |
1296 | | |
1297 | 0 | OStringBuffer aLine( 512 ); |
1298 | 0 | COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), getStringEncryptor(rEle.m_nObject)); |
1299 | 0 | aLine.append("<</Type"); |
1300 | 0 | sal_Int32 nParentTree = -1; |
1301 | 0 | sal_Int32 nIDTree = -1; |
1302 | 0 | if( rEle.m_nOwnElement == rEle.m_nParentElement ) |
1303 | 0 | { |
1304 | 0 | nParentTree = createObject(); |
1305 | 0 | if (!nParentTree) |
1306 | 0 | return 0; |
1307 | 0 | aLine.append("/StructTreeRoot\n"); |
1308 | 0 | aWriter.writeKeyAndReference("/ParentTree", nParentTree); |
1309 | | |
1310 | | // Write the references to the namespaces the elements below use |
1311 | 0 | OStringBuffer aNamespaces; |
1312 | | // m_aNamespacesMap is unordered, so name them rather than iterate it |
1313 | 0 | for (auto const& rURI : { constNamespacePDF2, constNamespacePDF17 }) |
1314 | 0 | { |
1315 | 0 | auto iterator = m_aNamespacesMap.find(rURI); |
1316 | 0 | if (iterator != m_aNamespacesMap.end()) |
1317 | 0 | { |
1318 | 0 | if (!aNamespaces.isEmpty()) |
1319 | 0 | aNamespaces.append(" "); |
1320 | 0 | appendObjectReference(iterator->second, aNamespaces); |
1321 | 0 | } |
1322 | 0 | } |
1323 | 0 | if (!aNamespaces.isEmpty()) |
1324 | 0 | aLine.append("/Namespaces [" + aNamespaces + "]"); |
1325 | |
|
1326 | 0 | if( ! m_aRoleMap.empty() ) |
1327 | 0 | { |
1328 | 0 | aLine.append( "/RoleMap<<" ); |
1329 | 0 | for (auto const& role : m_aRoleMap) |
1330 | 0 | { |
1331 | 0 | aLine.append("/" + role.first + "/" + role.second.m_aTag + "\n"); |
1332 | 0 | } |
1333 | 0 | aLine.append( ">>\n" ); |
1334 | 0 | } |
1335 | 0 | if (!m_StructElemObjsWithID.empty()) |
1336 | 0 | { |
1337 | 0 | nIDTree = createObject(); |
1338 | 0 | aLine.append("/IDTree "); |
1339 | 0 | appendObjectReference(nIDTree, aLine); |
1340 | 0 | aLine.append("\n"); |
1341 | 0 | } |
1342 | 0 | } |
1343 | 0 | else |
1344 | 0 | { |
1345 | 0 | aLine.append("/StructElem"); |
1346 | | |
1347 | | // tdf#173162 a type PDF 2.0 dropped stays in the 1.7 namespace, which ISO 14289-2 |
1348 | | // 8.2.4 allows alongside it |
1349 | 0 | auto iterator = m_aNamespacesMap.find( |
1350 | 0 | isPDF17OnlyType(*rEle.m_oType) ? constNamespacePDF17 : constNamespacePDF2); |
1351 | 0 | if (iterator != m_aNamespacesMap.end()) |
1352 | 0 | aWriter.writeKeyAndReference("/NS", iterator->second); |
1353 | 0 | aLine.append("/S/"); |
1354 | 0 | if( !rEle.m_aAlias.isEmpty() ) |
1355 | 0 | aLine.append( rEle.m_aAlias ); |
1356 | 0 | else |
1357 | 0 | aLine.append( getStructureTag(*rEle.m_oType) ); |
1358 | 0 | if (m_StructElemObjsWithID.find(rEle.m_nObject) != m_StructElemObjsWithID.end()) |
1359 | 0 | { |
1360 | 0 | aLine.append("\n/ID "); |
1361 | 0 | aWriter.writeLiteralEncrypt(GenerateID(rEle.m_nObject), rEle.m_nObject); |
1362 | 0 | } |
1363 | 0 | aLine.append( |
1364 | 0 | "\n" |
1365 | 0 | "/P " |
1366 | 0 | + OString::number(m_aStructure[ rEle.m_nParentElement ].m_nObject) |
1367 | 0 | + " 0 R\n" |
1368 | 0 | "/Pg " |
1369 | 0 | + OString::number(rEle.m_nFirstPageObject) |
1370 | 0 | + " 0 R\n" ); |
1371 | 0 | if (!rEle.m_RefElements.empty()) |
1372 | 0 | { |
1373 | 0 | aLine.append("/Ref["); |
1374 | 0 | for (const auto nRef : rEle.m_RefElements) |
1375 | 0 | { |
1376 | 0 | aLine.append(" "); |
1377 | 0 | appendObjectReference(m_aStructure[nRef].m_nObject, aLine); |
1378 | 0 | } |
1379 | 0 | aLine.append("]\n"); |
1380 | 0 | } |
1381 | 0 | if( !rEle.m_aActualText.isEmpty() ) |
1382 | 0 | { |
1383 | 0 | aLine.append( "/ActualText" ); |
1384 | 0 | aWriter.writeUnicodeEncrypt(rEle.m_aActualText, rEle.m_nObject); |
1385 | 0 | aLine.append( "\n" ); |
1386 | 0 | } |
1387 | 0 | if( !rEle.m_aAltText.isEmpty() ) |
1388 | 0 | { |
1389 | 0 | aLine.append( "/Alt" ); |
1390 | 0 | aWriter.writeUnicodeEncrypt(rEle.m_aAltText, rEle.m_nObject); |
1391 | 0 | aLine.append( "\n" ); |
1392 | 0 | } |
1393 | 0 | } |
1394 | 0 | if( (! rEle.m_aBBox.IsEmpty()) || (! rEle.m_aAttributes.empty()) ) |
1395 | 0 | { |
1396 | 0 | OString aAttribs = emitStructureAttributes( rEle ); |
1397 | 0 | if( !aAttribs.isEmpty() ) |
1398 | 0 | { |
1399 | 0 | aLine.append( "/A" + aAttribs + "\n" ); |
1400 | 0 | } |
1401 | 0 | } |
1402 | 0 | if( !rEle.m_aLocale.Language.isEmpty() ) |
1403 | 0 | { |
1404 | | /* PDF allows only RFC 3066, which is only partly BCP 47 and does not |
1405 | | * include script tags and others. |
1406 | | * http://pdf.editme.com/pdfua-naturalLanguageSpecification |
1407 | | * http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf#page=886 |
1408 | | * https://www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf#M13.9.19332.1Heading.97.Natural.Language.Specification |
1409 | | * */ |
1410 | 0 | LanguageTag aLanguageTag( rEle.m_aLocale); |
1411 | 0 | OUString aLanguage, aScript, aCountry; |
1412 | 0 | aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry); |
1413 | 0 | if (!aLanguage.isEmpty()) |
1414 | 0 | { |
1415 | 0 | OUStringBuffer aLocBuf( 16 ); |
1416 | 0 | aLocBuf.append( aLanguage ); |
1417 | 0 | if( !aCountry.isEmpty() ) |
1418 | 0 | { |
1419 | 0 | aLocBuf.append( "-" + aCountry ); |
1420 | 0 | } |
1421 | 0 | aLine.append( "/Lang" ); |
1422 | 0 | aWriter.writeLiteralEncrypt(aLocBuf.makeStringAndClear(), rEle.m_nObject); |
1423 | 0 | aLine.append( "\n" ); |
1424 | 0 | } |
1425 | 0 | } |
1426 | 0 | if (!rEle.m_AnnotIds.empty()) |
1427 | 0 | { |
1428 | 0 | for (auto const id : rEle.m_AnnotIds) |
1429 | 0 | { |
1430 | 0 | auto const it(m_aLinkPropertyMap.find(id)); |
1431 | 0 | assert(it != m_aLinkPropertyMap.end()); |
1432 | |
|
1433 | 0 | if (*rEle.m_oType == StructElement::Form) |
1434 | 0 | { |
1435 | 0 | assert(0 <= it->second && o3tl::make_unsigned(it->second) < m_aWidgets.size()); |
1436 | 0 | AppendAnnotKid(rEle, m_aWidgets[it->second]); |
1437 | 0 | } |
1438 | 0 | else |
1439 | 0 | { |
1440 | 0 | assert(0 <= it->second && o3tl::make_unsigned(it->second) < m_aScreens.size()); |
1441 | 0 | AppendAnnotKid(rEle, m_aScreens[it->second]); |
1442 | 0 | } |
1443 | 0 | } |
1444 | 0 | } |
1445 | 0 | for (const auto id : rEle.m_LinkAnnotIds) |
1446 | 0 | { |
1447 | 0 | sal_Int32 nLink(id); |
1448 | 0 | const auto it(m_aLinkPropertyMap.find(nLink)); |
1449 | 0 | if (it != m_aLinkPropertyMap.end()) |
1450 | 0 | nLink = it->second; |
1451 | 0 | if (0 <= nLink && o3tl::make_unsigned(nLink) < m_aLinks.size()) |
1452 | 0 | { |
1453 | 0 | AppendAnnotKid(rEle, m_aLinks[nLink]); |
1454 | 0 | } |
1455 | 0 | else |
1456 | 0 | { |
1457 | 0 | SAL_WARN("vcl.pdfwriter", "unresolved link id " << nLink << " for Link structure"); |
1458 | 0 | if (g_bDebugDisableCompression) |
1459 | 0 | { |
1460 | 0 | const OString aComment |
1461 | 0 | = "unresolved link id " + OString::number(nLink) + " for Link structure"; |
1462 | 0 | emitComment(aComment.getStr()); |
1463 | 0 | } |
1464 | 0 | } |
1465 | 0 | } |
1466 | 0 | if( ! rEle.m_aKids.empty() ) |
1467 | 0 | { |
1468 | 0 | unsigned int i = 0; |
1469 | 0 | aLine.append( "/K[" ); |
1470 | 0 | for (auto const& rKid : rEle.m_aKids) |
1471 | 0 | { |
1472 | 0 | if (std::holds_alternative<ObjReference>(rKid)) |
1473 | 0 | { |
1474 | 0 | ObjReference const& rObj(std::get<ObjReference>(rKid)); |
1475 | 0 | appendObjectReference(rObj.nObject, aLine); |
1476 | 0 | aLine.append( ( (i & 15) == 15 ) ? "\n" : " " ); |
1477 | 0 | } |
1478 | 0 | else if (std::holds_alternative<ObjReferenceObj>(rKid)) |
1479 | 0 | { |
1480 | 0 | ObjReferenceObj const& rObj(std::get<ObjReferenceObj>(rKid)); |
1481 | 0 | aLine.append("<</Type/OBJR/Obj "); |
1482 | 0 | appendObjectReference(rObj.nObject, aLine); |
1483 | 0 | aLine.append(">>\n"); |
1484 | 0 | } |
1485 | 0 | else |
1486 | 0 | { |
1487 | 0 | assert(std::holds_alternative<MCIDReference>(rKid)); |
1488 | 0 | MCIDReference const& rMCID(std::get<MCIDReference>(rKid)); |
1489 | 0 | if (rMCID.nPageObj == rEle.m_nFirstPageObject) |
1490 | 0 | { |
1491 | 0 | aLine.append(OString::number(rMCID.nMCID) + " "); |
1492 | 0 | } |
1493 | 0 | else |
1494 | 0 | { |
1495 | 0 | aLine.append("<</Type/MCR/Pg "); |
1496 | 0 | appendObjectReference(rMCID.nPageObj, aLine); |
1497 | 0 | aLine.append(" /MCID " + OString::number(rMCID.nMCID) + ">>\n"); |
1498 | 0 | } |
1499 | 0 | } |
1500 | 0 | ++i; |
1501 | 0 | } |
1502 | 0 | aLine.append( "]\n" ); |
1503 | 0 | } |
1504 | 0 | aLine.append(">>"); |
1505 | |
|
1506 | 0 | if (!writeObject(rEle.m_nObject, aLine)) |
1507 | 0 | return 0; |
1508 | | |
1509 | 0 | if (!emitStructParentTree(nParentTree)) return 0; |
1510 | 0 | if (!emitStructIDTree(nIDTree)) return 0; |
1511 | | |
1512 | 0 | return rEle.m_nObject; |
1513 | 0 | } |
1514 | | |
1515 | | bool PDFWriterImpl::emitGradients() |
1516 | 0 | { |
1517 | 0 | return std::all_of(m_aGradients.begin(), m_aGradients.end(), |
1518 | 0 | [this](const auto& gradient) { return writeGradientFunction(gradient); }); |
1519 | 0 | } |
1520 | | |
1521 | | bool PDFWriterImpl::emitTilings() |
1522 | 0 | { |
1523 | 0 | OStringBuffer aTilingObj( PDFWRITER_IMPL_BUFFERSIZE ); |
1524 | |
|
1525 | 0 | for (auto & tiling : m_aTilings) |
1526 | 0 | { |
1527 | 0 | SAL_WARN_IF( !tiling.m_pTilingStream, "vcl.pdfwriter", "tiling without stream" ); |
1528 | 0 | if( ! tiling.m_pTilingStream ) |
1529 | 0 | continue; |
1530 | | |
1531 | 0 | aTilingObj.setLength( 0 ); |
1532 | |
|
1533 | 0 | if (g_bDebugDisableCompression) |
1534 | 0 | { |
1535 | 0 | emitComment( "PDFWriterImpl::emitTilings" ); |
1536 | 0 | } |
1537 | |
|
1538 | 0 | sal_Int32 nX = static_cast<sal_Int32>(tiling.m_aRectangle.Left()); |
1539 | 0 | sal_Int32 nY = static_cast<sal_Int32>(tiling.m_aRectangle.Top()); |
1540 | 0 | sal_Int32 nW = static_cast<sal_Int32>(tiling.m_aRectangle.GetWidth()); |
1541 | 0 | sal_Int32 nH = static_cast<sal_Int32>(tiling.m_aRectangle.GetHeight()); |
1542 | 0 | if( tiling.m_aCellSize.Width() == 0 ) |
1543 | 0 | tiling.m_aCellSize.setWidth( nW ); |
1544 | 0 | if( tiling.m_aCellSize.Height() == 0 ) |
1545 | 0 | tiling.m_aCellSize.setHeight( nH ); |
1546 | |
|
1547 | 0 | bool bDeflate = compressStream( tiling.m_pTilingStream.get() ); |
1548 | 0 | sal_uInt64 const nTilingStreamSize = tiling.m_pTilingStream->TellEnd(); |
1549 | 0 | tiling.m_pTilingStream->Seek( STREAM_SEEK_TO_BEGIN ); |
1550 | | |
1551 | | // write pattern object |
1552 | 0 | aTilingObj.append( |
1553 | 0 | OString::number(tiling.m_nObject) |
1554 | 0 | + " 0 obj\n" |
1555 | 0 | "<</Type/Pattern/PatternType 1\n" |
1556 | 0 | "/PaintType 1\n" |
1557 | 0 | "/TilingType 2\n" |
1558 | 0 | "/BBox[" ); |
1559 | 0 | appendFixedInt( nX, aTilingObj ); |
1560 | 0 | aTilingObj.append( ' ' ); |
1561 | 0 | appendFixedInt( nY, aTilingObj ); |
1562 | 0 | aTilingObj.append( ' ' ); |
1563 | 0 | appendFixedInt( nX+nW, aTilingObj ); |
1564 | 0 | aTilingObj.append( ' ' ); |
1565 | 0 | appendFixedInt( nY+nH, aTilingObj ); |
1566 | 0 | aTilingObj.append( "]\n" |
1567 | 0 | "/XStep " ); |
1568 | 0 | appendFixedInt( tiling.m_aCellSize.Width(), aTilingObj ); |
1569 | 0 | aTilingObj.append( "\n" |
1570 | 0 | "/YStep " ); |
1571 | 0 | appendFixedInt( tiling.m_aCellSize.Height(), aTilingObj ); |
1572 | 0 | aTilingObj.append( "\n" ); |
1573 | 0 | if( tiling.m_aTransform.matrix[0] != 1.0 || |
1574 | 0 | tiling.m_aTransform.matrix[1] != 0.0 || |
1575 | 0 | tiling.m_aTransform.matrix[3] != 0.0 || |
1576 | 0 | tiling.m_aTransform.matrix[4] != 1.0 || |
1577 | 0 | tiling.m_aTransform.matrix[2] != 0.0 || |
1578 | 0 | tiling.m_aTransform.matrix[5] != 0.0 ) |
1579 | 0 | { |
1580 | 0 | aTilingObj.append( "/Matrix [" ); |
1581 | | // TODO: scaling, mirroring on y, etc |
1582 | 0 | appendDouble( tiling.m_aTransform.matrix[0], aTilingObj ); |
1583 | 0 | aTilingObj.append( ' ' ); |
1584 | 0 | appendDouble( tiling.m_aTransform.matrix[1], aTilingObj ); |
1585 | 0 | aTilingObj.append( ' ' ); |
1586 | 0 | appendDouble( tiling.m_aTransform.matrix[3], aTilingObj ); |
1587 | 0 | aTilingObj.append( ' ' ); |
1588 | 0 | appendDouble( tiling.m_aTransform.matrix[4], aTilingObj ); |
1589 | 0 | aTilingObj.append( ' ' ); |
1590 | 0 | appendDouble( tiling.m_aTransform.matrix[2], aTilingObj ); |
1591 | 0 | aTilingObj.append( ' ' ); |
1592 | 0 | appendDouble( tiling.m_aTransform.matrix[5], aTilingObj ); |
1593 | 0 | aTilingObj.append( "]\n" ); |
1594 | 0 | } |
1595 | 0 | aTilingObj.append( "/Resources" ); |
1596 | 0 | tiling.m_aResources.append(aTilingObj, getFontDictObject(), m_aContext.Version); |
1597 | 0 | if( bDeflate ) |
1598 | 0 | aTilingObj.append( "/Filter/FlateDecode" ); |
1599 | 0 | aTilingObj.append( "/Length " |
1600 | 0 | + OString::number(calculateStreamSize(nTilingStreamSize)) |
1601 | 0 | + ">>\nstream\n" ); |
1602 | 0 | if ( !updateObject( tiling.m_nObject ) ) return false; |
1603 | 0 | if ( !writeBuffer( aTilingObj ) ) return false; |
1604 | 0 | checkAndEnableStreamEncryption( tiling.m_nObject ); |
1605 | 0 | bool written = writeBufferBytes( tiling.m_pTilingStream->GetData(), nTilingStreamSize ); |
1606 | 0 | tiling.m_pTilingStream.reset(); |
1607 | 0 | if( !written ) |
1608 | 0 | return false; |
1609 | 0 | disableStreamEncryption(); |
1610 | 0 | aTilingObj.setLength( 0 ); |
1611 | 0 | aTilingObj.append( "\nendstream\nendobj\n\n" ); |
1612 | 0 | if ( !writeBuffer( aTilingObj ) ) return false; |
1613 | 0 | } |
1614 | 0 | return true; |
1615 | 0 | } |
1616 | | |
1617 | | sal_Int32 PDFWriterImpl::emitBuildinFont(const pdf::BuildinFontFace* pFD, sal_Int32 nFontObject) |
1618 | 0 | { |
1619 | 0 | if( !pFD ) |
1620 | 0 | return 0; |
1621 | 0 | const pdf::BuildinFont& rBuildinFont = pFD->GetBuildinFont(); |
1622 | |
|
1623 | 0 | OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE ); |
1624 | |
|
1625 | 0 | if( nFontObject <= 0 ) |
1626 | 0 | nFontObject = createObject(); |
1627 | 0 | if (!updateObject(nFontObject)) |
1628 | 0 | return 0; |
1629 | 0 | aLine.append( |
1630 | 0 | OString::number(nFontObject) |
1631 | 0 | + " 0 obj\n" |
1632 | 0 | "<</Type/Font/Subtype/Type1/BaseFont/" ); |
1633 | 0 | COSWriter::appendName( rBuildinFont.m_pPSName, aLine ); |
1634 | 0 | aLine.append( "\n" ); |
1635 | 0 | if( rBuildinFont.m_eCharSet == RTL_TEXTENCODING_MS_1252 ) |
1636 | 0 | aLine.append( "/Encoding/WinAnsiEncoding\n" ); |
1637 | 0 | aLine.append( ">>\nendobj\n\n" ); |
1638 | 0 | if (!writeBuffer(aLine)) |
1639 | 0 | return 0; |
1640 | 0 | return nFontObject; |
1641 | 0 | } |
1642 | | |
1643 | | |
1644 | | std::map< sal_Int32, sal_Int32 > PDFWriterImpl::emitSystemFont( const vcl::font::PhysicalFontFace* pFace, EmbedFont const & rEmbed ) |
1645 | 0 | { |
1646 | 0 | std::map< sal_Int32, sal_Int32 > aRet; |
1647 | |
|
1648 | 0 | if (g_bDebugDisableCompression) |
1649 | 0 | emitComment("PDFWriterImpl::emitSystemFont"); |
1650 | |
|
1651 | 0 | FontSubsetInfo aInfo; |
1652 | | // fill in dummy values |
1653 | 0 | aInfo.m_nAscent = 1000; |
1654 | 0 | aInfo.m_nDescent = 200; |
1655 | 0 | aInfo.m_nCapHeight = 1000; |
1656 | 0 | aInfo.m_aFontBBox = tools::Rectangle( Point( -200, -200 ), Size( 1700, 1700 ) ); |
1657 | 0 | aInfo.m_aPSName = pFace->GetFamilyName(); |
1658 | |
|
1659 | 0 | sal_Int32 pWidths[256] = { 0 }; |
1660 | 0 | const LogicalFontInstance* pFontInstance = rEmbed.m_pFontInstance; |
1661 | 0 | auto nUPEM = pFace->UnitsPerEm(); |
1662 | 0 | for( sal_Ucs c = 32; c < 256; c++ ) |
1663 | 0 | { |
1664 | 0 | sal_GlyphId nGlyph = pFontInstance->GetGlyphIndex(c); |
1665 | 0 | pWidths[c] = XUnits(nUPEM, pFontInstance->GetGlyphWidth(nGlyph, false, false)); |
1666 | 0 | } |
1667 | | |
1668 | | // We are interested only in filling aInfo |
1669 | 0 | sal_GlyphId aGlyphIds[] = { 0 }; |
1670 | 0 | sal_uInt8 pEncoding[] = { 0 }; |
1671 | 0 | std::vector<sal_uInt8> aBuffer; |
1672 | 0 | pFace->CreateFontSubset(aBuffer, aGlyphIds, pEncoding, 1, aInfo); |
1673 | | |
1674 | | // write font descriptor |
1675 | 0 | sal_Int32 nFontDescriptor = emitFontDescriptor( pFace, aInfo, 0, 0, 0 ); |
1676 | 0 | if( nFontDescriptor ) |
1677 | 0 | { |
1678 | | // write font object |
1679 | 0 | sal_Int32 nObject = createObject(); |
1680 | 0 | if( updateObject( nObject ) ) |
1681 | 0 | { |
1682 | 0 | OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE ); |
1683 | 0 | aLine.append( |
1684 | 0 | OString::number(nObject) |
1685 | 0 | + " 0 obj\n" |
1686 | 0 | "<</Type/Font/Subtype/TrueType" |
1687 | 0 | "/BaseFont/" ); |
1688 | 0 | COSWriter::appendName( aInfo.m_aPSName, aLine ); |
1689 | 0 | aLine.append( "\n" ); |
1690 | 0 | if (!pFace->IsMicrosoftSymbolEncoded()) |
1691 | 0 | aLine.append( "/Encoding/WinAnsiEncoding\n" ); |
1692 | 0 | aLine.append( "/FirstChar 32 /LastChar 255\n" |
1693 | 0 | "/Widths[" ); |
1694 | 0 | for( int i = 32; i < 256; i++ ) |
1695 | 0 | { |
1696 | 0 | aLine.append( pWidths[i] ); |
1697 | 0 | aLine.append( ((i&15) == 15) ? "\n" : " " ); |
1698 | 0 | } |
1699 | 0 | aLine.append( "]\n" |
1700 | 0 | "/FontDescriptor " |
1701 | 0 | + OString::number( nFontDescriptor ) |
1702 | 0 | + " 0 R>>\n" |
1703 | 0 | "endobj\n\n" ); |
1704 | 0 | writeBuffer( aLine ); |
1705 | |
|
1706 | 0 | aRet[ rEmbed.m_nNormalFontID ] = nObject; |
1707 | 0 | } |
1708 | 0 | } |
1709 | |
|
1710 | 0 | return aRet; |
1711 | 0 | } |
1712 | | |
1713 | | namespace |
1714 | | { |
1715 | | uint32_t fillSubsetArrays(const FontEmit& rSubset, sal_GlyphId* pGlyphIds, sal_Int32* pWidths, |
1716 | | sal_uInt8* pEncoding, sal_Int32* pEncToUnicodeIndex, |
1717 | | sal_Int32* pCodeUnitsPerGlyph, std::vector<sal_Ucs>& rCodeUnits, |
1718 | | sal_Int32& nToUnicodeStream) |
1719 | 16.3k | { |
1720 | 16.3k | rCodeUnits.reserve(256); |
1721 | | |
1722 | | // if it gets used then it will appear in s_subset.m_aMapping, otherwise 0 is fine |
1723 | 16.3k | pWidths[0] = 0; |
1724 | | |
1725 | 16.3k | uint32_t nGlyphs = 1; |
1726 | 16.3k | for (auto const& item : rSubset.m_aMapping) |
1727 | 489k | { |
1728 | 489k | sal_uInt8 nEnc = item.second.getGlyphId(); |
1729 | | |
1730 | 489k | SAL_WARN_IF(pGlyphIds[nEnc] != 0 || pEncoding[nEnc] != 0, "vcl.pdfwriter", |
1731 | 489k | "duplicate glyph"); |
1732 | 489k | SAL_WARN_IF(nEnc > rSubset.m_aMapping.size(), "vcl.pdfwriter", "invalid glyph encoding"); |
1733 | | |
1734 | 489k | pGlyphIds[nEnc] = item.first; |
1735 | 489k | pEncoding[nEnc] = nEnc; |
1736 | 489k | pEncToUnicodeIndex[nEnc] = rCodeUnits.size(); |
1737 | 489k | pCodeUnitsPerGlyph[nEnc] = item.second.countCodes(); |
1738 | 489k | pWidths[nEnc] = item.second.getGlyphWidth(); |
1739 | 977k | for (sal_Int32 n = 0; n < pCodeUnitsPerGlyph[nEnc]; n++) |
1740 | 487k | rCodeUnits.push_back(item.second.getCode(n)); |
1741 | 489k | if (item.second.getCode(0)) |
1742 | 487k | nToUnicodeStream = 1; |
1743 | 489k | if (nGlyphs < 256) |
1744 | 489k | nGlyphs++; |
1745 | 0 | else |
1746 | 489k | OSL_FAIL("too many glyphs for subset"); |
1747 | 489k | } |
1748 | | |
1749 | 16.3k | return nGlyphs; |
1750 | 16.3k | } |
1751 | | } |
1752 | | |
1753 | | bool PDFWriterImpl::emitType3Font(const vcl::font::PhysicalFontFace* pFace, |
1754 | | const FontSubset& rType3Font, |
1755 | | std::map<sal_Int32, sal_Int32>& rFontIDToObject) |
1756 | 0 | { |
1757 | 0 | if (g_bDebugDisableCompression) |
1758 | 0 | emitComment("PDFWriterImpl::emitType3Font"); |
1759 | |
|
1760 | 0 | const auto& rColorPalettes = pFace->GetColorPalettes(); |
1761 | |
|
1762 | 0 | FontSubsetInfo aSubsetInfo; |
1763 | 0 | sal_GlyphId pTempGlyphIds[] = { 0 }; |
1764 | 0 | sal_uInt8 pTempEncoding[] = { 0 }; |
1765 | 0 | std::vector<sal_uInt8> aBuffer; |
1766 | 0 | pFace->CreateFontSubset(aBuffer, pTempGlyphIds, pTempEncoding, 1, aSubsetInfo); |
1767 | |
|
1768 | 0 | for (auto& rSubset : rType3Font.m_aSubsets) |
1769 | 0 | { |
1770 | 0 | sal_GlyphId pGlyphIds[256] = {}; |
1771 | 0 | sal_Int32 pWidths[256]; |
1772 | 0 | sal_uInt8 pEncoding[256] = {}; |
1773 | 0 | sal_Int32 pEncToUnicodeIndex[256] = {}; |
1774 | 0 | sal_Int32 pCodeUnitsPerGlyph[256] = {}; |
1775 | 0 | std::vector<sal_Ucs> aCodeUnits; |
1776 | 0 | sal_Int32 nToUnicodeStream = 0; |
1777 | | |
1778 | | // fill arrays and prepare encoding index map |
1779 | 0 | auto nGlyphs = fillSubsetArrays(rSubset, pGlyphIds, pWidths, pEncoding, pEncToUnicodeIndex, |
1780 | 0 | pCodeUnitsPerGlyph, aCodeUnits, nToUnicodeStream); |
1781 | | |
1782 | | // write font descriptor |
1783 | 0 | sal_Int32 nFontDescriptor = 0; |
1784 | 0 | if (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_4) |
1785 | 0 | nFontDescriptor = emitFontDescriptor(pFace, aSubsetInfo, rSubset.m_nFontID, 0, 0); |
1786 | |
|
1787 | 0 | if (nToUnicodeStream) |
1788 | 0 | nToUnicodeStream = createToUnicodeCMap(pEncoding, aCodeUnits, pCodeUnitsPerGlyph, |
1789 | 0 | pEncToUnicodeIndex, nGlyphs); |
1790 | | |
1791 | | // write font object |
1792 | 0 | sal_Int32 nFontObject = createObject(); |
1793 | 0 | if (!updateObject(nFontObject)) |
1794 | 0 | return false; |
1795 | | |
1796 | 0 | OStringBuffer aLine(PDFWRITER_IMPL_BUFFERSIZE); |
1797 | 0 | aLine.append( |
1798 | 0 | OString::number(nFontObject) |
1799 | 0 | + " 0 obj\n" |
1800 | 0 | "<</Type/Font/Subtype/Type3/Name/"); |
1801 | 0 | COSWriter::appendName(aSubsetInfo.m_aPSName, aLine); |
1802 | |
|
1803 | 0 | aLine.append( |
1804 | 0 | "\n/FontBBox[" |
1805 | | // note: Top and Bottom are reversed in VCL and PDF rectangles |
1806 | 0 | + OString::number(aSubsetInfo.m_aFontBBox.Left()) |
1807 | 0 | + " " |
1808 | 0 | + OString::number(aSubsetInfo.m_aFontBBox.Top()) |
1809 | 0 | + " " |
1810 | 0 | + OString::number(aSubsetInfo.m_aFontBBox.Right()) |
1811 | 0 | + " " |
1812 | 0 | + OString::number(aSubsetInfo.m_aFontBBox.Bottom() + 1) |
1813 | 0 | + "]\n"); |
1814 | | |
1815 | | // tdf#155610 |
1816 | | // Adobe Acrobat does not seem to like certain UPEMs, so instead of |
1817 | | // setting the FontMatrix scale relative to the UPEM, we always set to |
1818 | | // 0.001 (1000 UPEM) and scale everything if the font’s UPEM is |
1819 | | // different. |
1820 | 0 | double fScale = 1000. / pFace->UnitsPerEm(); |
1821 | |
|
1822 | 0 | aLine.append("/FontMatrix[0.001 0 0 0.001 0 0]\n"); |
1823 | |
|
1824 | 0 | sal_Int32 pGlyphStreams[256] = {}; |
1825 | 0 | aLine.append("/CharProcs<<\n"); |
1826 | 0 | for (auto i = 1u; i < nGlyphs; i++) |
1827 | 0 | { |
1828 | 0 | auto nStream = createObject(); |
1829 | 0 | aLine.append("/" |
1830 | 0 | + pFace->GetGlyphName(pGlyphIds[i], true) |
1831 | 0 | + " " |
1832 | 0 | + OString::number(nStream) |
1833 | 0 | + " 0 R\n"); |
1834 | 0 | pGlyphStreams[i] = nStream; |
1835 | 0 | } |
1836 | 0 | aLine.append(">>\n" |
1837 | |
|
1838 | 0 | "/Encoding<</Type/Encoding/Differences[1"); |
1839 | 0 | for (auto i = 1u; i < nGlyphs; i++) |
1840 | 0 | aLine.append(" /" + pFace->GetGlyphName(pGlyphIds[i], true)); |
1841 | 0 | aLine.append("]>>\n" |
1842 | |
|
1843 | 0 | "/FirstChar 0\n" |
1844 | 0 | "/LastChar " |
1845 | 0 | + OString::number(nGlyphs - 1) |
1846 | 0 | + "\n" |
1847 | |
|
1848 | 0 | "/Widths["); |
1849 | 0 | for (auto i = 0u; i < nGlyphs; i++) |
1850 | 0 | { |
1851 | 0 | appendDouble(pWidths[i] * fScale, aLine); |
1852 | 0 | aLine.append(" "); |
1853 | 0 | } |
1854 | 0 | aLine.append("]\n"); |
1855 | |
|
1856 | 0 | if (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_4) |
1857 | 0 | { |
1858 | 0 | aLine.append("/FontDescriptor " + OString::number(nFontDescriptor) + " 0 R\n"); |
1859 | 0 | } |
1860 | |
|
1861 | 0 | auto nResources = createObject(); |
1862 | 0 | aLine.append("/Resources " + OString::number(nResources) + " 0 R\n"); |
1863 | |
|
1864 | 0 | if (nToUnicodeStream) |
1865 | 0 | { |
1866 | 0 | aLine.append("/ToUnicode " + OString::number(nToUnicodeStream) + " 0 R\n"); |
1867 | 0 | } |
1868 | |
|
1869 | 0 | aLine.append(">>\n" |
1870 | 0 | "endobj\n\n"); |
1871 | |
|
1872 | 0 | if (!writeBuffer(aLine)) |
1873 | 0 | return false; |
1874 | | |
1875 | 0 | std::set<sal_Int32> aUsedFonts; |
1876 | 0 | std::list<BitmapEmit> aUsedBitmaps; |
1877 | 0 | std::map<sal_uInt8, sal_Int32> aUsedAlpha; |
1878 | 0 | ResourceDict aResourceDict; |
1879 | 0 | std::list<StreamRedirect> aOutputStreams; |
1880 | |
|
1881 | 0 | for (auto i = 1u; i < nGlyphs; i++) |
1882 | 0 | { |
1883 | 0 | auto nStream = pGlyphStreams[i]; |
1884 | 0 | if (!updateObject(nStream)) |
1885 | 0 | return false; |
1886 | 0 | OStringBuffer aContents(PDFWRITER_IMPL_BUFFERSIZE); |
1887 | 0 | appendDouble(pWidths[i] * fScale, aContents); |
1888 | 0 | aContents.append(" 0 d0\n"); |
1889 | |
|
1890 | 0 | const auto& rGlyph = rSubset.m_aMapping.find(pGlyphIds[i])->second; |
1891 | 0 | const auto& rLayers = rGlyph.getColorLayers(); |
1892 | 0 | for (const auto& rLayer : rLayers) |
1893 | 0 | { |
1894 | 0 | aUsedFonts.insert(rLayer.m_nFontID); |
1895 | |
|
1896 | 0 | aContents.append("q "); |
1897 | | // 0xFFFF is a special value means foreground color. |
1898 | 0 | if (rLayer.m_nColorIndex != 0xFFFF) |
1899 | 0 | { |
1900 | 0 | auto& rPalette = rColorPalettes[0]; |
1901 | 0 | auto aColor(rPalette[rLayer.m_nColorIndex]); |
1902 | 0 | appendNonStrokingColor(aColor, aContents); |
1903 | 0 | aContents.append(" "); |
1904 | 0 | if (aColor.GetAlpha() != 0xFF |
1905 | 0 | && m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4) |
1906 | 0 | { |
1907 | 0 | auto nAlpha = aColor.GetAlpha(); |
1908 | 0 | OStringBuffer aName(16); |
1909 | 0 | aName.append("GS"); |
1910 | 0 | COSWriter::appendHex(nAlpha, aName); |
1911 | |
|
1912 | 0 | aContents.append("/" + aName + " gs "); |
1913 | |
|
1914 | 0 | if (aUsedAlpha.find(nAlpha) == aUsedAlpha.end()) |
1915 | 0 | { |
1916 | 0 | auto nObject = createObject(); |
1917 | 0 | aUsedAlpha[nAlpha] = nObject; |
1918 | 0 | pushResource(ResourceKind::ExtGState, aName.makeStringAndClear(), |
1919 | 0 | nObject, aResourceDict, aOutputStreams); |
1920 | 0 | } |
1921 | 0 | } |
1922 | 0 | } |
1923 | 0 | aContents.append( |
1924 | 0 | "BT " |
1925 | 0 | "/F" + OString::number(rLayer.m_nFontID) + " "); |
1926 | 0 | appendDouble(pFace->UnitsPerEm() * fScale, aContents); |
1927 | 0 | aContents.append( |
1928 | 0 | " Tf " |
1929 | 0 | "<"); |
1930 | 0 | COSWriter::appendHex(rLayer.m_nSubsetGlyphID, aContents); |
1931 | 0 | aContents.append( |
1932 | 0 | ">Tj " |
1933 | 0 | "ET " |
1934 | 0 | "Q\n"); |
1935 | 0 | } |
1936 | |
|
1937 | 0 | tools::Rectangle aRect; |
1938 | 0 | const auto& rBitmapData = rGlyph.getColorBitmap(aRect); |
1939 | 0 | if (!rBitmapData.empty()) |
1940 | 0 | { |
1941 | 0 | SvMemoryStream aStream(const_cast<uint8_t*>(rBitmapData.data()), rBitmapData.size(), |
1942 | 0 | StreamMode::READ); |
1943 | 0 | vcl::PngImageReader aReader(aStream); |
1944 | |
|
1945 | 0 | Bitmap aBitmap = aReader.read(); |
1946 | 0 | const BitmapEmit& rBitmapEmit = createBitmapEmit(aBitmap, Graphic(), |
1947 | 0 | aUsedBitmaps, aResourceDict, |
1948 | 0 | aOutputStreams); |
1949 | |
|
1950 | 0 | auto nObject = rBitmapEmit.m_aReferenceXObject.getObject(); |
1951 | 0 | aContents.append("q "); |
1952 | 0 | appendDouble(aRect.GetWidth() * fScale, aContents); |
1953 | 0 | aContents.append(" 0 0 "); |
1954 | 0 | appendDouble(aRect.GetHeight() * fScale, aContents); |
1955 | 0 | aContents.append(" "); |
1956 | 0 | appendDouble(aRect.getX() * fScale, aContents); |
1957 | 0 | aContents.append(" "); |
1958 | 0 | appendDouble(aRect.getY() * fScale, aContents); |
1959 | 0 | aContents.append(" cm /Im" + OString::number(nObject) + " Do Q\n"); |
1960 | 0 | } |
1961 | | |
1962 | | // The newline before `endstream` is not counted as within the stream |
1963 | 0 | assert(aContents[aContents.getLength() - 1] == '\n'); |
1964 | 0 | aContents.setLength(aContents.getLength() - 1); |
1965 | 0 | assert(aContents[aContents.getLength() - 1] != '\n'); |
1966 | 0 | aLine.setLength(0); |
1967 | 0 | aLine.append(OString::number(nStream) |
1968 | 0 | + " 0 obj\n<</Length " |
1969 | 0 | + OString::number(calculateStreamSize(aContents.getLength())) |
1970 | 0 | + ">>\nstream\n"); |
1971 | 0 | if (!writeBuffer(aLine)) |
1972 | 0 | return false; |
1973 | 0 | checkAndEnableStreamEncryption(nStream); |
1974 | 0 | if (!writeBuffer(aContents)) |
1975 | 0 | return false; |
1976 | 0 | disableStreamEncryption(); |
1977 | 0 | aLine.setLength(0); |
1978 | 0 | aLine.append("\nendstream\nendobj\n\n"); |
1979 | 0 | if (!writeBuffer(aLine)) |
1980 | 0 | return false; |
1981 | 0 | } |
1982 | | |
1983 | | // write font dict |
1984 | 0 | sal_Int32 nFontDict = 0; |
1985 | 0 | if (!aUsedFonts.empty()) |
1986 | 0 | { |
1987 | 0 | nFontDict = createObject(); |
1988 | 0 | aLine.setLength(0); |
1989 | 0 | aLine.append(OString::number(nFontDict) + " 0 obj\n<<"); |
1990 | 0 | for (auto nFontID : aUsedFonts) |
1991 | 0 | { |
1992 | 0 | aLine.append("/F" |
1993 | 0 | + OString::number(nFontID) |
1994 | 0 | + " " |
1995 | 0 | + OString::number(rFontIDToObject[nFontID]) |
1996 | 0 | + " 0 R"); |
1997 | 0 | } |
1998 | 0 | aLine.append(">>\nendobj\n\n"); |
1999 | 0 | if (!updateObject(nFontDict)) |
2000 | 0 | return false; |
2001 | 0 | if (!writeBuffer(aLine)) |
2002 | 0 | return false; |
2003 | 0 | } |
2004 | | |
2005 | | // write ExtGState objects |
2006 | 0 | if (!aUsedAlpha.empty()) |
2007 | 0 | { |
2008 | 0 | for (const auto & [ nAlpha, nObject ] : aUsedAlpha) |
2009 | 0 | { |
2010 | 0 | aLine.setLength(0); |
2011 | 0 | aLine.append(OString::number(nObject) + " 0 obj\n<<"); |
2012 | 0 | if (m_bIsPDF_A1) |
2013 | 0 | { |
2014 | 0 | aLine.append("/CA 1.0/ca 1.0"); |
2015 | 0 | m_aErrors.insert(PDFWriter::Warning_Transparency_Omitted_PDFA); |
2016 | 0 | } |
2017 | 0 | else |
2018 | 0 | { |
2019 | 0 | aLine.append("/CA "); |
2020 | 0 | appendDouble(nAlpha / 255., aLine); |
2021 | 0 | aLine.append("/ca "); |
2022 | 0 | appendDouble(nAlpha / 255., aLine); |
2023 | 0 | } |
2024 | 0 | aLine.append(">>\nendobj\n\n"); |
2025 | 0 | if (!updateObject(nObject)) |
2026 | 0 | return false; |
2027 | 0 | if (!writeBuffer(aLine)) |
2028 | 0 | return false; |
2029 | 0 | } |
2030 | 0 | } |
2031 | | |
2032 | | // write bitmap objects |
2033 | 0 | for (auto& aBitmap : aUsedBitmaps) |
2034 | 0 | writeBitmapObject(aBitmap); |
2035 | | |
2036 | | // write resources dict |
2037 | 0 | aLine.setLength(0); |
2038 | 0 | aLine.append(OString::number(nResources) + " 0 obj\n"); |
2039 | 0 | aResourceDict.append(aLine, nFontDict, m_aContext.Version); |
2040 | 0 | aLine.append("endobj\n\n"); |
2041 | 0 | if (!updateObject(nResources)) |
2042 | 0 | return false; |
2043 | 0 | if (!writeBuffer(aLine)) |
2044 | 0 | return false; |
2045 | | |
2046 | 0 | rFontIDToObject[rSubset.m_nFontID] = nFontObject; |
2047 | 0 | } |
2048 | | |
2049 | 0 | return true; |
2050 | 0 | } |
2051 | | |
2052 | | static void appendSubsetName( int nSubsetID, std::u16string_view rPSName, OStringBuffer& rBuffer ) |
2053 | 48.9k | { |
2054 | 48.9k | if( nSubsetID ) |
2055 | 48.9k | { |
2056 | 342k | for( int i = 0; i < 6; i++ ) |
2057 | 293k | { |
2058 | 293k | int nOffset = nSubsetID % 26; |
2059 | 293k | nSubsetID /= 26; |
2060 | 293k | rBuffer.append( static_cast<char>('A'+nOffset) ); |
2061 | 293k | } |
2062 | 48.9k | rBuffer.append( '+' ); |
2063 | 48.9k | } |
2064 | 48.9k | COSWriter::appendName( rPSName, rBuffer ); |
2065 | 48.9k | } |
2066 | | |
2067 | | sal_Int32 PDFWriterImpl::createToUnicodeCMap( sal_uInt8 const * pEncoding, |
2068 | | const std::vector<sal_Ucs>& rCodeUnits, |
2069 | | const sal_Int32* pCodeUnitsPerGlyph, |
2070 | | const sal_Int32* pEncToUnicodeIndex, |
2071 | | uint32_t nGlyphs ) |
2072 | 16.3k | { |
2073 | 16.3k | int nMapped = 0; |
2074 | 521k | for (auto n = 0u; n < nGlyphs; ++n) |
2075 | 505k | if (pCodeUnitsPerGlyph[n] && rCodeUnits[pEncToUnicodeIndex[n]]) |
2076 | 487k | nMapped++; |
2077 | | |
2078 | 16.3k | if( nMapped == 0 ) |
2079 | 0 | return 0; |
2080 | | |
2081 | 16.3k | sal_Int32 nStream = createObject(); |
2082 | 16.3k | if (!updateObject(nStream)) |
2083 | 0 | return 0; |
2084 | 16.3k | OStringBuffer aContents( PDFWRITER_IMPL_BUFFERSIZE ); |
2085 | 16.3k | aContents.append( |
2086 | 16.3k | "/CIDInit/ProcSet findresource begin\n" |
2087 | 16.3k | "12 dict begin\n" |
2088 | 16.3k | "begincmap\n" |
2089 | 16.3k | "/CIDSystemInfo<<\n" |
2090 | 16.3k | "/Registry (Adobe)\n" |
2091 | 16.3k | "/Ordering (UCS)\n" |
2092 | 16.3k | "/Supplement 0\n" |
2093 | 16.3k | ">> def\n" |
2094 | 16.3k | "/CMapName/Adobe-Identity-UCS def\n" |
2095 | 16.3k | "/CMapType 2 def\n" |
2096 | 16.3k | "1 begincodespacerange\n" |
2097 | 16.3k | "<00> <FF>\n" |
2098 | 16.3k | "endcodespacerange\n" |
2099 | 16.3k | ); |
2100 | 16.3k | int nCount = 0; |
2101 | 521k | for (auto n = 0u; n < nGlyphs; ++n) |
2102 | 505k | { |
2103 | 505k | if (pCodeUnitsPerGlyph[n] && rCodeUnits[pEncToUnicodeIndex[n]]) |
2104 | 487k | { |
2105 | 487k | if( (nCount % 100) == 0 ) |
2106 | 16.6k | { |
2107 | 16.6k | if( nCount ) |
2108 | 359 | aContents.append( "endbfchar\n" ); |
2109 | 16.6k | aContents.append( OString::number(std::min(nMapped-nCount, 100) ) |
2110 | 16.6k | + " beginbfchar\n" ); |
2111 | 16.6k | } |
2112 | 487k | aContents.append( '<' ); |
2113 | 487k | COSWriter::appendHex(static_cast<sal_Int8>(pEncoding[n]), aContents); |
2114 | 487k | aContents.append( "> <" ); |
2115 | | // TODO: handle code points>U+FFFF |
2116 | 487k | sal_Int32 nIndex = pEncToUnicodeIndex[n]; |
2117 | 975k | for( sal_Int32 j = 0; j < pCodeUnitsPerGlyph[n]; j++ ) |
2118 | 487k | { |
2119 | 487k | COSWriter::appendHex(static_cast<sal_Int8>(rCodeUnits[nIndex + j] / 256), aContents); |
2120 | 487k | COSWriter::appendHex(static_cast<sal_Int8>(rCodeUnits[nIndex + j] & 255), aContents); |
2121 | 487k | } |
2122 | 487k | aContents.append( ">\n" ); |
2123 | 487k | nCount++; |
2124 | 487k | } |
2125 | 505k | } |
2126 | 16.3k | aContents.append( "endbfchar\n" |
2127 | 16.3k | "endcmap\n" |
2128 | 16.3k | "CMapName currentdict /CMap defineresource pop\n" |
2129 | 16.3k | "end\n" |
2130 | 16.3k | "end\n" ); |
2131 | 16.3k | SvMemoryStream aStream; |
2132 | 16.3k | if (!g_bDebugDisableCompression) |
2133 | 16.3k | { |
2134 | 16.3k | ZCodec aCodec( 0x4000, 0x4000 ); |
2135 | 16.3k | aCodec.BeginCompression(); |
2136 | 16.3k | aCodec.Write( aStream, reinterpret_cast<const sal_uInt8*>(aContents.getStr()), aContents.getLength() ); |
2137 | 16.3k | aCodec.EndCompression(); |
2138 | 16.3k | } |
2139 | | |
2140 | 16.3k | if (g_bDebugDisableCompression) |
2141 | 0 | { |
2142 | 0 | emitComment( "PDFWriterImpl::createToUnicodeCMap" ); |
2143 | 0 | } |
2144 | 16.3k | OStringBuffer aLine( 40 ); |
2145 | | |
2146 | 16.3k | aLine.append( OString::number(nStream ) + " 0 obj\n<</Length " ); |
2147 | 16.3k | sal_uInt64 nLen = 0; |
2148 | 16.3k | if (!g_bDebugDisableCompression) |
2149 | 16.3k | { |
2150 | 16.3k | nLen = aStream.Tell(); |
2151 | 16.3k | aStream.Seek( 0 ); |
2152 | 16.3k | aLine.append(OString::number(calculateStreamSize(nLen)) + "/Filter/FlateDecode"); |
2153 | 16.3k | } |
2154 | 0 | else |
2155 | 0 | aLine.append(OString::number(calculateStreamSize(aContents.getLength()))); |
2156 | 16.3k | aLine.append( ">>\nstream\n" ); |
2157 | 16.3k | if (!writeBuffer(aLine)) return 0; |
2158 | 16.3k | checkAndEnableStreamEncryption( nStream ); |
2159 | 16.3k | if (!g_bDebugDisableCompression) |
2160 | 16.3k | { |
2161 | 16.3k | if(!writeBufferBytes(aStream.GetData(), nLen)) return 0; |
2162 | 16.3k | } |
2163 | 0 | else |
2164 | 0 | { |
2165 | 0 | if (!writeBuffer(aContents)) return 0; |
2166 | 0 | } |
2167 | 16.3k | disableStreamEncryption(); |
2168 | 16.3k | aLine.setLength( 0 ); |
2169 | 16.3k | aLine.append( "\nendstream\n" |
2170 | 16.3k | "endobj\n\n" ); |
2171 | 16.3k | if (!writeBuffer(aLine)) return 0; |
2172 | 16.3k | return nStream; |
2173 | 16.3k | } |
2174 | | |
2175 | | sal_Int32 PDFWriterImpl::emitCIDCMap(sal_Int32 nSubsetID, sal_uInt32 nGlyphs) |
2176 | 16.3k | { |
2177 | 16.3k | sal_Int32 nStream = createObject(); |
2178 | 16.3k | if (!updateObject(nStream)) |
2179 | 0 | return 0; |
2180 | | |
2181 | 16.3k | OStringBuffer aContents(PDFWRITER_IMPL_BUFFERSIZE); |
2182 | 16.3k | aContents.append("/CIDInit/ProcSet findresource begin\n" |
2183 | 16.3k | "12 dict begin\n" |
2184 | 16.3k | "begincmap\n" |
2185 | 16.3k | "/CIDSystemInfo<<\n" |
2186 | 16.3k | "/Registry (Adobe)\n" |
2187 | 16.3k | "/Ordering (Identity)\n" |
2188 | 16.3k | "/Supplement 0\n" |
2189 | 16.3k | ">> def\n" |
2190 | 16.3k | "/CMapName/Subset-" |
2191 | 16.3k | + OString::number(nSubsetID) |
2192 | 16.3k | + " def\n" |
2193 | 16.3k | "/CMapType 1 def\n" |
2194 | 16.3k | "1 begincodespacerange\n" |
2195 | 16.3k | "<00> <FF>\n" |
2196 | 16.3k | "endcodespacerange\n"); |
2197 | | |
2198 | | // The subset has an identity charset, so the CID of each glyph is its glyph |
2199 | | // ID, which is also the code we encoded it with. |
2200 | 16.3k | aContents.append("1 begincidrange\n<00> <"); |
2201 | 16.3k | COSWriter::appendHex(static_cast<sal_Int8>(nGlyphs - 1), aContents); |
2202 | 16.3k | aContents.append("> 0\nendcidrange\n"); |
2203 | | |
2204 | 16.3k | aContents.append("endcmap\n" |
2205 | 16.3k | "CMapName currentdict /CMap defineresource pop\n" |
2206 | 16.3k | "end\n" |
2207 | 16.3k | "end\n"); |
2208 | | |
2209 | 16.3k | SvMemoryStream aStream; |
2210 | 16.3k | if (!g_bDebugDisableCompression) |
2211 | 16.3k | { |
2212 | 16.3k | ZCodec aCodec(0x4000, 0x4000); |
2213 | 16.3k | aCodec.BeginCompression(); |
2214 | 16.3k | aCodec.Write(aStream, reinterpret_cast<const sal_uInt8*>(aContents.getStr()), |
2215 | 16.3k | aContents.getLength()); |
2216 | 16.3k | aCodec.EndCompression(); |
2217 | 16.3k | } |
2218 | | |
2219 | 16.3k | OStringBuffer aLine(80); |
2220 | 16.3k | aLine.append(OString::number(nStream) |
2221 | 16.3k | + " 0 obj\n" |
2222 | 16.3k | "<</Type/CMap" |
2223 | 16.3k | "/CMapName/Subset-" |
2224 | 16.3k | + OString::number(nSubsetID) |
2225 | 16.3k | + "-CMap" |
2226 | 16.3k | "/CIDSystemInfo<</Registry(Adobe)/Ordering(Identity)/Supplement 0>>" |
2227 | 16.3k | "/Length "); |
2228 | 16.3k | sal_uInt64 nLen = 0; |
2229 | 16.3k | if (!g_bDebugDisableCompression) |
2230 | 16.3k | { |
2231 | 16.3k | nLen = aStream.Tell(); |
2232 | 16.3k | aStream.Seek(0); |
2233 | 16.3k | aLine.append(OString::number(nLen) + "/Filter/FlateDecode"); |
2234 | 16.3k | } |
2235 | 0 | else |
2236 | 0 | aLine.append(aContents.getLength()); |
2237 | 16.3k | aLine.append(">>\nstream\n"); |
2238 | 16.3k | if (!writeBuffer(aLine)) |
2239 | 0 | return 0; |
2240 | 16.3k | checkAndEnableStreamEncryption(nStream); |
2241 | 16.3k | if (!g_bDebugDisableCompression) |
2242 | 16.3k | { |
2243 | 16.3k | if (!writeBufferBytes(aStream.GetData(), nLen)) |
2244 | 0 | return 0; |
2245 | 16.3k | } |
2246 | 0 | else |
2247 | 0 | { |
2248 | 0 | if (!writeBuffer(aContents)) |
2249 | 0 | return 0; |
2250 | 0 | } |
2251 | 16.3k | disableStreamEncryption(); |
2252 | 16.3k | aLine.setLength(0); |
2253 | 16.3k | aLine.append("\nendstream\nendobj\n\n"); |
2254 | 16.3k | if (!writeBuffer(aLine)) |
2255 | 0 | return 0; |
2256 | 16.3k | return nStream; |
2257 | 16.3k | } |
2258 | | |
2259 | | sal_Int32 PDFWriterImpl::emitCIDSet(sal_uInt32 nGlyphs) |
2260 | 16.3k | { |
2261 | | // Only PDF/A-1 requires one, and PDF 2.0 deprecates it. |
2262 | 16.3k | if (m_nPDFA_Version != 1 || !nGlyphs) |
2263 | 16.3k | return 0; |
2264 | | |
2265 | | // A table of bits indexed by CID, high-order bit of the first byte first. |
2266 | | // The charset is identity, so the CIDs are 0 to nGlyphs - 1. |
2267 | 0 | std::vector<sal_uInt8> aCIDSet((nGlyphs - 1) / 8 + 1, 0); |
2268 | 0 | for (sal_uInt32 nCID = 0; nCID < nGlyphs; ++nCID) |
2269 | 0 | aCIDSet[nCID / 8] |= 0x80 >> (nCID % 8); |
2270 | |
|
2271 | 0 | sal_Int32 nStream = createObject(); |
2272 | 0 | if (!updateObject(nStream)) |
2273 | 0 | return 0; |
2274 | | |
2275 | 0 | SvMemoryStream aStream; |
2276 | 0 | if (!g_bDebugDisableCompression) |
2277 | 0 | { |
2278 | 0 | ZCodec aCodec(0x4000, 0x4000); |
2279 | 0 | aCodec.BeginCompression(); |
2280 | 0 | aCodec.Write(aStream, aCIDSet.data(), aCIDSet.size()); |
2281 | 0 | aCodec.EndCompression(); |
2282 | 0 | } |
2283 | |
|
2284 | 0 | OStringBuffer aLine(80); |
2285 | 0 | aLine.append(OString::number(nStream) + " 0 obj\n<</Length "); |
2286 | 0 | sal_uInt64 nLen = 0; |
2287 | 0 | if (!g_bDebugDisableCompression) |
2288 | 0 | { |
2289 | 0 | nLen = aStream.Tell(); |
2290 | 0 | aStream.Seek(0); |
2291 | 0 | aLine.append(OString::number(nLen) + "/Filter/FlateDecode"); |
2292 | 0 | } |
2293 | 0 | else |
2294 | 0 | aLine.append(OString::number(aCIDSet.size())); |
2295 | 0 | aLine.append(">>\nstream\n"); |
2296 | 0 | if (!writeBuffer(aLine)) |
2297 | 0 | return 0; |
2298 | 0 | checkAndEnableStreamEncryption(nStream); |
2299 | 0 | if (!g_bDebugDisableCompression) |
2300 | 0 | { |
2301 | 0 | if (!writeBufferBytes(aStream.GetData(), nLen)) |
2302 | 0 | return 0; |
2303 | 0 | } |
2304 | 0 | else |
2305 | 0 | { |
2306 | 0 | if (!writeBufferBytes(aCIDSet.data(), aCIDSet.size())) |
2307 | 0 | return 0; |
2308 | 0 | } |
2309 | 0 | disableStreamEncryption(); |
2310 | 0 | aLine.setLength(0); |
2311 | 0 | aLine.append("\nendstream\nendobj\n\n"); |
2312 | 0 | if (!writeBuffer(aLine)) |
2313 | 0 | return 0; |
2314 | 0 | return nStream; |
2315 | 0 | } |
2316 | | |
2317 | | sal_Int32 PDFWriterImpl::emitFontDescriptor( const vcl::font::PhysicalFontFace* pFace, FontSubsetInfo const & rInfo, sal_Int32 nSubsetID, sal_Int32 nFontStream, sal_Int32 nCIDSet ) |
2318 | 16.3k | { |
2319 | 16.3k | OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE ); |
2320 | | // get font flags, see PDF reference 1.4 p. 358 |
2321 | | // possibly characters outside Adobe standard encoding |
2322 | | // so set Symbolic flag |
2323 | 16.3k | sal_Int32 nFontFlags = (1<<2); |
2324 | 16.3k | if( pFace->GetItalic() == ITALIC_NORMAL || pFace->GetItalic() == ITALIC_OBLIQUE ) |
2325 | 889 | nFontFlags |= (1 << 6); |
2326 | 16.3k | if( pFace->GetPitch() == PITCH_FIXED ) |
2327 | 0 | nFontFlags |= 1; |
2328 | 16.3k | if( pFace->GetFamilyType() == FAMILY_SCRIPT ) |
2329 | 0 | nFontFlags |= (1 << 3); |
2330 | 16.3k | else if( pFace->GetFamilyType() == FAMILY_ROMAN ) |
2331 | 0 | nFontFlags |= (1 << 1); |
2332 | | |
2333 | 16.3k | sal_Int32 nFontDescriptor = createObject(); |
2334 | 16.3k | if (!updateObject(nFontDescriptor)) return 0; |
2335 | 16.3k | aLine.setLength( 0 ); |
2336 | 16.3k | aLine.append( |
2337 | 16.3k | OString::number(nFontDescriptor) |
2338 | 16.3k | + " 0 obj\n" |
2339 | 16.3k | "<</Type/FontDescriptor/FontName/" ); |
2340 | 16.3k | appendSubsetName( nSubsetID, rInfo.m_aPSName, aLine ); |
2341 | 16.3k | aLine.append( "\n" |
2342 | 16.3k | "/Flags " |
2343 | 16.3k | + OString::number( nFontFlags ) |
2344 | 16.3k | + "\n" |
2345 | 16.3k | "/FontBBox[" |
2346 | | // note: Top and Bottom are reversed in VCL and PDF rectangles |
2347 | 16.3k | + OString::number( rInfo.m_aFontBBox.Left() ) |
2348 | 16.3k | + " " |
2349 | 16.3k | + OString::number( rInfo.m_aFontBBox.Top() ) |
2350 | 16.3k | + " " |
2351 | 16.3k | + OString::number( rInfo.m_aFontBBox.Right() ) |
2352 | 16.3k | + " " |
2353 | 16.3k | + OString::number( rInfo.m_aFontBBox.Bottom()+1 ) |
2354 | 16.3k | + "]/ItalicAngle " ); |
2355 | 16.3k | if( pFace->GetItalic() == ITALIC_OBLIQUE || pFace->GetItalic() == ITALIC_NORMAL ) |
2356 | 889 | aLine.append( "-30" ); |
2357 | 15.4k | else |
2358 | 15.4k | aLine.append( "0" ); |
2359 | 16.3k | aLine.append( "\n" |
2360 | 16.3k | "/Ascent " |
2361 | 16.3k | + OString::number( rInfo.m_nAscent ) |
2362 | 16.3k | + "\n" |
2363 | 16.3k | "/Descent " |
2364 | 16.3k | + OString::number( -rInfo.m_nDescent ) |
2365 | 16.3k | + "\n" |
2366 | 16.3k | "/CapHeight " |
2367 | 16.3k | + OString::number( rInfo.m_nCapHeight ) |
2368 | | // According to PDF reference 1.4 StemV is required |
2369 | | // seems a tad strange to me, but well ... |
2370 | 16.3k | + "\n" |
2371 | 16.3k | "/StemV 80\n" ); |
2372 | 16.3k | if( nFontStream ) |
2373 | 16.3k | { |
2374 | 16.3k | aLine.append( "/FontFile" ); |
2375 | 16.3k | switch( rInfo.m_nFontType ) |
2376 | 16.3k | { |
2377 | 16.3k | case FontType::SFNT_TTF: |
2378 | 16.3k | aLine.append( '2' ); |
2379 | 16.3k | break; |
2380 | 0 | case FontType::CFF_FONT: |
2381 | 0 | aLine.append( '3' ); |
2382 | 0 | break; |
2383 | 0 | default: |
2384 | 0 | OSL_FAIL( "unknown fonttype in PDF font descriptor" ); |
2385 | 0 | return 0; |
2386 | 16.3k | } |
2387 | 16.3k | aLine.append( " " + OString::number(nFontStream) + " 0 R\n" ); |
2388 | 16.3k | } |
2389 | 16.3k | if( nCIDSet ) |
2390 | 0 | aLine.append( "/CIDSet " + OString::number(nCIDSet) + " 0 R\n" ); |
2391 | 16.3k | aLine.append( ">>\n" |
2392 | 16.3k | "endobj\n\n" ); |
2393 | 16.3k | if (!writeBuffer(aLine)) return 0; |
2394 | | |
2395 | 16.3k | return nFontDescriptor; |
2396 | 16.3k | } |
2397 | | |
2398 | | void PDFWriterImpl::appendBuildinFontsToDict( OStringBuffer& rDict ) const |
2399 | 8.85k | { |
2400 | 8.85k | for (auto const& item : m_aBuildinFontToObjectMap) |
2401 | 0 | { |
2402 | 0 | rDict.append( pdf::BuildinFontFace::Get(item.first).getNameObject() ); |
2403 | 0 | rDict.append( ' ' ); |
2404 | 0 | rDict.append( item.second ); |
2405 | 0 | rDict.append( " 0 R" ); |
2406 | 0 | } |
2407 | 8.85k | } |
2408 | | |
2409 | | bool PDFWriterImpl::emitFonts() |
2410 | 8.85k | { |
2411 | 8.85k | OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE ); |
2412 | | |
2413 | 8.85k | std::map< sal_Int32, sal_Int32 > aFontIDToObject; |
2414 | | |
2415 | 8.85k | for (const auto & subset : m_aSubsets) |
2416 | 16.3k | { |
2417 | 16.3k | for (auto & s_subset :subset.second.m_aSubsets) |
2418 | 16.3k | { |
2419 | 16.3k | sal_GlyphId pGlyphIds[ 256 ] = {}; |
2420 | 16.3k | sal_Int32 pWidths[ 256 ]; |
2421 | 16.3k | sal_uInt8 pEncoding[ 256 ] = {}; |
2422 | 16.3k | sal_Int32 pEncToUnicodeIndex[ 256 ] = {}; |
2423 | 16.3k | sal_Int32 pCodeUnitsPerGlyph[ 256 ] = {}; |
2424 | 16.3k | std::vector<sal_Ucs> aCodeUnits; |
2425 | 16.3k | sal_Int32 nToUnicodeStream = 0; |
2426 | | |
2427 | | // fill arrays and prepare encoding index map |
2428 | 16.3k | auto nGlyphs = fillSubsetArrays(s_subset, pGlyphIds, pWidths, pEncoding, pEncToUnicodeIndex, |
2429 | 16.3k | pCodeUnitsPerGlyph, aCodeUnits, nToUnicodeStream); |
2430 | | |
2431 | 16.3k | std::vector<sal_uInt8> aBuffer; |
2432 | 16.3k | FontSubsetInfo aSubsetInfo; |
2433 | 16.3k | const auto* pFace = subset.first.m_pFace; |
2434 | 16.3k | if (pFace->CreateFontSubset(aBuffer, pGlyphIds, pEncoding, nGlyphs, aSubsetInfo, |
2435 | 16.3k | subset.first.m_rVariations)) |
2436 | 16.3k | { |
2437 | | // create font stream |
2438 | 16.3k | if (g_bDebugDisableCompression) |
2439 | 0 | { |
2440 | 0 | emitComment( "PDFWriterImpl::emitFonts" ); |
2441 | 0 | } |
2442 | 16.3k | sal_Int32 nFontStream = createObject(); |
2443 | 16.3k | sal_Int32 nStreamLengthObject = createObject(); |
2444 | 16.3k | if ( !updateObject( nFontStream ) ) return false; |
2445 | 16.3k | aLine.setLength( 0 ); |
2446 | 16.3k | aLine.append( OString::number(nFontStream) |
2447 | 16.3k | + " 0 obj\n" |
2448 | 16.3k | "<</Length " |
2449 | 16.3k | + OString::number( nStreamLengthObject ) ); |
2450 | 16.3k | if (!g_bDebugDisableCompression) |
2451 | 16.3k | aLine.append( " 0 R" |
2452 | 16.3k | "/Filter/FlateDecode"); |
2453 | 0 | else |
2454 | 0 | aLine.append( " 0 R"); |
2455 | | |
2456 | 16.3k | sal_uInt64 nStartPos = 0; |
2457 | 16.3k | if( aSubsetInfo.m_nFontType == FontType::SFNT_TTF ) |
2458 | 16.3k | { |
2459 | 16.3k | aLine.append("/Length1 " |
2460 | 16.3k | + OString::number(aBuffer.size()) |
2461 | 16.3k | + ">>\n" |
2462 | 16.3k | "stream\n" ); |
2463 | 16.3k | if ( !writeBuffer( aLine ) ) return false; |
2464 | 16.3k | if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false; |
2465 | | |
2466 | | // copy font file |
2467 | 16.3k | beginCompression(); |
2468 | 16.3k | checkAndEnableStreamEncryption( nFontStream ); |
2469 | 16.3k | if (!writeBufferBytes(aBuffer.data(), aBuffer.size())) |
2470 | 0 | return false; |
2471 | 16.3k | } |
2472 | 0 | else if( aSubsetInfo.m_nFontType == FontType::CFF_FONT ) |
2473 | 0 | { |
2474 | 0 | aLine.append("/Subtype/CIDFontType0C>>\nstream\n"); |
2475 | 0 | if ( !writeBuffer( aLine ) ) return false; |
2476 | 0 | if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false; |
2477 | | |
2478 | | // copy bare CFF font program |
2479 | 0 | beginCompression(); |
2480 | 0 | checkAndEnableStreamEncryption( nFontStream ); |
2481 | 0 | if (!writeBufferBytes(aBuffer.data(), aBuffer.size())) |
2482 | 0 | return false; |
2483 | 0 | } |
2484 | 0 | else |
2485 | 0 | { |
2486 | 0 | SAL_INFO("vcl.pdfwriter", "PDF: CreateFontSubset result in not yet supported format=" << static_cast<int>(aSubsetInfo.m_nFontType)); |
2487 | 0 | aLine.append( "0 >>\nstream\n" ); |
2488 | 0 | } |
2489 | | |
2490 | 16.3k | endCompression(); |
2491 | 16.3k | disableStreamEncryption(); |
2492 | | |
2493 | 16.3k | sal_uInt64 nEndPos = 0; |
2494 | 16.3k | if ( osl::File::E_None != m_aFile.getPos(nEndPos) ) return false; |
2495 | | // end the stream |
2496 | 16.3k | aLine.setLength( 0 ); |
2497 | 16.3k | aLine.append( "\nendstream\nendobj\n\n" ); |
2498 | 16.3k | if ( !writeBuffer( aLine ) ) return false; |
2499 | | |
2500 | | // emit stream length object |
2501 | 16.3k | if ( !updateObject( nStreamLengthObject ) ) return false; |
2502 | 16.3k | aLine.setLength( 0 ); |
2503 | 16.3k | aLine.append( OString::number(nStreamLengthObject) |
2504 | 16.3k | + " 0 obj\n" |
2505 | 16.3k | + OString::number( nEndPos-nStartPos ) |
2506 | 16.3k | + "\nendobj\n\n" ); |
2507 | 16.3k | if ( !writeBuffer( aLine ) ) return false; |
2508 | | |
2509 | | // PDF/A-1 wants the CIDs of a composite subset listed |
2510 | 16.3k | sal_Int32 nCIDSet = emitCIDSet(nGlyphs); |
2511 | | |
2512 | | // write font descriptor |
2513 | 16.3k | sal_Int32 nFontDescriptor = emitFontDescriptor( subset.first.m_pFace, aSubsetInfo, s_subset.m_nFontID, nFontStream, nCIDSet ); |
2514 | | |
2515 | 16.3k | if( nToUnicodeStream ) |
2516 | 16.3k | nToUnicodeStream = createToUnicodeCMap( pEncoding, aCodeUnits, pCodeUnitsPerGlyph, pEncToUnicodeIndex, nGlyphs ); |
2517 | | |
2518 | | // Emit the CMap and the descendant CIDFont before the Type 0 |
2519 | | // wrapper. CFF subsets are CIDFontType0, glyf ones CIDFontType2. |
2520 | 16.3k | bool bTrueType = aSubsetInfo.m_nFontType == FontType::SFNT_TTF; |
2521 | 16.3k | sal_Int32 nCMapStream = emitCIDCMap(s_subset.m_nFontID, nGlyphs); |
2522 | 16.3k | if (!nCMapStream) return false; |
2523 | | |
2524 | 16.3k | sal_Int32 nCIDFontObject = createObject(); |
2525 | 16.3k | if (!updateObject(nCIDFontObject)) return false; |
2526 | 16.3k | { |
2527 | 16.3k | OStringBuffer aCIDLine; |
2528 | 16.3k | aCIDLine.append(OString::number(nCIDFontObject) + " 0 obj\n" |
2529 | 16.3k | "<</Type/Font/Subtype/"); |
2530 | 16.3k | aCIDLine.append(bTrueType ? "CIDFontType2" : "CIDFontType0"); |
2531 | 16.3k | aCIDLine.append("/BaseFont/"); |
2532 | 16.3k | appendSubsetName(s_subset.m_nFontID, aSubsetInfo.m_aPSName, aCIDLine); |
2533 | 16.3k | aCIDLine.append("\n/CIDSystemInfo<</Registry(Adobe)/Ordering(Identity)/Supplement 0>>\n" |
2534 | 16.3k | "/FontDescriptor " + OString::number(nFontDescriptor) + " 0 R\n"); |
2535 | 16.3k | if (bTrueType) |
2536 | 16.3k | aCIDLine.append("/CIDToGIDMap/Identity\n"); |
2537 | | // /W is indexed by CID, which is the glyph ID here |
2538 | 16.3k | aCIDLine.append("/W[0["); |
2539 | 521k | for (auto i = 0u; i < nGlyphs; i++) |
2540 | 505k | { |
2541 | 505k | aCIDLine.append(pWidths[i]); |
2542 | 505k | aCIDLine.append(((i & 15) == 15) ? "\n" : " "); |
2543 | 505k | } |
2544 | 16.3k | aCIDLine.append("]]\n>>\nendobj\n\n"); |
2545 | 16.3k | if (!writeBuffer(aCIDLine)) return false; |
2546 | 16.3k | } |
2547 | | |
2548 | 16.3k | sal_Int32 nFontObject = createObject(); |
2549 | 16.3k | if ( !updateObject( nFontObject ) ) return false; |
2550 | 16.3k | aLine.setLength( 0 ); |
2551 | 16.3k | aLine.append( OString::number(nFontObject) + " 0 obj\n" |
2552 | 16.3k | "<</Type/Font/Subtype/Type0/BaseFont/"); |
2553 | 16.3k | appendSubsetName(s_subset.m_nFontID, aSubsetInfo.m_aPSName, aLine); |
2554 | 16.3k | aLine.append("\n/Encoding " + OString::number(nCMapStream) + " 0 R\n" |
2555 | 16.3k | "/DescendantFonts[" + OString::number(nCIDFontObject) + " 0 R]\n"); |
2556 | 16.3k | if (nToUnicodeStream) |
2557 | 16.3k | { |
2558 | 16.3k | aLine.append("/ToUnicode " + OString::number(nToUnicodeStream) + " 0 R\n"); |
2559 | 16.3k | } |
2560 | 16.3k | aLine.append(">>\nendobj\n\n"); |
2561 | 16.3k | if ( !writeBuffer( aLine ) ) return false; |
2562 | | |
2563 | 16.3k | aFontIDToObject[ s_subset.m_nFontID ] = nFontObject; |
2564 | 16.3k | } |
2565 | 0 | else |
2566 | 0 | { |
2567 | 0 | OStringBuffer aErrorComment( 256 ); |
2568 | 0 | aErrorComment.append( "CreateFontSubset failed for font \"" |
2569 | 0 | + OUStringToOString( pFace->GetFamilyName(), RTL_TEXTENCODING_UTF8 ) |
2570 | 0 | + "\"" ); |
2571 | 0 | if( pFace->GetItalic() == ITALIC_NORMAL ) |
2572 | 0 | aErrorComment.append( " italic" ); |
2573 | 0 | else if( pFace->GetItalic() == ITALIC_OBLIQUE ) |
2574 | 0 | aErrorComment.append( " oblique" ); |
2575 | 0 | aErrorComment.append( " weight=" + OString::number( sal_Int32(pFace->GetWeight()) ) ); |
2576 | 0 | emitComment( aErrorComment.getStr() ); |
2577 | 0 | } |
2578 | 16.3k | } |
2579 | 16.3k | } |
2580 | | |
2581 | | // emit system fonts |
2582 | 8.85k | for (auto const& systemFont : m_aSystemFonts) |
2583 | 0 | { |
2584 | 0 | std::map< sal_Int32, sal_Int32 > aObjects = emitSystemFont( systemFont.first, systemFont.second ); |
2585 | 0 | for (auto const& item : aObjects) |
2586 | 0 | { |
2587 | 0 | if ( !item.second ) return false; |
2588 | 0 | aFontIDToObject[ item.first ] = item.second; |
2589 | 0 | } |
2590 | 0 | } |
2591 | | |
2592 | | // emit Type3 fonts |
2593 | 8.85k | for (auto const& it : m_aType3Fonts) |
2594 | 0 | { |
2595 | 0 | if (!emitType3Font(it.first, it.second, aFontIDToObject)) |
2596 | 0 | return false; |
2597 | 0 | } |
2598 | | |
2599 | 8.85k | OStringBuffer aFontDict( PDFWRITER_IMPL_BUFFERSIZE ); |
2600 | 8.85k | aFontDict.append( OString::number(getFontDictObject()) |
2601 | 8.85k | + " 0 obj\n" |
2602 | 8.85k | "<<" ); |
2603 | 8.85k | int ni = 0; |
2604 | 8.85k | for (auto const& itemMap : aFontIDToObject) |
2605 | 16.3k | { |
2606 | 16.3k | aFontDict.append( "/F" |
2607 | 16.3k | + OString::number( itemMap.first ) |
2608 | 16.3k | + " " |
2609 | 16.3k | + OString::number( itemMap.second ) |
2610 | 16.3k | + " 0 R" ); |
2611 | 16.3k | if( ((++ni) & 7) == 0 ) |
2612 | 36 | aFontDict.append( '\n' ); |
2613 | 16.3k | } |
2614 | | // emit builtin font for widget appearances / variable text |
2615 | 8.85k | for (auto & item : m_aBuildinFontToObjectMap) |
2616 | 0 | { |
2617 | 0 | rtl::Reference<pdf::BuildinFontFace> aData(new pdf::BuildinFontFace(item.first)); |
2618 | 0 | item.second = emitBuildinFont( aData.get(), item.second ); |
2619 | 0 | } |
2620 | | |
2621 | 8.85k | appendBuildinFontsToDict(aFontDict); |
2622 | 8.85k | aFontDict.append( "\n>>\nendobj\n\n" ); |
2623 | | |
2624 | 8.85k | if ( !updateObject( getFontDictObject() ) ) return false; |
2625 | 8.85k | if ( !writeBuffer( aFontDict ) ) return false; |
2626 | 8.85k | return true; |
2627 | 8.85k | } |
2628 | | |
2629 | | sal_Int32 PDFWriterImpl::emitResources() |
2630 | 8.85k | { |
2631 | | // emit shadings |
2632 | 8.85k | if (!m_aGradients.empty()) |
2633 | 0 | { |
2634 | 0 | if (!emitGradients()) return 0; |
2635 | 0 | } |
2636 | | // emit tilings |
2637 | 8.85k | if (!m_aTilings.empty()) |
2638 | 0 | { |
2639 | 0 | if(!emitTilings()) return 0; |
2640 | 0 | } |
2641 | | |
2642 | | // emit font dict |
2643 | 8.85k | if (!emitFonts()) return 0; |
2644 | | |
2645 | | // emit Resource dict |
2646 | 8.85k | OStringBuffer aLine( 512 ); |
2647 | 8.85k | sal_Int32 nResourceDict = getResourceDictObj(); |
2648 | 8.85k | if (!updateObject(nResourceDict)) |
2649 | 0 | return 0; |
2650 | 8.85k | aLine.setLength( 0 ); |
2651 | 8.85k | aLine.append( OString::number(nResourceDict) |
2652 | 8.85k | + " 0 obj\n" ); |
2653 | 8.85k | m_aGlobalResourceDict.append(aLine, getFontDictObject(), m_aContext.Version); |
2654 | 8.85k | aLine.append( "endobj\n\n" ); |
2655 | 8.85k | if (!writeBuffer(aLine)) return 0; |
2656 | 8.85k | return nResourceDict; |
2657 | 8.85k | } |
2658 | | |
2659 | | sal_Int32 PDFWriterImpl::updateOutlineItemCount( std::vector< sal_Int32 >& rCounts, |
2660 | | sal_Int32 nItemLevel, |
2661 | | sal_Int32 nCurrentItemId ) |
2662 | 1.24k | { |
2663 | | /* The /Count number of an item is |
2664 | | positive: the number of visible subitems |
2665 | | negative: the negative number of subitems that will become visible if |
2666 | | the item gets opened |
2667 | | see PDF ref 1.4 p 478 |
2668 | | */ |
2669 | | |
2670 | 1.24k | sal_Int32 nCount = 0; |
2671 | | |
2672 | 1.24k | if( m_aContext.OpenBookmarkLevels < 0 || // all levels are visible |
2673 | 0 | m_aContext.OpenBookmarkLevels >= nItemLevel // this level is visible |
2674 | 1.24k | ) |
2675 | 1.24k | { |
2676 | 1.24k | PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ]; |
2677 | 1.24k | sal_Int32 nChildren = rItem.m_aChildren.size(); |
2678 | 1.96k | for( sal_Int32 i = 0; i < nChildren; i++ ) |
2679 | 723 | nCount += updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] ); |
2680 | 1.24k | rCounts[nCurrentItemId] = nCount; |
2681 | | // return 1 (this item) + visible sub items |
2682 | 1.24k | if( nCount < 0 ) |
2683 | 0 | nCount = 0; |
2684 | 1.24k | nCount++; |
2685 | 1.24k | } |
2686 | 0 | else |
2687 | 0 | { |
2688 | | // this bookmark level is invisible |
2689 | 0 | PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ]; |
2690 | 0 | sal_Int32 nChildren = rItem.m_aChildren.size(); |
2691 | 0 | rCounts[ nCurrentItemId ] = -sal_Int32(rItem.m_aChildren.size()); |
2692 | 0 | for( sal_Int32 i = 0; i < nChildren; i++ ) |
2693 | 0 | updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] ); |
2694 | 0 | nCount = -1; |
2695 | 0 | } |
2696 | | |
2697 | 1.24k | return nCount; |
2698 | 1.24k | } |
2699 | | |
2700 | | sal_Int32 PDFWriterImpl::emitOutline() |
2701 | 8.85k | { |
2702 | 8.85k | int i, nItems = m_aOutline.size(); |
2703 | | |
2704 | | // do we have an outline at all ? |
2705 | 8.85k | if( nItems < 2 ) |
2706 | 8.33k | return 0; |
2707 | | |
2708 | | // reserve object numbers for all outline items |
2709 | 1.76k | for( i = 0; i < nItems; ++i ) |
2710 | 1.24k | m_aOutline[i].m_nObject = createObject(); |
2711 | | |
2712 | | // update all parent, next and prev object ids |
2713 | 1.76k | for( i = 0; i < nItems; ++i ) |
2714 | 1.24k | { |
2715 | 1.24k | PDFOutlineEntry& rItem = m_aOutline[i]; |
2716 | 1.24k | int nChildren = rItem.m_aChildren.size(); |
2717 | | |
2718 | 1.24k | if( nChildren ) |
2719 | 525 | { |
2720 | 1.24k | for( int n = 0; n < nChildren; ++n ) |
2721 | 723 | { |
2722 | 723 | PDFOutlineEntry& rChild = m_aOutline[ rItem.m_aChildren[n] ]; |
2723 | | |
2724 | 723 | rChild.m_nParentObject = rItem.m_nObject; |
2725 | 723 | rChild.m_nPrevObject = (n > 0) ? m_aOutline[ rItem.m_aChildren[n-1] ].m_nObject : 0; |
2726 | 723 | rChild.m_nNextObject = (n < nChildren-1) ? m_aOutline[ rItem.m_aChildren[n+1] ].m_nObject : 0; |
2727 | 723 | } |
2728 | | |
2729 | 525 | } |
2730 | 1.24k | } |
2731 | | |
2732 | | // calculate Count entries for all items |
2733 | 523 | std::vector< sal_Int32 > aCounts( nItems ); |
2734 | 523 | updateOutlineItemCount( aCounts, 0, 0 ); |
2735 | | |
2736 | | // emit hierarchy |
2737 | 1.76k | for( i = 0; i < nItems; ++i ) |
2738 | 1.24k | { |
2739 | 1.24k | PDFOutlineEntry& rItem = m_aOutline[i]; |
2740 | 1.24k | OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE ); |
2741 | 1.24k | COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor); |
2742 | | |
2743 | 1.24k | if (!updateObject(rItem.m_nObject)) |
2744 | 0 | return 0; |
2745 | 1.24k | aLine.append( OString::number(rItem.m_nObject) |
2746 | 1.24k | + " 0 obj\n" |
2747 | 1.24k | "<<" ); |
2748 | | // number of visible children (all levels) |
2749 | 1.24k | if( i > 0 || aCounts[0] > 0 ) |
2750 | 1.24k | { |
2751 | 1.24k | aLine.append( "/Count " + OString::number( aCounts[i] ) ); |
2752 | 1.24k | } |
2753 | 1.24k | if( ! rItem.m_aChildren.empty() ) |
2754 | 525 | { |
2755 | | // children list: First, Last |
2756 | 525 | aLine.append( "/First " |
2757 | 525 | + OString::number( m_aOutline[rItem.m_aChildren.front()].m_nObject ) |
2758 | 525 | + " 0 R/Last " |
2759 | 525 | + OString::number( m_aOutline[rItem.m_aChildren.back()].m_nObject ) |
2760 | 525 | + " 0 R\n" ); |
2761 | 525 | } |
2762 | 1.24k | if( i > 0 ) |
2763 | 723 | { |
2764 | | // Title, Dest, Parent, Prev, Next |
2765 | 723 | aLine.append( "/Title" ); |
2766 | 723 | aWriter.writeUnicodeEncrypt(rItem.m_aTitle, rItem.m_nObject); |
2767 | 723 | aLine.append( "\n" ); |
2768 | | // Dest is not required |
2769 | 723 | if( rItem.m_nDestID >= 0 && o3tl::make_unsigned(rItem.m_nDestID) < m_aDests.size() ) |
2770 | 723 | { |
2771 | 723 | appendDestOrGoTo(rItem.m_nDestID, aLine); |
2772 | 723 | } |
2773 | 723 | aLine.append( "/Parent " |
2774 | 723 | + OString::number( rItem.m_nParentObject ) |
2775 | 723 | + " 0 R" ); |
2776 | 723 | if( rItem.m_nPrevObject ) |
2777 | 198 | { |
2778 | 198 | aLine.append( "/Prev " |
2779 | 198 | + OString::number( rItem.m_nPrevObject ) |
2780 | 198 | + " 0 R" ); |
2781 | 198 | } |
2782 | 723 | if( rItem.m_nNextObject ) |
2783 | 198 | { |
2784 | 198 | aLine.append( "/Next " |
2785 | 198 | + OString::number( rItem.m_nNextObject ) |
2786 | 198 | + " 0 R" ); |
2787 | 198 | } |
2788 | 723 | } |
2789 | 1.24k | aLine.append( ">>\nendobj\n\n" ); |
2790 | 1.24k | if (!writeBuffer(aLine)) |
2791 | 0 | return 0; |
2792 | 1.24k | } |
2793 | | |
2794 | 523 | return m_aOutline[0].m_nObject; |
2795 | 523 | } |
2796 | | |
2797 | | bool PDFWriterImpl::appendStructureDest(sal_Int32 nDestID, OStringBuffer& rBuffer) |
2798 | 3.54k | { |
2799 | 3.54k | if (nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size()) |
2800 | 0 | return false; |
2801 | | |
2802 | 3.54k | const PDFDest& rDest = m_aDests[nDestID]; |
2803 | 3.54k | if (rDest.m_nStructElement < 0 |
2804 | 0 | || o3tl::make_unsigned(rDest.m_nStructElement) >= m_aStructure.size()) |
2805 | 3.54k | return false; |
2806 | | |
2807 | 0 | const sal_Int32 nObject(m_aStructure[rDest.m_nStructElement].m_nObject); |
2808 | 0 | if (nObject <= 0) // not emitted: NonStruct, or no structure at all |
2809 | 0 | return false; |
2810 | | |
2811 | | // same as the page destination, bar the first entry |
2812 | 0 | OStringBuffer aPageDest; |
2813 | 0 | if (!appendDest(nDestID, aPageDest)) |
2814 | 0 | return false; |
2815 | 0 | const OString aPage(aPageDest.makeStringAndClear()); |
2816 | 0 | const sal_Int32 nAfterPage(aPage.indexOf(" 0 R") + 4); |
2817 | |
|
2818 | 0 | rBuffer.append("[" + OString::number(nObject) + " 0 R" + aPage.subView(nAfterPage)); |
2819 | 0 | return true; |
2820 | 0 | } |
2821 | | |
2822 | | void PDFWriterImpl::appendDestOrGoTo(sal_Int32 nDestID, OStringBuffer& rBuffer) |
2823 | 3.54k | { |
2824 | 3.54k | OStringBuffer aStructure; |
2825 | 3.54k | if (!appendStructureDest(nDestID, aStructure)) |
2826 | 3.54k | { |
2827 | 3.54k | rBuffer.append("/Dest"); |
2828 | 3.54k | appendDest(nDestID, rBuffer); |
2829 | 3.54k | return; |
2830 | 3.54k | } |
2831 | | |
2832 | | // ISO 14289-2 8.8: a destination inside the document shall be a structure destination |
2833 | 0 | rBuffer.append("/A<</Type/Action/S/GoTo/D"); |
2834 | 0 | appendDest(nDestID, rBuffer); |
2835 | 0 | rBuffer.append("/SD" + aStructure.makeStringAndClear() + ">>"); |
2836 | 0 | } |
2837 | | |
2838 | | bool PDFWriterImpl::appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer ) |
2839 | 3.54k | { |
2840 | 3.54k | if( nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size() ) |
2841 | 0 | { |
2842 | 0 | SAL_INFO("vcl.pdfwriter", "ERROR: invalid dest " << nDestID << " requested"); |
2843 | 0 | return false; |
2844 | 0 | } |
2845 | | |
2846 | 3.54k | const PDFDest& rDest = m_aDests[ nDestID ]; |
2847 | 3.54k | const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ]; |
2848 | | |
2849 | 3.54k | rBuffer.append( '[' ); |
2850 | 3.54k | rBuffer.append( rDestPage.m_nPageObject ); |
2851 | 3.54k | rBuffer.append( " 0 R" ); |
2852 | | |
2853 | 3.54k | switch( rDest.m_eType ) |
2854 | 3.54k | { |
2855 | 3.54k | case PDFWriter::DestAreaType::XYZ: |
2856 | 3.54k | default: |
2857 | 3.54k | rBuffer.append( "/XYZ " ); |
2858 | 3.54k | appendFixedInt( rDest.m_aRect.Left(), rBuffer ); |
2859 | 3.54k | rBuffer.append( ' ' ); |
2860 | 3.54k | appendFixedInt( rDest.m_aRect.Bottom(), rBuffer ); |
2861 | 3.54k | rBuffer.append( " 0" ); |
2862 | 3.54k | break; |
2863 | 0 | case PDFWriter::DestAreaType::FitRectangle: |
2864 | 0 | rBuffer.append( "/FitR " ); |
2865 | 0 | appendFixedInt( rDest.m_aRect.Left(), rBuffer ); |
2866 | 0 | rBuffer.append( ' ' ); |
2867 | 0 | appendFixedInt( rDest.m_aRect.Top(), rBuffer ); |
2868 | 0 | rBuffer.append( ' ' ); |
2869 | 0 | appendFixedInt( rDest.m_aRect.Right(), rBuffer ); |
2870 | 0 | rBuffer.append( ' ' ); |
2871 | 0 | appendFixedInt( rDest.m_aRect.Bottom(), rBuffer ); |
2872 | 0 | break; |
2873 | 3.54k | } |
2874 | 3.54k | rBuffer.append( ']' ); |
2875 | | |
2876 | 3.54k | return true; |
2877 | 3.54k | } |
2878 | | |
2879 | | void PDFWriterImpl::addDocumentAttachedFile(OUString const& rFileName, OUString const& rMimeType, OUString const& rDescription, std::unique_ptr<PDFOutputStream> rStream) |
2880 | 0 | { |
2881 | 0 | if (m_nPDFA_Version == 1 || m_nPDFA_Version == 2) |
2882 | 0 | return; |
2883 | | |
2884 | 0 | sal_Int32 nObjectID = addEmbeddedFile(std::move(rStream), rMimeType); |
2885 | 0 | auto& rAttachedFile = m_aDocumentAttachedFiles.emplace_back(); |
2886 | 0 | rAttachedFile.maFilename = rFileName; |
2887 | 0 | rAttachedFile.maMimeType = rMimeType; |
2888 | 0 | rAttachedFile.maDescription = rDescription; |
2889 | 0 | rAttachedFile.mnEmbeddedFileObjectId = nObjectID; |
2890 | 0 | rAttachedFile.mnObjectId = createObject(); |
2891 | 0 | } |
2892 | | |
2893 | | sal_Int32 PDFWriterImpl::addEmbeddedFile(std::unique_ptr<PDFOutputStream> rStream, OUString const& rMimeType) |
2894 | 0 | { |
2895 | 0 | sal_Int32 aObjectID = createObject(); |
2896 | 0 | auto& rEmbedded = m_aEmbeddedFiles.emplace_back(); |
2897 | 0 | rEmbedded.m_nObject = aObjectID; |
2898 | 0 | rEmbedded.m_aSubType = rMimeType; |
2899 | 0 | rEmbedded.m_pStream = std::move(rStream); |
2900 | 0 | return aObjectID; |
2901 | 0 | } |
2902 | | |
2903 | | sal_Int32 PDFWriterImpl::addEmbeddedFile(BinaryDataContainer const & rDataContainer) |
2904 | 0 | { |
2905 | 0 | sal_Int32 aObjectID = createObject(); |
2906 | 0 | m_aEmbeddedFiles.emplace_back(); |
2907 | 0 | m_aEmbeddedFiles.back().m_nObject = aObjectID; |
2908 | 0 | m_aEmbeddedFiles.back().m_aDataContainer = rDataContainer; |
2909 | 0 | return aObjectID; |
2910 | 0 | } |
2911 | | |
2912 | | bool PDFWriterImpl::emitScreenAnnotations() |
2913 | 8.85k | { |
2914 | 8.85k | int nAnnots = m_aScreens.size(); |
2915 | 8.85k | for (int i = 0; i < nAnnots; i++) |
2916 | 0 | { |
2917 | 0 | const PDFScreen& rScreen = m_aScreens[i]; |
2918 | |
|
2919 | 0 | OStringBuffer aLine; |
2920 | 0 | COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor); |
2921 | 0 | bool bEmbed = false; |
2922 | 0 | if (!rScreen.m_aTempFileURL.isEmpty()) |
2923 | 0 | { |
2924 | 0 | bEmbed = true; |
2925 | 0 | if (!updateObject(rScreen.m_nTempFileObject)) |
2926 | 0 | continue; |
2927 | | |
2928 | 0 | SvFileStream aFileStream(rScreen.m_aTempFileURL, StreamMode::READ); |
2929 | 0 | SvMemoryStream aMemoryStream; |
2930 | 0 | aMemoryStream.WriteStream(aFileStream); |
2931 | |
|
2932 | 0 | aLine.append(OString::number(rScreen.m_nTempFileObject) |
2933 | 0 | + " 0 obj\n<< /Type /EmbeddedFile /Length " |
2934 | 0 | + OString::number(calculateStreamSize(aMemoryStream.GetSize())) |
2935 | 0 | + " >>\nstream\n"); |
2936 | 0 | if (!writeBuffer(aLine)) |
2937 | 0 | return false; |
2938 | 0 | aLine.setLength(0); |
2939 | |
|
2940 | 0 | checkAndEnableStreamEncryption(rScreen.m_nTempFileObject); |
2941 | 0 | if (!writeBufferBytes(aMemoryStream.GetData(), aMemoryStream.GetSize())) |
2942 | 0 | return false; |
2943 | 0 | disableStreamEncryption(); |
2944 | |
|
2945 | 0 | aLine.append("\nendstream\nendobj\n\n"); |
2946 | 0 | if (!writeBuffer(aLine)) |
2947 | 0 | return false; |
2948 | 0 | aLine.setLength(0); |
2949 | 0 | } |
2950 | | |
2951 | 0 | if (!updateObject(rScreen.m_nObject)) |
2952 | 0 | continue; |
2953 | | |
2954 | | // Annot dictionary. |
2955 | 0 | aLine.append(OString::number(rScreen.m_nObject) |
2956 | 0 | + " 0 obj\n" |
2957 | 0 | "<</Type/Annot" |
2958 | 0 | "/Subtype/Screen/Rect["); |
2959 | 0 | appendFixedInt(rScreen.m_aRect.Left(), aLine); |
2960 | 0 | aLine.append(' '); |
2961 | 0 | appendFixedInt(rScreen.m_aRect.Top(), aLine); |
2962 | 0 | aLine.append(' '); |
2963 | 0 | appendFixedInt(rScreen.m_aRect.Right(), aLine); |
2964 | 0 | aLine.append(' '); |
2965 | 0 | appendFixedInt(rScreen.m_aRect.Bottom(), aLine); |
2966 | 0 | aLine.append("]"); |
2967 | | |
2968 | | // Action dictionary. |
2969 | 0 | aLine.append("/A<</Type/Action /S/Rendition /AN " |
2970 | 0 | + OString::number(rScreen.m_nObject) |
2971 | 0 | + " 0 R "); |
2972 | | |
2973 | | // Rendition dictionary. |
2974 | 0 | aLine.append("/R<</Type/Rendition /S/MR "); |
2975 | | |
2976 | | // MediaClip dictionary. |
2977 | 0 | aLine.append("/C<</Type/MediaClip /S/MCD "); |
2978 | 0 | if (bEmbed) |
2979 | 0 | { |
2980 | 0 | aLine.append("\n/D << /Type /Filespec /F (<embedded file>) "); |
2981 | 0 | if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version) |
2982 | 0 | { // ISO 14289-1:2014, Clause: 7.11 |
2983 | 0 | aLine.append("/UF (<embedded file>) "); |
2984 | 0 | } |
2985 | 0 | aLine.append("/EF << /F " |
2986 | 0 | + OString::number(rScreen.m_nTempFileObject) |
2987 | 0 | + " 0 R >>"); |
2988 | 0 | } |
2989 | 0 | else |
2990 | 0 | { |
2991 | | // Linked. |
2992 | 0 | aLine.append("\n/D << /Type /Filespec /FS /URL /F "); |
2993 | 0 | aWriter.writeLiteralEncrypt(rScreen.m_aURL, rScreen.m_nObject, osl_getThreadTextEncoding()); |
2994 | 0 | if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version) |
2995 | 0 | { // ISO 14289-1:2014, Clause: 7.11 |
2996 | 0 | aLine.append("/UF "); |
2997 | 0 | aWriter.writeUnicodeEncrypt(rScreen.m_aURL, rScreen.m_nObject); |
2998 | 0 | } |
2999 | 0 | } |
3000 | 0 | if (PDFWriter::PDFVersion::PDF_1_6 <= m_aContext.Version |
3001 | 0 | && !rScreen.m_AltText.isEmpty()) |
3002 | 0 | { // ISO 14289-1:2014, Clause: 7.11 |
3003 | 0 | aLine.append("/Desc "); |
3004 | 0 | aWriter.writeUnicodeEncrypt(rScreen.m_AltText, rScreen.m_nObject); |
3005 | 0 | } |
3006 | 0 | aLine.append(" >>\n"); // end of /D |
3007 | | // Allow playing the video via a tempfile. |
3008 | 0 | aLine.append("/P <</TF (TEMPACCESS)>>"); |
3009 | | // ISO 14289-1:2014, Clause: 7.18.6.2 |
3010 | 0 | aLine.append("/CT "); |
3011 | 0 | aWriter.writeLiteralEncrypt(rScreen.m_MimeType, rScreen.m_nObject); |
3012 | | // ISO 14289-1:2014, Clause: 7.18.6.2 |
3013 | | // Alt text is a "Multi-language Text Array" |
3014 | 0 | aLine.append(" /Alt [ () "); |
3015 | 0 | aWriter.writeUnicodeEncrypt(rScreen.m_AltText, rScreen.m_nObject); |
3016 | 0 | aLine.append(" ] " |
3017 | 0 | ">>"); |
3018 | | |
3019 | | // End Rendition dictionary by requesting play/pause/stop controls. |
3020 | 0 | aLine.append("/P<</BE<</C true >>>>" |
3021 | 0 | ">>"); |
3022 | | |
3023 | | // End Action dictionary. |
3024 | 0 | aLine.append("/OP 0 >>"); |
3025 | |
|
3026 | 0 | if (-1 != rScreen.m_nStructParent) |
3027 | 0 | { |
3028 | 0 | aLine.append("\n/StructParent " |
3029 | 0 | + OString::number(rScreen.m_nStructParent) |
3030 | 0 | + "\n"); |
3031 | 0 | } |
3032 | | |
3033 | | // End Annot dictionary. |
3034 | 0 | aLine.append("/P " |
3035 | 0 | + OString::number(m_aPages[rScreen.m_nPage].m_nPageObject) |
3036 | 0 | + " 0 R\n>>\nendobj\n\n"); |
3037 | 0 | if (!writeBuffer(aLine)) |
3038 | 0 | return false; |
3039 | 0 | } |
3040 | | |
3041 | 8.85k | return true; |
3042 | 8.85k | } |
3043 | | |
3044 | | bool PDFWriterImpl::emitLinkAnnotations() |
3045 | 8.85k | { |
3046 | 8.85k | MARK("PDFWriterImpl::emitLinkAnnotations"); |
3047 | 8.85k | int nAnnots = m_aLinks.size(); |
3048 | 12.2k | for( int i = 0; i < nAnnots; i++ ) |
3049 | 3.34k | { |
3050 | 3.34k | const PDFLink& rLink = m_aLinks[i]; |
3051 | 3.34k | if( ! updateObject( rLink.m_nObject ) ) |
3052 | 0 | continue; |
3053 | 3.34k | if( m_aContext.DefaultLinkAction == PDFWriter::RemoveExternalLinks && rLink.m_nDest < 0 ) |
3054 | 0 | continue; |
3055 | | |
3056 | 3.34k | OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE ); |
3057 | 3.34k | COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor); |
3058 | 3.34k | aLine.append( rLink.m_nObject ); |
3059 | 3.34k | aLine.append( " 0 obj\n" ); |
3060 | | // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should' |
3061 | | // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3 |
3062 | 3.34k | aLine.append( "<</Type/Annot" ); |
3063 | 3.34k | if (m_nPDFA_Version > 0) |
3064 | 0 | aLine.append( "/F 4" ); |
3065 | 3.34k | aLine.append( "/Subtype/Link/Border[0 0 0]/Rect[" ); |
3066 | | |
3067 | 3.34k | appendFixedInt( rLink.m_aRect.Left()-7, aLine );//the +7 to have a better shape of the border rectangle |
3068 | 3.34k | aLine.append( ' ' ); |
3069 | 3.34k | appendFixedInt( rLink.m_aRect.Top(), aLine ); |
3070 | 3.34k | aLine.append( ' ' ); |
3071 | 3.34k | appendFixedInt( rLink.m_aRect.Right()+7, aLine );//the +7 to have a better shape of the border rectangle |
3072 | 3.34k | aLine.append( ' ' ); |
3073 | 3.34k | appendFixedInt( rLink.m_aRect.Bottom(), aLine ); |
3074 | 3.34k | aLine.append( "]" ); |
3075 | | // ISO 14289-1:2014, Clause: 7.18.5 |
3076 | 3.34k | OUString const& rContents(rLink.m_AltText.isEmpty() && rLink.m_nDest < 0 ? rLink.m_aURL |
3077 | 3.34k | : rLink.m_AltText); |
3078 | 3.34k | if (!rContents.isEmpty()) |
3079 | 3.26k | { |
3080 | 3.26k | aLine.append("/Contents"); |
3081 | 3.26k | aWriter.writeUnicodeEncrypt(rContents, rLink.m_nObject); |
3082 | 3.26k | } |
3083 | 3.34k | if( rLink.m_nDest >= 0 ) |
3084 | 2.82k | { |
3085 | 2.82k | appendDestOrGoTo(rLink.m_nDest, aLine); |
3086 | 2.82k | } |
3087 | 524 | else |
3088 | 524 | { |
3089 | | /* |
3090 | | destination is external to the document, so |
3091 | | we check in the following sequence: |
3092 | | |
3093 | | if target type is neither .pdf, nor .od[tpgs], then |
3094 | | check if relative or absolute and act accordingly (use URI or 'launch application' as requested) |
3095 | | end processing |
3096 | | else if target is .od[tpgs]: then |
3097 | | if conversion of type from od[tpgs] to pdf is requested, convert it and this becomes the new target file |
3098 | | processing continue |
3099 | | |
3100 | | if (new)target is .pdf : then |
3101 | | if GotToR is requested, then |
3102 | | convert the target in GoToR where the fragment of the URI is |
3103 | | considered the named destination in the target file, set relative or absolute as requested |
3104 | | else strip the fragment from URL and then set URI or 'launch application' as requested |
3105 | | */ |
3106 | | |
3107 | | // FIXME: check if the decode mechanisms for URL processing throughout this implementation |
3108 | | // are the correct one!! |
3109 | | |
3110 | | // extract target file type |
3111 | 524 | auto url(URIHelper::resolveIdnaHost(rLink.m_aURL)); |
3112 | | |
3113 | 524 | INetURLObject aDocumentURL( m_aContext.BaseURL ); |
3114 | 524 | INetURLObject aTargetURL( url ); |
3115 | 524 | bool bSetGoToRMode = false; |
3116 | 524 | bool bTargetHasPDFExtension = false; |
3117 | 524 | INetProtocol eTargetProtocol = aTargetURL.GetProtocol(); |
3118 | 524 | bool bIsUNCPath = false; |
3119 | 524 | bool bUnparsedURI = false; |
3120 | | |
3121 | | // check if the protocol is a known one, or if there is no protocol at all (on target only) |
3122 | | // if there is no protocol, make the target relative to the current document directory |
3123 | | // getting the needed URL information from the current document path |
3124 | 524 | if( eTargetProtocol == INetProtocol::NotValid ) |
3125 | 100 | { |
3126 | 100 | if( url.getLength() > 4 && url.startsWith("\\\\\\\\")) |
3127 | 0 | { |
3128 | 0 | bIsUNCPath = true; |
3129 | 0 | } |
3130 | 100 | else |
3131 | 100 | { |
3132 | | //reassign the new target URL |
3133 | 100 | aTargetURL = INetURLObject(rtl::Uri::convertRelToAbs( |
3134 | 100 | (m_aContext.BaseURL.getLength() > 0 ? |
3135 | 0 | m_aContext.BaseURL : |
3136 | | //use dummy location if empty |
3137 | 100 | u"http://ahost.ax"_ustr), |
3138 | 100 | url)); |
3139 | | |
3140 | | //recompute the target protocol, with the new URL |
3141 | | //normal URL processing resumes |
3142 | 100 | eTargetProtocol = aTargetURL.GetProtocol(); |
3143 | | |
3144 | 100 | bUnparsedURI = eTargetProtocol == INetProtocol::NotValid; |
3145 | 100 | } |
3146 | 100 | } |
3147 | | |
3148 | 524 | OUString aFileExtension = aTargetURL.GetFileExtension(); |
3149 | | |
3150 | | // Check if the URL ends in '/': if yes it's a directory, |
3151 | | // it will be forced to a URI link. |
3152 | | // possibly a malformed URI, leave it as it is, force as URI |
3153 | 524 | if( aTargetURL.hasFinalSlash() ) |
3154 | 151 | m_aContext.DefaultLinkAction = PDFWriter::URIAction; |
3155 | | |
3156 | 524 | if( !aFileExtension.isEmpty() ) |
3157 | 120 | { |
3158 | 120 | if( m_aContext.ConvertOOoTargetToPDFTarget ) |
3159 | 0 | { |
3160 | 0 | bool bChangeFileExtensionToPDF = false; |
3161 | | //examine the file type (.odm .odt. .odp, odg, ods) |
3162 | 0 | if( aFileExtension.equalsIgnoreAsciiCase( "odm" ) ) |
3163 | 0 | bChangeFileExtensionToPDF = true; |
3164 | 0 | if( aFileExtension.equalsIgnoreAsciiCase( "odt" ) ) |
3165 | 0 | bChangeFileExtensionToPDF = true; |
3166 | 0 | else if( aFileExtension.equalsIgnoreAsciiCase( "odp" ) ) |
3167 | 0 | bChangeFileExtensionToPDF = true; |
3168 | 0 | else if( aFileExtension.equalsIgnoreAsciiCase( "odg" ) ) |
3169 | 0 | bChangeFileExtensionToPDF = true; |
3170 | 0 | else if( aFileExtension.equalsIgnoreAsciiCase( "ods" ) ) |
3171 | 0 | bChangeFileExtensionToPDF = true; |
3172 | 0 | if( bChangeFileExtensionToPDF ) |
3173 | 0 | aTargetURL.setExtension(u"pdf" ); |
3174 | 0 | } |
3175 | | //check if extension is pdf, see if GoToR should be forced |
3176 | 120 | bTargetHasPDFExtension = aTargetURL.GetFileExtension().equalsIgnoreAsciiCase( "pdf" ); |
3177 | 120 | if( m_aContext.ForcePDFAction && bTargetHasPDFExtension ) |
3178 | 0 | bSetGoToRMode = true; |
3179 | 120 | } |
3180 | | //prepare the URL, if relative or not |
3181 | 524 | INetProtocol eBaseProtocol = aDocumentURL.GetProtocol(); |
3182 | | //queue the string common to all types of actions |
3183 | 524 | aLine.append( "/A<</Type/Action/S"); |
3184 | 524 | if( bIsUNCPath ) // handle Win UNC paths |
3185 | 0 | { |
3186 | 0 | aLine.append("/Launch"); |
3187 | | // Entry /Win is deprecated in PDF 2.0 |
3188 | 0 | if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0) |
3189 | 0 | { |
3190 | | // So write /F directly. AFAICS it's up to PDF viewer to resolve this correctly |
3191 | 0 | aWriter.writeKeyAndLiteralEncrypt("/F", url, rLink.m_nObject, osl_getThreadTextEncoding()); |
3192 | 0 | } |
3193 | 0 | else |
3194 | 0 | { |
3195 | 0 | aLine.append("/Win"); |
3196 | 0 | aWriter.startDict(); |
3197 | | // INetURLObject is not good with UNC paths, use original path |
3198 | 0 | aWriter.writeKeyAndLiteralEncrypt("/F", url, rLink.m_nObject, osl_getThreadTextEncoding()); |
3199 | 0 | aWriter.endDict(); |
3200 | 0 | } |
3201 | 0 | } |
3202 | 524 | else |
3203 | 524 | { |
3204 | 524 | bool bSetRelative = false; |
3205 | 524 | bool bFileSpec = false; |
3206 | | //check if relative file link is requested and if the protocol is 'file://' |
3207 | 524 | if( m_aContext.RelFsys && eBaseProtocol == eTargetProtocol && eTargetProtocol == INetProtocol::File ) |
3208 | 0 | bSetRelative = true; |
3209 | | |
3210 | 524 | OUString aFragment = aTargetURL.GetMark( INetURLObject::DecodeMechanism::NONE /*DecodeMechanism::WithCharset*/ ); //fragment as is, |
3211 | 524 | if( !bSetGoToRMode ) |
3212 | 524 | { |
3213 | 524 | switch( m_aContext.DefaultLinkAction ) |
3214 | 524 | { |
3215 | 0 | default: |
3216 | 524 | case PDFWriter::URIAction : |
3217 | 524 | case PDFWriter::URIActionDestination : |
3218 | 524 | aLine.append( "/URI/URI" ); |
3219 | 524 | break; |
3220 | 0 | case PDFWriter::LaunchAction: |
3221 | | // now: |
3222 | | // if a launch action is requested and the hyperlink target has a fragment |
3223 | | // and the target file does not have a pdf extension, or it's not a 'file:://' |
3224 | | // protocol then force the uri action on it |
3225 | | // This code will permit the correct opening of application on web pages, |
3226 | | // the one that normally have fragments (but I may be wrong...) |
3227 | | // and will force the use of URI when the protocol is not file: |
3228 | 0 | if( (!aFragment.isEmpty() && !bTargetHasPDFExtension) || |
3229 | 0 | eTargetProtocol != INetProtocol::File ) |
3230 | 0 | { |
3231 | 0 | aLine.append( "/URI/URI" ); |
3232 | 0 | } |
3233 | 0 | else |
3234 | 0 | { |
3235 | 0 | aLine.append( "/Launch/F" ); |
3236 | 0 | bFileSpec = true; |
3237 | 0 | } |
3238 | 0 | break; |
3239 | 524 | } |
3240 | 524 | } |
3241 | | |
3242 | | //fragment are encoded in the same way as in the named destination processing |
3243 | 524 | if( bSetGoToRMode ) |
3244 | 0 | { |
3245 | | //add the fragment |
3246 | 0 | OUString aURLNoMark = aTargetURL.GetURLNoMark( INetURLObject::DecodeMechanism::WithCharset ); |
3247 | 0 | aLine.append("/GoToR"); |
3248 | 0 | aLine.append("/F"); |
3249 | 0 | aWriter.writeLiteralEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURLNoMark, |
3250 | 0 | INetURLObject::EncodeMechanism::WasEncoded, |
3251 | 0 | INetURLObject::DecodeMechanism::WithCharset ) : |
3252 | 0 | aURLNoMark, rLink.m_nObject, osl_getThreadTextEncoding()); |
3253 | 0 | if( !aFragment.isEmpty() ) |
3254 | 0 | { |
3255 | 0 | aLine.append("/D/"); |
3256 | 0 | appendDestinationName( aFragment , aLine ); |
3257 | 0 | } |
3258 | 0 | } |
3259 | 524 | else |
3260 | 524 | { |
3261 | | // change the fragment to accommodate the bookmark (only if the file extension |
3262 | | // is PDF and the requested action is of the correct type) |
3263 | 524 | if(m_aContext.DefaultLinkAction == PDFWriter::URIActionDestination && |
3264 | 0 | bTargetHasPDFExtension && !aFragment.isEmpty() ) |
3265 | 0 | { |
3266 | 0 | OStringBuffer aLineLoc( PDFWRITER_IMPL_BUFFERSIZE ); |
3267 | 0 | appendDestinationName( aFragment , aLineLoc ); |
3268 | | //substitute the fragment |
3269 | 0 | aTargetURL.SetMark( OStringToOUString(aLineLoc, RTL_TEXTENCODING_ASCII_US) ); |
3270 | 0 | } |
3271 | 524 | OUString aURL = bUnparsedURI ? url : |
3272 | 524 | aTargetURL.GetMainURL( bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : |
3273 | 431 | INetURLObject::DecodeMechanism::NONE ); |
3274 | 524 | aWriter.writeLiteralEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURL, |
3275 | 0 | INetURLObject::EncodeMechanism::WasEncoded, |
3276 | 0 | bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE |
3277 | 0 | ) : |
3278 | 524 | aURL , rLink.m_nObject, osl_getThreadTextEncoding() ); |
3279 | 524 | } |
3280 | 524 | } |
3281 | 524 | aLine.append( ">>\n" ); |
3282 | 524 | } |
3283 | 3.34k | if (rLink.m_nStructParent != -1) |
3284 | 0 | { |
3285 | 0 | aLine.append( "/StructParent " ); |
3286 | 0 | aLine.append( rLink.m_nStructParent ); |
3287 | 0 | } |
3288 | 3.34k | aLine.append( ">>\nendobj\n\n" ); |
3289 | 3.34k | if (!writeBuffer(aLine)) |
3290 | 0 | return false; |
3291 | 3.34k | } |
3292 | | |
3293 | 8.85k | return true; |
3294 | 8.85k | } |
3295 | | |
3296 | | namespace |
3297 | | { |
3298 | | |
3299 | | void appendAnnotationRect(tools::Rectangle const & rRectangle, OStringBuffer & aLine) |
3300 | 388 | { |
3301 | 388 | aLine.append("/Rect ["); |
3302 | 388 | appendFixedInt(rRectangle.Left(), aLine); |
3303 | 388 | aLine.append(' '); |
3304 | 388 | appendFixedInt(rRectangle.Top(), aLine); |
3305 | 388 | aLine.append(' '); |
3306 | 388 | appendFixedInt(rRectangle.Right(), aLine); |
3307 | 388 | aLine.append(' '); |
3308 | 388 | appendFixedInt(rRectangle.Bottom(), aLine); |
3309 | 388 | aLine.append("] "); |
3310 | 388 | } |
3311 | | |
3312 | | void appendAnnotationColor(Color const& rColor, OStringBuffer & aLine) |
3313 | 0 | { |
3314 | 0 | aLine.append("/C ["); |
3315 | 0 | appendColor(rColor, aLine, false); |
3316 | 0 | aLine.append("] "); |
3317 | 0 | } |
3318 | | |
3319 | | void appendAnnotationInteriorColor(Color const& rColor, OStringBuffer & aLine) |
3320 | 0 | { |
3321 | 0 | aLine.append("/IC ["); |
3322 | 0 | appendColor(rColor, aLine, false); |
3323 | 0 | aLine.append("] "); |
3324 | 0 | } |
3325 | | |
3326 | | void appendPolygon(basegfx::B2DPolygon const& rPolygon, double fPageHeight, OStringBuffer & aLine) |
3327 | 0 | { |
3328 | 0 | for (sal_uInt32 i = 0; i < rPolygon.count(); ++i) |
3329 | 0 | { |
3330 | 0 | appendDouble(convertMm100ToPoint(rPolygon.getB2DPoint(i).getX()), aLine, nLog10Divisor); |
3331 | 0 | aLine.append(" "); |
3332 | 0 | appendDouble(fPageHeight - convertMm100ToPoint(rPolygon.getB2DPoint(i).getY()), aLine, nLog10Divisor); |
3333 | 0 | aLine.append(" "); |
3334 | 0 | } |
3335 | 0 | } |
3336 | | |
3337 | | void appendVertices(basegfx::B2DPolygon const& rPolygon, double fPageHeight, OStringBuffer & aLine) |
3338 | 0 | { |
3339 | 0 | aLine.append("/Vertices ["); |
3340 | 0 | appendPolygon(rPolygon, fPageHeight, aLine); |
3341 | 0 | aLine.append("] "); |
3342 | |
|
3343 | 0 | } |
3344 | | |
3345 | | void appendAnnotationBorder(float fBorderWidth, OStringBuffer & aLine) |
3346 | 0 | { |
3347 | 0 | aLine.append("/Border [0 0 "); |
3348 | 0 | appendDouble(convertMm100ToPoint(fBorderWidth), aLine, nLog10Divisor); |
3349 | 0 | aLine.append("] "); |
3350 | 0 | } |
3351 | | |
3352 | | } // end anonymous namespace |
3353 | | |
3354 | | void PDFWriterImpl::emitTextAnnotationLine(OStringBuffer & aLine, PDFNoteEntry const & rNote) |
3355 | 388 | { |
3356 | 388 | COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor); |
3357 | | |
3358 | 388 | appendObjectID(rNote.m_nObject, aLine); |
3359 | | |
3360 | 388 | double fPageHeight = m_aPages[rNote.m_nPage].getHeight(); |
3361 | | |
3362 | 388 | aLine.append("<</Type /Annot "); |
3363 | | |
3364 | 388 | appendAnnotationRect(rNote.m_aRect, aLine); |
3365 | | |
3366 | 388 | aLine.append("/Subtype "); |
3367 | | |
3368 | 388 | if (rNote.m_aContents.meType == PDFAnnotationSubType::Polygon || |
3369 | 388 | rNote.m_aContents.meType == PDFAnnotationSubType::Polyline) |
3370 | 0 | { |
3371 | 0 | auto const& rPolygon = rNote.m_aContents.maPolygons[0]; |
3372 | 0 | if (rNote.m_aContents.meType == PDFAnnotationSubType::Polygon) |
3373 | 0 | aLine.append("/Polygon "); |
3374 | 0 | else |
3375 | 0 | aLine.append("/Polyline "); |
3376 | |
|
3377 | 0 | appendVertices(rPolygon, fPageHeight, aLine); |
3378 | 0 | appendAnnotationColor(rNote.m_aContents.maAnnotationColor, aLine); |
3379 | |
|
3380 | 0 | if (rNote.m_aContents.meType == PDFAnnotationSubType::Polygon) |
3381 | 0 | appendAnnotationInteriorColor(rNote.m_aContents.maInteriorColor, aLine); |
3382 | 0 | appendAnnotationBorder(rNote.m_aContents.mfWidth, aLine); |
3383 | 0 | } |
3384 | 388 | else if (rNote.m_aContents.meType == PDFAnnotationSubType::Square) |
3385 | 0 | { |
3386 | 0 | aLine.append("/Square "); |
3387 | 0 | appendAnnotationColor(rNote.m_aContents.maAnnotationColor, aLine); |
3388 | 0 | appendAnnotationInteriorColor(rNote.m_aContents.maInteriorColor, aLine); |
3389 | 0 | appendAnnotationBorder(rNote.m_aContents.mfWidth, aLine); |
3390 | 0 | } |
3391 | 388 | else if (rNote.m_aContents.meType == PDFAnnotationSubType::Circle) |
3392 | 0 | { |
3393 | 0 | aLine.append("/Circle "); |
3394 | 0 | appendAnnotationColor(rNote.m_aContents.maAnnotationColor, aLine); |
3395 | 0 | appendAnnotationInteriorColor(rNote.m_aContents.maInteriorColor, aLine); |
3396 | 0 | appendAnnotationBorder(rNote.m_aContents.mfWidth, aLine); |
3397 | 0 | } |
3398 | 388 | else if (rNote.m_aContents.meType == PDFAnnotationSubType::Ink) |
3399 | 0 | { |
3400 | 0 | aLine.append("/Ink "); |
3401 | |
|
3402 | 0 | aLine.append("/InkList ["); |
3403 | 0 | for (auto const& rPolygon : rNote.m_aContents.maPolygons) |
3404 | 0 | { |
3405 | 0 | aLine.append("["); |
3406 | 0 | appendPolygon(rPolygon, fPageHeight, aLine); |
3407 | 0 | aLine.append("]"); |
3408 | 0 | } |
3409 | 0 | aLine.append("] "); |
3410 | |
|
3411 | 0 | appendAnnotationColor(rNote.m_aContents.maAnnotationColor, aLine); |
3412 | 0 | appendAnnotationBorder(rNote.m_aContents.mfWidth, aLine); |
3413 | |
|
3414 | 0 | } |
3415 | 388 | else if (rNote.m_aContents.meType == PDFAnnotationSubType::FreeText) |
3416 | 0 | { |
3417 | 0 | aLine.append("/FreeText "); |
3418 | 0 | } |
3419 | 388 | else |
3420 | 388 | { |
3421 | 388 | aLine.append("/Text "); |
3422 | 388 | } |
3423 | | |
3424 | | // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should' |
3425 | | // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3 |
3426 | 388 | if (m_nPDFA_Version > 0) |
3427 | 0 | aLine.append("/F 4 "); |
3428 | | |
3429 | 388 | aLine.append("/Popup "); |
3430 | 388 | appendObjectReference(rNote.m_aPopUpAnnotation.m_nObject, aLine); |
3431 | | |
3432 | 388 | auto & rDateTime = rNote.m_aContents.maModificationDate; |
3433 | | |
3434 | 388 | aLine.append("/M ("); |
3435 | 388 | appendPdfTimeDate(aLine, rDateTime.Year, rDateTime.Month, rDateTime.Day, rDateTime.Hours, rDateTime.Minutes, rDateTime.Seconds, 0); |
3436 | 388 | aLine.append(") "); |
3437 | | |
3438 | | // contents of the note (type text string) |
3439 | 388 | aLine.append("/Contents "); |
3440 | 388 | aWriter.writeUnicodeEncrypt(rNote.m_aContents.maContents, rNote.m_nObject); |
3441 | 388 | aLine.append("\n"); |
3442 | | |
3443 | | // optional title |
3444 | 388 | if (!rNote.m_aContents.maTitle.isEmpty()) |
3445 | 388 | { |
3446 | 388 | aLine.append("/T "); |
3447 | 388 | aWriter.writeUnicodeEncrypt(rNote.m_aContents.maTitle, rNote.m_nObject); |
3448 | 388 | aLine.append("\n"); |
3449 | 388 | } |
3450 | | |
3451 | 388 | if (-1 != rNote.m_nStructParent) |
3452 | 0 | { |
3453 | 0 | aLine.append("/StructParent "); |
3454 | 0 | aLine.append(rNote.m_nStructParent); |
3455 | 0 | aLine.append("\n"); |
3456 | 0 | } |
3457 | | |
3458 | 388 | aLine.append(">>\n"); |
3459 | 388 | aLine.append("endobj\n\n"); |
3460 | 388 | } |
3461 | | |
3462 | | void PDFWriterImpl::emitPopupAnnotationLine(OStringBuffer & aLine, PDFPopupAnnotation const & rPopUp) |
3463 | 388 | { |
3464 | 388 | appendObjectID(rPopUp.m_nObject, aLine); |
3465 | 388 | aLine.append("<</Type /Annot /Subtype /Popup "); |
3466 | 388 | aLine.append("/Rect["); |
3467 | 388 | appendFixedInt(rPopUp.m_aRect.Left(), aLine); |
3468 | 388 | aLine.append(' '); |
3469 | 388 | appendFixedInt(rPopUp.m_aRect.Top(), aLine); |
3470 | 388 | aLine.append(' '); |
3471 | 388 | appendFixedInt(rPopUp.m_aRect.Right(), aLine); |
3472 | 388 | aLine.append(' '); |
3473 | 388 | appendFixedInt(rPopUp.m_aRect.Bottom(), aLine); |
3474 | 388 | aLine.append("]"); |
3475 | 388 | aLine.append("/Parent "); |
3476 | 388 | appendObjectReference(rPopUp.m_nParentObject, aLine); |
3477 | 388 | aLine.append(">>\n"); |
3478 | 388 | aLine.append("endobj\n\n"); |
3479 | 388 | } |
3480 | | |
3481 | | bool PDFWriterImpl::emitNoteAnnotations() |
3482 | 8.85k | { |
3483 | | // emit note annotations |
3484 | 8.85k | int nAnnots = m_aNotes.size(); |
3485 | 9.24k | for( int i = 0; i < nAnnots; i++ ) |
3486 | 388 | { |
3487 | 388 | const PDFNoteEntry& rNote = m_aNotes[i]; |
3488 | 388 | const PDFPopupAnnotation& rPopUp = rNote.m_aPopUpAnnotation; |
3489 | | |
3490 | 388 | { |
3491 | 388 | if (!updateObject(rNote.m_nObject)) |
3492 | 0 | return false; |
3493 | | |
3494 | 388 | OStringBuffer aLine(PDFWRITER_IMPL_BUFFERSIZE); |
3495 | | |
3496 | 388 | emitTextAnnotationLine(aLine, rNote); |
3497 | | |
3498 | 388 | if (!writeBuffer(aLine)) |
3499 | 0 | return false; |
3500 | 388 | } |
3501 | | |
3502 | 388 | { |
3503 | | |
3504 | 388 | if (!updateObject(rPopUp.m_nObject)) |
3505 | 0 | return false; |
3506 | | |
3507 | 388 | OStringBuffer aLine(PDFWRITER_IMPL_BUFFERSIZE); |
3508 | | |
3509 | 388 | emitPopupAnnotationLine(aLine, rPopUp); |
3510 | | |
3511 | 388 | if (!writeBuffer(aLine)) |
3512 | 0 | return false; |
3513 | 388 | } |
3514 | 388 | } |
3515 | 8.85k | return true; |
3516 | 8.85k | } |
3517 | | |
3518 | | Font PDFWriterImpl::replaceFont( const vcl::Font& rControlFont, const vcl::Font& rAppSetFont ) |
3519 | 0 | { |
3520 | 0 | bool bAdjustSize = false; |
3521 | |
|
3522 | 0 | Font aFont( rControlFont ); |
3523 | 0 | if( aFont.GetFamilyName().isEmpty() ) |
3524 | 0 | { |
3525 | 0 | aFont = rAppSetFont; |
3526 | 0 | if( rControlFont.GetFontHeight() ) |
3527 | 0 | aFont.SetFontSize( Size( 0, rControlFont.GetFontHeight() ) ); |
3528 | 0 | else |
3529 | 0 | bAdjustSize = true; |
3530 | 0 | if( rControlFont.GetItalic() != ITALIC_DONTKNOW ) |
3531 | 0 | aFont.SetItalic( rControlFont.GetItalic() ); |
3532 | 0 | if( rControlFont.GetWeight() != WEIGHT_DONTKNOW ) |
3533 | 0 | aFont.SetWeight( rControlFont.GetWeight() ); |
3534 | 0 | } |
3535 | 0 | else if( ! aFont.GetFontHeight() ) |
3536 | 0 | { |
3537 | 0 | aFont.SetFontSize( rAppSetFont.GetFontSize() ); |
3538 | 0 | bAdjustSize = true; |
3539 | 0 | } |
3540 | 0 | if( bAdjustSize ) |
3541 | 0 | { |
3542 | 0 | Size aFontSize = aFont.GetFontSize(); |
3543 | 0 | OutputDevice* pDefDev = Application::GetDefaultDevice(); |
3544 | 0 | aFontSize = OutputDevice::LogicToLogic( aFontSize, pDefDev->GetMapMode(), getMapMode() ); |
3545 | 0 | aFont.SetFontSize( aFontSize ); |
3546 | 0 | } |
3547 | 0 | return aFont; |
3548 | 0 | } |
3549 | | |
3550 | | sal_Int32 PDFWriterImpl::getBestBuildinFont( const vcl::Font& rFont ) |
3551 | 0 | { |
3552 | 0 | sal_Int32 nBest = 4; // default to Helvetica |
3553 | |
|
3554 | 0 | if (rFont.GetFamilyType() == FAMILY_ROMAN) |
3555 | 0 | { |
3556 | | // Serif: default to Times-Roman. |
3557 | 0 | nBest = 8; |
3558 | 0 | } |
3559 | |
|
3560 | 0 | OUString aFontName( rFont.GetFamilyName() ); |
3561 | 0 | aFontName = aFontName.toAsciiLowerCase(); |
3562 | |
|
3563 | 0 | if( aFontName.indexOf( "times" ) != -1 ) |
3564 | 0 | nBest = 8; |
3565 | 0 | else if( aFontName.indexOf( "courier" ) != -1 ) |
3566 | 0 | nBest = 0; |
3567 | 0 | else if( aFontName.indexOf( "dingbats" ) != -1 ) |
3568 | 0 | nBest = 13; |
3569 | 0 | else if( aFontName.indexOf( "symbol" ) != -1 ) |
3570 | 0 | nBest = 12; |
3571 | 0 | if( nBest < 12 ) |
3572 | 0 | { |
3573 | 0 | if( rFont.GetItalic() == ITALIC_OBLIQUE || rFont.GetItalic() == ITALIC_NORMAL ) |
3574 | 0 | nBest += 1; |
3575 | 0 | if( rFont.GetWeight() > WEIGHT_MEDIUM ) |
3576 | 0 | nBest += 2; |
3577 | 0 | } |
3578 | |
|
3579 | 0 | if( m_aBuildinFontToObjectMap.find( nBest ) == m_aBuildinFontToObjectMap.end() ) |
3580 | 0 | m_aBuildinFontToObjectMap[ nBest ] = createObject(); |
3581 | |
|
3582 | 0 | return nBest; |
3583 | 0 | } |
3584 | | |
3585 | | static const Color& replaceColor( const Color& rCol1, const Color& rCol2 ) |
3586 | 0 | { |
3587 | 0 | return (rCol1 == COL_TRANSPARENT) ? rCol2 : rCol1; |
3588 | 0 | } |
3589 | | |
3590 | | void PDFWriterImpl::createDefaultPushButtonAppearance( PDFWidget& rButton, const PDFWriter::PushButtonWidget& rWidget ) |
3591 | 0 | { |
3592 | 0 | const StyleSettings& rSettings = m_aWidgetStyleSettings; |
3593 | | |
3594 | | // save graphics state |
3595 | 0 | push( PushFlags::ALL ); |
3596 | | |
3597 | | // transform relative to control's coordinates since an |
3598 | | // appearance stream is a form XObject |
3599 | | // this relies on the m_aRect member of rButton NOT already being transformed |
3600 | | // to default user space |
3601 | 0 | if( rWidget.Background || rWidget.Border ) |
3602 | 0 | { |
3603 | 0 | setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetLightColor() ) : COL_TRANSPARENT ); |
3604 | 0 | setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetDialogColor() ) : COL_TRANSPARENT ); |
3605 | 0 | drawRectangle( rWidget.Location ); |
3606 | 0 | } |
3607 | | // prepare font to use |
3608 | 0 | Font aFont = replaceFont( rWidget.TextFont, rSettings.GetPushButtonFont() ); |
3609 | 0 | setFont( aFont ); |
3610 | 0 | setTextColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ) ); |
3611 | |
|
3612 | 0 | drawText( rButton.m_aRect, rButton.m_aText, rButton.m_nTextStyle ); |
3613 | | |
3614 | | // create DA string while local mapmode is still in place |
3615 | | // (that is before endRedirect()) |
3616 | 0 | OStringBuffer aDA( 256 ); |
3617 | 0 | appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ), aDA ); |
3618 | 0 | Font aDummyFont( u"Helvetica"_ustr, aFont.GetFontSize() ); |
3619 | 0 | sal_Int32 nDummyBuildin = getBestBuildinFont( aDummyFont ); |
3620 | 0 | aDA.append( ' ' ); |
3621 | 0 | aDA.append(pdf::BuildinFontFace::Get(nDummyBuildin).getNameObject()); |
3622 | 0 | aDA.append( ' ' ); |
3623 | 0 | m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA ); |
3624 | 0 | aDA.append( " Tf" ); |
3625 | 0 | rButton.m_aDAString = aDA.makeStringAndClear(); |
3626 | |
|
3627 | 0 | pop(); |
3628 | |
|
3629 | 0 | rButton.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = new SvMemoryStream(); |
3630 | | |
3631 | | /* seems like a bad hack but at least works in both AR5 and 6: |
3632 | | we draw the button ourselves and tell AR |
3633 | | the button would be totally transparent with no text |
3634 | | |
3635 | | One would expect that simply setting a normal appearance |
3636 | | should suffice, but no, as soon as the user actually presses |
3637 | | the button and an action is tied to it (gasp! a button that |
3638 | | does something) the appearance gets replaced by some crap that AR |
3639 | | creates on the fly even if no DA or MK is given. On AR6 at least |
3640 | | the DA and MK work as expected, but on AR5 this creates a region |
3641 | | filled with the background color but nor text. Urgh. |
3642 | | */ |
3643 | 0 | rButton.m_aMKDict = "/BC [] /BG [] /CA"_ostr; |
3644 | 0 | rButton.m_aMKDictCAString = ""_ostr; |
3645 | 0 | } |
3646 | | |
3647 | | Font PDFWriterImpl::drawFieldBorder( PDFWidget& rIntern, |
3648 | | const PDFWriter::AnyWidget& rWidget, |
3649 | | const StyleSettings& rSettings ) |
3650 | 0 | { |
3651 | 0 | Font aFont = replaceFont( rWidget.TextFont, rSettings.GetFieldFont() ); |
3652 | |
|
3653 | 0 | if( rWidget.Background || rWidget.Border ) |
3654 | 0 | { |
3655 | 0 | if( rWidget.Border && rWidget.BorderColor == COL_TRANSPARENT ) |
3656 | 0 | { |
3657 | 0 | sal_Int32 nDelta = GetDPIX() / 500; |
3658 | 0 | if( nDelta < 1 ) |
3659 | 0 | nDelta = 1; |
3660 | 0 | setLineColor( COL_TRANSPARENT ); |
3661 | 0 | tools::Rectangle aRect = rIntern.m_aRect; |
3662 | 0 | setFillColor( rSettings.GetLightBorderColor() ); |
3663 | 0 | drawRectangle( aRect ); |
3664 | 0 | aRect.AdjustLeft(nDelta ); aRect.AdjustTop(nDelta ); |
3665 | 0 | aRect.AdjustRight( -nDelta ); aRect.AdjustBottom( -nDelta ); |
3666 | 0 | setFillColor( rSettings.GetFieldColor() ); |
3667 | 0 | drawRectangle( aRect ); |
3668 | 0 | setFillColor( rSettings.GetLightColor() ); |
3669 | 0 | drawRectangle( tools::Rectangle( Point( aRect.Left(), aRect.Bottom()-nDelta ), aRect.BottomRight() ) ); |
3670 | 0 | drawRectangle( tools::Rectangle( Point( aRect.Right()-nDelta, aRect.Top() ), aRect.BottomRight() ) ); |
3671 | 0 | setFillColor( rSettings.GetDarkShadowColor() ); |
3672 | 0 | drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Left()+nDelta, aRect.Bottom() ) ) ); |
3673 | 0 | drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Right(), aRect.Top()+nDelta ) ) ); |
3674 | 0 | } |
3675 | 0 | else |
3676 | 0 | { |
3677 | 0 | setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetShadowColor() ) : COL_TRANSPARENT ); |
3678 | 0 | setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT ); |
3679 | 0 | drawRectangle( rIntern.m_aRect ); |
3680 | 0 | } |
3681 | |
|
3682 | 0 | if( rWidget.Border ) |
3683 | 0 | { |
3684 | | // adjust edit area accounting for border |
3685 | 0 | sal_Int32 nDelta = aFont.GetFontHeight()/4; |
3686 | 0 | if( nDelta < 1 ) |
3687 | 0 | nDelta = 1; |
3688 | 0 | rIntern.m_aRect.AdjustLeft(nDelta ); |
3689 | 0 | rIntern.m_aRect.AdjustTop(nDelta ); |
3690 | 0 | rIntern.m_aRect.AdjustRight( -nDelta ); |
3691 | 0 | rIntern.m_aRect.AdjustBottom( -nDelta ); |
3692 | 0 | } |
3693 | 0 | } |
3694 | 0 | return aFont; |
3695 | 0 | } |
3696 | | |
3697 | | void PDFWriterImpl::createDefaultEditAppearance( PDFWidget& rEdit, const PDFWriter::EditWidget& rWidget ) |
3698 | 0 | { |
3699 | 0 | const StyleSettings& rSettings = m_aWidgetStyleSettings; |
3700 | 0 | SvMemoryStream* pEditStream = new SvMemoryStream( PDFWRITER_IMPL_BUFFERSIZE, PDFWRITER_IMPL_BUFFERSIZE ); |
3701 | |
|
3702 | 0 | push( PushFlags::ALL ); |
3703 | | |
3704 | | // prepare font to use, draw field border |
3705 | 0 | Font aFont = drawFieldBorder( rEdit, rWidget, rSettings ); |
3706 | | // Get the built-in font which is closest to aFont. |
3707 | 0 | sal_Int32 nBest = getBestBuildinFont(aFont); |
3708 | | |
3709 | | // prepare DA string |
3710 | 0 | OStringBuffer aDA( 32 ); |
3711 | 0 | appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA ); |
3712 | 0 | aDA.append( ' ' ); |
3713 | 0 | aDA.append(pdf::BuildinFontFace::Get(nBest).getNameObject()); |
3714 | |
|
3715 | 0 | OStringBuffer aDR( 32 ); |
3716 | 0 | aDR.append( "/Font " ); |
3717 | 0 | aDR.append( getFontDictObject() ); |
3718 | 0 | aDR.append( " 0 R" ); |
3719 | 0 | rEdit.m_aDRDict = aDR.makeStringAndClear(); |
3720 | 0 | aDA.append( ' ' ); |
3721 | 0 | m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA ); |
3722 | 0 | aDA.append( " Tf" ); |
3723 | | |
3724 | | /* create an empty appearance stream, let the viewer create |
3725 | | the appearance at runtime. This is because AR5 seems to |
3726 | | paint the widget appearance always, and a dynamically created |
3727 | | appearance on top of it. AR6 is well behaved in that regard, so |
3728 | | that behaviour seems to be a bug. Anyway this empty appearance |
3729 | | relies on /NeedAppearances in the AcroForm dictionary set to "true" |
3730 | | */ |
3731 | 0 | beginRedirect( pEditStream, rEdit.m_aRect ); |
3732 | 0 | writeBuffer( "/Tx BMC\nEMC\n" ); |
3733 | |
|
3734 | 0 | endRedirect(); |
3735 | 0 | pop(); |
3736 | |
|
3737 | 0 | rEdit.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = pEditStream; |
3738 | |
|
3739 | 0 | rEdit.m_aDAString = aDA.makeStringAndClear(); |
3740 | 0 | } |
3741 | | |
3742 | | void PDFWriterImpl::createDefaultListBoxAppearance( PDFWidget& rBox, const PDFWriter::ListBoxWidget& rWidget ) |
3743 | 0 | { |
3744 | 0 | const StyleSettings& rSettings = m_aWidgetStyleSettings; |
3745 | 0 | SvMemoryStream* pListBoxStream = new SvMemoryStream( PDFWRITER_IMPL_BUFFERSIZE, PDFWRITER_IMPL_BUFFERSIZE ); |
3746 | |
|
3747 | 0 | push( PushFlags::ALL ); |
3748 | | |
3749 | | // prepare font to use, draw field border |
3750 | 0 | Font aFont = drawFieldBorder( rBox, rWidget, rSettings ); |
3751 | 0 | sal_Int32 nBest = getSystemFont( aFont ); |
3752 | |
|
3753 | 0 | setLineColor( COL_TRANSPARENT ); |
3754 | 0 | setFillColor( replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) ); |
3755 | 0 | drawRectangle( rBox.m_aRect ); |
3756 | |
|
3757 | 0 | pop(); |
3758 | | |
3759 | | // prepare DA string |
3760 | 0 | OStringBuffer aDA( 256 ); |
3761 | | // prepare DA string |
3762 | 0 | appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA ); |
3763 | 0 | aDA.append( ' ' ); |
3764 | 0 | aDA.append( "/F" ); |
3765 | 0 | aDA.append( nBest ); |
3766 | |
|
3767 | 0 | OStringBuffer aDR( 32 ); |
3768 | 0 | aDR.append( "/Font " ); |
3769 | 0 | aDR.append( getFontDictObject() ); |
3770 | 0 | aDR.append( " 0 R" ); |
3771 | 0 | rBox.m_aDRDict = aDR.makeStringAndClear(); |
3772 | 0 | aDA.append( ' ' ); |
3773 | 0 | m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA ); |
3774 | 0 | aDA.append( " Tf" ); |
3775 | |
|
3776 | 0 | beginRedirect(pListBoxStream, rBox.m_aRect); |
3777 | | // empty appearance, see createDefaultEditAppearance for reference |
3778 | 0 | writeBuffer("/Tx BMC\nEMC\n"); |
3779 | 0 | endRedirect(); |
3780 | |
|
3781 | 0 | rBox.m_aAppearances["N"_ostr]["Standard"_ostr] = pListBoxStream; |
3782 | 0 | rBox.m_aDAString = aDA.makeStringAndClear(); |
3783 | 0 | } |
3784 | | |
3785 | | void PDFWriterImpl::createDefaultCheckBoxAppearance( PDFWidget& rBox, const PDFWriter::CheckBoxWidget& rWidget ) |
3786 | 0 | { |
3787 | 0 | const StyleSettings& rSettings = m_aWidgetStyleSettings; |
3788 | | |
3789 | | // save graphics state |
3790 | 0 | push( PushFlags::ALL ); |
3791 | |
|
3792 | 0 | if( rWidget.Background || rWidget.Border ) |
3793 | 0 | { |
3794 | 0 | setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT ); |
3795 | 0 | setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT ); |
3796 | 0 | drawRectangle( rBox.m_aRect ); |
3797 | 0 | } |
3798 | |
|
3799 | 0 | Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() ); |
3800 | 0 | setFont( aFont ); |
3801 | 0 | Size aFontSize = aFont.GetFontSize(); |
3802 | 0 | if( aFontSize.Height() > rBox.m_aRect.GetHeight() ) |
3803 | 0 | aFontSize.setHeight( rBox.m_aRect.GetHeight() ); |
3804 | 0 | sal_Int32 nDelta = aFontSize.Height()/10; |
3805 | 0 | if( nDelta < 1 ) |
3806 | 0 | nDelta = 1; |
3807 | |
|
3808 | 0 | tools::Rectangle aCheckRect, aTextRect; |
3809 | 0 | { |
3810 | 0 | aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta ); |
3811 | 0 | aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 ); |
3812 | 0 | aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() ); |
3813 | 0 | aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() ); |
3814 | | |
3815 | | // #i74206# handle small controls without text area |
3816 | 0 | while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta ) |
3817 | 0 | { |
3818 | 0 | aCheckRect.AdjustRight( -nDelta ); |
3819 | 0 | aCheckRect.AdjustTop(nDelta/2 ); |
3820 | 0 | aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) ); |
3821 | 0 | } |
3822 | |
|
3823 | 0 | aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta ); |
3824 | 0 | aTextRect.SetTop( rBox.m_aRect.Top() ); |
3825 | 0 | aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta ); |
3826 | 0 | aTextRect.SetBottom( rBox.m_aRect.Bottom() ); |
3827 | 0 | } |
3828 | 0 | setLineColor( COL_BLACK ); |
3829 | 0 | setFillColor( COL_TRANSPARENT ); |
3830 | 0 | OStringBuffer aLW( 32 ); |
3831 | 0 | aLW.append( "q " ); |
3832 | 0 | m_aPages[m_nCurrentPage].appendMappedLength( nDelta, aLW ); |
3833 | 0 | aLW.append( " w " ); |
3834 | 0 | writeBuffer( aLW ); |
3835 | 0 | drawRectangle( aCheckRect ); |
3836 | 0 | writeBuffer( " Q\n" ); |
3837 | 0 | setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) ); |
3838 | 0 | drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle ); |
3839 | |
|
3840 | 0 | pop(); |
3841 | |
|
3842 | 0 | OStringBuffer aDA( 256 ); |
3843 | | |
3844 | | // tdf#93853 don't rely on Zapf (or any other 'standard' font) |
3845 | | // being present, but our own OpenSymbol - N.B. PDF/A for good |
3846 | | // reasons require even the standard PS fonts to be embedded! |
3847 | 0 | Push(); |
3848 | 0 | SetFont( Font( u"OpenSymbol"_ustr, aFont.GetFontSize() ) ); |
3849 | 0 | const LogicalFontInstance* pFontInstance = GetFontInstance(); |
3850 | 0 | const vcl::font::PhysicalFontFace* pFace = pFontInstance->GetFontFace(); |
3851 | 0 | Pop(); |
3852 | | |
3853 | | // make sure OpenSymbol is embedded, and includes our checkmark |
3854 | 0 | const sal_Unicode cMark=0x2713; |
3855 | 0 | const auto nGlyphId = pFontInstance->GetGlyphIndex(cMark); |
3856 | 0 | const auto nGlyphWidth = pFontInstance->GetGlyphWidth(nGlyphId, false, false); |
3857 | |
|
3858 | 0 | sal_uInt8 nMappedGlyph; |
3859 | 0 | sal_Int32 nMappedFontObject; |
3860 | 0 | registerGlyph(nGlyphId, pFace, pFontInstance, { cMark }, nGlyphWidth, nMappedGlyph, nMappedFontObject); |
3861 | |
|
3862 | 0 | appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA ); |
3863 | 0 | aDA.append( ' ' ); |
3864 | 0 | aDA.append( "/F" ); |
3865 | 0 | aDA.append( nMappedFontObject ); |
3866 | 0 | aDA.append( " 0 Tf" ); |
3867 | |
|
3868 | 0 | OStringBuffer aDR( 32 ); |
3869 | 0 | aDR.append( "/Font " ); |
3870 | 0 | aDR.append( getFontDictObject() ); |
3871 | 0 | aDR.append( " 0 R" ); |
3872 | 0 | rBox.m_aDRDict = aDR.makeStringAndClear(); |
3873 | 0 | rBox.m_aDAString = aDA.makeStringAndClear(); |
3874 | 0 | rBox.m_aMKDict = "/CA"_ostr; |
3875 | 0 | rBox.m_aMKDictCAString = "8"_ostr; |
3876 | 0 | rBox.m_aRect = aCheckRect; |
3877 | | |
3878 | | // create appearance streams |
3879 | 0 | sal_Int32 nCharXOffset = 1000 - 787; // metrics from OpenSymbol |
3880 | 0 | nCharXOffset *= aCheckRect.GetHeight(); |
3881 | 0 | nCharXOffset /= 2000; |
3882 | 0 | sal_Int32 nCharYOffset = 1000 - (820-143); // metrics from Zapf |
3883 | 0 | nCharYOffset *= aCheckRect.GetHeight(); |
3884 | 0 | nCharYOffset /= 2000; |
3885 | | |
3886 | | // write 'checked' appearance stream |
3887 | 0 | SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 ); |
3888 | 0 | beginRedirect( pCheckStream, aCheckRect ); |
3889 | 0 | aDA.append( "/Tx BMC\nq BT\n" ); |
3890 | 0 | appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA ); |
3891 | 0 | aDA.append( ' ' ); |
3892 | 0 | aDA.append( "/F" ); |
3893 | 0 | aDA.append( nMappedFontObject ); |
3894 | 0 | aDA.append( ' ' ); |
3895 | 0 | m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA ); |
3896 | 0 | aDA.append( " Tf\n" ); |
3897 | 0 | m_aPages[ m_nCurrentPage ].appendMappedLength( nCharXOffset, aDA ); |
3898 | 0 | aDA.append( " " ); |
3899 | 0 | m_aPages[ m_nCurrentPage ].appendMappedLength( nCharYOffset, aDA ); |
3900 | 0 | aDA.append( " Td <" ); |
3901 | 0 | COSWriter::appendHex( nMappedGlyph, aDA ); |
3902 | 0 | aDA.append( "> Tj\nET\nQ\nEMC\n" ); |
3903 | 0 | writeBuffer( aDA ); |
3904 | 0 | endRedirect(); |
3905 | 0 | rBox.m_aAppearances[ "N"_ostr ][ "Yes"_ostr ] = pCheckStream; |
3906 | | |
3907 | | // write 'unchecked' appearance stream |
3908 | 0 | SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 ); |
3909 | 0 | beginRedirect( pUncheckStream, aCheckRect ); |
3910 | 0 | writeBuffer( "/Tx BMC\nEMC\n" ); |
3911 | 0 | endRedirect(); |
3912 | 0 | rBox.m_aAppearances[ "N"_ostr ][ "Off"_ostr ] = pUncheckStream; |
3913 | 0 | } |
3914 | | |
3915 | | void PDFWriterImpl::createDefaultRadioButtonAppearance( PDFWidget& rBox, const PDFWriter::RadioButtonWidget& rWidget ) |
3916 | 0 | { |
3917 | 0 | const StyleSettings& rSettings = m_aWidgetStyleSettings; |
3918 | | |
3919 | | // save graphics state |
3920 | 0 | push( PushFlags::ALL ); |
3921 | |
|
3922 | 0 | if( rWidget.Background || rWidget.Border ) |
3923 | 0 | { |
3924 | 0 | setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT ); |
3925 | 0 | setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT ); |
3926 | 0 | drawRectangle( rBox.m_aRect ); |
3927 | 0 | } |
3928 | |
|
3929 | 0 | Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() ); |
3930 | 0 | setFont( aFont ); |
3931 | 0 | Size aFontSize = aFont.GetFontSize(); |
3932 | 0 | if( aFontSize.Height() > rBox.m_aRect.GetHeight() ) |
3933 | 0 | aFontSize.setHeight( rBox.m_aRect.GetHeight() ); |
3934 | 0 | sal_Int32 nDelta = aFontSize.Height()/10; |
3935 | 0 | if( nDelta < 1 ) |
3936 | 0 | nDelta = 1; |
3937 | |
|
3938 | 0 | tools::Rectangle aCheckRect, aTextRect; |
3939 | 0 | { |
3940 | 0 | aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta ); |
3941 | 0 | aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 ); |
3942 | 0 | aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() ); |
3943 | 0 | aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() ); |
3944 | | |
3945 | | // #i74206# handle small controls without text area |
3946 | 0 | while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta ) |
3947 | 0 | { |
3948 | 0 | aCheckRect.AdjustRight( -nDelta ); |
3949 | 0 | aCheckRect.AdjustTop(nDelta/2 ); |
3950 | 0 | aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) ); |
3951 | 0 | } |
3952 | |
|
3953 | 0 | aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta ); |
3954 | 0 | aTextRect.SetTop( rBox.m_aRect.Top() ); |
3955 | 0 | aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta ); |
3956 | 0 | aTextRect.SetBottom( rBox.m_aRect.Bottom() ); |
3957 | 0 | } |
3958 | 0 | setLineColor( COL_BLACK ); |
3959 | 0 | setFillColor( COL_TRANSPARENT ); |
3960 | 0 | OStringBuffer aLW( 32 ); |
3961 | 0 | aLW.append( "q " ); |
3962 | 0 | m_aPages[ m_nCurrentPage ].appendMappedLength( nDelta, aLW ); |
3963 | 0 | aLW.append( " w " ); |
3964 | 0 | writeBuffer( aLW ); |
3965 | 0 | drawEllipse( aCheckRect ); |
3966 | 0 | writeBuffer( " Q\n" ); |
3967 | 0 | setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) ); |
3968 | 0 | drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle ); |
3969 | |
|
3970 | 0 | pop(); |
3971 | | |
3972 | | //to encrypt this (el) |
3973 | 0 | rBox.m_aMKDict = "/CA"_ostr; |
3974 | | //after this assignment, to m_aMKDic cannot be added anything |
3975 | 0 | rBox.m_aMKDictCAString = "l"_ostr; |
3976 | |
|
3977 | 0 | rBox.m_aRect = aCheckRect; |
3978 | | |
3979 | | // create appearance streams |
3980 | 0 | push( PushFlags::ALL); |
3981 | 0 | SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 ); |
3982 | |
|
3983 | 0 | beginRedirect( pCheckStream, aCheckRect ); |
3984 | 0 | OStringBuffer aDA( 256 ); |
3985 | 0 | aDA.append( "/Tx BMC\nq BT\n" ); |
3986 | 0 | appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA ); |
3987 | 0 | aDA.append( ' ' ); |
3988 | 0 | m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA ); |
3989 | 0 | aDA.append( " 0 0 Td\nET\nQ\n" ); |
3990 | 0 | writeBuffer( aDA ); |
3991 | 0 | setFillColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) ); |
3992 | 0 | setLineColor( COL_TRANSPARENT ); |
3993 | 0 | aCheckRect.AdjustLeft(3*nDelta ); |
3994 | 0 | aCheckRect.AdjustTop(3*nDelta ); |
3995 | 0 | aCheckRect.AdjustBottom( -(3*nDelta) ); |
3996 | 0 | aCheckRect.AdjustRight( -(3*nDelta) ); |
3997 | 0 | drawEllipse( aCheckRect ); |
3998 | 0 | writeBuffer( "\nEMC\n" ); |
3999 | 0 | endRedirect(); |
4000 | |
|
4001 | 0 | pop(); |
4002 | 0 | rBox.m_aAppearances[ "N"_ostr ][ "Yes"_ostr ] = pCheckStream; |
4003 | |
|
4004 | 0 | SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 ); |
4005 | 0 | beginRedirect( pUncheckStream, aCheckRect ); |
4006 | 0 | writeBuffer( "/Tx BMC\nEMC\n" ); |
4007 | 0 | endRedirect(); |
4008 | 0 | rBox.m_aAppearances[ "N"_ostr ][ "Off"_ostr ] = pUncheckStream; |
4009 | 0 | } |
4010 | | |
4011 | | bool PDFWriterImpl::emitAppearances( PDFWidget& rWidget, OStringBuffer& rAnnotDict ) |
4012 | 0 | { |
4013 | | // TODO: check and insert default streams |
4014 | 0 | OString aStandardAppearance; |
4015 | 0 | switch( rWidget.m_eType ) |
4016 | 0 | { |
4017 | 0 | case PDFWriter::CheckBox: |
4018 | 0 | aStandardAppearance = OUStringToOString( rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US ); |
4019 | 0 | break; |
4020 | 0 | default: |
4021 | 0 | break; |
4022 | 0 | } |
4023 | | |
4024 | 0 | if( !rWidget.m_aAppearances.empty() ) |
4025 | 0 | { |
4026 | 0 | rAnnotDict.append( "/AP<<\n" ); |
4027 | 0 | for (auto & dict_item : rWidget.m_aAppearances) |
4028 | 0 | { |
4029 | 0 | rAnnotDict.append( "/" ); |
4030 | 0 | rAnnotDict.append( dict_item.first ); |
4031 | 0 | bool bUseSubDict = (dict_item.second.size() > 1); |
4032 | | |
4033 | | // PDF/A requires sub-dicts for /FT/Btn objects (clause |
4034 | | // 6.3.3) |
4035 | 0 | if (m_nPDFA_Version > 0) |
4036 | 0 | { |
4037 | 0 | if( rWidget.m_eType == PDFWriter::RadioButton || |
4038 | 0 | rWidget.m_eType == PDFWriter::CheckBox || |
4039 | 0 | rWidget.m_eType == PDFWriter::PushButton ) |
4040 | 0 | { |
4041 | 0 | bUseSubDict = true; |
4042 | 0 | } |
4043 | 0 | } |
4044 | |
|
4045 | 0 | rAnnotDict.append( bUseSubDict ? "<<" : " " ); |
4046 | |
|
4047 | 0 | for (auto const& stream_item : dict_item.second) |
4048 | 0 | { |
4049 | 0 | SvMemoryStream* pAppearanceStream = stream_item.second; |
4050 | 0 | dict_item.second[ stream_item.first ] = nullptr; |
4051 | |
|
4052 | 0 | bool bDeflate = compressStream( pAppearanceStream ); |
4053 | |
|
4054 | 0 | sal_Int64 nStreamLen = pAppearanceStream->TellEnd(); |
4055 | 0 | pAppearanceStream->Seek( STREAM_SEEK_TO_BEGIN ); |
4056 | 0 | sal_Int32 nObject = createObject(); |
4057 | 0 | if (!updateObject(nObject)) |
4058 | 0 | return false; |
4059 | 0 | if (g_bDebugDisableCompression) |
4060 | 0 | { |
4061 | 0 | emitComment( "PDFWriterImpl::emitAppearances" ); |
4062 | 0 | } |
4063 | 0 | OStringBuffer aLine; |
4064 | 0 | aLine.append( nObject ); |
4065 | |
|
4066 | 0 | aLine.append( " 0 obj\n" |
4067 | 0 | "<</Type/XObject\n" |
4068 | 0 | "/Subtype/Form\n" |
4069 | 0 | "/BBox[0 0 " ); |
4070 | 0 | appendFixedInt( rWidget.m_aRect.GetWidth()-1, aLine ); |
4071 | 0 | aLine.append( " " ); |
4072 | 0 | appendFixedInt( rWidget.m_aRect.GetHeight()-1, aLine ); |
4073 | 0 | aLine.append( "]\n" |
4074 | 0 | "/Resources " ); |
4075 | 0 | aLine.append( getResourceDictObj() ); |
4076 | 0 | aLine.append( " 0 R\n" |
4077 | 0 | "/Length " ); |
4078 | 0 | aLine.append(static_cast<sal_Int64>(calculateStreamSize(nStreamLen))); |
4079 | 0 | aLine.append( "\n" ); |
4080 | 0 | if( bDeflate ) |
4081 | 0 | aLine.append( "/Filter/FlateDecode\n" ); |
4082 | 0 | aLine.append( ">>\nstream\n" ); |
4083 | 0 | if (!writeBuffer(aLine)) return false; |
4084 | 0 | checkAndEnableStreamEncryption( nObject ); |
4085 | 0 | if (!writeBufferBytes( pAppearanceStream->GetData(), nStreamLen)) return false; |
4086 | 0 | disableStreamEncryption(); |
4087 | 0 | if (!writeBuffer("\nendstream\nendobj\n\n")) return false; |
4088 | | |
4089 | 0 | if( bUseSubDict ) |
4090 | 0 | { |
4091 | 0 | rAnnotDict.append( " /" ); |
4092 | 0 | rAnnotDict.append( stream_item.first ); |
4093 | 0 | rAnnotDict.append( " " ); |
4094 | 0 | } |
4095 | 0 | rAnnotDict.append( nObject ); |
4096 | 0 | rAnnotDict.append( " 0 R" ); |
4097 | |
|
4098 | 0 | delete pAppearanceStream; |
4099 | 0 | } |
4100 | | |
4101 | 0 | rAnnotDict.append( bUseSubDict ? ">>\n" : "\n" ); |
4102 | 0 | } |
4103 | 0 | rAnnotDict.append( ">>\n" ); |
4104 | 0 | if( !aStandardAppearance.isEmpty() ) |
4105 | 0 | { |
4106 | 0 | rAnnotDict.append( "/AS /" ); |
4107 | 0 | rAnnotDict.append( aStandardAppearance ); |
4108 | 0 | rAnnotDict.append( "\n" ); |
4109 | 0 | } |
4110 | 0 | } |
4111 | | |
4112 | 0 | return true; |
4113 | 0 | } |
4114 | | |
4115 | | bool PDFWriterImpl::emitWidgetAnnotations() |
4116 | 8.85k | { |
4117 | 8.85k | ensureUniqueRadioOnValues(); |
4118 | | |
4119 | 8.85k | int nAnnots = m_aWidgets.size(); |
4120 | 8.85k | for( int a = 0; a < nAnnots; a++ ) |
4121 | 0 | { |
4122 | 0 | PDFWidget& rWidget = m_aWidgets[a]; |
4123 | |
|
4124 | 0 | if( rWidget.m_eType == PDFWriter::CheckBox ) |
4125 | 0 | { |
4126 | 0 | if ( !rWidget.m_aOnValue.isEmpty() ) |
4127 | 0 | { |
4128 | 0 | auto app_it = rWidget.m_aAppearances.find( "N"_ostr ); |
4129 | 0 | if( app_it != rWidget.m_aAppearances.end() ) |
4130 | 0 | { |
4131 | 0 | auto stream_it = app_it->second.find( "Yes"_ostr ); |
4132 | 0 | if( stream_it != app_it->second.end() ) |
4133 | 0 | { |
4134 | 0 | SvMemoryStream* pStream = stream_it->second; |
4135 | 0 | app_it->second.erase( stream_it ); |
4136 | 0 | OStringBuffer aBuf( rWidget.m_aOnValue.getLength()*2 ); |
4137 | 0 | COSWriter::appendName( rWidget.m_aOnValue, aBuf ); |
4138 | 0 | (app_it->second)[ aBuf.makeStringAndClear() ] = pStream; |
4139 | 0 | } |
4140 | 0 | else |
4141 | 0 | SAL_INFO("vcl.pdfwriter", "error: CheckBox without \"Yes\" stream" ); |
4142 | 0 | } |
4143 | 0 | } |
4144 | | |
4145 | 0 | if ( !rWidget.m_aOffValue.isEmpty() ) |
4146 | 0 | { |
4147 | 0 | auto app_it = rWidget.m_aAppearances.find( "N"_ostr ); |
4148 | 0 | if( app_it != rWidget.m_aAppearances.end() ) |
4149 | 0 | { |
4150 | 0 | auto stream_it = app_it->second.find( "Off"_ostr ); |
4151 | 0 | if( stream_it != app_it->second.end() ) |
4152 | 0 | { |
4153 | 0 | SvMemoryStream* pStream = stream_it->second; |
4154 | 0 | app_it->second.erase( stream_it ); |
4155 | 0 | OStringBuffer aBuf( rWidget.m_aOffValue.getLength()*2 ); |
4156 | 0 | COSWriter::appendName( rWidget.m_aOffValue, aBuf ); |
4157 | 0 | (app_it->second)[ aBuf.makeStringAndClear() ] = pStream; |
4158 | 0 | } |
4159 | 0 | else |
4160 | 0 | SAL_INFO("vcl.pdfwriter", "error: CheckBox without \"Off\" stream" ); |
4161 | 0 | } |
4162 | 0 | } |
4163 | 0 | } |
4164 | | |
4165 | 0 | OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE ); |
4166 | 0 | COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor); |
4167 | 0 | OStringBuffer aValue( 256 ); |
4168 | 0 | COSWriter aValueWriter(aValue, m_aContext.Encryption.getParams(), m_pPDFEncryptor); |
4169 | 0 | aLine.append( rWidget.m_nObject ); |
4170 | 0 | aLine.append( " 0 obj\n" |
4171 | 0 | "<<" ); |
4172 | 0 | if( rWidget.m_eType != PDFWriter::Hierarchy ) |
4173 | 0 | { |
4174 | | // emit widget annotation only for terminal fields |
4175 | 0 | if( rWidget.m_aKids.empty() ) |
4176 | 0 | { |
4177 | 0 | int iRectMargin; |
4178 | |
|
4179 | 0 | aLine.append( "/Type/Annot/Subtype/Widget/F " ); |
4180 | |
|
4181 | 0 | if (rWidget.m_eType == PDFWriter::Signature) |
4182 | 0 | { |
4183 | 0 | aLine.append( "132\n" ); // Print & Locked |
4184 | 0 | iRectMargin = 0; |
4185 | 0 | } |
4186 | 0 | else |
4187 | 0 | { |
4188 | 0 | aLine.append( "4\n" ); |
4189 | 0 | iRectMargin = 1; |
4190 | 0 | } |
4191 | |
|
4192 | 0 | if (-1 != rWidget.m_nStructParent) |
4193 | 0 | { |
4194 | 0 | aLine.append("/StructParent "); |
4195 | 0 | aLine.append(rWidget.m_nStructParent); |
4196 | 0 | aLine.append("\n"); |
4197 | 0 | } |
4198 | |
|
4199 | 0 | aLine.append("/Rect[" ); |
4200 | 0 | appendFixedInt( rWidget.m_aRect.Left()-iRectMargin, aLine ); |
4201 | 0 | aLine.append( ' ' ); |
4202 | 0 | appendFixedInt( rWidget.m_aRect.Top()+iRectMargin, aLine ); |
4203 | 0 | aLine.append( ' ' ); |
4204 | 0 | appendFixedInt( rWidget.m_aRect.Right()+iRectMargin, aLine ); |
4205 | 0 | aLine.append( ' ' ); |
4206 | 0 | appendFixedInt( rWidget.m_aRect.Bottom()-iRectMargin, aLine ); |
4207 | 0 | aLine.append( "]\n" ); |
4208 | 0 | } |
4209 | 0 | aLine.append( "/FT/" ); |
4210 | 0 | switch( rWidget.m_eType ) |
4211 | 0 | { |
4212 | 0 | case PDFWriter::RadioButton: |
4213 | 0 | case PDFWriter::CheckBox: |
4214 | | // for radio buttons only the RadioButton field, not the |
4215 | | // CheckBox children should have a value, else acrobat reader |
4216 | | // does not always check the right button |
4217 | | // of course real check boxes (not belonging to a radio group) |
4218 | | // need their values, too |
4219 | 0 | if( rWidget.m_eType == PDFWriter::RadioButton || rWidget.m_nRadioGroup < 0 ) |
4220 | 0 | { |
4221 | 0 | aValue.append( "/" ); |
4222 | | // check for radio group with all buttons unpressed |
4223 | 0 | if( rWidget.m_aValue.isEmpty() ) |
4224 | 0 | aValue.append( "Off" ); |
4225 | 0 | else |
4226 | 0 | COSWriter::appendName( rWidget.m_aValue, aValue ); |
4227 | 0 | } |
4228 | 0 | [[fallthrough]]; |
4229 | 0 | case PDFWriter::PushButton: |
4230 | 0 | aLine.append( "Btn" ); |
4231 | 0 | break; |
4232 | 0 | case PDFWriter::ListBox: |
4233 | 0 | if( rWidget.m_nFlags & 0x200000 ) // multiselect |
4234 | 0 | { |
4235 | 0 | aValue.append( "[" ); |
4236 | 0 | for( size_t i = 0; i < rWidget.m_aSelectedEntries.size(); i++ ) |
4237 | 0 | { |
4238 | 0 | sal_Int32 nEntry = rWidget.m_aSelectedEntries[i]; |
4239 | 0 | if( nEntry >= 0 |
4240 | 0 | && o3tl::make_unsigned(nEntry) < rWidget.m_aListEntries.size() ) |
4241 | 0 | { |
4242 | 0 | aValueWriter.writeUnicodeEncrypt(rWidget.m_aListEntries[ nEntry ], rWidget.m_nObject); |
4243 | 0 | } |
4244 | 0 | } |
4245 | 0 | aValue.append( "]" ); |
4246 | 0 | } |
4247 | 0 | else if( !rWidget.m_aSelectedEntries.empty() && |
4248 | 0 | rWidget.m_aSelectedEntries[0] >= 0 && |
4249 | 0 | o3tl::make_unsigned(rWidget.m_aSelectedEntries[0]) < rWidget.m_aListEntries.size() ) |
4250 | 0 | { |
4251 | 0 | aValueWriter.writeUnicodeEncrypt(rWidget.m_aListEntries[ rWidget.m_aSelectedEntries[0] ], rWidget.m_nObject); |
4252 | 0 | } |
4253 | 0 | else |
4254 | 0 | aValueWriter.writeUnicodeEncrypt( OUString(), rWidget.m_nObject); |
4255 | 0 | aLine.append( "Ch" ); |
4256 | 0 | break; |
4257 | 0 | case PDFWriter::ComboBox: |
4258 | 0 | aValueWriter.writeUnicodeEncrypt( rWidget.m_aValue, rWidget.m_nObject); |
4259 | 0 | aLine.append( "Ch" ); |
4260 | 0 | break; |
4261 | 0 | case PDFWriter::Edit: |
4262 | 0 | aLine.append( "Tx" ); |
4263 | 0 | aValueWriter.writeUnicodeEncrypt( rWidget.m_aValue, rWidget.m_nObject); |
4264 | 0 | break; |
4265 | 0 | case PDFWriter::Signature: |
4266 | 0 | aLine.append( "Sig" ); |
4267 | 0 | aValue.append(OUStringToOString(rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US)); |
4268 | 0 | break; |
4269 | 0 | case PDFWriter::Hierarchy: // make the compiler happy |
4270 | 0 | break; |
4271 | 0 | } |
4272 | 0 | aLine.append( "\n" ); |
4273 | 0 | aLine.append( "/P " ); |
4274 | 0 | aLine.append( m_aPages[ rWidget.m_nPage ].m_nPageObject ); |
4275 | 0 | aLine.append( " 0 R\n" ); |
4276 | 0 | } |
4277 | 0 | if( rWidget.m_nParent ) |
4278 | 0 | { |
4279 | 0 | aLine.append( "/Parent " ); |
4280 | 0 | aLine.append( rWidget.m_nParent ); |
4281 | 0 | aLine.append( " 0 R\n" ); |
4282 | 0 | } |
4283 | 0 | if( !rWidget.m_aKids.empty() ) |
4284 | 0 | { |
4285 | 0 | aLine.append( "/Kids[" ); |
4286 | 0 | for( size_t i = 0; i < rWidget.m_aKids.size(); i++ ) |
4287 | 0 | { |
4288 | 0 | aLine.append( rWidget.m_aKids[i] ); |
4289 | 0 | aLine.append( " 0 R" ); |
4290 | 0 | aLine.append( ( (i&15) == 15 ) ? "\n" : " " ); |
4291 | 0 | } |
4292 | 0 | aLine.append( "]\n" ); |
4293 | 0 | } |
4294 | 0 | if( !rWidget.m_aName.isEmpty() ) |
4295 | 0 | { |
4296 | 0 | aLine.append( "/T" ); |
4297 | 0 | aWriter.writeLiteralEncrypt(rWidget.m_aName, rWidget.m_nObject); |
4298 | 0 | aLine.append( "\n" ); |
4299 | 0 | } |
4300 | 0 | if (!rWidget.m_aDescription.isEmpty()) |
4301 | 0 | { |
4302 | | // the alternate field name should be unicode able since it is |
4303 | | // supposed to be used in UI |
4304 | 0 | aLine.append( "/TU" ); |
4305 | 0 | aWriter.writeUnicodeEncrypt(rWidget.m_aDescription, rWidget.m_nObject); |
4306 | 0 | aLine.append( "\n" ); |
4307 | 0 | } |
4308 | |
|
4309 | 0 | if( rWidget.m_nFlags ) |
4310 | 0 | { |
4311 | 0 | aLine.append( "/Ff " ); |
4312 | 0 | aLine.append( rWidget.m_nFlags ); |
4313 | 0 | aLine.append( "\n" ); |
4314 | 0 | } |
4315 | 0 | if( !aValue.isEmpty() ) |
4316 | 0 | { |
4317 | 0 | OString aVal = aValue.makeStringAndClear(); |
4318 | 0 | aLine.append( "/V " ); |
4319 | 0 | aLine.append( aVal ); |
4320 | 0 | aLine.append( "\n" |
4321 | 0 | "/DV " ); |
4322 | 0 | aLine.append( aVal ); |
4323 | 0 | aLine.append( "\n" ); |
4324 | 0 | } |
4325 | 0 | if( rWidget.m_eType == PDFWriter::ListBox || rWidget.m_eType == PDFWriter::ComboBox ) |
4326 | 0 | { |
4327 | 0 | sal_Int32 nTI = -1; |
4328 | 0 | aLine.append( "/Opt[\n" ); |
4329 | 0 | sal_Int32 i = 0; |
4330 | 0 | for (auto const& entry : rWidget.m_aListEntries) |
4331 | 0 | { |
4332 | 0 | aWriter.writeUnicodeEncrypt(entry, rWidget.m_nObject); |
4333 | 0 | aLine.append( "\n" ); |
4334 | 0 | if( entry == rWidget.m_aValue ) |
4335 | 0 | nTI = i; |
4336 | 0 | ++i; |
4337 | 0 | } |
4338 | 0 | aLine.append( "]\n" ); |
4339 | 0 | if( nTI > 0 ) |
4340 | 0 | { |
4341 | 0 | aLine.append( "/TI " ); |
4342 | 0 | aLine.append( nTI ); |
4343 | 0 | aLine.append( "\n" ); |
4344 | 0 | if( rWidget.m_nFlags & 0x200000 ) // Multiselect |
4345 | 0 | { |
4346 | 0 | aLine.append( "/I [" ); |
4347 | 0 | aLine.append( nTI ); |
4348 | 0 | aLine.append( "]\n" ); |
4349 | 0 | } |
4350 | 0 | } |
4351 | 0 | } |
4352 | 0 | if( rWidget.m_eType == PDFWriter::Edit ) |
4353 | 0 | { |
4354 | 0 | if ( rWidget.m_nMaxLen > 0 ) |
4355 | 0 | { |
4356 | 0 | aLine.append( "/MaxLen " ); |
4357 | 0 | aLine.append( rWidget.m_nMaxLen ); |
4358 | 0 | aLine.append( "\n" ); |
4359 | 0 | } |
4360 | |
|
4361 | 0 | if ( rWidget.m_nFormat == PDFWriter::Number ) |
4362 | 0 | { |
4363 | 0 | OString aHexText; |
4364 | |
|
4365 | 0 | if ( !rWidget.m_aCurrencySymbol.isEmpty() ) |
4366 | 0 | { |
4367 | | // Get the hexadecimal code |
4368 | 0 | sal_UCS4 cChar = rWidget.m_aCurrencySymbol.iterateCodePoints(&o3tl::temporary(sal_Int32(1)), -1); |
4369 | 0 | aHexText = "\\\\u" + OString::number(cChar, 16); |
4370 | 0 | } |
4371 | |
|
4372 | 0 | aLine.append("/AA<<\n"); |
4373 | 0 | aLine.append("/F<</JS(AFNumber_Format\\("); |
4374 | 0 | aLine.append(OString::number(rWidget.m_nDecimalAccuracy)); |
4375 | 0 | aLine.append(", 0, 0, 0, \""); |
4376 | 0 | aLine.append( aHexText ); |
4377 | 0 | aLine.append("\","); |
4378 | 0 | aLine.append(OString::boolean(rWidget.m_bPrependCurrencySymbol)); |
4379 | 0 | aLine.append("\\);)"); |
4380 | 0 | aLine.append("/S/JavaScript>>\n"); |
4381 | 0 | aLine.append("/K<</JS(AFNumber_Keystroke\\("); |
4382 | 0 | aLine.append(OString::number(rWidget.m_nDecimalAccuracy)); |
4383 | 0 | aLine.append(", 0, 0, 0, \""); |
4384 | 0 | aLine.append( aHexText ); |
4385 | 0 | aLine.append("\","); |
4386 | 0 | aLine.append(OString::boolean(rWidget.m_bPrependCurrencySymbol)); |
4387 | 0 | aLine.append("\\);)"); |
4388 | 0 | aLine.append("/S/JavaScript>>\n"); |
4389 | 0 | aLine.append(">>\n"); |
4390 | 0 | } |
4391 | 0 | else if ( rWidget.m_nFormat == PDFWriter::Time ) |
4392 | 0 | { |
4393 | 0 | aLine.append("/AA<<\n"); |
4394 | 0 | aLine.append("/F<</JS(AFTime_FormatEx\\(\""); |
4395 | 0 | aLine.append(OUStringToOString(rWidget.m_aTimeFormat, RTL_TEXTENCODING_ASCII_US)); |
4396 | 0 | aLine.append("\"\\);)"); |
4397 | 0 | aLine.append("/S/JavaScript>>\n"); |
4398 | 0 | aLine.append("/K<</JS(AFTime_KeystrokeEx\\(\""); |
4399 | 0 | aLine.append(OUStringToOString(rWidget.m_aTimeFormat, RTL_TEXTENCODING_ASCII_US)); |
4400 | 0 | aLine.append("\"\\);)"); |
4401 | 0 | aLine.append("/S/JavaScript>>\n"); |
4402 | 0 | aLine.append(">>\n"); |
4403 | 0 | } |
4404 | 0 | else if ( rWidget.m_nFormat == PDFWriter::Date ) |
4405 | 0 | { |
4406 | 0 | aLine.append("/AA<<\n"); |
4407 | 0 | aLine.append("/F<</JS(AFDate_FormatEx\\(\""); |
4408 | 0 | aLine.append(OUStringToOString(rWidget.m_aDateFormat, RTL_TEXTENCODING_ASCII_US)); |
4409 | 0 | aLine.append("\"\\);)"); |
4410 | 0 | aLine.append("/S/JavaScript>>\n"); |
4411 | 0 | aLine.append("/K<</JS(AFDate_KeystrokeEx\\(\""); |
4412 | 0 | aLine.append(OUStringToOString(rWidget.m_aDateFormat, RTL_TEXTENCODING_ASCII_US)); |
4413 | 0 | aLine.append("\"\\);)"); |
4414 | 0 | aLine.append("/S/JavaScript>>\n"); |
4415 | 0 | aLine.append(">>\n"); |
4416 | 0 | } |
4417 | 0 | } |
4418 | 0 | if( rWidget.m_eType == PDFWriter::PushButton ) |
4419 | 0 | { |
4420 | 0 | if (!m_bIsPDF_A1) |
4421 | 0 | { |
4422 | 0 | OStringBuffer aDest; |
4423 | 0 | if( rWidget.m_nDest != -1 && appendDest( m_aDestinationIdTranslation[ rWidget.m_nDest ], aDest ) ) |
4424 | 0 | { |
4425 | 0 | aLine.append( "/AA<</D<</Type/Action/S/GoTo/D " ); |
4426 | 0 | aLine.append( aDest ); |
4427 | 0 | aLine.append( ">>>>\n" ); |
4428 | 0 | } |
4429 | 0 | else if( rWidget.m_aListEntries.empty() ) |
4430 | 0 | { |
4431 | 0 | if( !m_bIsPDF_A2 && !m_bIsPDF_A3 ) |
4432 | 0 | { |
4433 | | // create a reset form action |
4434 | 0 | aLine.append( "/AA<</D<</Type/Action/S/ResetForm>>>>\n" ); |
4435 | 0 | } |
4436 | 0 | } |
4437 | 0 | else if( rWidget.m_bSubmit ) |
4438 | 0 | { |
4439 | | // create a submit form action |
4440 | 0 | aLine.append( "/AA<</D<</Type/Action/S/SubmitForm/F" ); |
4441 | 0 | aWriter.writeLiteralEncrypt(rWidget.m_aListEntries.front(), rWidget.m_nObject, osl_getThreadTextEncoding()); |
4442 | 0 | aLine.append( "/Flags " ); |
4443 | |
|
4444 | 0 | sal_Int32 nFlags = 0; |
4445 | 0 | switch( m_aContext.SubmitFormat ) |
4446 | 0 | { |
4447 | 0 | case PDFWriter::HTML: |
4448 | 0 | nFlags |= 4; |
4449 | 0 | break; |
4450 | 0 | case PDFWriter::XML: |
4451 | 0 | nFlags |= 32; |
4452 | 0 | break; |
4453 | 0 | case PDFWriter::PDF: |
4454 | 0 | nFlags |= 256; |
4455 | 0 | break; |
4456 | 0 | case PDFWriter::FDF: |
4457 | 0 | default: |
4458 | 0 | break; |
4459 | 0 | } |
4460 | 0 | if( rWidget.m_bSubmitGet ) |
4461 | 0 | nFlags |= 8; |
4462 | 0 | aLine.append( nFlags ); |
4463 | 0 | aLine.append( ">>>>\n" ); |
4464 | 0 | } |
4465 | 0 | else |
4466 | 0 | { |
4467 | | // create a URI action |
4468 | 0 | aLine.append( "/AA<</D<</Type/Action/S/URI/URI(" ); |
4469 | 0 | aLine.append( OUStringToOString( rWidget.m_aListEntries.front(), RTL_TEXTENCODING_ASCII_US ) ); |
4470 | 0 | aLine.append( ")>>>>\n" ); |
4471 | 0 | } |
4472 | 0 | } |
4473 | 0 | else |
4474 | 0 | m_aErrors.insert( PDFWriter::Warning_FormAction_Omitted_PDFA ); |
4475 | 0 | } |
4476 | 0 | if( !rWidget.m_aDAString.isEmpty() ) |
4477 | 0 | { |
4478 | 0 | if( !rWidget.m_aDRDict.isEmpty() ) |
4479 | 0 | { |
4480 | 0 | aLine.append( "/DR<<" ); |
4481 | 0 | aLine.append( rWidget.m_aDRDict ); |
4482 | 0 | aLine.append( ">>\n" ); |
4483 | 0 | } |
4484 | 0 | else |
4485 | 0 | { |
4486 | 0 | aLine.append( "/DR<</Font<<" ); |
4487 | 0 | appendBuildinFontsToDict( aLine ); |
4488 | 0 | aLine.append( ">>>>\n" ); |
4489 | 0 | } |
4490 | 0 | aLine.append( "/DA" ); |
4491 | 0 | aWriter.writeLiteralEncrypt(rWidget.m_aDAString, rWidget.m_nObject); |
4492 | 0 | aLine.append( "\n" ); |
4493 | 0 | if( rWidget.m_nTextStyle & DrawTextFlags::Center ) |
4494 | 0 | aLine.append( "/Q 1\n" ); |
4495 | 0 | else if( rWidget.m_nTextStyle & DrawTextFlags::Right ) |
4496 | 0 | aLine.append( "/Q 2\n" ); |
4497 | 0 | } |
4498 | | // appearance characteristics for terminal fields |
4499 | | // which are supposed to have an appearance constructed |
4500 | | // by the viewer application |
4501 | 0 | if( !rWidget.m_aMKDict.isEmpty() ) |
4502 | 0 | { |
4503 | 0 | aLine.append( "/MK<<" ); |
4504 | 0 | aLine.append( rWidget.m_aMKDict ); |
4505 | | //add the CA string, encrypting it |
4506 | 0 | aWriter.writeLiteralEncrypt(rWidget.m_aMKDictCAString, rWidget.m_nObject); |
4507 | 0 | aLine.append( ">>\n" ); |
4508 | 0 | } |
4509 | |
|
4510 | 0 | if (!emitAppearances(rWidget, aLine)) return false; |
4511 | | |
4512 | 0 | aLine.append( ">>\n" |
4513 | 0 | "endobj\n\n" ); |
4514 | 0 | if (!updateObject(rWidget.m_nObject)) return false; |
4515 | 0 | if (!writeBuffer(aLine)) return false; |
4516 | 0 | } |
4517 | 8.85k | return true; |
4518 | 8.85k | } |
4519 | | |
4520 | | bool PDFWriterImpl::emitAnnotations() |
4521 | 8.85k | { |
4522 | 8.85k | if( m_aPages.empty() ) |
4523 | 0 | return false; |
4524 | | |
4525 | 8.85k | if (!emitLinkAnnotations()) return false; |
4526 | 8.85k | if (!emitScreenAnnotations()) return false; |
4527 | 8.85k | if (!emitNoteAnnotations()) return false; |
4528 | 8.85k | if (!emitWidgetAnnotations()) return false; |
4529 | | |
4530 | 8.85k | return true; |
4531 | 8.85k | } |
4532 | | |
4533 | | class PDFStreamIf : public cppu::WeakImplHelper< css::io::XOutputStream > |
4534 | | { |
4535 | | VclPtr<PDFWriterImpl> m_pWriter; |
4536 | | bool m_bWrite; |
4537 | | public: |
4538 | 0 | explicit PDFStreamIf( PDFWriterImpl* pWriter ) : m_pWriter( pWriter ), m_bWrite( true ) {} |
4539 | | |
4540 | | virtual void SAL_CALL writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override |
4541 | 0 | { |
4542 | 0 | if( m_bWrite && aData.hasElements() ) |
4543 | 0 | { |
4544 | 0 | sal_Int32 nBytes = aData.getLength(); |
4545 | 0 | (void)m_pWriter->writeBufferBytes( aData.getConstArray(), nBytes ); |
4546 | 0 | } |
4547 | 0 | } |
4548 | 0 | virtual void SAL_CALL flush() override {} |
4549 | | virtual void SAL_CALL closeOutput() override |
4550 | 0 | { |
4551 | 0 | m_bWrite = false; |
4552 | 0 | } |
4553 | | }; |
4554 | | |
4555 | | bool PDFWriterImpl::emitEmbeddedFiles() |
4556 | 8.85k | { |
4557 | 8.85k | for (auto& rEmbeddedFile : m_aEmbeddedFiles) |
4558 | 0 | { |
4559 | 0 | if (!updateObject(rEmbeddedFile.m_nObject)) |
4560 | 0 | continue; |
4561 | | |
4562 | 0 | sal_Int32 nSizeObject = createObject(); |
4563 | 0 | sal_Int32 nParamsObject = createObject(); |
4564 | |
|
4565 | 0 | OStringBuffer aLine; |
4566 | 0 | aLine.append(rEmbeddedFile.m_nObject); |
4567 | 0 | aLine.append(" 0 obj\n"); |
4568 | 0 | aLine.append("<< /Type /EmbeddedFile"); |
4569 | 0 | if (!rEmbeddedFile.m_aSubType.isEmpty()) |
4570 | 0 | { |
4571 | 0 | aLine.append("/Subtype /"); |
4572 | 0 | COSWriter::appendName(rEmbeddedFile.m_aSubType, aLine); |
4573 | 0 | } |
4574 | 0 | aLine.append(" /Length "); |
4575 | 0 | appendObjectReference(nSizeObject, aLine); |
4576 | 0 | aLine.append(" /Params "); |
4577 | 0 | appendObjectReference(nParamsObject, aLine); |
4578 | 0 | aLine.append(">>\nstream\n"); |
4579 | 0 | if (!writeBuffer(aLine)) return false; |
4580 | 0 | aLine.setLength(0); |
4581 | |
|
4582 | 0 | sal_Int64 nSize{}; |
4583 | 0 | if (!rEmbeddedFile.m_aDataContainer.isEmpty()) |
4584 | 0 | { |
4585 | 0 | checkAndEnableStreamEncryption(rEmbeddedFile.m_nObject); |
4586 | 0 | sal_uInt64 const nStartPos{getCurrentFilePosition()}; |
4587 | 0 | if (!writeBufferBytes(rEmbeddedFile.m_aDataContainer.getData(), rEmbeddedFile.m_aDataContainer.getSize())) |
4588 | 0 | return false; |
4589 | 0 | disableStreamEncryption(); |
4590 | 0 | nSize = sal_Int64(getCurrentFilePosition() - nStartPos); |
4591 | 0 | } |
4592 | 0 | else if (rEmbeddedFile.m_pStream) |
4593 | 0 | { |
4594 | 0 | checkAndEnableStreamEncryption(rEmbeddedFile.m_nObject); |
4595 | 0 | sal_uInt64 nBegin = getCurrentFilePosition(); |
4596 | 0 | css::uno::Reference<css::io::XOutputStream> xStream(new PDFStreamIf(this)); |
4597 | 0 | rEmbeddedFile.m_pStream->write(xStream); |
4598 | 0 | rEmbeddedFile.m_pStream.reset(); |
4599 | 0 | xStream.clear(); |
4600 | 0 | nSize = sal_Int64(getCurrentFilePosition() - nBegin); |
4601 | 0 | disableStreamEncryption(); |
4602 | 0 | } |
4603 | 0 | aLine.append("\nendstream\nendobj\n\n"); |
4604 | 0 | if (!writeBuffer(aLine)) return false; |
4605 | 0 | aLine.setLength(0); |
4606 | |
|
4607 | 0 | if (!updateObject(nSizeObject)) |
4608 | 0 | return false; |
4609 | 0 | aLine.append(nSizeObject); |
4610 | 0 | aLine.append(" 0 obj\n"); |
4611 | 0 | aLine.append(nSize); |
4612 | 0 | aLine.append("\nendobj\n\n"); |
4613 | 0 | if (!writeBuffer(aLine)) |
4614 | 0 | return false; |
4615 | 0 | aLine.setLength(0); |
4616 | |
|
4617 | 0 | if (!updateObject(nParamsObject)) |
4618 | 0 | return false; |
4619 | 0 | aLine.append(nParamsObject); |
4620 | 0 | aLine.append(" 0 obj\n"); |
4621 | 0 | aLine.append("<<"); |
4622 | 0 | aLine.append("/Size "); |
4623 | 0 | aLine.append(nSize); |
4624 | 0 | aLine.append(">>"); |
4625 | 0 | aLine.append("\nendobj\n\n"); |
4626 | 0 | if (!writeBuffer(aLine)) |
4627 | 0 | return false; |
4628 | 0 | } |
4629 | 8.85k | return true; |
4630 | 8.85k | } |
4631 | | |
4632 | | bool PDFWriterImpl::emitCatalog() |
4633 | 8.85k | { |
4634 | | // build page tree |
4635 | | // currently there is only one node that contains all leaves |
4636 | | |
4637 | | // first create a page tree node id |
4638 | 8.85k | sal_Int32 nTreeNode = createObject(); |
4639 | | |
4640 | | // emit global resource dictionary (page emit needs it) |
4641 | 8.85k | if (!emitResources()) return false; |
4642 | | |
4643 | | // emit all pages |
4644 | 8.85k | for (auto & page : m_aPages) |
4645 | 73.7k | if( ! page.emit( nTreeNode ) ) |
4646 | 0 | return false; |
4647 | | |
4648 | 8.85k | sal_Int32 nNamedDestinationsDictionary = emitNamedDestinations(); |
4649 | | |
4650 | 8.85k | sal_Int32 nOutlineDict = emitOutline(); |
4651 | | |
4652 | | // emit Output intent |
4653 | 8.85k | sal_Int32 nOutputIntentObject = emitOutputIntent(); |
4654 | | |
4655 | | // emit metadata |
4656 | 8.85k | sal_Int32 nMetadataObject = emitDocumentMetadata(); |
4657 | | |
4658 | 8.85k | emitNamespaces(); |
4659 | | |
4660 | 8.85k | sal_Int32 nStructureDict = 0; |
4661 | 8.85k | if(m_aStructure.size() > 1) |
4662 | 0 | { |
4663 | 0 | removePlaceholderSE(m_aStructure, m_aStructure[0]); |
4664 | | // check if dummy structure containers are needed |
4665 | 0 | addInternalStructureContainer(0); |
4666 | 0 | nStructureDict = m_aStructure[0].m_nObject = createObject(); |
4667 | 0 | emitStructure( m_aStructure[ 0 ] ); |
4668 | 0 | if (!emitObjectStream()) |
4669 | 0 | return false; |
4670 | | // whatever put its strings in plain has to have landed in an object stream; that the |
4671 | | // stream is encrypted is asserted where it is written |
4672 | 0 | assert(std::ranges::all_of(m_aPlainStringObjects, [this](sal_Int32 nPlain) { |
4673 | 0 | return m_aObjects[nPlain - 1].isCompressed(); |
4674 | 0 | })); |
4675 | 0 | } |
4676 | | |
4677 | | // adjust tree node file offset |
4678 | 8.85k | if( ! updateObject( nTreeNode ) ) |
4679 | 0 | return false; |
4680 | | |
4681 | | // emit tree node |
4682 | 8.85k | OStringBuffer aLine( 2048 ); |
4683 | 8.85k | COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor); |
4684 | 8.85k | aLine.append( nTreeNode ); |
4685 | 8.85k | aLine.append( " 0 obj\n" ); |
4686 | 8.85k | aLine.append( "<</Type/Pages\n" ); |
4687 | 8.85k | aLine.append( "/Resources " ); |
4688 | 8.85k | aLine.append( getResourceDictObj() ); |
4689 | 8.85k | aLine.append( " 0 R\n" ); |
4690 | | |
4691 | 8.85k | if( m_aPages.empty() ) // sanity check, this should not happen |
4692 | 0 | aLine.append( "/MediaBox[0 0 595 842]\n" ); // default A4 size in pt |
4693 | | |
4694 | 8.85k | aLine.append("/Kids[ "); |
4695 | 8.85k | unsigned int i = 0; |
4696 | 8.85k | for (const auto & page : m_aPages) |
4697 | 73.7k | { |
4698 | 73.7k | aLine.append( page.m_nPageObject ); |
4699 | 73.7k | aLine.append( " 0 R" ); |
4700 | 73.7k | aLine.append( ( (i&15) == 15 ) ? "\n" : " " ); |
4701 | 73.7k | ++i; |
4702 | 73.7k | } |
4703 | 8.85k | aLine.append( "]\n" |
4704 | 8.85k | "/Count " ); |
4705 | 8.85k | aLine.append( static_cast<sal_Int32>(m_aPages.size()) ); |
4706 | 8.85k | aLine.append( ">>\n" |
4707 | 8.85k | "endobj\n\n" ); |
4708 | 8.85k | if (!writeBuffer(aLine)) return false; |
4709 | | |
4710 | | // emit annotation objects |
4711 | 8.85k | if (!emitAnnotations()) return false; |
4712 | 8.85k | if (!emitEmbeddedFiles()) return false; |
4713 | | |
4714 | | // emit attached files |
4715 | 8.85k | for (auto & rAttachedFile : m_aDocumentAttachedFiles) |
4716 | 0 | { |
4717 | 0 | if (!updateObject(rAttachedFile.mnObjectId)) |
4718 | 0 | return false; |
4719 | 0 | aLine.setLength(0); |
4720 | |
|
4721 | 0 | aWriter.startObject(rAttachedFile.mnObjectId); |
4722 | 0 | aWriter.startDict(); |
4723 | 0 | aWriter.write("/Type", "/Filespec"); |
4724 | | |
4725 | | // Add associated files relationship (since PDF 2.0) |
4726 | 0 | if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0 || m_nPDFA_Version == 3) |
4727 | 0 | { |
4728 | 0 | aWriter.write("/AFRelationship", "/Source"); |
4729 | 0 | } |
4730 | |
|
4731 | 0 | aWriter.writeKeyAndUnicodeEncrypt("/F", rAttachedFile.maFilename, rAttachedFile.mnObjectId); |
4732 | 0 | if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version) |
4733 | 0 | { |
4734 | 0 | aWriter.writeKeyAndUnicodeEncrypt("/UF", rAttachedFile.maFilename, rAttachedFile.mnObjectId); |
4735 | 0 | } |
4736 | 0 | if (!rAttachedFile.maDescription.isEmpty()) |
4737 | 0 | { |
4738 | 0 | aWriter.writeKeyAndUnicodeEncrypt("/Desc", rAttachedFile.maDescription, rAttachedFile.mnObjectId); |
4739 | 0 | } |
4740 | 0 | aLine.append("/EF"); |
4741 | 0 | aWriter.startDict(); |
4742 | 0 | aWriter.writeKeyAndReference("/F", rAttachedFile.mnEmbeddedFileObjectId); |
4743 | 0 | aWriter.endDict(); |
4744 | 0 | aWriter.endDict(); |
4745 | 0 | aWriter.endObject(); |
4746 | 0 | if (!writeBuffer(aLine)) return false; |
4747 | 0 | } |
4748 | | |
4749 | | // emit Catalog |
4750 | 8.85k | m_nCatalogObject = createObject(); |
4751 | 8.85k | if( ! updateObject( m_nCatalogObject ) ) |
4752 | 0 | return false; |
4753 | 8.85k | aLine.setLength( 0 ); |
4754 | 8.85k | aLine.append( m_nCatalogObject ); |
4755 | 8.85k | aLine.append( " 0 obj\n" |
4756 | 8.85k | "<</Type/Catalog/Pages " ); |
4757 | 8.85k | aLine.append( nTreeNode ); |
4758 | 8.85k | aLine.append( " 0 R\n" ); |
4759 | | |
4760 | | // check if there are named destinations to emit (root must be inside the catalog) |
4761 | 8.85k | if( nNamedDestinationsDictionary ) |
4762 | 0 | { |
4763 | 0 | aLine.append("/Dests "); |
4764 | 0 | aLine.append( nNamedDestinationsDictionary ); |
4765 | 0 | aLine.append( " 0 R\n" ); |
4766 | 0 | } |
4767 | | |
4768 | 8.85k | if (!m_aDocumentAttachedFiles.empty()) |
4769 | 0 | { |
4770 | | // Write the associated files catalog entry (since PDF 2.0) |
4771 | 0 | if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0 || m_nPDFA_Version == 3) |
4772 | 0 | { |
4773 | 0 | aLine.append("/AF"); |
4774 | 0 | aLine.append("["); |
4775 | 0 | for (auto & rAttachedFile : m_aDocumentAttachedFiles) |
4776 | 0 | aWriter.writeReference(rAttachedFile.mnObjectId); |
4777 | 0 | aLine.append("]"); |
4778 | 0 | } |
4779 | |
|
4780 | 0 | aWriter.startDictWithKey("/Names"); |
4781 | 0 | aWriter.startDictWithKey("/EmbeddedFiles"); |
4782 | 0 | aLine.append("/Names ["); |
4783 | 0 | for (auto & rAttachedFile : m_aDocumentAttachedFiles) |
4784 | 0 | { |
4785 | 0 | aWriter.writeUnicodeEncrypt(rAttachedFile.maFilename, m_nCatalogObject); |
4786 | 0 | aWriter.writeReference(rAttachedFile.mnObjectId); |
4787 | 0 | } |
4788 | 0 | aLine.append("]"); |
4789 | 0 | aWriter.endDict(); |
4790 | 0 | aWriter.endDict(); |
4791 | 0 | } |
4792 | | |
4793 | 8.85k | if( m_aContext.PageLayout != PDFWriter::DefaultLayout ) |
4794 | 0 | switch( m_aContext.PageLayout ) |
4795 | 0 | { |
4796 | 0 | default : |
4797 | 0 | case PDFWriter::SinglePage : |
4798 | 0 | aLine.append( "/PageLayout/SinglePage\n" ); |
4799 | 0 | break; |
4800 | 0 | case PDFWriter::Continuous : |
4801 | 0 | aLine.append( "/PageLayout/OneColumn\n" ); |
4802 | 0 | break; |
4803 | 0 | case PDFWriter::ContinuousFacing : |
4804 | | // the flag m_aContext.FirstPageLeft below is used to set the page on the left side |
4805 | 0 | aLine.append( "/PageLayout/TwoColumnRight\n" );//odd page on the right side |
4806 | 0 | break; |
4807 | 0 | } |
4808 | 8.85k | if( m_aContext.PDFDocumentMode != PDFWriter::ModeDefault && !m_aContext.OpenInFullScreenMode ) |
4809 | 0 | switch( m_aContext.PDFDocumentMode ) |
4810 | 0 | { |
4811 | 0 | default : |
4812 | 0 | aLine.append( "/PageMode/UseNone\n" ); |
4813 | 0 | break; |
4814 | 0 | case PDFWriter::UseOutlines : |
4815 | 0 | aLine.append( "/PageMode/UseOutlines\n" ); //document is opened with outline pane open |
4816 | 0 | break; |
4817 | 0 | case PDFWriter::UseThumbs : |
4818 | 0 | aLine.append( "/PageMode/UseThumbs\n" ); //document is opened with thumbnails pane open |
4819 | 0 | break; |
4820 | 0 | } |
4821 | 8.85k | else if( m_aContext.OpenInFullScreenMode ) |
4822 | 0 | aLine.append( "/PageMode/FullScreen\n" ); //document is opened full screen |
4823 | | |
4824 | 8.85k | OStringBuffer aInitPageRef; |
4825 | 8.85k | if( m_aContext.InitialPage >= 0 && o3tl::make_unsigned(m_aContext.InitialPage) < m_aPages.size() ) |
4826 | 8.85k | { |
4827 | 8.85k | aInitPageRef.append( m_aPages[m_aContext.InitialPage].m_nPageObject ); |
4828 | 8.85k | aInitPageRef.append( " 0 R" ); |
4829 | 8.85k | } |
4830 | 0 | else |
4831 | 0 | aInitPageRef.append( "0" ); |
4832 | | |
4833 | 8.85k | switch( m_aContext.PDFDocumentAction ) |
4834 | 8.85k | { |
4835 | 8.85k | case PDFWriter::ActionDefault : //do nothing, this is the Acrobat default |
4836 | 8.85k | default: |
4837 | 8.85k | if ( aInitPageRef.getLength() > 1 && m_aContext.InitialPage > 0 ) |
4838 | 0 | { |
4839 | 0 | aLine.append( "/OpenAction[" ); |
4840 | 0 | aLine.append( aInitPageRef ); |
4841 | 0 | aLine.append( " /XYZ null null 0]\n" ); |
4842 | 0 | } |
4843 | 8.85k | break; |
4844 | 0 | case PDFWriter::FitInWindow : |
4845 | 0 | aLine.append( "/OpenAction[" ); |
4846 | 0 | aLine.append( aInitPageRef ); |
4847 | 0 | aLine.append( " /Fit]\n" ); //Open fit page |
4848 | 0 | break; |
4849 | 0 | case PDFWriter::FitWidth : |
4850 | 0 | aLine.append( "/OpenAction[" ); |
4851 | 0 | aLine.append( aInitPageRef ); |
4852 | 0 | aLine.append( " /FitH null]\n" ); //Open fit width |
4853 | 0 | break; |
4854 | 0 | case PDFWriter::FitVisible : |
4855 | 0 | aLine.append( "/OpenAction[" ); |
4856 | 0 | aLine.append( aInitPageRef ); |
4857 | 0 | aLine.append( " /FitBH null]\n" ); //Open fit visible |
4858 | 0 | break; |
4859 | 0 | case PDFWriter::ActionZoom : |
4860 | 0 | aLine.append( "/OpenAction[" ); |
4861 | 0 | aLine.append( aInitPageRef ); |
4862 | 0 | aLine.append( " /XYZ null null " ); |
4863 | 0 | if( m_aContext.Zoom >= 50 && m_aContext.Zoom <= 1600 ) |
4864 | 0 | aLine.append( m_aContext.Zoom/100.0 ); |
4865 | 0 | else |
4866 | 0 | aLine.append( "0" ); |
4867 | 0 | aLine.append( "]\n" ); |
4868 | 0 | break; |
4869 | 8.85k | } |
4870 | | |
4871 | | // viewer preferences, if we had some, then emit |
4872 | 8.85k | if (m_aContext.HideViewerToolbar || |
4873 | 8.85k | (!m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle) || |
4874 | 8.21k | m_aContext.HideViewerMenubar || |
4875 | 8.21k | m_aContext.HideViewerWindowControls || m_aContext.FitWindow || |
4876 | 8.21k | m_aContext.CenterWindow || (m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing ) || |
4877 | 8.21k | m_aContext.OpenInFullScreenMode || |
4878 | 8.21k | m_bIsPDF_UA) |
4879 | 637 | { |
4880 | 637 | aLine.append( "/ViewerPreferences<<" ); |
4881 | 637 | if( m_aContext.HideViewerToolbar ) |
4882 | 0 | aLine.append( "/HideToolbar true\n" ); |
4883 | 637 | if( m_aContext.HideViewerMenubar ) |
4884 | 0 | aLine.append( "/HideMenubar true\n" ); |
4885 | 637 | if( m_aContext.HideViewerWindowControls ) |
4886 | 0 | aLine.append( "/HideWindowUI true\n" ); |
4887 | 637 | if( m_aContext.FitWindow ) |
4888 | 0 | aLine.append( "/FitWindow true\n" ); |
4889 | 637 | if( m_aContext.CenterWindow ) |
4890 | 0 | aLine.append( "/CenterWindow true\n" ); |
4891 | | // PDF/UA-2 requires /DisplayDocTitle set to true |
4892 | 637 | if ((!m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle) || m_bIsPDF_UA) |
4893 | 637 | aLine.append( "/DisplayDocTitle true\n" ); |
4894 | 637 | if( m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing ) |
4895 | 0 | aLine.append( "/Direction/R2L\n" ); |
4896 | 637 | if( m_aContext.OpenInFullScreenMode ) |
4897 | 0 | switch( m_aContext.PDFDocumentMode ) |
4898 | 0 | { |
4899 | 0 | default : |
4900 | 0 | case PDFWriter::ModeDefault : |
4901 | 0 | aLine.append( "/NonFullScreenPageMode/UseNone\n" ); |
4902 | 0 | break; |
4903 | 0 | case PDFWriter::UseOutlines : |
4904 | 0 | aLine.append( "/NonFullScreenPageMode/UseOutlines\n" ); |
4905 | 0 | break; |
4906 | 0 | case PDFWriter::UseThumbs : |
4907 | 0 | aLine.append( "/NonFullScreenPageMode/UseThumbs\n" ); |
4908 | 0 | break; |
4909 | 0 | } |
4910 | 637 | aLine.append( ">>\n" ); |
4911 | 637 | } |
4912 | | |
4913 | 8.85k | if( nOutlineDict ) |
4914 | 523 | { |
4915 | 523 | aLine.append( "/Outlines " ); |
4916 | 523 | aLine.append( nOutlineDict ); |
4917 | 523 | aLine.append( " 0 R\n" ); |
4918 | 523 | } |
4919 | 8.85k | if( nStructureDict ) |
4920 | 0 | { |
4921 | 0 | aLine.append( "/StructTreeRoot " ); |
4922 | 0 | aLine.append( nStructureDict ); |
4923 | 0 | aLine.append( " 0 R\n" ); |
4924 | 0 | } |
4925 | 8.85k | if( !m_aContext.DocumentLocale.Language.isEmpty() ) |
4926 | 8.85k | { |
4927 | | /* PDF allows only RFC 3066, see above in emitStructure(). */ |
4928 | 8.85k | LanguageTag aLanguageTag( m_aContext.DocumentLocale); |
4929 | 8.85k | OUString aLanguage, aScript, aCountry; |
4930 | 8.85k | aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry); |
4931 | 8.85k | if (!aLanguage.isEmpty()) |
4932 | 8.85k | { |
4933 | 8.85k | OUStringBuffer aLocBuf( 16 ); |
4934 | 8.85k | aLocBuf.append( aLanguage ); |
4935 | 8.85k | if( !aCountry.isEmpty() ) |
4936 | 476 | { |
4937 | 476 | aLocBuf.append( '-' ); |
4938 | 476 | aLocBuf.append( aCountry ); |
4939 | 476 | } |
4940 | 8.85k | aLine.append( "/Lang" ); |
4941 | 8.85k | aWriter.writeLiteralEncrypt(aLocBuf, m_nCatalogObject); |
4942 | 8.85k | aLine.append( "\n" ); |
4943 | 8.85k | } |
4944 | 8.85k | } |
4945 | 8.85k | if (m_aContext.Tagged) |
4946 | 0 | { |
4947 | 0 | aLine.append( "/MarkInfo<</Marked true>>\n" ); |
4948 | 0 | } |
4949 | | |
4950 | 8.85k | if (!m_aWidgets.empty() || !m_aCopiedWidgets.empty()) |
4951 | 0 | { |
4952 | 0 | aLine.append("/AcroForm"); |
4953 | 0 | aLine.append("<<"); |
4954 | 0 | aLine.append("/Fields"); |
4955 | 0 | aLine.append("[ "); |
4956 | |
|
4957 | 0 | for (auto const& rWidget : m_aWidgets) |
4958 | 0 | { |
4959 | | // output only root fields |
4960 | 0 | if (rWidget.m_nParent < 1) |
4961 | 0 | { |
4962 | 0 | appendObjectReference(rWidget.m_nObject, aLine); |
4963 | 0 | } |
4964 | 0 | } |
4965 | | // Add widgets that were copied from an external PDF |
4966 | 0 | for (auto const& rCopiedWidget : m_aCopiedWidgets) |
4967 | 0 | { |
4968 | 0 | appendObjectReference(rCopiedWidget.m_nObject, aLine); |
4969 | 0 | } |
4970 | 0 | aLine.append(" ]\n"); |
4971 | |
|
4972 | 0 | bool bSigned = false; |
4973 | | #if HAVE_FEATURE_NSS |
4974 | | if (m_nSignatureObject != -1) |
4975 | | { |
4976 | | aLine.append("/SigFlags 3 "); |
4977 | | bSigned = true; |
4978 | | } |
4979 | | #endif |
4980 | 0 | aLine.append("/DR<</Font "); |
4981 | 0 | appendObjectReference(getFontDictObject(), aLine); |
4982 | 0 | aLine.append(">>"); |
4983 | | |
4984 | | // NeedAppearances must not be used if PDF is signed, PDF/A is used or |
4985 | | // we have copied widgets (can't guarantee we have appearance streams in this case) |
4986 | 0 | if (m_nPDFA_Version == 0 && !bSigned && m_aCopiedWidgets.empty()) |
4987 | 0 | aLine.append("/NeedAppearances true "); |
4988 | |
|
4989 | 0 | aLine.append(">>\n"); |
4990 | 0 | } |
4991 | | |
4992 | | //check if there is a Metadata object |
4993 | 8.85k | if( nOutputIntentObject ) |
4994 | 0 | { |
4995 | 0 | aLine.append("/OutputIntents["); |
4996 | 0 | aLine.append( nOutputIntentObject ); |
4997 | 0 | aLine.append( " 0 R]" ); |
4998 | 0 | } |
4999 | | |
5000 | 8.85k | if( nMetadataObject ) |
5001 | 8.85k | { |
5002 | 8.85k | aWriter.writeKeyAndReference("/Metadata", nMetadataObject); |
5003 | 8.85k | } |
5004 | | |
5005 | 8.85k | aLine.append( ">>\n" |
5006 | 8.85k | "endobj\n\n" ); |
5007 | 8.85k | return writeBuffer( aLine ); |
5008 | 8.85k | } |
5009 | | |
5010 | | #if HAVE_FEATURE_NSS |
5011 | | |
5012 | | bool PDFWriterImpl::emitSignature() |
5013 | | { |
5014 | | if( !updateObject( m_nSignatureObject ) ) |
5015 | | return false; |
5016 | | |
5017 | | OStringBuffer aLine( 0x5000 ); |
5018 | | COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor); |
5019 | | aLine.append( m_nSignatureObject ); |
5020 | | aLine.append( " 0 obj\n" ); |
5021 | | aLine.append("<</Contents <" ); |
5022 | | |
5023 | | sal_uInt64 nOffset = ~0U; |
5024 | | if (osl::File::E_None != m_aFile.getPos(nOffset)) |
5025 | | return false; |
5026 | | |
5027 | | m_nSignatureContentOffset = nOffset + aLine.getLength(); |
5028 | | |
5029 | | // reserve some space for the PKCS#7 object |
5030 | | aLine.append(RepeatedChar('0', MAX_SIGNATURE_CONTENT_LENGTH) |
5031 | | + ">\n/Type/Sig/SubFilter/adbe.pkcs7.detached"); |
5032 | | |
5033 | | if( !m_aContext.DocumentInfo.Author.isEmpty() ) |
5034 | | { |
5035 | | aLine.append( "/Name" ); |
5036 | | aWriter.writeUnicodeEncrypt(m_aContext.DocumentInfo.Author, m_nSignatureObject); |
5037 | | } |
5038 | | |
5039 | | aLine.append( " /M "); |
5040 | | aWriter.writeLiteralEncrypt(m_aCreationDateString, m_nSignatureObject); |
5041 | | |
5042 | | aLine.append( " /ByteRange [ 0 "); |
5043 | | aLine.append( m_nSignatureContentOffset - 1 ); |
5044 | | aLine.append( " " ); |
5045 | | aLine.append( m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1 ); |
5046 | | aLine.append( " " ); |
5047 | | |
5048 | | m_nSignatureLastByteRangeNoOffset = nOffset + aLine.getLength(); |
5049 | | |
5050 | | // mark the last ByteRange no and add some space. Now, we don't know |
5051 | | // how many bytes we need for this ByteRange value |
5052 | | // The real value will be overwritten in the finalizeSignature method |
5053 | | aLine.append(RepeatedChar(' ', 100) + " /Filter/Adobe.PPKMS"); |
5054 | | |
5055 | | //emit reason, location and contactinfo |
5056 | | if ( !m_aContext.SignReason.isEmpty() ) |
5057 | | { |
5058 | | aLine.append("/Reason"); |
5059 | | aWriter.writeUnicodeEncrypt(m_aContext.SignReason, m_nSignatureObject); |
5060 | | } |
5061 | | |
5062 | | if ( !m_aContext.SignLocation.isEmpty() ) |
5063 | | { |
5064 | | aLine.append("/Location"); |
5065 | | aWriter.writeUnicodeEncrypt(m_aContext.SignLocation, m_nSignatureObject); |
5066 | | } |
5067 | | |
5068 | | if ( !m_aContext.SignContact.isEmpty() ) |
5069 | | { |
5070 | | aLine.append("/ContactInfo"); |
5071 | | aWriter.writeUnicodeEncrypt(m_aContext.SignContact, m_nSignatureObject); |
5072 | | } |
5073 | | |
5074 | | aLine.append(" >>\nendobj\n\n" ); |
5075 | | |
5076 | | return writeBuffer( aLine ); |
5077 | | } |
5078 | | |
5079 | | bool PDFWriterImpl::finalizeSignature() |
5080 | | { |
5081 | | if (!m_aContext.SignCertificate.is()) |
5082 | | return false; |
5083 | | |
5084 | | // 1- calculate last ByteRange value |
5085 | | sal_uInt64 nOffset = ~0U; |
5086 | | if (osl::File::E_None != m_aFile.getPos(nOffset)) |
5087 | | return false; |
5088 | | |
5089 | | sal_Int64 nLastByteRangeNo = nOffset - (m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1); |
5090 | | |
5091 | | // 2- overwrite the value to the m_nSignatureLastByteRangeNoOffset position |
5092 | | sal_uInt64 nWritten = 0; |
5093 | | if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureLastByteRangeNoOffset)) |
5094 | | return false; |
5095 | | OString aByteRangeNo = OString::number( nLastByteRangeNo ) + " ]"; |
5096 | | |
5097 | | if (m_aFile.write(aByteRangeNo.getStr(), aByteRangeNo.getLength(), nWritten) != osl::File::E_None) |
5098 | | { |
5099 | | (void)m_aFile.setPos(osl_Pos_Absolut, nOffset); |
5100 | | return false; |
5101 | | } |
5102 | | |
5103 | | // 3- create the PKCS#7 object using NSS |
5104 | | |
5105 | | // Prepare buffer and calculate PDF file digest |
5106 | | if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, 0)) |
5107 | | return false; |
5108 | | |
5109 | | std::unique_ptr<char[]> buffer1(new char[m_nSignatureContentOffset + 1]); |
5110 | | sal_uInt64 bytesRead1; |
5111 | | |
5112 | | //FIXME: Check if hash is calculated from the correct byterange |
5113 | | if (osl::File::E_None != m_aFile.read(buffer1.get(), m_nSignatureContentOffset - 1 , bytesRead1) || |
5114 | | bytesRead1 != o3tl::make_unsigned(m_nSignatureContentOffset) - 1) |
5115 | | { |
5116 | | SAL_WARN("vcl.pdfwriter", "First buffer read failed"); |
5117 | | return false; |
5118 | | } |
5119 | | |
5120 | | std::unique_ptr<char[]> buffer2(new char[nLastByteRangeNo + 1]); |
5121 | | sal_uInt64 bytesRead2; |
5122 | | |
5123 | | if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1) || |
5124 | | osl::File::E_None != m_aFile.read(buffer2.get(), nLastByteRangeNo, bytesRead2) || |
5125 | | bytesRead2 != o3tl::make_unsigned(nLastByteRangeNo)) |
5126 | | { |
5127 | | SAL_WARN("vcl.pdfwriter", "Second buffer read failed"); |
5128 | | return false; |
5129 | | } |
5130 | | |
5131 | | OStringBuffer aCMSHexBuffer; |
5132 | | svl::crypto::SigningContext aSigningContext; |
5133 | | aSigningContext.m_xCertificate = m_aContext.SignCertificate; |
5134 | | svl::crypto::Signing aSigning(aSigningContext); |
5135 | | aSigning.AddDataRange(buffer1.get(), bytesRead1); |
5136 | | aSigning.AddDataRange(buffer2.get(), bytesRead2); |
5137 | | aSigning.SetSignTSA(m_aContext.SignTSA); |
5138 | | aSigning.SetSignPassword(m_aContext.SignPassword); |
5139 | | if (!aSigning.Sign(aCMSHexBuffer)) |
5140 | | { |
5141 | | SAL_WARN("vcl.pdfwriter", "PDFWriter::Sign() failed"); |
5142 | | return false; |
5143 | | } |
5144 | | |
5145 | | assert(aCMSHexBuffer.getLength() <= MAX_SIGNATURE_CONTENT_LENGTH); |
5146 | | |
5147 | | // Set file pointer to the m_nSignatureContentOffset, we're ready to overwrite PKCS7 object |
5148 | | nWritten = 0; |
5149 | | if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset)) |
5150 | | return false; |
5151 | | m_aFile.write(aCMSHexBuffer.getStr(), aCMSHexBuffer.getLength(), nWritten); |
5152 | | |
5153 | | return osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset); |
5154 | | } |
5155 | | |
5156 | | #endif //HAVE_FEATURE_NSS |
5157 | | |
5158 | | sal_Int32 PDFWriterImpl::emitInfoDict( ) |
5159 | 8.85k | { |
5160 | 8.85k | sal_Int32 nObject = createObject(); |
5161 | | |
5162 | 8.85k | if (!updateObject(nObject)) |
5163 | 0 | return 0; |
5164 | | |
5165 | 8.85k | COSWriter aWriter(m_aContext.Encryption.getParams(), m_pPDFEncryptor); |
5166 | 8.85k | aWriter.startObject(nObject); |
5167 | 8.85k | aWriter.startDict(); |
5168 | | |
5169 | | // These entries are deprecated in PDF 2.0 (in favor of XMP metadata) and shouldn't be written. |
5170 | | // Exception: CreationDate and ModDate (which we don't write) |
5171 | 8.85k | if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0) |
5172 | 8.85k | { |
5173 | 8.85k | if (!m_aContext.DocumentInfo.Title.isEmpty()) |
5174 | 637 | { |
5175 | 637 | aWriter.writeKeyAndUnicodeEncrypt("/Title", m_aContext.DocumentInfo.Title, nObject); |
5176 | 637 | } |
5177 | 8.85k | if (!m_aContext.DocumentInfo.Author.isEmpty()) |
5178 | 450 | { |
5179 | 450 | aWriter.writeKeyAndUnicodeEncrypt("/Author", m_aContext.DocumentInfo.Author, nObject); |
5180 | 450 | } |
5181 | 8.85k | if (!m_aContext.DocumentInfo.Subject.isEmpty()) |
5182 | 58 | { |
5183 | 58 | aWriter.writeKeyAndUnicodeEncrypt("/Subject", m_aContext.DocumentInfo.Subject, nObject); |
5184 | 58 | } |
5185 | 8.85k | if (!m_aContext.DocumentInfo.Keywords.isEmpty()) |
5186 | 275 | { |
5187 | 275 | aWriter.writeKeyAndUnicodeEncrypt("/Keywords", m_aContext.DocumentInfo.Keywords, nObject); |
5188 | 275 | } |
5189 | 8.85k | if (!m_aContext.DocumentInfo.Creator.isEmpty()) |
5190 | 8.85k | { |
5191 | 8.85k | aWriter.writeKeyAndUnicodeEncrypt("/Creator", m_aContext.DocumentInfo.Creator, nObject); |
5192 | 8.85k | } |
5193 | 8.85k | if (!m_aContext.DocumentInfo.Producer.isEmpty()) |
5194 | 0 | { |
5195 | 0 | aWriter.writeKeyAndUnicodeEncrypt("/Producer", m_aContext.DocumentInfo.Producer, nObject); |
5196 | 0 | } |
5197 | 8.85k | } |
5198 | | // Allowed in PDF 2.0 |
5199 | 8.85k | aWriter.writeKeyAndLiteralEncrypt("/CreationDate", m_aCreationDateString, nObject); |
5200 | 8.85k | aWriter.endDict(); |
5201 | 8.85k | aWriter.endObject(); |
5202 | | |
5203 | 8.85k | if (!writeBuffer(aWriter.getLine())) |
5204 | 0 | nObject = 0; |
5205 | | |
5206 | 8.85k | return nObject; |
5207 | 8.85k | } |
5208 | | |
5209 | | // Part of this function may be shared with method appendDest. |
5210 | | sal_Int32 PDFWriterImpl::emitNamedDestinations() |
5211 | 8.85k | { |
5212 | 8.85k | sal_Int32 nCount = m_aNamedDests.size(); |
5213 | 8.85k | if( nCount <= 0 ) |
5214 | 8.85k | return 0;//define internal error |
5215 | | |
5216 | | //get the object number for all the destinations |
5217 | 0 | sal_Int32 nObject = createObject(); |
5218 | |
|
5219 | 0 | if( updateObject( nObject ) ) |
5220 | 0 | { |
5221 | | //emit the dictionary |
5222 | 0 | OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE ); |
5223 | 0 | aLine.append( nObject ); |
5224 | 0 | aLine.append( " 0 obj\n" |
5225 | 0 | "<<" ); |
5226 | |
|
5227 | 0 | sal_Int32 nDestID; |
5228 | 0 | for( nDestID = 0; nDestID < nCount; nDestID++ ) |
5229 | 0 | { |
5230 | 0 | const PDFNamedDest& rDest = m_aNamedDests[ nDestID ]; |
5231 | | // In order to correctly function both under an Internet browser and |
5232 | | // directly with a reader (provided the reader has the feature) we |
5233 | | // need to set the name of the destination the same way it will be encoded |
5234 | | // in an Internet link |
5235 | 0 | INetURLObject aLocalURL( u"http://ahost.ax" ); //dummy location, won't be used |
5236 | 0 | aLocalURL.SetMark( rDest.m_aDestName ); |
5237 | |
|
5238 | 0 | const OUString aName = aLocalURL.GetMark( INetURLObject::DecodeMechanism::NONE ); //same coding as |
5239 | | // in link creation ( see PDFWriterImpl::emitLinkAnnotations ) |
5240 | 0 | const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ]; |
5241 | |
|
5242 | 0 | aLine.append( '/' ); |
5243 | 0 | appendDestinationName( aName, aLine ); // this conversion must be done when forming the link to target ( see in emitCatalog ) |
5244 | 0 | aLine.append( '[' ); // the '[' can be emitted immediately, because the appendDestinationName function |
5245 | | //maps the preceding character properly |
5246 | 0 | aLine.append( rDestPage.m_nPageObject ); |
5247 | 0 | aLine.append( " 0 R" ); |
5248 | |
|
5249 | 0 | switch( rDest.m_eType ) |
5250 | 0 | { |
5251 | 0 | case PDFWriter::DestAreaType::XYZ: |
5252 | 0 | default: |
5253 | 0 | aLine.append( "/XYZ " ); |
5254 | 0 | appendFixedInt( rDest.m_aRect.Left(), aLine ); |
5255 | 0 | aLine.append( ' ' ); |
5256 | 0 | appendFixedInt( rDest.m_aRect.Bottom(), aLine ); |
5257 | 0 | aLine.append( " 0" ); |
5258 | 0 | break; |
5259 | 0 | case PDFWriter::DestAreaType::FitRectangle: |
5260 | 0 | aLine.append( "/FitR " ); |
5261 | 0 | appendFixedInt( rDest.m_aRect.Left(), aLine ); |
5262 | 0 | aLine.append( ' ' ); |
5263 | 0 | appendFixedInt( rDest.m_aRect.Top(), aLine ); |
5264 | 0 | aLine.append( ' ' ); |
5265 | 0 | appendFixedInt( rDest.m_aRect.Right(), aLine ); |
5266 | 0 | aLine.append( ' ' ); |
5267 | 0 | appendFixedInt( rDest.m_aRect.Bottom(), aLine ); |
5268 | 0 | break; |
5269 | 0 | } |
5270 | 0 | aLine.append( "]\n" ); |
5271 | 0 | } |
5272 | | |
5273 | | //close |
5274 | 0 | aLine.append( ">>\nendobj\n\n" ); |
5275 | 0 | if( ! writeBuffer( aLine ) ) |
5276 | 0 | nObject = 0; |
5277 | 0 | } |
5278 | 0 | else |
5279 | 0 | nObject = 0; |
5280 | | |
5281 | 0 | return nObject; |
5282 | 0 | } |
5283 | | |
5284 | | // emits the output intent dictionary |
5285 | | sal_Int32 PDFWriterImpl::emitOutputIntent() |
5286 | 8.85k | { |
5287 | 8.85k | if (m_nPDFA_Version == 0) // not PDFA |
5288 | 8.85k | return 0; |
5289 | | |
5290 | | //emit the sRGB standard profile, in ICC format, in a stream, per IEC61966-2.1 |
5291 | | |
5292 | 0 | OStringBuffer aLine( PDFWRITER_IMPL_BUFFERSIZE ); |
5293 | 0 | COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor); |
5294 | 0 | sal_Int32 nICCObject = createObject(); |
5295 | 0 | sal_Int32 nStreamLengthObject = createObject(); |
5296 | |
|
5297 | 0 | aLine.append( nICCObject ); |
5298 | | // sRGB has 3 colors, hence /N 3 below (PDF 1.4 table 4.16) |
5299 | 0 | aLine.append( " 0 obj\n<</N 3/Length " ); |
5300 | 0 | aLine.append( nStreamLengthObject ); |
5301 | 0 | aLine.append( " 0 R" ); |
5302 | 0 | if (!g_bDebugDisableCompression) |
5303 | 0 | aLine.append( "/Filter/FlateDecode" ); |
5304 | 0 | aLine.append( ">>\nstream\n" ); |
5305 | 0 | if ( !updateObject( nICCObject ) ) return 0; |
5306 | 0 | if ( !writeBuffer( aLine ) ) return 0; |
5307 | | //get file position |
5308 | 0 | sal_uInt64 nBeginStreamPos = 0; |
5309 | 0 | if (osl::File::E_None != m_aFile.getPos(nBeginStreamPos)) |
5310 | 0 | return 0; |
5311 | 0 | beginCompression(); |
5312 | 0 | checkAndEnableStreamEncryption( nICCObject ); |
5313 | 0 | cmsHPROFILE hProfile = cmsCreate_sRGBProfile(); |
5314 | | //force ICC profile version 2.1 |
5315 | 0 | cmsSetProfileVersion(hProfile, 2.1); |
5316 | 0 | cmsUInt32Number nBytesNeeded = 0; |
5317 | 0 | cmsSaveProfileToMem(hProfile, nullptr, &nBytesNeeded); |
5318 | 0 | if (!nBytesNeeded) |
5319 | 0 | return 0; |
5320 | 0 | std::vector<unsigned char> aBuffer(nBytesNeeded); |
5321 | 0 | cmsSaveProfileToMem(hProfile, aBuffer.data(), &nBytesNeeded); |
5322 | 0 | cmsCloseProfile(hProfile); |
5323 | 0 | bool written = writeBufferBytes( aBuffer.data(), aBuffer.size() ); |
5324 | 0 | disableStreamEncryption(); |
5325 | 0 | endCompression(); |
5326 | |
|
5327 | 0 | sal_uInt64 nEndStreamPos = 0; |
5328 | 0 | if (m_aFile.getPos(nEndStreamPos) != osl::File::E_None) |
5329 | 0 | return 0; |
5330 | | |
5331 | 0 | if( !written ) |
5332 | 0 | return 0; |
5333 | 0 | if( ! writeBuffer( "\nendstream\nendobj\n\n" ) ) |
5334 | 0 | return 0 ; |
5335 | 0 | aLine.setLength( 0 ); |
5336 | | |
5337 | | //emit the stream length object |
5338 | 0 | if ( !updateObject( nStreamLengthObject ) ) return 0; |
5339 | 0 | aLine.setLength( 0 ); |
5340 | 0 | aLine.append( nStreamLengthObject ); |
5341 | 0 | aLine.append( " 0 obj\n" ); |
5342 | 0 | aLine.append( static_cast<sal_Int64>(nEndStreamPos-nBeginStreamPos) ); |
5343 | 0 | aLine.append( "\nendobj\n\n" ); |
5344 | 0 | if ( !writeBuffer( aLine ) ) return 0; |
5345 | 0 | aLine.setLength( 0 ); |
5346 | | |
5347 | | //emit the OutputIntent dictionary |
5348 | 0 | sal_Int32 nOIObject = createObject(); |
5349 | 0 | if ( !updateObject( nOIObject ) ) return 0; |
5350 | 0 | aLine.append( nOIObject ); |
5351 | 0 | aLine.append( " 0 obj\n" |
5352 | 0 | "<</Type/OutputIntent/S/GTS_PDFA1/OutputConditionIdentifier"); |
5353 | |
|
5354 | 0 | aWriter.writeLiteralEncrypt(std::string_view("sRGB IEC61966-2.1"), nOIObject); |
5355 | 0 | aLine.append("/DestOutputProfile "); |
5356 | 0 | aLine.append( nICCObject ); |
5357 | 0 | aLine.append( " 0 R>>\nendobj\n\n" ); |
5358 | 0 | if ( !writeBuffer( aLine ) ) return 0; |
5359 | | |
5360 | 0 | return nOIObject; |
5361 | 0 | } |
5362 | | |
5363 | | static void lcl_assignMeta(std::u16string_view aValue, OString& aMeta) |
5364 | 97.3k | { |
5365 | 97.3k | if (!aValue.empty()) |
5366 | 10.2k | { |
5367 | 10.2k | aMeta = OUStringToOString(aValue, RTL_TEXTENCODING_UTF8); |
5368 | 10.2k | } |
5369 | 97.3k | } |
5370 | | |
5371 | | static void lcl_assignMeta(const css::uno::Sequence<OUString>& rValues, std::vector<OString>& rMeta) |
5372 | 26.5k | { |
5373 | 26.5k | if (!rValues.hasElements()) |
5374 | 26.5k | return; |
5375 | | |
5376 | 0 | std::vector<OString> aNewMetaVector; |
5377 | 0 | aNewMetaVector.reserve(rValues.getLength()); |
5378 | |
|
5379 | 0 | for (const OUString& rValue : rValues) |
5380 | 0 | { |
5381 | 0 | aNewMetaVector.emplace_back( |
5382 | 0 | OUStringToOString(comphelper::string::encodeForXml(rValue), RTL_TEXTENCODING_UTF8)); |
5383 | 0 | } |
5384 | |
|
5385 | 0 | rMeta = std::move(aNewMetaVector); |
5386 | 0 | } |
5387 | | |
5388 | | // emits the document metadata |
5389 | | // Since in PDF 1.4 |
5390 | | sal_Int32 PDFWriterImpl::emitDocumentMetadata() |
5391 | 8.85k | { |
5392 | 8.85k | if (m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4) |
5393 | 0 | return 0; |
5394 | | |
5395 | | //get the object number for all the destinations |
5396 | 8.85k | sal_Int32 nObject = createObject(); |
5397 | | |
5398 | 8.85k | if (!updateObject(nObject)) |
5399 | 0 | return 0; |
5400 | | |
5401 | 8.85k | pdf::XmpMetadata aMetadata; |
5402 | | |
5403 | 8.85k | if (m_nPDFA_Version > 0) |
5404 | 0 | aMetadata.mnPDF_A = m_nPDFA_Version; |
5405 | | |
5406 | 8.85k | if (m_bIsPDF_UA) |
5407 | 0 | { |
5408 | | // If PDF 2.0+ we need to use PDF/UA-2 otherwise PDF/UA-1 |
5409 | 0 | aMetadata.mnPDF_UA = (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0) ? 2 : 1; |
5410 | 0 | } |
5411 | | |
5412 | 8.85k | lcl_assignMeta(m_aContext.DocumentInfo.Title, aMetadata.msTitle); |
5413 | 8.85k | lcl_assignMeta(m_aContext.DocumentInfo.Author, aMetadata.msAuthor); |
5414 | 8.85k | lcl_assignMeta(m_aContext.DocumentInfo.Subject, aMetadata.msSubject); |
5415 | 8.85k | lcl_assignMeta(m_aContext.DocumentInfo.Producer, aMetadata.msProducer); |
5416 | 8.85k | aMetadata.msPDFVersion = getPDFVersionStr(m_aContext.Version); |
5417 | 8.85k | if (m_nPDFA_Version == 4) |
5418 | 0 | { |
5419 | | // if we have embedded files we need to use conformance level "F" |
5420 | 0 | aMetadata.msConformance = m_aEmbeddedFiles.empty() ? ""_ostr : "F"_ostr; |
5421 | 0 | } |
5422 | 8.85k | else |
5423 | 8.85k | { |
5424 | 8.85k | aMetadata.msConformance = "B"_ostr; |
5425 | 8.85k | } |
5426 | 8.85k | lcl_assignMeta(m_aContext.DocumentInfo.Keywords, aMetadata.msKeywords); |
5427 | 8.85k | lcl_assignMeta(m_aContext.DocumentInfo.Contributor, aMetadata.maContributor); |
5428 | 8.85k | lcl_assignMeta(m_aContext.DocumentInfo.Coverage, aMetadata.msCoverage); |
5429 | 8.85k | lcl_assignMeta(m_aContext.DocumentInfo.Identifier, aMetadata.msIdentifier); |
5430 | 8.85k | lcl_assignMeta(m_aContext.DocumentInfo.Publisher, aMetadata.maPublisher); |
5431 | 8.85k | lcl_assignMeta(m_aContext.DocumentInfo.Relation, aMetadata.maRelation); |
5432 | 8.85k | lcl_assignMeta(m_aContext.DocumentInfo.Rights, aMetadata.msRights); |
5433 | 8.85k | lcl_assignMeta(m_aContext.DocumentInfo.Source, aMetadata.msSource); |
5434 | 8.85k | lcl_assignMeta(m_aContext.DocumentInfo.Type, aMetadata.msType); |
5435 | 8.85k | lcl_assignMeta(m_aContext.DocumentInfo.Creator, aMetadata.m_sCreatorTool); |
5436 | 8.85k | aMetadata.m_sCreateDate = m_aCreationMetaDateString; |
5437 | | |
5438 | 8.85k | bool const bEncryptMetadata{m_pPDFEncryptor && m_pPDFEncryptor->isMetadataEncrypted()}; |
5439 | | |
5440 | 8.85k | { |
5441 | 8.85k | sal_uInt64 const nLength{bEncryptMetadata |
5442 | 8.85k | ? calculateStreamSize(aMetadata.getSize()) |
5443 | 8.85k | : aMetadata.getSize()}; |
5444 | 8.85k | COSWriter aWriter; |
5445 | 8.85k | aWriter.startObject(nObject); |
5446 | 8.85k | aWriter.startDict(); |
5447 | 8.85k | aWriter.write("/Type", "/Metadata"); |
5448 | 8.85k | aWriter.write("/Subtype", "/XML"); |
5449 | 8.85k | aWriter.write("/Length", sal_Int32(nLength)); |
5450 | 8.85k | aWriter.endDict(); |
5451 | 8.85k | aWriter.startStream(); |
5452 | 8.85k | if (!writeBuffer(aWriter.getLine())) |
5453 | 0 | return 0; |
5454 | 8.85k | } |
5455 | | |
5456 | | //emit the stream |
5457 | 8.85k | if (bEncryptMetadata) |
5458 | 0 | checkAndEnableStreamEncryption(nObject); |
5459 | | |
5460 | 8.85k | if (!writeBufferBytes(aMetadata.getData(), aMetadata.getSize())) |
5461 | 0 | return 0; |
5462 | | |
5463 | 8.85k | if (bEncryptMetadata) |
5464 | 0 | disableStreamEncryption(); |
5465 | | |
5466 | 8.85k | { |
5467 | 8.85k | COSWriter aWriter; |
5468 | 8.85k | aWriter.endStream(); |
5469 | 8.85k | aWriter.endObject(); |
5470 | 8.85k | if (!writeBuffer(aWriter.getLine())) |
5471 | 0 | return 0; |
5472 | 8.85k | } |
5473 | | |
5474 | 8.85k | return nObject; |
5475 | 8.85k | } |
5476 | | |
5477 | | sal_Int32 PDFWriterImpl::emitEncrypt() |
5478 | 0 | { |
5479 | | //emit the security information |
5480 | | //must be emitted as indirect dictionary object, since |
5481 | | //Acrobat Reader 5 works only with this kind of implementation |
5482 | |
|
5483 | 0 | sal_Int32 nObject = createObject(); |
5484 | |
|
5485 | 0 | if (updateObject(nObject)) |
5486 | 0 | { |
5487 | 0 | PDFEncryptionProperties& rProperties = m_aContext.Encryption; |
5488 | 0 | COSWriter aWriter(m_aContext.Encryption.getParams(), m_pPDFEncryptor); |
5489 | 0 | aWriter.startObject(nObject); |
5490 | 0 | aWriter.startDict(); |
5491 | 0 | aWriter.write("/Filter", "/Standard"); |
5492 | 0 | aWriter.write("/V", m_pPDFEncryptor->getVersion()); |
5493 | 0 | aWriter.write("/Length", m_pPDFEncryptor->getKeyLength() * 8); |
5494 | 0 | aWriter.write("/R", m_pPDFEncryptor->getRevision()); |
5495 | |
|
5496 | 0 | if (m_pPDFEncryptor->getVersion() == 5 && m_pPDFEncryptor->getRevision() == 6) |
5497 | 0 | { |
5498 | | // emit the owner password, must not be encrypted |
5499 | 0 | aWriter.writeHexArray("/U", rProperties.UValue.data(), rProperties.UValue.size()); |
5500 | 0 | aWriter.writeHexArray("/UE", rProperties.UE.data(), rProperties.UE.size()); |
5501 | 0 | aWriter.writeHexArray("/O", rProperties.OValue.data(), rProperties.OValue.size()); |
5502 | 0 | aWriter.writeHexArray("/OE", rProperties.OE.data(), rProperties.OE.size()); |
5503 | | |
5504 | | // Encrypted perms |
5505 | 0 | std::vector<sal_uInt8> aEncryptedPermissions = m_pPDFEncryptor->getEncryptedAccessPermissions(rProperties.EncryptionKey); |
5506 | 0 | aWriter.writeHexArray("/Perms", aEncryptedPermissions.data(), aEncryptedPermissions.size()); |
5507 | | |
5508 | | // Write content filter stuff - to select we want AESv3 256bit |
5509 | 0 | aWriter.write("/CF", "<</StdCF <</CFM /AESV3 /Length 256>>>>"); |
5510 | 0 | aWriter.write("/StmF", "/StdCF"); |
5511 | 0 | aWriter.write("/StrF", "/StdCF"); |
5512 | | // Encrypt metadata. Default is true. Relevant for Revision 6+ |
5513 | 0 | if (!m_pPDFEncryptor->isMetadataEncrypted()) |
5514 | 0 | aWriter.write("/EncryptMetadata", " false "); |
5515 | 0 | } |
5516 | 0 | else |
5517 | 0 | { |
5518 | | // emit the owner password, must not be encrypted |
5519 | 0 | aWriter.writeHexArray("/U", rProperties.UValue.data(), rProperties.UValue.size()); |
5520 | 0 | aWriter.writeHexArray("/O", rProperties.OValue.data(), rProperties.OValue.size()); |
5521 | 0 | } |
5522 | 0 | aWriter.write("/P", m_pPDFEncryptor->getAccessPermissions()); |
5523 | 0 | aWriter.endDict(); |
5524 | 0 | aWriter.endObject(); |
5525 | |
|
5526 | 0 | if (!writeBuffer(aWriter.getLine())) |
5527 | 0 | nObject = 0; |
5528 | 0 | } |
5529 | 0 | else |
5530 | 0 | nObject = 0; |
5531 | |
|
5532 | 0 | return nObject; |
5533 | 0 | } |
5534 | | |
5535 | | bool PDFWriterImpl::emitTrailer() |
5536 | 8.85k | { |
5537 | | // emit doc info |
5538 | 8.85k | sal_Int32 nDocInfoObject = 0; |
5539 | | // Deprecated in PDF 2.0, but still allowed if /PieceInfo is written (which we don't support) |
5540 | 8.85k | if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0) |
5541 | 8.85k | nDocInfoObject = emitInfoDict(); |
5542 | | |
5543 | 8.85k | sal_Int32 nSecObject = 0; |
5544 | | |
5545 | 8.85k | if (m_aContext.Encryption.canEncrypt()) |
5546 | 0 | { |
5547 | 0 | nSecObject = emitEncrypt(); |
5548 | 0 | } |
5549 | | // a cross-reference table cannot address an object inside an object stream |
5550 | 8.85k | const bool bXRefStream = std::ranges::any_of(m_aObjects, &ObjectLocation::isCompressed); |
5551 | | |
5552 | 8.85k | sal_uInt64 nXRefOffset = 0; |
5553 | 8.85k | OStringBuffer aLine; |
5554 | 8.85k | if (!bXRefStream) |
5555 | 8.85k | { |
5556 | | // emit the xref table, remembering where it starts |
5557 | 8.85k | if (osl::File::E_None != m_aFile.getPos(nXRefOffset)) |
5558 | 0 | return false; |
5559 | 8.85k | if (!writeBuffer("xref\n")) |
5560 | 0 | return false; |
5561 | | |
5562 | 8.85k | aLine.append("0 "); |
5563 | 8.85k | aLine.append(static_cast<sal_Int32>(m_aObjects.size() + 1)); |
5564 | 8.85k | aLine.append("\n"); |
5565 | 8.85k | aLine.append("0000000000 65535 f \n"); |
5566 | 8.85k | if (!writeBuffer(aLine)) |
5567 | 0 | return false; |
5568 | | |
5569 | 8.85k | for (const auto& rLocation : m_aObjects) |
5570 | 394k | { |
5571 | 394k | if (rLocation.m_nOffset == SAL_MAX_UINT64) |
5572 | 0 | { |
5573 | 0 | SAL_WARN("vcl.pdfwriter", "object was created but never written"); |
5574 | 0 | if (!writeBuffer("0000000000 65535 f \n")) |
5575 | 0 | return false; |
5576 | 0 | continue; |
5577 | 0 | } |
5578 | 394k | auto aOffset(OString::number(rLocation.m_nOffset)); |
5579 | 394k | aLine = RepeatedChar('0', 10 - aOffset.length) + aOffset + " 00000 n \n"; |
5580 | 394k | SAL_WARN_IF(aLine.getLength() != 20, "vcl.pdfwriter", "invalid xref entry"); |
5581 | 394k | if (!writeBuffer(aLine)) |
5582 | 0 | return false; |
5583 | 394k | } |
5584 | 8.85k | } |
5585 | | |
5586 | | // prepare document checksum |
5587 | 8.85k | OStringBuffer aDocChecksum( 2*RTL_DIGEST_LENGTH_MD5+1 ); |
5588 | 8.85k | ::std::vector<unsigned char> const nMD5Sum(m_DocDigest.finalize()); |
5589 | 8.85k | for (sal_uInt8 i : nMD5Sum) |
5590 | 141k | COSWriter::appendHex( i, aDocChecksum ); |
5591 | | // document id set in setDocInfo method |
5592 | | // emit trailer |
5593 | 8.85k | aLine.setLength( 0 ); |
5594 | 8.85k | aLine.append( "/Root " ); |
5595 | 8.85k | aLine.append( m_nCatalogObject ); |
5596 | 8.85k | aLine.append( " 0 R\n" ); |
5597 | 8.85k | if( nSecObject ) |
5598 | 0 | { |
5599 | 0 | aLine.append( "/Encrypt "); |
5600 | 0 | aLine.append( nSecObject ); |
5601 | 0 | aLine.append( " 0 R\n" ); |
5602 | 0 | } |
5603 | 8.85k | if( nDocInfoObject ) |
5604 | 8.85k | { |
5605 | 8.85k | aLine.append( "/Info " ); |
5606 | 8.85k | aLine.append( nDocInfoObject ); |
5607 | 8.85k | aLine.append( " 0 R\n" ); |
5608 | 8.85k | } |
5609 | 8.85k | if( ! m_aContext.Encryption.DocumentIdentifier.empty() ) |
5610 | 8.85k | { |
5611 | 8.85k | aLine.append( "/ID [ <" ); |
5612 | 8.85k | for (auto const& item : m_aContext.Encryption.DocumentIdentifier) |
5613 | 141k | { |
5614 | 141k | COSWriter::appendHex( sal_Int8(item), aLine ); |
5615 | 141k | } |
5616 | 8.85k | aLine.append( ">\n" |
5617 | 8.85k | "<" ); |
5618 | 8.85k | for (auto const& item : m_aContext.Encryption.DocumentIdentifier) |
5619 | 141k | { |
5620 | 141k | COSWriter::appendHex( sal_Int8(item), aLine ); |
5621 | 141k | } |
5622 | 8.85k | aLine.append( "> ]\n" ); |
5623 | 8.85k | } |
5624 | | |
5625 | | // Writes the /DocChecksum - hash of the PDF stream |
5626 | | // This entry is not defined in the standard, so don't write it if we |
5627 | | // are using PDF/UA or PDF/A as the compliance checkers will complain. |
5628 | | // Actually we shouldn't write it at all... |
5629 | 8.85k | if (!aDocChecksum.isEmpty() && !m_bIsPDF_UA && m_nPDFA_Version == 0) |
5630 | 8.85k | { |
5631 | 8.85k | aLine.append( "/DocChecksum /" ); |
5632 | 8.85k | aLine.append( aDocChecksum ); |
5633 | 8.85k | aLine.append( "\n" ); |
5634 | 8.85k | } |
5635 | | |
5636 | | // Writes the /AdditionalStreams - writes the embedded / attached files into the PDF |
5637 | | // This entry is not defined in the standard, so don't write it if we |
5638 | | // are using PDF/UA or PDF/A as the compliance checkers will complain. |
5639 | 8.85k | if (!m_aDocumentAttachedFiles.empty() && !m_bIsPDF_UA && m_nPDFA_Version == 0) |
5640 | 0 | { |
5641 | 0 | aLine.append( "/AdditionalStreams [" ); |
5642 | 0 | for (auto const& rAttachedFile : m_aDocumentAttachedFiles) |
5643 | 0 | { |
5644 | 0 | aLine.append( "/" ); |
5645 | 0 | COSWriter::appendName(rAttachedFile.maMimeType, aLine); |
5646 | 0 | aLine.append(" "); |
5647 | 0 | appendObjectReference(rAttachedFile.mnEmbeddedFileObjectId, aLine); |
5648 | 0 | aLine.append("\n"); |
5649 | 0 | } |
5650 | 0 | aLine.append( "]\n" ); |
5651 | 0 | } |
5652 | | |
5653 | | // After calling m_DocDigest.finalize(), we need to initialize the hash again, |
5654 | | // otherwise, m_DocDigest.update() inside writeBuffer will fail with |
5655 | | // Assertion failure: rv == SECSuccess, at sechash.c:140 |
5656 | 8.85k | m_DocDigest.initialize(); |
5657 | | |
5658 | 8.85k | if (bXRefStream) |
5659 | 0 | return emitXRefStream(aLine); |
5660 | | |
5661 | 8.85k | return writeBuffer("trailer\n<</Size " + OString::number(m_aObjects.size() + 1)) |
5662 | 8.85k | && writeBuffer(aLine) |
5663 | 8.85k | && writeBuffer(">>\nstartxref\n" + OString::number(static_cast<sal_Int64>(nXRefOffset)) |
5664 | 8.85k | + "\n%%EOF\n"); |
5665 | 8.85k | } |
5666 | | |
5667 | | bool PDFWriterImpl::emitXRefStream(std::string_view aTrailerEntries) |
5668 | 0 | { |
5669 | | // ISO 32000-2 7.5.8.3 requires an entry for the stream itself |
5670 | 0 | const sal_Int32 nXRefObject = createObject(); |
5671 | 0 | sal_uInt64 nXRefOffset = 0; |
5672 | 0 | if (osl::File::E_None != m_aFile.getPos(nXRefOffset)) |
5673 | 0 | return false; |
5674 | 0 | m_aObjects[nXRefObject - 1].m_nOffset = nXRefOffset; |
5675 | | |
5676 | | // the second field is a byte offset or an object stream number, so measure both |
5677 | 0 | const auto aSecondField = [](const ObjectLocation& rLocation) -> sal_uInt64 { |
5678 | 0 | if (rLocation.isCompressed()) |
5679 | 0 | return rLocation.m_nObjectStream; |
5680 | | // an unwritten object gets a free entry, which has no offset |
5681 | 0 | return rLocation.m_nOffset == SAL_MAX_UINT64 ? 0 : rLocation.m_nOffset; |
5682 | 0 | }; |
5683 | 0 | sal_Int32 nOffsetBytes = 1; |
5684 | 0 | for (sal_uInt64 nWidest = std::ranges::max(m_aObjects | std::views::transform(aSecondField)); |
5685 | 0 | nWidest > 0xff; nWidest >>= 8) |
5686 | 0 | ++nOffsetBytes; |
5687 | |
|
5688 | 0 | OStringBuffer aEntries(static_cast<sal_Int32>((3 + nOffsetBytes) * (m_aObjects.size() + 1))); |
5689 | 0 | auto appendEntry |
5690 | 0 | = [&aEntries, nOffsetBytes](sal_uInt8 nType, sal_uInt64 nSecond, sal_uInt16 nThird) { |
5691 | 0 | aEntries.append(char(nType)); |
5692 | 0 | for (sal_Int32 nByte = nOffsetBytes; nByte-- > 0;) |
5693 | 0 | aEntries.append(char(nSecond >> (8 * nByte))); |
5694 | 0 | aEntries.append(char(nThird >> 8)); |
5695 | 0 | aEntries.append(char(nThird)); |
5696 | 0 | }; |
5697 | | |
5698 | | // object 0 heads the list of free objects, as it does in a table |
5699 | 0 | appendEntry(0, 0, 0xffff); |
5700 | 0 | for (const auto& rLocation : m_aObjects) |
5701 | 0 | { |
5702 | 0 | if (rLocation.isCompressed()) |
5703 | 0 | appendEntry(2, rLocation.m_nObjectStream, rLocation.m_nIndex); |
5704 | 0 | else if (rLocation.m_nOffset == SAL_MAX_UINT64) |
5705 | 0 | appendEntry(0, 0, 0xffff); |
5706 | 0 | else |
5707 | 0 | appendEntry(1, rLocation.m_nOffset, 0); |
5708 | 0 | } |
5709 | |
|
5710 | 0 | const std::vector<sal_uInt8> aStream(deflateBuffer(aEntries)); |
5711 | 0 | if (aStream.empty()) |
5712 | 0 | return false; |
5713 | | |
5714 | 0 | OStringBuffer aLine(512); |
5715 | | // 7.5.8.2: every entry of Table 17 shall be direct, and a reader cannot resolve an |
5716 | | // indirect /Length before it has read this stream |
5717 | 0 | aLine.append(OString::number(nXRefObject) + " 0 obj\n<</Type/XRef/Size " |
5718 | 0 | + OString::number(static_cast<sal_Int32>(m_aObjects.size() + 1)) + "/W[1 " |
5719 | 0 | + OString::number(nOffsetBytes) + " 2]/Length " |
5720 | 0 | + OString::number(static_cast<sal_Int32>(aStream.size()))); |
5721 | 0 | if (!g_bDebugDisableCompression) |
5722 | 0 | aLine.append("/Filter/FlateDecode"); |
5723 | 0 | aLine.append(aTrailerEntries); |
5724 | 0 | aLine.append(">>\nstream\n"); |
5725 | | |
5726 | | // 7.5.8.2 forbids encrypting this stream, so it goes out as built |
5727 | 0 | return writeBuffer(aLine) && writeBufferBytes(aStream.data(), aStream.size()) |
5728 | 0 | && writeBuffer("\nendstream\nendobj\n\nstartxref\n" |
5729 | 0 | + OString::number(static_cast<sal_Int64>(nXRefOffset)) + "\n%%EOF\n"); |
5730 | 0 | } |
5731 | | |
5732 | | namespace { |
5733 | | |
5734 | | struct AnnotationSortEntry |
5735 | | { |
5736 | | sal_Int32 nTabOrder; |
5737 | | sal_Int32 nObject; |
5738 | | sal_Int32 nWidgetIndex; |
5739 | | |
5740 | | AnnotationSortEntry( sal_Int32 nTab, sal_Int32 nObj, sal_Int32 nI ) : |
5741 | 0 | nTabOrder( nTab ), |
5742 | 0 | nObject( nObj ), |
5743 | 0 | nWidgetIndex( nI ) |
5744 | 0 | {} |
5745 | | }; |
5746 | | |
5747 | | struct AnnotSortContainer |
5748 | | { |
5749 | | o3tl::sorted_vector< sal_Int32 > aObjects; |
5750 | | std::vector< AnnotationSortEntry > aSortedAnnots; |
5751 | | }; |
5752 | | |
5753 | | struct AnnotSorterLess |
5754 | | { |
5755 | | std::vector<PDFWidget>& m_rWidgets; |
5756 | | |
5757 | 0 | explicit AnnotSorterLess( std::vector<PDFWidget>& rWidgets ) : m_rWidgets( rWidgets ) {} |
5758 | | |
5759 | | bool operator()( const AnnotationSortEntry& rLeft, const AnnotationSortEntry& rRight ) |
5760 | 0 | { |
5761 | 0 | if( rLeft.nTabOrder < rRight.nTabOrder ) |
5762 | 0 | return true; |
5763 | 0 | if( rRight.nTabOrder < rLeft.nTabOrder ) |
5764 | 0 | return false; |
5765 | 0 | if( rLeft.nWidgetIndex < 0 && rRight.nWidgetIndex < 0 ) |
5766 | 0 | return false; |
5767 | 0 | if( rRight.nWidgetIndex < 0 ) |
5768 | 0 | return true; |
5769 | 0 | if( rLeft.nWidgetIndex < 0 ) |
5770 | 0 | return false; |
5771 | | // remember: widget rects are in PDF coordinates, so they are ordered down up |
5772 | 0 | if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() > |
5773 | 0 | m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() ) |
5774 | 0 | return true; |
5775 | 0 | if( m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() > |
5776 | 0 | m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() ) |
5777 | 0 | return false; |
5778 | 0 | if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Left() < |
5779 | 0 | m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Left() ) |
5780 | 0 | return true; |
5781 | 0 | return false; |
5782 | 0 | } |
5783 | | }; |
5784 | | |
5785 | | } |
5786 | | |
5787 | | void PDFWriterImpl::sortWidgets() |
5788 | 8.85k | { |
5789 | | // sort widget annotations on each page as per their |
5790 | | // TabOrder attribute |
5791 | 8.85k | std::unordered_map< sal_Int32, AnnotSortContainer > sorted; |
5792 | 8.85k | int nWidgets = m_aWidgets.size(); |
5793 | 8.85k | for( int nW = 0; nW < nWidgets; nW++ ) |
5794 | 0 | { |
5795 | 0 | const PDFWidget& rWidget = m_aWidgets[nW]; |
5796 | 0 | if( rWidget.m_nPage >= 0 ) |
5797 | 0 | { |
5798 | 0 | AnnotSortContainer& rCont = sorted[ rWidget.m_nPage ]; |
5799 | | // optimize vector allocation |
5800 | 0 | if( rCont.aSortedAnnots.empty() ) |
5801 | 0 | rCont.aSortedAnnots.reserve( m_aPages[ rWidget.m_nPage ].m_aAnnotations.size() ); |
5802 | | // insert widget to tab sorter |
5803 | | // RadioButtons are not page annotations, only their individual check boxes are |
5804 | 0 | if( rWidget.m_eType != PDFWriter::RadioButton ) |
5805 | 0 | { |
5806 | 0 | rCont.aObjects.insert( rWidget.m_nObject ); |
5807 | 0 | rCont.aSortedAnnots.emplace_back( rWidget.m_nTabOrder, rWidget.m_nObject, nW ); |
5808 | 0 | } |
5809 | 0 | } |
5810 | 0 | } |
5811 | 8.85k | for (auto & item : sorted) |
5812 | 0 | { |
5813 | | // append entries for non widget annotations |
5814 | 0 | PDFPage& rPage = m_aPages[ item.first ]; |
5815 | 0 | unsigned int nAnnots = rPage.m_aAnnotations.size(); |
5816 | 0 | for( unsigned int nA = 0; nA < nAnnots; nA++ ) |
5817 | 0 | if( item.second.aObjects.find( rPage.m_aAnnotations[nA] ) == item.second.aObjects.end()) |
5818 | 0 | item.second.aSortedAnnots.emplace_back( 10000, rPage.m_aAnnotations[nA], -1 ); |
5819 | |
|
5820 | 0 | AnnotSorterLess aLess( m_aWidgets ); |
5821 | 0 | std::stable_sort( item.second.aSortedAnnots.begin(), item.second.aSortedAnnots.end(), aLess ); |
5822 | | // sanity check |
5823 | 0 | if( item.second.aSortedAnnots.size() == nAnnots) |
5824 | 0 | { |
5825 | 0 | for( unsigned int nA = 0; nA < nAnnots; nA++ ) |
5826 | 0 | rPage.m_aAnnotations[nA] = item.second.aSortedAnnots[nA].nObject; |
5827 | 0 | } |
5828 | 0 | else |
5829 | 0 | { |
5830 | 0 | SAL_WARN( "vcl.pdfwriter", "wrong number of sorted annotations" ); |
5831 | 0 | SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::sortWidgets(): wrong number of sorted assertions " |
5832 | 0 | "on page nr " << item.first << ", " << |
5833 | 0 | item.second.aSortedAnnots.size() << " sorted and " << |
5834 | 0 | nAnnots << " unsorted"); |
5835 | 0 | } |
5836 | 0 | } |
5837 | | |
5838 | | // FIXME: implement tab order in structure tree for PDF 1.5 |
5839 | 8.85k | } |
5840 | | |
5841 | | bool PDFWriterImpl::emit() |
5842 | 8.85k | { |
5843 | 8.85k | endPage(); |
5844 | | |
5845 | | // resort structure tree and annotations if necessary |
5846 | | // needed for widget tab order |
5847 | 8.85k | sortWidgets(); |
5848 | | |
5849 | | #if HAVE_FEATURE_NSS |
5850 | | if( m_aContext.SignPDF ) |
5851 | | { |
5852 | | // sign the document |
5853 | | PDFWriter::SignatureWidget aSignature; |
5854 | | aSignature.Name = "Signature1"; |
5855 | | createControl( aSignature, 0 ); |
5856 | | } |
5857 | | #endif |
5858 | | |
5859 | | // emit catalog |
5860 | 8.85k | if (!emitCatalog()) |
5861 | 0 | return false; |
5862 | | |
5863 | | #if HAVE_FEATURE_NSS |
5864 | | if (m_nSignatureObject != -1) // if document is signed, emit sigdict |
5865 | | { |
5866 | | if( !emitSignature() ) |
5867 | | { |
5868 | | m_aErrors.insert( PDFWriter::Error_Signature_Failed ); |
5869 | | return false; |
5870 | | } |
5871 | | } |
5872 | | #endif |
5873 | | |
5874 | | // emit trailer |
5875 | 8.85k | if (!emitTrailer()) |
5876 | 0 | return false; |
5877 | | |
5878 | | #if HAVE_FEATURE_NSS |
5879 | | if (m_nSignatureObject != -1) // finalize the signature |
5880 | | { |
5881 | | if( !finalizeSignature() ) |
5882 | | { |
5883 | | m_aErrors.insert( PDFWriter::Error_Signature_Failed ); |
5884 | | return false; |
5885 | | } |
5886 | | } |
5887 | | #endif |
5888 | | |
5889 | 8.85k | m_aFile.close(); |
5890 | 8.85k | m_bOpen = false; |
5891 | | |
5892 | 8.85k | return true; |
5893 | 8.85k | } |
5894 | | |
5895 | | |
5896 | | sal_Int32 PDFWriterImpl::getSystemFont( const vcl::Font& i_rFont ) |
5897 | 0 | { |
5898 | 0 | auto popIt = ScopedPush(); |
5899 | |
|
5900 | 0 | SetFont( i_rFont ); |
5901 | |
|
5902 | 0 | const LogicalFontInstance* pFontInstance = GetFontInstance(); |
5903 | 0 | const vcl::font::PhysicalFontFace* pFace = pFontInstance->GetFontFace(); |
5904 | 0 | sal_Int32 nFontID = 0; |
5905 | 0 | auto it = m_aSystemFonts.find( pFace ); |
5906 | 0 | if( it != m_aSystemFonts.end() ) |
5907 | 0 | nFontID = it->second.m_nNormalFontID; |
5908 | 0 | else |
5909 | 0 | { |
5910 | 0 | nFontID = m_nNextFID++; |
5911 | 0 | m_aSystemFonts[ pFace ] = EmbedFont(); |
5912 | 0 | m_aSystemFonts[ pFace ].m_pFontInstance = const_cast<LogicalFontInstance*>(pFontInstance); |
5913 | 0 | m_aSystemFonts[ pFace ].m_nNormalFontID = nFontID; |
5914 | 0 | } |
5915 | |
|
5916 | 0 | return nFontID; |
5917 | 0 | } |
5918 | | |
5919 | | void PDFWriterImpl::registerSimpleGlyph(const sal_GlyphId nFontGlyphId, |
5920 | | const vcl::font::PhysicalFontFace* pFace, |
5921 | | const LogicalFontInstance* pFont, |
5922 | | const std::vector<sal_Ucs>& rCodeUnits, |
5923 | | sal_Int32 nGlyphWidth, |
5924 | | sal_uInt8& nMappedGlyph, |
5925 | | sal_Int32& nMappedFontObject) |
5926 | 13.2M | { |
5927 | 13.2M | FontSubset& rSubset = m_aSubsets[ { pFace, pFont } ]; |
5928 | | |
5929 | | // search for font specific glyphID |
5930 | 13.2M | auto it = rSubset.m_aMapping.find( nFontGlyphId ); |
5931 | 13.2M | if( it != rSubset.m_aMapping.end() ) |
5932 | 12.7M | { |
5933 | 12.7M | nMappedFontObject = it->second.m_nFontID; |
5934 | 12.7M | nMappedGlyph = it->second.m_nSubsetGlyphID; |
5935 | 12.7M | } |
5936 | 489k | else |
5937 | 489k | { |
5938 | | // create new subset if necessary |
5939 | 489k | if( rSubset.m_aSubsets.empty() |
5940 | 472k | || (rSubset.m_aSubsets.back().m_aMapping.size() > 254) ) |
5941 | 16.3k | { |
5942 | 16.3k | rSubset.m_aSubsets.emplace_back( m_nNextFID++ ); |
5943 | 16.3k | } |
5944 | | |
5945 | | // copy font id |
5946 | 489k | nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID; |
5947 | | // create new glyph in subset |
5948 | 489k | sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(rSubset.m_aSubsets.back().m_aMapping.size()+1); |
5949 | 489k | nMappedGlyph = nNewId; |
5950 | | |
5951 | | // add new glyph to emitted font subset |
5952 | 489k | GlyphEmit& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[ nFontGlyphId ]; |
5953 | 489k | rNewGlyphEmit.setGlyphId( nNewId ); |
5954 | 489k | rNewGlyphEmit.setGlyphWidth(XUnits(pFace->UnitsPerEm(), nGlyphWidth)); |
5955 | 489k | for (const auto nCode : rCodeUnits) |
5956 | 487k | rNewGlyphEmit.addCode(nCode); |
5957 | | |
5958 | | // add new glyph to font mapping |
5959 | 489k | Glyph& rNewGlyph = rSubset.m_aMapping[ nFontGlyphId ]; |
5960 | 489k | rNewGlyph.m_nFontID = nMappedFontObject; |
5961 | 489k | rNewGlyph.m_nSubsetGlyphID = nNewId; |
5962 | 489k | } |
5963 | 13.2M | } |
5964 | | |
5965 | | void PDFWriterImpl::registerGlyph(const sal_GlyphId nFontGlyphId, |
5966 | | const vcl::font::PhysicalFontFace* pFace, |
5967 | | const LogicalFontInstance* pFont, |
5968 | | const std::vector<sal_Ucs>& rCodeUnits, sal_Int32 nGlyphWidth, |
5969 | | sal_uInt8& nMappedGlyph, sal_Int32& nMappedFontObject) |
5970 | 13.2M | { |
5971 | 13.2M | if (pFace->IsColorFont()) |
5972 | 0 | { |
5973 | | // Font has colors, check if this glyph has color layers or bitmap. |
5974 | 0 | tools::Rectangle aRect; |
5975 | 0 | auto aLayers = pFace->GetGlyphColorLayers(nFontGlyphId); |
5976 | 0 | auto aBitmap = pFace->GetGlyphColorBitmap(nFontGlyphId, aRect); |
5977 | 0 | if (!aLayers.empty() || !aBitmap.empty()) |
5978 | 0 | { |
5979 | 0 | auto& rSubset = m_aType3Fonts[pFace]; |
5980 | 0 | auto it = rSubset.m_aMapping.find(nFontGlyphId); |
5981 | 0 | if (it != rSubset.m_aMapping.end()) |
5982 | 0 | { |
5983 | 0 | nMappedFontObject = it->second.m_nFontID; |
5984 | 0 | nMappedGlyph = it->second.m_nSubsetGlyphID; |
5985 | 0 | } |
5986 | 0 | else |
5987 | 0 | { |
5988 | | // create new subset if necessary |
5989 | 0 | if (rSubset.m_aSubsets.empty() |
5990 | 0 | || (rSubset.m_aSubsets.back().m_aMapping.size() > 254)) |
5991 | 0 | { |
5992 | 0 | rSubset.m_aSubsets.emplace_back(m_nNextFID++); |
5993 | 0 | } |
5994 | | |
5995 | | // copy font id |
5996 | 0 | nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID; |
5997 | | // create new glyph in subset |
5998 | 0 | sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>( |
5999 | 0 | rSubset.m_aSubsets.back().m_aMapping.size() + 1); |
6000 | 0 | nMappedGlyph = nNewId; |
6001 | | |
6002 | | // add new glyph to emitted font subset |
6003 | 0 | auto& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[nFontGlyphId]; |
6004 | 0 | rNewGlyphEmit.setGlyphId(nNewId); |
6005 | 0 | rNewGlyphEmit.setGlyphWidth(nGlyphWidth); |
6006 | 0 | for (const auto nCode : rCodeUnits) |
6007 | 0 | rNewGlyphEmit.addCode(nCode); |
6008 | | |
6009 | | // add color layers to the glyphs |
6010 | 0 | if (!aLayers.empty()) |
6011 | 0 | { |
6012 | 0 | for (const auto& aLayer : aLayers) |
6013 | 0 | { |
6014 | 0 | sal_uInt8 nLayerGlyph; |
6015 | 0 | sal_Int32 nLayerFontID; |
6016 | 0 | registerSimpleGlyph(aLayer.nGlyphIndex, pFace, pFont, rCodeUnits, |
6017 | 0 | nGlyphWidth, nLayerGlyph, nLayerFontID); |
6018 | |
|
6019 | 0 | rNewGlyphEmit.addColorLayer( |
6020 | 0 | { nLayerFontID, nLayerGlyph, aLayer.nColorIndex }); |
6021 | 0 | } |
6022 | 0 | } |
6023 | 0 | else if (!aBitmap.empty()) |
6024 | 0 | rNewGlyphEmit.setColorBitmap(aBitmap, aRect); |
6025 | | |
6026 | | // add new glyph to font mapping |
6027 | 0 | Glyph& rNewGlyph = rSubset.m_aMapping[nFontGlyphId]; |
6028 | 0 | rNewGlyph.m_nFontID = nMappedFontObject; |
6029 | 0 | rNewGlyph.m_nSubsetGlyphID = nNewId; |
6030 | 0 | } |
6031 | 0 | return; |
6032 | 0 | } |
6033 | 0 | } |
6034 | | |
6035 | | // If we reach here then the glyph has no color layers. |
6036 | 13.2M | registerSimpleGlyph(nFontGlyphId, pFace, pFont, rCodeUnits, nGlyphWidth, nMappedGlyph, |
6037 | 13.2M | nMappedFontObject); |
6038 | 13.2M | } |
6039 | | |
6040 | | void PDFWriterImpl::drawRelief( SalLayout& rLayout, const OUString& rText, bool bTextLines ) |
6041 | 0 | { |
6042 | 0 | push( PushFlags::ALL ); |
6043 | |
|
6044 | 0 | FontRelief eRelief = m_aCurrentPDFState.m_aFont.GetRelief(); |
6045 | |
|
6046 | 0 | Color aTextColor = m_aCurrentPDFState.m_aFont.GetColor(); |
6047 | 0 | Color aTextLineColor = m_aCurrentPDFState.m_aTextLineColor; |
6048 | 0 | Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor; |
6049 | 0 | Color aReliefColor( COL_LIGHTGRAY ); |
6050 | 0 | if( aTextColor == COL_BLACK ) |
6051 | 0 | aTextColor = COL_WHITE; |
6052 | 0 | if( aTextLineColor == COL_BLACK ) |
6053 | 0 | aTextLineColor = COL_WHITE; |
6054 | 0 | if( aOverlineColor == COL_BLACK ) |
6055 | 0 | aOverlineColor = COL_WHITE; |
6056 | | // coverity[copy_paste_error : FALSE] - aReliefColor depending on aTextColor is correct |
6057 | 0 | if( aTextColor == COL_WHITE ) |
6058 | 0 | aReliefColor = COL_BLACK; |
6059 | |
|
6060 | 0 | Font aSetFont = m_aCurrentPDFState.m_aFont; |
6061 | 0 | aSetFont.SetRelief( FontRelief::NONE ); |
6062 | 0 | aSetFont.SetShadow( false ); |
6063 | |
|
6064 | 0 | aSetFont.SetColor( aReliefColor ); |
6065 | 0 | setTextLineColor( aReliefColor ); |
6066 | 0 | setOverlineColor( aReliefColor ); |
6067 | 0 | setFont( aSetFont ); |
6068 | 0 | tools::Long nOff = 1 + GetDPIX()/300; |
6069 | 0 | if( eRelief == FontRelief::Engraved ) |
6070 | 0 | nOff = -nOff; |
6071 | |
|
6072 | 0 | auto aPrevOffset = rLayout.DrawOffset(); |
6073 | 0 | rLayout.DrawOffset() |
6074 | 0 | += basegfx::B2DPoint{ static_cast<double>(nOff), static_cast<double>(nOff) }; |
6075 | 0 | updateGraphicsState(); |
6076 | 0 | drawLayout( rLayout, rText, bTextLines ); |
6077 | |
|
6078 | 0 | rLayout.DrawOffset() = aPrevOffset; |
6079 | 0 | setTextLineColor( aTextLineColor ); |
6080 | 0 | setOverlineColor( aOverlineColor ); |
6081 | 0 | aSetFont.SetColor( aTextColor ); |
6082 | 0 | setFont( aSetFont ); |
6083 | 0 | updateGraphicsState(); |
6084 | 0 | drawLayout( rLayout, rText, bTextLines ); |
6085 | | |
6086 | | // clean up the mess |
6087 | 0 | pop(); |
6088 | 0 | } |
6089 | | |
6090 | | void PDFWriterImpl::drawShadow( SalLayout& rLayout, const OUString& rText, bool bTextLines ) |
6091 | 2.69k | { |
6092 | 2.69k | Font aSaveFont = m_aCurrentPDFState.m_aFont; |
6093 | 2.69k | Color aSaveTextLineColor = m_aCurrentPDFState.m_aTextLineColor; |
6094 | 2.69k | Color aSaveOverlineColor = m_aCurrentPDFState.m_aOverlineColor; |
6095 | | |
6096 | 2.69k | Font& rFont = m_aCurrentPDFState.m_aFont; |
6097 | 2.69k | if( rFont.GetColor() == COL_BLACK || rFont.GetColor().GetLuminance() < 8 ) |
6098 | 420 | rFont.SetColor( COL_LIGHTGRAY ); |
6099 | 2.27k | else |
6100 | 2.27k | rFont.SetColor( COL_BLACK ); |
6101 | 2.69k | rFont.SetShadow( false ); |
6102 | 2.69k | rFont.SetOutline( false ); |
6103 | 2.69k | setFont( rFont ); |
6104 | 2.69k | setTextLineColor( rFont.GetColor() ); |
6105 | 2.69k | setOverlineColor( rFont.GetColor() ); |
6106 | 2.69k | updateGraphicsState(); |
6107 | | |
6108 | 2.69k | tools::Long nOff = 1 + ((GetFontInstance()->mnLineHeight-24)/24); |
6109 | 2.69k | if( rFont.IsOutline() ) |
6110 | 0 | nOff++; |
6111 | 2.69k | rLayout.DrawBase() += basegfx::B2DPoint(nOff, nOff); |
6112 | 2.69k | drawLayout( rLayout, rText, bTextLines ); |
6113 | 2.69k | rLayout.DrawBase() -= basegfx::B2DPoint(nOff, nOff); |
6114 | | |
6115 | 2.69k | setFont( aSaveFont ); |
6116 | 2.69k | setTextLineColor( aSaveTextLineColor ); |
6117 | 2.69k | setOverlineColor( aSaveOverlineColor ); |
6118 | 2.69k | updateGraphicsState(); |
6119 | 2.69k | } |
6120 | | |
6121 | | void PDFWriterImpl::drawVerticalGlyphs( |
6122 | | const std::vector<PDFGlyph>& rGlyphs, |
6123 | | OStringBuffer& rLine, |
6124 | | const Point& rAlignOffset, |
6125 | | const Matrix3& rRotScale, |
6126 | | double fAngle, |
6127 | | double fXScale, |
6128 | | sal_Int32 nFontHeight) |
6129 | 0 | { |
6130 | 0 | double nXOffset = 0; |
6131 | 0 | Point aCurPos(SubPixelToLogic(rGlyphs[0].m_aPos)); |
6132 | 0 | aCurPos += rAlignOffset; |
6133 | 0 | for( size_t i = 0; i < rGlyphs.size(); i++ ) |
6134 | 0 | { |
6135 | | // have to emit each glyph on its own |
6136 | 0 | double fDeltaAngle = 0.0; |
6137 | 0 | double fYScale = 1.0; |
6138 | 0 | double fTempXScale = fXScale; |
6139 | |
|
6140 | 0 | Point aDeltaPos; |
6141 | 0 | if (rGlyphs[i].m_pGlyph->IsVertical()) |
6142 | 0 | { |
6143 | 0 | fDeltaAngle = M_PI/2.0; |
6144 | 0 | fYScale = fXScale; |
6145 | 0 | fTempXScale = 1.0; |
6146 | 0 | } |
6147 | 0 | aDeltaPos += SubPixelToLogic(basegfx::B2DPoint(nXOffset / fXScale, 0)) - SubPixelToLogic(basegfx::B2DPoint()); |
6148 | 0 | if( i < rGlyphs.size()-1 ) |
6149 | | // #i120627# the text on the Y axis is reversed when export ppt file to PDF format |
6150 | 0 | { |
6151 | 0 | double nOffsetX = rGlyphs[i+1].m_aPos.getX() - rGlyphs[i].m_aPos.getX(); |
6152 | 0 | double nOffsetY = rGlyphs[i+1].m_aPos.getY() - rGlyphs[i].m_aPos.getY(); |
6153 | 0 | nXOffset += std::hypot(nOffsetX, nOffsetY); |
6154 | 0 | } |
6155 | 0 | if (!rGlyphs[i].m_pGlyph->glyphId()) |
6156 | 0 | continue; |
6157 | | |
6158 | 0 | aDeltaPos = rRotScale.transform( aDeltaPos ); |
6159 | |
|
6160 | 0 | Matrix3 aMat; |
6161 | | // perform artificial italics if necessary |
6162 | 0 | if (rGlyphs[i].m_pFont->NeedsArtificialItalic()) |
6163 | 0 | aMat.skew(0.0, ARTIFICIAL_ITALIC_SKEW); |
6164 | 0 | aMat.scale( fTempXScale, fYScale ); |
6165 | 0 | aMat.rotate( fAngle+fDeltaAngle ); |
6166 | 0 | aMat.translate( aCurPos.X()+aDeltaPos.X(), aCurPos.Y()+aDeltaPos.Y() ); |
6167 | 0 | m_aPages.back().appendMatrix3(aMat, rLine); |
6168 | 0 | rLine.append( " Tm" ); |
6169 | 0 | if( i == 0 || rGlyphs[i-1].m_nMappedFontId != rGlyphs[i].m_nMappedFontId ) |
6170 | 0 | { |
6171 | 0 | rLine.append( " /F" ); |
6172 | 0 | rLine.append( rGlyphs[i].m_nMappedFontId ); |
6173 | 0 | rLine.append( ' ' ); |
6174 | 0 | m_aPages.back().appendMappedLength( nFontHeight, rLine ); |
6175 | 0 | rLine.append( " Tf" ); |
6176 | 0 | } |
6177 | 0 | rLine.append( "<" ); |
6178 | 0 | COSWriter::appendHex( rGlyphs[i].m_nMappedGlyphId, rLine ); |
6179 | 0 | rLine.append( ">Tj\n" ); |
6180 | 0 | } |
6181 | 0 | } |
6182 | | |
6183 | | void PDFWriterImpl::drawHorizontalGlyphs( |
6184 | | const std::vector<PDFGlyph>& rGlyphs, |
6185 | | OStringBuffer& rLine, |
6186 | | const Point& rAlignOffset, |
6187 | | bool bFirst, |
6188 | | double fAngle, |
6189 | | double fXScale, |
6190 | | sal_Int32 nFontHeight, |
6191 | | sal_Int32 nPixelFontHeight) |
6192 | 733k | { |
6193 | | // horizontal (= normal) case |
6194 | | |
6195 | | // fill in run end indices |
6196 | | // end is marked by index of the first glyph of the next run |
6197 | | // a run is marked by same mapped font id and same Y position |
6198 | 733k | std::vector< sal_uInt32 > aRunEnds; |
6199 | 733k | aRunEnds.reserve( rGlyphs.size() ); |
6200 | 13.2M | for( size_t i = 1; i < rGlyphs.size(); i++ ) |
6201 | 12.5M | { |
6202 | 12.5M | if( rGlyphs[i].m_nMappedFontId != rGlyphs[i-1].m_nMappedFontId || |
6203 | 12.5M | rGlyphs[i].m_pFont != rGlyphs[i-1].m_pFont || |
6204 | 12.5M | rGlyphs[i].m_aPos.getY() != rGlyphs[i-1].m_aPos.getY() ) |
6205 | 2.73k | { |
6206 | 2.73k | aRunEnds.push_back(i); |
6207 | 2.73k | } |
6208 | 12.5M | } |
6209 | | // last run ends at last glyph |
6210 | 733k | aRunEnds.push_back( rGlyphs.size() ); |
6211 | | |
6212 | | // loop over runs of the same font |
6213 | 733k | sal_uInt32 nBeginRun = 0; |
6214 | 1.47M | for( size_t nRun = 0; nRun < aRunEnds.size(); nRun++ ) |
6215 | 736k | { |
6216 | | // setup text matrix back transformed to current coordinate system |
6217 | 736k | Point aCurPos(SubPixelToLogic(rGlyphs[nBeginRun].m_aPos)); |
6218 | 736k | aCurPos += rAlignOffset; |
6219 | | |
6220 | | // perform artificial italics if necessary |
6221 | 736k | double fSkew = 0.0; |
6222 | 736k | if (rGlyphs[nBeginRun].m_pFont->NeedsArtificialItalic()) |
6223 | 0 | fSkew = ARTIFICIAL_ITALIC_SKEW; |
6224 | | |
6225 | | // the first run can be set with "Td" operator |
6226 | | // subsequent use of that operator would move |
6227 | | // the textline matrix relative to what was set before |
6228 | | // making use of that would drive us into rounding issues |
6229 | 736k | Matrix3 aMat; |
6230 | 736k | if( bFirst && nRun == 0 && fAngle == 0.0 && fXScale == 1.0 && fSkew == 0.0 ) |
6231 | 665k | { |
6232 | 665k | m_aPages.back().appendPoint( aCurPos, rLine ); |
6233 | 665k | rLine.append( " Td " ); |
6234 | 665k | } |
6235 | 71.2k | else |
6236 | 71.2k | { |
6237 | 71.2k | if( fSkew != 0.0 ) |
6238 | 0 | aMat.skew( 0.0, fSkew ); |
6239 | 71.2k | aMat.scale( fXScale, 1.0 ); |
6240 | 71.2k | aMat.rotate( fAngle ); |
6241 | 71.2k | aMat.translate( aCurPos.X(), aCurPos.Y() ); |
6242 | 71.2k | m_aPages.back().appendMatrix3(aMat, rLine); |
6243 | 71.2k | rLine.append( " Tm\n" ); |
6244 | 71.2k | } |
6245 | | // set up correct font |
6246 | 736k | rLine.append( "/F" ); |
6247 | 736k | rLine.append( rGlyphs[nBeginRun].m_nMappedFontId ); |
6248 | 736k | rLine.append( ' ' ); |
6249 | 736k | m_aPages.back().appendMappedLength( nFontHeight, rLine ); |
6250 | 736k | rLine.append( " Tf" ); |
6251 | | |
6252 | | // output glyphs using Tj or TJ |
6253 | 736k | OStringBuffer aKernedLine( 256 ), aUnkernedLine( 256 ); |
6254 | 736k | aKernedLine.append( "[<" ); |
6255 | 736k | aUnkernedLine.append( '<' ); |
6256 | 736k | COSWriter::appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aKernedLine ); |
6257 | 736k | COSWriter::appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aUnkernedLine ); |
6258 | | |
6259 | 736k | aMat.invert(); |
6260 | 736k | bool bNeedKern = false; |
6261 | 13.2M | for( sal_uInt32 nPos = nBeginRun+1; nPos < aRunEnds[nRun]; nPos++ ) |
6262 | 12.5M | { |
6263 | 12.5M | COSWriter::appendHex( rGlyphs[nPos].m_nMappedGlyphId, aUnkernedLine ); |
6264 | | // check if default glyph positioning is sufficient |
6265 | 12.5M | const basegfx::B2DPoint aThisPos = aMat.transform( rGlyphs[nPos].m_aPos ); |
6266 | 12.5M | const basegfx::B2DPoint aPrevPos = aMat.transform( rGlyphs[nPos-1].m_aPos ); |
6267 | 12.5M | double fAdvance = aThisPos.getX() - aPrevPos.getX(); |
6268 | 12.5M | fAdvance *= 1000.0 / nPixelFontHeight; |
6269 | 12.5M | const double fAdjustment = rGlyphs[nPos-1].m_nNativeWidth - fAdvance + 0.5; |
6270 | 12.5M | SAL_WARN_IF( |
6271 | 12.5M | fAdjustment < SAL_MIN_INT32 || fAdjustment > SAL_MAX_INT32, "vcl.pdfwriter", |
6272 | 12.5M | "adjustment " << fAdjustment << " outside 32-bit int"); |
6273 | 12.5M | const sal_Int32 nAdjustment = static_cast<sal_Int32>( |
6274 | 12.5M | std::clamp(fAdjustment, double(SAL_MIN_INT32), double(SAL_MAX_INT32))); |
6275 | 12.5M | if( nAdjustment != 0 ) |
6276 | 8.60M | { |
6277 | | // apply individual glyph positioning |
6278 | 8.60M | bNeedKern = true; |
6279 | 8.60M | aKernedLine.append( ">" ); |
6280 | 8.60M | aKernedLine.append( nAdjustment ); |
6281 | 8.60M | aKernedLine.append( "<" ); |
6282 | 8.60M | } |
6283 | 12.5M | COSWriter::appendHex( rGlyphs[nPos].m_nMappedGlyphId, aKernedLine ); |
6284 | 12.5M | } |
6285 | 736k | aKernedLine.append( ">]TJ\n" ); |
6286 | 736k | aUnkernedLine.append( ">Tj\n" ); |
6287 | 736k | rLine.append( bNeedKern ? aKernedLine : aUnkernedLine ); |
6288 | | |
6289 | | // set beginning of next run |
6290 | 736k | nBeginRun = aRunEnds[nRun]; |
6291 | 736k | } |
6292 | 733k | } |
6293 | | |
6294 | | void PDFWriterImpl::drawLayout( SalLayout& rLayout, const OUString& rText, bool bTextLines ) |
6295 | 665k | { |
6296 | | // relief takes precedence over shadow (see outdev3.cxx) |
6297 | 665k | if( m_aCurrentPDFState.m_aFont.GetRelief() != FontRelief::NONE ) |
6298 | 0 | { |
6299 | 0 | drawRelief( rLayout, rText, bTextLines ); |
6300 | 0 | return; |
6301 | 0 | } |
6302 | 665k | else if( m_aCurrentPDFState.m_aFont.IsShadow() ) |
6303 | 2.69k | drawShadow( rLayout, rText, bTextLines ); |
6304 | | |
6305 | 665k | OStringBuffer aLine( 512 ); |
6306 | | |
6307 | 665k | const int nMaxGlyphs = 256; |
6308 | | |
6309 | 665k | std::vector<sal_Ucs> aCodeUnits; |
6310 | 665k | bool bVertical = m_aCurrentPDFState.m_aFont.IsVertical(); |
6311 | 665k | int nIndex = 0; |
6312 | 665k | double fXScale = 1.0; |
6313 | 665k | sal_Int32 nPixelFontHeight = GetFontInstance()->GetFontSelectPattern().mnHeight; |
6314 | 665k | TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment(); |
6315 | | |
6316 | | // transform font height back to current units |
6317 | | // note: the layout calculates in outdevs device pixel !! |
6318 | 665k | sal_Int32 nFontHeight = DevicePixelToLogicHeight(nPixelFontHeight); |
6319 | 665k | if( m_aCurrentPDFState.m_aFont.GetFontWidth() ) |
6320 | 19 | { |
6321 | 19 | Font aFont( m_aCurrentPDFState.m_aFont ); |
6322 | 19 | aFont.SetFontWidth( 0 ); |
6323 | 19 | FontMetric aMetric = GetFontMetric( aFont ); |
6324 | 19 | if( aMetric.GetFontWidth() != m_aCurrentPDFState.m_aFont.GetFontWidth() ) |
6325 | 19 | { |
6326 | 19 | fXScale = |
6327 | 19 | static_cast<double>(m_aCurrentPDFState.m_aFont.GetFontWidth()) / |
6328 | 19 | static_cast<double>(aMetric.GetFontWidth()); |
6329 | 19 | } |
6330 | 19 | } |
6331 | | |
6332 | | // if the mapmode is distorted we need to adjust for that also |
6333 | 665k | if( m_aCurrentPDFState.m_aMapMode.GetScaleX() != m_aCurrentPDFState.m_aMapMode.GetScaleY() ) |
6334 | 35 | { |
6335 | 35 | fXScale *= m_aCurrentPDFState.m_aMapMode.GetScaleX() / m_aCurrentPDFState.m_aMapMode.GetScaleY(); |
6336 | 35 | } |
6337 | | |
6338 | 665k | Degree10 nAngle = m_aCurrentPDFState.m_aFont.GetOrientation(); |
6339 | | // normalize angles |
6340 | 665k | while( nAngle < 0_deg10 ) |
6341 | 2 | nAngle += 3600_deg10; |
6342 | 665k | nAngle = nAngle % 3600_deg10; |
6343 | 665k | double fAngle = toRadians(nAngle); |
6344 | | |
6345 | 665k | Matrix3 aRotScale; |
6346 | 665k | aRotScale.scale( fXScale, 1.0 ); |
6347 | 665k | if( fAngle != 0.0 ) |
6348 | 2 | aRotScale.rotate( -fAngle ); |
6349 | | |
6350 | 665k | bool bPop = false; |
6351 | 665k | bool bABold = false; |
6352 | | // artificial bold necessary ? |
6353 | 665k | if (GetFontInstance()->NeedsArtificialBold()) |
6354 | 0 | { |
6355 | 0 | aLine.append("q "); |
6356 | 0 | bPop = true; |
6357 | 0 | bABold = true; |
6358 | 0 | } |
6359 | | // setup text colors (if necessary) |
6360 | 665k | Color aStrokeColor( COL_TRANSPARENT ); |
6361 | 665k | Color aNonStrokeColor( COL_TRANSPARENT ); |
6362 | | |
6363 | 665k | if( m_aCurrentPDFState.m_aFont.IsOutline() ) |
6364 | 343 | { |
6365 | 343 | aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor(); |
6366 | 343 | aNonStrokeColor = COL_WHITE; |
6367 | 343 | } |
6368 | 665k | else |
6369 | 665k | aNonStrokeColor = m_aCurrentPDFState.m_aFont.GetColor(); |
6370 | 665k | if( bABold ) |
6371 | 0 | aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor(); |
6372 | | |
6373 | 665k | if( aStrokeColor != COL_TRANSPARENT && aStrokeColor != m_aCurrentPDFState.m_aLineColor ) |
6374 | 343 | { |
6375 | 343 | if( ! bPop ) |
6376 | 343 | aLine.append( "q " ); |
6377 | 343 | bPop = true; |
6378 | 343 | appendStrokingColor( aStrokeColor, aLine ); |
6379 | 343 | aLine.append( "\n" ); |
6380 | 343 | } |
6381 | 665k | if( aNonStrokeColor != COL_TRANSPARENT && aNonStrokeColor != m_aCurrentPDFState.m_aFillColor ) |
6382 | 665k | { |
6383 | 665k | if( ! bPop ) |
6384 | 665k | aLine.append( "q " ); |
6385 | 665k | bPop = true; |
6386 | 665k | appendNonStrokingColor( aNonStrokeColor, aLine ); |
6387 | 665k | aLine.append( "\n" ); |
6388 | 665k | } |
6389 | | |
6390 | | // begin text object |
6391 | 665k | aLine.append( "BT\n" ); |
6392 | | // outline attribute ? |
6393 | 665k | if( m_aCurrentPDFState.m_aFont.IsOutline() || bABold ) |
6394 | 343 | { |
6395 | | // set correct text mode, set stroke width |
6396 | 343 | aLine.append( "2 Tr " ); // fill, then stroke |
6397 | | |
6398 | 343 | if( m_aCurrentPDFState.m_aFont.IsOutline() ) |
6399 | 343 | { |
6400 | | // unclear what to do in case of outline and artificial bold |
6401 | | // for the time being outline wins |
6402 | 343 | aLine.append( "0.25 w \n" ); |
6403 | 343 | } |
6404 | 0 | else |
6405 | 0 | { |
6406 | 0 | double fW = static_cast<double>(m_aCurrentPDFState.m_aFont.GetFontHeight()) / 30.0; |
6407 | 0 | m_aPages.back().appendMappedLength( fW, aLine ); |
6408 | 0 | aLine.append ( " w\n" ); |
6409 | 0 | } |
6410 | 343 | } |
6411 | | |
6412 | 665k | FontMetric aRefDevFontMetric = GetFontMetric(); |
6413 | 665k | const GlyphItem* pGlyph = nullptr; |
6414 | 665k | const LogicalFontInstance* pGlyphFont = nullptr; |
6415 | | |
6416 | | // collect the glyphs into a single array |
6417 | 665k | std::vector< PDFGlyph > aGlyphs; |
6418 | 665k | aGlyphs.reserve( nMaxGlyphs ); |
6419 | | // first get all the glyphs and register them; coordinates still in Pixel |
6420 | 665k | basegfx::B2DPoint aPos; |
6421 | 13.9M | while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pGlyphFont)) |
6422 | 13.2M | { |
6423 | 13.2M | const auto* pFace = pGlyphFont->GetFontFace(); |
6424 | | |
6425 | 13.2M | aCodeUnits.clear(); |
6426 | | |
6427 | | // tdf#66597, tdf#115117 |
6428 | | // |
6429 | | // Here is how we embed textual content in PDF files, to allow for |
6430 | | // better text extraction for complex and typography-rich text. |
6431 | | // |
6432 | | // * If there is many to one or many to many mapping, use an |
6433 | | // ActualText span embedding the original string, since ToUnicode |
6434 | | // can't handle these. |
6435 | | // * If the one glyph is used for several Unicode code points, also |
6436 | | // use ActualText since ToUnicode can map each glyph in the font |
6437 | | // only once. |
6438 | | // * Limit ActualText to single cluster at a time, since using it |
6439 | | // for whole words or sentences breaks text selection and |
6440 | | // highlighting in PDF viewers (there will be no way to tell |
6441 | | // which glyphs belong to which characters). |
6442 | | |
6443 | 13.2M | assert(pGlyph->charCount() >= 0); |
6444 | 26.5M | for (int n = 0; n < pGlyph->charCount(); n++) |
6445 | 13.2M | aCodeUnits.push_back(rText[pGlyph->charPos() + n]); |
6446 | | |
6447 | 13.2M | bool bUseActualText = false; |
6448 | | |
6449 | | // If this is a start of complex cluster, use ActualText. |
6450 | 13.2M | if (pGlyph->IsClusterStart()) |
6451 | 7.11k | bUseActualText = true; |
6452 | | |
6453 | 13.2M | const auto nGlyphId = pGlyph->glyphId(); |
6454 | | |
6455 | | // A glyph can't have more than one ToUnicode entry, use ActualText |
6456 | | // instead. |
6457 | 13.2M | if (!aCodeUnits.empty() && !bUseActualText) |
6458 | 13.2M | { |
6459 | 13.2M | for (const auto& rSubset : m_aSubsets[{ pFace, pGlyphFont }].m_aSubsets) |
6460 | 13.2M | { |
6461 | 13.2M | const auto it = rSubset.m_aMapping.find(nGlyphId); |
6462 | 13.2M | if (it != rSubset.m_aMapping.cend() && it->second.codes() != aCodeUnits) |
6463 | 50.4k | bUseActualText = true; |
6464 | 13.2M | } |
6465 | 13.2M | } |
6466 | | |
6467 | | // If we are using ActualText, then ToUnicode mapping is broken, drop it. |
6468 | 13.2M | if (bUseActualText) |
6469 | 57.6k | aCodeUnits.clear(); |
6470 | | |
6471 | | // tdf#157390: The width stored by registerGlyph() must be the actual glyph width. |
6472 | | // This must be obtained by calling GetGlyphWidth(vertical=false), otherwise an incorrect |
6473 | | // width value will be returned for CJK characters. |
6474 | 13.2M | auto nMappedGlyphWidth = pGlyphFont->GetGlyphWidth(nGlyphId, /*vertical*/ false, false); |
6475 | 13.2M | auto nGlyphWidth = nMappedGlyphWidth; |
6476 | 13.2M | if (pGlyph->IsVertical()) |
6477 | 0 | { |
6478 | 0 | nGlyphWidth = pGlyphFont->GetGlyphWidth(nGlyphId, /*vertical*/ true, false); |
6479 | 0 | } |
6480 | | |
6481 | 13.2M | sal_uInt8 nMappedGlyph; |
6482 | 13.2M | sal_Int32 nMappedFontObject; |
6483 | 13.2M | registerGlyph(nGlyphId, pFace, pGlyphFont, aCodeUnits, nMappedGlyphWidth, nMappedGlyph, |
6484 | 13.2M | nMappedFontObject); |
6485 | | |
6486 | 13.2M | int nCharPos = -1; |
6487 | 13.2M | if (bUseActualText || pGlyph->IsInCluster()) |
6488 | 64.8k | nCharPos = pGlyph->charPos(); |
6489 | | |
6490 | 13.2M | aGlyphs.emplace_back(aPos, |
6491 | 13.2M | pGlyph, |
6492 | 13.2M | pGlyphFont, |
6493 | 13.2M | XUnits(pFace->UnitsPerEm(), nGlyphWidth), |
6494 | 13.2M | nMappedFontObject, |
6495 | 13.2M | nMappedGlyph, |
6496 | 13.2M | nCharPos); |
6497 | 13.2M | } |
6498 | | |
6499 | | // Avoid fill color when map mode is in pixels, the below code assumes |
6500 | | // logic map mode. |
6501 | 665k | bool bPixel = m_aCurrentPDFState.m_aMapMode.GetMapUnit() == MapUnit::MapPixel; |
6502 | 665k | if (m_aCurrentPDFState.m_aFont.GetFillColor() != COL_TRANSPARENT && !bPixel) |
6503 | 0 | { |
6504 | | // PDF doesn't have a text fill color, so draw a rectangle before |
6505 | | // drawing the actual text. |
6506 | 0 | push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR); |
6507 | 0 | setFillColor(m_aCurrentPDFState.m_aFont.GetFillColor()); |
6508 | | // Avoid border around the rectangle for Writer shape text. |
6509 | 0 | setLineColor(COL_TRANSPARENT); |
6510 | | |
6511 | | // The rectangle is the bounding box of the text, but also includes |
6512 | | // ascent / descent to match the on-screen rendering. |
6513 | | // This is the top left of the text without ascent / descent. |
6514 | 0 | basegfx::B2DPoint aDrawPosition(rLayout.GetDrawPosition()); |
6515 | 0 | tools::Rectangle aRectangle(SubPixelToLogic(aDrawPosition), |
6516 | 0 | Size(DevicePixelToLogicWidth(rLayout.GetTextWidth()), 0)); |
6517 | 0 | aRectangle.AdjustTop(-aRefDevFontMetric.GetAscent()); |
6518 | | // This includes ascent / descent. |
6519 | 0 | aRectangle.setHeight(aRefDevFontMetric.GetLineHeight()); |
6520 | |
|
6521 | 0 | const LogicalFontInstance* pFontInstance = GetFontInstance(); |
6522 | 0 | if (pFontInstance->mnOrientation) |
6523 | 0 | { |
6524 | | // Adapt rectangle for rotated text. |
6525 | 0 | tools::Polygon aPolygon(aRectangle); |
6526 | 0 | aPolygon.Rotate(SubPixelToLogic(aDrawPosition), pFontInstance->mnOrientation); |
6527 | 0 | drawPolygon(aPolygon); |
6528 | 0 | } |
6529 | 0 | else |
6530 | 0 | drawRectangle(aRectangle); |
6531 | |
|
6532 | 0 | pop(); |
6533 | 0 | } |
6534 | | |
6535 | 665k | Point aAlignOffset; |
6536 | 665k | if ( eAlign == ALIGN_BOTTOM ) |
6537 | 0 | aAlignOffset.AdjustY( -(aRefDevFontMetric.GetDescent()) ); |
6538 | 665k | else if ( eAlign == ALIGN_TOP ) |
6539 | 0 | aAlignOffset.AdjustY(aRefDevFontMetric.GetAscent() ); |
6540 | 665k | if( aAlignOffset.X() || aAlignOffset.Y() ) |
6541 | 0 | aAlignOffset = aRotScale.transform( aAlignOffset ); |
6542 | | |
6543 | | /* #159153# do not emit an empty glyph vector; this can happen if e.g. the original |
6544 | | string contained only one of the UTF16 BOMs |
6545 | | */ |
6546 | 665k | if( ! aGlyphs.empty() ) |
6547 | 665k | { |
6548 | 665k | size_t nStart = 0; |
6549 | 665k | size_t nEnd = 0; |
6550 | 1.39M | while (nStart < aGlyphs.size()) |
6551 | 733k | { |
6552 | 14.0M | while (nEnd < aGlyphs.size() && aGlyphs[nEnd].m_nCharPos == aGlyphs[nStart].m_nCharPos) |
6553 | 13.2M | nEnd++; |
6554 | | |
6555 | 733k | std::vector<PDFGlyph> aRun(aGlyphs.begin() + nStart, aGlyphs.begin() + nEnd); |
6556 | | |
6557 | 733k | int nCharPos, nCharCount; |
6558 | 733k | if (!aRun.front().m_pGlyph->IsRTLGlyph()) |
6559 | 679k | { |
6560 | 679k | nCharPos = aRun.front().m_nCharPos; |
6561 | 679k | nCharCount = aRun.front().m_pGlyph->charCount(); |
6562 | 679k | } |
6563 | 54.4k | else |
6564 | 54.4k | { |
6565 | 54.4k | nCharPos = aRun.back().m_nCharPos; |
6566 | 54.4k | nCharCount = aRun.back().m_pGlyph->charCount(); |
6567 | 54.4k | } |
6568 | | |
6569 | 733k | if (nCharPos >= 0 && nCharCount) |
6570 | 57.6k | { |
6571 | 57.6k | aLine.append("/Span<</ActualText<FEFF"); |
6572 | 127k | for (int i = 0; i < nCharCount; i++) |
6573 | 69.7k | { |
6574 | 69.7k | sal_Unicode aChar = rText[nCharPos + i]; |
6575 | 69.7k | COSWriter::appendHex(static_cast<sal_Int8>(aChar >> 8), aLine); |
6576 | 69.7k | COSWriter::appendHex(static_cast<sal_Int8>(aChar & 255), aLine); |
6577 | 69.7k | } |
6578 | 57.6k | aLine.append( ">>>\nBDC\n" ); |
6579 | 57.6k | } |
6580 | | |
6581 | 733k | if (bVertical) |
6582 | 0 | drawVerticalGlyphs(aRun, aLine, aAlignOffset, aRotScale, fAngle, fXScale, nFontHeight); |
6583 | 733k | else |
6584 | 733k | drawHorizontalGlyphs(aRun, aLine, aAlignOffset, nStart == 0, fAngle, fXScale, nFontHeight, nPixelFontHeight); |
6585 | | |
6586 | 733k | if (nCharPos >= 0 && nCharCount) |
6587 | 57.6k | aLine.append( "EMC\n" ); |
6588 | | |
6589 | 733k | nStart = nEnd; |
6590 | 733k | } |
6591 | 665k | } |
6592 | | |
6593 | | // end textobject |
6594 | 665k | aLine.append( "ET\n" ); |
6595 | 665k | if( bPop ) |
6596 | 665k | aLine.append( "Q\n" ); |
6597 | | |
6598 | 665k | writeBuffer( aLine ); |
6599 | | |
6600 | | // draw eventual textlines |
6601 | 665k | FontStrikeout eStrikeout = m_aCurrentPDFState.m_aFont.GetStrikeout(); |
6602 | 665k | FontLineStyle eUnderline = m_aCurrentPDFState.m_aFont.GetUnderline(); |
6603 | 665k | FontLineStyle eOverline = m_aCurrentPDFState.m_aFont.GetOverline(); |
6604 | 665k | if( bTextLines && |
6605 | 665k | ( |
6606 | 665k | ( eUnderline != LINESTYLE_NONE && eUnderline != LINESTYLE_DONTKNOW ) || |
6607 | 656k | ( eOverline != LINESTYLE_NONE && eOverline != LINESTYLE_DONTKNOW ) || |
6608 | 656k | ( eStrikeout != STRIKEOUT_NONE && eStrikeout != STRIKEOUT_DONTKNOW ) |
6609 | 665k | ) |
6610 | 665k | ) |
6611 | 10.1k | { |
6612 | 10.1k | bool bUnderlineAbove = m_aCurrentPDFState.m_aFont.IsUnderlineAbove(); |
6613 | 10.1k | if( m_aCurrentPDFState.m_aFont.IsWordLineMode() ) |
6614 | 290 | { |
6615 | 290 | basegfx::B2DPoint aStartPt; |
6616 | 290 | double nWidth = 0; |
6617 | 290 | nIndex = 0; |
6618 | 7.97k | while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex)) |
6619 | 7.68k | { |
6620 | 7.68k | if (!pGlyph->IsSpacing()) |
6621 | 6.74k | { |
6622 | 6.74k | if( !nWidth ) |
6623 | 1.07k | aStartPt = aPos; |
6624 | | |
6625 | 6.74k | nWidth += pGlyph->newWidth(); |
6626 | 6.74k | } |
6627 | 937 | else if( nWidth > 0 ) |
6628 | 880 | { |
6629 | 880 | drawTextLine( SubPixelToLogic(aStartPt), |
6630 | 880 | DevicePixelToLogicWidth(nWidth), |
6631 | 880 | eStrikeout, eUnderline, eOverline, bUnderlineAbove ); |
6632 | 880 | nWidth = 0; |
6633 | 880 | } |
6634 | 7.68k | } |
6635 | | |
6636 | 290 | if( nWidth > 0 ) |
6637 | 194 | { |
6638 | 194 | drawTextLine( SubPixelToLogic(aStartPt), |
6639 | 194 | DevicePixelToLogicWidth(nWidth), |
6640 | 194 | eStrikeout, eUnderline, eOverline, bUnderlineAbove ); |
6641 | 194 | } |
6642 | 290 | } |
6643 | 9.81k | else |
6644 | 9.81k | { |
6645 | 9.81k | basegfx::B2DPoint aStartPt = rLayout.GetDrawPosition(); |
6646 | 9.81k | int nWidth = rLayout.GetTextWidth(); |
6647 | 9.81k | drawTextLine( SubPixelToLogic(aStartPt), |
6648 | 9.81k | DevicePixelToLogicWidth(nWidth), |
6649 | 9.81k | eStrikeout, eUnderline, eOverline, bUnderlineAbove ); |
6650 | 9.81k | } |
6651 | 10.1k | } |
6652 | | |
6653 | | // write eventual emphasis marks |
6654 | 665k | if( !(m_aCurrentPDFState.m_aFont.GetEmphasisMark() & FontEmphasisMark::Style) ) |
6655 | 665k | return; |
6656 | | |
6657 | 0 | push( PushFlags::ALL ); |
6658 | |
|
6659 | 0 | aLine.setLength( 0 ); |
6660 | 0 | aLine.append( "q\n" ); |
6661 | |
|
6662 | 0 | FontEmphasisMark nEmphMark = m_aCurrentPDFState.m_aFont.GetEmphasisMarkStyle(); |
6663 | |
|
6664 | 0 | tools::Long nEmphHeight; |
6665 | 0 | if ( nEmphMark & FontEmphasisMark::PosBelow ) |
6666 | 0 | nEmphHeight = GetEmphasisDescent(); |
6667 | 0 | else |
6668 | 0 | nEmphHeight = GetEmphasisAscent(); |
6669 | |
|
6670 | 0 | vcl::font::EmphasisMark aEmphasisMark(nEmphMark, DevicePixelToLogicWidth(nEmphHeight), GetDPIY()); |
6671 | 0 | if ( aEmphasisMark.IsShapePolyLine() ) |
6672 | 0 | { |
6673 | 0 | setLineColor( m_aCurrentPDFState.m_aFont.GetColor() ); |
6674 | 0 | setFillColor( COL_TRANSPARENT ); |
6675 | 0 | } |
6676 | 0 | else |
6677 | 0 | { |
6678 | 0 | setFillColor( m_aCurrentPDFState.m_aFont.GetColor() ); |
6679 | 0 | setLineColor( COL_TRANSPARENT ); |
6680 | 0 | } |
6681 | |
|
6682 | 0 | writeBuffer( aLine ); |
6683 | |
|
6684 | 0 | Point aOffset(0,0); |
6685 | 0 | Point aOffsetVert(0,0); |
6686 | |
|
6687 | 0 | if ( nEmphMark & FontEmphasisMark::PosBelow ) |
6688 | 0 | { |
6689 | 0 | aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset() ); |
6690 | 0 | aOffsetVert = aOffset; |
6691 | 0 | } |
6692 | 0 | else |
6693 | 0 | { |
6694 | 0 | aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetAscent() + aEmphasisMark.GetYOffset()) ); |
6695 | | // Todo: use ideographic em-box or ideographic character face information. |
6696 | 0 | aOffsetVert.AdjustY(-(GetFontInstance()->mxFontMetric->GetAscent() + |
6697 | 0 | GetFontInstance()->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset())); |
6698 | 0 | } |
6699 | |
|
6700 | 0 | tools::Long nEmphWidth2 = aEmphasisMark.GetWidth() / 2; |
6701 | 0 | tools::Long nEmphHeight2 = nEmphHeight / 2; |
6702 | 0 | aOffset += Point( nEmphWidth2, nEmphHeight2 ); |
6703 | |
|
6704 | 0 | if ( eAlign == ALIGN_BOTTOM ) |
6705 | 0 | aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetDescent()) ); |
6706 | 0 | else if ( eAlign == ALIGN_TOP ) |
6707 | 0 | aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetAscent() ); |
6708 | |
|
6709 | 0 | basegfx::B2DRectangle aRectangle; |
6710 | 0 | nIndex = 0; |
6711 | 0 | while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pGlyphFont)) |
6712 | 0 | { |
6713 | 0 | if (!pGlyph->GetGlyphBoundRect(pGlyphFont, aRectangle)) |
6714 | 0 | continue; |
6715 | | |
6716 | 0 | if (!pGlyph->IsSpacing()) |
6717 | 0 | { |
6718 | 0 | basegfx::B2DPoint aAdjOffset; |
6719 | 0 | if (pGlyph->IsVertical()) |
6720 | 0 | { |
6721 | 0 | aAdjOffset = basegfx::B2DPoint(aOffsetVert.X(), aOffsetVert.Y()); |
6722 | 0 | aAdjOffset.adjustX((-pGlyph->origWidth() + aEmphasisMark.GetWidth()) / 2); |
6723 | 0 | } |
6724 | 0 | else |
6725 | 0 | { |
6726 | 0 | aAdjOffset = basegfx::B2DPoint(aOffset.X(), aOffset.Y()); |
6727 | 0 | aAdjOffset.adjustX(aRectangle.getMinX() + (aRectangle.getWidth() - aEmphasisMark.GetWidth()) / 2 ); |
6728 | 0 | } |
6729 | |
|
6730 | 0 | aAdjOffset = aRotScale.transform( aAdjOffset ); |
6731 | |
|
6732 | 0 | aAdjOffset -= basegfx::B2DPoint(nEmphWidth2, nEmphHeight2); |
6733 | |
|
6734 | 0 | basegfx::B2DPoint aMarkDevPos(aPos); |
6735 | 0 | aMarkDevPos += aAdjOffset; |
6736 | 0 | Point aMarkPos = SubPixelToLogic(aMarkDevPos); |
6737 | 0 | drawEmphasisMark( aMarkPos.X(), aMarkPos.Y(), |
6738 | 0 | aEmphasisMark.GetShape(), aEmphasisMark.IsShapePolyLine(), |
6739 | 0 | aEmphasisMark.GetRect1(), aEmphasisMark.GetRect2() ); |
6740 | 0 | } |
6741 | 0 | } |
6742 | |
|
6743 | 0 | writeBuffer( "Q\n" ); |
6744 | 0 | pop(); |
6745 | |
|
6746 | 0 | } |
6747 | | |
6748 | | void PDFWriterImpl::drawEmphasisMark( tools::Long nX, tools::Long nY, |
6749 | | const tools::PolyPolygon& rPolyPoly, bool bPolyLine, |
6750 | | const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 ) |
6751 | 0 | { |
6752 | | // TODO: pass nWidth as width of this mark |
6753 | | // long nWidth = 0; |
6754 | |
|
6755 | 0 | if ( rPolyPoly.Count() ) |
6756 | 0 | { |
6757 | 0 | if ( bPolyLine ) |
6758 | 0 | { |
6759 | 0 | tools::Polygon aPoly = rPolyPoly.GetObject( 0 ); |
6760 | 0 | aPoly.Move( nX, nY ); |
6761 | 0 | drawPolyLine( aPoly ); |
6762 | 0 | } |
6763 | 0 | else |
6764 | 0 | { |
6765 | 0 | tools::PolyPolygon aPolyPoly = rPolyPoly; |
6766 | 0 | aPolyPoly.Move( nX, nY ); |
6767 | 0 | drawPolyPolygon( aPolyPoly ); |
6768 | 0 | } |
6769 | 0 | } |
6770 | |
|
6771 | 0 | if ( !rRect1.IsEmpty() ) |
6772 | 0 | { |
6773 | 0 | tools::Rectangle aRect( Point( nX+rRect1.Left(), |
6774 | 0 | nY+rRect1.Top() ), rRect1.GetSize() ); |
6775 | 0 | drawRectangle( aRect ); |
6776 | 0 | } |
6777 | |
|
6778 | 0 | if ( !rRect2.IsEmpty() ) |
6779 | 0 | { |
6780 | 0 | tools::Rectangle aRect( Point( nX+rRect2.Left(), |
6781 | 0 | nY+rRect2.Top() ), rRect2.GetSize() ); |
6782 | |
|
6783 | 0 | drawRectangle( aRect ); |
6784 | 0 | } |
6785 | 0 | } |
6786 | | |
6787 | | void PDFWriterImpl::drawText( const Point& rPos, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen, bool bTextLines ) |
6788 | 0 | { |
6789 | 0 | MARK( "drawText" ); |
6790 | |
|
6791 | 0 | updateGraphicsState(); |
6792 | | |
6793 | | // get a layout from the OutputDevice's SalGraphics |
6794 | | // this also enforces font substitution and sets the font on SalGraphics |
6795 | 0 | const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()-> |
6796 | 0 | GetLayoutGlyphs( this, rText, nIndex, nLen ); |
6797 | 0 | std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, |
6798 | 0 | 0, {}, {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs ); |
6799 | 0 | if( pLayout ) |
6800 | 0 | { |
6801 | 0 | drawLayout( *pLayout, rText, bTextLines ); |
6802 | 0 | } |
6803 | 0 | } |
6804 | | |
6805 | | void PDFWriterImpl::drawTextArray(const Point& rPos, const OUString& rText, KernArraySpan pDXArray, |
6806 | | std::span<const sal_Bool> pKashidaArray, sal_Int32 nIndex, |
6807 | | sal_Int32 nLen, sal_Int32 nLayoutContextIndex, |
6808 | | sal_Int32 nLayoutContextLen) |
6809 | 657k | { |
6810 | 657k | MARK( "drawText with array" ); |
6811 | | |
6812 | 657k | updateGraphicsState(); |
6813 | | |
6814 | | // get a layout from the OutputDevice's SalGraphics |
6815 | | // this also enforces font substitution and sets the font on SalGraphics |
6816 | 657k | std::unique_ptr<SalLayout> pLayout; |
6817 | 657k | if (nLayoutContextIndex >= 0) |
6818 | 493k | { |
6819 | 493k | const auto* layoutGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs( |
6820 | 493k | this, rText, nLayoutContextIndex, nLayoutContextLen, nIndex, nIndex + nLen); |
6821 | 493k | pLayout = ImplLayout(rText, nLayoutContextIndex, nLayoutContextLen, rPos, 0, pDXArray, |
6822 | 493k | pKashidaArray, SalLayoutFlags::UnclusteredGlyphs, nullptr, |
6823 | 493k | layoutGlyphs, /*nDrawOriginCluster=*/nIndex, |
6824 | 493k | /*nDrawMinCharPos=*/nIndex, /*nDrawEndCharPos=*/nIndex + nLen); |
6825 | 493k | } |
6826 | 164k | else |
6827 | 164k | { |
6828 | 164k | const auto* layoutGlyphs |
6829 | 164k | = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(this, rText, nIndex, nLen); |
6830 | 164k | pLayout = ImplLayout(rText, nIndex, nLen, rPos, 0, pDXArray, pKashidaArray, |
6831 | 164k | SalLayoutFlags::NONE, nullptr, layoutGlyphs); |
6832 | 164k | } |
6833 | | |
6834 | 657k | if( pLayout ) |
6835 | 657k | { |
6836 | 657k | drawLayout( *pLayout, rText, true ); |
6837 | 657k | } |
6838 | 657k | } |
6839 | | |
6840 | | void PDFWriterImpl::drawStretchText( const Point& rPos, sal_Int32 nWidth, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen ) |
6841 | 5.43k | { |
6842 | 5.43k | MARK( "drawStretchText" ); |
6843 | | |
6844 | 5.43k | updateGraphicsState(); |
6845 | | |
6846 | | // get a layout from the OutputDevice's SalGraphics |
6847 | | // this also enforces font substitution and sets the font on SalGraphics |
6848 | 5.43k | const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()-> |
6849 | 5.43k | GetLayoutGlyphs( this, rText, nIndex, nLen, nWidth ); |
6850 | 5.43k | std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, nWidth, |
6851 | 5.43k | {}, {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs ); |
6852 | 5.43k | if( pLayout ) |
6853 | 5.43k | { |
6854 | 5.43k | drawLayout( *pLayout, rText, true ); |
6855 | 5.43k | } |
6856 | 5.43k | } |
6857 | | |
6858 | | void PDFWriterImpl::drawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle ) |
6859 | 0 | { |
6860 | 0 | tools::Long nWidth = rRect.GetWidth(); |
6861 | 0 | tools::Long nHeight = rRect.GetHeight(); |
6862 | |
|
6863 | 0 | if ( nWidth <= 0 || nHeight <= 0 ) |
6864 | 0 | return; |
6865 | | |
6866 | 0 | MARK( "drawText with rectangle" ); |
6867 | |
|
6868 | 0 | updateGraphicsState(); |
6869 | | |
6870 | | // clip with rectangle |
6871 | 0 | OStringBuffer aLine; |
6872 | 0 | aLine.append( "q " ); |
6873 | 0 | m_aPages.back().appendRect( rRect, aLine ); |
6874 | 0 | aLine.append( " W* n\n" ); |
6875 | 0 | writeBuffer( aLine ); |
6876 | | |
6877 | | // if disabled text is needed, put in here |
6878 | |
|
6879 | 0 | Point aPos = rRect.TopLeft(); |
6880 | |
|
6881 | 0 | tools::Long nTextHeight = GetTextHeight(); |
6882 | |
|
6883 | 0 | OUString aStr = rOrigStr; |
6884 | 0 | if ( nStyle & DrawTextFlags::Mnemonic ) |
6885 | 0 | aStr = removeMnemonicFromString(aStr); |
6886 | | |
6887 | | // multiline text |
6888 | 0 | if ( nStyle & DrawTextFlags::MultiLine ) |
6889 | 0 | { |
6890 | 0 | ImplMultiTextLineInfo aMultiLineInfo; |
6891 | 0 | sal_Int32 i; |
6892 | 0 | sal_Int32 nFormatLines; |
6893 | |
|
6894 | 0 | if ( nTextHeight ) |
6895 | 0 | { |
6896 | 0 | vcl::DefaultTextLayout aLayout( *this ); |
6897 | 0 | OUString aLastLine; |
6898 | 0 | aLayout.GetTextLines( rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle ); |
6899 | 0 | sal_Int32 nLines = nHeight/nTextHeight; |
6900 | 0 | nFormatLines = aMultiLineInfo.Count(); |
6901 | 0 | if ( !nLines ) |
6902 | 0 | nLines = 1; |
6903 | 0 | if ( nFormatLines > nLines ) |
6904 | 0 | { |
6905 | 0 | if ( nStyle & DrawTextFlags::EndEllipsis ) |
6906 | 0 | { |
6907 | | // handle last line |
6908 | 0 | nFormatLines = nLines-1; |
6909 | |
|
6910 | 0 | ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( nFormatLines ); |
6911 | 0 | aLastLine = convertLineEnd(aStr.copy(rLineInfo.GetIndex()), LINEEND_LF); |
6912 | | // replace line feed by space |
6913 | 0 | aLastLine = aLastLine.replace('\n', ' '); |
6914 | 0 | aLastLine = GetEllipsisString( aLastLine, nWidth, nStyle ); |
6915 | 0 | nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom); |
6916 | 0 | nStyle |= DrawTextFlags::Top; |
6917 | 0 | } |
6918 | 0 | } |
6919 | | |
6920 | | // vertical alignment |
6921 | 0 | if ( nStyle & DrawTextFlags::Bottom ) |
6922 | 0 | aPos.AdjustY(nHeight-(nFormatLines*nTextHeight) ); |
6923 | 0 | else if ( nStyle & DrawTextFlags::VCenter ) |
6924 | 0 | aPos.AdjustY((nHeight-(nFormatLines*nTextHeight))/2 ); |
6925 | | |
6926 | | // draw all lines excluding the last |
6927 | 0 | for ( i = 0; i < nFormatLines; i++ ) |
6928 | 0 | { |
6929 | 0 | ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i ); |
6930 | 0 | if ( nStyle & DrawTextFlags::Right ) |
6931 | 0 | aPos.AdjustX(nWidth-rLineInfo.GetWidth() ); |
6932 | 0 | else if ( nStyle & DrawTextFlags::Center ) |
6933 | 0 | aPos.AdjustX((nWidth-rLineInfo.GetWidth())/2 ); |
6934 | 0 | sal_Int32 nIndex = rLineInfo.GetIndex(); |
6935 | 0 | sal_Int32 nLineLen = rLineInfo.GetLen(); |
6936 | 0 | drawText( aPos, aStr, nIndex, nLineLen ); |
6937 | | // mnemonics should not appear in documents, |
6938 | | // if the need arises, put them in here |
6939 | 0 | aPos.AdjustY(nTextHeight ); |
6940 | 0 | aPos.setX( rRect.Left() ); |
6941 | 0 | } |
6942 | | |
6943 | | // output last line left adjusted since it was shortened |
6944 | 0 | if (!aLastLine.isEmpty()) |
6945 | 0 | drawText( aPos, aLastLine, 0, aLastLine.getLength() ); |
6946 | 0 | } |
6947 | 0 | } |
6948 | 0 | else |
6949 | 0 | { |
6950 | 0 | tools::Long nTextWidth = GetTextWidth( aStr ); |
6951 | | |
6952 | | // Evt. Text kuerzen |
6953 | 0 | if ( nTextWidth > nWidth ) |
6954 | 0 | { |
6955 | 0 | if ( nStyle & (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis) ) |
6956 | 0 | { |
6957 | 0 | aStr = GetEllipsisString( aStr, nWidth, nStyle ); |
6958 | 0 | nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right); |
6959 | 0 | nStyle |= DrawTextFlags::Left; |
6960 | 0 | nTextWidth = GetTextWidth( aStr ); |
6961 | 0 | } |
6962 | 0 | } |
6963 | | |
6964 | | // vertical alignment |
6965 | 0 | if ( nStyle & DrawTextFlags::Right ) |
6966 | 0 | aPos.AdjustX(nWidth-nTextWidth ); |
6967 | 0 | else if ( nStyle & DrawTextFlags::Center ) |
6968 | 0 | aPos.AdjustX((nWidth-nTextWidth)/2 ); |
6969 | |
|
6970 | 0 | if ( nStyle & DrawTextFlags::Bottom ) |
6971 | 0 | aPos.AdjustY(nHeight-nTextHeight ); |
6972 | 0 | else if ( nStyle & DrawTextFlags::VCenter ) |
6973 | 0 | aPos.AdjustY((nHeight-nTextHeight)/2 ); |
6974 | | |
6975 | | // mnemonics should be inserted here if the need arises |
6976 | | |
6977 | | // draw the actual text |
6978 | 0 | drawText( aPos, aStr, 0, aStr.getLength() ); |
6979 | 0 | } |
6980 | | |
6981 | | // reset clip region to original value |
6982 | 0 | aLine.setLength( 0 ); |
6983 | 0 | aLine.append( "Q\n" ); |
6984 | 0 | writeBuffer( aLine ); |
6985 | 0 | } |
6986 | | |
6987 | | void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop ) |
6988 | 0 | { |
6989 | 0 | MARK( "drawLine" ); |
6990 | |
|
6991 | 0 | updateGraphicsState(); |
6992 | |
|
6993 | 0 | if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT ) |
6994 | 0 | return; |
6995 | | |
6996 | 0 | OStringBuffer aLine; |
6997 | 0 | m_aPages.back().appendPoint( rStart, aLine ); |
6998 | 0 | aLine.append( " m " ); |
6999 | 0 | m_aPages.back().appendPoint( rStop, aLine ); |
7000 | 0 | aLine.append( " l S\n" ); |
7001 | |
|
7002 | 0 | writeBuffer( aLine ); |
7003 | 0 | } |
7004 | | |
7005 | | void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo ) |
7006 | 0 | { |
7007 | 0 | MARK( "drawLine with LineInfo" ); |
7008 | 0 | updateGraphicsState(); |
7009 | |
|
7010 | 0 | if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT ) |
7011 | 0 | return; |
7012 | | |
7013 | 0 | if( rInfo.GetStyle() == LineStyle::Solid && rInfo.GetWidth() < 2 ) |
7014 | 0 | { |
7015 | 0 | drawLine( rStart, rStop ); |
7016 | 0 | return; |
7017 | 0 | } |
7018 | | |
7019 | 0 | OStringBuffer aLine; |
7020 | |
|
7021 | 0 | aLine.append( "q " ); |
7022 | 0 | if( m_aPages.back().appendLineInfo( rInfo, aLine ) ) |
7023 | 0 | { |
7024 | 0 | m_aPages.back().appendPoint( rStart, aLine ); |
7025 | 0 | aLine.append( " m " ); |
7026 | 0 | m_aPages.back().appendPoint( rStop, aLine ); |
7027 | 0 | aLine.append( " l S Q\n" ); |
7028 | |
|
7029 | 0 | writeBuffer( aLine ); |
7030 | 0 | } |
7031 | 0 | else |
7032 | 0 | { |
7033 | 0 | PDFWriter::ExtLineInfo aInfo; |
7034 | 0 | convertLineInfoToExtLineInfo( rInfo, aInfo ); |
7035 | 0 | Point aPolyPoints[2] = { rStart, rStop }; |
7036 | 0 | tools::Polygon aPoly( 2, aPolyPoints ); |
7037 | 0 | drawPolyLine( aPoly, aInfo ); |
7038 | 0 | } |
7039 | 0 | } |
7040 | | |
7041 | 32.6k | #define HCONV(x) DevicePixelToLogicHeight(x) |
7042 | | |
7043 | | void PDFWriterImpl::drawWaveTextLine( OStringBuffer& aLine, tools::Long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove ) |
7044 | 0 | { |
7045 | | // note: units in pFontInstance are ref device pixel |
7046 | 0 | const LogicalFontInstance* pFontInstance = GetFontInstance(); |
7047 | 0 | tools::Long nLineHeight = 0; |
7048 | 0 | tools::Long nLinePos = 0; |
7049 | |
|
7050 | 0 | appendStrokingColor( aColor, aLine ); |
7051 | 0 | aLine.append( "\n" ); |
7052 | |
|
7053 | 0 | if ( bIsAbove ) |
7054 | 0 | { |
7055 | 0 | if ( !pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() ) |
7056 | 0 | ImplInitAboveTextLineSize(); |
7057 | 0 | nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() ); |
7058 | 0 | nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineOffset() ); |
7059 | 0 | } |
7060 | 0 | else |
7061 | 0 | { |
7062 | 0 | if ( !pFontInstance->mxFontMetric->GetWavelineUnderlineSize() ) |
7063 | 0 | ImplInitTextLineSize(); |
7064 | 0 | nLineHeight = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineSize() ); |
7065 | 0 | nLinePos = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineOffset() ); |
7066 | 0 | } |
7067 | 0 | if ( (eTextLine == LINESTYLE_SMALLWAVE) && (nLineHeight > 3) ) |
7068 | 0 | nLineHeight = 3; |
7069 | |
|
7070 | 0 | tools::Long nLineWidth = GetDPIX()/450; |
7071 | 0 | if ( ! nLineWidth ) |
7072 | 0 | nLineWidth = 1; |
7073 | |
|
7074 | 0 | if ( eTextLine == LINESTYLE_BOLDWAVE ) |
7075 | 0 | nLineWidth = 3*nLineWidth; |
7076 | |
|
7077 | 0 | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineWidth), aLine ); |
7078 | 0 | aLine.append( " w " ); |
7079 | |
|
7080 | 0 | if ( eTextLine == LINESTYLE_DOUBLEWAVE ) |
7081 | 0 | { |
7082 | 0 | tools::Long nOrgLineHeight = nLineHeight; |
7083 | 0 | nLineHeight /= 3; |
7084 | 0 | if ( nLineHeight < 2 ) |
7085 | 0 | { |
7086 | 0 | if ( nOrgLineHeight > 1 ) |
7087 | 0 | nLineHeight = 2; |
7088 | 0 | else |
7089 | 0 | nLineHeight = 1; |
7090 | 0 | } |
7091 | 0 | tools::Long nLineDY = nOrgLineHeight-(nLineHeight*2); |
7092 | 0 | if ( nLineDY < nLineWidth ) |
7093 | 0 | nLineDY = nLineWidth; |
7094 | 0 | tools::Long nLineDY2 = nLineDY/2; |
7095 | 0 | if ( !nLineDY2 ) |
7096 | 0 | nLineDY2 = 1; |
7097 | |
|
7098 | 0 | nLinePos -= nLineWidth-nLineDY2; |
7099 | |
|
7100 | 0 | m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine ); |
7101 | |
|
7102 | 0 | nLinePos += nLineWidth+nLineDY; |
7103 | 0 | m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine ); |
7104 | 0 | } |
7105 | 0 | else |
7106 | 0 | { |
7107 | 0 | if ( eTextLine != LINESTYLE_BOLDWAVE ) |
7108 | 0 | nLinePos -= nLineWidth/2; |
7109 | 0 | m_aPages.back().appendWaveLine( nWidth, -nLinePos, nLineHeight, aLine ); |
7110 | 0 | } |
7111 | 0 | } |
7112 | | |
7113 | | void PDFWriterImpl::drawStraightTextLine( OStringBuffer& aLine, tools::Long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove ) |
7114 | 21.7k | { |
7115 | | // note: units in pFontInstance are ref device pixel |
7116 | 21.7k | const LogicalFontInstance* pFontInstance = GetFontInstance(); |
7117 | 21.7k | tools::Long nLineHeight = 0; |
7118 | 21.7k | tools::Long nLinePos = 0; |
7119 | 21.7k | tools::Long nLinePos2 = 0; |
7120 | | |
7121 | 21.7k | if ( eTextLine > LINESTYLE_BOLDWAVE ) |
7122 | 0 | eTextLine = LINESTYLE_SINGLE; |
7123 | | |
7124 | 21.7k | switch ( eTextLine ) |
7125 | 21.7k | { |
7126 | 5.04k | case LINESTYLE_SINGLE: |
7127 | 5.06k | case LINESTYLE_DOTTED: |
7128 | 5.06k | case LINESTYLE_DASH: |
7129 | 5.06k | case LINESTYLE_LONGDASH: |
7130 | 5.06k | case LINESTYLE_DASHDOT: |
7131 | 5.06k | case LINESTYLE_DASHDOTDOT: |
7132 | 5.06k | if ( bIsAbove ) |
7133 | 0 | { |
7134 | 0 | if ( !pFontInstance->mxFontMetric->GetAboveUnderlineSize() ) |
7135 | 0 | ImplInitAboveTextLineSize(); |
7136 | 0 | nLineHeight = pFontInstance->mxFontMetric->GetAboveUnderlineSize(); |
7137 | 0 | nLinePos = pFontInstance->mxFontMetric->GetAboveUnderlineOffset(); |
7138 | 0 | } |
7139 | 5.06k | else |
7140 | 5.06k | { |
7141 | 5.06k | if ( !pFontInstance->mxFontMetric->GetUnderlineSize() ) |
7142 | 0 | ImplInitTextLineSize(); |
7143 | 5.06k | nLineHeight = pFontInstance->mxFontMetric->GetUnderlineSize(); |
7144 | 5.06k | nLinePos = pFontInstance->mxFontMetric->GetUnderlineOffset(); |
7145 | 5.06k | } |
7146 | 5.06k | break; |
7147 | 0 | case LINESTYLE_BOLD: |
7148 | 0 | case LINESTYLE_BOLDDOTTED: |
7149 | 0 | case LINESTYLE_BOLDDASH: |
7150 | 0 | case LINESTYLE_BOLDLONGDASH: |
7151 | 0 | case LINESTYLE_BOLDDASHDOT: |
7152 | 0 | case LINESTYLE_BOLDDASHDOTDOT: |
7153 | 0 | if ( bIsAbove ) |
7154 | 0 | { |
7155 | 0 | if ( !pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() ) |
7156 | 0 | ImplInitAboveTextLineSize(); |
7157 | 0 | nLineHeight = pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize(); |
7158 | 0 | nLinePos = pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset(); |
7159 | 0 | } |
7160 | 0 | else |
7161 | 0 | { |
7162 | 0 | if ( !pFontInstance->mxFontMetric->GetBoldUnderlineSize() ) |
7163 | 0 | ImplInitTextLineSize(); |
7164 | 0 | nLineHeight = pFontInstance->mxFontMetric->GetBoldUnderlineSize(); |
7165 | 0 | nLinePos = pFontInstance->mxFontMetric->GetBoldUnderlineOffset(); |
7166 | 0 | } |
7167 | 0 | break; |
7168 | 5.16k | case LINESTYLE_DOUBLE: |
7169 | 5.16k | if ( bIsAbove ) |
7170 | 0 | { |
7171 | 0 | if ( !pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() ) |
7172 | 0 | ImplInitAboveTextLineSize(); |
7173 | 0 | nLineHeight = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize(); |
7174 | 0 | nLinePos = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1(); |
7175 | 0 | nLinePos2 = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2(); |
7176 | 0 | } |
7177 | 5.16k | else |
7178 | 5.16k | { |
7179 | 5.16k | if ( !pFontInstance->mxFontMetric->GetDoubleUnderlineSize() ) |
7180 | 0 | ImplInitTextLineSize(); |
7181 | 5.16k | nLineHeight = pFontInstance->mxFontMetric->GetDoubleUnderlineSize(); |
7182 | 5.16k | nLinePos = pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1(); |
7183 | 5.16k | nLinePos2 = pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2(); |
7184 | 5.16k | } |
7185 | 5.16k | break; |
7186 | 11.5k | default: |
7187 | 11.5k | break; |
7188 | 21.7k | } |
7189 | | |
7190 | 21.7k | if ( !nLineHeight ) |
7191 | 11.5k | return; |
7192 | | |
7193 | | // tdf#154235 |
7194 | | // nLinePos/nLinePos2 is the distance from baseline to the top of the line, |
7195 | | // while in PDF we stroke the line so the position is to the middle of the |
7196 | | // line, we add half of nLineHeight to account for that. |
7197 | 10.2k | auto nOffset = nLineHeight / 2; |
7198 | 10.2k | if (m_aCurrentPDFState.m_aFont.IsOutline() && eTextLine == LINESTYLE_SINGLE) |
7199 | 2 | { |
7200 | | // Except when outlining, as now we are drawing a rectangle, so |
7201 | | // nLinePos is the bottom of the rectangle, so need to add nLineHeight |
7202 | | // to it. |
7203 | 2 | nOffset = nLineHeight; |
7204 | 2 | } |
7205 | | |
7206 | 10.2k | nLineHeight = HCONV(nLineHeight); |
7207 | 10.2k | nLinePos = HCONV(nLinePos + nOffset); |
7208 | 10.2k | nLinePos2 = HCONV(nLinePos2 + nOffset); |
7209 | | |
7210 | | // outline attribute ? |
7211 | 10.2k | if (m_aCurrentPDFState.m_aFont.IsOutline() && eTextLine == LINESTYLE_SINGLE) |
7212 | 2 | { |
7213 | 2 | appendStrokingColor(aColor, aLine); // stroke with text color |
7214 | 2 | aLine.append( " " ); |
7215 | 2 | appendNonStrokingColor(COL_WHITE, aLine); // fill with white |
7216 | 2 | aLine.append( "\n" ); |
7217 | 2 | aLine.append( "0.25 w \n" ); // same line thickness as in drawLayout |
7218 | | |
7219 | | // draw rectangle instead |
7220 | 2 | aLine.append( "0 " ); |
7221 | 2 | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine ); |
7222 | 2 | aLine.append( " " ); |
7223 | 2 | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false ); |
7224 | 2 | aLine.append( ' ' ); |
7225 | 2 | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine ); |
7226 | 2 | aLine.append( " re h B\n" ); |
7227 | 2 | return; |
7228 | 2 | } |
7229 | | |
7230 | | |
7231 | 10.2k | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine ); |
7232 | 10.2k | aLine.append( " w " ); |
7233 | 10.2k | appendStrokingColor( aColor, aLine ); |
7234 | 10.2k | aLine.append( "\n" ); |
7235 | | |
7236 | 10.2k | switch ( eTextLine ) |
7237 | 10.2k | { |
7238 | 18 | case LINESTYLE_DOTTED: |
7239 | 18 | case LINESTYLE_BOLDDOTTED: |
7240 | 18 | aLine.append( "[ " ); |
7241 | 18 | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false ); |
7242 | 18 | aLine.append( " ] 0 d\n" ); |
7243 | 18 | break; |
7244 | 0 | case LINESTYLE_DASH: |
7245 | 0 | case LINESTYLE_LONGDASH: |
7246 | 0 | case LINESTYLE_BOLDDASH: |
7247 | 0 | case LINESTYLE_BOLDLONGDASH: |
7248 | 0 | { |
7249 | 0 | sal_Int32 nDashLength = 4*nLineHeight; |
7250 | 0 | sal_Int32 nVoidLength = 2*nLineHeight; |
7251 | 0 | if ( ( eTextLine == LINESTYLE_LONGDASH ) || ( eTextLine == LINESTYLE_BOLDLONGDASH ) ) |
7252 | 0 | nDashLength = 8*nLineHeight; |
7253 | |
|
7254 | 0 | aLine.append( "[ " ); |
7255 | 0 | m_aPages.back().appendMappedLength( nDashLength, aLine, false ); |
7256 | 0 | aLine.append( ' ' ); |
7257 | 0 | m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); |
7258 | 0 | aLine.append( " ] 0 d\n" ); |
7259 | 0 | } |
7260 | 0 | break; |
7261 | 0 | case LINESTYLE_DASHDOT: |
7262 | 0 | case LINESTYLE_BOLDDASHDOT: |
7263 | 0 | { |
7264 | 0 | sal_Int32 nDashLength = 4*nLineHeight; |
7265 | 0 | sal_Int32 nVoidLength = 2*nLineHeight; |
7266 | 0 | aLine.append( "[ " ); |
7267 | 0 | m_aPages.back().appendMappedLength( nDashLength, aLine, false ); |
7268 | 0 | aLine.append( ' ' ); |
7269 | 0 | m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); |
7270 | 0 | aLine.append( ' ' ); |
7271 | 0 | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false ); |
7272 | 0 | aLine.append( ' ' ); |
7273 | 0 | m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); |
7274 | 0 | aLine.append( " ] 0 d\n" ); |
7275 | 0 | } |
7276 | 0 | break; |
7277 | 0 | case LINESTYLE_DASHDOTDOT: |
7278 | 0 | case LINESTYLE_BOLDDASHDOTDOT: |
7279 | 0 | { |
7280 | 0 | sal_Int32 nDashLength = 4*nLineHeight; |
7281 | 0 | sal_Int32 nVoidLength = 2*nLineHeight; |
7282 | 0 | aLine.append( "[ " ); |
7283 | 0 | m_aPages.back().appendMappedLength( nDashLength, aLine, false ); |
7284 | 0 | aLine.append( ' ' ); |
7285 | 0 | m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); |
7286 | 0 | aLine.append( ' ' ); |
7287 | 0 | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false ); |
7288 | 0 | aLine.append( ' ' ); |
7289 | 0 | m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); |
7290 | 0 | aLine.append( ' ' ); |
7291 | 0 | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false ); |
7292 | 0 | aLine.append( ' ' ); |
7293 | 0 | m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); |
7294 | 0 | aLine.append( " ] 0 d\n" ); |
7295 | 0 | } |
7296 | 0 | break; |
7297 | 10.2k | default: |
7298 | 10.2k | break; |
7299 | 10.2k | } |
7300 | | |
7301 | 10.2k | aLine.append( "0 " ); |
7302 | 10.2k | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine ); |
7303 | 10.2k | aLine.append( " m " ); |
7304 | 10.2k | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false ); |
7305 | 10.2k | aLine.append( ' ' ); |
7306 | 10.2k | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine ); |
7307 | 10.2k | aLine.append( " l S\n" ); |
7308 | 10.2k | if ( eTextLine == LINESTYLE_DOUBLE ) |
7309 | 5.16k | { |
7310 | 5.16k | aLine.append( "0 " ); |
7311 | 5.16k | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine ); |
7312 | 5.16k | aLine.append( " m " ); |
7313 | 5.16k | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false ); |
7314 | 5.16k | aLine.append( ' ' ); |
7315 | 5.16k | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine ); |
7316 | 5.16k | aLine.append( " l S\n" ); |
7317 | 5.16k | } |
7318 | | |
7319 | 10.2k | } |
7320 | | |
7321 | | void PDFWriterImpl::drawStrikeoutLine( OStringBuffer& aLine, tools::Long nWidth, FontStrikeout eStrikeout, Color aColor ) |
7322 | 10.8k | { |
7323 | | // note: units in pFontInstance are ref device pixel |
7324 | 10.8k | const LogicalFontInstance* pFontInstance = GetFontInstance(); |
7325 | 10.8k | tools::Long nLineHeight = 0; |
7326 | 10.8k | tools::Long nLinePos = 0; |
7327 | 10.8k | tools::Long nLinePos2 = 0; |
7328 | | |
7329 | 10.8k | if ( eStrikeout > STRIKEOUT_X ) |
7330 | 0 | eStrikeout = STRIKEOUT_SINGLE; |
7331 | | |
7332 | 10.8k | switch ( eStrikeout ) |
7333 | 10.8k | { |
7334 | 663 | case STRIKEOUT_SINGLE: |
7335 | 663 | if ( !pFontInstance->mxFontMetric->GetStrikeoutSize() ) |
7336 | 0 | ImplInitTextLineSize(); |
7337 | 663 | nLineHeight = pFontInstance->mxFontMetric->GetStrikeoutSize(); |
7338 | 663 | nLinePos = pFontInstance->mxFontMetric->GetStrikeoutOffset(); |
7339 | 663 | break; |
7340 | 0 | case STRIKEOUT_BOLD: |
7341 | 0 | if ( !pFontInstance->mxFontMetric->GetBoldStrikeoutSize() ) |
7342 | 0 | ImplInitTextLineSize(); |
7343 | 0 | nLineHeight = pFontInstance->mxFontMetric->GetBoldStrikeoutSize(); |
7344 | 0 | nLinePos = pFontInstance->mxFontMetric->GetBoldStrikeoutOffset(); |
7345 | 0 | break; |
7346 | 0 | case STRIKEOUT_DOUBLE: |
7347 | 0 | if ( !pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() ) |
7348 | 0 | ImplInitTextLineSize(); |
7349 | 0 | nLineHeight = pFontInstance->mxFontMetric->GetDoubleStrikeoutSize(); |
7350 | 0 | nLinePos = pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1(); |
7351 | 0 | nLinePos2 = pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2(); |
7352 | 0 | break; |
7353 | 10.2k | default: |
7354 | 10.2k | break; |
7355 | 10.8k | } |
7356 | | |
7357 | 10.8k | if ( !nLineHeight ) |
7358 | 10.2k | return; |
7359 | | |
7360 | | // tdf#154235 |
7361 | | // nLinePos/nLinePos2 is the distance from baseline to the bottom of the line, |
7362 | | // while in PDF we stroke the line so the position is to the middle of the |
7363 | | // line, we add half of nLineHeight to account for that. |
7364 | 663 | nLinePos = HCONV(nLinePos + nLineHeight / 2); |
7365 | 663 | nLinePos2 = HCONV(nLinePos2 + nLineHeight / 2); |
7366 | 663 | nLineHeight = HCONV(nLineHeight); |
7367 | | |
7368 | 663 | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine ); |
7369 | 663 | aLine.append( " w " ); |
7370 | 663 | appendStrokingColor( aColor, aLine ); |
7371 | 663 | aLine.append( "\n" ); |
7372 | | |
7373 | 663 | aLine.append( "0 " ); |
7374 | 663 | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine ); |
7375 | 663 | aLine.append( " m " ); |
7376 | 663 | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine ); |
7377 | 663 | aLine.append( ' ' ); |
7378 | 663 | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine ); |
7379 | 663 | aLine.append( " l S\n" ); |
7380 | | |
7381 | 663 | if ( eStrikeout == STRIKEOUT_DOUBLE ) |
7382 | 0 | { |
7383 | 0 | aLine.append( "0 " ); |
7384 | 0 | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine ); |
7385 | 0 | aLine.append( " m " ); |
7386 | 0 | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine ); |
7387 | 0 | aLine.append( ' ' ); |
7388 | 0 | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine ); |
7389 | 0 | aLine.append( " l S\n" ); |
7390 | 0 | } |
7391 | | |
7392 | 663 | } |
7393 | | |
7394 | | void PDFWriterImpl::drawStrikeoutChar( const Point& rPos, tools::Long nWidth, FontStrikeout eStrikeout ) |
7395 | 0 | { |
7396 | | //See qadevOOo/testdocs/StrikeThrough.odt for examples if you need |
7397 | | //to tweak this |
7398 | |
|
7399 | 0 | OUString aStrikeoutChar = eStrikeout == STRIKEOUT_SLASH ? u"/"_ustr : u"X"_ustr; |
7400 | 0 | OUString aStrikeout = aStrikeoutChar; |
7401 | 0 | while( GetTextWidth( aStrikeout ) < nWidth ) |
7402 | 0 | aStrikeout += aStrikeout; |
7403 | | |
7404 | | // do not get broader than nWidth modulo 1 character |
7405 | 0 | while( GetTextWidth( aStrikeout ) >= nWidth ) |
7406 | 0 | aStrikeout = aStrikeout.copy(1); |
7407 | 0 | aStrikeout += aStrikeoutChar; |
7408 | 0 | bool bShadow = m_aCurrentPDFState.m_aFont.IsShadow(); |
7409 | 0 | if ( bShadow ) |
7410 | 0 | { |
7411 | 0 | Font aFont = m_aCurrentPDFState.m_aFont; |
7412 | 0 | aFont.SetShadow( false ); |
7413 | 0 | setFont( aFont ); |
7414 | 0 | updateGraphicsState(); |
7415 | 0 | } |
7416 | |
|
7417 | 0 | push(PushFlags::TEXTLAYOUTMODE | PushFlags::CLIPREGION); |
7418 | | |
7419 | | // strikeout string is left aligned non-CTL text |
7420 | 0 | setLayoutMode(vcl::text::ComplexTextLayoutFlags::BiDiStrong); |
7421 | |
|
7422 | 0 | FontMetric aRefDevFontMetric = GetFontMetric(); |
7423 | 0 | tools::Rectangle aRect; |
7424 | 0 | aRect.SetLeft( rPos.X() ); |
7425 | 0 | aRect.SetRight( aRect.Left()+nWidth ); |
7426 | 0 | aRect.SetBottom( rPos.Y()+aRefDevFontMetric.GetDescent() ); |
7427 | 0 | aRect.SetTop( rPos.Y()-aRefDevFontMetric.GetAscent() ); |
7428 | |
|
7429 | 0 | const LogicalFontInstance* pFontInstance = GetFontInstance(); |
7430 | 0 | if (pFontInstance->mnOrientation) |
7431 | 0 | { |
7432 | 0 | tools::Polygon aPoly( aRect ); |
7433 | 0 | aPoly.Rotate( rPos, pFontInstance->mnOrientation); |
7434 | 0 | aRect = aPoly.GetBoundRect(); |
7435 | 0 | } |
7436 | |
|
7437 | 0 | intersectClipRegion( aRect ); |
7438 | 0 | drawText( rPos, aStrikeout, 0, aStrikeout.getLength(), false ); |
7439 | 0 | pop(); |
7440 | |
|
7441 | 0 | if ( bShadow ) |
7442 | 0 | { |
7443 | 0 | Font aFont = m_aCurrentPDFState.m_aFont; |
7444 | 0 | aFont.SetShadow( true ); |
7445 | 0 | setFont( aFont ); |
7446 | 0 | updateGraphicsState(); |
7447 | 0 | } |
7448 | 0 | } |
7449 | | |
7450 | | void PDFWriterImpl::drawTextLine( const Point& rPos, tools::Long nWidth, FontStrikeout eStrikeout, FontLineStyle eUnderline, FontLineStyle eOverline, bool bUnderlineAbove ) |
7451 | 10.8k | { |
7452 | 10.8k | if ( !nWidth || |
7453 | 10.8k | ( ((eStrikeout == STRIKEOUT_NONE)||(eStrikeout == STRIKEOUT_DONTKNOW)) && |
7454 | 10.2k | ((eUnderline == LINESTYLE_NONE)||(eUnderline == LINESTYLE_DONTKNOW)) && |
7455 | 0 | ((eOverline == LINESTYLE_NONE)||(eOverline == LINESTYLE_DONTKNOW)) ) ) |
7456 | 3 | return; |
7457 | | |
7458 | 10.8k | MARK( "drawTextLine" ); |
7459 | 10.8k | updateGraphicsState(); |
7460 | | |
7461 | | // note: units in pFontInstance are ref device pixel |
7462 | 10.8k | const LogicalFontInstance* pFontInstance = GetFontInstance(); |
7463 | 10.8k | Color aUnderlineColor = m_aCurrentPDFState.m_aTextLineColor; |
7464 | 10.8k | Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor; |
7465 | 10.8k | Color aStrikeoutColor = m_aCurrentPDFState.m_aFont.GetColor(); |
7466 | 10.8k | bool bStrikeoutDone = false; |
7467 | 10.8k | bool bUnderlineDone = false; |
7468 | 10.8k | bool bOverlineDone = false; |
7469 | | |
7470 | 10.8k | if ( (eStrikeout == STRIKEOUT_SLASH) || (eStrikeout == STRIKEOUT_X) ) |
7471 | 0 | { |
7472 | 0 | drawStrikeoutChar( rPos, nWidth, eStrikeout ); |
7473 | 0 | bStrikeoutDone = true; |
7474 | 0 | } |
7475 | | |
7476 | 10.8k | Point aPos( rPos ); |
7477 | 10.8k | TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment(); |
7478 | 10.8k | if( eAlign == ALIGN_TOP ) |
7479 | 0 | aPos.AdjustY(HCONV( pFontInstance->mxFontMetric->GetAscent() )); |
7480 | 10.8k | else if( eAlign == ALIGN_BOTTOM ) |
7481 | 0 | aPos.AdjustY( -HCONV( pFontInstance->mxFontMetric->GetDescent() ) ); |
7482 | | |
7483 | 10.8k | OStringBuffer aLine( 512 ); |
7484 | | // save GS |
7485 | 10.8k | aLine.append( "q " ); |
7486 | | |
7487 | | // rotate and translate matrix |
7488 | 10.8k | double fAngle = toRadians(m_aCurrentPDFState.m_aFont.GetOrientation()); |
7489 | 10.8k | Matrix3 aMat; |
7490 | 10.8k | aMat.rotate( fAngle ); |
7491 | 10.8k | aMat.translate( aPos.X(), aPos.Y() ); |
7492 | 10.8k | m_aPages.back().appendMatrix3(aMat, aLine); |
7493 | 10.8k | aLine.append( " cm\n" ); |
7494 | | |
7495 | 10.8k | if ( aUnderlineColor.IsTransparent() ) |
7496 | 10.7k | aUnderlineColor = aStrikeoutColor; |
7497 | | |
7498 | 10.8k | if ( aOverlineColor.IsTransparent() ) |
7499 | 10.7k | aOverlineColor = aStrikeoutColor; |
7500 | | |
7501 | 10.8k | if ( (eUnderline == LINESTYLE_SMALLWAVE) || |
7502 | 10.8k | (eUnderline == LINESTYLE_WAVE) || |
7503 | 10.8k | (eUnderline == LINESTYLE_DOUBLEWAVE) || |
7504 | 10.8k | (eUnderline == LINESTYLE_BOLDWAVE) ) |
7505 | 0 | { |
7506 | 0 | drawWaveTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove ); |
7507 | 0 | bUnderlineDone = true; |
7508 | 0 | } |
7509 | | |
7510 | 10.8k | if ( (eOverline == LINESTYLE_SMALLWAVE) || |
7511 | 10.8k | (eOverline == LINESTYLE_WAVE) || |
7512 | 10.8k | (eOverline == LINESTYLE_DOUBLEWAVE) || |
7513 | 10.8k | (eOverline == LINESTYLE_BOLDWAVE) ) |
7514 | 0 | { |
7515 | 0 | drawWaveTextLine( aLine, nWidth, eOverline, aOverlineColor, true ); |
7516 | 0 | bOverlineDone = true; |
7517 | 0 | } |
7518 | | |
7519 | 10.8k | if ( !bUnderlineDone ) |
7520 | 10.8k | { |
7521 | 10.8k | drawStraightTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove ); |
7522 | 10.8k | } |
7523 | | |
7524 | 10.8k | if ( !bOverlineDone ) |
7525 | 10.8k | { |
7526 | 10.8k | drawStraightTextLine( aLine, nWidth, eOverline, aOverlineColor, true ); |
7527 | 10.8k | } |
7528 | | |
7529 | 10.8k | if ( !bStrikeoutDone ) |
7530 | 10.8k | { |
7531 | 10.8k | drawStrikeoutLine( aLine, nWidth, eStrikeout, aStrikeoutColor ); |
7532 | 10.8k | } |
7533 | | |
7534 | 10.8k | aLine.append( "Q\n" ); |
7535 | 10.8k | writeBuffer( aLine ); |
7536 | 10.8k | } |
7537 | | |
7538 | | void PDFWriterImpl::drawPolygon( const tools::Polygon& rPoly ) |
7539 | 0 | { |
7540 | 0 | MARK( "drawPolygon" ); |
7541 | |
|
7542 | 0 | updateGraphicsState(); |
7543 | |
|
7544 | 0 | if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT && |
7545 | 0 | m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT ) |
7546 | 0 | return; |
7547 | | |
7548 | 0 | int nPoints = rPoly.GetSize(); |
7549 | 0 | OStringBuffer aLine( 20 * nPoints ); |
7550 | 0 | m_aPages.back().appendPolygon( rPoly, aLine ); |
7551 | 0 | if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT && |
7552 | 0 | m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT ) |
7553 | 0 | aLine.append( "B*\n" ); |
7554 | 0 | else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) |
7555 | 0 | aLine.append( "S\n" ); |
7556 | 0 | else |
7557 | 0 | aLine.append( "f*\n" ); |
7558 | |
|
7559 | 0 | writeBuffer( aLine ); |
7560 | 0 | } |
7561 | | |
7562 | | void PDFWriterImpl::drawPolyPolygon( const tools::PolyPolygon& rPolyPoly ) |
7563 | 3.54k | { |
7564 | 3.54k | MARK( "drawPolyPolygon" ); |
7565 | | |
7566 | 3.54k | updateGraphicsState(); |
7567 | | |
7568 | 3.54k | if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT && |
7569 | 1.13k | m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT ) |
7570 | 0 | return; |
7571 | | |
7572 | 3.54k | int nPolygons = rPolyPoly.Count(); |
7573 | | |
7574 | 3.54k | OStringBuffer aLine( 40 * nPolygons ); |
7575 | 3.54k | m_aPages.back().appendPolyPolygon( rPolyPoly, aLine ); |
7576 | 3.54k | if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT && |
7577 | 2.41k | m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT ) |
7578 | 2.41k | aLine.append( "B*\n" ); |
7579 | 1.13k | else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) |
7580 | 0 | aLine.append( "S\n" ); |
7581 | 1.13k | else |
7582 | 1.13k | aLine.append( "f*\n" ); |
7583 | | |
7584 | 3.54k | writeBuffer( aLine ); |
7585 | 3.54k | } |
7586 | | |
7587 | | void PDFWriterImpl::drawTransparent( const tools::PolyPolygon& rPolyPoly, sal_uInt32 nTransparentPercent ) |
7588 | 0 | { |
7589 | 0 | SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" ); |
7590 | 0 | nTransparentPercent = nTransparentPercent % 100; |
7591 | |
|
7592 | 0 | MARK( "drawTransparent" ); |
7593 | |
|
7594 | 0 | updateGraphicsState(); |
7595 | |
|
7596 | 0 | if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT && |
7597 | 0 | m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT ) |
7598 | 0 | return; |
7599 | | |
7600 | 0 | if( m_bIsPDF_A1 || m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 ) |
7601 | 0 | { |
7602 | 0 | m_aErrors.insert( m_bIsPDF_A1 ? |
7603 | 0 | PDFWriter::Warning_Transparency_Omitted_PDFA : |
7604 | 0 | PDFWriter::Warning_Transparency_Omitted_PDF13 ); |
7605 | |
|
7606 | 0 | drawPolyPolygon( rPolyPoly ); |
7607 | 0 | return; |
7608 | 0 | } |
7609 | | |
7610 | | // create XObject |
7611 | 0 | m_aTransparentObjects.emplace_back( ); |
7612 | | // FIXME: polygons with beziers may yield incorrect bound rect |
7613 | 0 | m_aTransparentObjects.back().m_aBoundRect = rPolyPoly.GetBoundRect(); |
7614 | | // convert rectangle to default user space |
7615 | 0 | m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect ); |
7616 | 0 | m_aTransparentObjects.back().m_nObject = createObject(); |
7617 | 0 | m_aTransparentObjects.back().m_nExtGStateObject = createObject(); |
7618 | 0 | m_aTransparentObjects.back().m_fAlpha = (100-nTransparentPercent) / 100.0; |
7619 | 0 | m_aTransparentObjects.back().m_pContentStream.reset(new SvMemoryStream( 256, 256 )); |
7620 | | // create XObject's content stream |
7621 | 0 | OStringBuffer aContent( 256 ); |
7622 | 0 | m_aPages.back().appendPolyPolygon( rPolyPoly, aContent ); |
7623 | 0 | if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT && |
7624 | 0 | m_aCurrentPDFState.m_aFillColor != COL_TRANSPARENT ) |
7625 | 0 | aContent.append( " B*\n" ); |
7626 | 0 | else if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT ) |
7627 | 0 | aContent.append( " S\n" ); |
7628 | 0 | else |
7629 | 0 | aContent.append( " f*\n" ); |
7630 | 0 | m_aTransparentObjects.back().m_pContentStream->WriteBytes( |
7631 | 0 | aContent.getStr(), aContent.getLength() ); |
7632 | |
|
7633 | 0 | OStringBuffer aObjName( 16 ); |
7634 | 0 | aObjName.append( "Tr" ); |
7635 | 0 | aObjName.append( m_aTransparentObjects.back().m_nObject ); |
7636 | 0 | OString aTrName( aObjName.makeStringAndClear() ); |
7637 | 0 | aObjName.append( "EGS" ); |
7638 | 0 | aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject ); |
7639 | 0 | OString aExtName( aObjName.makeStringAndClear() ); |
7640 | |
|
7641 | 0 | OString aLine = |
7642 | | // insert XObject |
7643 | 0 | "q /" + |
7644 | 0 | aExtName + |
7645 | 0 | " gs /" + |
7646 | 0 | aTrName + |
7647 | 0 | " Do Q\n"; |
7648 | 0 | writeBuffer( aLine ); |
7649 | |
|
7650 | 0 | pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject ); |
7651 | 0 | pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject ); |
7652 | 0 | } |
7653 | | |
7654 | | void PDFWriterImpl::pushResource(ResourceKind eKind, const OString& rResource, sal_Int32 nObject, ResourceDict& rResourceDict, std::list<StreamRedirect>& rOutputStreams) |
7655 | 12 | { |
7656 | 12 | if( nObject < 0 ) |
7657 | 0 | return; |
7658 | | |
7659 | 12 | switch( eKind ) |
7660 | 12 | { |
7661 | 6 | case ResourceKind::XObject: |
7662 | 6 | rResourceDict.m_aXObjects[rResource] = nObject; |
7663 | 6 | if (!rOutputStreams.empty()) |
7664 | 0 | rOutputStreams.front().m_aResourceDict.m_aXObjects[rResource] = nObject; |
7665 | 6 | break; |
7666 | 6 | case ResourceKind::ExtGState: |
7667 | 6 | rResourceDict.m_aExtGStates[rResource] = nObject; |
7668 | 6 | if (!rOutputStreams.empty()) |
7669 | 0 | rOutputStreams.front().m_aResourceDict.m_aExtGStates[rResource] = nObject; |
7670 | 6 | break; |
7671 | 0 | case ResourceKind::Shading: |
7672 | 0 | rResourceDict.m_aShadings[rResource] = nObject; |
7673 | 0 | if (!rOutputStreams.empty()) |
7674 | 0 | rOutputStreams.front().m_aResourceDict.m_aShadings[rResource] = nObject; |
7675 | 0 | break; |
7676 | 0 | case ResourceKind::Pattern: |
7677 | 0 | rResourceDict.m_aPatterns[rResource] = nObject; |
7678 | 0 | if (!rOutputStreams.empty()) |
7679 | 0 | rOutputStreams.front().m_aResourceDict.m_aPatterns[rResource] = nObject; |
7680 | 0 | break; |
7681 | 12 | } |
7682 | 12 | } |
7683 | | |
7684 | | void PDFWriterImpl::pushResource( ResourceKind eKind, const OString& rResource, sal_Int32 nObject ) |
7685 | 12 | { |
7686 | 12 | pushResource(eKind, rResource, nObject, m_aGlobalResourceDict, m_aOutputStreams); |
7687 | 12 | } |
7688 | | |
7689 | | void PDFWriterImpl::beginRedirect( SvStream* pStream, const tools::Rectangle& rTargetRect ) |
7690 | 6 | { |
7691 | 6 | push( PushFlags::ALL ); |
7692 | | |
7693 | | // force reemitting clip region inside the new stream, and |
7694 | | // prevent emitting an unbalanced "Q" at the start |
7695 | 6 | clearClipRegion(); |
7696 | | // this is needed to point m_aCurrentPDFState at the pushed state |
7697 | | // ... but it's pointless to actually write into the "outer" stream here! |
7698 | 6 | updateGraphicsState(Mode::NOWRITE); |
7699 | | |
7700 | 6 | m_aOutputStreams.push_front( StreamRedirect() ); |
7701 | 6 | m_aOutputStreams.front().m_pStream = pStream; |
7702 | 6 | m_aOutputStreams.front().m_aMapMode = m_aMapMode; |
7703 | | |
7704 | 6 | if( !rTargetRect.IsEmpty() ) |
7705 | 0 | { |
7706 | 0 | m_aOutputStreams.front().m_aTargetRect = |
7707 | 0 | convert( m_aGraphicsStack.front().m_aMapMode, |
7708 | 0 | m_aMapMode, |
7709 | 0 | this, |
7710 | 0 | rTargetRect ); |
7711 | 0 | Point aDelta = m_aOutputStreams.front().m_aTargetRect.BottomLeft(); |
7712 | 0 | tools::Long nPageHeight = pointToPixel(m_aPages[m_nCurrentPage].getHeight()); |
7713 | 0 | aDelta.setY( -(nPageHeight - m_aOutputStreams.front().m_aTargetRect.Bottom()) ); |
7714 | 0 | m_aMapMode.SetOrigin( m_aMapMode.GetOrigin() + aDelta ); |
7715 | 0 | } |
7716 | | |
7717 | | // setup graphics state for independent object stream |
7718 | | |
7719 | | // force reemitting colors |
7720 | 6 | m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT; |
7721 | 6 | m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT; |
7722 | 6 | } |
7723 | | |
7724 | | SvStream* PDFWriterImpl::endRedirect() |
7725 | 6 | { |
7726 | 6 | SvStream* pStream = nullptr; |
7727 | 6 | if( ! m_aOutputStreams.empty() ) |
7728 | 6 | { |
7729 | 6 | pStream = m_aOutputStreams.front().m_pStream; |
7730 | 6 | m_aMapMode = m_aOutputStreams.front().m_aMapMode; |
7731 | 6 | m_aOutputStreams.pop_front(); |
7732 | 6 | } |
7733 | | |
7734 | 6 | pop(); |
7735 | | |
7736 | 6 | m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT; |
7737 | 6 | m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT; |
7738 | | |
7739 | | // needed after pop() to set m_aCurrentPDFState |
7740 | 6 | updateGraphicsState(Mode::NOWRITE); |
7741 | | |
7742 | 6 | return pStream; |
7743 | 6 | } |
7744 | | |
7745 | | void PDFWriterImpl::beginTransparencyGroup() |
7746 | 6 | { |
7747 | 6 | updateGraphicsState(); |
7748 | 6 | if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 ) |
7749 | 6 | beginRedirect( new SvMemoryStream( PDFWRITER_IMPL_BUFFERSIZE, PDFWRITER_IMPL_BUFFERSIZE ), tools::Rectangle() ); |
7750 | 6 | } |
7751 | | |
7752 | | void PDFWriterImpl::endTransparencyGroup( const tools::Rectangle& rBoundingBox, sal_uInt32 nTransparentPercent ) |
7753 | 6 | { |
7754 | 6 | SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" ); |
7755 | 6 | nTransparentPercent = nTransparentPercent % 100; |
7756 | | |
7757 | 6 | if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 ) |
7758 | 0 | return; |
7759 | | |
7760 | | // create XObject |
7761 | 6 | m_aTransparentObjects.emplace_back( ); |
7762 | 6 | m_aTransparentObjects.back().m_aBoundRect = rBoundingBox; |
7763 | | // convert rectangle to default user space |
7764 | 6 | m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect ); |
7765 | 6 | m_aTransparentObjects.back().m_nObject = createObject(); |
7766 | 6 | m_aTransparentObjects.back().m_fAlpha = (100-nTransparentPercent) / 100.0; |
7767 | | // get XObject's content stream |
7768 | 6 | m_aTransparentObjects.back().m_pContentStream.reset( static_cast<SvMemoryStream*>(endRedirect()) ); |
7769 | 6 | m_aTransparentObjects.back().m_nExtGStateObject = createObject(); |
7770 | | |
7771 | 6 | OStringBuffer aObjName( 16 ); |
7772 | 6 | aObjName.append( "Tr" ); |
7773 | 6 | aObjName.append( m_aTransparentObjects.back().m_nObject ); |
7774 | 6 | OString aTrName( aObjName.makeStringAndClear() ); |
7775 | 6 | aObjName.append( "EGS" ); |
7776 | 6 | aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject ); |
7777 | 6 | OString aExtName( aObjName.makeStringAndClear() ); |
7778 | | |
7779 | 6 | OString aLine = |
7780 | | // insert XObject |
7781 | 6 | "q /" + |
7782 | 6 | aExtName + |
7783 | 6 | " gs /" + |
7784 | 6 | aTrName + |
7785 | 6 | " Do Q\n"; |
7786 | 6 | writeBuffer( aLine ); |
7787 | | |
7788 | 6 | pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject ); |
7789 | 6 | pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject ); |
7790 | | |
7791 | 6 | } |
7792 | | |
7793 | | void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect ) |
7794 | 74.1k | { |
7795 | 74.1k | MARK( "drawRectangle" ); |
7796 | | |
7797 | 74.1k | updateGraphicsState(); |
7798 | | |
7799 | 74.1k | if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT && |
7800 | 74.1k | m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT ) |
7801 | 73.3k | return; |
7802 | | |
7803 | 761 | OStringBuffer aLine( 40 ); |
7804 | 761 | m_aPages.back().appendRect( rRect, aLine ); |
7805 | | |
7806 | 761 | if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT && |
7807 | 0 | m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT ) |
7808 | 0 | aLine.append( " B*\n" ); |
7809 | 761 | else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) |
7810 | 0 | aLine.append( " S\n" ); |
7811 | 761 | else |
7812 | 761 | aLine.append( " f*\n" ); |
7813 | | |
7814 | 761 | writeBuffer( aLine ); |
7815 | 761 | } |
7816 | | |
7817 | | void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound ) |
7818 | 0 | { |
7819 | 0 | MARK( "drawRectangle with rounded edges" ); |
7820 | |
|
7821 | 0 | if( !nHorzRound && !nVertRound ) |
7822 | 0 | drawRectangle( rRect ); |
7823 | |
|
7824 | 0 | updateGraphicsState(); |
7825 | |
|
7826 | 0 | if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT && |
7827 | 0 | m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT ) |
7828 | 0 | return; |
7829 | | |
7830 | 0 | if( nHorzRound > o3tl::make_unsigned(rRect.GetWidth())/2 ) |
7831 | 0 | nHorzRound = rRect.GetWidth()/2; |
7832 | 0 | if( nVertRound > o3tl::make_unsigned(rRect.GetHeight())/2 ) |
7833 | 0 | nVertRound = rRect.GetHeight()/2; |
7834 | |
|
7835 | 0 | Point aPoints[16]; |
7836 | 0 | const double kappa = 0.5522847498; |
7837 | 0 | const sal_uInt32 kx = (kappa*nHorzRound)+0.5; |
7838 | 0 | const sal_uInt32 ky = (kappa*nVertRound)+0.5; |
7839 | |
|
7840 | 0 | aPoints[1] = Point( rRect.Left() + nHorzRound, rRect.Top() ); |
7841 | 0 | aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() ); |
7842 | 0 | aPoints[2] = Point( rRect.Right()+1 - nHorzRound, aPoints[1].Y() ); |
7843 | 0 | aPoints[3] = Point( aPoints[2].X()+kx, aPoints[2].Y() ); |
7844 | |
|
7845 | 0 | aPoints[5] = Point( rRect.Right()+1, rRect.Top()+nVertRound ); |
7846 | 0 | aPoints[4] = Point( aPoints[5].X(), aPoints[5].Y()-ky ); |
7847 | 0 | aPoints[6] = Point( aPoints[5].X(), rRect.Bottom()+1 - nVertRound ); |
7848 | 0 | aPoints[7] = Point( aPoints[6].X(), aPoints[6].Y()+ky ); |
7849 | |
|
7850 | 0 | aPoints[9] = Point( rRect.Right()+1-nHorzRound, rRect.Bottom()+1 ); |
7851 | 0 | aPoints[8] = Point( aPoints[9].X()+kx, aPoints[9].Y() ); |
7852 | 0 | aPoints[10] = Point( rRect.Left() + nHorzRound, aPoints[9].Y() ); |
7853 | 0 | aPoints[11] = Point( aPoints[10].X()-kx, aPoints[10].Y() ); |
7854 | |
|
7855 | 0 | aPoints[13] = Point( rRect.Left(), rRect.Bottom()+1-nVertRound ); |
7856 | 0 | aPoints[12] = Point( aPoints[13].X(), aPoints[13].Y()+ky ); |
7857 | 0 | aPoints[14] = Point( rRect.Left(), rRect.Top()+nVertRound ); |
7858 | 0 | aPoints[15] = Point( aPoints[14].X(), aPoints[14].Y()-ky ); |
7859 | |
|
7860 | 0 | OStringBuffer aLine( 80 ); |
7861 | 0 | m_aPages.back().appendPoint( aPoints[1], aLine ); |
7862 | 0 | aLine.append( " m " ); |
7863 | 0 | m_aPages.back().appendPoint( aPoints[2], aLine ); |
7864 | 0 | aLine.append( " l " ); |
7865 | 0 | m_aPages.back().appendPoint( aPoints[3], aLine ); |
7866 | 0 | aLine.append( ' ' ); |
7867 | 0 | m_aPages.back().appendPoint( aPoints[4], aLine ); |
7868 | 0 | aLine.append( ' ' ); |
7869 | 0 | m_aPages.back().appendPoint( aPoints[5], aLine ); |
7870 | 0 | aLine.append( " c\n" ); |
7871 | 0 | m_aPages.back().appendPoint( aPoints[6], aLine ); |
7872 | 0 | aLine.append( " l " ); |
7873 | 0 | m_aPages.back().appendPoint( aPoints[7], aLine ); |
7874 | 0 | aLine.append( ' ' ); |
7875 | 0 | m_aPages.back().appendPoint( aPoints[8], aLine ); |
7876 | 0 | aLine.append( ' ' ); |
7877 | 0 | m_aPages.back().appendPoint( aPoints[9], aLine ); |
7878 | 0 | aLine.append( " c\n" ); |
7879 | 0 | m_aPages.back().appendPoint( aPoints[10], aLine ); |
7880 | 0 | aLine.append( " l " ); |
7881 | 0 | m_aPages.back().appendPoint( aPoints[11], aLine ); |
7882 | 0 | aLine.append( ' ' ); |
7883 | 0 | m_aPages.back().appendPoint( aPoints[12], aLine ); |
7884 | 0 | aLine.append( ' ' ); |
7885 | 0 | m_aPages.back().appendPoint( aPoints[13], aLine ); |
7886 | 0 | aLine.append( " c\n" ); |
7887 | 0 | m_aPages.back().appendPoint( aPoints[14], aLine ); |
7888 | 0 | aLine.append( " l " ); |
7889 | 0 | m_aPages.back().appendPoint( aPoints[15], aLine ); |
7890 | 0 | aLine.append( ' ' ); |
7891 | 0 | m_aPages.back().appendPoint( aPoints[0], aLine ); |
7892 | 0 | aLine.append( ' ' ); |
7893 | 0 | m_aPages.back().appendPoint( aPoints[1], aLine ); |
7894 | 0 | aLine.append( " c " ); |
7895 | |
|
7896 | 0 | if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT && |
7897 | 0 | m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT ) |
7898 | 0 | aLine.append( "b*\n" ); |
7899 | 0 | else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) |
7900 | 0 | aLine.append( "s\n" ); |
7901 | 0 | else |
7902 | 0 | aLine.append( "f*\n" ); |
7903 | |
|
7904 | 0 | writeBuffer( aLine ); |
7905 | 0 | } |
7906 | | |
7907 | | void PDFWriterImpl::drawEllipse( const tools::Rectangle& rRect ) |
7908 | 0 | { |
7909 | 0 | MARK( "drawEllipse" ); |
7910 | |
|
7911 | 0 | updateGraphicsState(); |
7912 | |
|
7913 | 0 | if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT && |
7914 | 0 | m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT ) |
7915 | 0 | return; |
7916 | | |
7917 | 0 | Point aPoints[12]; |
7918 | 0 | const double kappa = 0.5522847498; |
7919 | 0 | const sal_uInt32 kx = (kappa*static_cast<double>(rRect.GetWidth())/2.0)+0.5; |
7920 | 0 | const sal_uInt32 ky = (kappa*static_cast<double>(rRect.GetHeight())/2.0)+0.5; |
7921 | |
|
7922 | 0 | aPoints[1] = Point( rRect.Left() + rRect.GetWidth()/2, rRect.Top() ); |
7923 | 0 | aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() ); |
7924 | 0 | aPoints[2] = Point( aPoints[1].X() + kx, aPoints[1].Y() ); |
7925 | |
|
7926 | 0 | aPoints[4] = Point( rRect.Right()+1, rRect.Top() + rRect.GetHeight()/2 ); |
7927 | 0 | aPoints[3] = Point( aPoints[4].X(), aPoints[4].Y() - ky ); |
7928 | 0 | aPoints[5] = Point( aPoints[4].X(), aPoints[4].Y() + ky ); |
7929 | |
|
7930 | 0 | aPoints[7] = Point( rRect.Left() + rRect.GetWidth()/2, rRect.Bottom()+1 ); |
7931 | 0 | aPoints[6] = Point( aPoints[7].X() + kx, aPoints[7].Y() ); |
7932 | 0 | aPoints[8] = Point( aPoints[7].X() - kx, aPoints[7].Y() ); |
7933 | |
|
7934 | 0 | aPoints[10] = Point( rRect.Left(), rRect.Top() + rRect.GetHeight()/2 ); |
7935 | 0 | aPoints[9] = Point( aPoints[10].X(), aPoints[10].Y() + ky ); |
7936 | 0 | aPoints[11] = Point( aPoints[10].X(), aPoints[10].Y() - ky ); |
7937 | |
|
7938 | 0 | OStringBuffer aLine( 80 ); |
7939 | 0 | m_aPages.back().appendPoint( aPoints[1], aLine ); |
7940 | 0 | aLine.append( " m " ); |
7941 | 0 | m_aPages.back().appendPoint( aPoints[2], aLine ); |
7942 | 0 | aLine.append( ' ' ); |
7943 | 0 | m_aPages.back().appendPoint( aPoints[3], aLine ); |
7944 | 0 | aLine.append( ' ' ); |
7945 | 0 | m_aPages.back().appendPoint( aPoints[4], aLine ); |
7946 | 0 | aLine.append( " c\n" ); |
7947 | 0 | m_aPages.back().appendPoint( aPoints[5], aLine ); |
7948 | 0 | aLine.append( ' ' ); |
7949 | 0 | m_aPages.back().appendPoint( aPoints[6], aLine ); |
7950 | 0 | aLine.append( ' ' ); |
7951 | 0 | m_aPages.back().appendPoint( aPoints[7], aLine ); |
7952 | 0 | aLine.append( " c\n" ); |
7953 | 0 | m_aPages.back().appendPoint( aPoints[8], aLine ); |
7954 | 0 | aLine.append( ' ' ); |
7955 | 0 | m_aPages.back().appendPoint( aPoints[9], aLine ); |
7956 | 0 | aLine.append( ' ' ); |
7957 | 0 | m_aPages.back().appendPoint( aPoints[10], aLine ); |
7958 | 0 | aLine.append( " c\n" ); |
7959 | 0 | m_aPages.back().appendPoint( aPoints[11], aLine ); |
7960 | 0 | aLine.append( ' ' ); |
7961 | 0 | m_aPages.back().appendPoint( aPoints[0], aLine ); |
7962 | 0 | aLine.append( ' ' ); |
7963 | 0 | m_aPages.back().appendPoint( aPoints[1], aLine ); |
7964 | 0 | aLine.append( " c " ); |
7965 | |
|
7966 | 0 | if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT && |
7967 | 0 | m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT ) |
7968 | 0 | aLine.append( "b*\n" ); |
7969 | 0 | else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) |
7970 | 0 | aLine.append( "s\n" ); |
7971 | 0 | else |
7972 | 0 | aLine.append( "f*\n" ); |
7973 | |
|
7974 | 0 | writeBuffer( aLine ); |
7975 | 0 | } |
7976 | | |
7977 | | static double calcAngle( const tools::Rectangle& rRect, const Point& rPoint ) |
7978 | 0 | { |
7979 | 0 | Point aOrigin((rRect.Left()+rRect.Right()+1)/2, |
7980 | 0 | (rRect.Top()+rRect.Bottom()+1)/2); |
7981 | 0 | Point aPoint = rPoint - aOrigin; |
7982 | |
|
7983 | 0 | double fX = static_cast<double>(aPoint.X()); |
7984 | 0 | double fY = static_cast<double>(-aPoint.Y()); |
7985 | |
|
7986 | 0 | if ((rRect.GetHeight() == 0) || (rRect.GetWidth() == 0)) |
7987 | 0 | throw o3tl::divide_by_zero(); |
7988 | | |
7989 | 0 | if( rRect.GetWidth() > rRect.GetHeight() ) |
7990 | 0 | fY *= static_cast<double>(rRect.GetWidth())/static_cast<double>(rRect.GetHeight()); |
7991 | 0 | else if( rRect.GetHeight() > rRect.GetWidth() ) |
7992 | 0 | fX *= static_cast<double>(rRect.GetHeight())/static_cast<double>(rRect.GetWidth()); |
7993 | 0 | return atan2( fY, fX ); |
7994 | 0 | } |
7995 | | |
7996 | | void PDFWriterImpl::drawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop, bool bWithPie, bool bWithChord ) |
7997 | 0 | { |
7998 | 0 | MARK( "drawArc" ); |
7999 | |
|
8000 | 0 | updateGraphicsState(); |
8001 | |
|
8002 | 0 | if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT && |
8003 | 0 | m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT ) |
8004 | 0 | return; |
8005 | | |
8006 | | // calculate start and stop angles |
8007 | 0 | const double fStartAngle = calcAngle( rRect, rStart ); |
8008 | 0 | double fStopAngle = calcAngle( rRect, rStop ); |
8009 | 0 | while( fStopAngle < fStartAngle ) |
8010 | 0 | fStopAngle += 2.0*M_PI; |
8011 | 0 | const int nFragments = static_cast<int>((fStopAngle-fStartAngle)/(M_PI/2.0))+1; |
8012 | 0 | const double fFragmentDelta = (fStopAngle-fStartAngle)/nFragments; |
8013 | 0 | const double kappa = fabs( 4.0 * (1.0-cos(fFragmentDelta/2.0))/sin(fFragmentDelta/2.0) / 3.0); |
8014 | 0 | const double halfWidth = static_cast<double>(rRect.GetWidth())/2.0; |
8015 | 0 | const double halfHeight = static_cast<double>(rRect.GetHeight())/2.0; |
8016 | |
|
8017 | 0 | const Point aCenter( (rRect.Left()+rRect.Right()+1)/2, |
8018 | 0 | (rRect.Top()+rRect.Bottom()+1)/2 ); |
8019 | |
|
8020 | 0 | OStringBuffer aLine( 30*nFragments ); |
8021 | 0 | Point aPoint( static_cast<int>(halfWidth * cos(fStartAngle) ), |
8022 | 0 | -static_cast<int>(halfHeight * sin(fStartAngle) ) ); |
8023 | 0 | aPoint += aCenter; |
8024 | 0 | m_aPages.back().appendPoint( aPoint, aLine ); |
8025 | 0 | aLine.append( " m " ); |
8026 | 0 | if( !basegfx::fTools::equal(fStartAngle, fStopAngle) ) |
8027 | 0 | { |
8028 | 0 | for( int i = 0; i < nFragments; i++ ) |
8029 | 0 | { |
8030 | 0 | const double fStartFragment = fStartAngle + i*fFragmentDelta; |
8031 | 0 | const double fStopFragment = fStartFragment + fFragmentDelta; |
8032 | 0 | aPoint = Point( static_cast<int>(halfWidth * (cos(fStartFragment) - kappa*sin(fStartFragment) ) ), |
8033 | 0 | -static_cast<int>(halfHeight * (sin(fStartFragment) + kappa*cos(fStartFragment) ) ) ); |
8034 | 0 | aPoint += aCenter; |
8035 | 0 | m_aPages.back().appendPoint( aPoint, aLine ); |
8036 | 0 | aLine.append( ' ' ); |
8037 | |
|
8038 | 0 | aPoint = Point( static_cast<int>(halfWidth * (cos(fStopFragment) + kappa*sin(fStopFragment) ) ), |
8039 | 0 | -static_cast<int>(halfHeight * (sin(fStopFragment) - kappa*cos(fStopFragment) ) ) ); |
8040 | 0 | aPoint += aCenter; |
8041 | 0 | m_aPages.back().appendPoint( aPoint, aLine ); |
8042 | 0 | aLine.append( ' ' ); |
8043 | |
|
8044 | 0 | aPoint = Point( static_cast<int>(halfWidth * cos(fStopFragment) ), |
8045 | 0 | -static_cast<int>(halfHeight * sin(fStopFragment) ) ); |
8046 | 0 | aPoint += aCenter; |
8047 | 0 | m_aPages.back().appendPoint( aPoint, aLine ); |
8048 | 0 | aLine.append( " c\n" ); |
8049 | 0 | } |
8050 | 0 | } |
8051 | 0 | if( bWithChord || bWithPie ) |
8052 | 0 | { |
8053 | 0 | if( bWithPie ) |
8054 | 0 | { |
8055 | 0 | m_aPages.back().appendPoint( aCenter, aLine ); |
8056 | 0 | aLine.append( " l " ); |
8057 | 0 | } |
8058 | 0 | aLine.append( "h " ); |
8059 | 0 | } |
8060 | 0 | if( ! bWithChord && ! bWithPie ) |
8061 | 0 | aLine.append( "S\n" ); |
8062 | 0 | else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT && |
8063 | 0 | m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT ) |
8064 | 0 | aLine.append( "B*\n" ); |
8065 | 0 | else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) |
8066 | 0 | aLine.append( "S\n" ); |
8067 | 0 | else |
8068 | 0 | aLine.append( "f*\n" ); |
8069 | |
|
8070 | 0 | writeBuffer( aLine ); |
8071 | 0 | } |
8072 | | |
8073 | | void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly ) |
8074 | 13.1k | { |
8075 | 13.1k | MARK( "drawPolyLine" ); |
8076 | | |
8077 | 13.1k | sal_uInt16 nPoints = rPoly.GetSize(); |
8078 | 13.1k | if( nPoints < 2 ) |
8079 | 0 | return; |
8080 | | |
8081 | 13.1k | updateGraphicsState(); |
8082 | | |
8083 | 13.1k | if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT ) |
8084 | 0 | return; |
8085 | | |
8086 | 13.1k | OStringBuffer aLine( 20 * nPoints ); |
8087 | 13.1k | m_aPages.back().appendPolygon( rPoly, aLine, rPoly[0] == rPoly[nPoints-1] ); |
8088 | 13.1k | aLine.append( "S\n" ); |
8089 | | |
8090 | 13.1k | writeBuffer( aLine ); |
8091 | 13.1k | } |
8092 | | |
8093 | | void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const LineInfo& rInfo ) |
8094 | 2 | { |
8095 | 2 | MARK( "drawPolyLine with LineInfo" ); |
8096 | | |
8097 | 2 | updateGraphicsState(); |
8098 | | |
8099 | 2 | if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT ) |
8100 | 0 | return; |
8101 | | |
8102 | 2 | OStringBuffer aLine; |
8103 | 2 | aLine.append( "q " ); |
8104 | 2 | if( m_aPages.back().appendLineInfo( rInfo, aLine ) ) |
8105 | 0 | { |
8106 | 0 | writeBuffer( aLine ); |
8107 | 0 | drawPolyLine( rPoly ); |
8108 | 0 | writeBuffer( "Q\n" ); |
8109 | 0 | } |
8110 | 2 | else |
8111 | 2 | { |
8112 | 2 | PDFWriter::ExtLineInfo aInfo; |
8113 | 2 | convertLineInfoToExtLineInfo( rInfo, aInfo ); |
8114 | 2 | drawPolyLine( rPoly, aInfo ); |
8115 | 2 | } |
8116 | 2 | } |
8117 | | |
8118 | | void PDFWriterImpl::convertLineInfoToExtLineInfo( const LineInfo& rIn, PDFWriter::ExtLineInfo& rOut ) |
8119 | 2 | { |
8120 | 2 | SAL_WARN_IF( rIn.GetStyle() != LineStyle::Dash, "vcl.pdfwriter", "invalid conversion" ); |
8121 | 2 | rOut.m_fLineWidth = rIn.GetWidth(); |
8122 | 2 | rOut.m_fTransparency = 0.0; |
8123 | 2 | rOut.m_eCap = PDFWriter::capButt; |
8124 | 2 | rOut.m_eJoin = PDFWriter::joinMiter; |
8125 | 2 | rOut.m_fMiterLimit = 10; |
8126 | 2 | rOut.m_aDashArray = rIn.GetDotDashArray(); |
8127 | | |
8128 | | // add LineJoin |
8129 | 2 | switch(rIn.GetLineJoin()) |
8130 | 2 | { |
8131 | 0 | case basegfx::B2DLineJoin::Bevel : |
8132 | 0 | { |
8133 | 0 | rOut.m_eJoin = PDFWriter::joinBevel; |
8134 | 0 | break; |
8135 | 0 | } |
8136 | | // Pdf has no 'none' lineJoin, default is miter |
8137 | 0 | case basegfx::B2DLineJoin::NONE : |
8138 | 0 | case basegfx::B2DLineJoin::Miter : |
8139 | 0 | { |
8140 | 0 | rOut.m_eJoin = PDFWriter::joinMiter; |
8141 | 0 | break; |
8142 | 0 | } |
8143 | 2 | case basegfx::B2DLineJoin::Round : |
8144 | 2 | { |
8145 | 2 | rOut.m_eJoin = PDFWriter::joinRound; |
8146 | 2 | break; |
8147 | 0 | } |
8148 | 2 | } |
8149 | | |
8150 | | // add LineCap |
8151 | 2 | switch(rIn.GetLineCap()) |
8152 | 2 | { |
8153 | 2 | default: /* css::drawing::LineCap_BUTT */ |
8154 | 2 | { |
8155 | 2 | rOut.m_eCap = PDFWriter::capButt; |
8156 | 2 | break; |
8157 | 0 | } |
8158 | 0 | case css::drawing::LineCap_ROUND: |
8159 | 0 | { |
8160 | 0 | rOut.m_eCap = PDFWriter::capRound; |
8161 | 0 | break; |
8162 | 0 | } |
8163 | 0 | case css::drawing::LineCap_SQUARE: |
8164 | 0 | { |
8165 | 0 | rOut.m_eCap = PDFWriter::capSquare; |
8166 | 0 | break; |
8167 | 0 | } |
8168 | 2 | } |
8169 | 2 | } |
8170 | | |
8171 | | void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const PDFWriter::ExtLineInfo& rInfo ) |
8172 | 13.1k | { |
8173 | 13.1k | MARK( "drawPolyLine with ExtLineInfo" ); |
8174 | | |
8175 | 13.1k | updateGraphicsState(); |
8176 | | |
8177 | 13.1k | if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT ) |
8178 | 0 | return; |
8179 | | |
8180 | 13.1k | if( rInfo.m_fTransparency >= 1.0 ) |
8181 | 0 | return; |
8182 | | |
8183 | 13.1k | if( rInfo.m_fTransparency != 0.0 ) |
8184 | 0 | beginTransparencyGroup(); |
8185 | | |
8186 | 13.1k | OStringBuffer aLine; |
8187 | 13.1k | aLine.append( "q " ); |
8188 | 13.1k | m_aPages.back().appendMappedLength( rInfo.m_fLineWidth, aLine ); |
8189 | 13.1k | aLine.append( " w" ); |
8190 | 13.1k | if( rInfo.m_aDashArray.size() < 10 ) // implementation limit of acrobat reader |
8191 | 13.1k | { |
8192 | 13.1k | switch( rInfo.m_eCap ) |
8193 | 13.1k | { |
8194 | 0 | default: |
8195 | 13.1k | case PDFWriter::capButt: aLine.append( " 0 J" );break; |
8196 | 6 | case PDFWriter::capRound: aLine.append( " 1 J" );break; |
8197 | 0 | case PDFWriter::capSquare: aLine.append( " 2 J" );break; |
8198 | 13.1k | } |
8199 | 13.1k | switch( rInfo.m_eJoin ) |
8200 | 13.1k | { |
8201 | 0 | default: |
8202 | 13 | case PDFWriter::joinMiter: |
8203 | 13 | { |
8204 | 13 | double fLimit = rInfo.m_fMiterLimit; |
8205 | 13 | if (rInfo.m_fLineWidth < rInfo.m_fMiterLimit) |
8206 | 13 | { |
8207 | 13 | if (rInfo.m_fLineWidth != 0.0) |
8208 | 13 | fLimit = fLimit / rInfo.m_fLineWidth; |
8209 | 0 | else |
8210 | 0 | { |
8211 | 0 | SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::drawPolyLine divide by zero attempt"); |
8212 | 0 | fLimit = 1.0; |
8213 | 0 | } |
8214 | 13 | } |
8215 | 13 | if( fLimit < 1.0 ) |
8216 | 0 | fLimit = 1.0; |
8217 | 13 | aLine.append( " 0 j " ); |
8218 | 13 | appendDouble( fLimit, aLine ); |
8219 | 13 | aLine.append( " M" ); |
8220 | 13 | } |
8221 | 0 | break; |
8222 | 13.1k | case PDFWriter::joinRound: aLine.append( " 1 j" );break; |
8223 | 0 | case PDFWriter::joinBevel: aLine.append( " 2 j" );break; |
8224 | 13.1k | } |
8225 | 13.1k | if( !rInfo.m_aDashArray.empty() ) |
8226 | 2 | { |
8227 | 2 | aLine.append( " [ " ); |
8228 | 2 | for (auto const& dash : rInfo.m_aDashArray) |
8229 | 4 | { |
8230 | 4 | m_aPages.back().appendMappedLength( dash, aLine ); |
8231 | 4 | aLine.append( ' ' ); |
8232 | 4 | } |
8233 | 2 | aLine.append( "] 0 d" ); |
8234 | 2 | } |
8235 | 13.1k | aLine.append( "\n" ); |
8236 | 13.1k | writeBuffer( aLine ); |
8237 | 13.1k | drawPolyLine( rPoly ); |
8238 | 13.1k | } |
8239 | 0 | else |
8240 | 0 | { |
8241 | 0 | basegfx::B2DPolygon aPoly(rPoly.getB2DPolygon()); |
8242 | 0 | basegfx::B2DPolyPolygon aPolyPoly; |
8243 | |
|
8244 | 0 | basegfx::utils::applyLineDashing(aPoly, rInfo.m_aDashArray, &aPolyPoly); |
8245 | | |
8246 | | // Old applyLineDashing subdivided the polygon. New one will create bezier curve segments. |
8247 | | // To mimic old behaviour, apply subdivide here. If beziers shall be written (better quality) |
8248 | | // this line needs to be removed and the loop below adapted accordingly |
8249 | 0 | aPolyPoly = basegfx::utils::adaptiveSubdivideByAngle(aPolyPoly); |
8250 | |
|
8251 | 0 | const sal_uInt32 nPolygonCount(aPolyPoly.count()); |
8252 | |
|
8253 | 0 | for( sal_uInt32 nPoly = 0; nPoly < nPolygonCount; nPoly++ ) |
8254 | 0 | { |
8255 | 0 | aLine.append( (nPoly != 0 && (nPoly & 7) == 0) ? "\n" : " " ); |
8256 | 0 | aPoly = aPolyPoly.getB2DPolygon( nPoly ); |
8257 | 0 | const sal_uInt32 nPointCount(aPoly.count()); |
8258 | |
|
8259 | 0 | if(nPointCount) |
8260 | 0 | { |
8261 | 0 | const sal_uInt32 nEdgeCount(aPoly.isClosed() ? nPointCount : nPointCount - 1); |
8262 | 0 | basegfx::B2DPoint aCurrent(aPoly.getB2DPoint(0)); |
8263 | |
|
8264 | 0 | for(sal_uInt32 a(0); a < nEdgeCount; a++) |
8265 | 0 | { |
8266 | 0 | if( a > 0 ) |
8267 | 0 | aLine.append( " " ); |
8268 | 0 | const sal_uInt32 nNextIndex((a + 1) % nPointCount); |
8269 | 0 | const basegfx::B2DPoint aNext(aPoly.getB2DPoint(nNextIndex)); |
8270 | |
|
8271 | 0 | m_aPages.back().appendPoint( Point( basegfx::fround<tools::Long>(aCurrent.getX()), |
8272 | 0 | basegfx::fround<tools::Long>(aCurrent.getY()) ), |
8273 | 0 | aLine ); |
8274 | 0 | aLine.append( " m " ); |
8275 | 0 | m_aPages.back().appendPoint( Point( basegfx::fround<tools::Long>(aNext.getX()), |
8276 | 0 | basegfx::fround<tools::Long>(aNext.getY()) ), |
8277 | 0 | aLine ); |
8278 | 0 | aLine.append( " l" ); |
8279 | | |
8280 | | // prepare next edge |
8281 | 0 | aCurrent = aNext; |
8282 | 0 | } |
8283 | 0 | } |
8284 | 0 | } |
8285 | 0 | aLine.append( " S " ); |
8286 | 0 | writeBuffer( aLine ); |
8287 | 0 | } |
8288 | 13.1k | writeBuffer( "Q\n" ); |
8289 | | |
8290 | 13.1k | if( rInfo.m_fTransparency == 0.0 ) |
8291 | 13.1k | return; |
8292 | | |
8293 | | // FIXME: actually this may be incorrect with bezier polygons |
8294 | 0 | tools::Rectangle aBoundRect( rPoly.GetBoundRect() ); |
8295 | | // avoid clipping with thick lines |
8296 | 0 | if( rInfo.m_fLineWidth > 0.0 ) |
8297 | 0 | { |
8298 | 0 | sal_Int32 nLW = sal_Int32(rInfo.m_fLineWidth); |
8299 | 0 | aBoundRect.AdjustTop( -nLW ); |
8300 | 0 | aBoundRect.AdjustLeft( -nLW ); |
8301 | 0 | aBoundRect.AdjustRight(nLW ); |
8302 | 0 | aBoundRect.AdjustBottom(nLW ); |
8303 | 0 | } |
8304 | 0 | endTransparencyGroup( aBoundRect, static_cast<sal_uInt16>(100.0*rInfo.m_fTransparency) ); |
8305 | 0 | } |
8306 | | |
8307 | | void PDFWriterImpl::drawPixel( const Point& rPoint, const Color& rColor ) |
8308 | 0 | { |
8309 | 0 | MARK( "drawPixel" ); |
8310 | |
|
8311 | 0 | Color aColor = ( rColor == COL_TRANSPARENT ? m_aGraphicsStack.front().m_aLineColor : rColor ); |
8312 | |
|
8313 | 0 | if( aColor == COL_TRANSPARENT ) |
8314 | 0 | return; |
8315 | | |
8316 | | // pixels are drawn in line color, so have to set |
8317 | | // the nonstroking color to line color |
8318 | 0 | Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor; |
8319 | 0 | setFillColor( aColor ); |
8320 | |
|
8321 | 0 | updateGraphicsState(); |
8322 | |
|
8323 | 0 | OStringBuffer aLine( 20 ); |
8324 | 0 | m_aPages.back().appendPoint( rPoint, aLine ); |
8325 | 0 | aLine.append( ' ' ); |
8326 | 0 | appendDouble( 1.0/GetDPIX(), aLine ); |
8327 | 0 | aLine.append( ' ' ); |
8328 | 0 | appendDouble( 1.0/GetDPIY(), aLine ); |
8329 | 0 | aLine.append( " re f\n" ); |
8330 | 0 | writeBuffer( aLine ); |
8331 | |
|
8332 | 0 | setFillColor( aOldFillColor ); |
8333 | 0 | } |
8334 | | |
8335 | | void PDFWriterImpl::writeTransparentObject( TransparencyEmit& rObject ) |
8336 | 6 | { |
8337 | 6 | if (!updateObject(rObject.m_nObject)) |
8338 | 0 | return; |
8339 | | |
8340 | 6 | bool bFlateFilter = compressStream( rObject.m_pContentStream.get() ); |
8341 | 6 | sal_uInt64 nSize = rObject.m_pContentStream->TellEnd(); |
8342 | 6 | rObject.m_pContentStream->Seek( STREAM_SEEK_TO_BEGIN ); |
8343 | 6 | if (g_bDebugDisableCompression) |
8344 | 0 | { |
8345 | 0 | emitComment( "PDFWriterImpl::writeTransparentObject" ); |
8346 | 0 | } |
8347 | 6 | OStringBuffer aLine( 512 ); |
8348 | 6 | if (!updateObject(rObject.m_nObject)) |
8349 | 0 | return; |
8350 | 6 | aLine.append( rObject.m_nObject ); |
8351 | 6 | aLine.append( " 0 obj\n" |
8352 | 6 | "<</Type/XObject\n" |
8353 | 6 | "/Subtype/Form\n" |
8354 | 6 | "/BBox[ " ); |
8355 | 6 | appendFixedInt( rObject.m_aBoundRect.Left(), aLine ); |
8356 | 6 | aLine.append( ' ' ); |
8357 | 6 | appendFixedInt( rObject.m_aBoundRect.Top(), aLine ); |
8358 | 6 | aLine.append( ' ' ); |
8359 | 6 | appendFixedInt( rObject.m_aBoundRect.Right(), aLine ); |
8360 | 6 | aLine.append( ' ' ); |
8361 | 6 | appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aLine ); |
8362 | 6 | aLine.append( " ]\n" ); |
8363 | 6 | if( ! m_bIsPDF_A1 ) |
8364 | 6 | { |
8365 | | // 7.8.3 Resource dicts are required for content streams |
8366 | 6 | aLine.append( "/Resources " ); |
8367 | 6 | aLine.append( getResourceDictObj() ); |
8368 | 6 | aLine.append( " 0 R\n" ); |
8369 | | |
8370 | 6 | aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/K true>>\n" ); |
8371 | 6 | } |
8372 | | |
8373 | 6 | aLine.append( "/Length " ); |
8374 | 6 | aLine.append(static_cast<sal_Int64>(calculateStreamSize(nSize))); |
8375 | 6 | aLine.append( "\n" ); |
8376 | 6 | if( bFlateFilter ) |
8377 | 6 | aLine.append( "/Filter/FlateDecode\n" ); |
8378 | 6 | aLine.append( ">>\n" |
8379 | 6 | "stream\n" ); |
8380 | 6 | if (!writeBuffer(aLine)) |
8381 | 0 | return; |
8382 | 6 | checkAndEnableStreamEncryption( rObject.m_nObject ); |
8383 | 6 | if (!writeBufferBytes(rObject.m_pContentStream->GetData(), nSize)) |
8384 | 0 | return; |
8385 | 6 | disableStreamEncryption(); |
8386 | 6 | aLine.setLength( 0 ); |
8387 | 6 | aLine.append( "\n" |
8388 | 6 | "endstream\n" |
8389 | 6 | "endobj\n\n" ); |
8390 | 6 | if (!writeBuffer(aLine)) |
8391 | 0 | return; |
8392 | | |
8393 | | // write ExtGState dict for this XObject |
8394 | 6 | aLine.setLength( 0 ); |
8395 | 6 | aLine.append( rObject.m_nExtGStateObject ); |
8396 | 6 | aLine.append( " 0 obj\n" |
8397 | 6 | "<<" ); |
8398 | | |
8399 | 6 | if( m_bIsPDF_A1 ) |
8400 | 0 | { |
8401 | 0 | aLine.append( "/CA 1.0/ca 1.0" ); |
8402 | 0 | m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA ); |
8403 | 0 | } |
8404 | 6 | else |
8405 | 6 | { |
8406 | 6 | aLine.append( "/CA " ); |
8407 | 6 | appendDouble( rObject.m_fAlpha, aLine ); |
8408 | 6 | aLine.append( "\n" |
8409 | 6 | " /ca " ); |
8410 | 6 | appendDouble( rObject.m_fAlpha, aLine ); |
8411 | 6 | } |
8412 | 6 | aLine.append( "\n" ); |
8413 | | |
8414 | 6 | aLine.append( ">>\n" |
8415 | 6 | "endobj\n\n" ); |
8416 | 6 | if (!updateObject(rObject.m_nExtGStateObject)) return; |
8417 | 6 | if (!writeBuffer(aLine)) return; |
8418 | 6 | } |
8419 | | |
8420 | | bool PDFWriterImpl::writeGradientFunction( GradientEmit const & rObject ) |
8421 | 0 | { |
8422 | | // LO internal gradient -> PDF shading type: |
8423 | | // * css::awt::GradientStyle_LINEAR: axial shading, using sampled-function with 2 samples |
8424 | | // [t=0:colorStart, t=1:colorEnd] |
8425 | | // * css::awt::GradientStyle_AXIAL: axial shading, using sampled-function with 3 samples |
8426 | | // [t=0:colorEnd, t=0.5:colorStart, t=1:colorEnd] |
8427 | | // * other styles: function shading with aSize.Width() * aSize.Height() samples |
8428 | 0 | sal_Int32 nFunctionObject = createObject(); |
8429 | 0 | if (!updateObject(nFunctionObject)) |
8430 | 0 | return false; |
8431 | | |
8432 | 0 | ScopedVclPtrInstance< VirtualDevice > aDev; |
8433 | 0 | aDev->SetOutputSizePixel( rObject.m_aSize ); |
8434 | 0 | aDev->SetMapMode( MapMode( MapUnit::MapPixel ) ); |
8435 | 0 | if( m_aContext.ColorMode == PDFWriter::DrawGreyscale ) |
8436 | 0 | aDev->SetDrawMode( aDev->GetDrawMode() | |
8437 | 0 | ( DrawModeFlags::GrayLine | DrawModeFlags::GrayFill | DrawModeFlags::GrayText | |
8438 | 0 | DrawModeFlags::GrayBitmap | DrawModeFlags::GrayGradient ) ); |
8439 | 0 | aDev->DrawGradient( tools::Rectangle( Point( 0, 0 ), rObject.m_aSize ), rObject.m_aGradient ); |
8440 | |
|
8441 | 0 | Bitmap aSample = aDev->GetBitmap( Point( 0, 0 ), rObject.m_aSize ); |
8442 | 0 | BitmapScopedReadAccess pAccess(aSample); |
8443 | |
|
8444 | 0 | Size aSize = aSample.GetSizePixel(); |
8445 | |
|
8446 | 0 | sal_Int32 nStreamLengthObject = createObject(); |
8447 | 0 | if (g_bDebugDisableCompression) |
8448 | 0 | { |
8449 | 0 | emitComment( "PDFWriterImpl::writeGradientFunction" ); |
8450 | 0 | } |
8451 | 0 | OStringBuffer aLine( 120 ); |
8452 | 0 | aLine.append( nFunctionObject ); |
8453 | 0 | aLine.append( " 0 obj\n" |
8454 | 0 | "<</FunctionType 0\n"); |
8455 | 0 | switch (rObject.m_aGradient.GetStyle()) |
8456 | 0 | { |
8457 | 0 | case css::awt::GradientStyle_LINEAR: |
8458 | 0 | case css::awt::GradientStyle_AXIAL: |
8459 | 0 | aLine.append("/Domain[ 0 1]\n"); |
8460 | 0 | break; |
8461 | 0 | default: |
8462 | 0 | aLine.append("/Domain[ 0 1 0 1]\n"); |
8463 | 0 | } |
8464 | 0 | aLine.append("/Size[ " ); |
8465 | 0 | switch (rObject.m_aGradient.GetStyle()) |
8466 | 0 | { |
8467 | 0 | case css::awt::GradientStyle_LINEAR: |
8468 | 0 | aLine.append('2'); |
8469 | 0 | break; |
8470 | 0 | case css::awt::GradientStyle_AXIAL: |
8471 | 0 | aLine.append('3'); |
8472 | 0 | break; |
8473 | 0 | default: |
8474 | 0 | aLine.append( static_cast<sal_Int32>(aSize.Width()) ); |
8475 | 0 | aLine.append( ' ' ); |
8476 | 0 | aLine.append( static_cast<sal_Int32>(aSize.Height()) ); |
8477 | 0 | } |
8478 | 0 | aLine.append( " ]\n" |
8479 | 0 | "/BitsPerSample 8\n" |
8480 | 0 | "/Range[ 0 1 0 1 0 1 ]\n" |
8481 | 0 | "/Order 3\n" |
8482 | 0 | "/Length " ); |
8483 | 0 | aLine.append( nStreamLengthObject ); |
8484 | 0 | if (!g_bDebugDisableCompression) |
8485 | 0 | aLine.append( " 0 R\n" |
8486 | 0 | "/Filter/FlateDecode" |
8487 | 0 | ">>\n" |
8488 | 0 | "stream\n" ); |
8489 | 0 | else |
8490 | 0 | aLine.append( " 0 R\n" |
8491 | 0 | ">>\n" |
8492 | 0 | "stream\n" ); |
8493 | 0 | if (!writeBuffer(aLine)) |
8494 | 0 | return false; |
8495 | | |
8496 | 0 | sal_uInt64 nStartStreamPos = 0; |
8497 | 0 | if (osl::File::E_None != m_aFile.getPos(nStartStreamPos)) |
8498 | 0 | return false; |
8499 | | |
8500 | 0 | checkAndEnableStreamEncryption( nFunctionObject ); |
8501 | 0 | beginCompression(); |
8502 | 0 | sal_uInt8 aCol[3]; |
8503 | 0 | switch (rObject.m_aGradient.GetStyle()) |
8504 | 0 | { |
8505 | 0 | case css::awt::GradientStyle_AXIAL: |
8506 | 0 | aCol[0] = rObject.m_aGradient.GetEndColor().GetRed(); |
8507 | 0 | aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen(); |
8508 | 0 | aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue(); |
8509 | 0 | if (!writeBufferBytes(aCol, 3)) |
8510 | 0 | return false; |
8511 | 0 | [[fallthrough]]; |
8512 | 0 | case css::awt::GradientStyle_LINEAR: |
8513 | 0 | { |
8514 | 0 | aCol[0] = rObject.m_aGradient.GetStartColor().GetRed(); |
8515 | 0 | aCol[1] = rObject.m_aGradient.GetStartColor().GetGreen(); |
8516 | 0 | aCol[2] = rObject.m_aGradient.GetStartColor().GetBlue(); |
8517 | 0 | if (!writeBufferBytes(aCol, 3)) |
8518 | 0 | return false; |
8519 | | |
8520 | 0 | aCol[0] = rObject.m_aGradient.GetEndColor().GetRed(); |
8521 | 0 | aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen(); |
8522 | 0 | aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue(); |
8523 | 0 | if (!writeBufferBytes(aCol, 3)) |
8524 | 0 | return false; |
8525 | 0 | break; |
8526 | 0 | } |
8527 | 0 | default: |
8528 | 0 | for( int y = aSize.Height()-1; y >= 0; y-- ) |
8529 | 0 | { |
8530 | 0 | for( tools::Long x = 0; x < aSize.Width(); x++ ) |
8531 | 0 | { |
8532 | 0 | BitmapColor aColor = pAccess->GetColor( y, x ); |
8533 | 0 | aCol[0] = aColor.GetRed(); |
8534 | 0 | aCol[1] = aColor.GetGreen(); |
8535 | 0 | aCol[2] = aColor.GetBlue(); |
8536 | 0 | if (!writeBufferBytes(aCol, 3)) |
8537 | 0 | return false; |
8538 | 0 | } |
8539 | 0 | } |
8540 | 0 | } |
8541 | 0 | endCompression(); |
8542 | 0 | disableStreamEncryption(); |
8543 | |
|
8544 | 0 | sal_uInt64 nEndStreamPos = 0; |
8545 | 0 | if (osl::File::E_None != m_aFile.getPos(nEndStreamPos)) |
8546 | 0 | return false; |
8547 | | |
8548 | 0 | aLine.setLength( 0 ); |
8549 | 0 | aLine.append( "\nendstream\nendobj\n\n" ); |
8550 | 0 | if (!writeBuffer(aLine)) return false; |
8551 | | |
8552 | | // write stream length |
8553 | 0 | if (!updateObject(nStreamLengthObject)) return false; |
8554 | 0 | aLine.setLength( 0 ); |
8555 | 0 | aLine.append( nStreamLengthObject ); |
8556 | 0 | aLine.append( " 0 obj\n" ); |
8557 | 0 | aLine.append( static_cast<sal_Int64>(nEndStreamPos-nStartStreamPos) ); |
8558 | 0 | aLine.append( "\nendobj\n\n" ); |
8559 | 0 | if (!writeBuffer(aLine)) return false; |
8560 | | |
8561 | 0 | if (!updateObject(rObject.m_nObject)) return false; |
8562 | 0 | aLine.setLength( 0 ); |
8563 | 0 | aLine.append( rObject.m_nObject ); |
8564 | 0 | aLine.append( " 0 obj\n"); |
8565 | 0 | switch (rObject.m_aGradient.GetStyle()) |
8566 | 0 | { |
8567 | 0 | case css::awt::GradientStyle_LINEAR: |
8568 | 0 | case css::awt::GradientStyle_AXIAL: |
8569 | 0 | aLine.append("<</ShadingType 2\n"); |
8570 | 0 | break; |
8571 | 0 | default: |
8572 | 0 | aLine.append("<</ShadingType 1\n"); |
8573 | 0 | } |
8574 | 0 | aLine.append("/ColorSpace/DeviceRGB\n" |
8575 | 0 | "/AntiAlias true\n"); |
8576 | | |
8577 | | // Determination of shading axis |
8578 | | // See: OutputDevice::ImplDrawLinearGradient for reference |
8579 | 0 | tools::Rectangle aRect; |
8580 | 0 | aRect.SetLeft(0); |
8581 | 0 | aRect.SetTop(0); |
8582 | 0 | aRect.SetRight( aSize.Width() ); |
8583 | 0 | aRect.SetBottom( aSize.Height() ); |
8584 | |
|
8585 | 0 | tools::Rectangle aBoundRect; |
8586 | 0 | Point aCenter; |
8587 | 0 | Degree10 nAngle = rObject.m_aGradient.GetAngle() % 3600_deg10; |
8588 | 0 | rObject.m_aGradient.GetBoundRect( aRect, aBoundRect, aCenter ); |
8589 | |
|
8590 | 0 | const bool bLinear = (rObject.m_aGradient.GetStyle() == css::awt::GradientStyle_LINEAR); |
8591 | 0 | double fBorder = aBoundRect.GetHeight() * rObject.m_aGradient.GetBorder() / 100.0; |
8592 | 0 | if ( !bLinear ) |
8593 | 0 | { |
8594 | 0 | fBorder /= 2.0; |
8595 | 0 | } |
8596 | |
|
8597 | 0 | aBoundRect.AdjustBottom( -fBorder ); |
8598 | 0 | if (!bLinear) |
8599 | 0 | { |
8600 | 0 | aBoundRect.AdjustTop(fBorder ); |
8601 | 0 | } |
8602 | |
|
8603 | 0 | switch (rObject.m_aGradient.GetStyle()) |
8604 | 0 | { |
8605 | 0 | case css::awt::GradientStyle_LINEAR: |
8606 | 0 | case css::awt::GradientStyle_AXIAL: |
8607 | 0 | { |
8608 | 0 | aLine.append("/Domain[ 0 1 ]\n" |
8609 | 0 | "/Coords[ " ); |
8610 | 0 | tools::Polygon aPoly( 2 ); |
8611 | 0 | aPoly[0] = aBoundRect.BottomCenter(); |
8612 | 0 | aPoly[1] = aBoundRect.TopCenter(); |
8613 | 0 | aPoly.Rotate( aCenter, 3600_deg10 - nAngle ); |
8614 | |
|
8615 | 0 | aLine.append( static_cast<sal_Int32>(aPoly[0].X()) ); |
8616 | 0 | aLine.append( " " ); |
8617 | 0 | aLine.append( static_cast<sal_Int32>(aPoly[0].Y()) ); |
8618 | 0 | aLine.append( " " ); |
8619 | 0 | aLine.append( static_cast<sal_Int32>(aPoly[1].X())); |
8620 | 0 | aLine.append( " "); |
8621 | 0 | aLine.append( static_cast<sal_Int32>(aPoly[1].Y())); |
8622 | 0 | aLine.append( " ]\n"); |
8623 | 0 | aLine.append("/Extend [true true]\n"); |
8624 | 0 | break; |
8625 | 0 | } |
8626 | 0 | default: |
8627 | 0 | aLine.append("/Domain[ 0 1 0 1 ]\n" |
8628 | 0 | "/Matrix[ " ); |
8629 | 0 | aLine.append( static_cast<sal_Int32>(aSize.Width()) ); |
8630 | 0 | aLine.append( " 0 0 " ); |
8631 | 0 | aLine.append( static_cast<sal_Int32>(aSize.Height()) ); |
8632 | 0 | aLine.append( " 0 0 ]\n"); |
8633 | 0 | } |
8634 | 0 | aLine.append("/Function " ); |
8635 | 0 | aLine.append( nFunctionObject ); |
8636 | 0 | aLine.append( " 0 R\n" |
8637 | 0 | ">>\n" |
8638 | 0 | "endobj\n\n" ); |
8639 | 0 | return writeBuffer( aLine ); |
8640 | 0 | } |
8641 | | |
8642 | | void PDFWriterImpl::writeJPG( const JPGEmit& rObject ) |
8643 | 0 | { |
8644 | 0 | if (rObject.m_aReferenceXObject.hasExternalPDFData() && !m_aContext.UseReferenceXObject) |
8645 | 0 | { |
8646 | 0 | writeReferenceXObject(rObject.m_aReferenceXObject); |
8647 | 0 | return; |
8648 | 0 | } |
8649 | | |
8650 | 0 | if (!rObject.m_pStream) return; |
8651 | 0 | if (!updateObject(rObject.m_nObject)) return; |
8652 | | |
8653 | 0 | sal_uInt64 nLength = rObject.m_pStream->TellEnd(); |
8654 | 0 | rObject.m_pStream->Seek( STREAM_SEEK_TO_BEGIN ); |
8655 | |
|
8656 | 0 | sal_Int32 nMaskObject = 0; |
8657 | 0 | if( !rObject.m_aAlphaMask.IsEmpty() ) |
8658 | 0 | { |
8659 | 0 | if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 |
8660 | 0 | && !m_bIsPDF_A1) |
8661 | 0 | { |
8662 | 0 | nMaskObject = createObject(); |
8663 | 0 | } |
8664 | 0 | else if( m_bIsPDF_A1 ) |
8665 | 0 | m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA ); |
8666 | 0 | else if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 ) |
8667 | 0 | m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDF13 ); |
8668 | |
|
8669 | 0 | } |
8670 | 0 | if (g_bDebugDisableCompression) |
8671 | 0 | { |
8672 | 0 | emitComment( "PDFWriterImpl::writeJPG" ); |
8673 | 0 | } |
8674 | |
|
8675 | 0 | OStringBuffer aLine(200); |
8676 | 0 | aLine.append( rObject.m_nObject ); |
8677 | 0 | aLine.append( " 0 obj\n" |
8678 | 0 | "<</Type/XObject/Subtype/Image/Width " ); |
8679 | 0 | aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Width()) ); |
8680 | 0 | aLine.append( " /Height " ); |
8681 | 0 | aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Height()) ); |
8682 | 0 | aLine.append( " /BitsPerComponent 8 " ); |
8683 | 0 | if( rObject.m_bTrueColor ) |
8684 | 0 | aLine.append( "/ColorSpace/DeviceRGB" ); |
8685 | 0 | else |
8686 | 0 | aLine.append( "/ColorSpace/DeviceGray" ); |
8687 | 0 | aLine.append( "/Filter/DCTDecode/Length " ); |
8688 | 0 | aLine.append(static_cast<sal_Int64>(calculateStreamSize(nLength))); |
8689 | 0 | if( nMaskObject ) |
8690 | 0 | { |
8691 | 0 | aLine.append(" /SMask "); |
8692 | 0 | aLine.append( nMaskObject ); |
8693 | 0 | aLine.append( " 0 R " ); |
8694 | 0 | } |
8695 | 0 | aLine.append( ">>\nstream\n" ); |
8696 | 0 | if (!writeBuffer(aLine)) |
8697 | 0 | return; |
8698 | | |
8699 | 0 | checkAndEnableStreamEncryption( rObject.m_nObject ); |
8700 | 0 | if (!writeBufferBytes(rObject.m_pStream->GetData(), nLength)) |
8701 | 0 | return; |
8702 | 0 | disableStreamEncryption(); |
8703 | |
|
8704 | 0 | aLine.setLength( 0 ); |
8705 | 0 | if (!writeBuffer("\nendstream\nendobj\n\n")) |
8706 | 0 | return; |
8707 | | |
8708 | 0 | if( nMaskObject ) |
8709 | 0 | writeBitmapMaskObject( nMaskObject, rObject.m_aAlphaMask ); |
8710 | |
|
8711 | 0 | writeReferenceXObject(rObject.m_aReferenceXObject); |
8712 | 0 | } |
8713 | | |
8714 | | void PDFWriterImpl::writeReferenceXObject(const ReferenceXObjectEmit& rEmit) |
8715 | 0 | { |
8716 | 0 | if (rEmit.m_nFormObject <= 0) |
8717 | 0 | return; |
8718 | | |
8719 | | // Count /Matrix and /BBox. |
8720 | | // vcl::ImportPDF() uses getDefaultPdfResolutionDpi to set the desired |
8721 | | // rendering DPI so we have to take into account that here too. |
8722 | 0 | static const double fResolutionDPI = getDefaultPdfResolutionDpi(); |
8723 | 0 | static const double fMagicScaleFactor = PDF_INSERT_MAGIC_SCALE_FACTOR; |
8724 | |
|
8725 | 0 | sal_Int32 nOldDPIX = GetDPIX(); |
8726 | 0 | sal_Int32 nOldDPIY = GetDPIY(); |
8727 | 0 | SetDPIX(fResolutionDPI); |
8728 | 0 | SetDPIY(fResolutionDPI); |
8729 | 0 | Size aSize = PixelToLogic(rEmit.m_aPixelSize, MapMode(m_aMapMode.GetMapUnit())); |
8730 | 0 | SetDPIX(nOldDPIX); |
8731 | 0 | SetDPIY(nOldDPIY); |
8732 | 0 | double fScaleX = 1.0 / aSize.Width(); |
8733 | 0 | double fScaleY = 1.0 / aSize.Height(); |
8734 | |
|
8735 | 0 | sal_Int32 nWrappedFormObject = 0; |
8736 | 0 | if (!m_aContext.UseReferenceXObject) |
8737 | 0 | { |
8738 | | // tdf#156842 increase scale for external PDF data |
8739 | | // Multiply PDF_INSERT_MAGIC_SCALE_FACTOR for platforms like macOS |
8740 | | // that scale all images by this number. |
8741 | | // This fix also allows the CppunitTest_vcl_pdfexport to run |
8742 | | // successfully on macOS. |
8743 | 0 | fScaleX = fMagicScaleFactor / aSize.Width(); |
8744 | 0 | fScaleY = fMagicScaleFactor / aSize.Height(); |
8745 | | |
8746 | | // Parse the PDF data, we need that to write the PDF dictionary of our |
8747 | | // object. |
8748 | 0 | if (rEmit.m_nExternalPDFDataIndex < 0) |
8749 | 0 | return; |
8750 | 0 | auto& rExternalPDFStream = m_aExternalPDFStreams.get(rEmit.m_nExternalPDFDataIndex); |
8751 | 0 | auto& pPDFDocument = rExternalPDFStream.getPDFDocument(); |
8752 | 0 | if (!pPDFDocument) |
8753 | 0 | { |
8754 | | // Couldn't parse the document and can't continue |
8755 | 0 | SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: failed to parse the document"); |
8756 | 0 | return; |
8757 | 0 | } |
8758 | | |
8759 | 0 | std::vector<filter::PDFObjectElement*> aPages = pPDFDocument->GetPages(); |
8760 | 0 | if (aPages.empty()) |
8761 | 0 | { |
8762 | 0 | SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no pages"); |
8763 | 0 | return; |
8764 | 0 | } |
8765 | | |
8766 | 0 | size_t nPageIndex = rEmit.m_nExternalPDFPageIndex >= 0 ? rEmit.m_nExternalPDFPageIndex : 0; |
8767 | |
|
8768 | 0 | filter::PDFObjectElement* pPage = aPages[nPageIndex]; |
8769 | 0 | if (!pPage) |
8770 | 0 | { |
8771 | 0 | SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no page"); |
8772 | 0 | return; |
8773 | 0 | } |
8774 | | |
8775 | | // Get the copied resource map, so we can use that to skip objects we already copied |
8776 | 0 | auto& rCopiedResourcesMap = rExternalPDFStream.getCopiedResources(); |
8777 | | |
8778 | | // Add page mapping to the copied resources map. |
8779 | | // Needed if we reference the current page and we want to prevent copying the page |
8780 | | // if it is referenced. |
8781 | 0 | rCopiedResourcesMap.emplace(pPage->GetObjectValue(), m_aPages.back().m_nPageObject); |
8782 | |
|
8783 | 0 | double aOrigin[2] = { 0.0, 0.0 }; |
8784 | | |
8785 | | // tdf#160714 use crop box for bounds of embedded PDF object |
8786 | | // If there is no crop box, fallback to the media box just to be safe. |
8787 | 0 | auto* pBoundsArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("CropBox"_ostr)); |
8788 | 0 | if (!pBoundsArray) |
8789 | 0 | pBoundsArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("MediaBox"_ostr)); |
8790 | 0 | if (pBoundsArray) |
8791 | 0 | { |
8792 | 0 | const auto& rElements = pBoundsArray->GetElements(); |
8793 | 0 | if (rElements.size() >= 4) |
8794 | 0 | { |
8795 | | // get x1, y1 of the rectangle. |
8796 | 0 | for (sal_Int32 nIdx = 0; nIdx < 2; ++nIdx) |
8797 | 0 | { |
8798 | 0 | if (const auto* pNumElement = dynamic_cast<filter::PDFNumberElement*>(rElements[nIdx])) |
8799 | 0 | aOrigin[nIdx] = pNumElement->GetValue(); |
8800 | 0 | } |
8801 | 0 | } |
8802 | 0 | } |
8803 | |
|
8804 | 0 | std::vector<filter::PDFObjectElement*> aContentStreams; |
8805 | 0 | if (filter::PDFObjectElement* pContentStream = pPage->LookupObject("Contents"_ostr)) |
8806 | 0 | aContentStreams.push_back(pContentStream); |
8807 | 0 | else if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Contents"_ostr))) |
8808 | 0 | { |
8809 | 0 | for (const auto pElement : pArray->GetElements()) |
8810 | 0 | { |
8811 | 0 | auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement); |
8812 | 0 | if (!pReference) |
8813 | 0 | continue; |
8814 | | |
8815 | 0 | filter::PDFObjectElement* pObject = pReference->LookupObject(); |
8816 | 0 | if (!pObject) |
8817 | 0 | continue; |
8818 | | |
8819 | 0 | aContentStreams.push_back(pObject); |
8820 | 0 | } |
8821 | 0 | } |
8822 | |
|
8823 | 0 | if (aContentStreams.empty()) |
8824 | 0 | { |
8825 | 0 | SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no content stream"); |
8826 | 0 | return; |
8827 | 0 | } |
8828 | | |
8829 | | // Merge link and widget annotations from pPage to our page. |
8830 | 0 | mergeAnnotationsFromExternalPage(pPage, rCopiedResourcesMap); |
8831 | |
|
8832 | 0 | nWrappedFormObject = createObject(); |
8833 | | // Write the form XObject wrapped below. This is a separate object from |
8834 | | // the wrapper, this way there is no need to alter the stream contents. |
8835 | |
|
8836 | 0 | OStringBuffer aLine; |
8837 | 0 | aLine.append(nWrappedFormObject); |
8838 | 0 | aLine.append(" 0 obj\n"); |
8839 | 0 | aLine.append("<< /Type /XObject"); |
8840 | 0 | aLine.append(" /Subtype /Form"); |
8841 | |
|
8842 | 0 | tools::Long nWidth = aSize.Width(); |
8843 | 0 | tools::Long nHeight = aSize.Height(); |
8844 | 0 | basegfx::B2DRange aBBox(0, 0, aSize.Width(), aSize.Height()); |
8845 | 0 | if (auto pRotate = dynamic_cast<filter::PDFNumberElement*>(pPage->Lookup("Rotate"_ostr))) |
8846 | 0 | { |
8847 | | // The original page was rotated, then construct a transformation matrix which does the |
8848 | | // same with our form object. |
8849 | 0 | sal_Int32 nRotAngle = static_cast<sal_Int32>(pRotate->GetValue()) % 360; |
8850 | | // /Rotate is clockwise, matrix rotate is counter-clockwise. |
8851 | 0 | sal_Int32 nAngle = -1 * nRotAngle; |
8852 | | |
8853 | | // The bounding box just rotates. |
8854 | 0 | basegfx::B2DHomMatrix aBBoxMat; |
8855 | 0 | aBBoxMat.rotate(basegfx::deg2rad(pRotate->GetValue())); |
8856 | 0 | aBBox.transform(aBBoxMat); |
8857 | | |
8858 | | // Now transform the object: rotate around the center and make sure that the rotation |
8859 | | // doesn't affect the aspect ratio. |
8860 | 0 | basegfx::B2DHomMatrix aMat; |
8861 | 0 | aMat.translate((-0.5 * aBBox.getWidth() / fMagicScaleFactor) - aOrigin[0], (-0.5 * aBBox.getHeight() / fMagicScaleFactor) - aOrigin[1]); |
8862 | 0 | aMat.rotate(basegfx::deg2rad(nAngle)); |
8863 | 0 | aMat.translate(0.5 * nWidth / fMagicScaleFactor, 0.5 * nHeight / fMagicScaleFactor); |
8864 | |
|
8865 | 0 | aLine.append(" /Matrix [ "); |
8866 | 0 | aLine.append(aMat.a()); |
8867 | 0 | aLine.append(" "); |
8868 | 0 | aLine.append(aMat.b()); |
8869 | 0 | aLine.append(" "); |
8870 | 0 | aLine.append(aMat.c()); |
8871 | 0 | aLine.append(" "); |
8872 | 0 | aLine.append(aMat.d()); |
8873 | 0 | aLine.append(" "); |
8874 | 0 | aLine.append(aMat.e()); |
8875 | 0 | aLine.append(" "); |
8876 | 0 | aLine.append(aMat.f()); |
8877 | 0 | aLine.append(" ] "); |
8878 | 0 | } |
8879 | |
|
8880 | 0 | PDFObjectCopier aCopier(*this); |
8881 | 0 | aCopier.copyPageResources(pPage, aLine, rCopiedResourcesMap); |
8882 | |
|
8883 | 0 | aLine.append(" /BBox [ "); |
8884 | 0 | aLine.append(aOrigin[0]); |
8885 | 0 | aLine.append(' '); |
8886 | 0 | aLine.append(aOrigin[1]); |
8887 | 0 | aLine.append(' '); |
8888 | 0 | aLine.append(aBBox.getWidth() + aOrigin[0]); |
8889 | 0 | aLine.append(' '); |
8890 | 0 | aLine.append(aBBox.getHeight() + aOrigin[1]); |
8891 | 0 | aLine.append(" ]"); |
8892 | |
|
8893 | 0 | if (!g_bDebugDisableCompression) |
8894 | 0 | aLine.append(" /Filter/FlateDecode"); |
8895 | 0 | aLine.append(" /Length "); |
8896 | |
|
8897 | 0 | SvMemoryStream aStream; |
8898 | 0 | bool bCompressed = false; |
8899 | 0 | bool bIsTaggedNonReferenceXObject = m_aContext.Tagged && !m_aContext.UseReferenceXObject; |
8900 | 0 | sal_Int32 nLength = PDFObjectCopier::copyPageStreams(aContentStreams, aStream, bCompressed, |
8901 | 0 | bIsTaggedNonReferenceXObject); |
8902 | 0 | aLine.append(static_cast<sal_Int64>(calculateStreamSize(nLength))); |
8903 | |
|
8904 | 0 | aLine.append(">>\nstream\n"); |
8905 | 0 | if (g_bDebugDisableCompression) |
8906 | 0 | { |
8907 | 0 | emitComment("PDFWriterImpl::writeReferenceXObject, WrappedFormObject"); |
8908 | 0 | } |
8909 | 0 | if (!updateObject(nWrappedFormObject)) |
8910 | 0 | return; |
8911 | 0 | if (!writeBuffer(aLine)) |
8912 | 0 | return; |
8913 | 0 | aLine.setLength(0); |
8914 | |
|
8915 | 0 | checkAndEnableStreamEncryption(nWrappedFormObject); |
8916 | | // Copy the original page streams to the form XObject stream. |
8917 | 0 | aLine.append(static_cast<const char*>(aStream.GetData()), aStream.GetSize()); |
8918 | 0 | if (!writeBuffer(aLine)) |
8919 | 0 | return; |
8920 | 0 | aLine.setLength(0); |
8921 | 0 | disableStreamEncryption(); |
8922 | |
|
8923 | 0 | aLine.append("\nendstream\nendobj\n\n"); |
8924 | 0 | if (!writeBuffer(aLine)) |
8925 | 0 | return; |
8926 | 0 | } |
8927 | | |
8928 | 0 | OStringBuffer aLine; |
8929 | 0 | if (g_bDebugDisableCompression) |
8930 | 0 | { |
8931 | 0 | emitComment("PDFWriterImpl::writeReferenceXObject, FormObject"); |
8932 | 0 | } |
8933 | 0 | if (!updateObject(rEmit.m_nFormObject)) |
8934 | 0 | return; |
8935 | | |
8936 | | // Now have all the info to write the form XObject. |
8937 | 0 | aLine.append(rEmit.m_nFormObject); |
8938 | 0 | aLine.append(" 0 obj\n"); |
8939 | 0 | aLine.append("<< /Type /XObject"); |
8940 | 0 | aLine.append(" /Subtype /Form"); |
8941 | 0 | aLine.append(" /Resources << /XObject<<"); |
8942 | |
|
8943 | 0 | sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject; |
8944 | 0 | aLine.append(" /Im"); |
8945 | 0 | aLine.append(nObject); |
8946 | 0 | aLine.append(" "); |
8947 | 0 | aLine.append(nObject); |
8948 | 0 | aLine.append(" 0 R"); |
8949 | |
|
8950 | 0 | aLine.append(">> >>"); |
8951 | 0 | aLine.append(" /Matrix [ "); |
8952 | 0 | appendDouble(fScaleX, aLine); |
8953 | 0 | aLine.append(" 0 0 "); |
8954 | 0 | appendDouble(fScaleY, aLine); |
8955 | 0 | aLine.append(" 0 0 ]"); |
8956 | 0 | aLine.append(" /BBox [ 0 0 "); |
8957 | | // tdf#157680 reduce size by magic scale factor in /BBox |
8958 | 0 | aLine.append(aSize.Width() / fMagicScaleFactor); |
8959 | 0 | aLine.append(" "); |
8960 | | // tdf#157680 reduce size by magic scale factor in /BBox |
8961 | 0 | aLine.append(aSize.Height() / fMagicScaleFactor); |
8962 | 0 | aLine.append(" ]\n"); |
8963 | |
|
8964 | 0 | if (m_aContext.UseReferenceXObject && rEmit.m_nEmbeddedObject > 0) |
8965 | 0 | { |
8966 | | // Write the reference dictionary. |
8967 | 0 | aLine.append("/Ref<< /F << /Type /Filespec /F (<embedded file>) "); |
8968 | 0 | if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version) |
8969 | 0 | { // ISO 14289-1:2014, Clause: 7.11 |
8970 | 0 | aLine.append("/UF (<embedded file>) "); |
8971 | 0 | } |
8972 | 0 | aLine.append("/EF << /F "); |
8973 | 0 | aLine.append(rEmit.m_nEmbeddedObject); |
8974 | 0 | aLine.append(" 0 R >> >> /Page 0 >>\n"); |
8975 | 0 | } |
8976 | |
|
8977 | 0 | aLine.append("/Length "); |
8978 | |
|
8979 | 0 | OStringBuffer aStream; |
8980 | 0 | aStream.append("q "); |
8981 | 0 | if (m_aContext.UseReferenceXObject) |
8982 | 0 | { |
8983 | | // Reference XObject markup is used, just refer to the fallback bitmap |
8984 | | // here. |
8985 | 0 | aStream.append(aSize.Width()); |
8986 | 0 | aStream.append(" 0 0 "); |
8987 | 0 | aStream.append(aSize.Height()); |
8988 | 0 | aStream.append(" 0 0 cm\n"); |
8989 | 0 | aStream.append("/Im"); |
8990 | 0 | aStream.append(rEmit.m_nBitmapObject); |
8991 | 0 | aStream.append(" Do\n"); |
8992 | 0 | } |
8993 | 0 | else |
8994 | 0 | { |
8995 | | // Reset line width to the default. |
8996 | 0 | aStream.append(" 1 w\n"); |
8997 | | |
8998 | | // vcl::RenderPDFBitmaps() effectively renders a white background for transparent input, be |
8999 | | // consistent with that. |
9000 | 0 | aStream.append("1 1 1 rg\n"); |
9001 | 0 | aStream.append("0 0 "); |
9002 | 0 | aStream.append(aSize.Width()); |
9003 | 0 | aStream.append(" "); |
9004 | 0 | aStream.append(aSize.Height()); |
9005 | 0 | aStream.append(" re\n"); |
9006 | 0 | aStream.append("f*\n"); |
9007 | | |
9008 | | // Reset non-stroking color in case the XObject uses the default |
9009 | 0 | aStream.append("0 0 0 rg\n"); |
9010 | | // No reference XObject, draw the form XObject containing the original |
9011 | | // page streams. |
9012 | 0 | aStream.append("/Im"); |
9013 | 0 | aStream.append(nWrappedFormObject); |
9014 | 0 | aStream.append(" Do\n"); |
9015 | 0 | } |
9016 | 0 | aStream.append("Q"); |
9017 | 0 | aLine.append(static_cast<sal_Int64>(calculateStreamSize(aStream.getLength()))); |
9018 | |
|
9019 | 0 | aLine.append(">>\nstream\n"); |
9020 | 0 | if (!writeBuffer(aLine)) |
9021 | 0 | return; |
9022 | 0 | aLine.setLength(0); |
9023 | |
|
9024 | 0 | checkAndEnableStreamEncryption(rEmit.m_nFormObject); |
9025 | 0 | aLine.append(aStream.getStr()); |
9026 | 0 | if (!writeBuffer(aLine)) |
9027 | 0 | return; |
9028 | 0 | aLine.setLength(0); |
9029 | 0 | disableStreamEncryption(); |
9030 | |
|
9031 | 0 | aLine.append("\nendstream\nendobj\n\n"); |
9032 | 0 | if (!writeBuffer(aLine)) |
9033 | 0 | return; |
9034 | 0 | } |
9035 | | |
9036 | | namespace |
9037 | | { |
9038 | | |
9039 | | sal_Int32 getRootParent(filter::PDFObjectElement* pObject) |
9040 | 0 | { |
9041 | 0 | auto* pReference = dynamic_cast<filter::PDFReferenceElement*>(pObject->Lookup("Parent"_ostr)); |
9042 | 0 | if (!pReference) |
9043 | 0 | return pObject->GetObjectValue(); |
9044 | | |
9045 | 0 | auto* pParent = pReference->LookupObject(); |
9046 | 0 | return getRootParent(pParent); |
9047 | 0 | } |
9048 | | |
9049 | | } // end anonymous |
9050 | | |
9051 | | void PDFWriterImpl::mergeAnnotationsFromExternalPage(filter::PDFObjectElement* pPage, std::map<sal_Int32, sal_Int32>& rCopiedResourcesMap) |
9052 | 0 | { |
9053 | 0 | auto* pResult = pPage->Lookup("Annots"_ostr); |
9054 | 0 | filter::PDFArrayElement* pArray = nullptr; |
9055 | | // If the Annots array is a reference - get the array from the referenced object |
9056 | 0 | auto pAnnotsReference = dynamic_cast<filter::PDFReferenceElement*>(pResult); |
9057 | 0 | if (pAnnotsReference) |
9058 | 0 | { |
9059 | 0 | filter::PDFObjectElement* pObject = pAnnotsReference->LookupObject(); |
9060 | 0 | pArray = pObject->GetArray(); |
9061 | 0 | } |
9062 | 0 | else |
9063 | 0 | { |
9064 | | // Not a reference so is it an array |
9065 | 0 | pArray = dynamic_cast<filter::PDFArrayElement*>(pResult); |
9066 | 0 | } |
9067 | | |
9068 | | // Have we found our /Annots array? |
9069 | 0 | if (!pArray) |
9070 | 0 | return; |
9071 | | |
9072 | 0 | std::unordered_set<sal_Int32> aAlreadyCopied; |
9073 | 0 | PDFObjectCopier aCopier(*this); |
9074 | 0 | SvMemoryStream& rDocBuffer = pPage->GetDocument().GetEditBuffer(); |
9075 | |
|
9076 | 0 | for (const auto pElement : pArray->GetElements()) |
9077 | 0 | { |
9078 | 0 | auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement); |
9079 | 0 | if (!pReference) |
9080 | 0 | continue; |
9081 | | |
9082 | 0 | filter::PDFObjectElement* pObject = pReference->LookupObject(); |
9083 | 0 | if (!pObject) |
9084 | 0 | continue; |
9085 | | |
9086 | | // Get the /Type and the /Subtype |
9087 | 0 | auto pType = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Type"_ostr)); |
9088 | 0 | auto pSubtype = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Subtype"_ostr)); |
9089 | | |
9090 | | // Is it a /Annot we want to copy? |
9091 | 0 | if (pType && pType->GetValue() == "Annot" && pSubtype) |
9092 | 0 | { |
9093 | 0 | bool bIsLink = pSubtype->GetValue() == "Link"; |
9094 | 0 | bool bIsWidget = pSubtype->GetValue() == "Widget"; |
9095 | | |
9096 | | // is link or widget |
9097 | 0 | if (!bIsLink && !bIsWidget) |
9098 | 0 | continue; |
9099 | | |
9100 | | // Copy over the annotation and refer to its new id. |
9101 | 0 | sal_Int32 nNewId = aCopier.copyExternalResource(rDocBuffer, *pObject, rCopiedResourcesMap); |
9102 | 0 | m_aPages.back().m_aAnnotations.push_back(nNewId); |
9103 | |
|
9104 | 0 | if (!bIsWidget) |
9105 | 0 | continue; |
9106 | | |
9107 | | // Find the root |
9108 | 0 | sal_Int32 nRootID = getRootParent(pObject); |
9109 | |
|
9110 | 0 | auto aIterator = rCopiedResourcesMap.find(nRootID); |
9111 | 0 | if (aIterator == rCopiedResourcesMap.end()) // Can't find the mapped ID ? |
9112 | 0 | continue; |
9113 | | |
9114 | 0 | nNewId = aIterator->second; |
9115 | | |
9116 | | // Ignore if we added the ID already |
9117 | 0 | if (aAlreadyCopied.find(nNewId) == aAlreadyCopied.end()) |
9118 | 0 | { |
9119 | | // Add new entry into copied widgets vector |
9120 | 0 | auto& rCopiedWidget = m_aCopiedWidgets.emplace_back(); |
9121 | 0 | rCopiedWidget.m_nObject = nNewId; |
9122 | 0 | aAlreadyCopied.emplace(nNewId); |
9123 | 0 | } |
9124 | 0 | } |
9125 | 0 | } |
9126 | |
|
9127 | 0 | } |
9128 | | |
9129 | | bool PDFWriterImpl::writeBitmapObject( const BitmapEmit& rObject ) |
9130 | 0 | { |
9131 | 0 | if (rObject.m_aReferenceXObject.hasExternalPDFData() && !m_aContext.UseReferenceXObject) |
9132 | 0 | { |
9133 | 0 | writeReferenceXObject(rObject.m_aReferenceXObject); |
9134 | 0 | return true; |
9135 | 0 | } |
9136 | | |
9137 | 0 | if (!updateObject(rObject.m_nObject)) |
9138 | 0 | return false; |
9139 | | |
9140 | 0 | bool bWriteMask = false; |
9141 | 0 | if( rObject.m_aBitmap.HasAlpha() ) |
9142 | 0 | { |
9143 | 0 | if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 ) |
9144 | 0 | bWriteMask = true; |
9145 | | // else draw without alpha channel |
9146 | 0 | } |
9147 | |
|
9148 | 0 | BitmapScopedReadAccess pAccess(rObject.m_aBitmap); |
9149 | |
|
9150 | 0 | bool bTrueColor = true; |
9151 | 0 | sal_Int32 nBitsPerComponent = 0; |
9152 | 0 | auto const ePixelFormat = rObject.m_aBitmap.getPixelFormat(); |
9153 | 0 | switch (ePixelFormat) |
9154 | 0 | { |
9155 | 0 | case vcl::PixelFormat::N8_BPP: |
9156 | 0 | bTrueColor = false; |
9157 | 0 | nBitsPerComponent = vcl::pixelFormatBitCount(ePixelFormat); |
9158 | 0 | break; |
9159 | 0 | case vcl::PixelFormat::N24_BPP: |
9160 | 0 | case vcl::PixelFormat::N32_BPP: |
9161 | 0 | bTrueColor = true; |
9162 | 0 | nBitsPerComponent = 8; |
9163 | 0 | break; |
9164 | 0 | case vcl::PixelFormat::INVALID: |
9165 | 0 | return false; |
9166 | 0 | } |
9167 | | |
9168 | 0 | sal_Int32 nStreamLengthObject = createObject(); |
9169 | 0 | sal_Int32 nMaskObject = 0; |
9170 | |
|
9171 | 0 | if (g_bDebugDisableCompression) |
9172 | 0 | { |
9173 | 0 | emitComment( "PDFWriterImpl::writeBitmapObject" ); |
9174 | 0 | } |
9175 | 0 | OStringBuffer aLine(PDFWRITER_IMPL_BUFFERSIZE); |
9176 | 0 | aLine.append( rObject.m_nObject ); |
9177 | 0 | aLine.append( " 0 obj\n" |
9178 | 0 | "<</Type/XObject/Subtype/Image/Width " ); |
9179 | 0 | aLine.append( static_cast<sal_Int32>(rObject.m_aBitmap.GetSizePixel().Width()) ); |
9180 | 0 | aLine.append( "/Height " ); |
9181 | 0 | aLine.append( static_cast<sal_Int32>(rObject.m_aBitmap.GetSizePixel().Height()) ); |
9182 | 0 | aLine.append( "/BitsPerComponent " ); |
9183 | 0 | aLine.append( nBitsPerComponent ); |
9184 | 0 | aLine.append( "/Length " ); |
9185 | 0 | aLine.append( nStreamLengthObject ); |
9186 | 0 | aLine.append( " 0 R\n" ); |
9187 | 0 | if (!g_bDebugDisableCompression) |
9188 | 0 | { |
9189 | 0 | if( nBitsPerComponent != 1 ) |
9190 | 0 | { |
9191 | 0 | aLine.append( "/Filter/FlateDecode" ); |
9192 | 0 | } |
9193 | 0 | else |
9194 | 0 | { |
9195 | 0 | aLine.append( "/Filter/CCITTFaxDecode/DecodeParms<</K -1/BlackIs1 true/Columns " ); |
9196 | 0 | aLine.append( static_cast<sal_Int32>(rObject.m_aBitmap.GetSizePixel().Width()) ); |
9197 | 0 | aLine.append( ">>\n" ); |
9198 | 0 | } |
9199 | 0 | } |
9200 | 0 | aLine.append( "/ColorSpace" ); |
9201 | 0 | if( bTrueColor ) |
9202 | 0 | aLine.append( "/DeviceRGB\n" ); |
9203 | 0 | else |
9204 | 0 | { |
9205 | 0 | aLine.append( "[ /Indexed/DeviceRGB " ); |
9206 | 0 | aLine.append( static_cast<sal_Int32>(pAccess->GetPaletteEntryCount()-1) ); |
9207 | 0 | aLine.append( "\n<" ); |
9208 | 0 | if (m_aContext.Encryption.canEncrypt()) |
9209 | 0 | { |
9210 | 0 | enableStringEncryption(rObject.m_nObject); |
9211 | | //check encryption buffer size |
9212 | 0 | m_vEncryptionBuffer.resize(pAccess->GetPaletteEntryCount()*3); |
9213 | 0 | int nChar = 0; |
9214 | | //fill the encryption buffer |
9215 | 0 | for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ ) |
9216 | 0 | { |
9217 | 0 | const BitmapColor& rColor = pAccess->GetPaletteColor( i ); |
9218 | 0 | m_vEncryptionBuffer[nChar++] = rColor.GetRed(); |
9219 | 0 | m_vEncryptionBuffer[nChar++] = rColor.GetGreen(); |
9220 | 0 | m_vEncryptionBuffer[nChar++] = rColor.GetBlue(); |
9221 | 0 | } |
9222 | | //encrypt the colorspace lookup table |
9223 | 0 | std::vector<sal_uInt8> aOutputBuffer(nChar); |
9224 | 0 | m_pPDFEncryptor->encrypt(m_vEncryptionBuffer.data(), nChar, aOutputBuffer, nChar); |
9225 | | //now queue the data for output |
9226 | 0 | COSWriter::appendHexArray(aOutputBuffer.data(), aOutputBuffer.size(), aLine); |
9227 | 0 | } |
9228 | 0 | else //no encryption requested (PDF/A-1a program flow drops here) |
9229 | 0 | { |
9230 | 0 | for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ ) |
9231 | 0 | { |
9232 | 0 | const BitmapColor& rColor = pAccess->GetPaletteColor( i ); |
9233 | 0 | COSWriter::appendHex( rColor.GetRed(), aLine ); |
9234 | 0 | COSWriter::appendHex( rColor.GetGreen(), aLine ); |
9235 | 0 | COSWriter::appendHex( rColor.GetBlue(), aLine ); |
9236 | 0 | } |
9237 | 0 | } |
9238 | 0 | aLine.append( ">\n]\n" ); |
9239 | 0 | } |
9240 | |
|
9241 | 0 | if (!m_bIsPDF_A1) |
9242 | 0 | { |
9243 | 0 | if( bWriteMask ) |
9244 | 0 | { |
9245 | 0 | nMaskObject = createObject(); |
9246 | 0 | aLine.append( "/SMask " ); |
9247 | 0 | aLine.append( nMaskObject ); |
9248 | 0 | aLine.append( " 0 R\n" ); |
9249 | 0 | } |
9250 | 0 | } |
9251 | 0 | else if( m_bIsPDF_A1 && bWriteMask ) |
9252 | 0 | m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA ); |
9253 | |
|
9254 | 0 | aLine.append( ">>\n" |
9255 | 0 | "stream\n" ); |
9256 | 0 | if (!writeBuffer(aLine)) return false; |
9257 | 0 | sal_uInt64 nStartPos = 0; |
9258 | 0 | if (osl::File::E_None != m_aFile.getPos(nStartPos)) |
9259 | 0 | return false; |
9260 | | |
9261 | 0 | checkAndEnableStreamEncryption( rObject.m_nObject ); |
9262 | 0 | { |
9263 | 0 | beginCompression(); |
9264 | 0 | if( ! bTrueColor || pAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb ) |
9265 | 0 | { |
9266 | | //With PDF bitmaps, each row is padded to a BYTE boundary (multiple of 8 bits). |
9267 | 0 | const int nScanLineBytes = ((pAccess->GetBitCount() * pAccess->Width()) + 7U) / 8U; |
9268 | |
|
9269 | 0 | for( tools::Long i = 0, nHeight = pAccess->Height(); i < nHeight; i++ ) |
9270 | 0 | { |
9271 | 0 | if (!writeBufferBytes(pAccess->GetScanline(i), nScanLineBytes)) |
9272 | 0 | return false; |
9273 | 0 | } |
9274 | 0 | } |
9275 | 0 | else |
9276 | 0 | { |
9277 | 0 | const int nScanLineBytes = pAccess->Width()*3; |
9278 | 0 | std::unique_ptr<sal_uInt8[]> xCol(new sal_uInt8[nScanLineBytes]); |
9279 | 0 | for( tools::Long y = 0, nHeight = pAccess->Height(); y < nHeight; y++ ) |
9280 | 0 | { |
9281 | 0 | for( tools::Long x = 0, nWidth = pAccess->Width(); x < nWidth; x++ ) |
9282 | 0 | { |
9283 | 0 | BitmapColor aColor = pAccess->GetColor( y, x ); |
9284 | 0 | xCol[3*x+0] = aColor.GetRed(); |
9285 | 0 | xCol[3*x+1] = aColor.GetGreen(); |
9286 | 0 | xCol[3*x+2] = aColor.GetBlue(); |
9287 | 0 | } |
9288 | 0 | if (!writeBufferBytes(xCol.get(), nScanLineBytes)) |
9289 | 0 | return false; |
9290 | 0 | } |
9291 | 0 | } |
9292 | 0 | endCompression(); |
9293 | 0 | } |
9294 | 0 | disableStreamEncryption(); |
9295 | |
|
9296 | 0 | sal_uInt64 nEndPos = 0; |
9297 | 0 | if (osl::File::E_None != m_aFile.getPos(nEndPos)) |
9298 | 0 | return false; |
9299 | 0 | aLine.setLength( 0 ); |
9300 | 0 | aLine.append( "\nendstream\nendobj\n\n" ); |
9301 | 0 | if (!writeBuffer(aLine)) return false; |
9302 | 0 | if (!updateObject(nStreamLengthObject)) return false; |
9303 | 0 | aLine.setLength( 0 ); |
9304 | 0 | aLine.append( nStreamLengthObject ); |
9305 | 0 | aLine.append( " 0 obj\n" ); |
9306 | 0 | aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) ); |
9307 | 0 | aLine.append( "\nendobj\n\n" ); |
9308 | 0 | if (!writeBuffer(aLine)) return false; |
9309 | | |
9310 | 0 | if( nMaskObject ) |
9311 | 0 | return writeBitmapMaskObject( nMaskObject, rObject.m_aBitmap.CreateAlphaMask() ); |
9312 | | |
9313 | 0 | writeReferenceXObject(rObject.m_aReferenceXObject); |
9314 | |
|
9315 | 0 | return true; |
9316 | 0 | } |
9317 | | |
9318 | | bool PDFWriterImpl::writeBitmapMaskObject( sal_Int32 nMaskObject, const AlphaMask& rAlphaMask ) |
9319 | 0 | { |
9320 | 0 | assert( rAlphaMask.GetBitmap().getPixelFormat() == vcl::PixelFormat::N8_BPP ); |
9321 | |
|
9322 | 0 | if (!updateObject(nMaskObject)) |
9323 | 0 | return false; |
9324 | | |
9325 | 0 | Bitmap aBitmap; |
9326 | 0 | if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 ) |
9327 | 0 | { |
9328 | 0 | aBitmap = rAlphaMask.GetBitmap(); |
9329 | 0 | aBitmap.Convert( BmpConversion::N1BitThreshold ); |
9330 | 0 | } |
9331 | 0 | else |
9332 | 0 | { |
9333 | 0 | aBitmap = rAlphaMask.GetBitmap(); |
9334 | 0 | } |
9335 | |
|
9336 | 0 | const sal_Int32 nBitsPerComponent = 8; |
9337 | |
|
9338 | 0 | sal_Int32 nStreamLengthObject = createObject(); |
9339 | |
|
9340 | 0 | if (g_bDebugDisableCompression) |
9341 | 0 | { |
9342 | 0 | emitComment( "PDFWriterImpl::writeBitmapObject" ); |
9343 | 0 | } |
9344 | 0 | OStringBuffer aLine(PDFWRITER_IMPL_BUFFERSIZE); |
9345 | 0 | aLine.append( nMaskObject ); |
9346 | 0 | aLine.append( " 0 obj\n" |
9347 | 0 | "<</Type/XObject/Subtype/Image/Width " ); |
9348 | 0 | aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Width()) ); |
9349 | 0 | aLine.append( "/Height " ); |
9350 | 0 | aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Height()) ); |
9351 | 0 | aLine.append( "/BitsPerComponent " ); |
9352 | 0 | aLine.append( nBitsPerComponent ); |
9353 | 0 | aLine.append( "/Length " ); |
9354 | 0 | aLine.append( nStreamLengthObject ); |
9355 | 0 | aLine.append( " 0 R\n" ); |
9356 | 0 | if (!g_bDebugDisableCompression) |
9357 | 0 | { |
9358 | 0 | aLine.append( "/Filter/FlateDecode" ); |
9359 | 0 | } |
9360 | 0 | aLine.append( "/ColorSpace/DeviceGray\n" |
9361 | 0 | "/Decode [ 1 0 ]\n" ); |
9362 | |
|
9363 | 0 | aLine.append( ">>\n" |
9364 | 0 | "stream\n" ); |
9365 | 0 | if (!writeBuffer(aLine)) return false; |
9366 | 0 | sal_uInt64 nStartPos = 0; |
9367 | 0 | if (osl::File::E_None != m_aFile.getPos(nStartPos)) |
9368 | 0 | return false; |
9369 | | |
9370 | 0 | checkAndEnableStreamEncryption( nMaskObject ); |
9371 | 0 | beginCompression(); |
9372 | 0 | BitmapScopedReadAccess pAccess(aBitmap); |
9373 | | //With PDF bitmaps, each row is padded to a BYTE boundary (multiple of 8 bits). |
9374 | 0 | const int nScanLineBytes = ((pAccess->GetBitCount() * pAccess->Width()) + 7U) / 8U; |
9375 | | // we have alpha, but we want to output transparency, so we need to invert the data |
9376 | 0 | std::unique_ptr<sal_uInt8[]> pInvertedBytes = std::make_unique<sal_uInt8[]>(nScanLineBytes); |
9377 | 0 | for( tools::Long i = 0, nHeight = pAccess->Height(); i < nHeight; i++ ) |
9378 | 0 | { |
9379 | 0 | const Scanline pScanline = pAccess->GetScanline(i); |
9380 | 0 | std::copy(pScanline, pScanline + nScanLineBytes, pInvertedBytes.get()); |
9381 | 0 | for (auto p = pInvertedBytes.get(); p < pInvertedBytes.get() + nScanLineBytes; ++p) |
9382 | 0 | *p = ~(*p); |
9383 | 0 | if (!writeBufferBytes(pInvertedBytes.get(), nScanLineBytes)) |
9384 | 0 | return false; |
9385 | 0 | } |
9386 | 0 | endCompression(); |
9387 | 0 | disableStreamEncryption(); |
9388 | |
|
9389 | 0 | sal_uInt64 nEndPos = 0; |
9390 | 0 | if (osl::File::E_None != m_aFile.getPos(nEndPos)) |
9391 | 0 | return false; |
9392 | 0 | aLine.setLength( 0 ); |
9393 | 0 | aLine.append( "\nendstream\nendobj\n\n" ); |
9394 | 0 | if (!writeBuffer(aLine)) return false; |
9395 | 0 | if (!updateObject(nStreamLengthObject)) return false; |
9396 | 0 | aLine.setLength( 0 ); |
9397 | 0 | aLine.append( nStreamLengthObject ); |
9398 | 0 | aLine.append( " 0 obj\n" ); |
9399 | 0 | aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) ); |
9400 | 0 | aLine.append( "\nendobj\n\n" ); |
9401 | 0 | if (!writeBuffer(aLine)) return false; |
9402 | | |
9403 | 0 | return true; |
9404 | 0 | } |
9405 | | |
9406 | | void PDFWriterImpl::createEmbeddedFile(const Graphic& rGraphic, ReferenceXObjectEmit& rEmit, sal_Int32 nBitmapObject) |
9407 | 0 | { |
9408 | | // The bitmap object is always a valid identifier, even if the graphic has |
9409 | | // no pdf data. |
9410 | 0 | rEmit.m_nBitmapObject = nBitmapObject; |
9411 | |
|
9412 | 0 | if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf) |
9413 | 0 | return; |
9414 | | |
9415 | 0 | BinaryDataContainer const & rDataContainer = rGraphic.getVectorGraphicData()->getBinaryDataContainer(); |
9416 | |
|
9417 | 0 | if (m_aContext.UseReferenceXObject) |
9418 | 0 | { |
9419 | | // Store the original PDF data as an embedded file. |
9420 | 0 | auto nObjectID = addEmbeddedFile(rDataContainer); |
9421 | 0 | rEmit.m_nEmbeddedObject = nObjectID; |
9422 | 0 | } |
9423 | 0 | else |
9424 | 0 | { |
9425 | 0 | sal_Int32 aIndex = m_aExternalPDFStreams.store(rDataContainer); |
9426 | 0 | rEmit.m_nExternalPDFPageIndex = rGraphic.getVectorGraphicData()->getPageIndex(); |
9427 | 0 | rEmit.m_nExternalPDFDataIndex = aIndex; |
9428 | 0 | } |
9429 | |
|
9430 | 0 | rEmit.m_nFormObject = createObject(); |
9431 | 0 | rEmit.m_aPixelSize = rGraphic.GetSizePixel(); |
9432 | 0 | } |
9433 | | |
9434 | | void PDFWriterImpl::drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const Size& rSizePixel, const tools::Rectangle& rTargetArea, const AlphaMask& rAlphaMask, const Graphic& rGraphic ) |
9435 | 0 | { |
9436 | 0 | MARK( "drawJPGBitmap" ); |
9437 | |
|
9438 | 0 | OStringBuffer aLine( 80 ); |
9439 | 0 | updateGraphicsState(); |
9440 | | |
9441 | | // #i40055# sanity check |
9442 | 0 | if( ! (rTargetArea.GetWidth() && rTargetArea.GetHeight() ) ) |
9443 | 0 | return; |
9444 | 0 | if( ! (rSizePixel.Width() && rSizePixel.Height()) ) |
9445 | 0 | return; |
9446 | | |
9447 | 0 | rDCTData.Seek( 0 ); |
9448 | 0 | if( bIsTrueColor && m_aContext.ColorMode == PDFWriter::DrawGreyscale ) |
9449 | 0 | { |
9450 | | // need to convert to grayscale; |
9451 | | // load stream to bitmap and draw the bitmap instead |
9452 | 0 | Graphic aGraphic; |
9453 | 0 | GraphicConverter::Import( rDCTData, aGraphic, ConvertDataFormat::JPG ); |
9454 | 0 | if( !rAlphaMask.IsEmpty() && rAlphaMask.GetSizePixel() == aGraphic.GetSizePixel() ) |
9455 | 0 | { |
9456 | 0 | Bitmap aBmp( aGraphic.GetBitmap().CreateColorBitmap() ); |
9457 | 0 | Bitmap aBmp2( aBmp, rAlphaMask ); |
9458 | 0 | drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aBmp2 ); |
9459 | 0 | } |
9460 | 0 | else |
9461 | 0 | drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aGraphic.GetBitmap() ); |
9462 | 0 | return; |
9463 | 0 | } |
9464 | | |
9465 | 0 | std::unique_ptr<SvMemoryStream> pStream(new SvMemoryStream); |
9466 | 0 | pStream->WriteStream( rDCTData ); |
9467 | 0 | pStream->Seek( STREAM_SEEK_TO_END ); |
9468 | |
|
9469 | 0 | BitmapID aID; |
9470 | 0 | aID.m_aPixelSize = rSizePixel; |
9471 | 0 | aID.m_nSize = pStream->Tell(); |
9472 | 0 | pStream->Seek( STREAM_SEEK_TO_BEGIN ); |
9473 | 0 | aID.m_nChecksum = rtl_crc32( 0, pStream->GetData(), aID.m_nSize ); |
9474 | 0 | if( ! rAlphaMask.IsEmpty() ) |
9475 | 0 | aID.m_nMaskChecksum = rAlphaMask.GetChecksum(); |
9476 | |
|
9477 | 0 | std::vector< JPGEmit >::const_iterator it = std::find_if(m_aJPGs.begin(), m_aJPGs.end(), |
9478 | 0 | [&](const JPGEmit& arg) { return aID == arg.m_aID; }); |
9479 | 0 | if( it == m_aJPGs.end() ) |
9480 | 0 | { |
9481 | 0 | m_aJPGs.emplace( m_aJPGs.begin() ); |
9482 | 0 | JPGEmit& rEmit = m_aJPGs.front(); |
9483 | 0 | if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject) |
9484 | 0 | rEmit.m_nObject = createObject(); |
9485 | 0 | rEmit.m_aID = aID; |
9486 | 0 | rEmit.m_pStream = std::move( pStream ); |
9487 | 0 | rEmit.m_bTrueColor = bIsTrueColor; |
9488 | 0 | if( !rAlphaMask.IsEmpty() && rAlphaMask.GetSizePixel() == rSizePixel ) |
9489 | 0 | rEmit.m_aAlphaMask = rAlphaMask; |
9490 | 0 | createEmbeddedFile(rGraphic, rEmit.m_aReferenceXObject, rEmit.m_nObject); |
9491 | |
|
9492 | 0 | it = m_aJPGs.begin(); |
9493 | 0 | } |
9494 | |
|
9495 | 0 | aLine.append( "q " ); |
9496 | 0 | sal_Int32 nCheckWidth = 0; |
9497 | 0 | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetWidth()), aLine, false, &nCheckWidth ); |
9498 | 0 | aLine.append( " 0 0 " ); |
9499 | 0 | sal_Int32 nCheckHeight = 0; |
9500 | 0 | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetHeight()), aLine, true, &nCheckHeight ); |
9501 | 0 | aLine.append( ' ' ); |
9502 | 0 | m_aPages.back().appendPoint( rTargetArea.BottomLeft(), aLine ); |
9503 | 0 | aLine.append( " cm\n/Im" ); |
9504 | 0 | sal_Int32 nObject = it->m_aReferenceXObject.getObject(); |
9505 | 0 | aLine.append(nObject); |
9506 | 0 | aLine.append( " Do Q\n" ); |
9507 | 0 | if( nCheckWidth == 0 || nCheckHeight == 0 ) |
9508 | 0 | { |
9509 | | // #i97512# avoid invalid current matrix |
9510 | 0 | aLine.setLength( 0 ); |
9511 | 0 | aLine.append( "\n%jpeg image /Im" ); |
9512 | 0 | aLine.append( it->m_nObject ); |
9513 | 0 | aLine.append( " scaled to zero size, omitted\n" ); |
9514 | 0 | } |
9515 | 0 | writeBuffer( aLine ); |
9516 | |
|
9517 | 0 | OString aObjName = "Im" + OString::number(nObject); |
9518 | 0 | pushResource( ResourceKind::XObject, aObjName, nObject ); |
9519 | |
|
9520 | 0 | } |
9521 | | |
9522 | | void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEmit& rBitmap, const Color& rFillColor ) |
9523 | 0 | { |
9524 | 0 | OStringBuffer& rLine = drawBitmapLine; |
9525 | 0 | rLine.setLength(0); |
9526 | 0 | updateGraphicsState(); |
9527 | |
|
9528 | 0 | rLine.append( "q " ); |
9529 | 0 | if( rFillColor != COL_TRANSPARENT ) |
9530 | 0 | { |
9531 | 0 | appendNonStrokingColor( rFillColor, rLine ); |
9532 | 0 | rLine.append( ' ' ); |
9533 | 0 | } |
9534 | 0 | sal_Int32 nCheckWidth = 0; |
9535 | 0 | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Width()), rLine, false, &nCheckWidth ); |
9536 | 0 | rLine.append( " 0 0 " ); |
9537 | 0 | sal_Int32 nCheckHeight = 0; |
9538 | 0 | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Height()), rLine, true, &nCheckHeight ); |
9539 | 0 | rLine.append( ' ' ); |
9540 | 0 | m_aPages.back().appendPoint( rDestPoint + Point( 0, rDestSize.Height()-1 ), rLine ); |
9541 | 0 | rLine.append( " cm\n/Im" ); |
9542 | 0 | sal_Int32 nObject = rBitmap.m_aReferenceXObject.getObject(); |
9543 | 0 | rLine.append(nObject); |
9544 | 0 | rLine.append( " Do Q\n" ); |
9545 | 0 | if( nCheckWidth == 0 || nCheckHeight == 0 ) |
9546 | 0 | { |
9547 | | // #i97512# avoid invalid current matrix |
9548 | 0 | rLine.setLength( 0 ); |
9549 | 0 | rLine.append( "\n%bitmap image /Im" ); |
9550 | 0 | rLine.append( rBitmap.m_nObject ); |
9551 | 0 | rLine.append( " scaled to zero size, omitted\n" ); |
9552 | 0 | } |
9553 | 0 | writeBuffer( rLine ); |
9554 | 0 | } |
9555 | | |
9556 | | const BitmapEmit& PDFWriterImpl::createBitmapEmit(const Bitmap& i_rBitmap, const Graphic& rGraphic, std::list<BitmapEmit>& rBitmaps, ResourceDict& rResourceDict, std::list<StreamRedirect>& rOutputStreams) |
9557 | 0 | { |
9558 | 0 | Bitmap aBitmap( i_rBitmap ); |
9559 | 0 | auto ePixelFormat = aBitmap.getPixelFormat(); |
9560 | 0 | if( m_aContext.ColorMode == PDFWriter::DrawGreyscale ) |
9561 | 0 | aBitmap.Convert(BmpConversion::N8BitGreys); |
9562 | 0 | BitmapID aID; |
9563 | 0 | aID.m_aPixelSize = aBitmap.GetSizePixel(); |
9564 | 0 | aID.m_nSize = vcl::pixelFormatBitCount(ePixelFormat); |
9565 | 0 | if( aBitmap.HasAlpha() ) |
9566 | 0 | { |
9567 | 0 | std::pair<Bitmap, AlphaMask> aPair = aBitmap.SplitIntoColorAndAlpha(); |
9568 | 0 | aID.m_nChecksum = aPair.first.GetChecksum(); |
9569 | 0 | aID.m_nMaskChecksum = aPair.second.GetChecksum(); |
9570 | 0 | } |
9571 | 0 | else |
9572 | 0 | { |
9573 | 0 | aID.m_nChecksum = aBitmap.GetChecksum(); |
9574 | 0 | aID.m_nMaskChecksum = 0; |
9575 | 0 | } |
9576 | 0 | std::list<BitmapEmit>::const_iterator it = std::find_if(rBitmaps.begin(), rBitmaps.end(), |
9577 | 0 | [&](const BitmapEmit& arg) { return aID == arg.m_aID; }); |
9578 | 0 | if (it == rBitmaps.end()) |
9579 | 0 | { |
9580 | 0 | rBitmaps.push_front(BitmapEmit()); |
9581 | 0 | rBitmaps.front().m_aID = aID; |
9582 | 0 | rBitmaps.front().m_aBitmap = std::move(aBitmap); |
9583 | 0 | if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject) |
9584 | 0 | rBitmaps.front().m_nObject = createObject(); |
9585 | 0 | createEmbeddedFile(rGraphic, rBitmaps.front().m_aReferenceXObject, rBitmaps.front().m_nObject); |
9586 | 0 | it = rBitmaps.begin(); |
9587 | 0 | } |
9588 | |
|
9589 | 0 | sal_Int32 nObject = it->m_aReferenceXObject.getObject(); |
9590 | 0 | OString aObjName = "Im" + OString::number(nObject); |
9591 | 0 | pushResource(ResourceKind::XObject, aObjName, nObject, rResourceDict, rOutputStreams); |
9592 | |
|
9593 | 0 | return *it; |
9594 | 0 | } |
9595 | | |
9596 | | const BitmapEmit& PDFWriterImpl::createBitmapEmit( const Bitmap& i_rBitmap, const Graphic& rGraphic ) |
9597 | 0 | { |
9598 | 0 | return createBitmapEmit(i_rBitmap, rGraphic, m_aBitmaps, m_aGlobalResourceDict, m_aOutputStreams); |
9599 | 0 | } |
9600 | | |
9601 | | void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap, const Graphic& rGraphic ) |
9602 | 0 | { |
9603 | 0 | MARK( "drawBitmap (Bitmap)" ); |
9604 | | |
9605 | | // #i40055# sanity check |
9606 | 0 | if( ! (rDestSize.Width() && rDestSize.Height()) ) |
9607 | 0 | return; |
9608 | | |
9609 | 0 | const BitmapEmit& rEmit = createBitmapEmit( rBitmap, rGraphic ); |
9610 | 0 | drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT ); |
9611 | 0 | } |
9612 | | |
9613 | | void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap ) |
9614 | 0 | { |
9615 | 0 | MARK( "drawBitmap (Bitmap)" ); |
9616 | | |
9617 | | // #i40055# sanity check |
9618 | 0 | if( ! (rDestSize.Width() && rDestSize.Height()) ) |
9619 | 0 | return; |
9620 | | |
9621 | 0 | const BitmapEmit& rEmit = createBitmapEmit( rBitmap, Graphic() ); |
9622 | 0 | drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT ); |
9623 | 0 | } |
9624 | | |
9625 | | sal_Int32 PDFWriterImpl::createGradient( const Gradient& rGradient, const Size& rSize ) |
9626 | 0 | { |
9627 | 0 | Size aPtSize( convert( m_aGraphicsStack.front().m_aMapMode, |
9628 | 0 | MapMode( MapUnit::MapPoint ), |
9629 | 0 | this, |
9630 | 0 | rSize ) ); |
9631 | | // check if we already have this gradient |
9632 | | // rounding to point will generally lose some pixels |
9633 | | // round up to point boundary |
9634 | 0 | aPtSize.AdjustWidth( 1 ); |
9635 | 0 | aPtSize.AdjustHeight( 1 ); |
9636 | 0 | std::list< GradientEmit >::const_iterator it = std::find_if(m_aGradients.begin(), m_aGradients.end(), |
9637 | 0 | [&](const GradientEmit& arg) { return ((rGradient == arg.m_aGradient) && (aPtSize == arg.m_aSize) ); }); |
9638 | |
|
9639 | 0 | if( it == m_aGradients.end() ) |
9640 | 0 | { |
9641 | 0 | m_aGradients.push_front( GradientEmit() ); |
9642 | 0 | m_aGradients.front().m_aGradient = rGradient; |
9643 | 0 | m_aGradients.front().m_nObject = createObject(); |
9644 | 0 | m_aGradients.front().m_aSize = aPtSize; |
9645 | 0 | it = m_aGradients.begin(); |
9646 | 0 | } |
9647 | |
|
9648 | 0 | OStringBuffer aObjName( 16 ); |
9649 | 0 | aObjName.append( 'P' ); |
9650 | 0 | aObjName.append( it->m_nObject ); |
9651 | 0 | pushResource( ResourceKind::Shading, aObjName.makeStringAndClear(), it->m_nObject ); |
9652 | |
|
9653 | 0 | return it->m_nObject; |
9654 | 0 | } |
9655 | | |
9656 | | void PDFWriterImpl::drawGradient( const tools::Rectangle& rRect, const Gradient& rGradient ) |
9657 | 0 | { |
9658 | 0 | MARK( "drawGradient (Rectangle)" ); |
9659 | |
|
9660 | 0 | sal_Int32 nGradient = createGradient( rGradient, rRect.GetSize() ); |
9661 | |
|
9662 | 0 | Point aTranslate( rRect.BottomLeft() ); |
9663 | 0 | aTranslate += Point( 0, 1 ); |
9664 | |
|
9665 | 0 | updateGraphicsState(); |
9666 | |
|
9667 | 0 | OStringBuffer aLine( 80 ); |
9668 | 0 | aLine.append( "q 1 0 0 1 " ); |
9669 | 0 | m_aPages.back().appendPoint( aTranslate, aLine ); |
9670 | 0 | aLine.append( " cm " ); |
9671 | | // if a stroke is appended reset the clip region before stroke |
9672 | 0 | if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) |
9673 | 0 | aLine.append( "q " ); |
9674 | 0 | aLine.append( "0 0 " ); |
9675 | 0 | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false ); |
9676 | 0 | aLine.append( ' ' ); |
9677 | 0 | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine ); |
9678 | 0 | aLine.append( " re W n\n" ); |
9679 | |
|
9680 | 0 | aLine.append( "/P" ); |
9681 | 0 | aLine.append( nGradient ); |
9682 | 0 | aLine.append( " sh " ); |
9683 | 0 | if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT ) |
9684 | 0 | { |
9685 | 0 | aLine.append( "Q 0 0 " ); |
9686 | 0 | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false ); |
9687 | 0 | aLine.append( ' ' ); |
9688 | 0 | m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine ); |
9689 | 0 | aLine.append( " re S " ); |
9690 | 0 | } |
9691 | 0 | aLine.append( "Q\n" ); |
9692 | 0 | writeBuffer( aLine ); |
9693 | 0 | } |
9694 | | |
9695 | | void PDFWriterImpl::drawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch ) |
9696 | 0 | { |
9697 | 0 | MARK( "drawHatch" ); |
9698 | |
|
9699 | 0 | updateGraphicsState(); |
9700 | |
|
9701 | 0 | if( rPolyPoly.Count() ) |
9702 | 0 | { |
9703 | 0 | tools::PolyPolygon aPolyPoly( rPolyPoly ); |
9704 | |
|
9705 | 0 | aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME ); |
9706 | 0 | push( PushFlags::LINECOLOR ); |
9707 | 0 | setLineColor( rHatch.GetColor() ); |
9708 | 0 | DrawHatch( aPolyPoly, rHatch, false ); |
9709 | 0 | pop(); |
9710 | 0 | } |
9711 | 0 | } |
9712 | | |
9713 | | void PDFWriterImpl::drawWallpaper( const tools::Rectangle& rRect, const Wallpaper& rWall ) |
9714 | 0 | { |
9715 | 0 | MARK( "drawWallpaper" ); |
9716 | |
|
9717 | 0 | bool bDrawColor = false; |
9718 | 0 | bool bDrawGradient = false; |
9719 | 0 | bool bDrawBitmap = false; |
9720 | |
|
9721 | 0 | Bitmap aBitmap; |
9722 | 0 | Point aBmpPos = rRect.TopLeft(); |
9723 | 0 | Size aBmpSize; |
9724 | 0 | if( rWall.IsBitmap() ) |
9725 | 0 | { |
9726 | 0 | aBitmap = rWall.GetBitmap(); |
9727 | 0 | aBmpSize = convert( aBitmap.GetPrefMapMode(), |
9728 | 0 | getMapMode(), |
9729 | 0 | this, |
9730 | 0 | aBitmap.GetPrefSize() ); |
9731 | 0 | tools::Rectangle aRect( rRect ); |
9732 | 0 | if( rWall.IsRect() ) |
9733 | 0 | { |
9734 | 0 | aRect = rWall.GetRect(); |
9735 | 0 | aBmpPos = aRect.TopLeft(); |
9736 | 0 | aBmpSize = aRect.GetSize(); |
9737 | 0 | } |
9738 | 0 | if( rWall.GetStyle() != WallpaperStyle::Scale ) |
9739 | 0 | { |
9740 | 0 | if( rWall.GetStyle() != WallpaperStyle::Tile ) |
9741 | 0 | { |
9742 | 0 | bDrawBitmap = true; |
9743 | 0 | if( rWall.IsGradient() ) |
9744 | 0 | bDrawGradient = true; |
9745 | 0 | else |
9746 | 0 | bDrawColor = true; |
9747 | 0 | switch( rWall.GetStyle() ) |
9748 | 0 | { |
9749 | 0 | case WallpaperStyle::TopLeft: |
9750 | 0 | break; |
9751 | 0 | case WallpaperStyle::Top: |
9752 | 0 | aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 ); |
9753 | 0 | break; |
9754 | 0 | case WallpaperStyle::Left: |
9755 | 0 | aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 ); |
9756 | 0 | break; |
9757 | 0 | case WallpaperStyle::TopRight: |
9758 | 0 | aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() ); |
9759 | 0 | break; |
9760 | 0 | case WallpaperStyle::Center: |
9761 | 0 | aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 ); |
9762 | 0 | aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 ); |
9763 | 0 | break; |
9764 | 0 | case WallpaperStyle::Right: |
9765 | 0 | aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() ); |
9766 | 0 | aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 ); |
9767 | 0 | break; |
9768 | 0 | case WallpaperStyle::BottomLeft: |
9769 | 0 | aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() ); |
9770 | 0 | break; |
9771 | 0 | case WallpaperStyle::Bottom: |
9772 | 0 | aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 ); |
9773 | 0 | aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() ); |
9774 | 0 | break; |
9775 | 0 | case WallpaperStyle::BottomRight: |
9776 | 0 | aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() ); |
9777 | 0 | aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() ); |
9778 | 0 | break; |
9779 | 0 | default: ; |
9780 | 0 | } |
9781 | 0 | } |
9782 | 0 | else |
9783 | 0 | { |
9784 | | // push the bitmap |
9785 | 0 | const BitmapEmit& rEmit = createBitmapEmit( aBitmap, Graphic() ); |
9786 | | |
9787 | | // convert to page coordinates; this needs to be done here |
9788 | | // since the emit does not know the page anymore |
9789 | 0 | tools::Rectangle aConvertRect( aBmpPos, aBmpSize ); |
9790 | 0 | m_aPages.back().convertRect( aConvertRect ); |
9791 | |
|
9792 | 0 | OString aImageName = "Im" + OString::number( rEmit.m_nObject ); |
9793 | | |
9794 | | // push the pattern |
9795 | 0 | OStringBuffer aTilingStream( 32 ); |
9796 | 0 | appendFixedInt( aConvertRect.GetWidth(), aTilingStream ); |
9797 | 0 | aTilingStream.append( " 0 0 " ); |
9798 | 0 | appendFixedInt( aConvertRect.GetHeight(), aTilingStream ); |
9799 | 0 | aTilingStream.append( " 0 0 cm\n/" ); |
9800 | 0 | aTilingStream.append( aImageName ); |
9801 | 0 | aTilingStream.append( " Do\n" ); |
9802 | |
|
9803 | 0 | m_aTilings.emplace_back( ); |
9804 | 0 | m_aTilings.back().m_nObject = createObject(); |
9805 | 0 | m_aTilings.back().m_aRectangle = tools::Rectangle( Point( 0, 0 ), aConvertRect.GetSize() ); |
9806 | 0 | m_aTilings.back().m_pTilingStream.reset(new SvMemoryStream()); |
9807 | 0 | m_aTilings.back().m_pTilingStream->WriteBytes( |
9808 | 0 | aTilingStream.getStr(), aTilingStream.getLength() ); |
9809 | | // phase the tiling so wallpaper begins on upper left |
9810 | 0 | if ((aConvertRect.GetWidth() == 0) || (aConvertRect.GetHeight() == 0)) |
9811 | 0 | throw o3tl::divide_by_zero(); |
9812 | 0 | m_aTilings.back().m_aTransform.matrix[2] = double(aConvertRect.Left() % aConvertRect.GetWidth()) / fDivisor; |
9813 | 0 | m_aTilings.back().m_aTransform.matrix[5] = double(aConvertRect.Top() % aConvertRect.GetHeight()) / fDivisor; |
9814 | 0 | m_aTilings.back().m_aResources.m_aXObjects[aImageName] = rEmit.m_nObject; |
9815 | |
|
9816 | 0 | updateGraphicsState(); |
9817 | |
|
9818 | 0 | OStringBuffer aObjName( 16 ); |
9819 | 0 | aObjName.append( 'P' ); |
9820 | 0 | aObjName.append( m_aTilings.back().m_nObject ); |
9821 | 0 | OString aPatternName( aObjName.makeStringAndClear() ); |
9822 | 0 | pushResource( ResourceKind::Pattern, aPatternName, m_aTilings.back().m_nObject ); |
9823 | | |
9824 | | // fill a rRect with the pattern |
9825 | 0 | OStringBuffer aLine( 100 ); |
9826 | 0 | aLine.append( "q /Pattern cs /" ); |
9827 | 0 | aLine.append( aPatternName ); |
9828 | 0 | aLine.append( " scn " ); |
9829 | 0 | m_aPages.back().appendRect( rRect, aLine ); |
9830 | 0 | aLine.append( " f Q\n" ); |
9831 | 0 | writeBuffer( aLine ); |
9832 | 0 | } |
9833 | 0 | } |
9834 | 0 | else |
9835 | 0 | { |
9836 | 0 | aBmpPos = aRect.TopLeft(); |
9837 | 0 | aBmpSize = aRect.GetSize(); |
9838 | 0 | bDrawBitmap = true; |
9839 | 0 | } |
9840 | | |
9841 | 0 | if( aBitmap.HasAlpha() ) |
9842 | 0 | { |
9843 | 0 | if( rWall.IsGradient() ) |
9844 | 0 | bDrawGradient = true; |
9845 | 0 | else |
9846 | 0 | bDrawColor = true; |
9847 | 0 | } |
9848 | 0 | } |
9849 | 0 | else if( rWall.IsGradient() ) |
9850 | 0 | bDrawGradient = true; |
9851 | 0 | else |
9852 | 0 | bDrawColor = true; |
9853 | | |
9854 | 0 | if( bDrawGradient ) |
9855 | 0 | { |
9856 | 0 | drawGradient( rRect, rWall.GetGradient() ); |
9857 | 0 | } |
9858 | 0 | if( bDrawColor ) |
9859 | 0 | { |
9860 | 0 | Color aOldLineColor = m_aGraphicsStack.front().m_aLineColor; |
9861 | 0 | Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor; |
9862 | 0 | setLineColor( COL_TRANSPARENT ); |
9863 | 0 | setFillColor( rWall.GetColor() ); |
9864 | 0 | drawRectangle( rRect ); |
9865 | 0 | setLineColor( aOldLineColor ); |
9866 | 0 | setFillColor( aOldFillColor ); |
9867 | 0 | } |
9868 | 0 | if( bDrawBitmap ) |
9869 | 0 | { |
9870 | | // set temporary clip region since aBmpPos and aBmpSize |
9871 | | // may be outside rRect |
9872 | 0 | OStringBuffer aLine( 20 ); |
9873 | 0 | aLine.append( "q " ); |
9874 | 0 | m_aPages.back().appendRect( rRect, aLine ); |
9875 | 0 | aLine.append( " W n\n" ); |
9876 | 0 | writeBuffer( aLine ); |
9877 | 0 | drawBitmap( aBmpPos, aBmpSize, aBitmap ); |
9878 | 0 | writeBuffer( "Q\n" ); |
9879 | 0 | } |
9880 | 0 | } |
9881 | | |
9882 | | void PDFWriterImpl::updateGraphicsState(Mode const mode) |
9883 | 857k | { |
9884 | 857k | OStringBuffer& rLine = updateGraphicsStateLine; |
9885 | 857k | rLine.setLength(0); |
9886 | 857k | GraphicsState& rNewState = m_aGraphicsStack.front(); |
9887 | | // first set clip region since it might invalidate everything else |
9888 | | |
9889 | 857k | if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::ClipRegion ) |
9890 | 254k | { |
9891 | 254k | rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::ClipRegion; |
9892 | | |
9893 | 254k | if( m_aCurrentPDFState.m_bClipRegion != rNewState.m_bClipRegion || |
9894 | 105k | ( rNewState.m_bClipRegion && m_aCurrentPDFState.m_aClipRegion != rNewState.m_aClipRegion ) ) |
9895 | 220k | { |
9896 | 220k | if( m_aCurrentPDFState.m_bClipRegion ) |
9897 | 145k | { |
9898 | 145k | rLine.append( "Q " ); |
9899 | | // invalidate everything but the clip region |
9900 | 145k | m_aCurrentPDFState = GraphicsState(); |
9901 | 145k | rNewState.m_nUpdateFlags = ~GraphicsStateUpdateFlags::ClipRegion; |
9902 | 145k | } |
9903 | 220k | if( rNewState.m_bClipRegion ) |
9904 | 145k | { |
9905 | | // clip region is always stored in private PDF mapmode |
9906 | 145k | MapMode aNewMapMode = std::move(rNewState.m_aMapMode); |
9907 | 145k | rNewState.m_aMapMode = m_aMapMode; |
9908 | 145k | SetMapMode( rNewState.m_aMapMode ); |
9909 | 145k | m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode; |
9910 | | |
9911 | 145k | rLine.append("q "); |
9912 | 145k | if ( rNewState.m_aClipRegion.count() ) |
9913 | 145k | { |
9914 | 145k | m_aPages.back().appendPolyPolygon( rNewState.m_aClipRegion, rLine ); |
9915 | 145k | } |
9916 | 0 | else |
9917 | 0 | { |
9918 | | // tdf#130150 Need to revert tdf#99680, that breaks the |
9919 | | // rule that an set but empty clip-region clips everything |
9920 | | // aka draws nothing -> nothing is in an empty clip-region |
9921 | 0 | rLine.append( "0 0 m h " ); // NULL clip, i.e. nothing visible |
9922 | 0 | } |
9923 | 145k | rLine.append( "W* n\n" ); |
9924 | | |
9925 | 145k | rNewState.m_aMapMode = std::move(aNewMapMode); |
9926 | 145k | SetMapMode( rNewState.m_aMapMode ); |
9927 | 145k | m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode; |
9928 | 145k | } |
9929 | 220k | } |
9930 | 254k | } |
9931 | | |
9932 | 857k | if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::MapMode ) |
9933 | 189k | { |
9934 | 189k | rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::MapMode; |
9935 | 189k | SetMapMode( rNewState.m_aMapMode ); |
9936 | 189k | } |
9937 | | |
9938 | 857k | if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::Font ) |
9939 | 722k | { |
9940 | 722k | rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::Font; |
9941 | 722k | SetFont( rNewState.m_aFont ); |
9942 | 722k | } |
9943 | | |
9944 | 857k | if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LayoutMode ) |
9945 | 336k | { |
9946 | 336k | rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LayoutMode; |
9947 | 336k | SetLayoutMode( rNewState.m_nLayoutMode ); |
9948 | 336k | } |
9949 | | |
9950 | 857k | if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::DigitLanguage ) |
9951 | 761k | { |
9952 | 761k | rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::DigitLanguage; |
9953 | 761k | SetDigitLanguage( rNewState.m_aDigitLanguage ); |
9954 | 761k | } |
9955 | | |
9956 | 857k | if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LineColor ) |
9957 | 255k | { |
9958 | 255k | rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LineColor; |
9959 | 255k | if( m_aCurrentPDFState.m_aLineColor != rNewState.m_aLineColor && |
9960 | 4.59k | rNewState.m_aLineColor != COL_TRANSPARENT ) |
9961 | 4.18k | { |
9962 | 4.18k | appendStrokingColor( rNewState.m_aLineColor, rLine ); |
9963 | 4.18k | rLine.append( "\n" ); |
9964 | 4.18k | } |
9965 | 255k | } |
9966 | | |
9967 | 857k | if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::FillColor ) |
9968 | 254k | { |
9969 | 254k | rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::FillColor; |
9970 | 254k | if( m_aCurrentPDFState.m_aFillColor != rNewState.m_aFillColor && |
9971 | 4.24k | rNewState.m_aFillColor != COL_TRANSPARENT ) |
9972 | 3.38k | { |
9973 | 3.38k | appendNonStrokingColor( rNewState.m_aFillColor, rLine ); |
9974 | 3.38k | rLine.append( "\n" ); |
9975 | 3.38k | } |
9976 | 254k | } |
9977 | | |
9978 | 857k | if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::TransparentPercent ) |
9979 | 189k | { |
9980 | 189k | rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::TransparentPercent; |
9981 | 189k | if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 ) |
9982 | 189k | { |
9983 | | // TODO: switch extended graphicsstate |
9984 | 189k | } |
9985 | 189k | } |
9986 | | |
9987 | | // everything is up to date now |
9988 | 857k | m_aCurrentPDFState = m_aGraphicsStack.front(); |
9989 | 857k | if ((mode != Mode::NOWRITE) && !rLine.isEmpty()) |
9990 | 222k | writeBuffer( rLine ); |
9991 | 857k | } |
9992 | | |
9993 | | /* #i47544# imitate OutputDevice behaviour: |
9994 | | * if a font with a nontransparent color is set, it overwrites the current |
9995 | | * text color. OTOH setting the text color will overwrite the color of the font. |
9996 | | */ |
9997 | | void PDFWriterImpl::setFont( const vcl::Font& rFont ) |
9998 | 2.01M | { |
9999 | 2.01M | Color aColor = rFont.GetColor(); |
10000 | 2.01M | if( aColor == COL_TRANSPARENT ) |
10001 | 1.30M | aColor = m_aGraphicsStack.front().m_aFont.GetColor(); |
10002 | 2.01M | m_aGraphicsStack.front().m_aFont = rFont; |
10003 | 2.01M | m_aGraphicsStack.front().m_aFont.SetColor( aColor ); |
10004 | 2.01M | m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font; |
10005 | 2.01M | } |
10006 | | |
10007 | | void PDFWriterImpl::push( PushFlags nFlags ) |
10008 | 730k | { |
10009 | 730k | OSL_ENSURE( !m_aGraphicsStack.empty(), "invalid graphics stack" ); |
10010 | 730k | m_aGraphicsStack.push_front( m_aGraphicsStack.front() ); |
10011 | 730k | m_aGraphicsStack.front().m_nFlags = nFlags; |
10012 | 730k | } |
10013 | | |
10014 | | void PDFWriterImpl::pop() |
10015 | 730k | { |
10016 | 730k | OSL_ENSURE( m_aGraphicsStack.size() > 1, "pop without push" ); |
10017 | 730k | if( m_aGraphicsStack.size() < 2 ) |
10018 | 0 | return; |
10019 | | |
10020 | 730k | GraphicsState aState = m_aGraphicsStack.front(); |
10021 | 730k | m_aGraphicsStack.pop_front(); |
10022 | 730k | GraphicsState& rOld = m_aGraphicsStack.front(); |
10023 | | |
10024 | | // move those parameters back that were not pushed |
10025 | | // in the first place |
10026 | 730k | if( ! (aState.m_nFlags & PushFlags::LINECOLOR) ) |
10027 | 259k | setLineColor( aState.m_aLineColor ); |
10028 | 730k | if( ! (aState.m_nFlags & PushFlags::FILLCOLOR) ) |
10029 | 417k | setFillColor( aState.m_aFillColor ); |
10030 | 730k | if( ! (aState.m_nFlags & PushFlags::FONT) ) |
10031 | 586k | setFont( aState.m_aFont ); |
10032 | 730k | if( ! (aState.m_nFlags & PushFlags::TEXTCOLOR) ) |
10033 | 586k | setTextColor( aState.m_aFont.GetColor() ); |
10034 | 730k | if( ! (aState.m_nFlags & PushFlags::MAPMODE) ) |
10035 | 561k | setMapMode( aState.m_aMapMode ); |
10036 | 730k | if( ! (aState.m_nFlags & PushFlags::CLIPREGION) ) |
10037 | 351k | { |
10038 | | // do not use setClipRegion here |
10039 | | // it would convert again assuming the current mapmode |
10040 | 351k | rOld.m_aClipRegion = aState.m_aClipRegion; |
10041 | 351k | rOld.m_bClipRegion = aState.m_bClipRegion; |
10042 | 351k | } |
10043 | 730k | if( ! (aState.m_nFlags & PushFlags::TEXTLINECOLOR ) ) |
10044 | 586k | setTextLineColor( aState.m_aTextLineColor ); |
10045 | 730k | if( ! (aState.m_nFlags & PushFlags::OVERLINECOLOR ) ) |
10046 | 586k | setOverlineColor( aState.m_aOverlineColor ); |
10047 | 730k | if( ! (aState.m_nFlags & PushFlags::TEXTALIGN ) ) |
10048 | 586k | setTextAlign( aState.m_aFont.GetAlignment() ); |
10049 | 730k | if( ! (aState.m_nFlags & PushFlags::TEXTFILLCOLOR) ) |
10050 | 586k | setTextFillColor( aState.m_aFont.GetFillColor() ); |
10051 | 730k | if (!(aState.m_nFlags & PushFlags::TEXTLAYOUTMODE)) |
10052 | 586k | { |
10053 | 586k | setLayoutMode(aState.m_nLayoutMode); |
10054 | 586k | } |
10055 | 730k | if( ! (aState.m_nFlags & PushFlags::REFPOINT) ) |
10056 | 586k | { |
10057 | | // what ? |
10058 | 586k | } |
10059 | | // invalidate graphics state |
10060 | 730k | m_aGraphicsStack.front().m_nUpdateFlags = GraphicsStateUpdateFlags::All; |
10061 | 730k | } |
10062 | | |
10063 | | void PDFWriterImpl::setMapMode( const MapMode& rMapMode ) |
10064 | 960k | { |
10065 | 960k | m_aGraphicsStack.front().m_aMapMode = rMapMode; |
10066 | 960k | SetMapMode( rMapMode ); |
10067 | 960k | m_aCurrentPDFState.m_aMapMode = rMapMode; |
10068 | 960k | } |
10069 | | |
10070 | | void PDFWriterImpl::setClipRegion( const basegfx::B2DPolyPolygon& rRegion ) |
10071 | 149k | { |
10072 | | // tdf#130150 improve coordinate manipulations to double precision transformations |
10073 | 149k | const basegfx::B2DHomMatrix aCurrentTransform( |
10074 | 149k | GetInverseViewTransformation(m_aMapMode) * GetViewTransformation(m_aGraphicsStack.front().m_aMapMode)); |
10075 | 149k | basegfx::B2DPolyPolygon aRegion(rRegion); |
10076 | | |
10077 | 149k | aRegion.transform(aCurrentTransform); |
10078 | 149k | m_aGraphicsStack.front().m_aClipRegion = std::move(aRegion); |
10079 | 149k | m_aGraphicsStack.front().m_bClipRegion = true; |
10080 | 149k | m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion; |
10081 | 149k | } |
10082 | | |
10083 | | void PDFWriterImpl::moveClipRegion( sal_Int32 nX, sal_Int32 nY ) |
10084 | 0 | { |
10085 | 0 | if( !(m_aGraphicsStack.front().m_bClipRegion && m_aGraphicsStack.front().m_aClipRegion.count()) ) |
10086 | 0 | return; |
10087 | | |
10088 | | // tdf#130150 improve coordinate manipulations to double precision transformations |
10089 | 0 | basegfx::B2DHomMatrix aConvertA; |
10090 | |
|
10091 | 0 | if(MapUnit::MapPixel == m_aGraphicsStack.front().m_aMapMode.GetMapUnit()) |
10092 | 0 | { |
10093 | 0 | aConvertA = GetInverseViewTransformation(m_aMapMode); |
10094 | 0 | } |
10095 | 0 | else |
10096 | 0 | { |
10097 | 0 | aConvertA = LogicToLogic(m_aGraphicsStack.front().m_aMapMode, m_aMapMode); |
10098 | 0 | } |
10099 | |
|
10100 | 0 | basegfx::B2DPoint aB2DPointA(nX, nY); |
10101 | 0 | basegfx::B2DPoint aB2DPointB(0.0, 0.0); |
10102 | 0 | aB2DPointA *= aConvertA; |
10103 | 0 | aB2DPointB *= aConvertA; |
10104 | 0 | aB2DPointA -= aB2DPointB; |
10105 | 0 | basegfx::B2DHomMatrix aMat; |
10106 | |
|
10107 | 0 | aMat.translate(aB2DPointA.getX(), aB2DPointA.getY()); |
10108 | 0 | m_aGraphicsStack.front().m_aClipRegion.transform( aMat ); |
10109 | 0 | m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion; |
10110 | 0 | } |
10111 | | |
10112 | | void PDFWriterImpl::intersectClipRegion( const tools::Rectangle& rRect ) |
10113 | 215 | { |
10114 | 215 | basegfx::B2DPolyPolygon aRect( basegfx::utils::createPolygonFromRect( |
10115 | 215 | vcl::unotools::b2DRectangleFromRectangle(rRect) ) ); |
10116 | 215 | intersectClipRegion( aRect ); |
10117 | 215 | } |
10118 | | |
10119 | | void PDFWriterImpl::intersectClipRegion( const basegfx::B2DPolyPolygon& rRegion ) |
10120 | 231k | { |
10121 | | // tdf#130150 improve coordinate manipulations to double precision transformations |
10122 | 231k | const basegfx::B2DHomMatrix aCurrentTransform( |
10123 | 231k | GetInverseViewTransformation(m_aMapMode) * GetViewTransformation(m_aGraphicsStack.front().m_aMapMode)); |
10124 | 231k | basegfx::B2DPolyPolygon aRegion(rRegion); |
10125 | | |
10126 | 231k | aRegion.transform(aCurrentTransform); |
10127 | 231k | m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion; |
10128 | | |
10129 | 231k | if( m_aGraphicsStack.front().m_bClipRegion ) |
10130 | 231k | { |
10131 | 231k | basegfx::B2DPolyPolygon aOld( basegfx::utils::prepareForPolygonOperation( m_aGraphicsStack.front().m_aClipRegion ) ); |
10132 | 231k | aRegion = basegfx::utils::prepareForPolygonOperation( aRegion ); |
10133 | 231k | m_aGraphicsStack.front().m_aClipRegion = basegfx::utils::solvePolygonOperationAnd( aOld, aRegion ); |
10134 | 231k | } |
10135 | 215 | else |
10136 | 215 | { |
10137 | 215 | m_aGraphicsStack.front().m_aClipRegion = std::move(aRegion); |
10138 | 215 | m_aGraphicsStack.front().m_bClipRegion = true; |
10139 | 215 | } |
10140 | 231k | } |
10141 | | |
10142 | | sal_Int32 PDFWriterImpl::createNote(const tools::Rectangle& rRect, |
10143 | | const tools::Rectangle& rPopupRect, const pdf::PDFNote& rNote, |
10144 | | sal_Int32 nPageNr) |
10145 | 388 | { |
10146 | 388 | if (nPageNr < 0) |
10147 | 0 | nPageNr = m_nCurrentPage; |
10148 | | |
10149 | 388 | if (nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size()) |
10150 | 0 | return -1; |
10151 | | |
10152 | 388 | sal_Int32 nRet = m_aNotes.size(); |
10153 | | |
10154 | 388 | m_aNotes.emplace_back(); |
10155 | 388 | auto & rNoteEntry = m_aNotes.back(); |
10156 | 388 | rNoteEntry.m_nObject = createObject(); |
10157 | 388 | rNoteEntry.m_aPopUpAnnotation.m_nObject = createObject(); |
10158 | 388 | rNoteEntry.m_aPopUpAnnotation.m_nParentObject = rNoteEntry.m_nObject; |
10159 | 388 | rNoteEntry.m_aPopUpAnnotation.m_aRect = rPopupRect; |
10160 | 388 | rNoteEntry.m_aContents = rNote; |
10161 | 388 | rNoteEntry.m_aRect = rRect; |
10162 | 388 | rNoteEntry.m_nPage = nPageNr; |
10163 | | // convert to default user space now, since the mapmode may change |
10164 | 388 | m_aPages[nPageNr].convertRect(rNoteEntry.m_aRect); |
10165 | 388 | m_aPages[nPageNr].convertRect(rNoteEntry.m_aPopUpAnnotation.m_aRect); |
10166 | | |
10167 | | // insert note to page's annotation list |
10168 | 388 | m_aPages[nPageNr].m_aAnnotations.push_back(rNoteEntry.m_nObject); |
10169 | 388 | m_aPages[nPageNr].m_aAnnotations.push_back(rNoteEntry.m_aPopUpAnnotation.m_nObject); |
10170 | | |
10171 | 388 | return nRet; |
10172 | 388 | } |
10173 | | |
10174 | | sal_Int32 PDFWriterImpl::createLink(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText) |
10175 | 3.34k | { |
10176 | 3.34k | if( nPageNr < 0 ) |
10177 | 0 | nPageNr = m_nCurrentPage; |
10178 | | |
10179 | 3.34k | if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() ) |
10180 | 0 | return -1; |
10181 | | |
10182 | 3.34k | sal_Int32 nRet = m_aLinks.size(); |
10183 | | |
10184 | 3.34k | m_aLinks.emplace_back(rAltText); |
10185 | 3.34k | m_aLinks.back().m_nObject = createObject(); |
10186 | 3.34k | m_aLinks.back().m_nPage = nPageNr; |
10187 | 3.34k | m_aLinks.back().m_aRect = rRect; |
10188 | | // convert to default user space now, since the mapmode may change |
10189 | 3.34k | m_aPages[nPageNr].convertRect( m_aLinks.back().m_aRect ); |
10190 | | |
10191 | | // insert link to page's annotation list |
10192 | 3.34k | m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aLinks.back().m_nObject ); |
10193 | | |
10194 | 3.34k | return nRet; |
10195 | 3.34k | } |
10196 | | |
10197 | | sal_Int32 PDFWriterImpl::createScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText, OUString const& rMimeType) |
10198 | 0 | { |
10199 | 0 | if (nPageNr < 0) |
10200 | 0 | nPageNr = m_nCurrentPage; |
10201 | |
|
10202 | 0 | if (nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size()) |
10203 | 0 | return -1; |
10204 | | |
10205 | 0 | sal_Int32 nRet = m_aScreens.size(); |
10206 | |
|
10207 | 0 | m_aScreens.emplace_back(rAltText, rMimeType); |
10208 | 0 | m_aScreens.back().m_nObject = createObject(); |
10209 | 0 | m_aScreens.back().m_nPage = nPageNr; |
10210 | 0 | m_aScreens.back().m_aRect = rRect; |
10211 | | // Convert to default user space now, since the mapmode may change. |
10212 | 0 | m_aPages[nPageNr].convertRect(m_aScreens.back().m_aRect); |
10213 | | |
10214 | | // Insert link to page's annotation list. |
10215 | 0 | m_aPages[nPageNr].m_aAnnotations.push_back(m_aScreens.back().m_nObject); |
10216 | |
|
10217 | 0 | return nRet; |
10218 | 0 | } |
10219 | | |
10220 | | sal_Int32 PDFWriterImpl::createNamedDest( const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType ) |
10221 | 0 | { |
10222 | 0 | if( nPageNr < 0 ) |
10223 | 0 | nPageNr = m_nCurrentPage; |
10224 | |
|
10225 | 0 | if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() ) |
10226 | 0 | return -1; |
10227 | | |
10228 | 0 | sal_Int32 nRet = m_aNamedDests.size(); |
10229 | |
|
10230 | 0 | m_aNamedDests.emplace_back( ); |
10231 | 0 | m_aNamedDests.back().m_aDestName = sDestName; |
10232 | 0 | m_aNamedDests.back().m_nPage = nPageNr; |
10233 | 0 | m_aNamedDests.back().m_eType = eType; |
10234 | 0 | m_aNamedDests.back().m_aRect = rRect; |
10235 | | // convert to default user space now, since the mapmode may change |
10236 | 0 | m_aPages[nPageNr].convertRect( m_aNamedDests.back().m_aRect ); |
10237 | |
|
10238 | 0 | return nRet; |
10239 | 0 | } |
10240 | | |
10241 | | sal_Int32 PDFWriterImpl::createDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType ) |
10242 | 3.54k | { |
10243 | 3.54k | if( nPageNr < 0 ) |
10244 | 0 | nPageNr = m_nCurrentPage; |
10245 | | |
10246 | 3.54k | if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() ) |
10247 | 0 | return -1; |
10248 | | |
10249 | 3.54k | sal_Int32 nRet = m_aDests.size(); |
10250 | | |
10251 | 3.54k | m_aDests.emplace_back( ); |
10252 | 3.54k | m_aDests.back().m_nPage = nPageNr; |
10253 | 3.54k | m_aDests.back().m_eType = eType; |
10254 | 3.54k | m_aDests.back().m_aRect = rRect; |
10255 | | // convert to default user space now, since the mapmode may change |
10256 | 3.54k | m_aPages[nPageNr].convertRect( m_aDests.back().m_aRect ); |
10257 | | |
10258 | 3.54k | return nRet; |
10259 | 3.54k | } |
10260 | | |
10261 | | sal_Int32 PDFWriterImpl::registerDestReference( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType ) |
10262 | 0 | { |
10263 | 0 | m_aDestinationIdTranslation[ nDestId ] = createDest( rRect, nPageNr, eType ); |
10264 | 0 | return m_aDestinationIdTranslation[ nDestId ]; |
10265 | 0 | } |
10266 | | |
10267 | | void PDFWriterImpl::setDestStructureElement(sal_Int32 nDestId, sal_Int32 nStructElementId) |
10268 | 0 | { |
10269 | 0 | if (nDestId < 0 || o3tl::make_unsigned(nDestId) >= m_aDests.size()) |
10270 | 0 | return; |
10271 | | |
10272 | 0 | m_aDests[nDestId].m_nStructElement = nStructElementId; |
10273 | 0 | } |
10274 | | |
10275 | | void PDFWriterImpl::addStructureRef(sal_Int32 nElementId, sal_Int32 nRefElementId) |
10276 | 0 | { |
10277 | 0 | if (nElementId < 0 || o3tl::make_unsigned(nElementId) >= m_aStructure.size()) |
10278 | 0 | return; |
10279 | 0 | if (nRefElementId < 0 || o3tl::make_unsigned(nRefElementId) >= m_aStructure.size()) |
10280 | 0 | return; |
10281 | | // an element left out of the tree has no object to point at |
10282 | 0 | if (m_aStructure[nRefElementId].m_nObject <= 0) |
10283 | 0 | return; |
10284 | | // ISO 32000-2 added Ref to a structure element, and PDF 1.7 has no entry of that name |
10285 | 0 | if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0) |
10286 | 0 | return; |
10287 | | |
10288 | 0 | m_aStructure[nElementId].m_RefElements.push_back(nRefElementId); |
10289 | 0 | } |
10290 | | |
10291 | | void PDFWriterImpl::setLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId ) |
10292 | 2.82k | { |
10293 | 2.82k | if( nLinkId < 0 || o3tl::make_unsigned(nLinkId) >= m_aLinks.size() ) |
10294 | 0 | return; |
10295 | 2.82k | if( nDestId < 0 || o3tl::make_unsigned(nDestId) >= m_aDests.size() ) |
10296 | 0 | return; |
10297 | | |
10298 | 2.82k | m_aLinks[ nLinkId ].m_nDest = nDestId; |
10299 | 2.82k | } |
10300 | | |
10301 | | void PDFWriterImpl::setLinkURL( sal_Int32 nLinkId, const OUString& rURL ) |
10302 | 524 | { |
10303 | 524 | if( nLinkId < 0 || o3tl::make_unsigned(nLinkId) >= m_aLinks.size() ) |
10304 | 0 | return; |
10305 | | |
10306 | 524 | m_aLinks[ nLinkId ].m_nDest = -1; |
10307 | | |
10308 | 524 | using namespace ::com::sun::star; |
10309 | | |
10310 | 524 | if (!m_xTrans.is()) |
10311 | 200 | { |
10312 | 200 | const uno::Reference< uno::XComponentContext >& xContext( comphelper::getProcessComponentContext() ); |
10313 | 200 | m_xTrans = util::URLTransformer::create(xContext); |
10314 | 200 | } |
10315 | | |
10316 | 524 | util::URL aURL; |
10317 | 524 | aURL.Complete = rURL; |
10318 | | |
10319 | 524 | m_xTrans->parseStrict( aURL ); |
10320 | | |
10321 | 524 | m_aLinks[ nLinkId ].m_aURL = aURL.Complete; |
10322 | 524 | } |
10323 | | |
10324 | | void PDFWriterImpl::setScreenURL(sal_Int32 nScreenId, const OUString& rURL) |
10325 | 0 | { |
10326 | 0 | if (nScreenId < 0 || o3tl::make_unsigned(nScreenId) >= m_aScreens.size()) |
10327 | 0 | return; |
10328 | | |
10329 | 0 | m_aScreens[nScreenId].m_aURL = rURL; |
10330 | 0 | } |
10331 | | |
10332 | | void PDFWriterImpl::setScreenStream(sal_Int32 nScreenId, const OUString& rURL) |
10333 | 0 | { |
10334 | 0 | if (nScreenId < 0 || o3tl::make_unsigned(nScreenId) >= m_aScreens.size()) |
10335 | 0 | return; |
10336 | | |
10337 | 0 | m_aScreens[nScreenId].m_aTempFileURL = rURL; |
10338 | 0 | m_aScreens[nScreenId].m_nTempFileObject = createObject(); |
10339 | 0 | } |
10340 | | |
10341 | | void PDFWriterImpl::setLinkPropertyId( sal_Int32 nLinkId, sal_Int32 nPropertyId ) |
10342 | 3.73k | { |
10343 | 3.73k | m_aLinkPropertyMap[ nPropertyId ] = nLinkId; |
10344 | 3.73k | } |
10345 | | |
10346 | | sal_Int32 PDFWriterImpl::createOutlineItem( sal_Int32 nParent, std::u16string_view rText, sal_Int32 nDestID ) |
10347 | 723 | { |
10348 | | // create new item |
10349 | 723 | sal_Int32 nNewItem = m_aOutline.size(); |
10350 | 723 | m_aOutline.emplace_back( ); |
10351 | | |
10352 | | // set item attributes |
10353 | 723 | setOutlineItemParent( nNewItem, nParent ); |
10354 | 723 | setOutlineItemText( nNewItem, rText ); |
10355 | 723 | setOutlineItemDest( nNewItem, nDestID ); |
10356 | | |
10357 | 723 | return nNewItem; |
10358 | 723 | } |
10359 | | |
10360 | | void PDFWriterImpl::setOutlineItemParent( sal_Int32 nItem, sal_Int32 nNewParent ) |
10361 | 723 | { |
10362 | 723 | if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() ) |
10363 | 0 | return; |
10364 | | |
10365 | 723 | if( nNewParent < 0 || o3tl::make_unsigned(nNewParent) >= m_aOutline.size() || nNewParent == nItem ) |
10366 | 719 | { |
10367 | 719 | nNewParent = 0; |
10368 | 719 | } |
10369 | | // insert item to new parent's list of children |
10370 | 723 | m_aOutline[ nNewParent ].m_aChildren.push_back( nItem ); |
10371 | 723 | } |
10372 | | |
10373 | | void PDFWriterImpl::setOutlineItemText( sal_Int32 nItem, std::u16string_view rText ) |
10374 | 723 | { |
10375 | 723 | if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() ) |
10376 | 0 | return; |
10377 | | |
10378 | 723 | m_aOutline[ nItem ].m_aTitle = psp::WhitespaceToSpace( rText ); |
10379 | 723 | } |
10380 | | |
10381 | | void PDFWriterImpl::setOutlineItemDest( sal_Int32 nItem, sal_Int32 nDestID ) |
10382 | 723 | { |
10383 | 723 | if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() ) // item does not exist |
10384 | 0 | return; |
10385 | 723 | if( nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size() ) // dest does not exist |
10386 | 0 | return; |
10387 | 723 | m_aOutline[nItem].m_nDestID = nDestID; |
10388 | 723 | } |
10389 | | |
10390 | | const char* PDFWriterImpl::getStructureTag(StructElement eType) |
10391 | 0 | { |
10392 | | // First handle fallbacks for elements that were added in a certain PDF version |
10393 | | |
10394 | | // PDF 1.5 fallbacks |
10395 | 0 | if (m_aContext.Version < PDFWriter::PDFVersion::PDF_1_5 && eType == StructElement::Annot) |
10396 | 0 | eType = StructElement::Figure; |
10397 | | |
10398 | | // PDF 2.0 fallbacks |
10399 | 0 | if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0) |
10400 | 0 | { |
10401 | 0 | switch (eType) |
10402 | 0 | { |
10403 | 0 | case StructElement::Title: |
10404 | 0 | eType = StructElement::Paragraph; break; |
10405 | 0 | case StructElement::Emphasis: |
10406 | 0 | eType = StructElement::Span; break; |
10407 | 0 | case StructElement::Strong: |
10408 | 0 | eType = StructElement::Span; break; |
10409 | 0 | case StructElement::FENote: |
10410 | 0 | eType = StructElement::Note; break; |
10411 | 0 | default: |
10412 | 0 | break; |
10413 | 0 | } |
10414 | 0 | } |
10415 | | |
10416 | 0 | auto iterator = constTagStrings.find(eType); |
10417 | |
|
10418 | 0 | if (iterator == constTagStrings.end()) |
10419 | 0 | return "Div"; |
10420 | | |
10421 | 0 | return iterator->second; |
10422 | 0 | } |
10423 | | |
10424 | | bool PDFWriterImpl::isStandardStructureName(std::string_view aName) |
10425 | 0 | { |
10426 | | // below 2.0 getStructureTag returns P for Title, so a style may take that name |
10427 | 0 | const auto it(std::ranges::find_if(constTagStrings, |
10428 | 0 | [aName](const auto& rTag) { return aName == rTag.second; })); |
10429 | 0 | if ((it != constTagStrings.end() && aName == getStructureTag(it->first)) |
10430 | 0 | || std::ranges::find(constUnusedTagStrings, aName) != std::end(constUnusedTagStrings)) |
10431 | 0 | return true; |
10432 | | |
10433 | 0 | if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0) |
10434 | 0 | return false; |
10435 | | |
10436 | 0 | return isDeepHeading(aName) |
10437 | 0 | || std::ranges::find(constUnusedTagStrings20, aName) |
10438 | 0 | != std::end(constUnusedTagStrings20); |
10439 | 0 | } |
10440 | | |
10441 | | OString PDFWriterImpl::claimRoleName(const OString& rAlias, StructElement eType) |
10442 | 0 | { |
10443 | 0 | const OString aTag(getStructureTag(eType)); |
10444 | | // For PDF/UA it's not allowed to map an alias with the same name. |
10445 | | // Not aware of a reason for doing it in any case, so just don't do it. |
10446 | 0 | if (rAlias == aTag) |
10447 | 0 | return rAlias; |
10448 | | |
10449 | | // a heading deeper than the six this writer emits: PDF 2.0 knows the name, so the element |
10450 | | // says the level it is and needs no entry |
10451 | 0 | if (isDeepHeading(rAlias) && StructElement::H1 <= eType && eType <= StructElement::H6 |
10452 | 0 | && PDFWriter::PDFVersion::PDF_2_0 <= m_aContext.Version) |
10453 | 0 | return rAlias; |
10454 | | |
10455 | 0 | OString aName(rAlias); |
10456 | 0 | for (sal_Int32 nIndex(1);; ++nIndex) |
10457 | 0 | { |
10458 | | // a standard type's name cannot be made to mean another type |
10459 | 0 | if (!isStandardStructureName(aName)) |
10460 | 0 | { |
10461 | | // any other name belongs to the alias that asked for it first |
10462 | 0 | const auto[it, bInserted]( |
10463 | 0 | m_aRoleMap.emplace(aName, RoleMapEntry{ .m_aTag = aTag, .m_aAsked = rAlias })); |
10464 | 0 | if (bInserted || (it->second.m_aTag == aTag && it->second.m_aAsked == rAlias)) |
10465 | 0 | return aName; |
10466 | 0 | } |
10467 | | |
10468 | | // name taken, try the next index |
10469 | 0 | aName = rAlias + "-" + OString::number(nIndex); |
10470 | 0 | } |
10471 | 0 | } |
10472 | | |
10473 | | void PDFWriterImpl::beginStructureElementMCSeq() |
10474 | 860k | { |
10475 | 860k | assert(m_nCurrentStructElement == 0 || m_aStructure[m_nCurrentStructElement].m_oType); |
10476 | 860k | if( m_bEmitStructure && |
10477 | 860k | m_nCurrentStructElement > 0 && // StructTreeRoot |
10478 | | // Document = SwPageFrame => this is not *inside* the page content |
10479 | | // stream so do not emit MCID! |
10480 | 0 | m_aStructure[m_nCurrentStructElement].m_oType && |
10481 | 0 | *m_aStructure[m_nCurrentStructElement].m_oType != StructElement::Document && |
10482 | 0 | ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence |
10483 | 860k | ) |
10484 | 0 | { |
10485 | 0 | PDFStructureElement& rEle = m_aStructure[ m_nCurrentStructElement ]; |
10486 | 0 | OStringBuffer aLine( 128 ); |
10487 | 0 | sal_Int32 nMCID = m_aPages[ m_nCurrentPage ].m_aMCIDParents.size(); |
10488 | 0 | aLine.append( "/" ); |
10489 | 0 | if( !rEle.m_aAlias.isEmpty() ) |
10490 | 0 | aLine.append( rEle.m_aAlias ); |
10491 | 0 | else |
10492 | 0 | aLine.append( getStructureTag(*rEle.m_oType) ); |
10493 | 0 | aLine.append( "<</MCID " ); |
10494 | 0 | aLine.append( nMCID ); |
10495 | 0 | aLine.append( ">>BDC\n" ); |
10496 | 0 | writeBuffer( aLine ); |
10497 | | |
10498 | | // update the element's content list |
10499 | 0 | SAL_INFO("vcl.pdfwriter", "beginning marked content id " << nMCID << " on page object " |
10500 | 0 | << m_aPages[ m_nCurrentPage ].m_nPageObject << ", structure first page = " |
10501 | 0 | << rEle.m_nFirstPageObject); |
10502 | 0 | rEle.m_aKids.emplace_back(MCIDReference{m_aPages[m_nCurrentPage].m_nPageObject, nMCID}); |
10503 | | // update the page's mcid parent list |
10504 | 0 | m_aPages[ m_nCurrentPage ].m_aMCIDParents.push_back( rEle.m_nObject ); |
10505 | | // mark element MC sequence as open |
10506 | 0 | rEle.m_bOpenMCSeq = true; |
10507 | 0 | } |
10508 | | // handle artifacts |
10509 | 860k | else if( ! m_bEmitStructure && m_aContext.Tagged && |
10510 | 0 | m_nCurrentStructElement > 0 && |
10511 | 0 | m_aStructure[m_nCurrentStructElement].m_oType && |
10512 | 0 | *m_aStructure[m_nCurrentStructElement].m_oType == StructElement::NonStructElement && |
10513 | 0 | ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence |
10514 | 860k | ) |
10515 | 0 | { |
10516 | 0 | OString aLine = "/Artifact "_ostr; |
10517 | 0 | writeBuffer( aLine ); |
10518 | | // emit property list if requested |
10519 | 0 | OStringBuffer buf; |
10520 | 0 | for (auto const& rAttr : m_aStructure[m_nCurrentStructElement].m_aAttributes) |
10521 | 0 | { |
10522 | 0 | appendStructureAttributeLine(rAttr.first, rAttr.second, buf, false); |
10523 | 0 | } |
10524 | 0 | if (buf.isEmpty()) |
10525 | 0 | { |
10526 | 0 | writeBuffer("BMC\n"); |
10527 | 0 | } |
10528 | 0 | else |
10529 | 0 | { |
10530 | 0 | writeBuffer("<<"); |
10531 | 0 | writeBuffer(buf); |
10532 | 0 | writeBuffer(">> BDC\n"); |
10533 | 0 | } |
10534 | | // mark element MC sequence as open |
10535 | 0 | m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = true; |
10536 | 0 | } |
10537 | 860k | } |
10538 | | |
10539 | | void PDFWriterImpl::endStructureElementMCSeq(EndMode const endMode) |
10540 | 73.7k | { |
10541 | 73.7k | if (m_nCurrentStructElement > 0 // not StructTreeRoot |
10542 | 0 | && m_aStructure[m_nCurrentStructElement].m_oType |
10543 | 0 | && (m_bEmitStructure |
10544 | 0 | || (endMode != EndMode::OnlyStruct |
10545 | 0 | && m_aStructure[m_nCurrentStructElement].m_oType == StructElement::NonStructElement)) |
10546 | 0 | && m_aStructure[m_nCurrentStructElement].m_bOpenMCSeq) |
10547 | 0 | { |
10548 | 0 | writeBuffer( "EMC\n" ); |
10549 | 0 | m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = false; |
10550 | 0 | } |
10551 | 73.7k | } |
10552 | | |
10553 | | bool PDFWriterImpl::checkEmitStructure() |
10554 | 0 | { |
10555 | 0 | bool bEmit = false; |
10556 | 0 | if( m_aContext.Tagged ) |
10557 | 0 | { |
10558 | 0 | bEmit = true; |
10559 | 0 | sal_Int32 nEle = m_nCurrentStructElement; |
10560 | 0 | while( nEle > 0 && o3tl::make_unsigned(nEle) < m_aStructure.size() ) |
10561 | 0 | { |
10562 | 0 | if (m_aStructure[nEle].m_oType |
10563 | 0 | && *m_aStructure[nEle].m_oType == StructElement::NonStructElement) |
10564 | 0 | { |
10565 | 0 | bEmit = false; |
10566 | 0 | break; |
10567 | 0 | } |
10568 | 0 | nEle = m_aStructure[ nEle ].m_nParentElement; |
10569 | 0 | } |
10570 | 0 | } |
10571 | 0 | return bEmit; |
10572 | 0 | } |
10573 | | |
10574 | | sal_Int32 PDFWriterImpl::ensureStructureElement() |
10575 | 25 | { |
10576 | 25 | if( ! m_aContext.Tagged ) |
10577 | 25 | return -1; |
10578 | | |
10579 | 0 | sal_Int32 nNewId = sal_Int32(m_aStructure.size()); |
10580 | | |
10581 | | // use m_nCurrentStructElement as temporary parent |
10582 | 0 | m_aStructure.emplace_back(nNewId, m_nCurrentStructElement, m_aPages[m_nCurrentPage].m_nPageObject); |
10583 | |
|
10584 | 0 | m_aStructure[ m_nCurrentStructElement ].m_aChildren.push_back( nNewId ); |
10585 | 0 | return nNewId; |
10586 | 25 | } |
10587 | | |
10588 | | void PDFWriterImpl::initStructureElement(sal_Int32 const id, |
10589 | | StructElement const eType, std::u16string_view const rAlias) |
10590 | 25 | { |
10591 | 25 | if( ! m_aContext.Tagged ) |
10592 | 25 | return; |
10593 | | |
10594 | 0 | if( m_nCurrentStructElement == 0 && |
10595 | 0 | eType != StructElement::Document && eType != StructElement::NonStructElement ) |
10596 | 0 | { |
10597 | | // struct tree root hit, but not beginning document |
10598 | | // this might happen with setCurrentStructureElement |
10599 | | // silently insert structure into document again if one properly exists |
10600 | 0 | if( ! m_aStructure[ 0 ].m_aChildren.empty() ) |
10601 | 0 | { |
10602 | 0 | const std::vector< sal_Int32 >& rRootChildren = m_aStructure[0].m_aChildren; |
10603 | 0 | auto it = std::find_if(rRootChildren.begin(), rRootChildren.end(), |
10604 | 0 | [&](sal_Int32 nElement) { |
10605 | 0 | return m_aStructure[nElement].m_oType |
10606 | 0 | && *m_aStructure[nElement].m_oType == StructElement::Document; }); |
10607 | 0 | if( it != rRootChildren.end() ) |
10608 | 0 | { |
10609 | 0 | m_nCurrentStructElement = *it; |
10610 | 0 | SAL_WARN( "vcl.pdfwriter", "Structure element inserted to StructTreeRoot that is not a document" ); |
10611 | 0 | } |
10612 | 0 | else { |
10613 | 0 | OSL_FAIL( "document structure in disorder !" ); |
10614 | 0 | } |
10615 | 0 | } |
10616 | 0 | else { |
10617 | 0 | OSL_FAIL( "PDF document structure MUST be contained in a Document element" ); |
10618 | 0 | } |
10619 | 0 | } |
10620 | | |
10621 | 0 | PDFStructureElement& rEle = m_aStructure[id]; |
10622 | 0 | assert(!rEle.m_oType); |
10623 | 0 | rEle.m_oType.emplace(eType); |
10624 | | // remove it from its possibly placeholder parent; append to real parent |
10625 | 0 | auto const it(std::find(m_aStructure[rEle.m_nParentElement].m_aChildren.begin(), |
10626 | 0 | m_aStructure[rEle.m_nParentElement].m_aChildren.end(), id)); |
10627 | 0 | assert(it != m_aStructure[rEle.m_nParentElement].m_aChildren.end()); |
10628 | 0 | m_aStructure[rEle.m_nParentElement].m_aChildren.erase(it); |
10629 | 0 | rEle.m_nParentElement = m_nCurrentStructElement; |
10630 | 0 | rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject; |
10631 | 0 | m_aStructure[m_nCurrentStructElement].m_aChildren.push_back(id); |
10632 | | |
10633 | | // handle alias names |
10634 | 0 | if( !rAlias.empty() && eType != StructElement::NonStructElement ) |
10635 | 0 | { |
10636 | 0 | OStringBuffer aNameBuf( rAlias.size() ); |
10637 | 0 | COSWriter::appendName( rAlias, aNameBuf ); |
10638 | 0 | OString aAliasName( aNameBuf.makeStringAndClear() ); |
10639 | 0 | rEle.m_aAlias = claimRoleName(aAliasName, eType); |
10640 | 0 | } |
10641 | |
|
10642 | 0 | if (m_bEmitStructure && eType != StructElement::NonStructElement) // don't create nonexistent objects |
10643 | 0 | { |
10644 | 0 | rEle.m_nObject = createObject(); |
10645 | | // update parent's kids list |
10646 | 0 | m_aStructure[ rEle.m_nParentElement ].m_aKids.emplace_back(ObjReference{rEle.m_nObject}); |
10647 | | // ISO 14289-1:2014, Clause: 7.9, for the element it names Note |
10648 | 0 | if (*rEle.m_oType == StructElement::FENote) |
10649 | 0 | { |
10650 | 0 | m_StructElemObjsWithID.insert(rEle.m_nObject); |
10651 | 0 | } |
10652 | 0 | } |
10653 | 0 | } |
10654 | | |
10655 | | void PDFWriterImpl::beginStructureElement(sal_Int32 const id) |
10656 | 25 | { |
10657 | 25 | if( m_nCurrentPage < 0 ) |
10658 | 0 | return; |
10659 | | |
10660 | 25 | if( ! m_aContext.Tagged ) |
10661 | 25 | return; |
10662 | | |
10663 | 25 | assert(id != -1 && "cid#1538888 doesn't consider above m_aContext.Tagged"); |
10664 | | |
10665 | | // close eventual current MC sequence |
10666 | 0 | endStructureElementMCSeq(EndMode::OnlyStruct); |
10667 | |
|
10668 | 0 | PDFStructureElement& rEle = m_aStructure[id]; |
10669 | 0 | m_StructElementStack.push(m_nCurrentStructElement); |
10670 | 0 | m_nCurrentStructElement = id; |
10671 | |
|
10672 | 0 | if (g_bDebugDisableCompression) |
10673 | 0 | { |
10674 | 0 | OStringBuffer aLine( "beginStructureElement " ); |
10675 | 0 | aLine.append( m_nCurrentStructElement ); |
10676 | 0 | aLine.append( ": " ); |
10677 | 0 | aLine.append( rEle.m_oType |
10678 | 0 | ? getStructureTag(*rEle.m_oType) |
10679 | 0 | : "<placeholder>" ); |
10680 | 0 | if( !rEle.m_aAlias.isEmpty() ) |
10681 | 0 | { |
10682 | 0 | aLine.append( " aliased as \"" ); |
10683 | 0 | aLine.append( rEle.m_aAlias ); |
10684 | 0 | aLine.append( '\"' ); |
10685 | 0 | } |
10686 | 0 | emitComment( aLine.getStr() ); |
10687 | 0 | } |
10688 | | |
10689 | | // check whether to emit structure henceforth |
10690 | 0 | m_bEmitStructure = checkEmitStructure(); |
10691 | 0 | } |
10692 | | |
10693 | | void PDFWriterImpl::endStructureElement() |
10694 | 25 | { |
10695 | 25 | if( m_nCurrentPage < 0 ) |
10696 | 0 | return; |
10697 | | |
10698 | 25 | if( ! m_aContext.Tagged ) |
10699 | 25 | return; |
10700 | | |
10701 | 0 | if( m_nCurrentStructElement == 0 ) |
10702 | 0 | { |
10703 | | // hit the struct tree root, that means there is an endStructureElement |
10704 | | // without corresponding beginStructureElement |
10705 | 0 | return; |
10706 | 0 | } |
10707 | | |
10708 | | // end the marked content sequence |
10709 | 0 | endStructureElementMCSeq(); |
10710 | |
|
10711 | 0 | OStringBuffer aLine; |
10712 | 0 | if (g_bDebugDisableCompression) |
10713 | 0 | { |
10714 | 0 | aLine.append( "endStructureElement " ); |
10715 | 0 | aLine.append( m_nCurrentStructElement ); |
10716 | 0 | aLine.append( ": " ); |
10717 | 0 | aLine.append( m_aStructure[m_nCurrentStructElement].m_oType |
10718 | 0 | ? getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType) |
10719 | 0 | : "<placeholder>" ); |
10720 | 0 | if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() ) |
10721 | 0 | { |
10722 | 0 | aLine.append( " aliased as \"" ); |
10723 | 0 | aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias ); |
10724 | 0 | aLine.append( '\"' ); |
10725 | 0 | } |
10726 | 0 | } |
10727 | | |
10728 | | // "end" the structure element, the parent becomes current element |
10729 | 0 | m_nCurrentStructElement = m_StructElementStack.top(); |
10730 | 0 | m_StructElementStack.pop(); |
10731 | | |
10732 | | // check whether to emit structure henceforth |
10733 | 0 | m_bEmitStructure = checkEmitStructure(); |
10734 | |
|
10735 | 0 | if (g_bDebugDisableCompression && m_bEmitStructure) |
10736 | 0 | { |
10737 | 0 | emitComment( aLine.getStr() ); |
10738 | 0 | } |
10739 | 0 | } |
10740 | | |
10741 | | namespace { |
10742 | | |
10743 | | void removePlaceholderSEImpl(std::vector<PDFStructureElement> & rStructure, |
10744 | | std::vector<sal_Int32>::iterator & rParentIt) |
10745 | 0 | { |
10746 | 0 | PDFStructureElement& rEle(rStructure[*rParentIt]); |
10747 | 0 | removePlaceholderSE(rStructure, rEle); |
10748 | |
|
10749 | 0 | if (!rEle.m_oType) |
10750 | 0 | { |
10751 | | // Placeholder was not initialised - should not happen when printing |
10752 | | // a full page, but might if a selection is printed, which can be only |
10753 | | // a shape without its anchor. |
10754 | | // Handle this by moving the children to the parent SE. |
10755 | 0 | PDFStructureElement & rParent(rStructure[rEle.m_nParentElement]); |
10756 | 0 | rParentIt = rParent.m_aChildren.erase(rParentIt); |
10757 | 0 | std::vector<sal_Int32> children; |
10758 | 0 | for (auto const child : rEle.m_aChildren) |
10759 | 0 | { |
10760 | 0 | PDFStructureElement& rChild = rStructure[child]; |
10761 | 0 | rChild.m_nParentElement = rEle.m_nParentElement; |
10762 | 0 | children.push_back(rChild.m_nOwnElement); |
10763 | 0 | } |
10764 | 0 | rParentIt = rParent.m_aChildren.insert(rParentIt, children.begin(), children.end()) |
10765 | 0 | + children.size(); |
10766 | 0 | } |
10767 | 0 | else |
10768 | 0 | { |
10769 | 0 | ++rParentIt; |
10770 | 0 | } |
10771 | |
|
10772 | 0 | } |
10773 | | |
10774 | | void removePlaceholderSE(std::vector<PDFStructureElement> & rStructure, PDFStructureElement& rEle) |
10775 | 0 | { |
10776 | 0 | for (auto it = rEle.m_aChildren.begin(); it != rEle.m_aChildren.end(); ) |
10777 | 0 | { |
10778 | 0 | removePlaceholderSEImpl(rStructure, it); |
10779 | 0 | } |
10780 | 0 | } |
10781 | | |
10782 | | } // end anonymous namespace |
10783 | | |
10784 | | /* |
10785 | | * This function adds an internal structure list container to overcome the 8191 elements array limitation |
10786 | | * in kids element emission. |
10787 | | * Recursive function |
10788 | | * |
10789 | | */ |
10790 | | void PDFWriterImpl::addInternalStructureContainer(const sal_Int32 nEle) |
10791 | 0 | { |
10792 | | // m_aStructure grows below, so this addresses elements by index throughout: any |
10793 | | // reference into the vector dies at the next push_back |
10794 | 0 | if (m_aStructure[nEle].m_nOwnElement != m_aStructure[nEle].m_nParentElement |
10795 | 0 | && *m_aStructure[nEle].m_oType == StructElement::NonStructElement) |
10796 | 0 | { |
10797 | 0 | return; |
10798 | 0 | } |
10799 | | |
10800 | 0 | for (size_t i = 0; i < m_aStructure[nEle].m_aChildren.size(); ++i) |
10801 | 0 | { |
10802 | 0 | const sal_Int32 nChild = m_aStructure[nEle].m_aChildren[i]; |
10803 | 0 | assert(nChild > 0 && o3tl::make_unsigned(nChild) < m_aStructure.size()); |
10804 | 0 | if (nChild > 0 && o3tl::make_unsigned(nChild) < m_aStructure.size()) |
10805 | 0 | { |
10806 | 0 | if (*m_aStructure[nChild].m_oType != StructElement::NonStructElement) |
10807 | 0 | { |
10808 | | //triggered when a child of the nEle element is found |
10809 | 0 | assert(m_aStructure[nChild].m_nParentElement == m_aStructure[nEle].m_nOwnElement); |
10810 | 0 | if (m_aStructure[nChild].m_nParentElement == m_aStructure[nEle].m_nOwnElement) |
10811 | 0 | addInternalStructureContainer(nChild); //examine the child |
10812 | 0 | else |
10813 | 0 | { |
10814 | 0 | OSL_FAIL("PDFWriterImpl::addInternalStructureContainer: invalid child " |
10815 | 0 | "structure element"); |
10816 | 0 | SAL_INFO("vcl.pdfwriter", |
10817 | 0 | "PDFWriterImpl::addInternalStructureContainer: invalid child " |
10818 | 0 | "structure element with id " |
10819 | 0 | << nChild); |
10820 | 0 | } |
10821 | 0 | } |
10822 | 0 | } |
10823 | 0 | else |
10824 | 0 | { |
10825 | 0 | OSL_FAIL("PDFWriterImpl::emitStructure: invalid child structure id"); |
10826 | 0 | SAL_INFO("vcl.pdfwriter", |
10827 | 0 | "PDFWriterImpl::addInternalStructureContainer: invalid child structure id " |
10828 | 0 | << nChild); |
10829 | 0 | } |
10830 | 0 | } |
10831 | | |
10832 | 0 | if (m_aStructure[nEle].m_nOwnElement == m_aStructure[nEle].m_nParentElement) |
10833 | 0 | return; |
10834 | | |
10835 | 0 | if (m_aStructure[nEle].m_aKids.size() <= ncMaxPDFArraySize) |
10836 | 0 | return; |
10837 | | |
10838 | | //then we need to add the containers for the kids elements |
10839 | | // a list to be used for the new kid element |
10840 | 0 | std::list<PDFStructureElementKid> aNewKids; |
10841 | 0 | std::vector<sal_Int32> aNewChildren; |
10842 | |
|
10843 | 0 | while (m_aStructure[nEle].m_aKids.size() > ncMaxPDFArraySize) |
10844 | 0 | { |
10845 | 0 | const sal_Int32 nNewId = sal_Int32(m_aStructure.size()); |
10846 | 0 | { |
10847 | | //inherit the same page as the first child to be reparented |
10848 | 0 | const sal_Int32 nPage( |
10849 | 0 | m_aStructure[m_aStructure[nEle].m_aChildren.front()].m_nFirstPageObject); |
10850 | 0 | PDFStructureElement aNew(nNewId, nEle, nPage); |
10851 | 0 | aNew.m_oType.emplace(StructElement::Division); // a new Div type container |
10852 | 0 | aNew.m_nObject = createObject(); //assign a PDF object number |
10853 | 0 | m_aStructure.push_back(std::move(aNew)); |
10854 | 0 | } |
10855 | | //add the object to the kid list of the parent |
10856 | 0 | aNewKids.emplace_back(ObjReference{ m_aStructure[nNewId].m_nObject }); |
10857 | 0 | aNewChildren.push_back(nNewId); |
10858 | | |
10859 | | // m_aKids and m_aChildren are not parallel: every element gets a child entry, only an |
10860 | | // emitted one gets a kid, and marked content adds kids of its own. Cut the two at |
10861 | | // corresponding points, not at the same count. |
10862 | 0 | const auto aKidEndIt(std::next(m_aStructure[nEle].m_aKids.begin(), ncMaxPDFArraySize)); |
10863 | 0 | size_t nMovedChildren(0); |
10864 | 0 | for (auto it = m_aStructure[nEle].m_aKids.begin(); it != aKidEndIt; ++it) |
10865 | 0 | { |
10866 | 0 | if (std::holds_alternative<ObjReference>(*it)) |
10867 | 0 | ++nMovedChildren; |
10868 | 0 | } |
10869 | 0 | auto aChildEndIt(m_aStructure[nEle].m_aChildren.begin()); |
10870 | 0 | for (size_t nSeen = 0; |
10871 | 0 | nSeen < nMovedChildren && aChildEndIt != m_aStructure[nEle].m_aChildren.end(); |
10872 | 0 | ++aChildEndIt) |
10873 | 0 | { |
10874 | 0 | if (m_aStructure[*aChildEndIt].m_nObject != 0) // an emitted child has a kid entry |
10875 | 0 | ++nSeen; |
10876 | 0 | } |
10877 | |
|
10878 | 0 | m_aStructure[nNewId].m_aKids.splice(m_aStructure[nNewId].m_aKids.begin(), |
10879 | 0 | m_aStructure[nEle].m_aKids, |
10880 | 0 | m_aStructure[nEle].m_aKids.begin(), aKidEndIt); |
10881 | 0 | m_aStructure[nNewId].m_aChildren.insert(m_aStructure[nNewId].m_aChildren.begin(), |
10882 | 0 | m_aStructure[nEle].m_aChildren.begin(), |
10883 | 0 | aChildEndIt); |
10884 | 0 | m_aStructure[nEle].m_aChildren.erase(m_aStructure[nEle].m_aChildren.begin(), aChildEndIt); |
10885 | | |
10886 | | // set the kid's new parent |
10887 | 0 | for (const auto nMoved : m_aStructure[nNewId].m_aChildren) |
10888 | 0 | { |
10889 | 0 | m_aStructure[nMoved].m_nParentElement = nNewId; |
10890 | 0 | } |
10891 | 0 | } |
10892 | | //finally add the new kids resulting from the container added |
10893 | 0 | m_aStructure[nEle].m_aKids.insert(m_aStructure[nEle].m_aKids.begin(), aNewKids.begin(), |
10894 | 0 | aNewKids.end()); |
10895 | 0 | m_aStructure[nEle].m_aChildren.insert(m_aStructure[nEle].m_aChildren.begin(), |
10896 | 0 | aNewChildren.begin(), aNewChildren.end()); |
10897 | 0 | } |
10898 | | |
10899 | | bool PDFWriterImpl::setCurrentStructureElement( sal_Int32 nEle ) |
10900 | 0 | { |
10901 | 0 | bool bSuccess = false; |
10902 | |
|
10903 | 0 | if( m_aContext.Tagged && nEle >= 0 && o3tl::make_unsigned(nEle) < m_aStructure.size() ) |
10904 | 0 | { |
10905 | | // end eventual previous marked content sequence |
10906 | 0 | endStructureElementMCSeq(); |
10907 | |
|
10908 | 0 | m_nCurrentStructElement = nEle; |
10909 | 0 | m_bEmitStructure = checkEmitStructure(); |
10910 | 0 | if (g_bDebugDisableCompression) |
10911 | 0 | { |
10912 | 0 | OStringBuffer aLine( "setCurrentStructureElement " ); |
10913 | 0 | aLine.append( m_nCurrentStructElement ); |
10914 | 0 | aLine.append( ": " ); |
10915 | 0 | aLine.append( m_aStructure[m_nCurrentStructElement].m_oType |
10916 | 0 | ? getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType) |
10917 | 0 | : "<placeholder>" ); |
10918 | 0 | if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() ) |
10919 | 0 | { |
10920 | 0 | aLine.append( " aliased as \"" ); |
10921 | 0 | aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias ); |
10922 | 0 | aLine.append( '\"' ); |
10923 | 0 | } |
10924 | 0 | if( ! m_bEmitStructure ) |
10925 | 0 | aLine.append( " (inside NonStruct)" ); |
10926 | 0 | emitComment( aLine.getStr() ); |
10927 | 0 | } |
10928 | 0 | bSuccess = true; |
10929 | 0 | } |
10930 | |
|
10931 | 0 | return bSuccess; |
10932 | 0 | } |
10933 | | |
10934 | | bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr, enum PDFWriter::StructAttributeValue eVal ) |
10935 | 0 | { |
10936 | 0 | if( !m_aContext.Tagged ) |
10937 | 0 | return false; |
10938 | | |
10939 | 0 | assert(m_aStructure[m_nCurrentStructElement].m_oType); |
10940 | 0 | bool bInsert = false; |
10941 | 0 | if (m_nCurrentStructElement > 0 |
10942 | 0 | && (m_bEmitStructure |
10943 | | // allow it for topmost non-structured element |
10944 | 0 | || (m_aContext.Tagged |
10945 | 0 | && (0 == m_aStructure[m_nCurrentStructElement].m_nParentElement |
10946 | 0 | || !m_aStructure[m_aStructure[m_nCurrentStructElement].m_nParentElement].m_oType |
10947 | 0 | || *m_aStructure[m_aStructure[m_nCurrentStructElement].m_nParentElement].m_oType != StructElement::NonStructElement)))) |
10948 | 0 | { |
10949 | 0 | StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType; |
10950 | 0 | switch( eAttr ) |
10951 | 0 | { |
10952 | 0 | case PDFWriter::Placement: |
10953 | 0 | if( eVal == PDFWriter::Block || |
10954 | 0 | eVal == PDFWriter::Inline || |
10955 | 0 | eVal == PDFWriter::Before || |
10956 | 0 | eVal == PDFWriter::Start || |
10957 | 0 | eVal == PDFWriter::End ) |
10958 | 0 | bInsert = true; |
10959 | 0 | break; |
10960 | 0 | case PDFWriter::WritingMode: |
10961 | 0 | if( eVal == PDFWriter::LrTb || |
10962 | 0 | eVal == PDFWriter::RlTb || |
10963 | 0 | eVal == PDFWriter::TbRl ) |
10964 | 0 | { |
10965 | 0 | bInsert = true; |
10966 | 0 | } |
10967 | 0 | break; |
10968 | 0 | case PDFWriter::TextAlign: |
10969 | 0 | if( eVal == PDFWriter::Start || |
10970 | 0 | eVal == PDFWriter::Center || |
10971 | 0 | eVal == PDFWriter::End || |
10972 | 0 | eVal == PDFWriter::Justify ) |
10973 | 0 | { |
10974 | 0 | if (eType == StructElement::Paragraph || |
10975 | 0 | eType == StructElement::Title || |
10976 | 0 | eType == StructElement::Heading || |
10977 | 0 | eType == StructElement::H1 || |
10978 | 0 | eType == StructElement::H2 || |
10979 | 0 | eType == StructElement::H3 || |
10980 | 0 | eType == StructElement::H4 || |
10981 | 0 | eType == StructElement::H5 || |
10982 | 0 | eType == StructElement::H6 || |
10983 | 0 | eType == StructElement::List || |
10984 | 0 | eType == StructElement::ListItem || |
10985 | 0 | eType == StructElement::LILabel || |
10986 | 0 | eType == StructElement::LIBody || |
10987 | 0 | eType == StructElement::Table || |
10988 | 0 | eType == StructElement::TableRow || |
10989 | 0 | eType == StructElement::TableHeader || |
10990 | 0 | eType == StructElement::TableData) |
10991 | 0 | { |
10992 | 0 | bInsert = true; |
10993 | 0 | } |
10994 | 0 | } |
10995 | 0 | break; |
10996 | 0 | case PDFWriter::Width: |
10997 | 0 | case PDFWriter::Height: |
10998 | 0 | if( eVal == PDFWriter::Auto ) |
10999 | 0 | { |
11000 | 0 | if (eType == StructElement::Figure || |
11001 | 0 | eType == StructElement::Formula || |
11002 | 0 | eType == StructElement::Form || |
11003 | 0 | eType == StructElement::Table || |
11004 | 0 | eType == StructElement::TableHeader || |
11005 | 0 | eType == StructElement::TableData) |
11006 | 0 | { |
11007 | 0 | bInsert = true; |
11008 | 0 | } |
11009 | 0 | } |
11010 | 0 | break; |
11011 | 0 | case PDFWriter::BlockAlign: |
11012 | 0 | if( eVal == PDFWriter::Before || |
11013 | 0 | eVal == PDFWriter::Middle || |
11014 | 0 | eVal == PDFWriter::After || |
11015 | 0 | eVal == PDFWriter::Justify ) |
11016 | 0 | { |
11017 | 0 | if (eType == StructElement::TableHeader || |
11018 | 0 | eType == StructElement::TableData) |
11019 | 0 | { |
11020 | 0 | bInsert = true; |
11021 | 0 | } |
11022 | 0 | } |
11023 | 0 | break; |
11024 | 0 | case PDFWriter::InlineAlign: |
11025 | 0 | if( eVal == PDFWriter::Start || |
11026 | 0 | eVal == PDFWriter::Center || |
11027 | 0 | eVal == PDFWriter::End ) |
11028 | 0 | { |
11029 | 0 | if (eType == StructElement::TableHeader || |
11030 | 0 | eType == StructElement::TableData) |
11031 | 0 | { |
11032 | 0 | bInsert = true; |
11033 | 0 | } |
11034 | 0 | } |
11035 | 0 | break; |
11036 | 0 | case PDFWriter::LineHeight: |
11037 | 0 | if( eVal == PDFWriter::Normal || |
11038 | 0 | eVal == PDFWriter::Auto ) |
11039 | 0 | { |
11040 | | // only for ILSE and BLSE |
11041 | 0 | if (eType == StructElement::Paragraph || |
11042 | 0 | eType == StructElement::Title || |
11043 | 0 | eType == StructElement::Heading || |
11044 | 0 | eType == StructElement::H1 || |
11045 | 0 | eType == StructElement::H2 || |
11046 | 0 | eType == StructElement::H3 || |
11047 | 0 | eType == StructElement::H4 || |
11048 | 0 | eType == StructElement::H5 || |
11049 | 0 | eType == StructElement::H6 || |
11050 | 0 | eType == StructElement::List || |
11051 | 0 | eType == StructElement::ListItem || |
11052 | 0 | eType == StructElement::LILabel || |
11053 | 0 | eType == StructElement::LIBody || |
11054 | 0 | eType == StructElement::Table || |
11055 | 0 | eType == StructElement::TableRow || |
11056 | 0 | eType == StructElement::TableHeader || |
11057 | 0 | eType == StructElement::TableData || |
11058 | 0 | eType == StructElement::Span || |
11059 | 0 | eType == StructElement::Quote || |
11060 | 0 | eType == StructElement::Emphasis || |
11061 | 0 | eType == StructElement::Strong || |
11062 | 0 | eType == StructElement::Note || |
11063 | 0 | eType == StructElement::FENote || |
11064 | 0 | eType == StructElement::Reference || |
11065 | 0 | eType == StructElement::BibEntry || |
11066 | 0 | eType == StructElement::Code || |
11067 | 0 | eType == StructElement::Link) |
11068 | 0 | { |
11069 | 0 | bInsert = true; |
11070 | 0 | } |
11071 | 0 | } |
11072 | 0 | break; |
11073 | 0 | case PDFWriter::TextDecorationType: |
11074 | 0 | if( eVal == PDFWriter::NONE || |
11075 | 0 | eVal == PDFWriter::Underline || |
11076 | 0 | eVal == PDFWriter::Overline || |
11077 | 0 | eVal == PDFWriter::LineThrough ) |
11078 | 0 | { |
11079 | | // only for ILSE and BLSE |
11080 | 0 | if (eType == StructElement::Paragraph || |
11081 | 0 | eType == StructElement::Title || |
11082 | 0 | eType == StructElement::Heading || |
11083 | 0 | eType == StructElement::H1 || |
11084 | 0 | eType == StructElement::H2 || |
11085 | 0 | eType == StructElement::H3 || |
11086 | 0 | eType == StructElement::H4 || |
11087 | 0 | eType == StructElement::H5 || |
11088 | 0 | eType == StructElement::H6 || |
11089 | 0 | eType == StructElement::List || |
11090 | 0 | eType == StructElement::ListItem || |
11091 | 0 | eType == StructElement::LILabel || |
11092 | 0 | eType == StructElement::LIBody || |
11093 | 0 | eType == StructElement::Table || |
11094 | 0 | eType == StructElement::TableRow || |
11095 | 0 | eType == StructElement::TableHeader || |
11096 | 0 | eType == StructElement::TableData || |
11097 | 0 | eType == StructElement::Span || |
11098 | 0 | eType == StructElement::Quote || |
11099 | 0 | eType == StructElement::Emphasis || |
11100 | 0 | eType == StructElement::Strong || |
11101 | 0 | eType == StructElement::Note || |
11102 | 0 | eType == StructElement::FENote || |
11103 | 0 | eType == StructElement::Reference || |
11104 | 0 | eType == StructElement::BibEntry || |
11105 | 0 | eType == StructElement::Code || |
11106 | 0 | eType == StructElement::Link) |
11107 | 0 | { |
11108 | 0 | bInsert = true; |
11109 | 0 | } |
11110 | 0 | } |
11111 | 0 | break; |
11112 | 0 | case PDFWriter::Scope: |
11113 | 0 | if (eVal == PDFWriter::Row || eVal == PDFWriter::Column || eVal == PDFWriter::Both) |
11114 | 0 | { |
11115 | | // The Scope attribute on TH was introduced in PDF 1.5, but it |
11116 | | // is also mandatory for PDF/UA-1 (clause 7.5). When PDF/UA is |
11117 | | // requested with a PDF/A-1 (PDF 1.4) target, accept Scope so |
11118 | | // the resulting file can satisfy the PDF/UA structure rules. |
11119 | 0 | if (eType == StructElement::TableHeader |
11120 | 0 | && (PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version |
11121 | 0 | || m_aContext.UniversalAccessibilityCompliance)) |
11122 | 0 | { |
11123 | 0 | bInsert = true; |
11124 | 0 | } |
11125 | 0 | } |
11126 | 0 | break; |
11127 | 0 | case PDFWriter::Type: |
11128 | 0 | if (eVal == PDFWriter::Pagination || eVal == PDFWriter::Layout || eVal == PDFWriter::Page) |
11129 | | // + Background for PDF >= 1.7 |
11130 | 0 | { |
11131 | 0 | if (eType == StructElement::NonStructElement) |
11132 | 0 | { |
11133 | 0 | bInsert = true; |
11134 | 0 | } |
11135 | 0 | } |
11136 | 0 | break; |
11137 | 0 | case PDFWriter::Subtype: |
11138 | 0 | if (eVal == PDFWriter::Header || eVal == PDFWriter::Footer || eVal == PDFWriter::Watermark) |
11139 | 0 | { |
11140 | 0 | if (eType == StructElement::NonStructElement |
11141 | 0 | && PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version) |
11142 | 0 | { |
11143 | 0 | bInsert = true; |
11144 | 0 | } |
11145 | 0 | } |
11146 | 0 | break; |
11147 | 0 | case PDFWriter::Role: |
11148 | 0 | if (eVal == PDFWriter::Rb || eVal == PDFWriter::Cb || eVal == PDFWriter::Pb || eVal == PDFWriter::Tv) |
11149 | 0 | { |
11150 | 0 | if (eType == StructElement::Form |
11151 | 0 | && PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version) |
11152 | 0 | { |
11153 | 0 | bInsert = true; |
11154 | 0 | } |
11155 | 0 | } |
11156 | 0 | break; |
11157 | 0 | case PDFWriter::RubyAlign: |
11158 | 0 | if (eVal == PDFWriter::RStart || eVal == PDFWriter::RCenter || eVal == PDFWriter::REnd || eVal == PDFWriter::RJustify || eVal == PDFWriter::RDistribute) |
11159 | 0 | { |
11160 | 0 | if (eType == StructElement::RT |
11161 | 0 | && PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version) |
11162 | 0 | { |
11163 | 0 | bInsert = true; |
11164 | 0 | } |
11165 | 0 | } |
11166 | 0 | break; |
11167 | 0 | case PDFWriter::RubyPosition: |
11168 | 0 | if (eVal == PDFWriter::RBefore || eVal == PDFWriter::RAfter || eVal == PDFWriter::RWarichu || eVal == PDFWriter::RInline) |
11169 | 0 | { |
11170 | 0 | if (eType == StructElement::RT |
11171 | 0 | && PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version) |
11172 | 0 | { |
11173 | 0 | bInsert = true; |
11174 | 0 | } |
11175 | 0 | } |
11176 | 0 | break; |
11177 | 0 | case PDFWriter::ListNumbering: |
11178 | 0 | if( eVal == PDFWriter::NONE || |
11179 | 0 | eVal == PDFWriter::Disc || |
11180 | 0 | eVal == PDFWriter::Circle || |
11181 | 0 | eVal == PDFWriter::Square || |
11182 | 0 | eVal == PDFWriter::Decimal || |
11183 | 0 | eVal == PDFWriter::UpperRoman || |
11184 | 0 | eVal == PDFWriter::LowerRoman || |
11185 | 0 | eVal == PDFWriter::UpperAlpha || |
11186 | 0 | eVal == PDFWriter::LowerAlpha ) |
11187 | 0 | { |
11188 | 0 | if( eType == StructElement::List ) |
11189 | 0 | bInsert = true; |
11190 | 0 | } |
11191 | 0 | break; |
11192 | 0 | default: break; |
11193 | 0 | } |
11194 | 0 | } |
11195 | | |
11196 | 0 | if( bInsert ) |
11197 | 0 | m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( eVal ); |
11198 | 0 | else if( m_nCurrentStructElement > 0 && m_bEmitStructure ) |
11199 | 0 | SAL_INFO("vcl.pdfwriter", |
11200 | 0 | "rejecting setStructureAttribute( " << getAttributeTag( eAttr ) |
11201 | 0 | << ", " << getAttributeValueTag( eVal ) |
11202 | 0 | << " ) on " << getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType) |
11203 | 0 | << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias |
11204 | 0 | << ") element"); |
11205 | | |
11206 | 0 | return bInsert; |
11207 | 0 | } |
11208 | | |
11209 | | sal_Int32 PDFWriterImpl::convertStructureAttribute(enum PDFWriter::StructAttribute eAttr, |
11210 | | sal_Int32 nValue) |
11211 | 0 | { |
11212 | 0 | if (!isLengthAttribute(eAttr)) |
11213 | 0 | return nValue; |
11214 | | |
11215 | 0 | const Size aSize( |
11216 | 0 | convert(m_aGraphicsStack.front().m_aMapMode, m_aMapMode, this, Size(nValue, nValue))); |
11217 | | // the map mode can scale X and Y differently |
11218 | 0 | const bool bHorizontal(eAttr == PDFWriter::StartIndent || eAttr == PDFWriter::EndIndent |
11219 | 0 | || eAttr == PDFWriter::TextIndent || eAttr == PDFWriter::Width); |
11220 | |
|
11221 | 0 | return bHorizontal ? aSize.Width() : aSize.Height(); |
11222 | 0 | } |
11223 | | |
11224 | | bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttribute eAttr, sal_Int32 nValue ) |
11225 | 0 | { |
11226 | 0 | if( ! m_aContext.Tagged ) |
11227 | 0 | return false; |
11228 | | |
11229 | 0 | assert(m_aStructure[m_nCurrentStructElement].m_oType); |
11230 | 0 | bool bInsert = false; |
11231 | 0 | if( m_nCurrentStructElement > 0 && m_bEmitStructure ) |
11232 | 0 | { |
11233 | 0 | if( eAttr == PDFWriter::Language ) |
11234 | 0 | { |
11235 | 0 | m_aStructure[ m_nCurrentStructElement ].m_aLocale = LanguageTag( LanguageType(nValue) ).getLocale(); |
11236 | 0 | return true; |
11237 | 0 | } |
11238 | | |
11239 | 0 | StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType; |
11240 | 0 | switch( eAttr ) |
11241 | 0 | { |
11242 | 0 | case PDFWriter::SpaceBefore: |
11243 | 0 | case PDFWriter::SpaceAfter: |
11244 | 0 | case PDFWriter::StartIndent: |
11245 | 0 | case PDFWriter::EndIndent: |
11246 | | // just for BLSE |
11247 | 0 | if (eType == StructElement::Paragraph || |
11248 | 0 | eType == StructElement::Title || |
11249 | 0 | eType == StructElement::Heading || |
11250 | 0 | eType == StructElement::H1 || |
11251 | 0 | eType == StructElement::H2 || |
11252 | 0 | eType == StructElement::H3 || |
11253 | 0 | eType == StructElement::H4 || |
11254 | 0 | eType == StructElement::H5 || |
11255 | 0 | eType == StructElement::H6 || |
11256 | 0 | eType == StructElement::List || |
11257 | 0 | eType == StructElement::ListItem || |
11258 | 0 | eType == StructElement::LILabel || |
11259 | 0 | eType == StructElement::LIBody || |
11260 | 0 | eType == StructElement::Table || |
11261 | 0 | eType == StructElement::TableRow || |
11262 | 0 | eType == StructElement::TableHeader || |
11263 | 0 | eType == StructElement::TableData) |
11264 | 0 | { |
11265 | 0 | bInsert = true; |
11266 | 0 | } |
11267 | 0 | break; |
11268 | 0 | case PDFWriter::TextIndent: |
11269 | | // paragraph like BLSE and additional elements |
11270 | 0 | if (eType == StructElement::Paragraph || |
11271 | 0 | eType == StructElement::Title || |
11272 | 0 | eType == StructElement::Heading || |
11273 | 0 | eType == StructElement::H1 || |
11274 | 0 | eType == StructElement::H2 || |
11275 | 0 | eType == StructElement::H3 || |
11276 | 0 | eType == StructElement::H4 || |
11277 | 0 | eType == StructElement::H5 || |
11278 | 0 | eType == StructElement::H6 || |
11279 | 0 | eType == StructElement::LILabel || |
11280 | 0 | eType == StructElement::LIBody || |
11281 | 0 | eType == StructElement::TableHeader || |
11282 | 0 | eType == StructElement::TableData) |
11283 | 0 | { |
11284 | 0 | bInsert = true; |
11285 | 0 | } |
11286 | 0 | break; |
11287 | 0 | case PDFWriter::Width: |
11288 | 0 | case PDFWriter::Height: |
11289 | 0 | if (eType == StructElement::Figure || |
11290 | 0 | eType == StructElement::Formula || |
11291 | 0 | eType == StructElement::Form || |
11292 | 0 | eType == StructElement::Table || |
11293 | 0 | eType == StructElement::TableHeader || |
11294 | 0 | eType == StructElement::TableData) |
11295 | 0 | { |
11296 | 0 | bInsert = true; |
11297 | 0 | } |
11298 | 0 | break; |
11299 | 0 | case PDFWriter::LineHeight: |
11300 | 0 | case PDFWriter::BaselineShift: |
11301 | | // only for ILSE and BLSE |
11302 | 0 | if (eType == StructElement::Paragraph || |
11303 | 0 | eType == StructElement::Title || |
11304 | 0 | eType == StructElement::Heading || |
11305 | 0 | eType == StructElement::H1 || |
11306 | 0 | eType == StructElement::H2 || |
11307 | 0 | eType == StructElement::H3 || |
11308 | 0 | eType == StructElement::H4 || |
11309 | 0 | eType == StructElement::H5 || |
11310 | 0 | eType == StructElement::H6 || |
11311 | 0 | eType == StructElement::List || |
11312 | 0 | eType == StructElement::ListItem || |
11313 | 0 | eType == StructElement::LILabel || |
11314 | 0 | eType == StructElement::LIBody || |
11315 | 0 | eType == StructElement::Table || |
11316 | 0 | eType == StructElement::TableRow || |
11317 | 0 | eType == StructElement::TableHeader || |
11318 | 0 | eType == StructElement::TableData || |
11319 | 0 | eType == StructElement::Span || |
11320 | 0 | eType == StructElement::Quote || |
11321 | 0 | eType == StructElement::Emphasis || |
11322 | 0 | eType == StructElement::Strong || |
11323 | 0 | eType == StructElement::Note || |
11324 | 0 | eType == StructElement::FENote || |
11325 | 0 | eType == StructElement::Reference || |
11326 | 0 | eType == StructElement::BibEntry || |
11327 | 0 | eType == StructElement::Code || |
11328 | 0 | eType == StructElement::Link) |
11329 | 0 | { |
11330 | 0 | bInsert = true; |
11331 | 0 | } |
11332 | 0 | break; |
11333 | 0 | case PDFWriter::RowSpan: |
11334 | 0 | case PDFWriter::ColSpan: |
11335 | | // only for table cells |
11336 | 0 | if (eType == StructElement::TableHeader || |
11337 | 0 | eType == StructElement::TableData) |
11338 | 0 | { |
11339 | 0 | bInsert = true; |
11340 | 0 | } |
11341 | 0 | break; |
11342 | 0 | case PDFWriter::LinkAnnotation: |
11343 | 0 | if (eType == StructElement::Link) |
11344 | 0 | bInsert = true; |
11345 | 0 | break; |
11346 | 0 | case PDFWriter::NoteAnnotation: |
11347 | 0 | if (eType == StructElement::Annot) |
11348 | 0 | bInsert = true; |
11349 | 0 | break; |
11350 | 0 | default: break; |
11351 | 0 | } |
11352 | 0 | } |
11353 | | |
11354 | 0 | if (bInsert) |
11355 | 0 | { |
11356 | 0 | if (eAttr == PDFWriter::LinkAnnotation) |
11357 | 0 | { |
11358 | 0 | m_aStructure[m_nCurrentStructElement].m_LinkAnnotIds.push_back(nValue); |
11359 | 0 | } |
11360 | 0 | else |
11361 | 0 | { |
11362 | 0 | m_aStructure[m_nCurrentStructElement].m_aAttributes[eAttr] |
11363 | 0 | = PDFStructureAttribute(convertStructureAttribute(eAttr, nValue)); |
11364 | 0 | } |
11365 | 0 | } |
11366 | 0 | else if( m_nCurrentStructElement > 0 && m_bEmitStructure ) |
11367 | 0 | SAL_INFO("vcl.pdfwriter", |
11368 | 0 | "rejecting setStructureAttributeNumerical( " << getAttributeTag( eAttr ) |
11369 | 0 | << ", " << nValue |
11370 | 0 | << " ) on " << getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType) |
11371 | 0 | << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias |
11372 | 0 | << ") element"); |
11373 | | |
11374 | 0 | return bInsert; |
11375 | 0 | } |
11376 | | |
11377 | | void PDFWriterImpl::setStructureBoundingBox( const tools::Rectangle& rRect ) |
11378 | 0 | { |
11379 | 0 | sal_Int32 nPageNr = m_nCurrentPage; |
11380 | 0 | if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() || !m_aContext.Tagged ) |
11381 | 0 | return; |
11382 | | |
11383 | 0 | if( !(m_nCurrentStructElement > 0 && m_bEmitStructure) ) |
11384 | 0 | return; |
11385 | | |
11386 | 0 | assert(m_aStructure[m_nCurrentStructElement].m_oType); |
11387 | 0 | StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType; |
11388 | 0 | if (eType == StructElement::Figure || |
11389 | 0 | eType == StructElement::Formula || |
11390 | 0 | eType == StructElement::Form || |
11391 | 0 | eType == StructElement::Division || |
11392 | 0 | eType == StructElement::Table) |
11393 | 0 | { |
11394 | 0 | m_aStructure[ m_nCurrentStructElement ].m_aBBox = rRect; |
11395 | | // convert to default user space now, since the mapmode may change |
11396 | 0 | m_aPages[nPageNr].convertRect( m_aStructure[ m_nCurrentStructElement ].m_aBBox ); |
11397 | 0 | } |
11398 | 0 | } |
11399 | | |
11400 | | void PDFWriterImpl::setStructureAnnotIds(::std::vector<sal_Int32> const& rAnnotIds) |
11401 | 0 | { |
11402 | 0 | assert(!(m_nCurrentPage < 0 || m_aPages.size() <= o3tl::make_unsigned(m_nCurrentPage))); |
11403 | |
|
11404 | 0 | if (!m_aContext.Tagged || m_nCurrentStructElement <= 0 || !m_bEmitStructure) |
11405 | 0 | { |
11406 | 0 | return; |
11407 | 0 | } |
11408 | | |
11409 | 0 | m_aStructure[m_nCurrentStructElement].m_AnnotIds = rAnnotIds; |
11410 | 0 | } |
11411 | | |
11412 | | void PDFWriterImpl::setActualText( const OUString& rText ) |
11413 | 0 | { |
11414 | 0 | if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure ) |
11415 | 0 | { |
11416 | 0 | m_aStructure[ m_nCurrentStructElement ].m_aActualText = rText; |
11417 | 0 | } |
11418 | 0 | } |
11419 | | |
11420 | | void PDFWriterImpl::setAlternateText( const OUString& rText ) |
11421 | 0 | { |
11422 | 0 | if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure ) |
11423 | 0 | { |
11424 | 0 | m_aStructure[ m_nCurrentStructElement ].m_aAltText = rText; |
11425 | 0 | } |
11426 | 0 | } |
11427 | | |
11428 | | void PDFWriterImpl::setPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr ) |
11429 | 0 | { |
11430 | 0 | if( nPageNr < 0 ) |
11431 | 0 | nPageNr = m_nCurrentPage; |
11432 | |
|
11433 | 0 | if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() ) |
11434 | 0 | return; |
11435 | | |
11436 | 0 | m_aPages[ nPageNr ].m_eTransition = eType; |
11437 | 0 | m_aPages[ nPageNr ].m_nTransTime = nMilliSec; |
11438 | 0 | } |
11439 | | |
11440 | | void PDFWriterImpl::ensureUniqueRadioOnValues() |
11441 | 8.85k | { |
11442 | | // loop over radio groups |
11443 | 8.85k | for (auto const& group : m_aRadioGroupWidgets) |
11444 | 0 | { |
11445 | 0 | PDFWidget& rGroupWidget = m_aWidgets[ group.second ]; |
11446 | | // check whether all kids have a unique OnValue |
11447 | 0 | std::unordered_map< OUString, sal_Int32 > aOnValues; |
11448 | 0 | bool bIsUnique = true; |
11449 | 0 | for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex) |
11450 | 0 | { |
11451 | 0 | const OUString& rVal = m_aWidgets[nKidIndex].m_aOnValue; |
11452 | 0 | SAL_INFO("vcl.pdfwriter", "OnValue: " << rVal); |
11453 | 0 | if( aOnValues.find( rVal ) == aOnValues.end() ) |
11454 | 0 | { |
11455 | 0 | aOnValues[ rVal ] = 1; |
11456 | 0 | } |
11457 | 0 | else |
11458 | 0 | { |
11459 | 0 | bIsUnique = false; |
11460 | 0 | break; |
11461 | 0 | } |
11462 | 0 | } |
11463 | 0 | if( ! bIsUnique ) |
11464 | 0 | { |
11465 | 0 | SAL_INFO("vcl.pdfwriter", "enforcing unique OnValues" ); |
11466 | | // make unique by using ascending OnValues |
11467 | 0 | int nKid = 0; |
11468 | 0 | for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex) |
11469 | 0 | { |
11470 | 0 | PDFWidget& rKid = m_aWidgets[nKidIndex]; |
11471 | 0 | rKid.m_aOnValue = OUString::number( nKid+1 ); |
11472 | 0 | if( rKid.m_aValue != "Off" ) |
11473 | 0 | rKid.m_aValue = rKid.m_aOnValue; |
11474 | 0 | ++nKid; |
11475 | 0 | } |
11476 | 0 | } |
11477 | | // finally move the "Yes" appearance to the OnValue appearance |
11478 | 0 | for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex) |
11479 | 0 | { |
11480 | 0 | PDFWidget& rKid = m_aWidgets[nKidIndex]; |
11481 | 0 | if ( !rKid.m_aOnValue.isEmpty() ) |
11482 | 0 | { |
11483 | 0 | auto app_it = rKid.m_aAppearances.find( "N"_ostr ); |
11484 | 0 | if( app_it != rKid.m_aAppearances.end() ) |
11485 | 0 | { |
11486 | 0 | auto stream_it = app_it->second.find( "Yes"_ostr ); |
11487 | 0 | if( stream_it != app_it->second.end() ) |
11488 | 0 | { |
11489 | 0 | SvMemoryStream* pStream = stream_it->second; |
11490 | 0 | app_it->second.erase( stream_it ); |
11491 | 0 | OStringBuffer aBuf( rKid.m_aOnValue.getLength()*2 ); |
11492 | 0 | COSWriter::appendName( rKid.m_aOnValue, aBuf ); |
11493 | 0 | (app_it->second)[ aBuf.makeStringAndClear() ] = pStream; |
11494 | 0 | } |
11495 | 0 | else |
11496 | 0 | SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Yes\" stream" ); |
11497 | 0 | } |
11498 | 0 | } |
11499 | | |
11500 | 0 | if ( !rKid.m_aOffValue.isEmpty() ) |
11501 | 0 | { |
11502 | 0 | auto app_it = rKid.m_aAppearances.find( "N"_ostr ); |
11503 | 0 | if( app_it != rKid.m_aAppearances.end() ) |
11504 | 0 | { |
11505 | 0 | auto stream_it = app_it->second.find( "Off"_ostr ); |
11506 | 0 | if( stream_it != app_it->second.end() ) |
11507 | 0 | { |
11508 | 0 | SvMemoryStream* pStream = stream_it->second; |
11509 | 0 | app_it->second.erase( stream_it ); |
11510 | 0 | OStringBuffer aBuf( rKid.m_aOffValue.getLength()*2 ); |
11511 | 0 | COSWriter::appendName( rKid.m_aOffValue, aBuf ); |
11512 | 0 | (app_it->second)[ aBuf.makeStringAndClear() ] = pStream; |
11513 | 0 | } |
11514 | 0 | else |
11515 | 0 | SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Off\" stream" ); |
11516 | 0 | } |
11517 | 0 | } |
11518 | | |
11519 | | // update selected radio button |
11520 | 0 | if( rKid.m_aValue != "Off" ) |
11521 | 0 | { |
11522 | 0 | rGroupWidget.m_aValue = rKid.m_aValue; |
11523 | 0 | } |
11524 | 0 | } |
11525 | 0 | } |
11526 | 8.85k | } |
11527 | | |
11528 | | sal_Int32 PDFWriterImpl::findRadioGroupWidget( const PDFWriter::RadioButtonWidget& rBtn ) |
11529 | 0 | { |
11530 | 0 | sal_Int32 nRadioGroupWidget = -1; |
11531 | |
|
11532 | 0 | std::map< sal_Int32, sal_Int32 >::const_iterator it = m_aRadioGroupWidgets.find( rBtn.RadioGroup ); |
11533 | |
|
11534 | 0 | if( it == m_aRadioGroupWidgets.end() ) |
11535 | 0 | { |
11536 | 0 | m_aRadioGroupWidgets[ rBtn.RadioGroup ] = nRadioGroupWidget = |
11537 | 0 | sal_Int32(m_aWidgets.size()); |
11538 | | |
11539 | | // new group, insert the radiobutton |
11540 | 0 | m_aWidgets.emplace_back( ); |
11541 | 0 | m_aWidgets.back().m_nObject = createObject(); |
11542 | 0 | m_aWidgets.back().m_nPage = m_nCurrentPage; |
11543 | 0 | m_aWidgets.back().m_eType = PDFWriter::RadioButton; |
11544 | 0 | m_aWidgets.back().m_nRadioGroup = rBtn.RadioGroup; |
11545 | 0 | m_aWidgets.back().m_nFlags |= 0x0000C000; // NoToggleToOff and Radio bits |
11546 | |
|
11547 | 0 | createWidgetFieldName( sal_Int32(m_aWidgets.size()-1), rBtn ); |
11548 | 0 | } |
11549 | 0 | else |
11550 | 0 | nRadioGroupWidget = it->second; |
11551 | |
|
11552 | 0 | return nRadioGroupWidget; |
11553 | 0 | } |
11554 | | |
11555 | | sal_Int32 PDFWriterImpl::createControl( const PDFWriter::AnyWidget& rControl, sal_Int32 nPageNr ) |
11556 | 0 | { |
11557 | 0 | if( nPageNr < 0 ) |
11558 | 0 | nPageNr = m_nCurrentPage; |
11559 | |
|
11560 | 0 | if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() ) |
11561 | 0 | return -1; |
11562 | | |
11563 | 0 | sal_Int32 nNewWidget = m_aWidgets.size(); |
11564 | 0 | m_aWidgets.emplace_back( ); |
11565 | |
|
11566 | 0 | m_aWidgets.back().m_nObject = createObject(); |
11567 | 0 | m_aWidgets.back().m_aRect = rControl.Location; |
11568 | 0 | m_aWidgets.back().m_nPage = nPageNr; |
11569 | 0 | m_aWidgets.back().m_eType = rControl.getType(); |
11570 | |
|
11571 | 0 | sal_Int32 nRadioGroupWidget = -1; |
11572 | | // for unknown reasons the radio buttons of a radio group must not have a |
11573 | | // field name, else the buttons are in fact check boxes - |
11574 | | // that is multiple buttons of the radio group can be selected |
11575 | 0 | if( rControl.getType() == PDFWriter::RadioButton ) |
11576 | 0 | nRadioGroupWidget = findRadioGroupWidget( static_cast<const PDFWriter::RadioButtonWidget&>(rControl) ); |
11577 | 0 | else |
11578 | 0 | { |
11579 | 0 | createWidgetFieldName( nNewWidget, rControl ); |
11580 | 0 | } |
11581 | | |
11582 | | // caution: m_aWidgets must not be changed after here or rNewWidget may be invalid |
11583 | 0 | PDFWidget& rNewWidget = m_aWidgets[nNewWidget]; |
11584 | 0 | rNewWidget.m_aDescription = rControl.Description; |
11585 | 0 | rNewWidget.m_aText = rControl.Text; |
11586 | 0 | rNewWidget.m_nTextStyle = rControl.TextStyle & |
11587 | 0 | ( DrawTextFlags::Left | DrawTextFlags::Center | DrawTextFlags::Right | DrawTextFlags::Top | |
11588 | 0 | DrawTextFlags::VCenter | DrawTextFlags::Bottom | |
11589 | 0 | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak ); |
11590 | 0 | rNewWidget.m_nTabOrder = rControl.TabOrder; |
11591 | | |
11592 | | // various properties are set via the flags (/Ff) property of the field dict |
11593 | 0 | if( rControl.ReadOnly ) |
11594 | 0 | rNewWidget.m_nFlags |= 1; |
11595 | 0 | if( rControl.getType() == PDFWriter::PushButton ) |
11596 | 0 | { |
11597 | 0 | const PDFWriter::PushButtonWidget& rBtn = static_cast<const PDFWriter::PushButtonWidget&>(rControl); |
11598 | 0 | if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE ) |
11599 | 0 | rNewWidget.m_nTextStyle = |
11600 | 0 | DrawTextFlags::Center | DrawTextFlags::VCenter | |
11601 | 0 | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak; |
11602 | |
|
11603 | 0 | rNewWidget.m_nFlags |= 0x00010000; |
11604 | 0 | if( !rBtn.URL.isEmpty() ) |
11605 | 0 | rNewWidget.m_aListEntries.push_back( rBtn.URL ); |
11606 | 0 | rNewWidget.m_bSubmit = rBtn.Submit; |
11607 | 0 | rNewWidget.m_bSubmitGet = rBtn.SubmitGet; |
11608 | 0 | rNewWidget.m_nDest = rBtn.Dest; |
11609 | 0 | createDefaultPushButtonAppearance( rNewWidget, rBtn ); |
11610 | 0 | } |
11611 | 0 | else if( rControl.getType() == PDFWriter::RadioButton ) |
11612 | 0 | { |
11613 | 0 | const PDFWriter::RadioButtonWidget& rBtn = static_cast<const PDFWriter::RadioButtonWidget&>(rControl); |
11614 | 0 | if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE ) |
11615 | 0 | rNewWidget.m_nTextStyle = |
11616 | 0 | DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak; |
11617 | | /* PDF sees a RadioButton group as one radio button with |
11618 | | * children which are in turn check boxes |
11619 | | * |
11620 | | * so we need to create a radio button on demand for a new group |
11621 | | * and insert a checkbox for each RadioButtonWidget as its child |
11622 | | */ |
11623 | 0 | rNewWidget.m_eType = PDFWriter::CheckBox; |
11624 | 0 | rNewWidget.m_nRadioGroup = rBtn.RadioGroup; |
11625 | |
|
11626 | 0 | SAL_WARN_IF( nRadioGroupWidget < 0 || o3tl::make_unsigned(nRadioGroupWidget) >= m_aWidgets.size(), "vcl.pdfwriter", "no radio group parent" ); |
11627 | | |
11628 | 0 | PDFWidget& rRadioButton = m_aWidgets[nRadioGroupWidget]; |
11629 | 0 | rRadioButton.m_aKids.push_back( rNewWidget.m_nObject ); |
11630 | 0 | rRadioButton.m_aKidsIndex.push_back( nNewWidget ); |
11631 | 0 | rNewWidget.m_nParent = rRadioButton.m_nObject; |
11632 | |
|
11633 | 0 | rNewWidget.m_aValue = "Off"; |
11634 | 0 | rNewWidget.m_aOnValue = rBtn.OnValue; |
11635 | 0 | rNewWidget.m_aOffValue = rBtn.OffValue; |
11636 | 0 | if( rRadioButton.m_aValue.isEmpty() && rBtn.Selected ) |
11637 | 0 | { |
11638 | 0 | rNewWidget.m_aValue = rNewWidget.m_aOnValue; |
11639 | 0 | rRadioButton.m_aValue = rNewWidget.m_aOnValue; |
11640 | 0 | } |
11641 | 0 | createDefaultRadioButtonAppearance( rNewWidget, rBtn ); |
11642 | | |
11643 | | // union rect of radio group |
11644 | 0 | tools::Rectangle aRect = rNewWidget.m_aRect; |
11645 | 0 | m_aPages[ nPageNr ].convertRect( aRect ); |
11646 | 0 | rRadioButton.m_aRect.Union( aRect ); |
11647 | 0 | } |
11648 | 0 | else if( rControl.getType() == PDFWriter::CheckBox ) |
11649 | 0 | { |
11650 | 0 | const PDFWriter::CheckBoxWidget& rBox = static_cast<const PDFWriter::CheckBoxWidget&>(rControl); |
11651 | 0 | if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE ) |
11652 | 0 | rNewWidget.m_nTextStyle = |
11653 | 0 | DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak; |
11654 | |
|
11655 | 0 | rNewWidget.m_aValue |
11656 | 0 | = rBox.Checked ? std::u16string_view(u"Yes") : std::u16string_view(u"Off" ); |
11657 | 0 | rNewWidget.m_aOnValue = rBox.OnValue; |
11658 | 0 | rNewWidget.m_aOffValue = rBox.OffValue; |
11659 | | // create default appearance before m_aRect gets transformed |
11660 | 0 | createDefaultCheckBoxAppearance( rNewWidget, rBox ); |
11661 | 0 | } |
11662 | 0 | else if( rControl.getType() == PDFWriter::ListBox ) |
11663 | 0 | { |
11664 | 0 | if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE ) |
11665 | 0 | rNewWidget.m_nTextStyle = DrawTextFlags::VCenter; |
11666 | |
|
11667 | 0 | const PDFWriter::ListBoxWidget& rLstBox = static_cast<const PDFWriter::ListBoxWidget&>(rControl); |
11668 | 0 | rNewWidget.m_aListEntries = rLstBox.Entries; |
11669 | 0 | rNewWidget.m_aSelectedEntries = rLstBox.SelectedEntries; |
11670 | 0 | rNewWidget.m_aValue = rLstBox.Text; |
11671 | 0 | if( rLstBox.DropDown ) |
11672 | 0 | rNewWidget.m_nFlags |= 0x00020000; |
11673 | 0 | if (rLstBox.MultiSelect && !rLstBox.DropDown) |
11674 | 0 | rNewWidget.m_nFlags |= 0x00200000; |
11675 | |
|
11676 | 0 | createDefaultListBoxAppearance( rNewWidget, rLstBox ); |
11677 | 0 | } |
11678 | 0 | else if( rControl.getType() == PDFWriter::ComboBox ) |
11679 | 0 | { |
11680 | 0 | if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE ) |
11681 | 0 | rNewWidget.m_nTextStyle = DrawTextFlags::VCenter; |
11682 | |
|
11683 | 0 | const PDFWriter::ComboBoxWidget& rBox = static_cast<const PDFWriter::ComboBoxWidget&>(rControl); |
11684 | 0 | rNewWidget.m_aValue = rBox.Text; |
11685 | 0 | rNewWidget.m_aListEntries = rBox.Entries; |
11686 | 0 | rNewWidget.m_nFlags |= 0x00060000; // combo and edit flag |
11687 | |
|
11688 | 0 | PDFWriter::ListBoxWidget aLBox; |
11689 | 0 | aLBox.Name = rBox.Name; |
11690 | 0 | aLBox.Description = rBox.Description; |
11691 | 0 | aLBox.Text = rBox.Text; |
11692 | 0 | aLBox.TextStyle = rBox.TextStyle; |
11693 | 0 | aLBox.ReadOnly = rBox.ReadOnly; |
11694 | 0 | aLBox.Border = rBox.Border; |
11695 | 0 | aLBox.BorderColor = rBox.BorderColor; |
11696 | 0 | aLBox.Background = rBox.Background; |
11697 | 0 | aLBox.BackgroundColor = rBox.BackgroundColor; |
11698 | 0 | aLBox.TextFont = rBox.TextFont; |
11699 | 0 | aLBox.TextColor = rBox.TextColor; |
11700 | 0 | aLBox.DropDown = true; |
11701 | 0 | aLBox.MultiSelect = false; |
11702 | 0 | aLBox.Entries = rBox.Entries; |
11703 | |
|
11704 | 0 | createDefaultListBoxAppearance( rNewWidget, aLBox ); |
11705 | 0 | } |
11706 | 0 | else if( rControl.getType() == PDFWriter::Edit ) |
11707 | 0 | { |
11708 | 0 | if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE ) |
11709 | 0 | rNewWidget.m_nTextStyle = DrawTextFlags::Left | DrawTextFlags::VCenter; |
11710 | |
|
11711 | 0 | const PDFWriter::EditWidget& rEdit = static_cast<const PDFWriter::EditWidget&>(rControl); |
11712 | 0 | if( rEdit.MultiLine ) |
11713 | 0 | { |
11714 | 0 | rNewWidget.m_nFlags |= 0x00001000; |
11715 | 0 | rNewWidget.m_nTextStyle |= DrawTextFlags::MultiLine | DrawTextFlags::WordBreak; |
11716 | 0 | } |
11717 | 0 | if( rEdit.Password ) |
11718 | 0 | rNewWidget.m_nFlags |= 0x00002000; |
11719 | 0 | if (rEdit.FileSelect) |
11720 | 0 | rNewWidget.m_nFlags |= 0x00100000; |
11721 | 0 | rNewWidget.m_nMaxLen = rEdit.MaxLen; |
11722 | 0 | rNewWidget.m_nFormat = rEdit.Format; |
11723 | 0 | rNewWidget.m_aCurrencySymbol = rEdit.CurrencySymbol; |
11724 | 0 | rNewWidget.m_nDecimalAccuracy = rEdit.DecimalAccuracy; |
11725 | 0 | rNewWidget.m_bPrependCurrencySymbol = rEdit.PrependCurrencySymbol; |
11726 | 0 | rNewWidget.m_aTimeFormat = rEdit.TimeFormat; |
11727 | 0 | rNewWidget.m_aDateFormat = rEdit.DateFormat; |
11728 | 0 | rNewWidget.m_aValue = rEdit.Text; |
11729 | |
|
11730 | 0 | createDefaultEditAppearance( rNewWidget, rEdit ); |
11731 | 0 | } |
11732 | | #if HAVE_FEATURE_NSS |
11733 | | else if( rControl.getType() == PDFWriter::Signature) |
11734 | | { |
11735 | | rNewWidget.m_aRect = tools::Rectangle(0, 0, 0, 0); |
11736 | | |
11737 | | m_nSignatureObject = createObject(); |
11738 | | rNewWidget.m_aValue = OUString::number( m_nSignatureObject ); |
11739 | | rNewWidget.m_aValue += " 0 R"; |
11740 | | // let's add a fake appearance |
11741 | | rNewWidget.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = new SvMemoryStream(); |
11742 | | } |
11743 | | #endif |
11744 | | |
11745 | | // if control is a signature, do not convert coordinates since we |
11746 | | // need /Rect [ 0 0 0 0 ] |
11747 | 0 | if ( rControl.getType() != PDFWriter::Signature ) |
11748 | 0 | { |
11749 | | // convert to default user space now, since the mapmode may change |
11750 | | // note: create default appearances before m_aRect gets transformed |
11751 | 0 | m_aPages[ nPageNr ].convertRect( rNewWidget.m_aRect ); |
11752 | 0 | } |
11753 | | |
11754 | | // insert widget to page's annotation list |
11755 | 0 | m_aPages[ nPageNr ].m_aAnnotations.push_back( rNewWidget.m_nObject ); |
11756 | |
|
11757 | 0 | return nNewWidget; |
11758 | 0 | } |
11759 | | |
11760 | | void PDFWriterImpl::MARK( const char* pString ) |
11761 | 860k | { |
11762 | 860k | beginStructureElementMCSeq(); |
11763 | 860k | if (g_bDebugDisableCompression) |
11764 | 0 | emitComment( pString ); |
11765 | 860k | } |
11766 | | |
11767 | | sal_Int32 ReferenceXObjectEmit::getObject() const |
11768 | 0 | { |
11769 | 0 | if (m_nFormObject > 0) |
11770 | 0 | return m_nFormObject; |
11771 | 0 | else |
11772 | 0 | return m_nBitmapObject; |
11773 | 0 | } |
11774 | | } |
11775 | | |
11776 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |