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 <string_view>
28
29 #include <lcms2.h>
30
31 #include <basegfx/matrix/b2dhommatrix.hxx>
32 #include <basegfx/polygon/b2dpolygon.hxx>
33 #include <basegfx/polygon/b2dpolygontools.hxx>
34 #include <basegfx/polygon/b2dpolypolygon.hxx>
35 #include <basegfx/polygon/b2dpolypolygoncutter.hxx>
36 #include <basegfx/polygon/b2dpolypolygontools.hxx>
37 #include <memory>
38 #include <com/sun/star/io/XOutputStream.hpp>
39 #include <com/sun/star/util/URL.hpp>
40 #include <com/sun/star/util/URLTransformer.hpp>
41 #include <comphelper/processfactory.hxx>
42 #include <comphelper/string.hxx>
43 #include <comphelper/xmlencode.hxx>
44 #include <cppuhelper/implbase.hxx>
45 #include <i18nlangtag/languagetag.hxx>
46 #include <o3tl/numeric.hxx>
47 #include <o3tl/safeint.hxx>
48 #include <o3tl/temporary.hxx>
49 #include <officecfg/Office/Common.hxx>
50 #include <osl/diagnose.h>
51 #include <osl/file.hxx>
52 #include <osl/thread.h>
53 #include <rtl/crc.h>
54 #include <rtl/character.hxx>
55 #include <rtl/digest.h>
56 #include <rtl/uri.hxx>
57 #include <rtl/ustrbuf.hxx>
58 #include <svl/cryptosign.hxx>
59 #include <sal/log.hxx>
60 #include <svl/urihelper.hxx>
61 #include <tools/fract.hxx>
62 #include <tools/stream.hxx>
63 #include <tools/helpers.hxx>
64 #include <tools/urlobj.hxx>
65 #include <tools/UnitConversion.hxx>
66 #include <tools/zcodec.hxx>
67 #include <unotools/configmgr.hxx>
68 #include <vcl/bitmapex.hxx>
69 #include <vcl/canvastools.hxx>
70 #include <vcl/cvtgrf.hxx>
71 #include <vcl/fontcharmap.hxx>
72 #include <vcl/glyphitemcache.hxx>
73 #include <vcl/kernarray.hxx>
74 #include <vcl/lineinfo.hxx>
75 #include <vcl/metric.hxx>
76 #include <vcl/mnemonic.hxx>
77 #include <vcl/pdfread.hxx>
78 #include <vcl/settings.hxx>
79 #include <strhelper.hxx>
80 #include <vcl/svapp.hxx>
81 #include <vcl/virdev.hxx>
82 #include <vcl/filter/pdfdocument.hxx>
83 #include <vcl/filter/PngImageReader.hxx>
84 #include <comphelper/hash.hxx>
85 #include <vcl/pdf/PDFNote.hxx>
86
87 #include <svdata.hxx>
88 #include <vcl/BitmapWriteAccess.hxx>
89 #include <pdf/COSWriter.hxx>
90 #include <fontsubset.hxx>
91 #include <font/EmphasisMark.hxx>
92 #include <font/PhysicalFontFace.hxx>
93 #include <salgdi.hxx>
94 #include <textlayout.hxx>
95 #include <textlineinfo.hxx>
96 #include <impglyphitem.hxx>
97 #include <pdf/XmpMetadata.hxx>
98 #include <pdf/objectcopier.hxx>
99 #include <pdf/pdfwriter_impl.hxx>
100 #include <pdf/PdfConfig.hxx>
101 #include <pdf/PDFEncryptorR6.hxx>
102 #include <pdf/PDFEncryptor.hxx>
103 #include <o3tl/sorted_vector.hxx>
104 #include <frozen/bits/defines.h>
105 #include <frozen/bits/elsa_std.h>
106 #include <frozen/unordered_map.h>
107
108 using namespace::com::sun::star;
109
110 static bool g_bDebugDisableCompression = getenv("VCL_DEBUG_DISABLE_PDFCOMPRESSION");
111
112 namespace
113 {
114
115 constexpr std::string_view constNamespacePDF2("http://iso.org/pdf2/ssn");
116
117 constexpr sal_Int32 nLog10Divisor = 3;
118 constexpr double fDivisor = 1000.0;
119
pixelToPoint(double px)120 constexpr double pixelToPoint(double px)
121 {
122 return px / fDivisor;
123 }
124
pointToPixel(double pt)125 constexpr sal_Int32 pointToPixel(double pt)
126 {
127 return sal_Int32(pt * fDivisor);
128 }
129
appendObjectID(sal_Int32 nObjectID,OStringBuffer & aLine)130 void appendObjectID(sal_Int32 nObjectID, OStringBuffer & aLine)
131 {
132 aLine.append(nObjectID);
133 aLine.append(" 0 obj\n");
134 }
135
appendObjectReference(sal_Int32 nObjectID,OStringBuffer & aLine)136 void appendObjectReference(sal_Int32 nObjectID, OStringBuffer & aLine)
137 {
138 aLine.append(nObjectID);
139 aLine.append(" 0 R ");
140 }
141
142 /*
143 * Convert a string before using it.
144 *
145 * This string conversion function is needed because the destination name
146 * in a PDF file seen through an Internet browser should be
147 * specially crafted, in order to be used directly by the browser.
148 * In this way the fragment part of a hyperlink to a PDF file (e.g. something
149 * as 'test1/test2/a-file.pdf\#thefragment) will be (hopefully) interpreted by the
150 * PDF reader (currently only Adobe Reader plug-in seems to be working that way) called
151 * from inside the Internet browser as: 'open the file test1/test2/a-file.pdf
152 * and go to named destination thefragment using default zoom'.
153 * The conversion is needed because in case of a fragment in the form: Slide%201
154 * (meaning Slide 1) as it is converted obeying the Inet rules, it will become Slide25201
155 * using this conversion, in both the generated named destinations, fragment and GoToR
156 * destination.
157 *
158 * The names for destinations are name objects and so they don't need to be encrypted
159 * even though they expose the content of PDF file (e.g. guessing the PDF content from the
160 * destination name).
161 *
162 * Further limitation: it is advisable to use standard ASCII characters for
163 * OOo bookmarks.
164 */
appendDestinationName(std::u16string_view rString,OStringBuffer & rBuffer)165 void appendDestinationName( std::u16string_view rString, OStringBuffer& rBuffer )
166 {
167 for( auto aChar: rString)
168 {
169 if( rtl::isAsciiAlphanumeric(aChar) || aChar == '-' )
170 {
171 rBuffer.append(static_cast<char>(aChar));
172 }
173 else
174 {
175 sal_Int8 aValueHigh = sal_Int8(aChar >> 8);
176 if(aValueHigh > 0)
177 vcl::COSWriter::appendHex(aValueHigh, rBuffer);
178 vcl::COSWriter::appendHex(static_cast<sal_Int8>(aChar & 255 ), rBuffer);
179 }
180 }
181 }
182 } // end anonymous namespace
183
184 namespace vcl
185 {
186
187 namespace
188 {
189
190 template < class GEOMETRY >
lcl_convert(const MapMode & _rSource,const MapMode & _rDest,OutputDevice * _pPixelConversion,const GEOMETRY & _rObject)191 GEOMETRY lcl_convert( const MapMode& _rSource, const MapMode& _rDest, OutputDevice* _pPixelConversion, const GEOMETRY& _rObject )
192 {
193 GEOMETRY aPoint;
194 if ( MapUnit::MapPixel == _rSource.GetMapUnit() )
195 {
196 aPoint = _pPixelConversion->PixelToLogic( _rObject, _rDest );
197 }
198 else
199 {
200 aPoint = OutputDevice::LogicToLogic( _rObject, _rSource, _rDest );
201 }
202 return aPoint;
203 }
204
205 void removePlaceholderSE(std::vector<PDFStructureElement> & rStructure, PDFStructureElement& rEle);
206
207 } // end anonymous namespace
208
createWidgetFieldName(sal_Int32 i_nWidgetIndex,const PDFWriter::AnyWidget & i_rControl)209 void PDFWriterImpl::createWidgetFieldName( sal_Int32 i_nWidgetIndex, const PDFWriter::AnyWidget& i_rControl )
210 {
211 /* #i80258# previously we use appendName here
212 however we need a slightly different coding scheme than the normal
213 name encoding for field names
214 */
215 const OUString& rName = i_rControl.Name;
216 OString aStr( OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ) );
217 int nLen = aStr.getLength();
218
219 OStringBuffer aBuffer( rName.getLength()+64 );
220 for( int i = 0; i < nLen; i++ )
221 {
222 /* #i16920# PDF recommendation: output UTF8, any byte
223 * outside the interval [32(=ASCII' ');126(=ASCII'~')]
224 * should be escaped hexadecimal
225 */
226 if( aStr[i] >= 32 && aStr[i] <= 126 )
227 aBuffer.append( aStr[i] );
228 else
229 {
230 aBuffer.append( '#' );
231 COSWriter::appendHex(static_cast<sal_Int8>(aStr[i]), aBuffer);
232 }
233 }
234
235 OString aFullName( aBuffer.makeStringAndClear() );
236
237 /* #i82785# create hierarchical fields down to the for each dot in i_rName */
238 sal_Int32 nTokenIndex = 0, nLastTokenIndex = 0;
239 OString aPartialName;
240 OString aDomain;
241 do
242 {
243 nLastTokenIndex = nTokenIndex;
244 aPartialName = aFullName.getToken( 0, '.', nTokenIndex );
245 if( nTokenIndex != -1 )
246 {
247 // find or create a hierarchical field
248 // first find the fully qualified name up to this field
249 aDomain = aFullName.copy( 0, nTokenIndex-1 );
250 std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
251 if( it == m_aFieldNameMap.end() )
252 {
253 // create new hierarchy field
254 sal_Int32 nNewWidget = m_aWidgets.size();
255 m_aWidgets.emplace_back( );
256 m_aWidgets[nNewWidget].m_nObject = createObject();
257 m_aWidgets[nNewWidget].m_eType = PDFWriter::Hierarchy;
258 m_aWidgets[nNewWidget].m_aName = aPartialName;
259 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
260 m_aFieldNameMap[aDomain] = nNewWidget;
261 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
262 if( nLastTokenIndex > 0 )
263 {
264 // this field is not a root field and
265 // needs to be inserted to its parent
266 OString aParentDomain( aDomain.copy( 0, nLastTokenIndex-1 ) );
267 it = m_aFieldNameMap.find( aParentDomain );
268 OSL_ENSURE( it != m_aFieldNameMap.end(), "field name not found" );
269 if( it != m_aFieldNameMap.end() )
270 {
271 OSL_ENSURE( it->second < sal_Int32(m_aWidgets.size()), "invalid field number entry" );
272 if( it->second < sal_Int32(m_aWidgets.size()) )
273 {
274 PDFWidget& rParentField( m_aWidgets[it->second] );
275 rParentField.m_aKids.push_back( m_aWidgets[nNewWidget].m_nObject );
276 rParentField.m_aKidsIndex.push_back( nNewWidget );
277 m_aWidgets[nNewWidget].m_nParent = rParentField.m_nObject;
278 }
279 }
280 }
281 }
282 else if( m_aWidgets[it->second].m_eType != PDFWriter::Hierarchy )
283 {
284 // this is invalid, someone tries to have a terminal field as parent
285 // example: a button with the name foo.bar exists and
286 // another button is named foo.bar.no
287 // workaround: put the second terminal field as much up in the hierarchy as
288 // necessary to have a non-terminal field as parent (or none at all)
289 // since it->second already is terminal, we just need to use its parent
290 aDomain.clear();
291 aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
292 if( nLastTokenIndex > 0 )
293 {
294 aDomain = aFullName.copy( 0, nLastTokenIndex-1 );
295 aFullName = aDomain + "." + aPartialName;
296 }
297 else
298 aFullName = aPartialName;
299 break;
300 }
301 }
302 } while( nTokenIndex != -1 );
303
304 // insert widget into its hierarchy field
305 if( !aDomain.isEmpty() )
306 {
307 std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
308 if( it != m_aFieldNameMap.end() )
309 {
310 OSL_ENSURE( it->second >= 0 && o3tl::make_unsigned(it->second) < m_aWidgets.size(), "invalid field index" );
311 if( it->second >= 0 && o3tl::make_unsigned(it->second) < m_aWidgets.size() )
312 {
313 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[it->second].m_nObject;
314 m_aWidgets[it->second].m_aKids.push_back( m_aWidgets[i_nWidgetIndex].m_nObject);
315 m_aWidgets[it->second].m_aKidsIndex.push_back( i_nWidgetIndex );
316 }
317 }
318 }
319
320 if( aPartialName.isEmpty() )
321 {
322 // how funny, an empty field name
323 if( i_rControl.getType() == PDFWriter::RadioButton )
324 {
325 aPartialName = "RadioGroup" +
326 OString::number( static_cast<const PDFWriter::RadioButtonWidget&>(i_rControl).RadioGroup );
327 }
328 else
329 aPartialName = "Widget"_ostr;
330 }
331
332 if( ! m_aContext.AllowDuplicateFieldNames )
333 {
334 std::unordered_map<OString, sal_Int32>::iterator it = m_aFieldNameMap.find( aFullName );
335
336 if( it != m_aFieldNameMap.end() ) // not unique
337 {
338 std::unordered_map< OString, sal_Int32 >::const_iterator check_it;
339 OString aTry;
340 sal_Int32 nTry = 2;
341 do
342 {
343 aTry = aFullName + "_" + OString::number(nTry++);
344 check_it = m_aFieldNameMap.find( aTry );
345 } while( check_it != m_aFieldNameMap.end() );
346 aFullName = aTry;
347 m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
348 aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
349 }
350 else
351 m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
352 }
353
354 // finally
355 m_aWidgets[i_nWidgetIndex].m_aName = aPartialName;
356 }
357
358 namespace
359 {
360
appendFixedInt(sal_Int32 nValue,OStringBuffer & rBuffer)361 void appendFixedInt( sal_Int32 nValue, OStringBuffer& rBuffer )
362 {
363 if( nValue < 0 )
364 {
365 rBuffer.append( '-' );
366 nValue = -nValue;
367 }
368 sal_Int32 nFactor = 1, nDiv = nLog10Divisor;
369 while( nDiv-- )
370 nFactor *= 10;
371
372 sal_Int32 nInt = nValue / nFactor;
373 rBuffer.append( nInt );
374 if (nFactor > 1 && nValue % nFactor)
375 {
376 rBuffer.append( '.' );
377 do
378 {
379 nFactor /= 10;
380 rBuffer.append((nValue / nFactor) % 10);
381 }
382 while (nFactor > 1 && nValue % nFactor); // omit trailing zeros
383 }
384 }
385
386 // appends a double. PDF does not accept exponential format, only fixed point
appendDouble(double fValue,OStringBuffer & rBuffer,sal_Int32 nPrecision=10)387 void appendDouble( double fValue, OStringBuffer& rBuffer, sal_Int32 nPrecision = 10 )
388 {
389 bool bNeg = false;
390 if( fValue < 0.0 )
391 {
392 bNeg = true;
393 fValue=-fValue;
394 }
395
396 sal_Int64 nInt = static_cast<sal_Int64>(fValue);
397 fValue -= static_cast<double>(nInt);
398 // optimizing hardware may lead to a value of 1.0 after the subtraction
399 if( rtl::math::approxEqual(fValue, 1.0) || log10( 1.0-fValue ) <= -nPrecision )
400 {
401 nInt++;
402 fValue = 0.0;
403 }
404 sal_Int64 nFrac = 0;
405 if( fValue )
406 {
407 fValue *= pow( 10.0, static_cast<double>(nPrecision) );
408 nFrac = static_cast<sal_Int64>(fValue);
409 }
410 if( bNeg && ( nInt || nFrac ) )
411 rBuffer.append( '-' );
412 rBuffer.append( nInt );
413 if( !nFrac )
414 return;
415
416 int i;
417 rBuffer.append( '.' );
418 sal_Int64 nBound = static_cast<sal_Int64>(pow( 10.0, nPrecision - 1.0 )+0.5);
419 for ( i = 0; ( i < nPrecision ) && nFrac; i++ )
420 {
421 sal_Int64 nNumb = nFrac / nBound;
422 nFrac -= nNumb * nBound;
423 rBuffer.append( nNumb );
424 nBound /= 10;
425 }
426 }
427
appendColor(const Color & rColor,OStringBuffer & rBuffer,bool bConvertToGrey)428 void appendColor( const Color& rColor, OStringBuffer& rBuffer, bool bConvertToGrey )
429 {
430 if( rColor == COL_TRANSPARENT )
431 return;
432
433 if( bConvertToGrey )
434 {
435 sal_uInt8 cByte = rColor.GetLuminance();
436 appendDouble( static_cast<double>(cByte) / 255.0, rBuffer );
437 }
438 else
439 {
440 appendDouble( static_cast<double>(rColor.GetRed()) / 255.0, rBuffer );
441 rBuffer.append( ' ' );
442 appendDouble( static_cast<double>(rColor.GetGreen()) / 255.0, rBuffer );
443 rBuffer.append( ' ' );
444 appendDouble( static_cast<double>(rColor.GetBlue()) / 255.0, rBuffer );
445 }
446 }
447
448 } // end anonymous namespace
449
appendStrokingColor(const Color & rColor,OStringBuffer & rBuffer)450 void PDFWriterImpl::appendStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
451 {
452 if( rColor != COL_TRANSPARENT )
453 {
454 bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
455 appendColor( rColor, rBuffer, bGrey );
456 rBuffer.append( bGrey ? " G" : " RG" );
457 }
458 }
459
appendNonStrokingColor(const Color & rColor,OStringBuffer & rBuffer)460 void PDFWriterImpl::appendNonStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
461 {
462 if( rColor != COL_TRANSPARENT )
463 {
464 bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
465 appendColor( rColor, rBuffer, bGrey );
466 rBuffer.append( bGrey ? " g" : " rg" );
467 }
468 }
469
470 namespace
471 {
472
appendPdfTimeDate(OStringBuffer & rBuffer,sal_Int16 year,sal_uInt16 month,sal_uInt16 day,sal_uInt16 hours,sal_uInt16 minutes,sal_uInt16 seconds,sal_Int32 tzDelta)473 void appendPdfTimeDate(OStringBuffer & rBuffer,
474 sal_Int16 year, sal_uInt16 month, sal_uInt16 day, sal_uInt16 hours, sal_uInt16 minutes, sal_uInt16 seconds, sal_Int32 tzDelta)
475 {
476 rBuffer.append("D:");
477 rBuffer.append(char('0' + ((year / 1000) % 10)));
478 rBuffer.append(char('0' + ((year / 100) % 10)));
479 rBuffer.append(char('0' + ((year / 10) % 10)));
480 rBuffer.append(char('0' + (year % 10)));
481 rBuffer.append(char('0' + ((month / 10) % 10)));
482 rBuffer.append(char('0' + (month % 10)));
483 rBuffer.append(char('0' + ((day / 10) % 10)));
484 rBuffer.append(char('0' + (day % 10)));
485 rBuffer.append(char('0' + ((hours / 10) % 10)));
486 rBuffer.append(char('0' + (hours % 10)));
487 rBuffer.append(char('0' + ((minutes / 10) % 10)));
488 rBuffer.append(char('0' + (minutes % 10)));
489 rBuffer.append(char('0' + ((seconds / 10) % 10)));
490 rBuffer.append(char('0' + (seconds % 10)));
491
492 if (tzDelta == 0)
493 {
494 rBuffer.append("Z");
495 }
496 else
497 {
498 if (tzDelta > 0 )
499 rBuffer.append("+");
500 else
501 {
502 rBuffer.append("-");
503 tzDelta = -tzDelta;
504 }
505
506 rBuffer.append(char('0' + ((tzDelta / 36000) % 10)));
507 rBuffer.append(char('0' + ((tzDelta / 3600) % 10)));
508 rBuffer.append("'");
509 rBuffer.append(char('0' + ((tzDelta / 600) % 6)));
510 rBuffer.append(char('0' + ((tzDelta / 60) % 10)));
511 }
512 }
513
getPDFVersionStr(PDFWriter::PDFVersion ePDFVersion)514 const char* getPDFVersionStr(PDFWriter::PDFVersion ePDFVersion)
515 {
516 switch (ePDFVersion)
517 {
518 case PDFWriter::PDFVersion::PDF_A_1:
519 case PDFWriter::PDFVersion::PDF_1_4:
520 return "1.4";
521 case PDFWriter::PDFVersion::PDF_1_5:
522 return "1.5";
523 case PDFWriter::PDFVersion::PDF_1_6:
524 return "1.6";
525 default:
526 case PDFWriter::PDFVersion::PDF_A_2:
527 case PDFWriter::PDFVersion::PDF_A_3:
528 case PDFWriter::PDFVersion::PDF_1_7:
529 return "1.7";
530 // PDF 2.0
531 case PDFWriter::PDFVersion::PDF_A_4:
532 case PDFWriter::PDFVersion::PDF_2_0:
533 return "2.0";
534 }
535 }
536
computeDocumentIdentifier(std::vector<sal_uInt8> & o_rIdentifier,const vcl::PDFWriter::PDFDocInfo & i_rDocInfo,const OString & i_rCString1,const css::util::DateTime & rCreationMetaDate,OString & o_rCString2)537 void computeDocumentIdentifier(std::vector<sal_uInt8>& o_rIdentifier,
538 const vcl::PDFWriter::PDFDocInfo& i_rDocInfo,
539 const OString& i_rCString1,
540 const css::util::DateTime& rCreationMetaDate, OString& o_rCString2)
541 {
542 o_rIdentifier.clear();
543
544 //build the document id
545 OString aInfoValuesOut;
546 OStringBuffer aID(1024);
547 if (!i_rDocInfo.Title.isEmpty())
548 COSWriter::appendUnicodeTextString(i_rDocInfo.Title, aID);
549 if (!i_rDocInfo.Author.isEmpty())
550 COSWriter::appendUnicodeTextString(i_rDocInfo.Author, aID);
551 if (!i_rDocInfo.Subject.isEmpty())
552 COSWriter::appendUnicodeTextString(i_rDocInfo.Subject, aID);
553 if (!i_rDocInfo.Keywords.isEmpty())
554 COSWriter::appendUnicodeTextString(i_rDocInfo.Keywords, aID);
555 if (!i_rDocInfo.Creator.isEmpty())
556 COSWriter::appendUnicodeTextString(i_rDocInfo.Creator, aID);
557 if (!i_rDocInfo.Producer.isEmpty())
558 COSWriter::appendUnicodeTextString(i_rDocInfo.Producer, aID);
559
560 TimeValue aTVal, aGMT;
561 oslDateTime aDT;
562 aDT.NanoSeconds = rCreationMetaDate.NanoSeconds;
563 aDT.Seconds = rCreationMetaDate.Seconds;
564 aDT.Minutes = rCreationMetaDate.Minutes;
565 aDT.Hours = rCreationMetaDate.Hours;
566 aDT.Day = rCreationMetaDate.Day;
567 aDT.Month = rCreationMetaDate.Month;
568 aDT.Year = rCreationMetaDate.Year;
569
570 osl_getSystemTime(&aGMT);
571 osl_getLocalTimeFromSystemTime(&aGMT, &aTVal);
572 OStringBuffer aCreationMetaDateString(64);
573
574 // i59651: we fill the Metadata date string as well, if PDF/A is requested
575 // according to ISO 19005-1:2005 6.7.3 the date is corrected for
576 // local time zone offset UTC only, whereas Acrobat 8 seems
577 // to use the localtime notation only
578 // according to a recommendation in XMP Specification (Jan 2004, page 75)
579 // the Acrobat way seems the right approach
580 aCreationMetaDateString.append(char('0' + ((aDT.Year / 1000) % 10)));
581 aCreationMetaDateString.append(char('0' + ((aDT.Year / 100) % 10)));
582 aCreationMetaDateString.append(char('0' + ((aDT.Year / 10) % 10)));
583 aCreationMetaDateString.append(char('0' + ((aDT.Year) % 10)));
584 aCreationMetaDateString.append("-");
585 aCreationMetaDateString.append(char('0' + ((aDT.Month / 10) % 10)));
586 aCreationMetaDateString.append(char('0' + ((aDT.Month) % 10)));
587 aCreationMetaDateString.append("-");
588 aCreationMetaDateString.append(char('0' + ((aDT.Day / 10) % 10)));
589 aCreationMetaDateString.append(char('0' + ((aDT.Day) % 10)));
590 aCreationMetaDateString.append("T");
591 aCreationMetaDateString.append(char('0' + ((aDT.Hours / 10) % 10)));
592 aCreationMetaDateString.append(char('0' + ((aDT.Hours) % 10)));
593 aCreationMetaDateString.append(":");
594 aCreationMetaDateString.append(char('0' + ((aDT.Minutes / 10) % 10)));
595 aCreationMetaDateString.append(char('0' + ((aDT.Minutes) % 10)));
596 aCreationMetaDateString.append(":");
597 aCreationMetaDateString.append(char('0' + ((aDT.Seconds / 10) % 10)));
598 aCreationMetaDateString.append(char('0' + ((aDT.Seconds) % 10)));
599
600 sal_uInt32 nDelta = 0;
601 if (aGMT.Seconds > aTVal.Seconds)
602 {
603 nDelta = aGMT.Seconds - aTVal.Seconds;
604 aCreationMetaDateString.append("-");
605 }
606 else if (aGMT.Seconds < aTVal.Seconds)
607 {
608 nDelta = aTVal.Seconds - aGMT.Seconds;
609 aCreationMetaDateString.append("+");
610 }
611 else
612 {
613 aCreationMetaDateString.append("Z");
614 }
615 if (nDelta)
616 {
617 aCreationMetaDateString.append(char('0' + ((nDelta / 36000) % 10)));
618 aCreationMetaDateString.append(char('0' + ((nDelta / 3600) % 10)));
619 aCreationMetaDateString.append(":");
620 aCreationMetaDateString.append(char('0' + ((nDelta / 600) % 6)));
621 aCreationMetaDateString.append(char('0' + ((nDelta / 60) % 10)));
622 }
623 aID.append(i_rCString1.getStr(), i_rCString1.getLength());
624
625 aInfoValuesOut = aID.makeStringAndClear();
626 o_rCString2 = aCreationMetaDateString.makeStringAndClear();
627
628 ::comphelper::Hash aDigest(::comphelper::HashType::MD5);
629 aDigest.update(&aGMT, sizeof(aGMT));
630 aDigest.update(aInfoValuesOut.getStr(), aInfoValuesOut.getLength());
631 //the binary form of the doc id is needed for encryption stuff
632 o_rIdentifier = aDigest.finalize();
633 }
634
635 } // end anonymous namespace
636
PDFPage(PDFWriterImpl * pWriter,double nPageWidth,double nPageHeight,PDFWriter::Orientation eOrientation)637 PDFPage::PDFPage( PDFWriterImpl* pWriter, double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
638 :
639 m_pWriter( pWriter ),
640 m_nPageWidth( nPageWidth ),
641 m_nPageHeight( nPageHeight ),
642 m_nUserUnit( 1 ),
643 m_eOrientation( eOrientation ),
644 m_nPageObject( 0 ), // invalid object number
645 m_nStreamLengthObject( 0 ),
646 m_nBeginStreamPos( 0 ),
647 m_eTransition( PDFWriter::PageTransition::Regular ),
648 m_nTransTime( 0 )
649 {
650 // object ref must be only ever updated in emit()
651 m_nPageObject = m_pWriter->createObject();
652
653 switch (m_pWriter->m_aContext.Version)
654 {
655 // 1.6 or later
656 default:
657 m_nUserUnit = std::ceil(std::max(nPageWidth, nPageHeight) / 14400.0);
658 break;
659 case PDFWriter::PDFVersion::PDF_1_4:
660 case PDFWriter::PDFVersion::PDF_1_5:
661 case PDFWriter::PDFVersion::PDF_A_1:
662 break;
663 }
664 }
665
beginStream()666 void PDFPage::beginStream()
667 {
668 if (g_bDebugDisableCompression)
669 {
670 m_pWriter->emitComment("PDFWriterImpl::PDFPage::beginStream, +");
671 }
672 m_aStreamObjects.push_back(m_pWriter->createObject());
673 if( ! m_pWriter->updateObject( m_aStreamObjects.back() ) )
674 return;
675
676 m_nStreamLengthObject = m_pWriter->createObject();
677 // write content stream header
678 OStringBuffer aLine(
679 OString::number(m_aStreamObjects.back())
680 + " 0 obj\n<</Length "
681 + OString::number( m_nStreamLengthObject )
682 + " 0 R" );
683 if (!g_bDebugDisableCompression)
684 aLine.append( "/Filter/FlateDecode" );
685 aLine.append( ">>\nstream\n" );
686 if( ! m_pWriter->writeBuffer( aLine ) )
687 return;
688 if (osl::File::E_None != m_pWriter->m_aFile.getPos(m_nBeginStreamPos))
689 {
690 m_pWriter->m_aFile.close();
691 m_pWriter->m_bOpen = false;
692 }
693 if (!g_bDebugDisableCompression)
694 m_pWriter->beginCompression();
695 m_pWriter->checkAndEnableStreamEncryption( m_aStreamObjects.back() );
696 }
697
endStream()698 void PDFPage::endStream()
699 {
700 if (!g_bDebugDisableCompression)
701 m_pWriter->endCompression();
702 sal_uInt64 nEndStreamPos;
703 if (osl::File::E_None != m_pWriter->m_aFile.getPos(nEndStreamPos))
704 {
705 m_pWriter->m_aFile.close();
706 m_pWriter->m_bOpen = false;
707 return;
708 }
709 m_pWriter->disableStreamEncryption();
710 if( ! m_pWriter->writeBuffer( "\nendstream\nendobj\n\n" ) )
711 return;
712 // emit stream length object
713 if( ! m_pWriter->updateObject( m_nStreamLengthObject ) )
714 return;
715 OString aLine =
716 OString::number( m_nStreamLengthObject ) +
717 " 0 obj\n" +
718 OString::number( static_cast<sal_Int64>(nEndStreamPos-m_nBeginStreamPos) ) +
719 "\nendobj\n\n";
720 m_pWriter->writeBuffer( aLine );
721 }
722
emit(sal_Int32 nParentObject)723 bool PDFPage::emit(sal_Int32 nParentObject )
724 {
725 m_pWriter->MARK("PDFPage::emit");
726 // emit page object
727 if( ! m_pWriter->updateObject( m_nPageObject ) )
728 return false;
729 OStringBuffer aLine(
730 OString::number(m_nPageObject)
731 + " 0 obj\n"
732 "<</Type/Page/Parent "
733 + OString::number(nParentObject)
734 + " 0 R"
735 "/Resources "
736 + OString::number(m_pWriter->getResourceDictObj())
737 + " 0 R" );
738 if( m_nPageWidth && m_nPageHeight )
739 {
740 aLine.append( "/MediaBox[0 0 "
741 + OString::number(m_nPageWidth / m_nUserUnit)
742 + " "
743 + OString::number(m_nPageHeight / m_nUserUnit)
744 + "]" );
745 if (m_nUserUnit > 1)
746 {
747 aLine.append("\n/UserUnit " + OString::number(m_nUserUnit));
748 }
749 }
750 switch( m_eOrientation )
751 {
752 case PDFWriter::Orientation::Portrait: aLine.append( "/Rotate 0\n" );break;
753 case PDFWriter::Orientation::Inherit: break;
754 }
755 int nAnnots = m_aAnnotations.size();
756 if( nAnnots > 0 )
757 {
758 aLine.append( "/Annots[\n" );
759 for( int i = 0; i < nAnnots; i++ )
760 {
761 aLine.append( OString::number(m_aAnnotations[i])
762 + " 0 R" );
763 aLine.append( ((i+1)%15) ? " " : "\n" );
764 }
765 aLine.append( "]\n" );
766 }
767 if (PDFWriter::PDFVersion::PDF_1_5 <= m_pWriter->m_aContext.Version)
768 {
769 // ISO 14289-1:2014, Clause: 7.18.3 requires it if there are annotations
770 // but Adobe Acrobat Pro complains if it is ever missing so just
771 // write it always.
772 aLine.append( "/Tabs/S\n" );
773 }
774 if( !m_aMCIDParents.empty() )
775 {
776 OStringBuffer aStructParents( 1024 );
777 aStructParents.append( "[ " );
778 int nParents = m_aMCIDParents.size();
779 for( int i = 0; i < nParents; i++ )
780 {
781 aStructParents.append( OString::number(m_aMCIDParents[i])
782 + " 0 R" );
783 aStructParents.append( ((i%10) == 9) ? "\n" : " " );
784 }
785 aStructParents.append( "]" );
786 m_pWriter->m_aStructParentTree.push_back( aStructParents.makeStringAndClear() );
787
788 aLine.append( "/StructParents "
789 + OString::number( sal_Int32(m_pWriter->m_aStructParentTree.size()-1) )
790 + "\n" );
791 }
792 if( m_eTransition != PDFWriter::PageTransition::Regular && m_nTransTime > 0 )
793 {
794 // transition duration
795 aLine.append( "/Trans<</D " );
796 appendDouble( static_cast<double>(m_nTransTime)/1000.0, aLine, 3 );
797 aLine.append( "\n" );
798 const char *pStyle = nullptr, *pDm = nullptr, *pM = nullptr, *pDi = nullptr;
799 switch( m_eTransition )
800 {
801 case PDFWriter::PageTransition::SplitHorizontalInward:
802 pStyle = "Split"; pDm = "H"; pM = "I"; break;
803 case PDFWriter::PageTransition::SplitHorizontalOutward:
804 pStyle = "Split"; pDm = "H"; pM = "O"; break;
805 case PDFWriter::PageTransition::SplitVerticalInward:
806 pStyle = "Split"; pDm = "V"; pM = "I"; break;
807 case PDFWriter::PageTransition::SplitVerticalOutward:
808 pStyle = "Split"; pDm = "V"; pM = "O"; break;
809 case PDFWriter::PageTransition::BlindsHorizontal:
810 pStyle = "Blinds"; pDm = "H"; break;
811 case PDFWriter::PageTransition::BlindsVertical:
812 pStyle = "Blinds"; pDm = "V"; break;
813 case PDFWriter::PageTransition::BoxInward:
814 pStyle = "Box"; pM = "I"; break;
815 case PDFWriter::PageTransition::BoxOutward:
816 pStyle = "Box"; pM = "O"; break;
817 case PDFWriter::PageTransition::WipeLeftToRight:
818 pStyle = "Wipe"; pDi = "0"; break;
819 case PDFWriter::PageTransition::WipeBottomToTop:
820 pStyle = "Wipe"; pDi = "90"; break;
821 case PDFWriter::PageTransition::WipeRightToLeft:
822 pStyle = "Wipe"; pDi = "180"; break;
823 case PDFWriter::PageTransition::WipeTopToBottom:
824 pStyle = "Wipe"; pDi = "270"; break;
825 case PDFWriter::PageTransition::Dissolve:
826 pStyle = "Dissolve"; break;
827 case PDFWriter::PageTransition::Regular:
828 break;
829 }
830 // transition style
831 if( pStyle )
832 {
833 aLine.append( OString::Concat("/S/") + pStyle + "\n" );
834 }
835 if( pDm )
836 {
837 aLine.append( OString::Concat("/Dm/") + pDm + "\n" );
838 }
839 if( pM )
840 {
841 aLine.append( OString::Concat("/M/") + pM + "\n" );
842 }
843 if( pDi )
844 {
845 aLine.append( OString::Concat("/Di ") + pDi + "\n" );
846 }
847 aLine.append( ">>\n" );
848 }
849
850 aLine.append( "/Contents" );
851 unsigned int nStreamObjects = m_aStreamObjects.size();
852 if( nStreamObjects > 1 )
853 aLine.append( '[' );
854 for(sal_Int32 i : m_aStreamObjects)
855 {
856 aLine.append( " " + OString::number( i ) + " 0 R" );
857 }
858 if( nStreamObjects > 1 )
859 aLine.append( ']' );
860 aLine.append( ">>\nendobj\n\n" );
861 return m_pWriter->writeBuffer( aLine );
862 }
863
appendPoint(const Point & rPoint,OStringBuffer & rBuffer) const864 void PDFPage::appendPoint( const Point& rPoint, OStringBuffer& rBuffer ) const
865 {
866 Point aPoint( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
867 m_pWriter->m_aMapMode,
868 m_pWriter,
869 rPoint ) );
870
871 sal_Int32 nValue = aPoint.X();
872
873 appendFixedInt( nValue, rBuffer );
874
875 rBuffer.append( ' ' );
876
877 nValue = pointToPixel(getHeight()) - aPoint.Y();
878
879 appendFixedInt( nValue, rBuffer );
880 }
881
appendPixelPoint(const basegfx::B2DPoint & rPoint,OStringBuffer & rBuffer) const882 void PDFPage::appendPixelPoint( const basegfx::B2DPoint& rPoint, OStringBuffer& rBuffer ) const
883 {
884 double fValue = pixelToPoint(rPoint.getX());
885
886 appendDouble( fValue, rBuffer, nLog10Divisor );
887 rBuffer.append( ' ' );
888 fValue = getHeight() - pixelToPoint(rPoint.getY());
889 appendDouble( fValue, rBuffer, nLog10Divisor );
890 }
891
appendRect(const tools::Rectangle & rRect,OStringBuffer & rBuffer) const892 void PDFPage::appendRect( const tools::Rectangle& rRect, OStringBuffer& rBuffer ) const
893 {
894 appendPoint( rRect.BottomLeft() + Point( 0, 1 ), rBuffer );
895 rBuffer.append( ' ' );
896 appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), rBuffer, false );
897 rBuffer.append( ' ' );
898 appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), rBuffer );
899 rBuffer.append( " re" );
900 }
901
convertRect(tools::Rectangle & rRect) const902 void PDFPage::convertRect( tools::Rectangle& rRect ) const
903 {
904 Point aLL = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
905 m_pWriter->m_aMapMode,
906 m_pWriter,
907 rRect.BottomLeft() + Point( 0, 1 )
908 );
909 Size aSize = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
910 m_pWriter->m_aMapMode,
911 m_pWriter,
912 rRect.GetSize() );
913 rRect.SetLeft( aLL.X() );
914 rRect.SetRight( aLL.X() + aSize.Width() );
915 rRect.SetTop( pointToPixel(getHeight()) - aLL.Y() );
916 rRect.SetBottom( rRect.Top() + aSize.Height() );
917 }
918
appendPolygon(const tools::Polygon & rPoly,OStringBuffer & rBuffer,bool bClose) const919 void PDFPage::appendPolygon( const tools::Polygon& rPoly, OStringBuffer& rBuffer, bool bClose ) const
920 {
921 sal_uInt16 nPoints = rPoly.GetSize();
922 /*
923 * #108582# applications do weird things
924 */
925 sal_uInt32 nBufLen = rBuffer.getLength();
926 if( nPoints <= 0 )
927 return;
928
929 const PolyFlags* pFlagArray = rPoly.GetConstFlagAry();
930 appendPoint( rPoly[0], rBuffer );
931 rBuffer.append( " m\n" );
932 for( sal_uInt16 i = 1; i < nPoints; i++ )
933 {
934 if( pFlagArray && pFlagArray[i] == PolyFlags::Control && nPoints-i > 2 )
935 {
936 // bezier
937 SAL_WARN_IF( pFlagArray[i+1] != PolyFlags::Control || pFlagArray[i+2] == PolyFlags::Control, "vcl.pdfwriter", "unexpected sequence of control points" );
938 appendPoint( rPoly[i], rBuffer );
939 rBuffer.append( " " );
940 appendPoint( rPoly[i+1], rBuffer );
941 rBuffer.append( " " );
942 appendPoint( rPoly[i+2], rBuffer );
943 rBuffer.append( " c" );
944 i += 2; // add additionally consumed points
945 }
946 else
947 {
948 // line
949 appendPoint( rPoly[i], rBuffer );
950 rBuffer.append( " l" );
951 }
952 if( (rBuffer.getLength() - nBufLen) > 65 )
953 {
954 rBuffer.append( "\n" );
955 nBufLen = rBuffer.getLength();
956 }
957 else
958 rBuffer.append( " " );
959 }
960 if( bClose )
961 rBuffer.append( "h\n" );
962 }
963
appendPolygon(const basegfx::B2DPolygon & rPoly,OStringBuffer & rBuffer) const964 void PDFPage::appendPolygon( const basegfx::B2DPolygon& rPoly, OStringBuffer& rBuffer ) const
965 {
966 basegfx::B2DPolygon aPoly( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
967 m_pWriter->m_aMapMode,
968 m_pWriter,
969 rPoly ) );
970
971 if( basegfx::utils::isRectangle( aPoly ) )
972 {
973 basegfx::B2DRange aRange( aPoly.getB2DRange() );
974 basegfx::B2DPoint aBL( aRange.getMinX(), aRange.getMaxY() );
975 appendPixelPoint( aBL, rBuffer );
976 rBuffer.append( ' ' );
977 appendMappedLength( aRange.getWidth(), rBuffer, false, nLog10Divisor );
978 rBuffer.append( ' ' );
979 appendMappedLength( aRange.getHeight(), rBuffer, true, nLog10Divisor );
980 rBuffer.append( " re\n" );
981 return;
982 }
983 sal_uInt32 nPoints = aPoly.count();
984 if( nPoints <= 0 )
985 return;
986
987 sal_uInt32 nBufLen = rBuffer.getLength();
988 basegfx::B2DPoint aLastPoint( aPoly.getB2DPoint( 0 ) );
989 appendPixelPoint( aLastPoint, rBuffer );
990 rBuffer.append( " m\n" );
991 for( sal_uInt32 i = 1; i <= nPoints; i++ )
992 {
993 if( i != nPoints || aPoly.isClosed() )
994 {
995 sal_uInt32 nCurPoint = i % nPoints;
996 sal_uInt32 nLastPoint = i-1;
997 basegfx::B2DPoint aPoint( aPoly.getB2DPoint( nCurPoint ) );
998 if( aPoly.isNextControlPointUsed( nLastPoint ) &&
999 aPoly.isPrevControlPointUsed( nCurPoint ) )
1000 {
1001 appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer );
1002 rBuffer.append( ' ' );
1003 appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer );
1004 rBuffer.append( ' ' );
1005 appendPixelPoint( aPoint, rBuffer );
1006 rBuffer.append( " c" );
1007 }
1008 else if( aPoly.isNextControlPointUsed( nLastPoint ) )
1009 {
1010 appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer );
1011 rBuffer.append( ' ' );
1012 appendPixelPoint( aPoint, rBuffer );
1013 rBuffer.append( " y" );
1014 }
1015 else if( aPoly.isPrevControlPointUsed( nCurPoint ) )
1016 {
1017 appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer );
1018 rBuffer.append( ' ' );
1019 appendPixelPoint( aPoint, rBuffer );
1020 rBuffer.append( " v" );
1021 }
1022 else
1023 {
1024 appendPixelPoint( aPoint, rBuffer );
1025 rBuffer.append( " l" );
1026 }
1027 if( (rBuffer.getLength() - nBufLen) > 65 )
1028 {
1029 rBuffer.append( "\n" );
1030 nBufLen = rBuffer.getLength();
1031 }
1032 else
1033 rBuffer.append( " " );
1034 }
1035 }
1036 rBuffer.append( "h\n" );
1037 }
1038
appendPolyPolygon(const tools::PolyPolygon & rPolyPoly,OStringBuffer & rBuffer) const1039 void PDFPage::appendPolyPolygon( const tools::PolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
1040 {
1041 sal_uInt16 nPolygons = rPolyPoly.Count();
1042 for( sal_uInt16 n = 0; n < nPolygons; n++ )
1043 appendPolygon( rPolyPoly[n], rBuffer );
1044 }
1045
appendPolyPolygon(const basegfx::B2DPolyPolygon & rPolyPoly,OStringBuffer & rBuffer) const1046 void PDFPage::appendPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
1047 {
1048 for(auto const& rPolygon : rPolyPoly)
1049 appendPolygon( rPolygon, rBuffer );
1050 }
1051
appendMappedLength(sal_Int32 nLength,OStringBuffer & rBuffer,bool bVertical,sal_Int32 * pOutLength) const1052 void PDFPage::appendMappedLength( sal_Int32 nLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32* pOutLength ) const
1053 {
1054 sal_Int32 nValue = nLength;
1055 if ( nLength < 0 )
1056 {
1057 rBuffer.append( '-' );
1058 nValue = -nLength;
1059 }
1060 Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1061 m_pWriter->m_aMapMode,
1062 m_pWriter,
1063 Size( nValue, nValue ) ) );
1064 nValue = bVertical ? aSize.Height() : aSize.Width();
1065 if( pOutLength )
1066 *pOutLength = ((nLength < 0 ) ? -nValue : nValue);
1067
1068 appendFixedInt( nValue, rBuffer );
1069 }
1070
appendMappedLength(double fLength,OStringBuffer & rBuffer,bool bVertical,sal_Int32 nPrecision) const1071 void PDFPage::appendMappedLength( double fLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32 nPrecision ) const
1072 {
1073 Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1074 m_pWriter->m_aMapMode,
1075 m_pWriter,
1076 Size( 1000, 1000 ) ) );
1077 fLength *= pixelToPoint(static_cast<double>(bVertical ? aSize.Height() : aSize.Width()) / 1000.0);
1078 appendDouble( fLength, rBuffer, nPrecision );
1079 }
1080
appendLineInfo(const LineInfo & rInfo,OStringBuffer & rBuffer) const1081 bool PDFPage::appendLineInfo( const LineInfo& rInfo, OStringBuffer& rBuffer ) const
1082 {
1083 if(LineStyle::Dash == rInfo.GetStyle() && rInfo.GetDashLen() != rInfo.GetDotLen())
1084 {
1085 // dashed and non-degraded case, check for implementation limits of dash array
1086 // in PDF reader apps (e.g. acroread)
1087 if(2 * (rInfo.GetDashCount() + rInfo.GetDotCount()) > 10)
1088 {
1089 return false;
1090 }
1091 }
1092
1093 if(basegfx::B2DLineJoin::NONE != rInfo.GetLineJoin())
1094 {
1095 // LineJoin used, ExtLineInfo required
1096 return false;
1097 }
1098
1099 if(css::drawing::LineCap_BUTT != rInfo.GetLineCap())
1100 {
1101 // LineCap used, ExtLineInfo required
1102 return false;
1103 }
1104
1105 if( rInfo.GetStyle() == LineStyle::Dash )
1106 {
1107 rBuffer.append( "[ " );
1108 if( rInfo.GetDashLen() == rInfo.GetDotLen() ) // degraded case
1109 {
1110 appendMappedLength( rInfo.GetDashLen(), rBuffer );
1111 rBuffer.append( ' ' );
1112 appendMappedLength( rInfo.GetDistance(), rBuffer );
1113 rBuffer.append( ' ' );
1114 }
1115 else
1116 {
1117 for( int n = 0; n < rInfo.GetDashCount(); n++ )
1118 {
1119 appendMappedLength( rInfo.GetDashLen(), rBuffer );
1120 rBuffer.append( ' ' );
1121 appendMappedLength( rInfo.GetDistance(), rBuffer );
1122 rBuffer.append( ' ' );
1123 }
1124 for( int m = 0; m < rInfo.GetDotCount(); m++ )
1125 {
1126 appendMappedLength( rInfo.GetDotLen(), rBuffer );
1127 rBuffer.append( ' ' );
1128 appendMappedLength( rInfo.GetDistance(), rBuffer );
1129 rBuffer.append( ' ' );
1130 }
1131 }
1132 rBuffer.append( "] 0 d\n" );
1133 }
1134
1135 if( rInfo.GetWidth() > 1 )
1136 {
1137 appendMappedLength( rInfo.GetWidth(), rBuffer );
1138 rBuffer.append( " w\n" );
1139 }
1140 else if( rInfo.GetWidth() == 0 )
1141 {
1142 // "pixel" line
1143 appendDouble( 72.0/double(m_pWriter->GetDPIX()), rBuffer );
1144 rBuffer.append( " w\n" );
1145 }
1146
1147 return true;
1148 }
1149
appendWaveLine(sal_Int32 nWidth,sal_Int32 nY,sal_Int32 nDelta,OStringBuffer & rBuffer) const1150 void PDFPage::appendWaveLine( sal_Int32 nWidth, sal_Int32 nY, sal_Int32 nDelta, OStringBuffer& rBuffer ) const
1151 {
1152 if( nWidth <= 0 )
1153 return;
1154 if( nDelta < 1 )
1155 nDelta = 1;
1156
1157 rBuffer.append( "0 " );
1158 appendMappedLength( nY, rBuffer );
1159 rBuffer.append( " m\n" );
1160 for( sal_Int32 n = 0; n < nWidth; )
1161 {
1162 n += nDelta;
1163 appendMappedLength( n, rBuffer, false );
1164 rBuffer.append( ' ' );
1165 appendMappedLength( nDelta+nY, rBuffer );
1166 rBuffer.append( ' ' );
1167 n += nDelta;
1168 appendMappedLength( n, rBuffer, false );
1169 rBuffer.append( ' ' );
1170 appendMappedLength( nY, rBuffer );
1171 rBuffer.append( " v " );
1172 if( n < nWidth )
1173 {
1174 n += nDelta;
1175 appendMappedLength( n, rBuffer, false );
1176 rBuffer.append( ' ' );
1177 appendMappedLength( nY-nDelta, rBuffer );
1178 rBuffer.append( ' ' );
1179 n += nDelta;
1180 appendMappedLength( n, rBuffer, false );
1181 rBuffer.append( ' ' );
1182 appendMappedLength( nY, rBuffer );
1183 rBuffer.append( " v\n" );
1184 }
1185 }
1186 rBuffer.append( "S\n" );
1187 }
1188
appendMatrix3(Matrix3 const & rMatrix,OStringBuffer & rBuffer)1189 void PDFPage::appendMatrix3(Matrix3 const & rMatrix, OStringBuffer& rBuffer)
1190 {
1191 appendDouble(rMatrix.get(0), rBuffer);
1192 rBuffer.append(' ');
1193 appendDouble(rMatrix.get(1), rBuffer);
1194 rBuffer.append(' ');
1195 appendDouble(rMatrix.get(2), rBuffer);
1196 rBuffer.append(' ');
1197 appendDouble(rMatrix.get(3), rBuffer);
1198 rBuffer.append(' ');
1199 appendPoint(Point(tools::Long(rMatrix.get(4)), tools::Long(rMatrix.get(5))), rBuffer);
1200 }
1201
getHeight() const1202 double PDFPage::getHeight() const
1203 {
1204 double fRet = m_nPageHeight ? m_nPageHeight : 842; // default A4 height in inch/72, OK to use hardcoded value here?
1205
1206 if (m_nUserUnit > 1)
1207 {
1208 fRet /= m_nUserUnit;
1209 }
1210
1211 return fRet;
1212 }
1213
PDFWriterImpl(const PDFWriter::PDFWriterContext & rContext,const css::uno::Reference<css::beans::XMaterialHolder> & xEncryptionMaterialHolder,PDFWriter & i_rOuterFace)1214 PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext,
1215 const css::uno::Reference< css::beans::XMaterialHolder >& xEncryptionMaterialHolder,
1216 PDFWriter& i_rOuterFace)
1217 : VirtualDevice(Application::GetDefaultDevice(), DeviceFormat::WITHOUT_ALPHA, OUTDEV_PDF),
1218 m_aMapMode( MapUnit::MapPoint, Point(), Fraction( 1, pointToPixel(1) ), Fraction( 1, pointToPixel(1) ) ),
1219 m_aWidgetStyleSettings(Application::GetSettings().GetStyleSettings()),
1220 m_nCurrentStructElement( 0 ),
1221 m_bEmitStructure( true ),
1222 m_nNextFID( 1 ),
1223 m_aPDFBmpCache(comphelper::IsFuzzing() ? 15 :
1224 officecfg::Office::Common::VCL::PDFExportImageCacheSize::get()),
1225 m_nCurrentPage( -1 ),
1226 m_nCatalogObject(0),
1227 m_nSignatureObject( -1 ),
1228 m_nSignatureContentOffset( 0 ),
1229 m_nSignatureLastByteRangeNoOffset( 0 ),
1230 m_nResourceDict( -1 ),
1231 m_nFontDictObject( -1 ),
1232 m_aContext(rContext),
1233 m_aFile(m_aContext.URL),
1234 m_bOpen(false),
1235 m_DocDigest(::comphelper::HashType::MD5),
1236 m_rOuterFace( i_rOuterFace )
1237 {
1238 m_aStructure.emplace_back( );
1239 m_aStructure[0].m_nOwnElement = 0;
1240 m_aStructure[0].m_nParentElement = 0;
1241 //m_StructElementStack.push(0);
1242
1243 // tdf#150786 use the same settings for widgets regardless of theme
1244 m_aWidgetStyleSettings.SetStandardStyles();
1245
1246 GraphicsState aState;
1247 aState.m_aMapMode = m_aMapMode;
1248 aState.m_aFont.SetFamilyName( u"Times"_ustr );
1249 aState.m_aFont.SetFontSize( Size( 0, 12 ) );
1250
1251 m_aGraphicsStack.push_front( aState );
1252
1253 osl::File::RC aError = m_aFile.open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create);
1254 if (aError != osl::File::E_None)
1255 {
1256 if (aError == osl::File::E_EXIST)
1257 {
1258 aError = m_aFile.open(osl_File_OpenFlag_Write);
1259 if (aError == osl::File::E_None)
1260 aError = m_aFile.setSize(0);
1261 }
1262 }
1263 if (aError != osl::File::E_None)
1264 return;
1265
1266 m_bOpen = true;
1267
1268 // setup DocInfo
1269 setupDocInfo();
1270
1271 if (xEncryptionMaterialHolder.is())
1272 {
1273 if (m_aContext.Version == PDFWriter::PDFVersion::PDF_2_0 || m_aContext.Version == PDFWriter::PDFVersion::PDF_A_4)
1274 m_pPDFEncryptor.reset(new PDFEncryptorR6);
1275 else
1276 m_pPDFEncryptor.reset(new PDFEncryptor);
1277 m_pPDFEncryptor->prepareEncryption(xEncryptionMaterialHolder, m_aContext.Encryption);
1278 }
1279
1280 if (m_pPDFEncryptor && m_aContext.Encryption.canEncrypt())
1281 {
1282 m_pPDFEncryptor->setupKeysAndCheck(m_aContext.Encryption);
1283 }
1284
1285 // write header
1286 OStringBuffer aBuffer( 20 );
1287 aBuffer.append( "%PDF-" );
1288 aBuffer.append(getPDFVersionStr(m_aContext.Version));
1289 // append something binary as comment (suggested in PDF Reference)
1290 aBuffer.append( "\n%\303\244\303\274\303\266\303\237\n" );
1291 if( !writeBuffer( aBuffer ) )
1292 {
1293 m_aFile.close();
1294 m_bOpen = false;
1295 return;
1296 }
1297
1298 // insert outline root
1299 m_aOutline.emplace_back( );
1300
1301 switch (m_aContext.Version)
1302 {
1303 case PDFWriter::PDFVersion::PDF_A_1:
1304 m_nPDFA_Version = 1;
1305 m_bIsPDF_A1 = true;
1306 m_aContext.Version = PDFWriter::PDFVersion::PDF_1_4; //meaning we need PDF 1.4, PDF/A flavour
1307 break;
1308 case PDFWriter::PDFVersion::PDF_A_2:
1309 m_nPDFA_Version = 2;
1310 m_bIsPDF_A2 = true;
1311 m_aContext.Version = PDFWriter::PDFVersion::PDF_1_7;
1312 break;
1313 case PDFWriter::PDFVersion::PDF_A_3:
1314 m_nPDFA_Version = 3;
1315 m_bIsPDF_A3 = true;
1316 m_aContext.Version = PDFWriter::PDFVersion::PDF_1_7;
1317 break;
1318 case PDFWriter::PDFVersion::PDF_A_4:
1319 m_nPDFA_Version = 4;
1320 m_bIsPDF_A4 = true;
1321 m_aContext.Version = PDFWriter::PDFVersion::PDF_2_0;
1322 break;
1323 default:
1324 break;
1325 }
1326
1327 // PDF/UA can only be enabled if PDF version is 1.7 (PDF/UA-1) and 2.0 (PDF/UA-2)
1328 if (m_aContext.UniversalAccessibilityCompliance && m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_7)
1329 {
1330 m_bIsPDF_UA = true;
1331 m_aContext.Tagged = true;
1332 }
1333
1334 // Add common PDF 2.0 namespace when we are using PDF 2.0
1335 if (m_aContext.Version == PDFWriter::PDFVersion::PDF_2_0)
1336 {
1337 m_aNamespacesMap.emplace(constNamespacePDF2, createObject());
1338 }
1339
1340 if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 )
1341 SetReferenceDevice( VirtualDevice::RefDevMode::PDF1 );
1342 else
1343 SetReferenceDevice( m_aContext.DPIx, m_aContext.DPIy );
1344
1345 SetOutputSizePixel( Size( 640, 480 ) );
1346 SetMapMode(MapMode(MapUnit::MapMM));
1347 }
1348
~PDFWriterImpl()1349 PDFWriterImpl::~PDFWriterImpl()
1350 {
1351 disposeOnce();
1352 }
1353
dispose()1354 void PDFWriterImpl::dispose()
1355 {
1356 m_aPages.clear();
1357 VirtualDevice::dispose();
1358 }
1359
ImplNewFont() const1360 bool PDFWriterImpl::ImplNewFont() const
1361 {
1362 const ImplSVData* pSVData = ImplGetSVData();
1363
1364 if( mxFontCollection == pSVData->maGDIData.mxScreenFontList
1365 || mxFontCache == pSVData->maGDIData.mxScreenFontCache )
1366 {
1367 const_cast<vcl::PDFWriterImpl&>(*this).ImplUpdateFontData();
1368 }
1369
1370 return OutputDevice::ImplNewFont();
1371 }
1372
setupDocInfo()1373 void PDFWriterImpl::setupDocInfo()
1374 {
1375 std::vector< sal_uInt8 > aId;
1376 m_aCreationDateString = PDFWriter::GetDateTime();
1377 computeDocumentIdentifier(aId, m_aContext.DocumentInfo, m_aCreationDateString, m_aContext.DocumentInfo.ModificationDate, m_aCreationMetaDateString);
1378 if( m_aContext.Encryption.DocumentIdentifier.empty() )
1379 m_aContext.Encryption.DocumentIdentifier = std::move(aId);
1380 }
1381
GetDateTime(svl::crypto::SigningContext * pSigningContext)1382 OString PDFWriter::GetDateTime(svl::crypto::SigningContext* pSigningContext)
1383 {
1384 OStringBuffer aRet;
1385
1386 TimeValue aTVal, aGMT;
1387 oslDateTime aDT;
1388 osl_getSystemTime(&aGMT);
1389
1390 if (pSigningContext)
1391 {
1392 // The context unit is milliseconds, TimeValue is seconds + nanoseconds.
1393 if (pSigningContext->m_nSignatureTime)
1394 {
1395 aGMT = std::chrono::milliseconds(pSigningContext->m_nSignatureTime);
1396 }
1397 else
1398 {
1399 pSigningContext->m_nSignatureTime = static_cast<sal_Int64>(aGMT.Seconds) * 1000 + aGMT.Nanosec / 1000000;
1400 }
1401 }
1402
1403 osl_getLocalTimeFromSystemTime(&aGMT, &aTVal);
1404 osl_getDateTimeFromTimeValue(&aTVal, &aDT);
1405
1406 sal_Int32 nDelta = aTVal.Seconds-aGMT.Seconds;
1407
1408 appendPdfTimeDate(aRet, aDT.Year, aDT.Month, aDT.Day, aDT.Hours, aDT.Minutes, aDT.Seconds, nDelta);
1409
1410 aRet.append("'");
1411 return aRet.makeStringAndClear();
1412 }
1413
emitComment(const char * pComment)1414 void PDFWriterImpl::emitComment( const char* pComment )
1415 {
1416 OString aLine = OString::Concat("% ") + pComment + "\n";
1417 writeBuffer( aLine );
1418 }
1419
compressStream(SvMemoryStream * pStream)1420 bool PDFWriterImpl::compressStream( SvMemoryStream* pStream )
1421 {
1422 if (!g_bDebugDisableCompression)
1423 {
1424 sal_uInt64 nEndPos = pStream->TellEnd();
1425 pStream->Seek( STREAM_SEEK_TO_BEGIN );
1426 ZCodec aCodec( 0x4000, 0x4000 );
1427 SvMemoryStream aStream;
1428 aCodec.BeginCompression();
1429 aCodec.Write( aStream, static_cast<const sal_uInt8*>(pStream->GetData()), nEndPos );
1430 aCodec.EndCompression();
1431 nEndPos = aStream.Tell();
1432 pStream->Seek( STREAM_SEEK_TO_BEGIN );
1433 aStream.Seek( STREAM_SEEK_TO_BEGIN );
1434 pStream->SetStreamSize( nEndPos );
1435 pStream->WriteBytes( aStream.GetData(), nEndPos );
1436 return true;
1437 }
1438 else
1439 return false;
1440 }
1441
beginCompression()1442 void PDFWriterImpl::beginCompression()
1443 {
1444 if (!g_bDebugDisableCompression)
1445 {
1446 m_pCodec = std::make_unique<ZCodec>( 0x4000, 0x4000 );
1447 m_pMemStream = std::make_unique<SvMemoryStream>();
1448 m_pCodec->BeginCompression();
1449 }
1450 }
1451
endCompression()1452 void PDFWriterImpl::endCompression()
1453 {
1454 if (!g_bDebugDisableCompression && m_pCodec)
1455 {
1456 m_pCodec->EndCompression();
1457 m_pCodec.reset();
1458 sal_uInt64 nLen = m_pMemStream->Tell();
1459 m_pMemStream->Seek( 0 );
1460 (void)writeBufferBytes( m_pMemStream->GetData(), nLen );
1461 m_pMemStream.reset();
1462 }
1463 }
1464
writeBufferBytes(const void * pBuffer,sal_uInt64 nBytes)1465 bool PDFWriterImpl::writeBufferBytes( const void* pBuffer, sal_uInt64 nBytes )
1466 {
1467 if( ! m_bOpen ) // we are already down the drain
1468 return false;
1469
1470 if( ! nBytes ) // huh ?
1471 return true;
1472
1473 if( !m_aOutputStreams.empty() )
1474 {
1475 m_aOutputStreams.front().m_pStream->Seek( STREAM_SEEK_TO_END );
1476 m_aOutputStreams.front().m_pStream->WriteBytes(
1477 pBuffer, sal::static_int_cast<std::size_t>(nBytes));
1478 return true;
1479 }
1480
1481 sal_uInt64 nWritten;
1482 sal_uInt64 nActualSize = nBytes;
1483
1484 // we are compressing the stream
1485 if (m_pCodec)
1486 {
1487 m_pCodec->Write( *m_pMemStream, static_cast<const sal_uInt8*>(pBuffer), static_cast<sal_uLong>(nBytes) );
1488 nWritten = nBytes;
1489 }
1490 else
1491 {
1492 // is it encrypted?
1493 bool bStreamEncryption = m_pPDFEncryptor && m_pPDFEncryptor->isStreamEncryptionEnabled();
1494 if (bStreamEncryption)
1495 {
1496 nActualSize = m_pPDFEncryptor->calculateSizeIncludingHeader(nActualSize);
1497 m_vEncryptionBuffer.resize(nActualSize);
1498 m_pPDFEncryptor->encrypt(pBuffer, nBytes, m_vEncryptionBuffer, nActualSize);
1499 }
1500
1501 const void* pWriteBuffer = bStreamEncryption ? m_vEncryptionBuffer.data() : pBuffer;
1502 m_DocDigest.update(pWriteBuffer, sal_uInt32(nActualSize));
1503
1504
1505 if (m_aFile.write(pWriteBuffer, nActualSize, nWritten) != osl::File::E_None)
1506 nWritten = 0;
1507
1508 if (nWritten != nActualSize)
1509 {
1510 m_aFile.close();
1511 m_bOpen = false;
1512 return false;
1513 }
1514 return true;
1515 }
1516
1517 return nWritten == nActualSize;
1518 }
1519
newPage(double nPageWidth,double nPageHeight,PDFWriter::Orientation eOrientation)1520 void PDFWriterImpl::newPage( double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
1521 {
1522 endPage();
1523 m_nCurrentPage = m_aPages.size();
1524 m_aPages.emplace_back(this, nPageWidth, nPageHeight, eOrientation );
1525
1526 const Fraction frac(m_aPages.back().m_nUserUnit, pointToPixel(1));
1527 m_aMapMode = MapMode(MapUnit::MapPoint, Point(), frac, frac);
1528
1529 m_aPages.back().beginStream();
1530
1531 // setup global graphics state
1532 // linewidth is "1 pixel" by default
1533 OStringBuffer aBuf( 16 );
1534 appendDouble( 72.0/double(GetDPIX()), aBuf );
1535 aBuf.append( " w\n" );
1536 writeBuffer( aBuf );
1537 }
1538
endPage()1539 void PDFWriterImpl::endPage()
1540 {
1541 if( m_aPages.empty() )
1542 return;
1543
1544 // close eventual MC sequence
1545 endStructureElementMCSeq();
1546
1547 // sanity check
1548 if( !m_aOutputStreams.empty() )
1549 {
1550 OSL_FAIL( "redirection across pages !!!" );
1551 m_aOutputStreams.clear(); // leak !
1552 m_aMapMode.SetOrigin( Point() );
1553 }
1554
1555 m_aGraphicsStack.clear();
1556 m_aGraphicsStack.emplace_back( );
1557
1558 // this should pop the PDF graphics stack if necessary
1559 updateGraphicsState();
1560
1561 m_aPages.back().endStream();
1562
1563 // reset the default font
1564 Font aFont;
1565 aFont.SetFamilyName( u"Times"_ustr );
1566 aFont.SetFontSize( Size( 0, 12 ) );
1567
1568 m_aCurrentPDFState = m_aGraphicsStack.front();
1569 m_aGraphicsStack.front().m_aFont = std::move(aFont);
1570
1571 for (auto & bitmap : m_aBitmaps)
1572 {
1573 if( ! bitmap.m_aBitmap.IsEmpty() )
1574 {
1575 writeBitmapObject(bitmap);
1576 bitmap.m_aBitmap = Bitmap();
1577 }
1578 }
1579 for (auto & jpeg : m_aJPGs)
1580 {
1581 if( jpeg.m_pStream )
1582 {
1583 writeJPG( jpeg );
1584 jpeg.m_pStream.reset();
1585 jpeg.m_aAlphaMask = AlphaMask();
1586 }
1587 }
1588 for (auto & item : m_aTransparentObjects)
1589 {
1590 if( item.m_pContentStream )
1591 {
1592 writeTransparentObject(item);
1593 item.m_pContentStream.reset();
1594 }
1595 }
1596
1597 }
1598
createObject()1599 sal_Int32 PDFWriterImpl::createObject()
1600 {
1601 m_aObjects.push_back( ~0U );
1602 return m_aObjects.size();
1603 }
1604
updateObject(sal_Int32 n)1605 bool PDFWriterImpl::updateObject( sal_Int32 n )
1606 {
1607 if( ! m_bOpen )
1608 return false;
1609
1610 sal_uInt64 nOffset = ~0U;
1611 osl::File::RC aError = m_aFile.getPos(nOffset);
1612 SAL_WARN_IF( aError != osl::File::E_None, "vcl.pdfwriter", "could not register object" );
1613 if (aError != osl::File::E_None)
1614 {
1615 m_aFile.close();
1616 m_bOpen = false;
1617 }
1618 m_aObjects[ n-1 ] = nOffset;
1619 return aError == osl::File::E_None;
1620 }
1621
emitStructParentTree(sal_Int32 nObject)1622 sal_Int32 PDFWriterImpl::emitStructParentTree( sal_Int32 nObject )
1623 {
1624 if( nObject > 0 )
1625 {
1626 OStringBuffer aLine( 1024 );
1627
1628 aLine.append( OString::number(nObject)
1629 + " 0 obj\n"
1630 "<</Nums[\n" );
1631 sal_Int32 nTreeItems = m_aStructParentTree.size();
1632 for( sal_Int32 n = 0; n < nTreeItems; n++ )
1633 {
1634 aLine.append( OString::number(n) + " "
1635 + m_aStructParentTree[n]
1636 + "\n" );
1637 }
1638 aLine.append( "]>>\nendobj\n\n" );
1639 if (!updateObject(nObject))
1640 return 0;
1641 if (!writeBuffer(aLine))
1642 return 0;
1643 }
1644 return nObject;
1645 }
1646
1647 // every structure element already has a unique object id - just use it for ID
GenerateID(sal_Int32 const nObjectId)1648 static OString GenerateID(sal_Int32 const nObjectId)
1649 {
1650 return "id" + OString::number(nObjectId);
1651 }
1652
emitStructIDTree(sal_Int32 const nObject)1653 sal_Int32 PDFWriterImpl::emitStructIDTree(sal_Int32 const nObject)
1654 {
1655 // loosely following PDF 1.7, 10.6.5 Example of Logical Structure, Example 10.15
1656 if (nObject < 0)
1657 {
1658 return nObject;
1659 }
1660 // the name tree entries must be sorted lexicographically.
1661 std::map<OString, sal_Int32> ids;
1662 for (auto n : m_StructElemObjsWithID)
1663 {
1664 ids.emplace(GenerateID(n), n);
1665 }
1666 OStringBuffer buf;
1667 COSWriter aWriter(buf, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
1668 appendObjectID(nObject, buf);
1669 buf.append("<</Names [\n");
1670 for (auto const& it : ids)
1671 {
1672 aWriter.writeLiteralEncrypt(it.first, nObject);
1673 buf.append(" ");
1674 appendObjectReference(it.second, buf);
1675 buf.append("\n");
1676 }
1677 buf.append("] >>\nendobj\n\n");
1678
1679 if (!updateObject(nObject)) return 0;
1680 if (!writeBuffer(buf)) return 0;
1681
1682 return nObject;
1683 }
1684
getAttributeTag(PDFWriter::StructAttribute eAttr)1685 const char* PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr )
1686 {
1687 static constexpr auto aAttributeStrings = frozen::make_unordered_map<PDFWriter::StructAttribute, const char*>({
1688 { PDFWriter::Placement, "Placement" },
1689 { PDFWriter::WritingMode, "WritingMode" },
1690 { PDFWriter::SpaceBefore, "SpaceBefore" },
1691 { PDFWriter::SpaceAfter, "SpaceAfter" },
1692 { PDFWriter::StartIndent, "StartIndent" },
1693 { PDFWriter::EndIndent, "EndIndent" },
1694 { PDFWriter::TextIndent, "TextIndent" },
1695 { PDFWriter::TextAlign, "TextAlign" },
1696 { PDFWriter::Width, "Width" },
1697 { PDFWriter::Height, "Height" },
1698 { PDFWriter::BlockAlign, "BlockAlign" },
1699 { PDFWriter::InlineAlign, "InlineAlign" },
1700 { PDFWriter::LineHeight, "LineHeight" },
1701 { PDFWriter::BaselineShift, "BaselineShift" },
1702 { PDFWriter::TextDecorationType,"TextDecorationType" },
1703 { PDFWriter::ListNumbering, "ListNumbering" },
1704 { PDFWriter::RowSpan, "RowSpan" },
1705 { PDFWriter::ColSpan, "ColSpan" },
1706 { PDFWriter::Scope, "Scope" },
1707 { PDFWriter::Role, "Role" },
1708 { PDFWriter::RubyAlign, "RubyAlign" },
1709 { PDFWriter::RubyPosition, "RubyPosition" },
1710 { PDFWriter::Type, "Type" },
1711 { PDFWriter::Subtype, "Subtype" },
1712 { PDFWriter::LinkAnnotation, "LinkAnnotation" },
1713 { PDFWriter::NoteAnnotation, "NoteAnnotation" }
1714 });
1715
1716 auto it = aAttributeStrings.find( eAttr );
1717
1718 if( it == aAttributeStrings.end() )
1719 SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttribute " << eAttr);
1720
1721 return it != aAttributeStrings.end() ? it->second : "";
1722 }
1723
getAttributeValueTag(PDFWriter::StructAttributeValue eVal)1724 const char* PDFWriterImpl::getAttributeValueTag( PDFWriter::StructAttributeValue eVal )
1725 {
1726 static constexpr auto aValueStrings = frozen::make_unordered_map<PDFWriter::StructAttributeValue, const char*>({
1727 { PDFWriter::NONE, "None" },
1728 { PDFWriter::Block, "Block" },
1729 { PDFWriter::Inline, "Inline" },
1730 { PDFWriter::Before, "Before" },
1731 { PDFWriter::After, "After" },
1732 { PDFWriter::Start, "Start" },
1733 { PDFWriter::End, "End" },
1734 { PDFWriter::LrTb, "LrTb" },
1735 { PDFWriter::RlTb, "RlTb" },
1736 { PDFWriter::TbRl, "TbRl" },
1737 { PDFWriter::Center, "Center" },
1738 { PDFWriter::Justify, "Justify" },
1739 { PDFWriter::Auto, "Auto" },
1740 { PDFWriter::Middle, "Middle" },
1741 { PDFWriter::Normal, "Normal" },
1742 { PDFWriter::Underline, "Underline" },
1743 { PDFWriter::Overline, "Overline" },
1744 { PDFWriter::LineThrough,"LineThrough" },
1745 { PDFWriter::Row, "Row" },
1746 { PDFWriter::Column, "Column" },
1747 { PDFWriter::Both, "Both" },
1748 { PDFWriter::Pagination, "Pagination" },
1749 { PDFWriter::Layout, "Layout" },
1750 { PDFWriter::Page, "Page" },
1751 { PDFWriter::Background, "Background" },
1752 { PDFWriter::Header, "Header" },
1753 { PDFWriter::Footer, "Footer" },
1754 { PDFWriter::Watermark, "Watermark" },
1755 { PDFWriter::Rb, "rb" },
1756 { PDFWriter::Cb, "cb" },
1757 { PDFWriter::Pb, "pb" },
1758 { PDFWriter::Tv, "tv" },
1759 { PDFWriter::RStart, "Start" },
1760 { PDFWriter::RCenter, "Center" },
1761 { PDFWriter::REnd, "End" },
1762 { PDFWriter::RJustify, "Justify" },
1763 { PDFWriter::RDistribute,"Distribute" },
1764 { PDFWriter::RBefore, "Before" },
1765 { PDFWriter::RAfter, "After" },
1766 { PDFWriter::RWarichu, "Warichu" },
1767 { PDFWriter::RInline, "Inline" },
1768 { PDFWriter::Disc, "Disc" },
1769 { PDFWriter::Circle, "Circle" },
1770 { PDFWriter::Square, "Square" },
1771 { PDFWriter::Decimal, "Decimal" },
1772 { PDFWriter::UpperRoman, "UpperRoman" },
1773 { PDFWriter::LowerRoman, "LowerRoman" },
1774 { PDFWriter::UpperAlpha, "UpperAlpha" },
1775 { PDFWriter::LowerAlpha, "LowerAlpha" }
1776 });
1777
1778 auto it = aValueStrings.find( eVal );
1779
1780 if( it == aValueStrings.end() )
1781 SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttributeValue " << eVal);
1782
1783 return it != aValueStrings.end() ? it->second : "";
1784 }
1785
appendStructureAttributeLine(PDFWriter::StructAttribute i_eAttr,const PDFStructureAttribute & i_rVal,OStringBuffer & o_rLine,bool i_bIsFixedInt)1786 static void appendStructureAttributeLine( PDFWriter::StructAttribute i_eAttr, const PDFStructureAttribute& i_rVal, OStringBuffer& o_rLine, bool i_bIsFixedInt )
1787 {
1788 o_rLine.append( "/" );
1789 o_rLine.append( PDFWriterImpl::getAttributeTag( i_eAttr ) );
1790
1791 if( i_rVal.eValue != PDFWriter::Invalid )
1792 {
1793 o_rLine.append( "/" );
1794 o_rLine.append( PDFWriterImpl::getAttributeValueTag( i_rVal.eValue ) );
1795 }
1796 else
1797 {
1798 // numerical value
1799 o_rLine.append( " " );
1800 if( i_bIsFixedInt )
1801 appendFixedInt( i_rVal.nValue, o_rLine );
1802 else
1803 o_rLine.append( i_rVal.nValue );
1804 }
1805 o_rLine.append( "\n" );
1806 }
1807
1808 template<typename T>
AppendAnnotKid(PDFStructureElement & i_rEle,T & rAnnot)1809 void PDFWriterImpl::AppendAnnotKid(PDFStructureElement& i_rEle, T & rAnnot)
1810 {
1811 // update struct parent of link
1812 OString const aStructParentEntry(OString::number(i_rEle.m_nObject) + " 0 R");
1813 m_aStructParentTree.push_back( aStructParentEntry );
1814 rAnnot.m_nStructParent = m_aStructParentTree.size()-1;
1815 sal_Int32 const nAnnotObj(rAnnot.m_nObject);
1816 i_rEle.m_aKids.emplace_back(ObjReferenceObj{nAnnotObj});
1817 }
1818
emitStructureAttributes(PDFStructureElement & i_rEle)1819 OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle )
1820 {
1821 // create layout, list and table attribute sets
1822 OStringBuffer aLayout(256), aList(64), aTable(64);
1823 OStringBuffer aPrintField;
1824 for (auto const& attribute : i_rEle.m_aAttributes)
1825 {
1826 if( attribute.first == PDFWriter::ListNumbering )
1827 appendStructureAttributeLine( attribute.first, attribute.second, aList, true );
1828 else if (attribute.first == PDFWriter::Role)
1829 {
1830 appendStructureAttributeLine(attribute.first, attribute.second, aPrintField, true);
1831 }
1832 else if( attribute.first == PDFWriter::RowSpan ||
1833 attribute.first == PDFWriter::ColSpan ||
1834 attribute.first == PDFWriter::Scope)
1835 {
1836 appendStructureAttributeLine( attribute.first, attribute.second, aTable, false );
1837 }
1838 else if( attribute.first == PDFWriter::LinkAnnotation )
1839 {
1840 sal_Int32 nLink = attribute.second.nValue;
1841 std::map< sal_Int32, sal_Int32 >::const_iterator link_it =
1842 m_aLinkPropertyMap.find( nLink );
1843 if( link_it != m_aLinkPropertyMap.end() )
1844 nLink = link_it->second;
1845 if( nLink >= 0 && o3tl::make_unsigned(nLink) < m_aLinks.size() )
1846 {
1847 AppendAnnotKid(i_rEle, m_aLinks[nLink]);
1848 }
1849 else
1850 {
1851 OSL_FAIL( "unresolved link id for Link structure" );
1852 SAL_INFO("vcl.pdfwriter", "unresolved link id " << nLink << " for Link structure");
1853 if (g_bDebugDisableCompression)
1854 {
1855 OString aLine = "unresolved link id " +
1856 OString::number( nLink ) +
1857 " for Link structure";
1858 emitComment( aLine.getStr() );
1859 }
1860 }
1861 }
1862 else if (attribute.first == PDFWriter::NoteAnnotation)
1863 {
1864 sal_Int32 nNote = attribute.second.nValue;
1865 std::map<sal_Int32, sal_Int32>::const_iterator link_it = m_aLinkPropertyMap.find(nNote);
1866 if (link_it != m_aLinkPropertyMap.end())
1867 nNote = link_it->second;
1868 if (nNote >= 0 && o3tl::make_unsigned(nNote) < m_aNotes.size())
1869 {
1870 AppendAnnotKid(i_rEle, m_aNotes[nNote]);
1871 }
1872 else
1873 {
1874 OSL_FAIL("unresolved note id for Note structure");
1875 SAL_INFO("vcl.pdfwriter", "unresolved note id " << nNote << " for Note structure");
1876 if (g_bDebugDisableCompression)
1877 {
1878 OString aLine
1879 = "unresolved note id " + OString::number(nNote) + " for Note structure";
1880 emitComment(aLine.getStr());
1881 }
1882 }
1883 }
1884 else
1885 appendStructureAttributeLine( attribute.first, attribute.second, aLayout, true );
1886 }
1887 if( ! i_rEle.m_aBBox.IsEmpty() )
1888 {
1889 aLayout.append( "/BBox[" );
1890 appendFixedInt( i_rEle.m_aBBox.Left(), aLayout );
1891 aLayout.append( " " );
1892 appendFixedInt( i_rEle.m_aBBox.Top(), aLayout );
1893 aLayout.append( " " );
1894 appendFixedInt( i_rEle.m_aBBox.Right(), aLayout );
1895 aLayout.append( " " );
1896 appendFixedInt( i_rEle.m_aBBox.Bottom(), aLayout );
1897 aLayout.append( "]\n" );
1898 }
1899
1900 OStringBuffer aRet(256);
1901 bool isArray(false);
1902 if (1 < (aLayout.isEmpty() ? 0 : 1) + (aList.isEmpty() ? 0 : 1)
1903 + (aPrintField.isEmpty() ? 0 : 1) + (aTable.isEmpty() ? 0 : 1))
1904 {
1905 isArray = true;
1906 aRet.append(" [");
1907 }
1908 auto const WriteAttrs = [&](char const*const pName, OStringBuffer & rBuf)
1909 {
1910 aRet.append(" <</O");
1911 aRet.append(pName);
1912 aRet.append(rBuf);
1913 aRet.append(">>");
1914 };
1915 if( !aLayout.isEmpty() )
1916 {
1917 WriteAttrs("/Layout", aLayout);
1918 }
1919 if( !aList.isEmpty() )
1920 {
1921 WriteAttrs("/List", aList);
1922 }
1923 if (!aPrintField.isEmpty())
1924 {
1925 WriteAttrs("/PrintField", aPrintField);
1926 }
1927 if( !aTable.isEmpty() )
1928 {
1929 WriteAttrs("/Table", aTable);
1930 }
1931
1932 if (isArray)
1933 {
1934 aRet.append( " ]" );
1935 }
1936 return aRet.makeStringAndClear();
1937 }
1938
1939 // Write the namespace objects to the stream
emitNamespaces()1940 void PDFWriterImpl::emitNamespaces()
1941 {
1942 if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0)
1943 return;
1944
1945 for (auto&[sNamespace, nObject] : m_aNamespacesMap)
1946 {
1947 if (!updateObject(nObject))
1948 return;
1949
1950 COSWriter aWriter(m_aContext.Encryption.getParams(), m_pPDFEncryptor);
1951 aWriter.startObject(nObject);
1952 aWriter.startDict();
1953 aWriter.write("/Type", "/Namespace");
1954 aWriter.writeKeyAndLiteral("/NS", sNamespace);
1955 aWriter.endDict();
1956 aWriter.endObject();
1957
1958 if (!writeBuffer(aWriter.getLine()))
1959 m_aNamespacesMap[sNamespace] = 0;
1960 }
1961 }
1962
emitStructure(PDFStructureElement & rEle)1963 sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle )
1964 {
1965 assert(rEle.m_nOwnElement == 0 || rEle.m_oType);
1966 if (rEle.m_nOwnElement != rEle.m_nParentElement // emit the struct tree root
1967 // do not emit NonStruct and its children
1968 && *rEle.m_oType == vcl::pdf::StructElement::NonStructElement)
1969 {
1970 return 0;
1971 }
1972
1973 for (auto const& child : rEle.m_aChildren)
1974 {
1975 if( child > 0 && o3tl::make_unsigned(child) < m_aStructure.size() )
1976 {
1977 PDFStructureElement& rChild = m_aStructure[ child ];
1978 if (*rChild.m_oType != vcl::pdf::StructElement::NonStructElement)
1979 {
1980 if( rChild.m_nParentElement == rEle.m_nOwnElement )
1981 emitStructure( rChild );
1982 else
1983 {
1984 OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure element" );
1985 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure element with id " << child);
1986 }
1987 }
1988 }
1989 else
1990 {
1991 OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
1992 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure id " << child);
1993 }
1994 }
1995
1996 OStringBuffer aLine( 512 );
1997 COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
1998 aLine.append(
1999 OString::number(rEle.m_nObject)
2000 + " 0 obj\n"
2001 "<</Type" );
2002 sal_Int32 nParentTree = -1;
2003 sal_Int32 nIDTree = -1;
2004 if( rEle.m_nOwnElement == rEle.m_nParentElement )
2005 {
2006 nParentTree = createObject();
2007 if (!nParentTree)
2008 return 0;
2009 aLine.append("/StructTreeRoot\n");
2010 aWriter.writeKeyAndReference("/ParentTree", nParentTree);
2011
2012 // Write the reference to the PDF 2.0 namespace
2013 if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0)
2014 {
2015 auto iterator = m_aNamespacesMap.find(constNamespacePDF2);
2016 if (iterator != m_aNamespacesMap.end())
2017 {
2018 aLine.append("/Namespaces [");
2019 aWriter.writeReference(iterator->second);
2020 aLine.append("]");
2021 }
2022 }
2023
2024 if( ! m_aRoleMap.empty() )
2025 {
2026 aLine.append( "/RoleMap<<" );
2027 for (auto const& role : m_aRoleMap)
2028 {
2029 aLine.append( "/" + role.first + "/" + role.second + "\n" );
2030 }
2031 aLine.append( ">>\n" );
2032 }
2033 if (!m_StructElemObjsWithID.empty())
2034 {
2035 nIDTree = createObject();
2036 aLine.append("/IDTree ");
2037 appendObjectReference(nIDTree, aLine);
2038 aLine.append("\n");
2039 }
2040 }
2041 else
2042 {
2043 aLine.append("/StructElem");
2044
2045 // Write the reference to the PDF 2.0 namespace
2046 if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0)
2047 {
2048 auto iterator = m_aNamespacesMap.find(constNamespacePDF2);
2049 if (iterator != m_aNamespacesMap.end())
2050 aWriter.writeKeyAndReference("/NS", iterator->second);
2051 }
2052 aLine.append("/S/");
2053 if( !rEle.m_aAlias.isEmpty() )
2054 aLine.append( rEle.m_aAlias );
2055 else
2056 aLine.append( getStructureTag(*rEle.m_oType) );
2057 if (m_StructElemObjsWithID.find(rEle.m_nObject) != m_StructElemObjsWithID.end())
2058 {
2059 aLine.append("\n/ID ");
2060 aWriter.writeLiteralEncrypt(GenerateID(rEle.m_nObject), rEle.m_nObject);
2061 }
2062 aLine.append(
2063 "\n"
2064 "/P "
2065 + OString::number(m_aStructure[ rEle.m_nParentElement ].m_nObject)
2066 + " 0 R\n"
2067 "/Pg "
2068 + OString::number(rEle.m_nFirstPageObject)
2069 + " 0 R\n" );
2070 if( !rEle.m_aActualText.isEmpty() )
2071 {
2072 aLine.append( "/ActualText" );
2073 aWriter.writeUnicodeEncrypt(rEle.m_aActualText, rEle.m_nObject);
2074 aLine.append( "\n" );
2075 }
2076 if( !rEle.m_aAltText.isEmpty() )
2077 {
2078 aLine.append( "/Alt" );
2079 aWriter.writeUnicodeEncrypt(rEle.m_aAltText, rEle.m_nObject);
2080 aLine.append( "\n" );
2081 }
2082 }
2083 if( (! rEle.m_aBBox.IsEmpty()) || (! rEle.m_aAttributes.empty()) )
2084 {
2085 OString aAttribs = emitStructureAttributes( rEle );
2086 if( !aAttribs.isEmpty() )
2087 {
2088 aLine.append( "/A" + aAttribs + "\n" );
2089 }
2090 }
2091 if( !rEle.m_aLocale.Language.isEmpty() )
2092 {
2093 /* PDF allows only RFC 3066, which is only partly BCP 47 and does not
2094 * include script tags and others.
2095 * http://pdf.editme.com/pdfua-naturalLanguageSpecification
2096 * http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf#page=886
2097 * https://www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf#M13.9.19332.1Heading.97.Natural.Language.Specification
2098 * */
2099 LanguageTag aLanguageTag( rEle.m_aLocale);
2100 OUString aLanguage, aScript, aCountry;
2101 aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
2102 if (!aLanguage.isEmpty())
2103 {
2104 OUStringBuffer aLocBuf( 16 );
2105 aLocBuf.append( aLanguage );
2106 if( !aCountry.isEmpty() )
2107 {
2108 aLocBuf.append( "-" + aCountry );
2109 }
2110 aLine.append( "/Lang" );
2111 aWriter.writeLiteralEncrypt(aLocBuf.makeStringAndClear(), rEle.m_nObject);
2112 aLine.append( "\n" );
2113 }
2114 }
2115 if (!rEle.m_AnnotIds.empty())
2116 {
2117 for (auto const id : rEle.m_AnnotIds)
2118 {
2119 auto const it(m_aLinkPropertyMap.find(id));
2120 assert(it != m_aLinkPropertyMap.end());
2121
2122 if (*rEle.m_oType == vcl::pdf::StructElement::Form)
2123 {
2124 assert(0 <= it->second && o3tl::make_unsigned(it->second) < m_aWidgets.size());
2125 AppendAnnotKid(rEle, m_aWidgets[it->second]);
2126 }
2127 else
2128 {
2129 assert(0 <= it->second && o3tl::make_unsigned(it->second) < m_aScreens.size());
2130 AppendAnnotKid(rEle, m_aScreens[it->second]);
2131 }
2132 }
2133 }
2134 if( ! rEle.m_aKids.empty() )
2135 {
2136 unsigned int i = 0;
2137 aLine.append( "/K[" );
2138 for (auto const& rKid : rEle.m_aKids)
2139 {
2140 if (std::holds_alternative<ObjReference>(rKid))
2141 {
2142 ObjReference const& rObj(std::get<ObjReference>(rKid));
2143 appendObjectReference(rObj.nObject, aLine);
2144 aLine.append( ( (i & 15) == 15 ) ? "\n" : " " );
2145 }
2146 else if (std::holds_alternative<ObjReferenceObj>(rKid))
2147 {
2148 ObjReferenceObj const& rObj(std::get<ObjReferenceObj>(rKid));
2149 aLine.append("<</Type/OBJR/Obj ");
2150 appendObjectReference(rObj.nObject, aLine);
2151 aLine.append(">>\n");
2152 }
2153 else
2154 {
2155 assert(std::holds_alternative<MCIDReference>(rKid));
2156 MCIDReference const& rMCID(std::get<MCIDReference>(rKid));
2157 if (rMCID.nPageObj == rEle.m_nFirstPageObject)
2158 {
2159 aLine.append(OString::number(rMCID.nMCID) + " ");
2160 }
2161 else
2162 {
2163 aLine.append("<</Type/MCR/Pg ");
2164 appendObjectReference(rMCID.nPageObj, aLine);
2165 aLine.append(" /MCID " + OString::number(rMCID.nMCID) + ">>\n");
2166 }
2167 }
2168 ++i;
2169 }
2170 aLine.append( "]\n" );
2171 }
2172 aLine.append( ">>\nendobj\n\n" );
2173
2174 if (!updateObject(rEle.m_nObject)) return 0;
2175 if (!writeBuffer(aLine)) return 0;
2176
2177 if (!emitStructParentTree(nParentTree)) return 0;
2178 if (!emitStructIDTree(nIDTree)) return 0;
2179
2180 return rEle.m_nObject;
2181 }
2182
emitGradients()2183 bool PDFWriterImpl::emitGradients()
2184 {
2185 for (auto const& gradient : m_aGradients)
2186 {
2187 if ( !writeGradientFunction( gradient ) ) return false;
2188 }
2189 return true;
2190 }
2191
emitTilings()2192 bool PDFWriterImpl::emitTilings()
2193 {
2194 OStringBuffer aTilingObj( 1024 );
2195
2196 for (auto & tiling : m_aTilings)
2197 {
2198 SAL_WARN_IF( !tiling.m_pTilingStream, "vcl.pdfwriter", "tiling without stream" );
2199 if( ! tiling.m_pTilingStream )
2200 continue;
2201
2202 aTilingObj.setLength( 0 );
2203
2204 if (g_bDebugDisableCompression)
2205 {
2206 emitComment( "PDFWriterImpl::emitTilings" );
2207 }
2208
2209 sal_Int32 nX = static_cast<sal_Int32>(tiling.m_aRectangle.Left());
2210 sal_Int32 nY = static_cast<sal_Int32>(tiling.m_aRectangle.Top());
2211 sal_Int32 nW = static_cast<sal_Int32>(tiling.m_aRectangle.GetWidth());
2212 sal_Int32 nH = static_cast<sal_Int32>(tiling.m_aRectangle.GetHeight());
2213 if( tiling.m_aCellSize.Width() == 0 )
2214 tiling.m_aCellSize.setWidth( nW );
2215 if( tiling.m_aCellSize.Height() == 0 )
2216 tiling.m_aCellSize.setHeight( nH );
2217
2218 bool bDeflate = compressStream( tiling.m_pTilingStream.get() );
2219 sal_uInt64 const nTilingStreamSize = tiling.m_pTilingStream->TellEnd();
2220 tiling.m_pTilingStream->Seek( STREAM_SEEK_TO_BEGIN );
2221
2222 // write pattern object
2223 aTilingObj.append(
2224 OString::number(tiling.m_nObject)
2225 + " 0 obj\n"
2226 "<</Type/Pattern/PatternType 1\n"
2227 "/PaintType 1\n"
2228 "/TilingType 2\n"
2229 "/BBox[" );
2230 appendFixedInt( nX, aTilingObj );
2231 aTilingObj.append( ' ' );
2232 appendFixedInt( nY, aTilingObj );
2233 aTilingObj.append( ' ' );
2234 appendFixedInt( nX+nW, aTilingObj );
2235 aTilingObj.append( ' ' );
2236 appendFixedInt( nY+nH, aTilingObj );
2237 aTilingObj.append( "]\n"
2238 "/XStep " );
2239 appendFixedInt( tiling.m_aCellSize.Width(), aTilingObj );
2240 aTilingObj.append( "\n"
2241 "/YStep " );
2242 appendFixedInt( tiling.m_aCellSize.Height(), aTilingObj );
2243 aTilingObj.append( "\n" );
2244 if( tiling.m_aTransform.matrix[0] != 1.0 ||
2245 tiling.m_aTransform.matrix[1] != 0.0 ||
2246 tiling.m_aTransform.matrix[3] != 0.0 ||
2247 tiling.m_aTransform.matrix[4] != 1.0 ||
2248 tiling.m_aTransform.matrix[2] != 0.0 ||
2249 tiling.m_aTransform.matrix[5] != 0.0 )
2250 {
2251 aTilingObj.append( "/Matrix [" );
2252 // TODO: scaling, mirroring on y, etc
2253 appendDouble( tiling.m_aTransform.matrix[0], aTilingObj );
2254 aTilingObj.append( ' ' );
2255 appendDouble( tiling.m_aTransform.matrix[1], aTilingObj );
2256 aTilingObj.append( ' ' );
2257 appendDouble( tiling.m_aTransform.matrix[3], aTilingObj );
2258 aTilingObj.append( ' ' );
2259 appendDouble( tiling.m_aTransform.matrix[4], aTilingObj );
2260 aTilingObj.append( ' ' );
2261 appendDouble( tiling.m_aTransform.matrix[2], aTilingObj );
2262 aTilingObj.append( ' ' );
2263 appendDouble( tiling.m_aTransform.matrix[5], aTilingObj );
2264 aTilingObj.append( "]\n" );
2265 }
2266 aTilingObj.append( "/Resources" );
2267 tiling.m_aResources.append(aTilingObj, getFontDictObject(), m_aContext.Version);
2268 if( bDeflate )
2269 aTilingObj.append( "/Filter/FlateDecode" );
2270 aTilingObj.append( "/Length "
2271 + OString::number(static_cast<sal_Int32>(nTilingStreamSize))
2272 + ">>\nstream\n" );
2273 if ( !updateObject( tiling.m_nObject ) ) return false;
2274 if ( !writeBuffer( aTilingObj ) ) return false;
2275 checkAndEnableStreamEncryption( tiling.m_nObject );
2276 bool written = writeBufferBytes( tiling.m_pTilingStream->GetData(), nTilingStreamSize );
2277 tiling.m_pTilingStream.reset();
2278 if( !written )
2279 return false;
2280 disableStreamEncryption();
2281 aTilingObj.setLength( 0 );
2282 aTilingObj.append( "\nendstream\nendobj\n\n" );
2283 if ( !writeBuffer( aTilingObj ) ) return false;
2284 }
2285 return true;
2286 }
2287
emitBuildinFont(const pdf::BuildinFontFace * pFD,sal_Int32 nFontObject)2288 sal_Int32 PDFWriterImpl::emitBuildinFont(const pdf::BuildinFontFace* pFD, sal_Int32 nFontObject)
2289 {
2290 if( !pFD )
2291 return 0;
2292 const pdf::BuildinFont& rBuildinFont = pFD->GetBuildinFont();
2293
2294 OStringBuffer aLine( 1024 );
2295
2296 if( nFontObject <= 0 )
2297 nFontObject = createObject();
2298 if (!updateObject(nFontObject))
2299 return 0;
2300 aLine.append(
2301 OString::number(nFontObject)
2302 + " 0 obj\n"
2303 "<</Type/Font/Subtype/Type1/BaseFont/" );
2304 COSWriter::appendName( rBuildinFont.m_pPSName, aLine );
2305 aLine.append( "\n" );
2306 if( rBuildinFont.m_eCharSet == RTL_TEXTENCODING_MS_1252 )
2307 aLine.append( "/Encoding/WinAnsiEncoding\n" );
2308 aLine.append( ">>\nendobj\n\n" );
2309 if (!writeBuffer(aLine))
2310 return 0;
2311 return nFontObject;
2312 }
2313
2314 namespace
2315 {
2316 // Translate units from TT to PS (standard 1/1000)
XUnits(int nUPEM,int n)2317 int XUnits(int nUPEM, int n) { return (n * 1000) / nUPEM; }
2318 }
2319
emitSystemFont(const vcl::font::PhysicalFontFace * pFace,EmbedFont const & rEmbed)2320 std::map< sal_Int32, sal_Int32 > PDFWriterImpl::emitSystemFont( const vcl::font::PhysicalFontFace* pFace, EmbedFont const & rEmbed )
2321 {
2322 std::map< sal_Int32, sal_Int32 > aRet;
2323
2324 if (g_bDebugDisableCompression)
2325 emitComment("PDFWriterImpl::emitSystemFont");
2326
2327 FontSubsetInfo aInfo;
2328 // fill in dummy values
2329 aInfo.m_nAscent = 1000;
2330 aInfo.m_nDescent = 200;
2331 aInfo.m_nCapHeight = 1000;
2332 aInfo.m_aFontBBox = tools::Rectangle( Point( -200, -200 ), Size( 1700, 1700 ) );
2333 aInfo.m_aPSName = pFace->GetFamilyName();
2334
2335 sal_Int32 pWidths[256] = { 0 };
2336 const LogicalFontInstance* pFontInstance = rEmbed.m_pFontInstance;
2337 auto nUPEM = pFace->UnitsPerEm();
2338 for( sal_Ucs c = 32; c < 256; c++ )
2339 {
2340 sal_GlyphId nGlyph = pFontInstance->GetGlyphIndex(c);
2341 pWidths[c] = XUnits(nUPEM, pFontInstance->GetGlyphWidth(nGlyph, false, false));
2342 }
2343
2344 // We are interested only in filling aInfo
2345 sal_GlyphId aGlyphIds[] = { 0 };
2346 sal_uInt8 pEncoding[] = { 0 };
2347 std::vector<sal_uInt8> aBuffer;
2348 pFace->CreateFontSubset(aBuffer, aGlyphIds, pEncoding, 1, aInfo);
2349
2350 // write font descriptor
2351 sal_Int32 nFontDescriptor = emitFontDescriptor( pFace, aInfo, 0, 0 );
2352 if( nFontDescriptor )
2353 {
2354 // write font object
2355 sal_Int32 nObject = createObject();
2356 if( updateObject( nObject ) )
2357 {
2358 OStringBuffer aLine( 1024 );
2359 aLine.append(
2360 OString::number(nObject)
2361 + " 0 obj\n"
2362 "<</Type/Font/Subtype/TrueType"
2363 "/BaseFont/" );
2364 COSWriter::appendName( aInfo.m_aPSName, aLine );
2365 aLine.append( "\n" );
2366 if (!pFace->IsMicrosoftSymbolEncoded())
2367 aLine.append( "/Encoding/WinAnsiEncoding\n" );
2368 aLine.append( "/FirstChar 32 /LastChar 255\n"
2369 "/Widths[" );
2370 for( int i = 32; i < 256; i++ )
2371 {
2372 aLine.append( pWidths[i] );
2373 aLine.append( ((i&15) == 15) ? "\n" : " " );
2374 }
2375 aLine.append( "]\n"
2376 "/FontDescriptor "
2377 + OString::number( nFontDescriptor )
2378 + " 0 R>>\n"
2379 "endobj\n\n" );
2380 writeBuffer( aLine );
2381
2382 aRet[ rEmbed.m_nNormalFontID ] = nObject;
2383 }
2384 }
2385
2386 return aRet;
2387 }
2388
2389 namespace
2390 {
fillSubsetArrays(const FontEmit & rSubset,sal_GlyphId * pGlyphIds,sal_Int32 * pWidths,sal_uInt8 * pEncoding,sal_Int32 * pEncToUnicodeIndex,sal_Int32 * pCodeUnitsPerGlyph,std::vector<sal_Ucs> & rCodeUnits,sal_Int32 & nToUnicodeStream)2391 uint32_t fillSubsetArrays(const FontEmit& rSubset, sal_GlyphId* pGlyphIds, sal_Int32* pWidths,
2392 sal_uInt8* pEncoding, sal_Int32* pEncToUnicodeIndex,
2393 sal_Int32* pCodeUnitsPerGlyph, std::vector<sal_Ucs>& rCodeUnits,
2394 sal_Int32& nToUnicodeStream)
2395 {
2396 rCodeUnits.reserve(256);
2397
2398 // if it gets used then it will appear in s_subset.m_aMapping, otherwise 0 is fine
2399 pWidths[0] = 0;
2400
2401 uint32_t nGlyphs = 1;
2402 for (auto const& item : rSubset.m_aMapping)
2403 {
2404 sal_uInt8 nEnc = item.second.getGlyphId();
2405
2406 SAL_WARN_IF(pGlyphIds[nEnc] != 0 || pEncoding[nEnc] != 0, "vcl.pdfwriter",
2407 "duplicate glyph");
2408 SAL_WARN_IF(nEnc > rSubset.m_aMapping.size(), "vcl.pdfwriter", "invalid glyph encoding");
2409
2410 pGlyphIds[nEnc] = item.first;
2411 pEncoding[nEnc] = nEnc;
2412 pEncToUnicodeIndex[nEnc] = static_cast<sal_Int32>(rCodeUnits.size());
2413 pCodeUnitsPerGlyph[nEnc] = item.second.countCodes();
2414 pWidths[nEnc] = item.second.getGlyphWidth();
2415 for (sal_Int32 n = 0; n < pCodeUnitsPerGlyph[nEnc]; n++)
2416 rCodeUnits.push_back(item.second.getCode(n));
2417 if (item.second.getCode(0))
2418 nToUnicodeStream = 1;
2419 if (nGlyphs < 256)
2420 nGlyphs++;
2421 else
2422 OSL_FAIL("too many glyphs for subset");
2423 }
2424
2425 return nGlyphs;
2426 }
2427 }
2428
emitType3Font(const vcl::font::PhysicalFontFace * pFace,const FontSubset & rType3Font,std::map<sal_Int32,sal_Int32> & rFontIDToObject)2429 bool PDFWriterImpl::emitType3Font(const vcl::font::PhysicalFontFace* pFace,
2430 const FontSubset& rType3Font,
2431 std::map<sal_Int32, sal_Int32>& rFontIDToObject)
2432 {
2433 if (g_bDebugDisableCompression)
2434 emitComment("PDFWriterImpl::emitType3Font");
2435
2436 const auto& rColorPalettes = pFace->GetColorPalettes();
2437
2438 FontSubsetInfo aSubsetInfo;
2439 sal_GlyphId pTempGlyphIds[] = { 0 };
2440 sal_uInt8 pTempEncoding[] = { 0 };
2441 std::vector<sal_uInt8> aBuffer;
2442 pFace->CreateFontSubset(aBuffer, pTempGlyphIds, pTempEncoding, 1, aSubsetInfo);
2443
2444 for (auto& rSubset : rType3Font.m_aSubsets)
2445 {
2446 sal_GlyphId pGlyphIds[256] = {};
2447 sal_Int32 pWidths[256];
2448 sal_uInt8 pEncoding[256] = {};
2449 sal_Int32 pEncToUnicodeIndex[256] = {};
2450 sal_Int32 pCodeUnitsPerGlyph[256] = {};
2451 std::vector<sal_Ucs> aCodeUnits;
2452 sal_Int32 nToUnicodeStream = 0;
2453
2454 // fill arrays and prepare encoding index map
2455 auto nGlyphs = fillSubsetArrays(rSubset, pGlyphIds, pWidths, pEncoding, pEncToUnicodeIndex,
2456 pCodeUnitsPerGlyph, aCodeUnits, nToUnicodeStream);
2457
2458 // write font descriptor
2459 sal_Int32 nFontDescriptor = 0;
2460 if (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_4)
2461 nFontDescriptor = emitFontDescriptor(pFace, aSubsetInfo, rSubset.m_nFontID, 0);
2462
2463 if (nToUnicodeStream)
2464 nToUnicodeStream = createToUnicodeCMap(pEncoding, aCodeUnits, pCodeUnitsPerGlyph,
2465 pEncToUnicodeIndex, nGlyphs);
2466
2467 // write font object
2468 sal_Int32 nFontObject = createObject();
2469 if (!updateObject(nFontObject))
2470 return false;
2471
2472 OStringBuffer aLine(1024);
2473 aLine.append(
2474 OString::number(nFontObject)
2475 + " 0 obj\n"
2476 "<</Type/Font/Subtype/Type3/Name/");
2477 COSWriter::appendName(aSubsetInfo.m_aPSName, aLine);
2478
2479 aLine.append(
2480 "\n/FontBBox["
2481 // note: Top and Bottom are reversed in VCL and PDF rectangles
2482 + OString::number(aSubsetInfo.m_aFontBBox.Left())
2483 + " "
2484 + OString::number(aSubsetInfo.m_aFontBBox.Top())
2485 + " "
2486 + OString::number(aSubsetInfo.m_aFontBBox.Right())
2487 + " "
2488 + OString::number(aSubsetInfo.m_aFontBBox.Bottom() + 1)
2489 + "]\n");
2490
2491 // tdf#155610
2492 // Adobe Acrobat does not seem to like certain UPEMs, so instead of
2493 // setting the FontMatrix scale relative to the UPEM, we always set to
2494 // 0.001 (1000 UPEM) and scale everything if the font’s UPEM is
2495 // different.
2496 double fScale = 1000. / pFace->UnitsPerEm();
2497
2498 aLine.append("/FontMatrix[0.001 0 0 0.001 0 0]\n");
2499
2500 sal_Int32 pGlyphStreams[256] = {};
2501 aLine.append("/CharProcs<<\n");
2502 for (auto i = 1u; i < nGlyphs; i++)
2503 {
2504 auto nStream = createObject();
2505 aLine.append("/"
2506 + pFace->GetGlyphName(pGlyphIds[i], true)
2507 + " "
2508 + OString::number(nStream)
2509 + " 0 R\n");
2510 pGlyphStreams[i] = nStream;
2511 }
2512 aLine.append(">>\n"
2513
2514 "/Encoding<</Type/Encoding/Differences[1");
2515 for (auto i = 1u; i < nGlyphs; i++)
2516 aLine.append(" /" + pFace->GetGlyphName(pGlyphIds[i], true));
2517 aLine.append("]>>\n"
2518
2519 "/FirstChar 0\n"
2520 "/LastChar "
2521 + OString::number(nGlyphs - 1)
2522 + "\n"
2523
2524 "/Widths[");
2525 for (auto i = 0u; i < nGlyphs; i++)
2526 {
2527 appendDouble(pWidths[i] * fScale, aLine);
2528 aLine.append(" ");
2529 }
2530 aLine.append("]\n");
2531
2532 if (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_4)
2533 {
2534 aLine.append("/FontDescriptor " + OString::number(nFontDescriptor) + " 0 R\n");
2535 }
2536
2537 auto nResources = createObject();
2538 aLine.append("/Resources " + OString::number(nResources) + " 0 R\n");
2539
2540 if (nToUnicodeStream)
2541 {
2542 aLine.append("/ToUnicode " + OString::number(nToUnicodeStream) + " 0 R\n");
2543 }
2544
2545 aLine.append(">>\n"
2546 "endobj\n\n");
2547
2548 if (!writeBuffer(aLine))
2549 return false;
2550
2551 std::set<sal_Int32> aUsedFonts;
2552 std::list<BitmapEmit> aUsedBitmaps;
2553 std::map<sal_uInt8, sal_Int32> aUsedAlpha;
2554 ResourceDict aResourceDict;
2555 std::list<StreamRedirect> aOutputStreams;
2556
2557 // Scale for glyph outlines.
2558 double fScaleX = (GetDPIX() / 72.) * fScale;
2559 double fScaleY = (GetDPIY() / 72.) * fScale;
2560
2561 for (auto i = 1u; i < nGlyphs; i++)
2562 {
2563 auto nStream = pGlyphStreams[i];
2564 if (!updateObject(nStream))
2565 return false;
2566 OStringBuffer aContents(1024);
2567 appendDouble(pWidths[i] * fScale, aContents);
2568 aContents.append(" 0 d0\n");
2569
2570 const auto& rGlyph = rSubset.m_aMapping.find(pGlyphIds[i])->second;
2571 const auto& rLayers = rGlyph.getColorLayers();
2572 for (const auto& rLayer : rLayers)
2573 {
2574 aUsedFonts.insert(rLayer.m_nFontID);
2575
2576 aContents.append("q ");
2577 // 0xFFFF is a special value means foreground color.
2578 if (rLayer.m_nColorIndex != 0xFFFF)
2579 {
2580 auto& rPalette = rColorPalettes[0];
2581 auto aColor(rPalette[rLayer.m_nColorIndex]);
2582 appendNonStrokingColor(aColor, aContents);
2583 aContents.append(" ");
2584 if (aColor.GetAlpha() != 0xFF
2585 && m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4)
2586 {
2587 auto nAlpha = aColor.GetAlpha();
2588 OStringBuffer aName(16);
2589 aName.append("GS");
2590 COSWriter::appendHex(nAlpha, aName);
2591
2592 aContents.append("/" + aName + " gs ");
2593
2594 if (aUsedAlpha.find(nAlpha) == aUsedAlpha.end())
2595 {
2596 auto nObject = createObject();
2597 aUsedAlpha[nAlpha] = nObject;
2598 pushResource(ResourceKind::ExtGState, aName.makeStringAndClear(),
2599 nObject, aResourceDict, aOutputStreams);
2600 }
2601 }
2602 }
2603 aContents.append(
2604 "BT "
2605 "/F" + OString::number(rLayer.m_nFontID) + " ");
2606 appendDouble(pFace->UnitsPerEm() * fScale, aContents);
2607 aContents.append(
2608 " Tf "
2609 "<");
2610 COSWriter::appendHex(rLayer.m_nSubsetGlyphID, aContents);
2611 aContents.append(
2612 ">Tj "
2613 "ET "
2614 "Q\n");
2615 }
2616
2617 tools::Rectangle aRect;
2618 const auto& rBitmapData = rGlyph.getColorBitmap(aRect);
2619 if (!rBitmapData.empty())
2620 {
2621 SvMemoryStream aStream(const_cast<uint8_t*>(rBitmapData.data()), rBitmapData.size(),
2622 StreamMode::READ);
2623 vcl::PngImageReader aReader(aStream);
2624
2625 Bitmap aBitmap = aReader.read();
2626 const BitmapEmit& rBitmapEmit = createBitmapEmit(aBitmap, Graphic(),
2627 aUsedBitmaps, aResourceDict,
2628 aOutputStreams);
2629
2630 auto nObject = rBitmapEmit.m_aReferenceXObject.getObject();
2631 aContents.append("q ");
2632 appendDouble(aRect.GetWidth() * fScale, aContents);
2633 aContents.append(" 0 0 ");
2634 appendDouble(aRect.GetHeight() * fScale, aContents);
2635 aContents.append(" ");
2636 appendDouble(aRect.getX() * fScale, aContents);
2637 aContents.append(" ");
2638 appendDouble(aRect.getY() * fScale, aContents);
2639 aContents.append(" cm /Im" + OString::number(nObject) + " Do Q\n");
2640 }
2641
2642 const auto& rOutline = rGlyph.getOutline();
2643 if (rOutline.count())
2644 {
2645 aContents.append("q ");
2646 appendDouble(fScaleX, aContents);
2647 aContents.append(" 0 0 ");
2648 appendDouble(fScaleY, aContents);
2649 aContents.append(" 0 ");
2650 appendDouble(m_aPages.back().getHeight() * -fScaleY, aContents, 3);
2651 aContents.append(" cm\n");
2652 m_aPages.back().appendPolyPolygon(rOutline, aContents);
2653 aContents.append("f\n"
2654 "Q\n");
2655 }
2656
2657 aLine.setLength(0);
2658 aLine.append(OString::number(nStream)
2659 + " 0 obj\n<</Length "
2660 + OString::number(aContents.getLength() - 1) // Trailing newline doesn't count
2661 + ">>\nstream\n");
2662 if (!writeBuffer(aLine))
2663 return false;
2664 if (!writeBuffer(aContents))
2665 return false;
2666 aLine.setLength(0);
2667 aLine.append("endstream\nendobj\n\n");
2668 if (!writeBuffer(aLine))
2669 return false;
2670 }
2671
2672 // write font dict
2673 sal_Int32 nFontDict = 0;
2674 if (!aUsedFonts.empty())
2675 {
2676 nFontDict = createObject();
2677 aLine.setLength(0);
2678 aLine.append(OString::number(nFontDict) + " 0 obj\n<<");
2679 for (auto nFontID : aUsedFonts)
2680 {
2681 aLine.append("/F"
2682 + OString::number(nFontID)
2683 + " "
2684 + OString::number(rFontIDToObject[nFontID])
2685 + " 0 R");
2686 }
2687 aLine.append(">>\nendobj\n\n");
2688 if (!updateObject(nFontDict))
2689 return false;
2690 if (!writeBuffer(aLine))
2691 return false;
2692 }
2693
2694 // write ExtGState objects
2695 if (!aUsedAlpha.empty())
2696 {
2697 for (const auto & [ nAlpha, nObject ] : aUsedAlpha)
2698 {
2699 aLine.setLength(0);
2700 aLine.append(OString::number(nObject) + " 0 obj\n<<");
2701 if (m_bIsPDF_A1)
2702 {
2703 aLine.append("/CA 1.0/ca 1.0");
2704 m_aErrors.insert(PDFWriter::Warning_Transparency_Omitted_PDFA);
2705 }
2706 else
2707 {
2708 aLine.append("/CA ");
2709 appendDouble(nAlpha / 255., aLine);
2710 aLine.append("/ca ");
2711 appendDouble(nAlpha / 255., aLine);
2712 }
2713 aLine.append(">>\nendobj\n\n");
2714 if (!updateObject(nObject))
2715 return false;
2716 if (!writeBuffer(aLine))
2717 return false;
2718 }
2719 }
2720
2721 // write bitmap objects
2722 for (auto& aBitmap : aUsedBitmaps)
2723 writeBitmapObject(aBitmap);
2724
2725 // write resources dict
2726 aLine.setLength(0);
2727 aLine.append(OString::number(nResources) + " 0 obj\n");
2728 aResourceDict.append(aLine, nFontDict, m_aContext.Version);
2729 aLine.append("endobj\n\n");
2730 if (!updateObject(nResources))
2731 return false;
2732 if (!writeBuffer(aLine))
2733 return false;
2734
2735 rFontIDToObject[rSubset.m_nFontID] = nFontObject;
2736 }
2737
2738 return true;
2739 }
2740
2741 typedef int ThreeInts[3];
getPfbSegmentLengths(const unsigned char * pFontBytes,int nByteLen,ThreeInts & rSegmentLengths)2742 static bool getPfbSegmentLengths( const unsigned char* pFontBytes, int nByteLen,
2743 ThreeInts& rSegmentLengths )
2744 {
2745 if( !pFontBytes || (nByteLen < 0) )
2746 return false;
2747 const unsigned char* pPtr = pFontBytes;
2748 const unsigned char* pEnd = pFontBytes + nByteLen;
2749
2750 for(int & rSegmentLength : rSegmentLengths) {
2751 // read segment1 header
2752 if( pPtr+6 >= pEnd )
2753 return false;
2754 if( (pPtr[0] != 0x80) || (pPtr[1] >= 0x03) )
2755 return false;
2756 const int nLen = (pPtr[5]<<24) + (pPtr[4]<<16) + (pPtr[3]<<8) + pPtr[2];
2757 if( nLen <= 0)
2758 return false;
2759 rSegmentLength = nLen;
2760 pPtr += nLen + 6;
2761 }
2762
2763 // read segment-end header
2764 if( pPtr+2 >= pEnd )
2765 return false;
2766 if( (pPtr[0] != 0x80) || (pPtr[1] != 0x03) )
2767 return false;
2768
2769 return true;
2770 }
2771
appendSubsetName(int nSubsetID,std::u16string_view rPSName,OStringBuffer & rBuffer)2772 static void appendSubsetName( int nSubsetID, std::u16string_view rPSName, OStringBuffer& rBuffer )
2773 {
2774 if( nSubsetID )
2775 {
2776 for( int i = 0; i < 6; i++ )
2777 {
2778 int nOffset = nSubsetID % 26;
2779 nSubsetID /= 26;
2780 rBuffer.append( static_cast<char>('A'+nOffset) );
2781 }
2782 rBuffer.append( '+' );
2783 }
2784 COSWriter::appendName( rPSName, rBuffer );
2785 }
2786
createToUnicodeCMap(sal_uInt8 const * pEncoding,const std::vector<sal_Ucs> & rCodeUnits,const sal_Int32 * pCodeUnitsPerGlyph,const sal_Int32 * pEncToUnicodeIndex,uint32_t nGlyphs)2787 sal_Int32 PDFWriterImpl::createToUnicodeCMap( sal_uInt8 const * pEncoding,
2788 const std::vector<sal_Ucs>& rCodeUnits,
2789 const sal_Int32* pCodeUnitsPerGlyph,
2790 const sal_Int32* pEncToUnicodeIndex,
2791 uint32_t nGlyphs )
2792 {
2793 int nMapped = 0;
2794 for (auto n = 0u; n < nGlyphs; ++n)
2795 if (pCodeUnitsPerGlyph[n] && rCodeUnits[pEncToUnicodeIndex[n]])
2796 nMapped++;
2797
2798 if( nMapped == 0 )
2799 return 0;
2800
2801 sal_Int32 nStream = createObject();
2802 if (!updateObject(nStream))
2803 return 0;
2804 OStringBuffer aContents( 1024 );
2805 aContents.append(
2806 "/CIDInit/ProcSet findresource begin\n"
2807 "12 dict begin\n"
2808 "begincmap\n"
2809 "/CIDSystemInfo<<\n"
2810 "/Registry (Adobe)\n"
2811 "/Ordering (UCS)\n"
2812 "/Supplement 0\n"
2813 ">> def\n"
2814 "/CMapName/Adobe-Identity-UCS def\n"
2815 "/CMapType 2 def\n"
2816 "1 begincodespacerange\n"
2817 "<00> <FF>\n"
2818 "endcodespacerange\n"
2819 );
2820 int nCount = 0;
2821 for (auto n = 0u; n < nGlyphs; ++n)
2822 {
2823 if (pCodeUnitsPerGlyph[n] && rCodeUnits[pEncToUnicodeIndex[n]])
2824 {
2825 if( (nCount % 100) == 0 )
2826 {
2827 if( nCount )
2828 aContents.append( "endbfchar\n" );
2829 aContents.append( OString::number(static_cast<sal_Int32>(std::min(nMapped-nCount, 100)) )
2830 + " beginbfchar\n" );
2831 }
2832 aContents.append( '<' );
2833 COSWriter::appendHex(static_cast<sal_Int8>(pEncoding[n]), aContents);
2834 aContents.append( "> <" );
2835 // TODO: handle code points>U+FFFF
2836 sal_Int32 nIndex = pEncToUnicodeIndex[n];
2837 for( sal_Int32 j = 0; j < pCodeUnitsPerGlyph[n]; j++ )
2838 {
2839 COSWriter::appendHex(static_cast<sal_Int8>(rCodeUnits[nIndex + j] / 256), aContents);
2840 COSWriter::appendHex(static_cast<sal_Int8>(rCodeUnits[nIndex + j] & 255), aContents);
2841 }
2842 aContents.append( ">\n" );
2843 nCount++;
2844 }
2845 }
2846 aContents.append( "endbfchar\n"
2847 "endcmap\n"
2848 "CMapName currentdict /CMap defineresource pop\n"
2849 "end\n"
2850 "end\n" );
2851 SvMemoryStream aStream;
2852 if (!g_bDebugDisableCompression)
2853 {
2854 ZCodec aCodec( 0x4000, 0x4000 );
2855 aCodec.BeginCompression();
2856 aCodec.Write( aStream, reinterpret_cast<const sal_uInt8*>(aContents.getStr()), aContents.getLength() );
2857 aCodec.EndCompression();
2858 }
2859
2860 if (g_bDebugDisableCompression)
2861 {
2862 emitComment( "PDFWriterImpl::createToUnicodeCMap" );
2863 }
2864 OStringBuffer aLine( 40 );
2865
2866 aLine.append( OString::number(nStream ) + " 0 obj\n<</Length " );
2867 sal_uInt64 nLen = 0;
2868 if (!g_bDebugDisableCompression)
2869 {
2870 nLen = aStream.Tell();
2871 aStream.Seek( 0 );
2872 aLine.append( OString::number(nLen) + "/Filter/FlateDecode" );
2873 }
2874 else
2875 aLine.append( aContents.getLength() );
2876 aLine.append( ">>\nstream\n" );
2877 if (!writeBuffer(aLine)) return 0;
2878 checkAndEnableStreamEncryption( nStream );
2879 if (!g_bDebugDisableCompression)
2880 {
2881 if(!writeBufferBytes(aStream.GetData(), nLen)) return 0;
2882 }
2883 else
2884 {
2885 if (!writeBuffer(aContents)) return 0;
2886 }
2887 disableStreamEncryption();
2888 aLine.setLength( 0 );
2889 aLine.append( "\nendstream\n"
2890 "endobj\n\n" );
2891 if (!writeBuffer(aLine)) return 0;
2892 return nStream;
2893 }
2894
emitFontDescriptor(const vcl::font::PhysicalFontFace * pFace,FontSubsetInfo const & rInfo,sal_Int32 nSubsetID,sal_Int32 nFontStream)2895 sal_Int32 PDFWriterImpl::emitFontDescriptor( const vcl::font::PhysicalFontFace* pFace, FontSubsetInfo const & rInfo, sal_Int32 nSubsetID, sal_Int32 nFontStream )
2896 {
2897 OStringBuffer aLine( 1024 );
2898 // get font flags, see PDF reference 1.4 p. 358
2899 // possibly characters outside Adobe standard encoding
2900 // so set Symbolic flag
2901 sal_Int32 nFontFlags = (1<<2);
2902 if( pFace->GetItalic() == ITALIC_NORMAL || pFace->GetItalic() == ITALIC_OBLIQUE )
2903 nFontFlags |= (1 << 6);
2904 if( pFace->GetPitch() == PITCH_FIXED )
2905 nFontFlags |= 1;
2906 if( pFace->GetFamilyType() == FAMILY_SCRIPT )
2907 nFontFlags |= (1 << 3);
2908 else if( pFace->GetFamilyType() == FAMILY_ROMAN )
2909 nFontFlags |= (1 << 1);
2910
2911 sal_Int32 nFontDescriptor = createObject();
2912 if (!updateObject(nFontDescriptor)) return 0;
2913 aLine.setLength( 0 );
2914 aLine.append(
2915 OString::number(nFontDescriptor)
2916 + " 0 obj\n"
2917 "<</Type/FontDescriptor/FontName/" );
2918 appendSubsetName( nSubsetID, rInfo.m_aPSName, aLine );
2919 aLine.append( "\n"
2920 "/Flags "
2921 + OString::number( nFontFlags )
2922 + "\n"
2923 "/FontBBox["
2924 // note: Top and Bottom are reversed in VCL and PDF rectangles
2925 + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Left()) )
2926 + " "
2927 + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Top()) )
2928 + " "
2929 + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Right()) )
2930 + " "
2931 + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Bottom()+1) )
2932 + "]/ItalicAngle " );
2933 if( pFace->GetItalic() == ITALIC_OBLIQUE || pFace->GetItalic() == ITALIC_NORMAL )
2934 aLine.append( "-30" );
2935 else
2936 aLine.append( "0" );
2937 aLine.append( "\n"
2938 "/Ascent "
2939 + OString::number( static_cast<sal_Int32>(rInfo.m_nAscent) )
2940 + "\n"
2941 "/Descent "
2942 + OString::number( static_cast<sal_Int32>(-rInfo.m_nDescent) )
2943 + "\n"
2944 "/CapHeight "
2945 + OString::number( static_cast<sal_Int32>(rInfo.m_nCapHeight) )
2946 // According to PDF reference 1.4 StemV is required
2947 // seems a tad strange to me, but well ...
2948 + "\n"
2949 "/StemV 80\n" );
2950 if( nFontStream )
2951 {
2952 aLine.append( "/FontFile" );
2953 switch( rInfo.m_nFontType )
2954 {
2955 case FontType::SFNT_TTF:
2956 aLine.append( '2' );
2957 break;
2958 case FontType::TYPE1_PFA:
2959 case FontType::TYPE1_PFB:
2960 case FontType::ANY_TYPE1:
2961 break;
2962 default:
2963 OSL_FAIL( "unknown fonttype in PDF font descriptor" );
2964 return 0;
2965 }
2966 aLine.append( " " + OString::number(nFontStream) + " 0 R\n" );
2967 }
2968 aLine.append( ">>\n"
2969 "endobj\n\n" );
2970 if (!writeBuffer(aLine)) return 0;
2971
2972 return nFontDescriptor;
2973 }
2974
appendBuildinFontsToDict(OStringBuffer & rDict) const2975 void PDFWriterImpl::appendBuildinFontsToDict( OStringBuffer& rDict ) const
2976 {
2977 for (auto const& item : m_aBuildinFontToObjectMap)
2978 {
2979 rDict.append( pdf::BuildinFontFace::Get(item.first).getNameObject() );
2980 rDict.append( ' ' );
2981 rDict.append( item.second );
2982 rDict.append( " 0 R" );
2983 }
2984 }
2985
emitFonts()2986 bool PDFWriterImpl::emitFonts()
2987 {
2988 OStringBuffer aLine( 1024 );
2989
2990 std::map< sal_Int32, sal_Int32 > aFontIDToObject;
2991
2992 for (const auto & subset : m_aSubsets)
2993 {
2994 for (auto & s_subset :subset.second.m_aSubsets)
2995 {
2996 sal_GlyphId pGlyphIds[ 256 ] = {};
2997 sal_Int32 pWidths[ 256 ];
2998 sal_uInt8 pEncoding[ 256 ] = {};
2999 sal_Int32 pEncToUnicodeIndex[ 256 ] = {};
3000 sal_Int32 pCodeUnitsPerGlyph[ 256 ] = {};
3001 std::vector<sal_Ucs> aCodeUnits;
3002 sal_Int32 nToUnicodeStream = 0;
3003
3004 // fill arrays and prepare encoding index map
3005 auto nGlyphs = fillSubsetArrays(s_subset, pGlyphIds, pWidths, pEncoding, pEncToUnicodeIndex,
3006 pCodeUnitsPerGlyph, aCodeUnits, nToUnicodeStream);
3007
3008 std::vector<sal_uInt8> aBuffer;
3009 FontSubsetInfo aSubsetInfo;
3010 const auto* pFace = subset.first;
3011 if (pFace->CreateFontSubset(aBuffer, pGlyphIds, pEncoding, nGlyphs, aSubsetInfo))
3012 {
3013 // create font stream
3014 if (g_bDebugDisableCompression)
3015 {
3016 emitComment( "PDFWriterImpl::emitFonts" );
3017 }
3018 sal_Int32 nFontStream = createObject();
3019 sal_Int32 nStreamLengthObject = createObject();
3020 if ( !updateObject( nFontStream ) ) return false;
3021 aLine.setLength( 0 );
3022 aLine.append( OString::number(nFontStream)
3023 + " 0 obj\n"
3024 "<</Length "
3025 + OString::number( nStreamLengthObject ) );
3026 if (!g_bDebugDisableCompression)
3027 aLine.append( " 0 R"
3028 "/Filter/FlateDecode"
3029 "/Length1 " );
3030 else
3031 aLine.append( " 0 R"
3032 "/Length1 " );
3033
3034 sal_uInt64 nStartPos = 0;
3035 if( aSubsetInfo.m_nFontType == FontType::SFNT_TTF )
3036 {
3037 aLine.append( OString::number(static_cast<sal_Int32>(aBuffer.size()))
3038 + ">>\n"
3039 "stream\n" );
3040 if ( !writeBuffer( aLine ) ) return false;
3041 if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
3042
3043 // copy font file
3044 beginCompression();
3045 checkAndEnableStreamEncryption( nFontStream );
3046 if (!writeBufferBytes(aBuffer.data(), aBuffer.size()))
3047 return false;
3048 }
3049 else if( aSubsetInfo.m_nFontType & FontType::CFF_FONT)
3050 {
3051 // TODO: implement
3052 OSL_FAIL( "PDFWriterImpl does not support CFF-font subsets yet!" );
3053 }
3054 else if( aSubsetInfo.m_nFontType & FontType::TYPE1_PFB) // TODO: also support PFA?
3055 {
3056 // get the PFB-segment lengths
3057 ThreeInts aSegmentLengths = {0,0,0};
3058 getPfbSegmentLengths(aBuffer.data(), static_cast<int>(aBuffer.size()), aSegmentLengths);
3059 // the lengths below are mandatory for PDF-exported Type1 fonts
3060 // because the PFB segment headers get stripped! WhyOhWhy.
3061 aLine.append( OString::number(static_cast<sal_Int32>(aSegmentLengths[0]) )
3062 + "/Length2 "
3063 + OString::number( static_cast<sal_Int32>(aSegmentLengths[1]) )
3064 + "/Length3 "
3065 + OString::number( static_cast<sal_Int32>(aSegmentLengths[2]) )
3066 + ">>\n"
3067 "stream\n" );
3068 if ( !writeBuffer( aLine ) ) return false;
3069 if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
3070
3071 // emit PFB-sections without section headers
3072 beginCompression();
3073 checkAndEnableStreamEncryption( nFontStream );
3074 if ( !writeBufferBytes( &aBuffer[6], aSegmentLengths[0] ) ) return false;
3075 if ( !writeBufferBytes( &aBuffer[12] + aSegmentLengths[0], aSegmentLengths[1] ) ) return false;
3076 if ( !writeBufferBytes( &aBuffer[18] + aSegmentLengths[0] + aSegmentLengths[1], aSegmentLengths[2] ) ) return false;
3077 }
3078 else
3079 {
3080 SAL_INFO("vcl.pdfwriter", "PDF: CreateFontSubset result in not yet supported format=" << static_cast<int>(aSubsetInfo.m_nFontType));
3081 aLine.append( "0 >>\nstream\n" );
3082 }
3083
3084 endCompression();
3085 disableStreamEncryption();
3086
3087 sal_uInt64 nEndPos = 0;
3088 if ( osl::File::E_None != m_aFile.getPos(nEndPos) ) return false;
3089 // end the stream
3090 aLine.setLength( 0 );
3091 aLine.append( "\nendstream\nendobj\n\n" );
3092 if ( !writeBuffer( aLine ) ) return false;
3093
3094 // emit stream length object
3095 if ( !updateObject( nStreamLengthObject ) ) return false;
3096 aLine.setLength( 0 );
3097 aLine.append( OString::number(nStreamLengthObject)
3098 + " 0 obj\n"
3099 + OString::number( static_cast<sal_Int64>(nEndPos-nStartPos) )
3100 + "\nendobj\n\n" );
3101 if ( !writeBuffer( aLine ) ) return false;
3102
3103 // write font descriptor
3104 sal_Int32 nFontDescriptor = emitFontDescriptor( subset.first, aSubsetInfo, s_subset.m_nFontID, nFontStream );
3105
3106 if( nToUnicodeStream )
3107 nToUnicodeStream = createToUnicodeCMap( pEncoding, aCodeUnits, pCodeUnitsPerGlyph, pEncToUnicodeIndex, nGlyphs );
3108
3109 sal_Int32 nFontObject = createObject();
3110 if ( !updateObject( nFontObject ) ) return false;
3111 aLine.setLength( 0 );
3112 aLine.append( OString::number(nFontObject) + " 0 obj\n" );
3113 aLine.append( (aSubsetInfo.m_nFontType & FontType::ANY_TYPE1) ?
3114 "<</Type/Font/Subtype/Type1/BaseFont/" :
3115 "<</Type/Font/Subtype/TrueType/BaseFont/" );
3116 appendSubsetName( s_subset.m_nFontID, aSubsetInfo.m_aPSName, aLine );
3117 aLine.append( "\n"
3118 "/FirstChar 0\n"
3119 "/LastChar "
3120 + OString::number( static_cast<sal_Int32>(nGlyphs-1) )
3121 + "\n"
3122 "/Widths[" );
3123 for (auto i = 0u; i < nGlyphs; i++)
3124 {
3125 aLine.append( pWidths[ i ] );
3126 aLine.append( ((i & 15) == 15) ? "\n" : " " );
3127 }
3128 aLine.append( "]\n"
3129 "/FontDescriptor "
3130 + OString::number( nFontDescriptor )
3131 + " 0 R\n" );
3132 if( nToUnicodeStream )
3133 {
3134 aLine.append( "/ToUnicode "
3135 + OString::number( nToUnicodeStream )
3136 + " 0 R\n" );
3137 }
3138 aLine.append( ">>\n"
3139 "endobj\n\n" );
3140 if ( !writeBuffer( aLine ) ) return false;
3141
3142 aFontIDToObject[ s_subset.m_nFontID ] = nFontObject;
3143 }
3144 else
3145 {
3146 OStringBuffer aErrorComment( 256 );
3147 aErrorComment.append( "CreateFontSubset failed for font \""
3148 + OUStringToOString( pFace->GetFamilyName(), RTL_TEXTENCODING_UTF8 )
3149 + "\"" );
3150 if( pFace->GetItalic() == ITALIC_NORMAL )
3151 aErrorComment.append( " italic" );
3152 else if( pFace->GetItalic() == ITALIC_OBLIQUE )
3153 aErrorComment.append( " oblique" );
3154 aErrorComment.append( " weight=" + OString::number( sal_Int32(pFace->GetWeight()) ) );
3155 emitComment( aErrorComment.getStr() );
3156 }
3157 }
3158 }
3159
3160 // emit system fonts
3161 for (auto const& systemFont : m_aSystemFonts)
3162 {
3163 std::map< sal_Int32, sal_Int32 > aObjects = emitSystemFont( systemFont.first, systemFont.second );
3164 for (auto const& item : aObjects)
3165 {
3166 if ( !item.second ) return false;
3167 aFontIDToObject[ item.first ] = item.second;
3168 }
3169 }
3170
3171 // emit Type3 fonts
3172 for (auto const& it : m_aType3Fonts)
3173 {
3174 if (!emitType3Font(it.first, it.second, aFontIDToObject))
3175 return false;
3176 }
3177
3178 OStringBuffer aFontDict( 1024 );
3179 aFontDict.append( OString::number(getFontDictObject())
3180 + " 0 obj\n"
3181 "<<" );
3182 int ni = 0;
3183 for (auto const& itemMap : aFontIDToObject)
3184 {
3185 aFontDict.append( "/F"
3186 + OString::number( itemMap.first )
3187 + " "
3188 + OString::number( itemMap.second )
3189 + " 0 R" );
3190 if( ((++ni) & 7) == 0 )
3191 aFontDict.append( '\n' );
3192 }
3193 // emit builtin font for widget appearances / variable text
3194 for (auto & item : m_aBuildinFontToObjectMap)
3195 {
3196 rtl::Reference<pdf::BuildinFontFace> aData(new pdf::BuildinFontFace(item.first));
3197 item.second = emitBuildinFont( aData.get(), item.second );
3198 }
3199
3200 appendBuildinFontsToDict(aFontDict);
3201 aFontDict.append( "\n>>\nendobj\n\n" );
3202
3203 if ( !updateObject( getFontDictObject() ) ) return false;
3204 if ( !writeBuffer( aFontDict ) ) return false;
3205 return true;
3206 }
3207
emitResources()3208 sal_Int32 PDFWriterImpl::emitResources()
3209 {
3210 // emit shadings
3211 if (!m_aGradients.empty())
3212 {
3213 if (!emitGradients()) return 0;
3214 }
3215 // emit tilings
3216 if (!m_aTilings.empty())
3217 {
3218 if(!emitTilings()) return 0;
3219 }
3220
3221 // emit font dict
3222 if (!emitFonts()) return 0;
3223
3224 // emit Resource dict
3225 OStringBuffer aLine( 512 );
3226 sal_Int32 nResourceDict = getResourceDictObj();
3227 if (!updateObject(nResourceDict))
3228 return 0;
3229 aLine.setLength( 0 );
3230 aLine.append( OString::number(nResourceDict)
3231 + " 0 obj\n" );
3232 m_aGlobalResourceDict.append(aLine, getFontDictObject(), m_aContext.Version);
3233 aLine.append( "endobj\n\n" );
3234 if (!writeBuffer(aLine)) return 0;
3235 return nResourceDict;
3236 }
3237
updateOutlineItemCount(std::vector<sal_Int32> & rCounts,sal_Int32 nItemLevel,sal_Int32 nCurrentItemId)3238 sal_Int32 PDFWriterImpl::updateOutlineItemCount( std::vector< sal_Int32 >& rCounts,
3239 sal_Int32 nItemLevel,
3240 sal_Int32 nCurrentItemId )
3241 {
3242 /* The /Count number of an item is
3243 positive: the number of visible subitems
3244 negative: the negative number of subitems that will become visible if
3245 the item gets opened
3246 see PDF ref 1.4 p 478
3247 */
3248
3249 sal_Int32 nCount = 0;
3250
3251 if( m_aContext.OpenBookmarkLevels < 0 || // all levels are visible
3252 m_aContext.OpenBookmarkLevels >= nItemLevel // this level is visible
3253 )
3254 {
3255 PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
3256 sal_Int32 nChildren = rItem.m_aChildren.size();
3257 for( sal_Int32 i = 0; i < nChildren; i++ )
3258 nCount += updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
3259 rCounts[nCurrentItemId] = nCount;
3260 // return 1 (this item) + visible sub items
3261 if( nCount < 0 )
3262 nCount = 0;
3263 nCount++;
3264 }
3265 else
3266 {
3267 // this bookmark level is invisible
3268 PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
3269 sal_Int32 nChildren = rItem.m_aChildren.size();
3270 rCounts[ nCurrentItemId ] = -sal_Int32(rItem.m_aChildren.size());
3271 for( sal_Int32 i = 0; i < nChildren; i++ )
3272 updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
3273 nCount = -1;
3274 }
3275
3276 return nCount;
3277 }
3278
emitOutline()3279 sal_Int32 PDFWriterImpl::emitOutline()
3280 {
3281 int i, nItems = m_aOutline.size();
3282
3283 // do we have an outline at all ?
3284 if( nItems < 2 )
3285 return 0;
3286
3287 // reserve object numbers for all outline items
3288 for( i = 0; i < nItems; ++i )
3289 m_aOutline[i].m_nObject = createObject();
3290
3291 // update all parent, next and prev object ids
3292 for( i = 0; i < nItems; ++i )
3293 {
3294 PDFOutlineEntry& rItem = m_aOutline[i];
3295 int nChildren = rItem.m_aChildren.size();
3296
3297 if( nChildren )
3298 {
3299 for( int n = 0; n < nChildren; ++n )
3300 {
3301 PDFOutlineEntry& rChild = m_aOutline[ rItem.m_aChildren[n] ];
3302
3303 rChild.m_nParentObject = rItem.m_nObject;
3304 rChild.m_nPrevObject = (n > 0) ? m_aOutline[ rItem.m_aChildren[n-1] ].m_nObject : 0;
3305 rChild.m_nNextObject = (n < nChildren-1) ? m_aOutline[ rItem.m_aChildren[n+1] ].m_nObject : 0;
3306 }
3307
3308 }
3309 }
3310
3311 // calculate Count entries for all items
3312 std::vector< sal_Int32 > aCounts( nItems );
3313 updateOutlineItemCount( aCounts, 0, 0 );
3314
3315 // emit hierarchy
3316 for( i = 0; i < nItems; ++i )
3317 {
3318 PDFOutlineEntry& rItem = m_aOutline[i];
3319 OStringBuffer aLine( 1024 );
3320 COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
3321
3322 if (!updateObject(rItem.m_nObject))
3323 return 0;
3324 aLine.append( OString::number(rItem.m_nObject)
3325 + " 0 obj\n"
3326 "<<" );
3327 // number of visible children (all levels)
3328 if( i > 0 || aCounts[0] > 0 )
3329 {
3330 aLine.append( "/Count " + OString::number( aCounts[i] ) );
3331 }
3332 if( ! rItem.m_aChildren.empty() )
3333 {
3334 // children list: First, Last
3335 aLine.append( "/First "
3336 + OString::number( m_aOutline[rItem.m_aChildren.front()].m_nObject )
3337 + " 0 R/Last "
3338 + OString::number( m_aOutline[rItem.m_aChildren.back()].m_nObject )
3339 + " 0 R\n" );
3340 }
3341 if( i > 0 )
3342 {
3343 // Title, Dest, Parent, Prev, Next
3344 aLine.append( "/Title" );
3345 aWriter.writeUnicodeEncrypt(rItem.m_aTitle, rItem.m_nObject);
3346 aLine.append( "\n" );
3347 // Dest is not required
3348 if( rItem.m_nDestID >= 0 && o3tl::make_unsigned(rItem.m_nDestID) < m_aDests.size() )
3349 {
3350 aLine.append( "/Dest" );
3351 appendDest( rItem.m_nDestID, aLine );
3352 }
3353 aLine.append( "/Parent "
3354 + OString::number( rItem.m_nParentObject )
3355 + " 0 R" );
3356 if( rItem.m_nPrevObject )
3357 {
3358 aLine.append( "/Prev "
3359 + OString::number( rItem.m_nPrevObject )
3360 + " 0 R" );
3361 }
3362 if( rItem.m_nNextObject )
3363 {
3364 aLine.append( "/Next "
3365 + OString::number( rItem.m_nNextObject )
3366 + " 0 R" );
3367 }
3368 }
3369 aLine.append( ">>\nendobj\n\n" );
3370 if (!writeBuffer(aLine))
3371 return 0;
3372 }
3373
3374 return m_aOutline[0].m_nObject;
3375 }
3376
appendDest(sal_Int32 nDestID,OStringBuffer & rBuffer)3377 bool PDFWriterImpl::appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer )
3378 {
3379 if( nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size() )
3380 {
3381 SAL_INFO("vcl.pdfwriter", "ERROR: invalid dest " << static_cast<int>(nDestID) << " requested");
3382 return false;
3383 }
3384
3385 const PDFDest& rDest = m_aDests[ nDestID ];
3386 const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ];
3387
3388 rBuffer.append( '[' );
3389 rBuffer.append( rDestPage.m_nPageObject );
3390 rBuffer.append( " 0 R" );
3391
3392 switch( rDest.m_eType )
3393 {
3394 case PDFWriter::DestAreaType::XYZ:
3395 default:
3396 rBuffer.append( "/XYZ " );
3397 appendFixedInt( rDest.m_aRect.Left(), rBuffer );
3398 rBuffer.append( ' ' );
3399 appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
3400 rBuffer.append( " 0" );
3401 break;
3402 case PDFWriter::DestAreaType::FitRectangle:
3403 rBuffer.append( "/FitR " );
3404 appendFixedInt( rDest.m_aRect.Left(), rBuffer );
3405 rBuffer.append( ' ' );
3406 appendFixedInt( rDest.m_aRect.Top(), rBuffer );
3407 rBuffer.append( ' ' );
3408 appendFixedInt( rDest.m_aRect.Right(), rBuffer );
3409 rBuffer.append( ' ' );
3410 appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
3411 break;
3412 }
3413 rBuffer.append( ']' );
3414
3415 return true;
3416 }
3417
addDocumentAttachedFile(OUString const & rFileName,OUString const & rMimeType,OUString const & rDescription,std::unique_ptr<PDFOutputStream> rStream)3418 void PDFWriterImpl::addDocumentAttachedFile(OUString const& rFileName, OUString const& rMimeType, OUString const& rDescription, std::unique_ptr<PDFOutputStream> rStream)
3419 {
3420 if (m_nPDFA_Version == 1 || m_nPDFA_Version == 2)
3421 return;
3422
3423 sal_Int32 nObjectID = addEmbeddedFile(std::move(rStream), rMimeType);
3424 auto& rAttachedFile = m_aDocumentAttachedFiles.emplace_back();
3425 rAttachedFile.maFilename = rFileName;
3426 rAttachedFile.maMimeType = rMimeType;
3427 rAttachedFile.maDescription = rDescription;
3428 rAttachedFile.mnEmbeddedFileObjectId = nObjectID;
3429 rAttachedFile.mnObjectId = createObject();
3430 }
3431
addEmbeddedFile(std::unique_ptr<PDFOutputStream> rStream,OUString const & rMimeType)3432 sal_Int32 PDFWriterImpl::addEmbeddedFile(std::unique_ptr<PDFOutputStream> rStream, OUString const& rMimeType)
3433 {
3434 sal_Int32 aObjectID = createObject();
3435 auto& rEmbedded = m_aEmbeddedFiles.emplace_back();
3436 rEmbedded.m_nObject = aObjectID;
3437 rEmbedded.m_aSubType = rMimeType;
3438 rEmbedded.m_pStream = std::move(rStream);
3439 return aObjectID;
3440 }
3441
addEmbeddedFile(BinaryDataContainer const & rDataContainer)3442 sal_Int32 PDFWriterImpl::addEmbeddedFile(BinaryDataContainer const & rDataContainer)
3443 {
3444 sal_Int32 aObjectID = createObject();
3445 m_aEmbeddedFiles.emplace_back();
3446 m_aEmbeddedFiles.back().m_nObject = aObjectID;
3447 m_aEmbeddedFiles.back().m_aDataContainer = rDataContainer;
3448 return aObjectID;
3449 }
3450
emitScreenAnnotations()3451 bool PDFWriterImpl::emitScreenAnnotations()
3452 {
3453 int nAnnots = m_aScreens.size();
3454 for (int i = 0; i < nAnnots; i++)
3455 {
3456 const PDFScreen& rScreen = m_aScreens[i];
3457
3458 OStringBuffer aLine;
3459 COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
3460 bool bEmbed = false;
3461 if (!rScreen.m_aTempFileURL.isEmpty())
3462 {
3463 bEmbed = true;
3464 if (!updateObject(rScreen.m_nTempFileObject))
3465 continue;
3466
3467 SvFileStream aFileStream(rScreen.m_aTempFileURL, StreamMode::READ);
3468 SvMemoryStream aMemoryStream;
3469 aMemoryStream.WriteStream(aFileStream);
3470
3471 aLine.append(rScreen.m_nTempFileObject);
3472 aLine.append(" 0 obj\n");
3473 aLine.append("<< /Type /EmbeddedFile /Length ");
3474 aLine.append(static_cast<sal_Int64>(aMemoryStream.GetSize()));
3475 aLine.append(" >>\nstream\n");
3476 if (!writeBuffer(aLine))
3477 return false;
3478 aLine.setLength(0);
3479
3480 if (!writeBufferBytes(aMemoryStream.GetData(), aMemoryStream.GetSize()))
3481 return false;
3482
3483 aLine.append("\nendstream\nendobj\n\n");
3484 if (!writeBuffer(aLine))
3485 return false;
3486 aLine.setLength(0);
3487 }
3488
3489 if (!updateObject(rScreen.m_nObject))
3490 continue;
3491
3492 // Annot dictionary.
3493 aLine.append(OString::number(rScreen.m_nObject)
3494 + " 0 obj\n"
3495 "<</Type/Annot"
3496 "/Subtype/Screen/Rect[");
3497 appendFixedInt(rScreen.m_aRect.Left(), aLine);
3498 aLine.append(' ');
3499 appendFixedInt(rScreen.m_aRect.Top(), aLine);
3500 aLine.append(' ');
3501 appendFixedInt(rScreen.m_aRect.Right(), aLine);
3502 aLine.append(' ');
3503 appendFixedInt(rScreen.m_aRect.Bottom(), aLine);
3504 aLine.append("]");
3505
3506 // Action dictionary.
3507 aLine.append("/A<</Type/Action /S/Rendition /AN "
3508 + OString::number(rScreen.m_nObject)
3509 + " 0 R ");
3510
3511 // Rendition dictionary.
3512 aLine.append("/R<</Type/Rendition /S/MR ");
3513
3514 // MediaClip dictionary.
3515 aLine.append("/C<</Type/MediaClip /S/MCD ");
3516 if (bEmbed)
3517 {
3518 aLine.append("\n/D << /Type /Filespec /F (<embedded file>) ");
3519 if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
3520 { // ISO 14289-1:2014, Clause: 7.11
3521 aLine.append("/UF (<embedded file>) ");
3522 }
3523 aLine.append("/EF << /F ");
3524 aLine.append(rScreen.m_nTempFileObject);
3525 aLine.append(" 0 R >>");
3526 }
3527 else
3528 {
3529 // Linked.
3530 aLine.append("\n/D << /Type /Filespec /FS /URL /F ");
3531 aWriter.writeLiteralEncrypt(rScreen.m_aURL, rScreen.m_nObject, osl_getThreadTextEncoding());
3532 if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
3533 { // ISO 14289-1:2014, Clause: 7.11
3534 aLine.append("/UF ");
3535 aWriter.writeUnicodeEncrypt(rScreen.m_aURL, rScreen.m_nObject);
3536 }
3537 }
3538 if (PDFWriter::PDFVersion::PDF_1_6 <= m_aContext.Version
3539 && !rScreen.m_AltText.isEmpty())
3540 { // ISO 14289-1:2014, Clause: 7.11
3541 aLine.append("/Desc ");
3542 aWriter.writeUnicodeEncrypt(rScreen.m_AltText, rScreen.m_nObject);
3543 }
3544 aLine.append(" >>\n"); // end of /D
3545 // Allow playing the video via a tempfile.
3546 aLine.append("/P <</TF (TEMPACCESS)>>");
3547 // ISO 14289-1:2014, Clause: 7.18.6.2
3548 aLine.append("/CT ");
3549 aWriter.writeLiteralEncrypt(rScreen.m_MimeType, rScreen.m_nObject);
3550 // ISO 14289-1:2014, Clause: 7.18.6.2
3551 // Alt text is a "Multi-language Text Array"
3552 aLine.append(" /Alt [ () ");
3553 aWriter.writeUnicodeEncrypt(rScreen.m_AltText, rScreen.m_nObject);
3554 aLine.append(" ] "
3555 ">>");
3556
3557 // End Rendition dictionary by requesting play/pause/stop controls.
3558 aLine.append("/P<</BE<</C true >>>>"
3559 ">>");
3560
3561 // End Action dictionary.
3562 aLine.append("/OP 0 >>");
3563
3564 if (-1 != rScreen.m_nStructParent)
3565 {
3566 aLine.append("\n/StructParent "
3567 + OString::number(rScreen.m_nStructParent)
3568 + "\n");
3569 }
3570
3571 // End Annot dictionary.
3572 aLine.append("/P "
3573 + OString::number(m_aPages[rScreen.m_nPage].m_nPageObject)
3574 + " 0 R\n>>\nendobj\n\n");
3575 if (!writeBuffer(aLine))
3576 return false;
3577 }
3578
3579 return true;
3580 }
3581
emitLinkAnnotations()3582 bool PDFWriterImpl::emitLinkAnnotations()
3583 {
3584 MARK("PDFWriterImpl::emitLinkAnnotations");
3585 int nAnnots = m_aLinks.size();
3586 for( int i = 0; i < nAnnots; i++ )
3587 {
3588 const PDFLink& rLink = m_aLinks[i];
3589 if( ! updateObject( rLink.m_nObject ) )
3590 continue;
3591 if( m_aContext.DefaultLinkAction == PDFWriter::RemoveExternalLinks && rLink.m_nDest < 0 )
3592 continue;
3593
3594 OStringBuffer aLine( 1024 );
3595 COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
3596 aLine.append( rLink.m_nObject );
3597 aLine.append( " 0 obj\n" );
3598 // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
3599 // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
3600 aLine.append( "<</Type/Annot" );
3601 if (m_nPDFA_Version > 0)
3602 aLine.append( "/F 4" );
3603 aLine.append( "/Subtype/Link/Border[0 0 0]/Rect[" );
3604
3605 appendFixedInt( rLink.m_aRect.Left()-7, aLine );//the +7 to have a better shape of the border rectangle
3606 aLine.append( ' ' );
3607 appendFixedInt( rLink.m_aRect.Top(), aLine );
3608 aLine.append( ' ' );
3609 appendFixedInt( rLink.m_aRect.Right()+7, aLine );//the +7 to have a better shape of the border rectangle
3610 aLine.append( ' ' );
3611 appendFixedInt( rLink.m_aRect.Bottom(), aLine );
3612 aLine.append( "]" );
3613 // ISO 14289-1:2014, Clause: 7.18.5
3614 if (!rLink.m_AltText.isEmpty())
3615 {
3616 aLine.append("/Contents");
3617 aWriter.writeUnicodeEncrypt(rLink.m_AltText, rLink.m_nObject);
3618 }
3619 if( rLink.m_nDest >= 0 )
3620 {
3621 aLine.append( "/Dest" );
3622 appendDest( rLink.m_nDest, aLine );
3623 }
3624 else
3625 {
3626 /*
3627 destination is external to the document, so
3628 we check in the following sequence:
3629
3630 if target type is neither .pdf, nor .od[tpgs], then
3631 check if relative or absolute and act accordingly (use URI or 'launch application' as requested)
3632 end processing
3633 else if target is .od[tpgs]: then
3634 if conversion of type from od[tpgs] to pdf is requested, convert it and this becomes the new target file
3635 processing continue
3636
3637 if (new)target is .pdf : then
3638 if GotToR is requested, then
3639 convert the target in GoToR where the fragment of the URI is
3640 considered the named destination in the target file, set relative or absolute as requested
3641 else strip the fragment from URL and then set URI or 'launch application' as requested
3642 */
3643
3644 // FIXME: check if the decode mechanisms for URL processing throughout this implementation
3645 // are the correct one!!
3646
3647 // extract target file type
3648 auto url(URIHelper::resolveIdnaHost(rLink.m_aURL));
3649
3650 INetURLObject aDocumentURL( m_aContext.BaseURL );
3651 INetURLObject aTargetURL( url );
3652 bool bSetGoToRMode = false;
3653 bool bTargetHasPDFExtension = false;
3654 INetProtocol eTargetProtocol = aTargetURL.GetProtocol();
3655 bool bIsUNCPath = false;
3656 bool bUnparsedURI = false;
3657
3658 // check if the protocol is a known one, or if there is no protocol at all (on target only)
3659 // if there is no protocol, make the target relative to the current document directory
3660 // getting the needed URL information from the current document path
3661 if( eTargetProtocol == INetProtocol::NotValid )
3662 {
3663 if( url.getLength() > 4 && url.startsWith("\\\\\\\\"))
3664 {
3665 bIsUNCPath = true;
3666 }
3667 else
3668 {
3669 //reassign the new target URL
3670 aTargetURL = INetURLObject(rtl::Uri::convertRelToAbs(
3671 (m_aContext.BaseURL.getLength() > 0 ?
3672 m_aContext.BaseURL :
3673 //use dummy location if empty
3674 u"http://ahost.ax"_ustr),
3675 url));
3676
3677 //recompute the target protocol, with the new URL
3678 //normal URL processing resumes
3679 eTargetProtocol = aTargetURL.GetProtocol();
3680
3681 bUnparsedURI = eTargetProtocol == INetProtocol::NotValid;
3682 }
3683 }
3684
3685 OUString aFileExtension = aTargetURL.GetFileExtension();
3686
3687 // Check if the URL ends in '/': if yes it's a directory,
3688 // it will be forced to a URI link.
3689 // possibly a malformed URI, leave it as it is, force as URI
3690 if( aTargetURL.hasFinalSlash() )
3691 m_aContext.DefaultLinkAction = PDFWriter::URIAction;
3692
3693 if( !aFileExtension.isEmpty() )
3694 {
3695 if( m_aContext.ConvertOOoTargetToPDFTarget )
3696 {
3697 bool bChangeFileExtensionToPDF = false;
3698 //examine the file type (.odm .odt. .odp, odg, ods)
3699 if( aFileExtension.equalsIgnoreAsciiCase( "odm" ) )
3700 bChangeFileExtensionToPDF = true;
3701 if( aFileExtension.equalsIgnoreAsciiCase( "odt" ) )
3702 bChangeFileExtensionToPDF = true;
3703 else if( aFileExtension.equalsIgnoreAsciiCase( "odp" ) )
3704 bChangeFileExtensionToPDF = true;
3705 else if( aFileExtension.equalsIgnoreAsciiCase( "odg" ) )
3706 bChangeFileExtensionToPDF = true;
3707 else if( aFileExtension.equalsIgnoreAsciiCase( "ods" ) )
3708 bChangeFileExtensionToPDF = true;
3709 if( bChangeFileExtensionToPDF )
3710 aTargetURL.setExtension(u"pdf" );
3711 }
3712 //check if extension is pdf, see if GoToR should be forced
3713 bTargetHasPDFExtension = aTargetURL.GetFileExtension().equalsIgnoreAsciiCase( "pdf" );
3714 if( m_aContext.ForcePDFAction && bTargetHasPDFExtension )
3715 bSetGoToRMode = true;
3716 }
3717 //prepare the URL, if relative or not
3718 INetProtocol eBaseProtocol = aDocumentURL.GetProtocol();
3719 //queue the string common to all types of actions
3720 aLine.append( "/A<</Type/Action/S");
3721 if( bIsUNCPath ) // handle Win UNC paths
3722 {
3723 aLine.append("/Launch");
3724 // Entry /Win is deprecated in PDF 2.0
3725 if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0)
3726 {
3727 // So write /F directly. AFAICS it's up to PDF viewer to resolve this correctly
3728 aWriter.writeKeyAndLiteralEncrypt("/F", url, rLink.m_nObject, osl_getThreadTextEncoding());
3729 }
3730 else
3731 {
3732 aLine.append("/Win");
3733 aWriter.startDict();
3734 // INetURLObject is not good with UNC paths, use original path
3735 aWriter.writeKeyAndLiteralEncrypt("/F", url, rLink.m_nObject, osl_getThreadTextEncoding());
3736 aWriter.endDict();
3737 }
3738 }
3739 else
3740 {
3741 bool bSetRelative = false;
3742 bool bFileSpec = false;
3743 //check if relative file link is requested and if the protocol is 'file://'
3744 if( m_aContext.RelFsys && eBaseProtocol == eTargetProtocol && eTargetProtocol == INetProtocol::File )
3745 bSetRelative = true;
3746
3747 OUString aFragment = aTargetURL.GetMark( INetURLObject::DecodeMechanism::NONE /*DecodeMechanism::WithCharset*/ ); //fragment as is,
3748 if( !bSetGoToRMode )
3749 {
3750 switch( m_aContext.DefaultLinkAction )
3751 {
3752 default:
3753 case PDFWriter::URIAction :
3754 case PDFWriter::URIActionDestination :
3755 aLine.append( "/URI/URI" );
3756 break;
3757 case PDFWriter::LaunchAction:
3758 // now:
3759 // if a launch action is requested and the hyperlink target has a fragment
3760 // and the target file does not have a pdf extension, or it's not a 'file:://'
3761 // protocol then force the uri action on it
3762 // This code will permit the correct opening of application on web pages,
3763 // the one that normally have fragments (but I may be wrong...)
3764 // and will force the use of URI when the protocol is not file:
3765 if( (!aFragment.isEmpty() && !bTargetHasPDFExtension) ||
3766 eTargetProtocol != INetProtocol::File )
3767 {
3768 aLine.append( "/URI/URI" );
3769 }
3770 else
3771 {
3772 aLine.append( "/Launch/F" );
3773 bFileSpec = true;
3774 }
3775 break;
3776 }
3777 }
3778
3779 //fragment are encoded in the same way as in the named destination processing
3780 if( bSetGoToRMode )
3781 {
3782 //add the fragment
3783 OUString aURLNoMark = aTargetURL.GetURLNoMark( INetURLObject::DecodeMechanism::WithCharset );
3784 aLine.append("/GoToR");
3785 aLine.append("/F");
3786 aWriter.writeLiteralEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURLNoMark,
3787 INetURLObject::EncodeMechanism::WasEncoded,
3788 INetURLObject::DecodeMechanism::WithCharset ) :
3789 aURLNoMark, rLink.m_nObject, osl_getThreadTextEncoding());
3790 if( !aFragment.isEmpty() )
3791 {
3792 aLine.append("/D/");
3793 appendDestinationName( aFragment , aLine );
3794 }
3795 }
3796 else
3797 {
3798 // change the fragment to accommodate the bookmark (only if the file extension
3799 // is PDF and the requested action is of the correct type)
3800 if(m_aContext.DefaultLinkAction == PDFWriter::URIActionDestination &&
3801 bTargetHasPDFExtension && !aFragment.isEmpty() )
3802 {
3803 OStringBuffer aLineLoc( 1024 );
3804 appendDestinationName( aFragment , aLineLoc );
3805 //substitute the fragment
3806 aTargetURL.SetMark( OStringToOUString(aLineLoc, RTL_TEXTENCODING_ASCII_US) );
3807 }
3808 OUString aURL = bUnparsedURI ? url :
3809 aTargetURL.GetMainURL( bFileSpec ? INetURLObject::DecodeMechanism::WithCharset :
3810 INetURLObject::DecodeMechanism::NONE );
3811 aWriter.writeLiteralEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURL,
3812 INetURLObject::EncodeMechanism::WasEncoded,
3813 bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE
3814 ) :
3815 aURL , rLink.m_nObject, osl_getThreadTextEncoding() );
3816 }
3817 }
3818 aLine.append( ">>\n" );
3819 }
3820 if (rLink.m_nStructParent != -1)
3821 {
3822 aLine.append( "/StructParent " );
3823 aLine.append( rLink.m_nStructParent );
3824 }
3825 aLine.append( ">>\nendobj\n\n" );
3826 if (!writeBuffer(aLine))
3827 return false;
3828 }
3829
3830 return true;
3831 }
3832
3833 namespace
3834 {
3835
appendAnnotationRect(tools::Rectangle const & rRectangle,OStringBuffer & aLine)3836 void appendAnnotationRect(tools::Rectangle const & rRectangle, OStringBuffer & aLine)
3837 {
3838 aLine.append("/Rect [");
3839 appendFixedInt(rRectangle.Left(), aLine);
3840 aLine.append(' ');
3841 appendFixedInt(rRectangle.Top(), aLine);
3842 aLine.append(' ');
3843 appendFixedInt(rRectangle.Right(), aLine);
3844 aLine.append(' ');
3845 appendFixedInt(rRectangle.Bottom(), aLine);
3846 aLine.append("] ");
3847 }
3848
appendAnnotationColor(Color const & rColor,OStringBuffer & aLine)3849 void appendAnnotationColor(Color const& rColor, OStringBuffer & aLine)
3850 {
3851 aLine.append("/C [");
3852 appendColor(rColor, aLine, false);
3853 aLine.append("] ");
3854 }
3855
appendAnnotationInteriorColor(Color const & rColor,OStringBuffer & aLine)3856 void appendAnnotationInteriorColor(Color const& rColor, OStringBuffer & aLine)
3857 {
3858 aLine.append("/IC [");
3859 appendColor(rColor, aLine, false);
3860 aLine.append("] ");
3861 }
3862
appendPolygon(basegfx::B2DPolygon const & rPolygon,double fPageHeight,OStringBuffer & aLine)3863 void appendPolygon(basegfx::B2DPolygon const& rPolygon, double fPageHeight, OStringBuffer & aLine)
3864 {
3865 for (sal_uInt32 i = 0; i < rPolygon.count(); ++i)
3866 {
3867 appendDouble(convertMm100ToPoint(rPolygon.getB2DPoint(i).getX()), aLine, nLog10Divisor);
3868 aLine.append(" ");
3869 appendDouble(fPageHeight - convertMm100ToPoint(rPolygon.getB2DPoint(i).getY()), aLine, nLog10Divisor);
3870 aLine.append(" ");
3871 }
3872 }
3873
appendVertices(basegfx::B2DPolygon const & rPolygon,double fPageHeight,OStringBuffer & aLine)3874 void appendVertices(basegfx::B2DPolygon const& rPolygon, double fPageHeight, OStringBuffer & aLine)
3875 {
3876 aLine.append("/Vertices [");
3877 appendPolygon(rPolygon, fPageHeight, aLine);
3878 aLine.append("] ");
3879
3880 }
3881
appendAnnotationBorder(float fBorderWidth,OStringBuffer & aLine)3882 void appendAnnotationBorder(float fBorderWidth, OStringBuffer & aLine)
3883 {
3884 aLine.append("/Border [0 0 ");
3885 appendDouble(convertMm100ToPoint(fBorderWidth), aLine, nLog10Divisor);
3886 aLine.append("] ");
3887 }
3888
3889 } // end anonymous namespace
3890
emitTextAnnotationLine(OStringBuffer & aLine,PDFNoteEntry const & rNote)3891 void PDFWriterImpl::emitTextAnnotationLine(OStringBuffer & aLine, PDFNoteEntry const & rNote)
3892 {
3893 COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
3894
3895 appendObjectID(rNote.m_nObject, aLine);
3896
3897 double fPageHeight = m_aPages[rNote.m_nPage].getHeight();
3898
3899 aLine.append("<</Type /Annot ");
3900
3901 appendAnnotationRect(rNote.m_aRect, aLine);
3902
3903 aLine.append("/Subtype ");
3904
3905 if (rNote.m_aContents.meType == vcl::pdf::PDFAnnotationSubType::Polygon ||
3906 rNote.m_aContents.meType == vcl::pdf::PDFAnnotationSubType::Polyline)
3907 {
3908 auto const& rPolygon = rNote.m_aContents.maPolygons[0];
3909 if (rNote.m_aContents.meType == vcl::pdf::PDFAnnotationSubType::Polygon)
3910 aLine.append("/Polygon ");
3911 else
3912 aLine.append("/Polyline ");
3913
3914 appendVertices(rPolygon, fPageHeight, aLine);
3915 appendAnnotationColor(rNote.m_aContents.maAnnotationColor, aLine);
3916
3917 if (rNote.m_aContents.meType == vcl::pdf::PDFAnnotationSubType::Polygon)
3918 appendAnnotationInteriorColor(rNote.m_aContents.maInteriorColor, aLine);
3919 appendAnnotationBorder(rNote.m_aContents.mfWidth, aLine);
3920 }
3921 else if (rNote.m_aContents.meType == vcl::pdf::PDFAnnotationSubType::Square)
3922 {
3923 aLine.append("/Square ");
3924 appendAnnotationColor(rNote.m_aContents.maAnnotationColor, aLine);
3925 appendAnnotationInteriorColor(rNote.m_aContents.maInteriorColor, aLine);
3926 appendAnnotationBorder(rNote.m_aContents.mfWidth, aLine);
3927 }
3928 else if (rNote.m_aContents.meType == vcl::pdf::PDFAnnotationSubType::Circle)
3929 {
3930 aLine.append("/Circle ");
3931 appendAnnotationColor(rNote.m_aContents.maAnnotationColor, aLine);
3932 appendAnnotationInteriorColor(rNote.m_aContents.maInteriorColor, aLine);
3933 appendAnnotationBorder(rNote.m_aContents.mfWidth, aLine);
3934 }
3935 else if (rNote.m_aContents.meType == vcl::pdf::PDFAnnotationSubType::Ink)
3936 {
3937 aLine.append("/Ink ");
3938
3939 aLine.append("/InkList [");
3940 for (auto const& rPolygon : rNote.m_aContents.maPolygons)
3941 {
3942 aLine.append("[");
3943 appendPolygon(rPolygon, fPageHeight, aLine);
3944 aLine.append("]");
3945 }
3946 aLine.append("] ");
3947
3948 appendAnnotationColor(rNote.m_aContents.maAnnotationColor, aLine);
3949 appendAnnotationBorder(rNote.m_aContents.mfWidth, aLine);
3950
3951 }
3952 else if (rNote.m_aContents.meType == vcl::pdf::PDFAnnotationSubType::FreeText)
3953 {
3954 aLine.append("/FreeText ");
3955 }
3956 else
3957 {
3958 aLine.append("/Text ");
3959 }
3960
3961 // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
3962 // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
3963 if (m_nPDFA_Version > 0)
3964 aLine.append("/F 4 ");
3965
3966 aLine.append("/Popup ");
3967 appendObjectReference(rNote.m_aPopUpAnnotation.m_nObject, aLine);
3968
3969 auto & rDateTime = rNote.m_aContents.maModificationDate;
3970
3971 aLine.append("/M (");
3972 appendPdfTimeDate(aLine, rDateTime.Year, rDateTime.Month, rDateTime.Day, rDateTime.Hours, rDateTime.Minutes, rDateTime.Seconds, 0);
3973 aLine.append(") ");
3974
3975 // contents of the note (type text string)
3976 aLine.append("/Contents ");
3977 aWriter.writeUnicodeEncrypt(rNote.m_aContents.maContents, rNote.m_nObject);
3978 aLine.append("\n");
3979
3980 // optional title
3981 if (!rNote.m_aContents.maTitle.isEmpty())
3982 {
3983 aLine.append("/T ");
3984 aWriter.writeUnicodeEncrypt(rNote.m_aContents.maTitle, rNote.m_nObject);
3985 aLine.append("\n");
3986 }
3987
3988 if (-1 != rNote.m_nStructParent)
3989 {
3990 aLine.append("/StructParent ");
3991 aLine.append(rNote.m_nStructParent);
3992 aLine.append("\n");
3993 }
3994
3995 aLine.append(">>\n");
3996 aLine.append("endobj\n\n");
3997 }
3998
emitPopupAnnotationLine(OStringBuffer & aLine,PDFPopupAnnotation const & rPopUp)3999 void PDFWriterImpl::emitPopupAnnotationLine(OStringBuffer & aLine, PDFPopupAnnotation const & rPopUp)
4000 {
4001 appendObjectID(rPopUp.m_nObject, aLine);
4002 aLine.append("<</Type /Annot /Subtype /Popup ");
4003 aLine.append("/Rect[");
4004 appendFixedInt(rPopUp.m_aRect.Left(), aLine);
4005 aLine.append(' ');
4006 appendFixedInt(rPopUp.m_aRect.Top(), aLine);
4007 aLine.append(' ');
4008 appendFixedInt(rPopUp.m_aRect.Right(), aLine);
4009 aLine.append(' ');
4010 appendFixedInt(rPopUp.m_aRect.Bottom(), aLine);
4011 aLine.append("]");
4012 aLine.append("/Parent ");
4013 appendObjectReference(rPopUp.m_nParentObject, aLine);
4014 aLine.append(">>\n");
4015 aLine.append("endobj\n\n");
4016 }
4017
emitNoteAnnotations()4018 bool PDFWriterImpl::emitNoteAnnotations()
4019 {
4020 // emit note annotations
4021 int nAnnots = m_aNotes.size();
4022 for( int i = 0; i < nAnnots; i++ )
4023 {
4024 const PDFNoteEntry& rNote = m_aNotes[i];
4025 const PDFPopupAnnotation& rPopUp = rNote.m_aPopUpAnnotation;
4026
4027 {
4028 if (!updateObject(rNote.m_nObject))
4029 return false;
4030
4031 OStringBuffer aLine(1024);
4032
4033 emitTextAnnotationLine(aLine, rNote);
4034
4035 if (!writeBuffer(aLine))
4036 return false;
4037 }
4038
4039 {
4040
4041 if (!updateObject(rPopUp.m_nObject))
4042 return false;
4043
4044 OStringBuffer aLine(1024);
4045
4046 emitPopupAnnotationLine(aLine, rPopUp);
4047
4048 if (!writeBuffer(aLine))
4049 return false;
4050 }
4051 }
4052 return true;
4053 }
4054
replaceFont(const vcl::Font & rControlFont,const vcl::Font & rAppSetFont)4055 Font PDFWriterImpl::replaceFont( const vcl::Font& rControlFont, const vcl::Font& rAppSetFont )
4056 {
4057 bool bAdjustSize = false;
4058
4059 Font aFont( rControlFont );
4060 if( aFont.GetFamilyName().isEmpty() )
4061 {
4062 aFont = rAppSetFont;
4063 if( rControlFont.GetFontHeight() )
4064 aFont.SetFontSize( Size( 0, rControlFont.GetFontHeight() ) );
4065 else
4066 bAdjustSize = true;
4067 if( rControlFont.GetItalic() != ITALIC_DONTKNOW )
4068 aFont.SetItalic( rControlFont.GetItalic() );
4069 if( rControlFont.GetWeight() != WEIGHT_DONTKNOW )
4070 aFont.SetWeight( rControlFont.GetWeight() );
4071 }
4072 else if( ! aFont.GetFontHeight() )
4073 {
4074 aFont.SetFontSize( rAppSetFont.GetFontSize() );
4075 bAdjustSize = true;
4076 }
4077 if( bAdjustSize )
4078 {
4079 Size aFontSize = aFont.GetFontSize();
4080 OutputDevice* pDefDev = Application::GetDefaultDevice();
4081 aFontSize = OutputDevice::LogicToLogic( aFontSize, pDefDev->GetMapMode(), getMapMode() );
4082 aFont.SetFontSize( aFontSize );
4083 }
4084 return aFont;
4085 }
4086
getBestBuildinFont(const vcl::Font & rFont)4087 sal_Int32 PDFWriterImpl::getBestBuildinFont( const vcl::Font& rFont )
4088 {
4089 sal_Int32 nBest = 4; // default to Helvetica
4090
4091 if (rFont.GetFamilyType() == FAMILY_ROMAN)
4092 {
4093 // Serif: default to Times-Roman.
4094 nBest = 8;
4095 }
4096
4097 OUString aFontName( rFont.GetFamilyName() );
4098 aFontName = aFontName.toAsciiLowerCase();
4099
4100 if( aFontName.indexOf( "times" ) != -1 )
4101 nBest = 8;
4102 else if( aFontName.indexOf( "courier" ) != -1 )
4103 nBest = 0;
4104 else if( aFontName.indexOf( "dingbats" ) != -1 )
4105 nBest = 13;
4106 else if( aFontName.indexOf( "symbol" ) != -1 )
4107 nBest = 12;
4108 if( nBest < 12 )
4109 {
4110 if( rFont.GetItalic() == ITALIC_OBLIQUE || rFont.GetItalic() == ITALIC_NORMAL )
4111 nBest += 1;
4112 if( rFont.GetWeight() > WEIGHT_MEDIUM )
4113 nBest += 2;
4114 }
4115
4116 if( m_aBuildinFontToObjectMap.find( nBest ) == m_aBuildinFontToObjectMap.end() )
4117 m_aBuildinFontToObjectMap[ nBest ] = createObject();
4118
4119 return nBest;
4120 }
4121
replaceColor(const Color & rCol1,const Color & rCol2)4122 static const Color& replaceColor( const Color& rCol1, const Color& rCol2 )
4123 {
4124 return (rCol1 == COL_TRANSPARENT) ? rCol2 : rCol1;
4125 }
4126
createDefaultPushButtonAppearance(PDFWidget & rButton,const PDFWriter::PushButtonWidget & rWidget)4127 void PDFWriterImpl::createDefaultPushButtonAppearance( PDFWidget& rButton, const PDFWriter::PushButtonWidget& rWidget )
4128 {
4129 const StyleSettings& rSettings = m_aWidgetStyleSettings;
4130
4131 // save graphics state
4132 push( PushFlags::ALL );
4133
4134 // transform relative to control's coordinates since an
4135 // appearance stream is a form XObject
4136 // this relies on the m_aRect member of rButton NOT already being transformed
4137 // to default user space
4138 if( rWidget.Background || rWidget.Border )
4139 {
4140 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetLightColor() ) : COL_TRANSPARENT );
4141 setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetDialogColor() ) : COL_TRANSPARENT );
4142 drawRectangle( rWidget.Location );
4143 }
4144 // prepare font to use
4145 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetPushButtonFont() );
4146 setFont( aFont );
4147 setTextColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ) );
4148
4149 drawText( rButton.m_aRect, rButton.m_aText, rButton.m_nTextStyle );
4150
4151 // create DA string while local mapmode is still in place
4152 // (that is before endRedirect())
4153 OStringBuffer aDA( 256 );
4154 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ), aDA );
4155 Font aDummyFont( u"Helvetica"_ustr, aFont.GetFontSize() );
4156 sal_Int32 nDummyBuildin = getBestBuildinFont( aDummyFont );
4157 aDA.append( ' ' );
4158 aDA.append(pdf::BuildinFontFace::Get(nDummyBuildin).getNameObject());
4159 aDA.append( ' ' );
4160 m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
4161 aDA.append( " Tf" );
4162 rButton.m_aDAString = aDA.makeStringAndClear();
4163
4164 pop();
4165
4166 rButton.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = new SvMemoryStream();
4167
4168 /* seems like a bad hack but at least works in both AR5 and 6:
4169 we draw the button ourselves and tell AR
4170 the button would be totally transparent with no text
4171
4172 One would expect that simply setting a normal appearance
4173 should suffice, but no, as soon as the user actually presses
4174 the button and an action is tied to it (gasp! a button that
4175 does something) the appearance gets replaced by some crap that AR
4176 creates on the fly even if no DA or MK is given. On AR6 at least
4177 the DA and MK work as expected, but on AR5 this creates a region
4178 filled with the background color but nor text. Urgh.
4179 */
4180 rButton.m_aMKDict = "/BC [] /BG [] /CA"_ostr;
4181 rButton.m_aMKDictCAString = ""_ostr;
4182 }
4183
drawFieldBorder(PDFWidget & rIntern,const PDFWriter::AnyWidget & rWidget,const StyleSettings & rSettings)4184 Font PDFWriterImpl::drawFieldBorder( PDFWidget& rIntern,
4185 const PDFWriter::AnyWidget& rWidget,
4186 const StyleSettings& rSettings )
4187 {
4188 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetFieldFont() );
4189
4190 if( rWidget.Background || rWidget.Border )
4191 {
4192 if( rWidget.Border && rWidget.BorderColor == COL_TRANSPARENT )
4193 {
4194 sal_Int32 nDelta = GetDPIX() / 500;
4195 if( nDelta < 1 )
4196 nDelta = 1;
4197 setLineColor( COL_TRANSPARENT );
4198 tools::Rectangle aRect = rIntern.m_aRect;
4199 setFillColor( rSettings.GetLightBorderColor() );
4200 drawRectangle( aRect );
4201 aRect.AdjustLeft(nDelta ); aRect.AdjustTop(nDelta );
4202 aRect.AdjustRight( -nDelta ); aRect.AdjustBottom( -nDelta );
4203 setFillColor( rSettings.GetFieldColor() );
4204 drawRectangle( aRect );
4205 setFillColor( rSettings.GetLightColor() );
4206 drawRectangle( tools::Rectangle( Point( aRect.Left(), aRect.Bottom()-nDelta ), aRect.BottomRight() ) );
4207 drawRectangle( tools::Rectangle( Point( aRect.Right()-nDelta, aRect.Top() ), aRect.BottomRight() ) );
4208 setFillColor( rSettings.GetDarkShadowColor() );
4209 drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Left()+nDelta, aRect.Bottom() ) ) );
4210 drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Right(), aRect.Top()+nDelta ) ) );
4211 }
4212 else
4213 {
4214 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetShadowColor() ) : COL_TRANSPARENT );
4215 setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
4216 drawRectangle( rIntern.m_aRect );
4217 }
4218
4219 if( rWidget.Border )
4220 {
4221 // adjust edit area accounting for border
4222 sal_Int32 nDelta = aFont.GetFontHeight()/4;
4223 if( nDelta < 1 )
4224 nDelta = 1;
4225 rIntern.m_aRect.AdjustLeft(nDelta );
4226 rIntern.m_aRect.AdjustTop(nDelta );
4227 rIntern.m_aRect.AdjustRight( -nDelta );
4228 rIntern.m_aRect.AdjustBottom( -nDelta );
4229 }
4230 }
4231 return aFont;
4232 }
4233
createDefaultEditAppearance(PDFWidget & rEdit,const PDFWriter::EditWidget & rWidget)4234 void PDFWriterImpl::createDefaultEditAppearance( PDFWidget& rEdit, const PDFWriter::EditWidget& rWidget )
4235 {
4236 const StyleSettings& rSettings = m_aWidgetStyleSettings;
4237 SvMemoryStream* pEditStream = new SvMemoryStream( 1024, 1024 );
4238
4239 push( PushFlags::ALL );
4240
4241 // prepare font to use, draw field border
4242 Font aFont = drawFieldBorder( rEdit, rWidget, rSettings );
4243 // Get the built-in font which is closest to aFont.
4244 sal_Int32 nBest = getBestBuildinFont(aFont);
4245
4246 // prepare DA string
4247 OStringBuffer aDA( 32 );
4248 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
4249 aDA.append( ' ' );
4250 aDA.append(pdf::BuildinFontFace::Get(nBest).getNameObject());
4251
4252 OStringBuffer aDR( 32 );
4253 aDR.append( "/Font " );
4254 aDR.append( getFontDictObject() );
4255 aDR.append( " 0 R" );
4256 rEdit.m_aDRDict = aDR.makeStringAndClear();
4257 aDA.append( ' ' );
4258 m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
4259 aDA.append( " Tf" );
4260
4261 /* create an empty appearance stream, let the viewer create
4262 the appearance at runtime. This is because AR5 seems to
4263 paint the widget appearance always, and a dynamically created
4264 appearance on top of it. AR6 is well behaved in that regard, so
4265 that behaviour seems to be a bug. Anyway this empty appearance
4266 relies on /NeedAppearances in the AcroForm dictionary set to "true"
4267 */
4268 beginRedirect( pEditStream, rEdit.m_aRect );
4269 writeBuffer( "/Tx BMC\nEMC\n" );
4270
4271 endRedirect();
4272 pop();
4273
4274 rEdit.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = pEditStream;
4275
4276 rEdit.m_aDAString = aDA.makeStringAndClear();
4277 }
4278
createDefaultListBoxAppearance(PDFWidget & rBox,const PDFWriter::ListBoxWidget & rWidget)4279 void PDFWriterImpl::createDefaultListBoxAppearance( PDFWidget& rBox, const PDFWriter::ListBoxWidget& rWidget )
4280 {
4281 const StyleSettings& rSettings = m_aWidgetStyleSettings;
4282 SvMemoryStream* pListBoxStream = new SvMemoryStream( 1024, 1024 );
4283
4284 push( PushFlags::ALL );
4285
4286 // prepare font to use, draw field border
4287 Font aFont = drawFieldBorder( rBox, rWidget, rSettings );
4288 sal_Int32 nBest = getSystemFont( aFont );
4289
4290 setLineColor( COL_TRANSPARENT );
4291 setFillColor( replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) );
4292 drawRectangle( rBox.m_aRect );
4293
4294 pop();
4295
4296 // prepare DA string
4297 OStringBuffer aDA( 256 );
4298 // prepare DA string
4299 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
4300 aDA.append( ' ' );
4301 aDA.append( "/F" );
4302 aDA.append( nBest );
4303
4304 OStringBuffer aDR( 32 );
4305 aDR.append( "/Font " );
4306 aDR.append( getFontDictObject() );
4307 aDR.append( " 0 R" );
4308 rBox.m_aDRDict = aDR.makeStringAndClear();
4309 aDA.append( ' ' );
4310 m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
4311 aDA.append( " Tf" );
4312
4313 beginRedirect(pListBoxStream, rBox.m_aRect);
4314 // empty appearance, see createDefaultEditAppearance for reference
4315 writeBuffer("/Tx BMC\nEMC\n");
4316 endRedirect();
4317
4318 rBox.m_aAppearances["N"_ostr]["Standard"_ostr] = pListBoxStream;
4319 rBox.m_aDAString = aDA.makeStringAndClear();
4320 }
4321
createDefaultCheckBoxAppearance(PDFWidget & rBox,const PDFWriter::CheckBoxWidget & rWidget)4322 void PDFWriterImpl::createDefaultCheckBoxAppearance( PDFWidget& rBox, const PDFWriter::CheckBoxWidget& rWidget )
4323 {
4324 const StyleSettings& rSettings = m_aWidgetStyleSettings;
4325
4326 // save graphics state
4327 push( PushFlags::ALL );
4328
4329 if( rWidget.Background || rWidget.Border )
4330 {
4331 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
4332 setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
4333 drawRectangle( rBox.m_aRect );
4334 }
4335
4336 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
4337 setFont( aFont );
4338 Size aFontSize = aFont.GetFontSize();
4339 if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
4340 aFontSize.setHeight( rBox.m_aRect.GetHeight() );
4341 sal_Int32 nDelta = aFontSize.Height()/10;
4342 if( nDelta < 1 )
4343 nDelta = 1;
4344
4345 tools::Rectangle aCheckRect, aTextRect;
4346 {
4347 aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
4348 aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
4349 aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
4350 aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
4351
4352 // #i74206# handle small controls without text area
4353 while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
4354 {
4355 aCheckRect.AdjustRight( -nDelta );
4356 aCheckRect.AdjustTop(nDelta/2 );
4357 aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
4358 }
4359
4360 aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
4361 aTextRect.SetTop( rBox.m_aRect.Top() );
4362 aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
4363 aTextRect.SetBottom( rBox.m_aRect.Bottom() );
4364 }
4365 setLineColor( COL_BLACK );
4366 setFillColor( COL_TRANSPARENT );
4367 OStringBuffer aLW( 32 );
4368 aLW.append( "q " );
4369 m_aPages[m_nCurrentPage].appendMappedLength( nDelta, aLW );
4370 aLW.append( " w " );
4371 writeBuffer( aLW );
4372 drawRectangle( aCheckRect );
4373 writeBuffer( " Q\n" );
4374 setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4375 drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
4376
4377 pop();
4378
4379 OStringBuffer aDA( 256 );
4380
4381 // tdf#93853 don't rely on Zapf (or any other 'standard' font)
4382 // being present, but our own OpenSymbol - N.B. PDF/A for good
4383 // reasons require even the standard PS fonts to be embedded!
4384 Push();
4385 SetFont( Font( u"OpenSymbol"_ustr, aFont.GetFontSize() ) );
4386 const LogicalFontInstance* pFontInstance = GetFontInstance();
4387 const vcl::font::PhysicalFontFace* pFace = pFontInstance->GetFontFace();
4388 Pop();
4389
4390 // make sure OpenSymbol is embedded, and includes our checkmark
4391 const sal_Unicode cMark=0x2713;
4392 const auto nGlyphId = pFontInstance->GetGlyphIndex(cMark);
4393 const auto nGlyphWidth = pFontInstance->GetGlyphWidth(nGlyphId, false, false);
4394
4395 sal_uInt8 nMappedGlyph;
4396 sal_Int32 nMappedFontObject;
4397 registerGlyph(nGlyphId, pFace, pFontInstance, { cMark }, nGlyphWidth, nMappedGlyph, nMappedFontObject);
4398
4399 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
4400 aDA.append( ' ' );
4401 aDA.append( "/F" );
4402 aDA.append( nMappedFontObject );
4403 aDA.append( " 0 Tf" );
4404
4405 OStringBuffer aDR( 32 );
4406 aDR.append( "/Font " );
4407 aDR.append( getFontDictObject() );
4408 aDR.append( " 0 R" );
4409 rBox.m_aDRDict = aDR.makeStringAndClear();
4410 rBox.m_aDAString = aDA.makeStringAndClear();
4411 rBox.m_aMKDict = "/CA"_ostr;
4412 rBox.m_aMKDictCAString = "8"_ostr;
4413 rBox.m_aRect = aCheckRect;
4414
4415 // create appearance streams
4416 sal_Int32 nCharXOffset = 1000 - 787; // metrics from OpenSymbol
4417 nCharXOffset *= aCheckRect.GetHeight();
4418 nCharXOffset /= 2000;
4419 sal_Int32 nCharYOffset = 1000 - (820-143); // metrics from Zapf
4420 nCharYOffset *= aCheckRect.GetHeight();
4421 nCharYOffset /= 2000;
4422
4423 // write 'checked' appearance stream
4424 SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
4425 beginRedirect( pCheckStream, aCheckRect );
4426 aDA.append( "/Tx BMC\nq BT\n" );
4427 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
4428 aDA.append( ' ' );
4429 aDA.append( "/F" );
4430 aDA.append( nMappedFontObject );
4431 aDA.append( ' ' );
4432 m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
4433 aDA.append( " Tf\n" );
4434 m_aPages[ m_nCurrentPage ].appendMappedLength( nCharXOffset, aDA );
4435 aDA.append( " " );
4436 m_aPages[ m_nCurrentPage ].appendMappedLength( nCharYOffset, aDA );
4437 aDA.append( " Td <" );
4438 COSWriter::appendHex( nMappedGlyph, aDA );
4439 aDA.append( "> Tj\nET\nQ\nEMC\n" );
4440 writeBuffer( aDA );
4441 endRedirect();
4442 rBox.m_aAppearances[ "N"_ostr ][ "Yes"_ostr ] = pCheckStream;
4443
4444 // write 'unchecked' appearance stream
4445 SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
4446 beginRedirect( pUncheckStream, aCheckRect );
4447 writeBuffer( "/Tx BMC\nEMC\n" );
4448 endRedirect();
4449 rBox.m_aAppearances[ "N"_ostr ][ "Off"_ostr ] = pUncheckStream;
4450 }
4451
createDefaultRadioButtonAppearance(PDFWidget & rBox,const PDFWriter::RadioButtonWidget & rWidget)4452 void PDFWriterImpl::createDefaultRadioButtonAppearance( PDFWidget& rBox, const PDFWriter::RadioButtonWidget& rWidget )
4453 {
4454 const StyleSettings& rSettings = m_aWidgetStyleSettings;
4455
4456 // save graphics state
4457 push( PushFlags::ALL );
4458
4459 if( rWidget.Background || rWidget.Border )
4460 {
4461 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
4462 setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
4463 drawRectangle( rBox.m_aRect );
4464 }
4465
4466 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
4467 setFont( aFont );
4468 Size aFontSize = aFont.GetFontSize();
4469 if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
4470 aFontSize.setHeight( rBox.m_aRect.GetHeight() );
4471 sal_Int32 nDelta = aFontSize.Height()/10;
4472 if( nDelta < 1 )
4473 nDelta = 1;
4474
4475 tools::Rectangle aCheckRect, aTextRect;
4476 {
4477 aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
4478 aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
4479 aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
4480 aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
4481
4482 // #i74206# handle small controls without text area
4483 while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
4484 {
4485 aCheckRect.AdjustRight( -nDelta );
4486 aCheckRect.AdjustTop(nDelta/2 );
4487 aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
4488 }
4489
4490 aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
4491 aTextRect.SetTop( rBox.m_aRect.Top() );
4492 aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
4493 aTextRect.SetBottom( rBox.m_aRect.Bottom() );
4494 }
4495 setLineColor( COL_BLACK );
4496 setFillColor( COL_TRANSPARENT );
4497 OStringBuffer aLW( 32 );
4498 aLW.append( "q " );
4499 m_aPages[ m_nCurrentPage ].appendMappedLength( nDelta, aLW );
4500 aLW.append( " w " );
4501 writeBuffer( aLW );
4502 drawEllipse( aCheckRect );
4503 writeBuffer( " Q\n" );
4504 setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4505 drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
4506
4507 pop();
4508
4509 //to encrypt this (el)
4510 rBox.m_aMKDict = "/CA"_ostr;
4511 //after this assignment, to m_aMKDic cannot be added anything
4512 rBox.m_aMKDictCAString = "l"_ostr;
4513
4514 rBox.m_aRect = aCheckRect;
4515
4516 // create appearance streams
4517 push( PushFlags::ALL);
4518 SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
4519
4520 beginRedirect( pCheckStream, aCheckRect );
4521 OStringBuffer aDA( 256 );
4522 aDA.append( "/Tx BMC\nq BT\n" );
4523 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
4524 aDA.append( ' ' );
4525 m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
4526 aDA.append( " 0 0 Td\nET\nQ\n" );
4527 writeBuffer( aDA );
4528 setFillColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4529 setLineColor( COL_TRANSPARENT );
4530 aCheckRect.AdjustLeft(3*nDelta );
4531 aCheckRect.AdjustTop(3*nDelta );
4532 aCheckRect.AdjustBottom( -(3*nDelta) );
4533 aCheckRect.AdjustRight( -(3*nDelta) );
4534 drawEllipse( aCheckRect );
4535 writeBuffer( "\nEMC\n" );
4536 endRedirect();
4537
4538 pop();
4539 rBox.m_aAppearances[ "N"_ostr ][ "Yes"_ostr ] = pCheckStream;
4540
4541 SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
4542 beginRedirect( pUncheckStream, aCheckRect );
4543 writeBuffer( "/Tx BMC\nEMC\n" );
4544 endRedirect();
4545 rBox.m_aAppearances[ "N"_ostr ][ "Off"_ostr ] = pUncheckStream;
4546 }
4547
emitAppearances(PDFWidget & rWidget,OStringBuffer & rAnnotDict)4548 bool PDFWriterImpl::emitAppearances( PDFWidget& rWidget, OStringBuffer& rAnnotDict )
4549 {
4550 // TODO: check and insert default streams
4551 OString aStandardAppearance;
4552 switch( rWidget.m_eType )
4553 {
4554 case PDFWriter::CheckBox:
4555 aStandardAppearance = OUStringToOString( rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US );
4556 break;
4557 default:
4558 break;
4559 }
4560
4561 if( !rWidget.m_aAppearances.empty() )
4562 {
4563 rAnnotDict.append( "/AP<<\n" );
4564 for (auto & dict_item : rWidget.m_aAppearances)
4565 {
4566 rAnnotDict.append( "/" );
4567 rAnnotDict.append( dict_item.first );
4568 bool bUseSubDict = (dict_item.second.size() > 1);
4569
4570 // PDF/A requires sub-dicts for /FT/Btn objects (clause
4571 // 6.3.3)
4572 if (m_nPDFA_Version > 0)
4573 {
4574 if( rWidget.m_eType == PDFWriter::RadioButton ||
4575 rWidget.m_eType == PDFWriter::CheckBox ||
4576 rWidget.m_eType == PDFWriter::PushButton )
4577 {
4578 bUseSubDict = true;
4579 }
4580 }
4581
4582 rAnnotDict.append( bUseSubDict ? "<<" : " " );
4583
4584 for (auto const& stream_item : dict_item.second)
4585 {
4586 SvMemoryStream* pAppearanceStream = stream_item.second;
4587 dict_item.second[ stream_item.first ] = nullptr;
4588
4589 bool bDeflate = compressStream( pAppearanceStream );
4590
4591 sal_Int64 nStreamLen = pAppearanceStream->TellEnd();
4592 pAppearanceStream->Seek( STREAM_SEEK_TO_BEGIN );
4593 sal_Int32 nObject = createObject();
4594 if (!updateObject(nObject))
4595 return false;
4596 if (g_bDebugDisableCompression)
4597 {
4598 emitComment( "PDFWriterImpl::emitAppearances" );
4599 }
4600 OStringBuffer aLine;
4601 aLine.append( nObject );
4602
4603 aLine.append( " 0 obj\n"
4604 "<</Type/XObject\n"
4605 "/Subtype/Form\n"
4606 "/BBox[0 0 " );
4607 appendFixedInt( rWidget.m_aRect.GetWidth()-1, aLine );
4608 aLine.append( " " );
4609 appendFixedInt( rWidget.m_aRect.GetHeight()-1, aLine );
4610 aLine.append( "]\n"
4611 "/Resources " );
4612 aLine.append( getResourceDictObj() );
4613 aLine.append( " 0 R\n"
4614 "/Length " );
4615 aLine.append( nStreamLen );
4616 aLine.append( "\n" );
4617 if( bDeflate )
4618 aLine.append( "/Filter/FlateDecode\n" );
4619 aLine.append( ">>\nstream\n" );
4620 if (!writeBuffer(aLine)) return false;
4621 checkAndEnableStreamEncryption( nObject );
4622 if (!writeBufferBytes( pAppearanceStream->GetData(), nStreamLen)) return false;
4623 disableStreamEncryption();
4624 if (!writeBuffer("\nendstream\nendobj\n\n")) return false;
4625
4626 if( bUseSubDict )
4627 {
4628 rAnnotDict.append( " /" );
4629 rAnnotDict.append( stream_item.first );
4630 rAnnotDict.append( " " );
4631 }
4632 rAnnotDict.append( nObject );
4633 rAnnotDict.append( " 0 R" );
4634
4635 delete pAppearanceStream;
4636 }
4637
4638 rAnnotDict.append( bUseSubDict ? ">>\n" : "\n" );
4639 }
4640 rAnnotDict.append( ">>\n" );
4641 if( !aStandardAppearance.isEmpty() )
4642 {
4643 rAnnotDict.append( "/AS /" );
4644 rAnnotDict.append( aStandardAppearance );
4645 rAnnotDict.append( "\n" );
4646 }
4647 }
4648
4649 return true;
4650 }
4651
emitWidgetAnnotations()4652 bool PDFWriterImpl::emitWidgetAnnotations()
4653 {
4654 ensureUniqueRadioOnValues();
4655
4656 int nAnnots = m_aWidgets.size();
4657 for( int a = 0; a < nAnnots; a++ )
4658 {
4659 PDFWidget& rWidget = m_aWidgets[a];
4660
4661 if( rWidget.m_eType == PDFWriter::CheckBox )
4662 {
4663 if ( !rWidget.m_aOnValue.isEmpty() )
4664 {
4665 auto app_it = rWidget.m_aAppearances.find( "N"_ostr );
4666 if( app_it != rWidget.m_aAppearances.end() )
4667 {
4668 auto stream_it = app_it->second.find( "Yes"_ostr );
4669 if( stream_it != app_it->second.end() )
4670 {
4671 SvMemoryStream* pStream = stream_it->second;
4672 app_it->second.erase( stream_it );
4673 OStringBuffer aBuf( rWidget.m_aOnValue.getLength()*2 );
4674 COSWriter::appendName( rWidget.m_aOnValue, aBuf );
4675 (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
4676 }
4677 else
4678 SAL_INFO("vcl.pdfwriter", "error: CheckBox without \"Yes\" stream" );
4679 }
4680 }
4681
4682 if ( !rWidget.m_aOffValue.isEmpty() )
4683 {
4684 auto app_it = rWidget.m_aAppearances.find( "N"_ostr );
4685 if( app_it != rWidget.m_aAppearances.end() )
4686 {
4687 auto stream_it = app_it->second.find( "Off"_ostr );
4688 if( stream_it != app_it->second.end() )
4689 {
4690 SvMemoryStream* pStream = stream_it->second;
4691 app_it->second.erase( stream_it );
4692 OStringBuffer aBuf( rWidget.m_aOffValue.getLength()*2 );
4693 COSWriter::appendName( rWidget.m_aOffValue, aBuf );
4694 (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
4695 }
4696 else
4697 SAL_INFO("vcl.pdfwriter", "error: CheckBox without \"Off\" stream" );
4698 }
4699 }
4700 }
4701
4702 OStringBuffer aLine( 1024 );
4703 COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
4704 OStringBuffer aValue( 256 );
4705 COSWriter aValueWriter(aValue, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
4706 aLine.append( rWidget.m_nObject );
4707 aLine.append( " 0 obj\n"
4708 "<<" );
4709 if( rWidget.m_eType != PDFWriter::Hierarchy )
4710 {
4711 // emit widget annotation only for terminal fields
4712 if( rWidget.m_aKids.empty() )
4713 {
4714 int iRectMargin;
4715
4716 aLine.append( "/Type/Annot/Subtype/Widget/F " );
4717
4718 if (rWidget.m_eType == PDFWriter::Signature)
4719 {
4720 aLine.append( "132\n" ); // Print & Locked
4721 iRectMargin = 0;
4722 }
4723 else
4724 {
4725 aLine.append( "4\n" );
4726 iRectMargin = 1;
4727 }
4728
4729 if (-1 != rWidget.m_nStructParent)
4730 {
4731 aLine.append("/StructParent ");
4732 aLine.append(rWidget.m_nStructParent);
4733 aLine.append("\n");
4734 }
4735
4736 aLine.append("/Rect[" );
4737 appendFixedInt( rWidget.m_aRect.Left()-iRectMargin, aLine );
4738 aLine.append( ' ' );
4739 appendFixedInt( rWidget.m_aRect.Top()+iRectMargin, aLine );
4740 aLine.append( ' ' );
4741 appendFixedInt( rWidget.m_aRect.Right()+iRectMargin, aLine );
4742 aLine.append( ' ' );
4743 appendFixedInt( rWidget.m_aRect.Bottom()-iRectMargin, aLine );
4744 aLine.append( "]\n" );
4745 }
4746 aLine.append( "/FT/" );
4747 switch( rWidget.m_eType )
4748 {
4749 case PDFWriter::RadioButton:
4750 case PDFWriter::CheckBox:
4751 // for radio buttons only the RadioButton field, not the
4752 // CheckBox children should have a value, else acrobat reader
4753 // does not always check the right button
4754 // of course real check boxes (not belonging to a radio group)
4755 // need their values, too
4756 if( rWidget.m_eType == PDFWriter::RadioButton || rWidget.m_nRadioGroup < 0 )
4757 {
4758 aValue.append( "/" );
4759 // check for radio group with all buttons unpressed
4760 if( rWidget.m_aValue.isEmpty() )
4761 aValue.append( "Off" );
4762 else
4763 COSWriter::appendName( rWidget.m_aValue, aValue );
4764 }
4765 [[fallthrough]];
4766 case PDFWriter::PushButton:
4767 aLine.append( "Btn" );
4768 break;
4769 case PDFWriter::ListBox:
4770 if( rWidget.m_nFlags & 0x200000 ) // multiselect
4771 {
4772 aValue.append( "[" );
4773 for( size_t i = 0; i < rWidget.m_aSelectedEntries.size(); i++ )
4774 {
4775 sal_Int32 nEntry = rWidget.m_aSelectedEntries[i];
4776 if( nEntry >= 0
4777 && o3tl::make_unsigned(nEntry) < rWidget.m_aListEntries.size() )
4778 {
4779 aValueWriter.writeUnicodeEncrypt(rWidget.m_aListEntries[ nEntry ], rWidget.m_nObject);
4780 }
4781 }
4782 aValue.append( "]" );
4783 }
4784 else if( !rWidget.m_aSelectedEntries.empty() &&
4785 rWidget.m_aSelectedEntries[0] >= 0 &&
4786 o3tl::make_unsigned(rWidget.m_aSelectedEntries[0]) < rWidget.m_aListEntries.size() )
4787 {
4788 aValueWriter.writeUnicodeEncrypt(rWidget.m_aListEntries[ rWidget.m_aSelectedEntries[0] ], rWidget.m_nObject);
4789 }
4790 else
4791 aValueWriter.writeUnicodeEncrypt( OUString(), rWidget.m_nObject);
4792 aLine.append( "Ch" );
4793 break;
4794 case PDFWriter::ComboBox:
4795 aValueWriter.writeUnicodeEncrypt( rWidget.m_aValue, rWidget.m_nObject);
4796 aLine.append( "Ch" );
4797 break;
4798 case PDFWriter::Edit:
4799 aLine.append( "Tx" );
4800 aValueWriter.writeUnicodeEncrypt( rWidget.m_aValue, rWidget.m_nObject);
4801 break;
4802 case PDFWriter::Signature:
4803 aLine.append( "Sig" );
4804 aValue.append(OUStringToOString(rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US));
4805 break;
4806 case PDFWriter::Hierarchy: // make the compiler happy
4807 break;
4808 }
4809 aLine.append( "\n" );
4810 aLine.append( "/P " );
4811 aLine.append( m_aPages[ rWidget.m_nPage ].m_nPageObject );
4812 aLine.append( " 0 R\n" );
4813 }
4814 if( rWidget.m_nParent )
4815 {
4816 aLine.append( "/Parent " );
4817 aLine.append( rWidget.m_nParent );
4818 aLine.append( " 0 R\n" );
4819 }
4820 if( !rWidget.m_aKids.empty() )
4821 {
4822 aLine.append( "/Kids[" );
4823 for( size_t i = 0; i < rWidget.m_aKids.size(); i++ )
4824 {
4825 aLine.append( rWidget.m_aKids[i] );
4826 aLine.append( " 0 R" );
4827 aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
4828 }
4829 aLine.append( "]\n" );
4830 }
4831 if( !rWidget.m_aName.isEmpty() )
4832 {
4833 aLine.append( "/T" );
4834 aWriter.writeLiteralEncrypt(rWidget.m_aName, rWidget.m_nObject);
4835 aLine.append( "\n" );
4836 }
4837 if (!rWidget.m_aDescription.isEmpty())
4838 {
4839 // the alternate field name should be unicode able since it is
4840 // supposed to be used in UI
4841 aLine.append( "/TU" );
4842 aWriter.writeUnicodeEncrypt(rWidget.m_aDescription, rWidget.m_nObject);
4843 aLine.append( "\n" );
4844 }
4845
4846 if( rWidget.m_nFlags )
4847 {
4848 aLine.append( "/Ff " );
4849 aLine.append( rWidget.m_nFlags );
4850 aLine.append( "\n" );
4851 }
4852 if( !aValue.isEmpty() )
4853 {
4854 OString aVal = aValue.makeStringAndClear();
4855 aLine.append( "/V " );
4856 aLine.append( aVal );
4857 aLine.append( "\n"
4858 "/DV " );
4859 aLine.append( aVal );
4860 aLine.append( "\n" );
4861 }
4862 if( rWidget.m_eType == PDFWriter::ListBox || rWidget.m_eType == PDFWriter::ComboBox )
4863 {
4864 sal_Int32 nTI = -1;
4865 aLine.append( "/Opt[\n" );
4866 sal_Int32 i = 0;
4867 for (auto const& entry : rWidget.m_aListEntries)
4868 {
4869 aWriter.writeUnicodeEncrypt(entry, rWidget.m_nObject);
4870 aLine.append( "\n" );
4871 if( entry == rWidget.m_aValue )
4872 nTI = i;
4873 ++i;
4874 }
4875 aLine.append( "]\n" );
4876 if( nTI > 0 )
4877 {
4878 aLine.append( "/TI " );
4879 aLine.append( nTI );
4880 aLine.append( "\n" );
4881 if( rWidget.m_nFlags & 0x200000 ) // Multiselect
4882 {
4883 aLine.append( "/I [" );
4884 aLine.append( nTI );
4885 aLine.append( "]\n" );
4886 }
4887 }
4888 }
4889 if( rWidget.m_eType == PDFWriter::Edit )
4890 {
4891 if ( rWidget.m_nMaxLen > 0 )
4892 {
4893 aLine.append( "/MaxLen " );
4894 aLine.append( rWidget.m_nMaxLen );
4895 aLine.append( "\n" );
4896 }
4897
4898 if ( rWidget.m_nFormat == PDFWriter::Number )
4899 {
4900 OString aHexText;
4901
4902 if ( !rWidget.m_aCurrencySymbol.isEmpty() )
4903 {
4904 // Get the hexadecimal code
4905 sal_UCS4 cChar = rWidget.m_aCurrencySymbol.iterateCodePoints(&o3tl::temporary(sal_Int32(1)), -1);
4906 aHexText = "\\\\u" + OString::number(cChar, 16);
4907 }
4908
4909 aLine.append("/AA<<\n");
4910 aLine.append("/F<</JS(AFNumber_Format\\(");
4911 aLine.append(OString::number(rWidget.m_nDecimalAccuracy));
4912 aLine.append(", 0, 0, 0, \"");
4913 aLine.append( aHexText );
4914 aLine.append("\",");
4915 aLine.append(OString::boolean(rWidget.m_bPrependCurrencySymbol));
4916 aLine.append("\\);)");
4917 aLine.append("/S/JavaScript>>\n");
4918 aLine.append("/K<</JS(AFNumber_Keystroke\\(");
4919 aLine.append(OString::number(rWidget.m_nDecimalAccuracy));
4920 aLine.append(", 0, 0, 0, \"");
4921 aLine.append( aHexText );
4922 aLine.append("\",");
4923 aLine.append(OString::boolean(rWidget.m_bPrependCurrencySymbol));
4924 aLine.append("\\);)");
4925 aLine.append("/S/JavaScript>>\n");
4926 aLine.append(">>\n");
4927 }
4928 else if ( rWidget.m_nFormat == PDFWriter::Time )
4929 {
4930 aLine.append("/AA<<\n");
4931 aLine.append("/F<</JS(AFTime_FormatEx\\(\"");
4932 aLine.append(OUStringToOString(rWidget.m_aTimeFormat, RTL_TEXTENCODING_ASCII_US));
4933 aLine.append("\"\\);)");
4934 aLine.append("/S/JavaScript>>\n");
4935 aLine.append("/K<</JS(AFTime_KeystrokeEx\\(\"");
4936 aLine.append(OUStringToOString(rWidget.m_aTimeFormat, RTL_TEXTENCODING_ASCII_US));
4937 aLine.append("\"\\);)");
4938 aLine.append("/S/JavaScript>>\n");
4939 aLine.append(">>\n");
4940 }
4941 else if ( rWidget.m_nFormat == PDFWriter::Date )
4942 {
4943 aLine.append("/AA<<\n");
4944 aLine.append("/F<</JS(AFDate_FormatEx\\(\"");
4945 aLine.append(OUStringToOString(rWidget.m_aDateFormat, RTL_TEXTENCODING_ASCII_US));
4946 aLine.append("\"\\);)");
4947 aLine.append("/S/JavaScript>>\n");
4948 aLine.append("/K<</JS(AFDate_KeystrokeEx\\(\"");
4949 aLine.append(OUStringToOString(rWidget.m_aDateFormat, RTL_TEXTENCODING_ASCII_US));
4950 aLine.append("\"\\);)");
4951 aLine.append("/S/JavaScript>>\n");
4952 aLine.append(">>\n");
4953 }
4954 }
4955 if( rWidget.m_eType == PDFWriter::PushButton )
4956 {
4957 if (!m_bIsPDF_A1)
4958 {
4959 OStringBuffer aDest;
4960 if( rWidget.m_nDest != -1 && appendDest( m_aDestinationIdTranslation[ rWidget.m_nDest ], aDest ) )
4961 {
4962 aLine.append( "/AA<</D<</Type/Action/S/GoTo/D " );
4963 aLine.append( aDest );
4964 aLine.append( ">>>>\n" );
4965 }
4966 else if( rWidget.m_aListEntries.empty() )
4967 {
4968 if( !m_bIsPDF_A2 && !m_bIsPDF_A3 )
4969 {
4970 // create a reset form action
4971 aLine.append( "/AA<</D<</Type/Action/S/ResetForm>>>>\n" );
4972 }
4973 }
4974 else if( rWidget.m_bSubmit )
4975 {
4976 // create a submit form action
4977 aLine.append( "/AA<</D<</Type/Action/S/SubmitForm/F" );
4978 aWriter.writeLiteralEncrypt(rWidget.m_aListEntries.front(), rWidget.m_nObject, osl_getThreadTextEncoding());
4979 aLine.append( "/Flags " );
4980
4981 sal_Int32 nFlags = 0;
4982 switch( m_aContext.SubmitFormat )
4983 {
4984 case PDFWriter::HTML:
4985 nFlags |= 4;
4986 break;
4987 case PDFWriter::XML:
4988 nFlags |= 32;
4989 break;
4990 case PDFWriter::PDF:
4991 nFlags |= 256;
4992 break;
4993 case PDFWriter::FDF:
4994 default:
4995 break;
4996 }
4997 if( rWidget.m_bSubmitGet )
4998 nFlags |= 8;
4999 aLine.append( nFlags );
5000 aLine.append( ">>>>\n" );
5001 }
5002 else
5003 {
5004 // create a URI action
5005 aLine.append( "/AA<</D<</Type/Action/S/URI/URI(" );
5006 aLine.append( OUStringToOString( rWidget.m_aListEntries.front(), RTL_TEXTENCODING_ASCII_US ) );
5007 aLine.append( ")>>>>\n" );
5008 }
5009 }
5010 else
5011 m_aErrors.insert( PDFWriter::Warning_FormAction_Omitted_PDFA );
5012 }
5013 if( !rWidget.m_aDAString.isEmpty() )
5014 {
5015 if( !rWidget.m_aDRDict.isEmpty() )
5016 {
5017 aLine.append( "/DR<<" );
5018 aLine.append( rWidget.m_aDRDict );
5019 aLine.append( ">>\n" );
5020 }
5021 else
5022 {
5023 aLine.append( "/DR<</Font<<" );
5024 appendBuildinFontsToDict( aLine );
5025 aLine.append( ">>>>\n" );
5026 }
5027 aLine.append( "/DA" );
5028 aWriter.writeLiteralEncrypt(rWidget.m_aDAString, rWidget.m_nObject);
5029 aLine.append( "\n" );
5030 if( rWidget.m_nTextStyle & DrawTextFlags::Center )
5031 aLine.append( "/Q 1\n" );
5032 else if( rWidget.m_nTextStyle & DrawTextFlags::Right )
5033 aLine.append( "/Q 2\n" );
5034 }
5035 // appearance characteristics for terminal fields
5036 // which are supposed to have an appearance constructed
5037 // by the viewer application
5038 if( !rWidget.m_aMKDict.isEmpty() )
5039 {
5040 aLine.append( "/MK<<" );
5041 aLine.append( rWidget.m_aMKDict );
5042 //add the CA string, encrypting it
5043 aWriter.writeLiteralEncrypt(rWidget.m_aMKDictCAString, rWidget.m_nObject);
5044 aLine.append( ">>\n" );
5045 }
5046
5047 if (!emitAppearances(rWidget, aLine)) return false;
5048
5049 aLine.append( ">>\n"
5050 "endobj\n\n" );
5051 if (!updateObject(rWidget.m_nObject)) return false;
5052 if (!writeBuffer(aLine)) return false;
5053 }
5054 return true;
5055 }
5056
emitAnnotations()5057 bool PDFWriterImpl::emitAnnotations()
5058 {
5059 if( m_aPages.empty() )
5060 return false;
5061
5062 if (!emitLinkAnnotations()) return false;
5063 if (!emitScreenAnnotations()) return false;
5064 if (!emitNoteAnnotations()) return false;
5065 if (!emitWidgetAnnotations()) return false;
5066
5067 return true;
5068 }
5069
5070 class PDFStreamIf : public cppu::WeakImplHelper< css::io::XOutputStream >
5071 {
5072 VclPtr<PDFWriterImpl> m_pWriter;
5073 bool m_bWrite;
5074 public:
PDFStreamIf(PDFWriterImpl * pWriter)5075 explicit PDFStreamIf( PDFWriterImpl* pWriter ) : m_pWriter( pWriter ), m_bWrite( true ) {}
5076
writeBytes(const css::uno::Sequence<sal_Int8> & aData)5077 virtual void SAL_CALL writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override
5078 {
5079 if( m_bWrite && aData.hasElements() )
5080 {
5081 sal_Int32 nBytes = aData.getLength();
5082 (void)m_pWriter->writeBufferBytes( aData.getConstArray(), nBytes );
5083 }
5084 }
flush()5085 virtual void SAL_CALL flush() override {}
closeOutput()5086 virtual void SAL_CALL closeOutput() override
5087 {
5088 m_bWrite = false;
5089 }
5090 };
5091
emitEmbeddedFiles()5092 bool PDFWriterImpl::emitEmbeddedFiles()
5093 {
5094 for (auto& rEmbeddedFile : m_aEmbeddedFiles)
5095 {
5096 if (!updateObject(rEmbeddedFile.m_nObject))
5097 continue;
5098
5099 sal_Int32 nSizeObject = createObject();
5100 sal_Int32 nParamsObject = createObject();
5101
5102 OStringBuffer aLine;
5103 aLine.append(rEmbeddedFile.m_nObject);
5104 aLine.append(" 0 obj\n");
5105 aLine.append("<< /Type /EmbeddedFile");
5106 if (!rEmbeddedFile.m_aSubType.isEmpty())
5107 {
5108 aLine.append("/Subtype /");
5109 COSWriter::appendName(rEmbeddedFile.m_aSubType, aLine);
5110 }
5111 aLine.append(" /Length ");
5112 appendObjectReference(nSizeObject, aLine);
5113 aLine.append(" /Params ");
5114 appendObjectReference(nParamsObject, aLine);
5115 aLine.append(">>\nstream\n");
5116 if (!writeBuffer(aLine)) return false;
5117 aLine.setLength(0);
5118
5119 sal_Int64 nSize{};
5120 if (!rEmbeddedFile.m_aDataContainer.isEmpty())
5121 {
5122 nSize = rEmbeddedFile.m_aDataContainer.getSize();
5123 checkAndEnableStreamEncryption(rEmbeddedFile.m_nObject);
5124 if (!writeBufferBytes(rEmbeddedFile.m_aDataContainer.getData(), rEmbeddedFile.m_aDataContainer.getSize()))
5125 return false;
5126 disableStreamEncryption();
5127 }
5128 else if (rEmbeddedFile.m_pStream)
5129 {
5130 checkAndEnableStreamEncryption(rEmbeddedFile.m_nObject);
5131 sal_uInt64 nBegin = getCurrentFilePosition();
5132 css::uno::Reference<css::io::XOutputStream> xStream(new PDFStreamIf(this));
5133 rEmbeddedFile.m_pStream->write(xStream);
5134 rEmbeddedFile.m_pStream.reset();
5135 xStream.clear();
5136 nSize = sal_Int64(getCurrentFilePosition() - nBegin);
5137 disableStreamEncryption();
5138 }
5139 aLine.append("\nendstream\nendobj\n\n");
5140 if (!writeBuffer(aLine)) return false;
5141 aLine.setLength(0);
5142
5143 if (!updateObject(nSizeObject))
5144 return false;
5145 aLine.append(nSizeObject);
5146 aLine.append(" 0 obj\n");
5147 aLine.append(nSize);
5148 aLine.append("\nendobj\n\n");
5149 if (!writeBuffer(aLine))
5150 return false;
5151 aLine.setLength(0);
5152
5153 if (!updateObject(nParamsObject))
5154 return false;
5155 aLine.append(nParamsObject);
5156 aLine.append(" 0 obj\n");
5157 aLine.append("<<");
5158 aLine.append("/Size ");
5159 aLine.append(nSize);
5160 aLine.append(">>");
5161 aLine.append("\nendobj\n\n");
5162 if (!writeBuffer(aLine))
5163 return false;
5164 }
5165 return true;
5166 }
5167
emitCatalog()5168 bool PDFWriterImpl::emitCatalog()
5169 {
5170 // build page tree
5171 // currently there is only one node that contains all leaves
5172
5173 // first create a page tree node id
5174 sal_Int32 nTreeNode = createObject();
5175
5176 // emit global resource dictionary (page emit needs it)
5177 if (!emitResources()) return false;
5178
5179 // emit all pages
5180 for (auto & page : m_aPages)
5181 if( ! page.emit( nTreeNode ) )
5182 return false;
5183
5184 sal_Int32 nNamedDestinationsDictionary = emitNamedDestinations();
5185
5186 sal_Int32 nOutlineDict = emitOutline();
5187
5188 // emit Output intent
5189 sal_Int32 nOutputIntentObject = emitOutputIntent();
5190
5191 // emit metadata
5192 sal_Int32 nMetadataObject = emitDocumentMetadata();
5193
5194 emitNamespaces();
5195
5196 sal_Int32 nStructureDict = 0;
5197 if(m_aStructure.size() > 1)
5198 {
5199 removePlaceholderSE(m_aStructure, m_aStructure[0]);
5200 // check if dummy structure containers are needed
5201 addInternalStructureContainer(m_aStructure[0]);
5202 nStructureDict = m_aStructure[0].m_nObject = createObject();
5203 emitStructure( m_aStructure[ 0 ] );
5204 }
5205
5206 // adjust tree node file offset
5207 if( ! updateObject( nTreeNode ) )
5208 return false;
5209
5210 // emit tree node
5211 OStringBuffer aLine( 2048 );
5212 COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
5213 aLine.append( nTreeNode );
5214 aLine.append( " 0 obj\n" );
5215 aLine.append( "<</Type/Pages\n" );
5216 aLine.append( "/Resources " );
5217 aLine.append( getResourceDictObj() );
5218 aLine.append( " 0 R\n" );
5219
5220 if( m_aPages.empty() ) // sanity check, this should not happen
5221 aLine.append( "/MediaBox[0 0 595 842]\n" ); // default A4 size in pt
5222
5223 aLine.append("/Kids[ ");
5224 unsigned int i = 0;
5225 for (const auto & page : m_aPages)
5226 {
5227 aLine.append( page.m_nPageObject );
5228 aLine.append( " 0 R" );
5229 aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
5230 ++i;
5231 }
5232 aLine.append( "]\n"
5233 "/Count " );
5234 aLine.append( static_cast<sal_Int32>(m_aPages.size()) );
5235 aLine.append( ">>\n"
5236 "endobj\n\n" );
5237 if (!writeBuffer(aLine)) return false;
5238
5239 // emit annotation objects
5240 if (!emitAnnotations()) return false;
5241 if (!emitEmbeddedFiles()) return false;
5242
5243 // emit attached files
5244 for (auto & rAttachedFile : m_aDocumentAttachedFiles)
5245 {
5246 if (!updateObject(rAttachedFile.mnObjectId))
5247 return false;
5248 aLine.setLength(0);
5249
5250 aWriter.startObject(rAttachedFile.mnObjectId);
5251 aWriter.startDict();
5252 aWriter.write("/Type", "/Filespec");
5253
5254 // Add associated files relationship (since PDF 2.0)
5255 if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0 || m_nPDFA_Version == 3)
5256 {
5257 aWriter.write("/AFRelationship", "/Source");
5258 }
5259
5260 aWriter.writeKeyAndUnicodeEncrypt("/F", rAttachedFile.maFilename, rAttachedFile.mnObjectId);
5261 if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
5262 {
5263 aWriter.writeKeyAndUnicodeEncrypt("/UF", rAttachedFile.maFilename, rAttachedFile.mnObjectId);
5264 }
5265 if (!rAttachedFile.maDescription.isEmpty())
5266 {
5267 aWriter.writeKeyAndUnicodeEncrypt("/Desc", rAttachedFile.maDescription, rAttachedFile.mnObjectId);
5268 }
5269 aLine.append("/EF");
5270 aWriter.startDict();
5271 aWriter.writeKeyAndReference("/F", rAttachedFile.mnEmbeddedFileObjectId);
5272 aWriter.endDict();
5273 aWriter.endDict();
5274 aWriter.endObject();
5275 if (!writeBuffer(aLine)) return false;
5276 }
5277
5278 // emit Catalog
5279 m_nCatalogObject = createObject();
5280 if( ! updateObject( m_nCatalogObject ) )
5281 return false;
5282 aLine.setLength( 0 );
5283 aLine.append( m_nCatalogObject );
5284 aLine.append( " 0 obj\n"
5285 "<</Type/Catalog/Pages " );
5286 aLine.append( nTreeNode );
5287 aLine.append( " 0 R\n" );
5288
5289 // check if there are named destinations to emit (root must be inside the catalog)
5290 if( nNamedDestinationsDictionary )
5291 {
5292 aLine.append("/Dests ");
5293 aLine.append( nNamedDestinationsDictionary );
5294 aLine.append( " 0 R\n" );
5295 }
5296
5297 if (!m_aDocumentAttachedFiles.empty())
5298 {
5299 // Write the associated files catalog entry (since PDF 2.0)
5300 if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0 || m_nPDFA_Version == 3)
5301 {
5302 aLine.append("/AF");
5303 aLine.append("[");
5304 for (auto & rAttachedFile : m_aDocumentAttachedFiles)
5305 aWriter.writeReference(rAttachedFile.mnObjectId);
5306 aLine.append("]");
5307 }
5308
5309 aWriter.startDictWithKey("/Names");
5310 aWriter.startDictWithKey("/EmbeddedFiles");
5311 aLine.append("/Names [");
5312 for (auto & rAttachedFile : m_aDocumentAttachedFiles)
5313 {
5314 aWriter.writeUnicodeEncrypt(rAttachedFile.maFilename, m_nCatalogObject);
5315 aWriter.writeReference(rAttachedFile.mnObjectId);
5316 }
5317 aLine.append("]");
5318 aWriter.endDict();
5319 aWriter.endDict();
5320 }
5321
5322 if( m_aContext.PageLayout != PDFWriter::DefaultLayout )
5323 switch( m_aContext.PageLayout )
5324 {
5325 default :
5326 case PDFWriter::SinglePage :
5327 aLine.append( "/PageLayout/SinglePage\n" );
5328 break;
5329 case PDFWriter::Continuous :
5330 aLine.append( "/PageLayout/OneColumn\n" );
5331 break;
5332 case PDFWriter::ContinuousFacing :
5333 // the flag m_aContext.FirstPageLeft below is used to set the page on the left side
5334 aLine.append( "/PageLayout/TwoColumnRight\n" );//odd page on the right side
5335 break;
5336 }
5337 if( m_aContext.PDFDocumentMode != PDFWriter::ModeDefault && !m_aContext.OpenInFullScreenMode )
5338 switch( m_aContext.PDFDocumentMode )
5339 {
5340 default :
5341 aLine.append( "/PageMode/UseNone\n" );
5342 break;
5343 case PDFWriter::UseOutlines :
5344 aLine.append( "/PageMode/UseOutlines\n" ); //document is opened with outline pane open
5345 break;
5346 case PDFWriter::UseThumbs :
5347 aLine.append( "/PageMode/UseThumbs\n" ); //document is opened with thumbnails pane open
5348 break;
5349 }
5350 else if( m_aContext.OpenInFullScreenMode )
5351 aLine.append( "/PageMode/FullScreen\n" ); //document is opened full screen
5352
5353 OStringBuffer aInitPageRef;
5354 if( m_aContext.InitialPage >= 0 && o3tl::make_unsigned(m_aContext.InitialPage) < m_aPages.size() )
5355 {
5356 aInitPageRef.append( m_aPages[m_aContext.InitialPage].m_nPageObject );
5357 aInitPageRef.append( " 0 R" );
5358 }
5359 else
5360 aInitPageRef.append( "0" );
5361
5362 switch( m_aContext.PDFDocumentAction )
5363 {
5364 case PDFWriter::ActionDefault : //do nothing, this is the Acrobat default
5365 default:
5366 if ( aInitPageRef.getLength() > 1 && m_aContext.InitialPage > 0 )
5367 {
5368 aLine.append( "/OpenAction[" );
5369 aLine.append( aInitPageRef );
5370 aLine.append( " /XYZ null null 0]\n" );
5371 }
5372 break;
5373 case PDFWriter::FitInWindow :
5374 aLine.append( "/OpenAction[" );
5375 aLine.append( aInitPageRef );
5376 aLine.append( " /Fit]\n" ); //Open fit page
5377 break;
5378 case PDFWriter::FitWidth :
5379 aLine.append( "/OpenAction[" );
5380 aLine.append( aInitPageRef );
5381 aLine.append( " /FitH null]\n" ); //Open fit width
5382 break;
5383 case PDFWriter::FitVisible :
5384 aLine.append( "/OpenAction[" );
5385 aLine.append( aInitPageRef );
5386 aLine.append( " /FitBH null]\n" ); //Open fit visible
5387 break;
5388 case PDFWriter::ActionZoom :
5389 aLine.append( "/OpenAction[" );
5390 aLine.append( aInitPageRef );
5391 aLine.append( " /XYZ null null " );
5392 if( m_aContext.Zoom >= 50 && m_aContext.Zoom <= 1600 )
5393 aLine.append( static_cast<double>(m_aContext.Zoom)/100.0 );
5394 else
5395 aLine.append( "0" );
5396 aLine.append( "]\n" );
5397 break;
5398 }
5399
5400 // viewer preferences, if we had some, then emit
5401 if (m_aContext.HideViewerToolbar ||
5402 (!m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle) ||
5403 m_aContext.HideViewerMenubar ||
5404 m_aContext.HideViewerWindowControls || m_aContext.FitWindow ||
5405 m_aContext.CenterWindow || (m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing ) ||
5406 m_aContext.OpenInFullScreenMode ||
5407 m_bIsPDF_UA)
5408 {
5409 aLine.append( "/ViewerPreferences<<" );
5410 if( m_aContext.HideViewerToolbar )
5411 aLine.append( "/HideToolbar true\n" );
5412 if( m_aContext.HideViewerMenubar )
5413 aLine.append( "/HideMenubar true\n" );
5414 if( m_aContext.HideViewerWindowControls )
5415 aLine.append( "/HideWindowUI true\n" );
5416 if( m_aContext.FitWindow )
5417 aLine.append( "/FitWindow true\n" );
5418 if( m_aContext.CenterWindow )
5419 aLine.append( "/CenterWindow true\n" );
5420 // PDF/UA-2 requires /DisplayDocTitle set to true
5421 if ((!m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle) || m_bIsPDF_UA)
5422 aLine.append( "/DisplayDocTitle true\n" );
5423 if( m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing )
5424 aLine.append( "/Direction/R2L\n" );
5425 if( m_aContext.OpenInFullScreenMode )
5426 switch( m_aContext.PDFDocumentMode )
5427 {
5428 default :
5429 case PDFWriter::ModeDefault :
5430 aLine.append( "/NonFullScreenPageMode/UseNone\n" );
5431 break;
5432 case PDFWriter::UseOutlines :
5433 aLine.append( "/NonFullScreenPageMode/UseOutlines\n" );
5434 break;
5435 case PDFWriter::UseThumbs :
5436 aLine.append( "/NonFullScreenPageMode/UseThumbs\n" );
5437 break;
5438 }
5439 aLine.append( ">>\n" );
5440 }
5441
5442 if( nOutlineDict )
5443 {
5444 aLine.append( "/Outlines " );
5445 aLine.append( nOutlineDict );
5446 aLine.append( " 0 R\n" );
5447 }
5448 if( nStructureDict )
5449 {
5450 aLine.append( "/StructTreeRoot " );
5451 aLine.append( nStructureDict );
5452 aLine.append( " 0 R\n" );
5453 }
5454 if( !m_aContext.DocumentLocale.Language.isEmpty() )
5455 {
5456 /* PDF allows only RFC 3066, see above in emitStructure(). */
5457 LanguageTag aLanguageTag( m_aContext.DocumentLocale);
5458 OUString aLanguage, aScript, aCountry;
5459 aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
5460 if (!aLanguage.isEmpty())
5461 {
5462 OUStringBuffer aLocBuf( 16 );
5463 aLocBuf.append( aLanguage );
5464 if( !aCountry.isEmpty() )
5465 {
5466 aLocBuf.append( '-' );
5467 aLocBuf.append( aCountry );
5468 }
5469 aLine.append( "/Lang" );
5470 aWriter.writeLiteralEncrypt(aLocBuf, m_nCatalogObject);
5471 aLine.append( "\n" );
5472 }
5473 }
5474 if (m_aContext.Tagged)
5475 {
5476 aLine.append( "/MarkInfo<</Marked true>>\n" );
5477 }
5478
5479 if (!m_aWidgets.empty() || !m_aCopiedWidgets.empty())
5480 {
5481 aLine.append("/AcroForm");
5482 aLine.append("<<");
5483 aLine.append("/Fields");
5484 aLine.append("[ ");
5485
5486 for (auto const& rWidget : m_aWidgets)
5487 {
5488 // output only root fields
5489 if (rWidget.m_nParent < 1)
5490 {
5491 appendObjectReference(rWidget.m_nObject, aLine);
5492 }
5493 }
5494 // Add widgets that were copied from an external PDF
5495 for (auto const& rCopiedWidget : m_aCopiedWidgets)
5496 {
5497 appendObjectReference(rCopiedWidget.m_nObject, aLine);
5498 }
5499 aLine.append(" ]\n");
5500
5501 bool bSigned = false;
5502 #if HAVE_FEATURE_NSS
5503 if (m_nSignatureObject != -1)
5504 {
5505 aLine.append("/SigFlags 3 ");
5506 bSigned = true;
5507 }
5508 #endif
5509 aLine.append("/DR<</Font ");
5510 appendObjectReference(getFontDictObject(), aLine);
5511 aLine.append(">>");
5512
5513 // NeedAppearances must not be used if PDF is signed, PDF/A is used or
5514 // we have copied widgets (can't guarantee we have appearance streams in this case)
5515 if (m_nPDFA_Version == 0 && !bSigned && m_aCopiedWidgets.empty())
5516 aLine.append("/NeedAppearances true ");
5517
5518 aLine.append(">>\n");
5519 }
5520
5521 //check if there is a Metadata object
5522 if( nOutputIntentObject )
5523 {
5524 aLine.append("/OutputIntents[");
5525 aLine.append( nOutputIntentObject );
5526 aLine.append( " 0 R]" );
5527 }
5528
5529 if( nMetadataObject )
5530 {
5531 aWriter.writeKeyAndReference("/Metadata", nMetadataObject);
5532 }
5533
5534 aLine.append( ">>\n"
5535 "endobj\n\n" );
5536 return writeBuffer( aLine );
5537 }
5538
5539 #if HAVE_FEATURE_NSS
5540
emitSignature()5541 bool PDFWriterImpl::emitSignature()
5542 {
5543 if( !updateObject( m_nSignatureObject ) )
5544 return false;
5545
5546 OStringBuffer aLine( 0x5000 );
5547 COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
5548 aLine.append( m_nSignatureObject );
5549 aLine.append( " 0 obj\n" );
5550 aLine.append("<</Contents <" );
5551
5552 sal_uInt64 nOffset = ~0U;
5553 if (osl::File::E_None != m_aFile.getPos(nOffset))
5554 return false;
5555
5556 m_nSignatureContentOffset = nOffset + aLine.getLength();
5557
5558 // reserve some space for the PKCS#7 object
5559 aLine.append(RepeatedChar('0', MAX_SIGNATURE_CONTENT_LENGTH)
5560 + ">\n/Type/Sig/SubFilter/adbe.pkcs7.detached");
5561
5562 if( !m_aContext.DocumentInfo.Author.isEmpty() )
5563 {
5564 aLine.append( "/Name" );
5565 aWriter.writeUnicodeEncrypt(m_aContext.DocumentInfo.Author, m_nSignatureObject);
5566 }
5567
5568 aLine.append( " /M ");
5569 aWriter.writeLiteralEncrypt(m_aCreationDateString, m_nSignatureObject);
5570
5571 aLine.append( " /ByteRange [ 0 ");
5572 aLine.append( m_nSignatureContentOffset - 1 );
5573 aLine.append( " " );
5574 aLine.append( m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1 );
5575 aLine.append( " " );
5576
5577 m_nSignatureLastByteRangeNoOffset = nOffset + aLine.getLength();
5578
5579 // mark the last ByteRange no and add some space. Now, we don't know
5580 // how many bytes we need for this ByteRange value
5581 // The real value will be overwritten in the finalizeSignature method
5582 aLine.append(RepeatedChar(' ', 100) + " /Filter/Adobe.PPKMS");
5583
5584 //emit reason, location and contactinfo
5585 if ( !m_aContext.SignReason.isEmpty() )
5586 {
5587 aLine.append("/Reason");
5588 aWriter.writeUnicodeEncrypt(m_aContext.SignReason, m_nSignatureObject);
5589 }
5590
5591 if ( !m_aContext.SignLocation.isEmpty() )
5592 {
5593 aLine.append("/Location");
5594 aWriter.writeUnicodeEncrypt(m_aContext.SignLocation, m_nSignatureObject);
5595 }
5596
5597 if ( !m_aContext.SignContact.isEmpty() )
5598 {
5599 aLine.append("/ContactInfo");
5600 aWriter.writeUnicodeEncrypt(m_aContext.SignContact, m_nSignatureObject);
5601 }
5602
5603 aLine.append(" >>\nendobj\n\n" );
5604
5605 return writeBuffer( aLine );
5606 }
5607
finalizeSignature()5608 bool PDFWriterImpl::finalizeSignature()
5609 {
5610 if (!m_aContext.SignCertificate.is())
5611 return false;
5612
5613 // 1- calculate last ByteRange value
5614 sal_uInt64 nOffset = ~0U;
5615 if (osl::File::E_None != m_aFile.getPos(nOffset))
5616 return false;
5617
5618 sal_Int64 nLastByteRangeNo = nOffset - (m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1);
5619
5620 // 2- overwrite the value to the m_nSignatureLastByteRangeNoOffset position
5621 sal_uInt64 nWritten = 0;
5622 if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureLastByteRangeNoOffset))
5623 return false;
5624 OString aByteRangeNo = OString::number( nLastByteRangeNo ) + " ]";
5625
5626 if (m_aFile.write(aByteRangeNo.getStr(), aByteRangeNo.getLength(), nWritten) != osl::File::E_None)
5627 {
5628 (void)m_aFile.setPos(osl_Pos_Absolut, nOffset);
5629 return false;
5630 }
5631
5632 // 3- create the PKCS#7 object using NSS
5633
5634 // Prepare buffer and calculate PDF file digest
5635 if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, 0))
5636 return false;
5637
5638 std::unique_ptr<char[]> buffer1(new char[m_nSignatureContentOffset + 1]);
5639 sal_uInt64 bytesRead1;
5640
5641 //FIXME: Check if hash is calculated from the correct byterange
5642 if (osl::File::E_None != m_aFile.read(buffer1.get(), m_nSignatureContentOffset - 1 , bytesRead1) ||
5643 bytesRead1 != static_cast<sal_uInt64>(m_nSignatureContentOffset) - 1)
5644 {
5645 SAL_WARN("vcl.pdfwriter", "First buffer read failed");
5646 return false;
5647 }
5648
5649 std::unique_ptr<char[]> buffer2(new char[nLastByteRangeNo + 1]);
5650 sal_uInt64 bytesRead2;
5651
5652 if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1) ||
5653 osl::File::E_None != m_aFile.read(buffer2.get(), nLastByteRangeNo, bytesRead2) ||
5654 bytesRead2 != static_cast<sal_uInt64>(nLastByteRangeNo))
5655 {
5656 SAL_WARN("vcl.pdfwriter", "Second buffer read failed");
5657 return false;
5658 }
5659
5660 OStringBuffer aCMSHexBuffer;
5661 svl::crypto::SigningContext aSigningContext;
5662 aSigningContext.m_xCertificate = m_aContext.SignCertificate;
5663 svl::crypto::Signing aSigning(aSigningContext);
5664 aSigning.AddDataRange(buffer1.get(), bytesRead1);
5665 aSigning.AddDataRange(buffer2.get(), bytesRead2);
5666 aSigning.SetSignTSA(m_aContext.SignTSA);
5667 aSigning.SetSignPassword(m_aContext.SignPassword);
5668 if (!aSigning.Sign(aCMSHexBuffer))
5669 {
5670 SAL_WARN("vcl.pdfwriter", "PDFWriter::Sign() failed");
5671 return false;
5672 }
5673
5674 assert(aCMSHexBuffer.getLength() <= MAX_SIGNATURE_CONTENT_LENGTH);
5675
5676 // Set file pointer to the m_nSignatureContentOffset, we're ready to overwrite PKCS7 object
5677 nWritten = 0;
5678 if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset))
5679 return false;
5680 m_aFile.write(aCMSHexBuffer.getStr(), aCMSHexBuffer.getLength(), nWritten);
5681
5682 return osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset);
5683 }
5684
5685 #endif //HAVE_FEATURE_NSS
5686
emitInfoDict()5687 sal_Int32 PDFWriterImpl::emitInfoDict( )
5688 {
5689 sal_Int32 nObject = createObject();
5690
5691 if (!updateObject(nObject))
5692 return 0;
5693
5694 COSWriter aWriter(m_aContext.Encryption.getParams(), m_pPDFEncryptor);
5695 aWriter.startObject(nObject);
5696 aWriter.startDict();
5697
5698 // These entries are deprecated in PDF 2.0 (in favor of XMP metadata) and shouldn't be written.
5699 // Exception: CreationDate and ModDate (which we don't write)
5700 if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0)
5701 {
5702 if (!m_aContext.DocumentInfo.Title.isEmpty())
5703 {
5704 aWriter.writeKeyAndUnicodeEncrypt("/Title", m_aContext.DocumentInfo.Title, nObject);
5705 }
5706 if (!m_aContext.DocumentInfo.Author.isEmpty())
5707 {
5708 aWriter.writeKeyAndUnicodeEncrypt("/Author", m_aContext.DocumentInfo.Author, nObject);
5709 }
5710 if (!m_aContext.DocumentInfo.Subject.isEmpty())
5711 {
5712 aWriter.writeKeyAndUnicodeEncrypt("/Subject", m_aContext.DocumentInfo.Subject, nObject);
5713 }
5714 if (!m_aContext.DocumentInfo.Keywords.isEmpty())
5715 {
5716 aWriter.writeKeyAndUnicodeEncrypt("/Keywords", m_aContext.DocumentInfo.Keywords, nObject);
5717 }
5718 if (!m_aContext.DocumentInfo.Creator.isEmpty())
5719 {
5720 aWriter.writeKeyAndUnicodeEncrypt("/Creator", m_aContext.DocumentInfo.Creator, nObject);
5721 }
5722 if (!m_aContext.DocumentInfo.Producer.isEmpty())
5723 {
5724 aWriter.writeKeyAndUnicodeEncrypt("/Producer", m_aContext.DocumentInfo.Producer, nObject);
5725 }
5726 }
5727 // Allowed in PDF 2.0
5728 aWriter.writeKeyAndLiteralEncrypt("/CreationDate", m_aCreationDateString, nObject);
5729 aWriter.endDict();
5730 aWriter.endObject();
5731
5732 if (!writeBuffer(aWriter.getLine()))
5733 nObject = 0;
5734
5735 return nObject;
5736 }
5737
5738 // Part of this function may be shared with method appendDest.
emitNamedDestinations()5739 sal_Int32 PDFWriterImpl::emitNamedDestinations()
5740 {
5741 sal_Int32 nCount = m_aNamedDests.size();
5742 if( nCount <= 0 )
5743 return 0;//define internal error
5744
5745 //get the object number for all the destinations
5746 sal_Int32 nObject = createObject();
5747
5748 if( updateObject( nObject ) )
5749 {
5750 //emit the dictionary
5751 OStringBuffer aLine( 1024 );
5752 aLine.append( nObject );
5753 aLine.append( " 0 obj\n"
5754 "<<" );
5755
5756 sal_Int32 nDestID;
5757 for( nDestID = 0; nDestID < nCount; nDestID++ )
5758 {
5759 const PDFNamedDest& rDest = m_aNamedDests[ nDestID ];
5760 // In order to correctly function both under an Internet browser and
5761 // directly with a reader (provided the reader has the feature) we
5762 // need to set the name of the destination the same way it will be encoded
5763 // in an Internet link
5764 INetURLObject aLocalURL( u"http://ahost.ax" ); //dummy location, won't be used
5765 aLocalURL.SetMark( rDest.m_aDestName );
5766
5767 const OUString aName = aLocalURL.GetMark( INetURLObject::DecodeMechanism::NONE ); //same coding as
5768 // in link creation ( see PDFWriterImpl::emitLinkAnnotations )
5769 const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ];
5770
5771 aLine.append( '/' );
5772 appendDestinationName( aName, aLine ); // this conversion must be done when forming the link to target ( see in emitCatalog )
5773 aLine.append( '[' ); // the '[' can be emitted immediately, because the appendDestinationName function
5774 //maps the preceding character properly
5775 aLine.append( rDestPage.m_nPageObject );
5776 aLine.append( " 0 R" );
5777
5778 switch( rDest.m_eType )
5779 {
5780 case PDFWriter::DestAreaType::XYZ:
5781 default:
5782 aLine.append( "/XYZ " );
5783 appendFixedInt( rDest.m_aRect.Left(), aLine );
5784 aLine.append( ' ' );
5785 appendFixedInt( rDest.m_aRect.Bottom(), aLine );
5786 aLine.append( " 0" );
5787 break;
5788 case PDFWriter::DestAreaType::FitRectangle:
5789 aLine.append( "/FitR " );
5790 appendFixedInt( rDest.m_aRect.Left(), aLine );
5791 aLine.append( ' ' );
5792 appendFixedInt( rDest.m_aRect.Top(), aLine );
5793 aLine.append( ' ' );
5794 appendFixedInt( rDest.m_aRect.Right(), aLine );
5795 aLine.append( ' ' );
5796 appendFixedInt( rDest.m_aRect.Bottom(), aLine );
5797 break;
5798 }
5799 aLine.append( "]\n" );
5800 }
5801
5802 //close
5803 aLine.append( ">>\nendobj\n\n" );
5804 if( ! writeBuffer( aLine ) )
5805 nObject = 0;
5806 }
5807 else
5808 nObject = 0;
5809
5810 return nObject;
5811 }
5812
5813 // emits the output intent dictionary
emitOutputIntent()5814 sal_Int32 PDFWriterImpl::emitOutputIntent()
5815 {
5816 if (m_nPDFA_Version == 0) // not PDFA
5817 return 0;
5818
5819 //emit the sRGB standard profile, in ICC format, in a stream, per IEC61966-2.1
5820
5821 OStringBuffer aLine( 1024 );
5822 COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
5823 sal_Int32 nICCObject = createObject();
5824 sal_Int32 nStreamLengthObject = createObject();
5825
5826 aLine.append( nICCObject );
5827 // sRGB has 3 colors, hence /N 3 below (PDF 1.4 table 4.16)
5828 aLine.append( " 0 obj\n<</N 3/Length " );
5829 aLine.append( nStreamLengthObject );
5830 aLine.append( " 0 R" );
5831 if (!g_bDebugDisableCompression)
5832 aLine.append( "/Filter/FlateDecode" );
5833 aLine.append( ">>\nstream\n" );
5834 if ( !updateObject( nICCObject ) ) return 0;
5835 if ( !writeBuffer( aLine ) ) return 0;
5836 //get file position
5837 sal_uInt64 nBeginStreamPos = 0;
5838 if (osl::File::E_None != m_aFile.getPos(nBeginStreamPos))
5839 return 0;
5840 beginCompression();
5841 checkAndEnableStreamEncryption( nICCObject );
5842 cmsHPROFILE hProfile = cmsCreate_sRGBProfile();
5843 //force ICC profile version 2.1
5844 cmsSetProfileVersion(hProfile, 2.1);
5845 cmsUInt32Number nBytesNeeded = 0;
5846 cmsSaveProfileToMem(hProfile, nullptr, &nBytesNeeded);
5847 if (!nBytesNeeded)
5848 return 0;
5849 std::vector<unsigned char> aBuffer(nBytesNeeded);
5850 cmsSaveProfileToMem(hProfile, aBuffer.data(), &nBytesNeeded);
5851 cmsCloseProfile(hProfile);
5852 bool written = writeBufferBytes( aBuffer.data(), static_cast<sal_Int32>(aBuffer.size()) );
5853 disableStreamEncryption();
5854 endCompression();
5855
5856 sal_uInt64 nEndStreamPos = 0;
5857 if (m_aFile.getPos(nEndStreamPos) != osl::File::E_None)
5858 return 0;
5859
5860 if( !written )
5861 return 0;
5862 if( ! writeBuffer( "\nendstream\nendobj\n\n" ) )
5863 return 0 ;
5864 aLine.setLength( 0 );
5865
5866 //emit the stream length object
5867 if ( !updateObject( nStreamLengthObject ) ) return 0;
5868 aLine.setLength( 0 );
5869 aLine.append( nStreamLengthObject );
5870 aLine.append( " 0 obj\n" );
5871 aLine.append( static_cast<sal_Int64>(nEndStreamPos-nBeginStreamPos) );
5872 aLine.append( "\nendobj\n\n" );
5873 if ( !writeBuffer( aLine ) ) return 0;
5874 aLine.setLength( 0 );
5875
5876 //emit the OutputIntent dictionary
5877 sal_Int32 nOIObject = createObject();
5878 if ( !updateObject( nOIObject ) ) return 0;
5879 aLine.append( nOIObject );
5880 aLine.append( " 0 obj\n"
5881 "<</Type/OutputIntent/S/GTS_PDFA1/OutputConditionIdentifier");
5882
5883 aWriter.writeLiteralEncrypt(std::string_view("sRGB IEC61966-2.1"), nOIObject);
5884 aLine.append("/DestOutputProfile ");
5885 aLine.append( nICCObject );
5886 aLine.append( " 0 R>>\nendobj\n\n" );
5887 if ( !writeBuffer( aLine ) ) return 0;
5888
5889 return nOIObject;
5890 }
5891
lcl_assignMeta(std::u16string_view aValue,OString & aMeta)5892 static void lcl_assignMeta(std::u16string_view aValue, OString& aMeta)
5893 {
5894 if (!aValue.empty())
5895 {
5896 aMeta = OUStringToOString(comphelper::string::encodeForXml(aValue), RTL_TEXTENCODING_UTF8);
5897 }
5898 }
5899
lcl_assignMeta(const css::uno::Sequence<OUString> & rValues,std::vector<OString> & rMeta)5900 static void lcl_assignMeta(const css::uno::Sequence<OUString>& rValues, std::vector<OString>& rMeta)
5901 {
5902 if (!rValues.hasElements())
5903 return;
5904
5905 std::vector<OString> aNewMetaVector;
5906 aNewMetaVector.reserve(rValues.getLength());
5907
5908 for (const OUString& rValue : rValues)
5909 {
5910 aNewMetaVector.emplace_back(
5911 OUStringToOString(comphelper::string::encodeForXml(rValue), RTL_TEXTENCODING_UTF8));
5912 }
5913
5914 rMeta = std::move(aNewMetaVector);
5915 }
5916
5917 // emits the document metadata
5918 // Since in PDF 1.4
emitDocumentMetadata()5919 sal_Int32 PDFWriterImpl::emitDocumentMetadata()
5920 {
5921 if (m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4)
5922 return 0;
5923
5924 //get the object number for all the destinations
5925 sal_Int32 nObject = createObject();
5926
5927 if (!updateObject(nObject))
5928 return 0;
5929
5930 pdf::XmpMetadata aMetadata;
5931
5932 if (m_nPDFA_Version > 0)
5933 aMetadata.mnPDF_A = m_nPDFA_Version;
5934
5935 if (m_bIsPDF_UA)
5936 {
5937 // If PDF 2.0+ we need to use PDF/UA-2 otherwise PDF/UA-1
5938 aMetadata.mnPDF_UA = (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0) ? 2 : 1;
5939 }
5940
5941 lcl_assignMeta(m_aContext.DocumentInfo.Title, aMetadata.msTitle);
5942 lcl_assignMeta(m_aContext.DocumentInfo.Author, aMetadata.msAuthor);
5943 lcl_assignMeta(m_aContext.DocumentInfo.Subject, aMetadata.msSubject);
5944 lcl_assignMeta(m_aContext.DocumentInfo.Producer, aMetadata.msProducer);
5945 aMetadata.msPDFVersion = getPDFVersionStr(m_aContext.Version);
5946 if (m_nPDFA_Version == 4)
5947 {
5948 // if we have embedded files we need to use conformance level "F"
5949 aMetadata.msConformance = m_aEmbeddedFiles.empty() ? ""_ostr : "F"_ostr;
5950 }
5951 else
5952 {
5953 aMetadata.msConformance = "B"_ostr;
5954 }
5955 lcl_assignMeta(m_aContext.DocumentInfo.Keywords, aMetadata.msKeywords);
5956 lcl_assignMeta(m_aContext.DocumentInfo.Contributor, aMetadata.maContributor);
5957 lcl_assignMeta(m_aContext.DocumentInfo.Coverage, aMetadata.msCoverage);
5958 lcl_assignMeta(m_aContext.DocumentInfo.Identifier, aMetadata.msIdentifier);
5959 lcl_assignMeta(m_aContext.DocumentInfo.Publisher, aMetadata.maPublisher);
5960 lcl_assignMeta(m_aContext.DocumentInfo.Relation, aMetadata.maRelation);
5961 lcl_assignMeta(m_aContext.DocumentInfo.Rights, aMetadata.msRights);
5962 lcl_assignMeta(m_aContext.DocumentInfo.Source, aMetadata.msSource);
5963 lcl_assignMeta(m_aContext.DocumentInfo.Type, aMetadata.msType);
5964 lcl_assignMeta(m_aContext.DocumentInfo.Creator, aMetadata.m_sCreatorTool);
5965 aMetadata.m_sCreateDate = m_aCreationMetaDateString;
5966
5967 {
5968 COSWriter aWriter;
5969 aWriter.startObject(nObject);
5970 aWriter.startDict();
5971 aWriter.write("/Type", "/Metadata");
5972 aWriter.write("/Subtype", "/XML");
5973 aWriter.write("/Length", sal_Int32(aMetadata.getSize()));
5974 aWriter.endDict();
5975 aWriter.startStream();
5976 if (!writeBuffer(aWriter.getLine()))
5977 return 0;
5978 }
5979
5980 //emit the stream
5981 bool bEncryptMetadata = m_pPDFEncryptor && m_pPDFEncryptor->isMetadataEncrypted();
5982 if (bEncryptMetadata)
5983 checkAndEnableStreamEncryption(nObject);
5984
5985 if (!writeBufferBytes(aMetadata.getData(), aMetadata.getSize()))
5986 return 0;
5987
5988 if (bEncryptMetadata)
5989 disableStreamEncryption();
5990
5991 {
5992 COSWriter aWriter;
5993 aWriter.endStream();
5994 aWriter.endObject();
5995 if (!writeBuffer(aWriter.getLine()))
5996 return 0;
5997 }
5998
5999 return nObject;
6000 }
6001
emitEncrypt()6002 sal_Int32 PDFWriterImpl::emitEncrypt()
6003 {
6004 //emit the security information
6005 //must be emitted as indirect dictionary object, since
6006 //Acrobat Reader 5 works only with this kind of implementation
6007
6008 sal_Int32 nObject = createObject();
6009
6010 if (updateObject(nObject))
6011 {
6012 PDFEncryptionProperties& rProperties = m_aContext.Encryption;
6013 COSWriter aWriter(m_aContext.Encryption.getParams(), m_pPDFEncryptor);
6014 aWriter.startObject(nObject);
6015 aWriter.startDict();
6016 aWriter.write("/Filter", "/Standard");
6017 aWriter.write("/V", m_pPDFEncryptor->getVersion());
6018 aWriter.write("/Length", m_pPDFEncryptor->getKeyLength() * 8);
6019 aWriter.write("/R", m_pPDFEncryptor->getRevision());
6020
6021 if (m_pPDFEncryptor->getVersion() == 5 && m_pPDFEncryptor->getRevision() == 6)
6022 {
6023 // emit the owner password, must not be encrypted
6024 aWriter.writeHexArray("/U", rProperties.UValue.data(), rProperties.UValue.size());
6025 aWriter.writeHexArray("/UE", rProperties.UE.data(), rProperties.UE.size());
6026 aWriter.writeHexArray("/O", rProperties.OValue.data(), rProperties.OValue.size());
6027 aWriter.writeHexArray("/OE", rProperties.OE.data(), rProperties.OE.size());
6028
6029 // Encrypted perms
6030 std::vector<sal_uInt8> aEncryptedPermissions = m_pPDFEncryptor->getEncryptedAccessPermissions(rProperties.EncryptionKey);
6031 aWriter.writeHexArray("/Perms", aEncryptedPermissions.data(), aEncryptedPermissions.size());
6032
6033 // Write content filter stuff - to select we want AESv3 256bit
6034 aWriter.write("/CF", "<</StdCF <</CFM /AESV3 /Length 256>>>>");
6035 aWriter.write("/StmF", "/StdCF");
6036 aWriter.write("/StrF", "/StdCF");
6037 // Encrypt metadata. Default is true. Relevant for Revision 6+
6038 if (!m_pPDFEncryptor->isMetadataEncrypted())
6039 aWriter.write("/EncryptMetadata", " false ");
6040 }
6041 else
6042 {
6043 // emit the owner password, must not be encrypted
6044 aWriter.writeHexArray("/U", rProperties.UValue.data(), rProperties.UValue.size());
6045 aWriter.writeHexArray("/O", rProperties.OValue.data(), rProperties.OValue.size());
6046 }
6047 aWriter.write("/P", m_pPDFEncryptor->getAccessPermissions());
6048 aWriter.endDict();
6049 aWriter.endObject();
6050
6051 if (!writeBuffer(aWriter.getLine()))
6052 nObject = 0;
6053 }
6054 else
6055 nObject = 0;
6056
6057 return nObject;
6058 }
6059
emitTrailer()6060 bool PDFWriterImpl::emitTrailer()
6061 {
6062 // emit doc info
6063 sal_Int32 nDocInfoObject = 0;
6064 // Deprecated in PDF 2.0, but still allowed if /PieceInfo is written (which we don't support)
6065 if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0)
6066 nDocInfoObject = emitInfoDict();
6067
6068 sal_Int32 nSecObject = 0;
6069
6070 if (m_aContext.Encryption.canEncrypt())
6071 {
6072 nSecObject = emitEncrypt();
6073 }
6074 // emit xref table
6075 // remember start
6076 sal_uInt64 nXRefOffset = 0;
6077 if (osl::File::E_None != m_aFile.getPos(nXRefOffset))
6078 return false;
6079 if (!writeBuffer("xref\n"))
6080 return false;
6081
6082 sal_Int32 nObjects = m_aObjects.size();
6083 OStringBuffer aLine;
6084 aLine.append( "0 " );
6085 aLine.append( static_cast<sal_Int32>(nObjects+1) );
6086 aLine.append( "\n" );
6087 aLine.append( "0000000000 65535 f \n" );
6088 if (!writeBuffer(aLine))
6089 return false;
6090
6091 for( sal_Int32 i = 0; i < nObjects; i++ )
6092 {
6093 aLine.setLength( 0 );
6094 OString aOffset = OString::number( m_aObjects[i] );
6095 for( sal_Int32 j = 0; j < (10-aOffset.getLength()); j++ )
6096 aLine.append( '0' );
6097 aLine.append( aOffset );
6098 aLine.append( " 00000 n \n" );
6099 SAL_WARN_IF( aLine.getLength() != 20, "vcl.pdfwriter", "invalid xref entry" );
6100 if (!writeBuffer(aLine))
6101 return false;
6102 }
6103
6104 // prepare document checksum
6105 OStringBuffer aDocChecksum( 2*RTL_DIGEST_LENGTH_MD5+1 );
6106 ::std::vector<unsigned char> const nMD5Sum(m_DocDigest.finalize());
6107 for (sal_uInt8 i : nMD5Sum)
6108 COSWriter::appendHex( i, aDocChecksum );
6109 // document id set in setDocInfo method
6110 // emit trailer
6111 aLine.setLength( 0 );
6112 aLine.append( "trailer\n"
6113 "<</Size " );
6114 aLine.append( static_cast<sal_Int32>(nObjects+1) );
6115 aLine.append( "/Root " );
6116 aLine.append( m_nCatalogObject );
6117 aLine.append( " 0 R\n" );
6118 if( nSecObject )
6119 {
6120 aLine.append( "/Encrypt ");
6121 aLine.append( nSecObject );
6122 aLine.append( " 0 R\n" );
6123 }
6124 if( nDocInfoObject )
6125 {
6126 aLine.append( "/Info " );
6127 aLine.append( nDocInfoObject );
6128 aLine.append( " 0 R\n" );
6129 }
6130 if( ! m_aContext.Encryption.DocumentIdentifier.empty() )
6131 {
6132 aLine.append( "/ID [ <" );
6133 for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
6134 {
6135 COSWriter::appendHex( sal_Int8(item), aLine );
6136 }
6137 aLine.append( ">\n"
6138 "<" );
6139 for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
6140 {
6141 COSWriter::appendHex( sal_Int8(item), aLine );
6142 }
6143 aLine.append( "> ]\n" );
6144 }
6145
6146 // Writes the /DocChecksum - hash of the PDF stream
6147 // This entry is not defined in the standard, so don't write it if we
6148 // are using PDF/UA or PDF/A as the compliance checkers will complain.
6149 // Actually we shouldn't write it at all...
6150 if (!aDocChecksum.isEmpty() && !m_bIsPDF_UA && m_nPDFA_Version == 0)
6151 {
6152 aLine.append( "/DocChecksum /" );
6153 aLine.append( aDocChecksum );
6154 aLine.append( "\n" );
6155 }
6156
6157 // Writes the /AdditionalStreams - writes the embedded / attached files into the PDF
6158 // This entry is not defined in the standard, so don't write it if we
6159 // are using PDF/UA or PDF/A as the compliance checkers will complain.
6160 if (!m_aDocumentAttachedFiles.empty() && !m_bIsPDF_UA && m_nPDFA_Version == 0)
6161 {
6162 aLine.append( "/AdditionalStreams [" );
6163 for (auto const& rAttachedFile : m_aDocumentAttachedFiles)
6164 {
6165 aLine.append( "/" );
6166 COSWriter::appendName(rAttachedFile.maMimeType, aLine);
6167 aLine.append(" ");
6168 appendObjectReference(rAttachedFile.mnEmbeddedFileObjectId, aLine);
6169 aLine.append("\n");
6170 }
6171 aLine.append( "]\n" );
6172 }
6173
6174 // After calling m_DocDigest.finalize(), we need to initialize the hash again,
6175 // otherwise, m_DocDigest.update() inside writeBuffer will fail with
6176 // Assertion failure: rv == SECSuccess, at sechash.c:140
6177 m_DocDigest.initialize();
6178
6179 aLine.append( ">>\n"
6180 "startxref\n" );
6181 aLine.append( static_cast<sal_Int64>(nXRefOffset) );
6182 aLine.append( "\n"
6183 "%%EOF\n" );
6184 return writeBuffer( aLine );
6185 }
6186
6187 namespace {
6188
6189 struct AnnotationSortEntry
6190 {
6191 sal_Int32 nTabOrder;
6192 sal_Int32 nObject;
6193 sal_Int32 nWidgetIndex;
6194
AnnotationSortEntryvcl::__anon65e7e0150911::AnnotationSortEntry6195 AnnotationSortEntry( sal_Int32 nTab, sal_Int32 nObj, sal_Int32 nI ) :
6196 nTabOrder( nTab ),
6197 nObject( nObj ),
6198 nWidgetIndex( nI )
6199 {}
6200 };
6201
6202 struct AnnotSortContainer
6203 {
6204 o3tl::sorted_vector< sal_Int32 > aObjects;
6205 std::vector< AnnotationSortEntry > aSortedAnnots;
6206 };
6207
6208 struct AnnotSorterLess
6209 {
6210 std::vector<PDFWidget>& m_rWidgets;
6211
AnnotSorterLessvcl::__anon65e7e0150911::AnnotSorterLess6212 explicit AnnotSorterLess( std::vector<PDFWidget>& rWidgets ) : m_rWidgets( rWidgets ) {}
6213
operator ()vcl::__anon65e7e0150911::AnnotSorterLess6214 bool operator()( const AnnotationSortEntry& rLeft, const AnnotationSortEntry& rRight )
6215 {
6216 if( rLeft.nTabOrder < rRight.nTabOrder )
6217 return true;
6218 if( rRight.nTabOrder < rLeft.nTabOrder )
6219 return false;
6220 if( rLeft.nWidgetIndex < 0 && rRight.nWidgetIndex < 0 )
6221 return false;
6222 if( rRight.nWidgetIndex < 0 )
6223 return true;
6224 if( rLeft.nWidgetIndex < 0 )
6225 return false;
6226 // remember: widget rects are in PDF coordinates, so they are ordered down up
6227 if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() >
6228 m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() )
6229 return true;
6230 if( m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() >
6231 m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() )
6232 return false;
6233 if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Left() <
6234 m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Left() )
6235 return true;
6236 return false;
6237 }
6238 };
6239
6240 }
6241
sortWidgets()6242 void PDFWriterImpl::sortWidgets()
6243 {
6244 // sort widget annotations on each page as per their
6245 // TabOrder attribute
6246 std::unordered_map< sal_Int32, AnnotSortContainer > sorted;
6247 int nWidgets = m_aWidgets.size();
6248 for( int nW = 0; nW < nWidgets; nW++ )
6249 {
6250 const PDFWidget& rWidget = m_aWidgets[nW];
6251 if( rWidget.m_nPage >= 0 )
6252 {
6253 AnnotSortContainer& rCont = sorted[ rWidget.m_nPage ];
6254 // optimize vector allocation
6255 if( rCont.aSortedAnnots.empty() )
6256 rCont.aSortedAnnots.reserve( m_aPages[ rWidget.m_nPage ].m_aAnnotations.size() );
6257 // insert widget to tab sorter
6258 // RadioButtons are not page annotations, only their individual check boxes are
6259 if( rWidget.m_eType != PDFWriter::RadioButton )
6260 {
6261 rCont.aObjects.insert( rWidget.m_nObject );
6262 rCont.aSortedAnnots.emplace_back( rWidget.m_nTabOrder, rWidget.m_nObject, nW );
6263 }
6264 }
6265 }
6266 for (auto & item : sorted)
6267 {
6268 // append entries for non widget annotations
6269 PDFPage& rPage = m_aPages[ item.first ];
6270 unsigned int nAnnots = rPage.m_aAnnotations.size();
6271 for( unsigned int nA = 0; nA < nAnnots; nA++ )
6272 if( item.second.aObjects.find( rPage.m_aAnnotations[nA] ) == item.second.aObjects.end())
6273 item.second.aSortedAnnots.emplace_back( 10000, rPage.m_aAnnotations[nA], -1 );
6274
6275 AnnotSorterLess aLess( m_aWidgets );
6276 std::stable_sort( item.second.aSortedAnnots.begin(), item.second.aSortedAnnots.end(), aLess );
6277 // sanity check
6278 if( item.second.aSortedAnnots.size() == nAnnots)
6279 {
6280 for( unsigned int nA = 0; nA < nAnnots; nA++ )
6281 rPage.m_aAnnotations[nA] = item.second.aSortedAnnots[nA].nObject;
6282 }
6283 else
6284 {
6285 SAL_WARN( "vcl.pdfwriter", "wrong number of sorted annotations" );
6286 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::sortWidgets(): wrong number of sorted assertions "
6287 "on page nr " << item.first << ", " <<
6288 static_cast<tools::Long>(item.second.aSortedAnnots.size()) << " sorted and " <<
6289 static_cast<tools::Long>(nAnnots) << " unsorted");
6290 }
6291 }
6292
6293 // FIXME: implement tab order in structure tree for PDF 1.5
6294 }
6295
emit()6296 bool PDFWriterImpl::emit()
6297 {
6298 endPage();
6299
6300 // resort structure tree and annotations if necessary
6301 // needed for widget tab order
6302 sortWidgets();
6303
6304 #if HAVE_FEATURE_NSS
6305 if( m_aContext.SignPDF )
6306 {
6307 // sign the document
6308 PDFWriter::SignatureWidget aSignature;
6309 aSignature.Name = "Signature1";
6310 createControl( aSignature, 0 );
6311 }
6312 #endif
6313
6314 // emit catalog
6315 if (!emitCatalog())
6316 return false;
6317
6318 #if HAVE_FEATURE_NSS
6319 if (m_nSignatureObject != -1) // if document is signed, emit sigdict
6320 {
6321 if( !emitSignature() )
6322 {
6323 m_aErrors.insert( PDFWriter::Error_Signature_Failed );
6324 return false;
6325 }
6326 }
6327 #endif
6328
6329 // emit trailer
6330 if (!emitTrailer())
6331 return false;
6332
6333 #if HAVE_FEATURE_NSS
6334 if (m_nSignatureObject != -1) // finalize the signature
6335 {
6336 if( !finalizeSignature() )
6337 {
6338 m_aErrors.insert( PDFWriter::Error_Signature_Failed );
6339 return false;
6340 }
6341 }
6342 #endif
6343
6344 m_aFile.close();
6345 m_bOpen = false;
6346
6347 return true;
6348 }
6349
6350
getSystemFont(const vcl::Font & i_rFont)6351 sal_Int32 PDFWriterImpl::getSystemFont( const vcl::Font& i_rFont )
6352 {
6353 auto popIt = ScopedPush();
6354
6355 SetFont( i_rFont );
6356
6357 const LogicalFontInstance* pFontInstance = GetFontInstance();
6358 const vcl::font::PhysicalFontFace* pFace = pFontInstance->GetFontFace();
6359 sal_Int32 nFontID = 0;
6360 auto it = m_aSystemFonts.find( pFace );
6361 if( it != m_aSystemFonts.end() )
6362 nFontID = it->second.m_nNormalFontID;
6363 else
6364 {
6365 nFontID = m_nNextFID++;
6366 m_aSystemFonts[ pFace ] = EmbedFont();
6367 m_aSystemFonts[ pFace ].m_pFontInstance = const_cast<LogicalFontInstance*>(pFontInstance);
6368 m_aSystemFonts[ pFace ].m_nNormalFontID = nFontID;
6369 }
6370
6371 return nFontID;
6372 }
6373
registerSimpleGlyph(const sal_GlyphId nFontGlyphId,const vcl::font::PhysicalFontFace * pFace,const std::vector<sal_Ucs> & rCodeUnits,sal_Int32 nGlyphWidth,sal_uInt8 & nMappedGlyph,sal_Int32 & nMappedFontObject)6374 void PDFWriterImpl::registerSimpleGlyph(const sal_GlyphId nFontGlyphId,
6375 const vcl::font::PhysicalFontFace* pFace,
6376 const std::vector<sal_Ucs>& rCodeUnits,
6377 sal_Int32 nGlyphWidth,
6378 sal_uInt8& nMappedGlyph,
6379 sal_Int32& nMappedFontObject)
6380 {
6381 FontSubset& rSubset = m_aSubsets[ pFace ];
6382 // search for font specific glyphID
6383 auto it = rSubset.m_aMapping.find( nFontGlyphId );
6384 if( it != rSubset.m_aMapping.end() )
6385 {
6386 nMappedFontObject = it->second.m_nFontID;
6387 nMappedGlyph = it->second.m_nSubsetGlyphID;
6388 }
6389 else
6390 {
6391 // create new subset if necessary
6392 if( rSubset.m_aSubsets.empty()
6393 || (rSubset.m_aSubsets.back().m_aMapping.size() > 254) )
6394 {
6395 rSubset.m_aSubsets.emplace_back( m_nNextFID++ );
6396 }
6397
6398 // copy font id
6399 nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID;
6400 // create new glyph in subset
6401 sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(rSubset.m_aSubsets.back().m_aMapping.size()+1);
6402 nMappedGlyph = nNewId;
6403
6404 // add new glyph to emitted font subset
6405 GlyphEmit& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[ nFontGlyphId ];
6406 rNewGlyphEmit.setGlyphId( nNewId );
6407 rNewGlyphEmit.setGlyphWidth(XUnits(pFace->UnitsPerEm(), nGlyphWidth));
6408 for (const auto nCode : rCodeUnits)
6409 rNewGlyphEmit.addCode(nCode);
6410
6411 // add new glyph to font mapping
6412 Glyph& rNewGlyph = rSubset.m_aMapping[ nFontGlyphId ];
6413 rNewGlyph.m_nFontID = nMappedFontObject;
6414 rNewGlyph.m_nSubsetGlyphID = nNewId;
6415 }
6416 }
6417
registerGlyph(const sal_GlyphId nFontGlyphId,const vcl::font::PhysicalFontFace * pFace,const LogicalFontInstance * pFont,const std::vector<sal_Ucs> & rCodeUnits,sal_Int32 nGlyphWidth,sal_uInt8 & nMappedGlyph,sal_Int32 & nMappedFontObject)6418 void PDFWriterImpl::registerGlyph(const sal_GlyphId nFontGlyphId,
6419 const vcl::font::PhysicalFontFace* pFace,
6420 const LogicalFontInstance* pFont,
6421 const std::vector<sal_Ucs>& rCodeUnits, sal_Int32 nGlyphWidth,
6422 sal_uInt8& nMappedGlyph, sal_Int32& nMappedFontObject)
6423 {
6424 auto bVariations = !pFace->GetVariations(*pFont).empty();
6425 // tdf#155161
6426 // PDF doesn’t support CFF2 table and we currently don’t convert them to
6427 // Type 1 (like we do with CFF table), so treat it like fonts with
6428 // variations and embed as Type 3 fonts.
6429 if (!pFace->GetRawFontData(HB_TAG('C', 'F', 'F', '2')).empty())
6430 bVariations = true;
6431
6432 if (pFace->IsColorFont() || bVariations)
6433 {
6434 // Font has colors, check if this glyph has color layers or bitmap.
6435 tools::Rectangle aRect;
6436 auto aLayers = pFace->GetGlyphColorLayers(nFontGlyphId);
6437 auto aBitmap = pFace->GetGlyphColorBitmap(nFontGlyphId, aRect);
6438 if (!aLayers.empty() || !aBitmap.empty() || bVariations)
6439 {
6440 auto& rSubset = m_aType3Fonts[pFace];
6441 auto it = rSubset.m_aMapping.find(nFontGlyphId);
6442 if (it != rSubset.m_aMapping.end())
6443 {
6444 nMappedFontObject = it->second.m_nFontID;
6445 nMappedGlyph = it->second.m_nSubsetGlyphID;
6446 }
6447 else
6448 {
6449 // create new subset if necessary
6450 if (rSubset.m_aSubsets.empty()
6451 || (rSubset.m_aSubsets.back().m_aMapping.size() > 254))
6452 {
6453 rSubset.m_aSubsets.emplace_back(m_nNextFID++);
6454 }
6455
6456 // copy font id
6457 nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID;
6458 // create new glyph in subset
6459 sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(
6460 rSubset.m_aSubsets.back().m_aMapping.size() + 1);
6461 nMappedGlyph = nNewId;
6462
6463 // add new glyph to emitted font subset
6464 auto& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[nFontGlyphId];
6465 rNewGlyphEmit.setGlyphId(nNewId);
6466 rNewGlyphEmit.setGlyphWidth(nGlyphWidth);
6467 for (const auto nCode : rCodeUnits)
6468 rNewGlyphEmit.addCode(nCode);
6469
6470 // add color layers to the glyphs
6471 if (!aLayers.empty())
6472 {
6473 for (const auto& aLayer : aLayers)
6474 {
6475 sal_uInt8 nLayerGlyph;
6476 sal_Int32 nLayerFontID;
6477 registerSimpleGlyph(aLayer.nGlyphIndex, pFace, rCodeUnits, nGlyphWidth,
6478 nLayerGlyph, nLayerFontID);
6479
6480 rNewGlyphEmit.addColorLayer(
6481 { nLayerFontID, nLayerGlyph, aLayer.nColorIndex });
6482 }
6483 }
6484 else if (!aBitmap.empty())
6485 rNewGlyphEmit.setColorBitmap(aBitmap, aRect);
6486 else if (bVariations)
6487 rNewGlyphEmit.setOutline(pFont->GetGlyphOutlineUntransformed(nFontGlyphId));
6488
6489 // add new glyph to font mapping
6490 Glyph& rNewGlyph = rSubset.m_aMapping[nFontGlyphId];
6491 rNewGlyph.m_nFontID = nMappedFontObject;
6492 rNewGlyph.m_nSubsetGlyphID = nNewId;
6493 }
6494 return;
6495 }
6496 }
6497
6498 // If we reach here then the glyph has no color layers.
6499 registerSimpleGlyph(nFontGlyphId, pFace, rCodeUnits, nGlyphWidth, nMappedGlyph,
6500 nMappedFontObject);
6501 }
6502
drawRelief(SalLayout & rLayout,const OUString & rText,bool bTextLines)6503 void PDFWriterImpl::drawRelief( SalLayout& rLayout, const OUString& rText, bool bTextLines )
6504 {
6505 push( PushFlags::ALL );
6506
6507 FontRelief eRelief = m_aCurrentPDFState.m_aFont.GetRelief();
6508
6509 Color aTextColor = m_aCurrentPDFState.m_aFont.GetColor();
6510 Color aTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
6511 Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
6512 Color aReliefColor( COL_LIGHTGRAY );
6513 if( aTextColor == COL_BLACK )
6514 aTextColor = COL_WHITE;
6515 if( aTextLineColor == COL_BLACK )
6516 aTextLineColor = COL_WHITE;
6517 if( aOverlineColor == COL_BLACK )
6518 aOverlineColor = COL_WHITE;
6519 // coverity[copy_paste_error : FALSE] - aReliefColor depending on aTextColor is correct
6520 if( aTextColor == COL_WHITE )
6521 aReliefColor = COL_BLACK;
6522
6523 Font aSetFont = m_aCurrentPDFState.m_aFont;
6524 aSetFont.SetRelief( FontRelief::NONE );
6525 aSetFont.SetShadow( false );
6526
6527 aSetFont.SetColor( aReliefColor );
6528 setTextLineColor( aReliefColor );
6529 setOverlineColor( aReliefColor );
6530 setFont( aSetFont );
6531 tools::Long nOff = 1 + GetDPIX()/300;
6532 if( eRelief == FontRelief::Engraved )
6533 nOff = -nOff;
6534
6535 auto aPrevOffset = rLayout.DrawOffset();
6536 rLayout.DrawOffset()
6537 += basegfx::B2DPoint{ static_cast<double>(nOff), static_cast<double>(nOff) };
6538 updateGraphicsState();
6539 drawLayout( rLayout, rText, bTextLines );
6540
6541 rLayout.DrawOffset() = aPrevOffset;
6542 setTextLineColor( aTextLineColor );
6543 setOverlineColor( aOverlineColor );
6544 aSetFont.SetColor( aTextColor );
6545 setFont( aSetFont );
6546 updateGraphicsState();
6547 drawLayout( rLayout, rText, bTextLines );
6548
6549 // clean up the mess
6550 pop();
6551 }
6552
drawShadow(SalLayout & rLayout,const OUString & rText,bool bTextLines)6553 void PDFWriterImpl::drawShadow( SalLayout& rLayout, const OUString& rText, bool bTextLines )
6554 {
6555 Font aSaveFont = m_aCurrentPDFState.m_aFont;
6556 Color aSaveTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
6557 Color aSaveOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
6558
6559 Font& rFont = m_aCurrentPDFState.m_aFont;
6560 if( rFont.GetColor() == COL_BLACK || rFont.GetColor().GetLuminance() < 8 )
6561 rFont.SetColor( COL_LIGHTGRAY );
6562 else
6563 rFont.SetColor( COL_BLACK );
6564 rFont.SetShadow( false );
6565 rFont.SetOutline( false );
6566 setFont( rFont );
6567 setTextLineColor( rFont.GetColor() );
6568 setOverlineColor( rFont.GetColor() );
6569 updateGraphicsState();
6570
6571 tools::Long nOff = 1 + ((GetFontInstance()->mnLineHeight-24)/24);
6572 if( rFont.IsOutline() )
6573 nOff++;
6574 rLayout.DrawBase() += basegfx::B2DPoint(nOff, nOff);
6575 drawLayout( rLayout, rText, bTextLines );
6576 rLayout.DrawBase() -= basegfx::B2DPoint(nOff, nOff);
6577
6578 setFont( aSaveFont );
6579 setTextLineColor( aSaveTextLineColor );
6580 setOverlineColor( aSaveOverlineColor );
6581 updateGraphicsState();
6582 }
6583
drawVerticalGlyphs(const std::vector<PDFGlyph> & rGlyphs,OStringBuffer & rLine,const Point & rAlignOffset,const Matrix3 & rRotScale,double fAngle,double fXScale,sal_Int32 nFontHeight)6584 void PDFWriterImpl::drawVerticalGlyphs(
6585 const std::vector<PDFGlyph>& rGlyphs,
6586 OStringBuffer& rLine,
6587 const Point& rAlignOffset,
6588 const Matrix3& rRotScale,
6589 double fAngle,
6590 double fXScale,
6591 sal_Int32 nFontHeight)
6592 {
6593 double nXOffset = 0;
6594 Point aCurPos(SubPixelToLogic(rGlyphs[0].m_aPos));
6595 aCurPos += rAlignOffset;
6596 for( size_t i = 0; i < rGlyphs.size(); i++ )
6597 {
6598 // have to emit each glyph on its own
6599 double fDeltaAngle = 0.0;
6600 double fYScale = 1.0;
6601 double fTempXScale = fXScale;
6602
6603 // perform artificial italics if necessary
6604 double fSkew = 0.0;
6605 if (rGlyphs[i].m_pFont->NeedsArtificialItalic())
6606 fSkew = ARTIFICIAL_ITALIC_SKEW;
6607
6608 double fSkewB = fSkew;
6609 double fSkewA = 0.0;
6610
6611 Point aDeltaPos;
6612 if (rGlyphs[i].m_pGlyph->IsVertical())
6613 {
6614 fDeltaAngle = M_PI/2.0;
6615 fYScale = fXScale;
6616 fTempXScale = 1.0;
6617 fSkewA = -fSkewB;
6618 fSkewB = 0.0;
6619 }
6620 aDeltaPos += SubPixelToLogic(basegfx::B2DPoint(nXOffset / fXScale, 0)) - SubPixelToLogic(basegfx::B2DPoint());
6621 if( i < rGlyphs.size()-1 )
6622 // #i120627# the text on the Y axis is reversed when export ppt file to PDF format
6623 {
6624 double nOffsetX = rGlyphs[i+1].m_aPos.getX() - rGlyphs[i].m_aPos.getX();
6625 double nOffsetY = rGlyphs[i+1].m_aPos.getY() - rGlyphs[i].m_aPos.getY();
6626 nXOffset += std::hypot(nOffsetX, nOffsetY);
6627 }
6628 if (!rGlyphs[i].m_pGlyph->glyphId())
6629 continue;
6630
6631 aDeltaPos = rRotScale.transform( aDeltaPos );
6632
6633 Matrix3 aMat;
6634 if( fSkewB != 0.0 || fSkewA != 0.0 )
6635 aMat.skew( fSkewA, fSkewB );
6636 aMat.scale( fTempXScale, fYScale );
6637 aMat.rotate( fAngle+fDeltaAngle );
6638 aMat.translate( aCurPos.X()+aDeltaPos.X(), aCurPos.Y()+aDeltaPos.Y() );
6639 m_aPages.back().appendMatrix3(aMat, rLine);
6640 rLine.append( " Tm" );
6641 if( i == 0 || rGlyphs[i-1].m_nMappedFontId != rGlyphs[i].m_nMappedFontId )
6642 {
6643 rLine.append( " /F" );
6644 rLine.append( rGlyphs[i].m_nMappedFontId );
6645 rLine.append( ' ' );
6646 m_aPages.back().appendMappedLength( nFontHeight, rLine );
6647 rLine.append( " Tf" );
6648 }
6649 rLine.append( "<" );
6650 COSWriter::appendHex( rGlyphs[i].m_nMappedGlyphId, rLine );
6651 rLine.append( ">Tj\n" );
6652 }
6653 }
6654
drawHorizontalGlyphs(const std::vector<PDFGlyph> & rGlyphs,OStringBuffer & rLine,const Point & rAlignOffset,bool bFirst,double fAngle,double fXScale,sal_Int32 nFontHeight,sal_Int32 nPixelFontHeight)6655 void PDFWriterImpl::drawHorizontalGlyphs(
6656 const std::vector<PDFGlyph>& rGlyphs,
6657 OStringBuffer& rLine,
6658 const Point& rAlignOffset,
6659 bool bFirst,
6660 double fAngle,
6661 double fXScale,
6662 sal_Int32 nFontHeight,
6663 sal_Int32 nPixelFontHeight)
6664 {
6665 // horizontal (= normal) case
6666
6667 // fill in run end indices
6668 // end is marked by index of the first glyph of the next run
6669 // a run is marked by same mapped font id and same Y position
6670 std::vector< sal_uInt32 > aRunEnds;
6671 aRunEnds.reserve( rGlyphs.size() );
6672 for( size_t i = 1; i < rGlyphs.size(); i++ )
6673 {
6674 if( rGlyphs[i].m_nMappedFontId != rGlyphs[i-1].m_nMappedFontId ||
6675 rGlyphs[i].m_pFont != rGlyphs[i-1].m_pFont ||
6676 rGlyphs[i].m_aPos.getY() != rGlyphs[i-1].m_aPos.getY() )
6677 {
6678 aRunEnds.push_back(i);
6679 }
6680 }
6681 // last run ends at last glyph
6682 aRunEnds.push_back( rGlyphs.size() );
6683
6684 // loop over runs of the same font
6685 sal_uInt32 nBeginRun = 0;
6686 for( size_t nRun = 0; nRun < aRunEnds.size(); nRun++ )
6687 {
6688 // setup text matrix back transformed to current coordinate system
6689 Point aCurPos(SubPixelToLogic(rGlyphs[nBeginRun].m_aPos));
6690 aCurPos += rAlignOffset;
6691
6692 // perform artificial italics if necessary
6693 double fSkew = 0.0;
6694 if (rGlyphs[nBeginRun].m_pFont->NeedsArtificialItalic())
6695 fSkew = ARTIFICIAL_ITALIC_SKEW;
6696
6697 // the first run can be set with "Td" operator
6698 // subsequent use of that operator would move
6699 // the textline matrix relative to what was set before
6700 // making use of that would drive us into rounding issues
6701 Matrix3 aMat;
6702 if( bFirst && nRun == 0 && fAngle == 0.0 && fXScale == 1.0 && fSkew == 0.0 )
6703 {
6704 m_aPages.back().appendPoint( aCurPos, rLine );
6705 rLine.append( " Td " );
6706 }
6707 else
6708 {
6709 if( fSkew != 0.0 )
6710 aMat.skew( 0.0, fSkew );
6711 aMat.scale( fXScale, 1.0 );
6712 aMat.rotate( fAngle );
6713 aMat.translate( aCurPos.X(), aCurPos.Y() );
6714 m_aPages.back().appendMatrix3(aMat, rLine);
6715 rLine.append( " Tm\n" );
6716 }
6717 // set up correct font
6718 rLine.append( "/F" );
6719 rLine.append( rGlyphs[nBeginRun].m_nMappedFontId );
6720 rLine.append( ' ' );
6721 m_aPages.back().appendMappedLength( nFontHeight, rLine );
6722 rLine.append( " Tf" );
6723
6724 // output glyphs using Tj or TJ
6725 OStringBuffer aKernedLine( 256 ), aUnkernedLine( 256 );
6726 aKernedLine.append( "[<" );
6727 aUnkernedLine.append( '<' );
6728 COSWriter::appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aKernedLine );
6729 COSWriter::appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aUnkernedLine );
6730
6731 aMat.invert();
6732 bool bNeedKern = false;
6733 for( sal_uInt32 nPos = nBeginRun+1; nPos < aRunEnds[nRun]; nPos++ )
6734 {
6735 COSWriter::appendHex( rGlyphs[nPos].m_nMappedGlyphId, aUnkernedLine );
6736 // check if default glyph positioning is sufficient
6737 const basegfx::B2DPoint aThisPos = aMat.transform( rGlyphs[nPos].m_aPos );
6738 const basegfx::B2DPoint aPrevPos = aMat.transform( rGlyphs[nPos-1].m_aPos );
6739 double fAdvance = aThisPos.getX() - aPrevPos.getX();
6740 fAdvance *= 1000.0 / nPixelFontHeight;
6741 const double fAdjustment = rGlyphs[nPos-1].m_nNativeWidth - fAdvance + 0.5;
6742 SAL_WARN_IF(
6743 fAdjustment < SAL_MIN_INT32 || fAdjustment > SAL_MAX_INT32, "vcl.pdfwriter",
6744 "adjustment " << fAdjustment << " outside 32-bit int");
6745 const sal_Int32 nAdjustment = static_cast<sal_Int32>(
6746 std::clamp(fAdjustment, double(SAL_MIN_INT32), double(SAL_MAX_INT32)));
6747 if( nAdjustment != 0 )
6748 {
6749 // apply individual glyph positioning
6750 bNeedKern = true;
6751 aKernedLine.append( ">" );
6752 aKernedLine.append( nAdjustment );
6753 aKernedLine.append( "<" );
6754 }
6755 COSWriter::appendHex( rGlyphs[nPos].m_nMappedGlyphId, aKernedLine );
6756 }
6757 aKernedLine.append( ">]TJ\n" );
6758 aUnkernedLine.append( ">Tj\n" );
6759 rLine.append( bNeedKern ? aKernedLine : aUnkernedLine );
6760
6761 // set beginning of next run
6762 nBeginRun = aRunEnds[nRun];
6763 }
6764 }
6765
drawLayout(SalLayout & rLayout,const OUString & rText,bool bTextLines)6766 void PDFWriterImpl::drawLayout( SalLayout& rLayout, const OUString& rText, bool bTextLines )
6767 {
6768 // relief takes precedence over shadow (see outdev3.cxx)
6769 if( m_aCurrentPDFState.m_aFont.GetRelief() != FontRelief::NONE )
6770 {
6771 drawRelief( rLayout, rText, bTextLines );
6772 return;
6773 }
6774 else if( m_aCurrentPDFState.m_aFont.IsShadow() )
6775 drawShadow( rLayout, rText, bTextLines );
6776
6777 OStringBuffer aLine( 512 );
6778
6779 const int nMaxGlyphs = 256;
6780
6781 std::vector<sal_Ucs> aCodeUnits;
6782 bool bVertical = m_aCurrentPDFState.m_aFont.IsVertical();
6783 int nIndex = 0;
6784 double fXScale = 1.0;
6785 sal_Int32 nPixelFontHeight = GetFontInstance()->GetFontSelectPattern().mnHeight;
6786 TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
6787
6788 // transform font height back to current units
6789 // note: the layout calculates in outdevs device pixel !!
6790 sal_Int32 nFontHeight = ImplDevicePixelToLogicHeight( nPixelFontHeight );
6791 if( m_aCurrentPDFState.m_aFont.GetAverageFontWidth() )
6792 {
6793 Font aFont( m_aCurrentPDFState.m_aFont );
6794 aFont.SetAverageFontWidth( 0 );
6795 FontMetric aMetric = GetFontMetric( aFont );
6796 if( aMetric.GetAverageFontWidth() != m_aCurrentPDFState.m_aFont.GetAverageFontWidth() )
6797 {
6798 fXScale =
6799 static_cast<double>(m_aCurrentPDFState.m_aFont.GetAverageFontWidth()) /
6800 static_cast<double>(aMetric.GetAverageFontWidth());
6801 }
6802 }
6803
6804 // if the mapmode is distorted we need to adjust for that also
6805 if( m_aCurrentPDFState.m_aMapMode.GetScaleX() != m_aCurrentPDFState.m_aMapMode.GetScaleY() )
6806 {
6807 fXScale *= double(m_aCurrentPDFState.m_aMapMode.GetScaleX()) / double(m_aCurrentPDFState.m_aMapMode.GetScaleY());
6808 }
6809
6810 Degree10 nAngle = m_aCurrentPDFState.m_aFont.GetOrientation();
6811 // normalize angles
6812 while( nAngle < 0_deg10 )
6813 nAngle += 3600_deg10;
6814 nAngle = nAngle % 3600_deg10;
6815 double fAngle = toRadians(nAngle);
6816
6817 Matrix3 aRotScale;
6818 aRotScale.scale( fXScale, 1.0 );
6819 if( fAngle != 0.0 )
6820 aRotScale.rotate( -fAngle );
6821
6822 bool bPop = false;
6823 bool bABold = false;
6824 // artificial bold necessary ?
6825 if (GetFontInstance()->NeedsArtificialBold())
6826 {
6827 aLine.append("q ");
6828 bPop = true;
6829 bABold = true;
6830 }
6831 // setup text colors (if necessary)
6832 Color aStrokeColor( COL_TRANSPARENT );
6833 Color aNonStrokeColor( COL_TRANSPARENT );
6834
6835 if( m_aCurrentPDFState.m_aFont.IsOutline() )
6836 {
6837 aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6838 aNonStrokeColor = COL_WHITE;
6839 }
6840 else
6841 aNonStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6842 if( bABold )
6843 aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6844
6845 if( aStrokeColor != COL_TRANSPARENT && aStrokeColor != m_aCurrentPDFState.m_aLineColor )
6846 {
6847 if( ! bPop )
6848 aLine.append( "q " );
6849 bPop = true;
6850 appendStrokingColor( aStrokeColor, aLine );
6851 aLine.append( "\n" );
6852 }
6853 if( aNonStrokeColor != COL_TRANSPARENT && aNonStrokeColor != m_aCurrentPDFState.m_aFillColor )
6854 {
6855 if( ! bPop )
6856 aLine.append( "q " );
6857 bPop = true;
6858 appendNonStrokingColor( aNonStrokeColor, aLine );
6859 aLine.append( "\n" );
6860 }
6861
6862 // begin text object
6863 aLine.append( "BT\n" );
6864 // outline attribute ?
6865 if( m_aCurrentPDFState.m_aFont.IsOutline() || bABold )
6866 {
6867 // set correct text mode, set stroke width
6868 aLine.append( "2 Tr " ); // fill, then stroke
6869
6870 if( m_aCurrentPDFState.m_aFont.IsOutline() )
6871 {
6872 // unclear what to do in case of outline and artificial bold
6873 // for the time being outline wins
6874 aLine.append( "0.25 w \n" );
6875 }
6876 else
6877 {
6878 double fW = static_cast<double>(m_aCurrentPDFState.m_aFont.GetFontHeight()) / 30.0;
6879 m_aPages.back().appendMappedLength( fW, aLine );
6880 aLine.append ( " w\n" );
6881 }
6882 }
6883
6884 FontMetric aRefDevFontMetric = GetFontMetric();
6885 const GlyphItem* pGlyph = nullptr;
6886 const LogicalFontInstance* pGlyphFont = nullptr;
6887
6888 // collect the glyphs into a single array
6889 std::vector< PDFGlyph > aGlyphs;
6890 aGlyphs.reserve( nMaxGlyphs );
6891 // first get all the glyphs and register them; coordinates still in Pixel
6892 basegfx::B2DPoint aPos;
6893 while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pGlyphFont))
6894 {
6895 const auto* pFace = pGlyphFont->GetFontFace();
6896
6897 aCodeUnits.clear();
6898
6899 // tdf#66597, tdf#115117
6900 //
6901 // Here is how we embed textual content in PDF files, to allow for
6902 // better text extraction for complex and typography-rich text.
6903 //
6904 // * If there is many to one or many to many mapping, use an
6905 // ActualText span embedding the original string, since ToUnicode
6906 // can't handle these.
6907 // * If the one glyph is used for several Unicode code points, also
6908 // use ActualText since ToUnicode can map each glyph in the font
6909 // only once.
6910 // * Limit ActualText to single cluster at a time, since using it
6911 // for whole words or sentences breaks text selection and
6912 // highlighting in PDF viewers (there will be no way to tell
6913 // which glyphs belong to which characters).
6914 // * Keep generating (now) redundant ToUnicode entries for
6915 // compatibility with old tools not supporting ActualText.
6916
6917 assert(pGlyph->charCount() >= 0);
6918 for (int n = 0; n < pGlyph->charCount(); n++)
6919 aCodeUnits.push_back(rText[pGlyph->charPos() + n]);
6920
6921 bool bUseActualText = false;
6922
6923 // If this is a start of complex cluster, use ActualText.
6924 if (pGlyph->IsClusterStart())
6925 bUseActualText = true;
6926
6927 const auto nGlyphId = pGlyph->glyphId();
6928
6929 // A glyph can't have more than one ToUnicode entry, use ActualText
6930 // instead.
6931 if (!aCodeUnits.empty() && !bUseActualText)
6932 {
6933 for (const auto& rSubset : m_aSubsets[pFace].m_aSubsets)
6934 {
6935 const auto it = rSubset.m_aMapping.find(nGlyphId);
6936 if (it != rSubset.m_aMapping.cend() && it->second.codes() != aCodeUnits)
6937 {
6938 bUseActualText = true;
6939 aCodeUnits.clear();
6940 }
6941 }
6942 }
6943
6944 // tdf#157390: The width stored by registerGlyph() must be the actual glyph width.
6945 // This must be obtained by calling GetGlyphWidth(vertical=false), otherwise an incorrect
6946 // width value will be returned for CJK characters.
6947 auto nMappedGlyphWidth = pGlyphFont->GetGlyphWidth(nGlyphId, /*vertical*/ false, false);
6948 auto nGlyphWidth = nMappedGlyphWidth;
6949 if (pGlyph->IsVertical())
6950 {
6951 nGlyphWidth = pGlyphFont->GetGlyphWidth(nGlyphId, /*vertical*/ true, false);
6952 }
6953
6954 sal_uInt8 nMappedGlyph;
6955 sal_Int32 nMappedFontObject;
6956 registerGlyph(nGlyphId, pFace, pGlyphFont, aCodeUnits, nMappedGlyphWidth, nMappedGlyph,
6957 nMappedFontObject);
6958
6959 int nCharPos = -1;
6960 if (bUseActualText || pGlyph->IsInCluster())
6961 nCharPos = pGlyph->charPos();
6962
6963 aGlyphs.emplace_back(aPos,
6964 pGlyph,
6965 pGlyphFont,
6966 XUnits(pFace->UnitsPerEm(), nGlyphWidth),
6967 nMappedFontObject,
6968 nMappedGlyph,
6969 nCharPos);
6970 }
6971
6972 // Avoid fill color when map mode is in pixels, the below code assumes
6973 // logic map mode.
6974 bool bPixel = m_aCurrentPDFState.m_aMapMode.GetMapUnit() == MapUnit::MapPixel;
6975 if (m_aCurrentPDFState.m_aFont.GetFillColor() != COL_TRANSPARENT && !bPixel)
6976 {
6977 // PDF doesn't have a text fill color, so draw a rectangle before
6978 // drawing the actual text.
6979 push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR);
6980 setFillColor(m_aCurrentPDFState.m_aFont.GetFillColor());
6981 // Avoid border around the rectangle for Writer shape text.
6982 setLineColor(COL_TRANSPARENT);
6983
6984 // The rectangle is the bounding box of the text, but also includes
6985 // ascent / descent to match the on-screen rendering.
6986 // This is the top left of the text without ascent / descent.
6987 basegfx::B2DPoint aDrawPosition(rLayout.GetDrawPosition());
6988 tools::Rectangle aRectangle(SubPixelToLogic(aDrawPosition),
6989 Size(ImplDevicePixelToLogicWidth(rLayout.GetTextWidth()), 0));
6990 aRectangle.AdjustTop(-aRefDevFontMetric.GetAscent());
6991 // This includes ascent / descent.
6992 aRectangle.setHeight(aRefDevFontMetric.GetLineHeight());
6993
6994 const LogicalFontInstance* pFontInstance = GetFontInstance();
6995 if (pFontInstance->mnOrientation)
6996 {
6997 // Adapt rectangle for rotated text.
6998 tools::Polygon aPolygon(aRectangle);
6999 aPolygon.Rotate(SubPixelToLogic(aDrawPosition), pFontInstance->mnOrientation);
7000 drawPolygon(aPolygon);
7001 }
7002 else
7003 drawRectangle(aRectangle);
7004
7005 pop();
7006 }
7007
7008 Point aAlignOffset;
7009 if ( eAlign == ALIGN_BOTTOM )
7010 aAlignOffset.AdjustY( -(aRefDevFontMetric.GetDescent()) );
7011 else if ( eAlign == ALIGN_TOP )
7012 aAlignOffset.AdjustY(aRefDevFontMetric.GetAscent() );
7013 if( aAlignOffset.X() || aAlignOffset.Y() )
7014 aAlignOffset = aRotScale.transform( aAlignOffset );
7015
7016 /* #159153# do not emit an empty glyph vector; this can happen if e.g. the original
7017 string contained only one of the UTF16 BOMs
7018 */
7019 if( ! aGlyphs.empty() )
7020 {
7021 size_t nStart = 0;
7022 size_t nEnd = 0;
7023 while (nStart < aGlyphs.size())
7024 {
7025 while (nEnd < aGlyphs.size() && aGlyphs[nEnd].m_nCharPos == aGlyphs[nStart].m_nCharPos)
7026 nEnd++;
7027
7028 std::vector<PDFGlyph> aRun(aGlyphs.begin() + nStart, aGlyphs.begin() + nEnd);
7029
7030 int nCharPos, nCharCount;
7031 if (!aRun.front().m_pGlyph->IsRTLGlyph())
7032 {
7033 nCharPos = aRun.front().m_nCharPos;
7034 nCharCount = aRun.front().m_pGlyph->charCount();
7035 }
7036 else
7037 {
7038 nCharPos = aRun.back().m_nCharPos;
7039 nCharCount = aRun.back().m_pGlyph->charCount();
7040 }
7041
7042 if (nCharPos >= 0 && nCharCount)
7043 {
7044 aLine.append("/Span<</ActualText<FEFF");
7045 for (int i = 0; i < nCharCount; i++)
7046 {
7047 sal_Unicode aChar = rText[nCharPos + i];
7048 COSWriter::appendHex(static_cast<sal_Int8>(aChar >> 8), aLine);
7049 COSWriter::appendHex(static_cast<sal_Int8>(aChar & 255), aLine);
7050 }
7051 aLine.append( ">>>\nBDC\n" );
7052 }
7053
7054 if (bVertical)
7055 drawVerticalGlyphs(aRun, aLine, aAlignOffset, aRotScale, fAngle, fXScale, nFontHeight);
7056 else
7057 drawHorizontalGlyphs(aRun, aLine, aAlignOffset, nStart == 0, fAngle, fXScale, nFontHeight, nPixelFontHeight);
7058
7059 if (nCharPos >= 0 && nCharCount)
7060 aLine.append( "EMC\n" );
7061
7062 nStart = nEnd;
7063 }
7064 }
7065
7066 // end textobject
7067 aLine.append( "ET\n" );
7068 if( bPop )
7069 aLine.append( "Q\n" );
7070
7071 writeBuffer( aLine );
7072
7073 // draw eventual textlines
7074 FontStrikeout eStrikeout = m_aCurrentPDFState.m_aFont.GetStrikeout();
7075 FontLineStyle eUnderline = m_aCurrentPDFState.m_aFont.GetUnderline();
7076 FontLineStyle eOverline = m_aCurrentPDFState.m_aFont.GetOverline();
7077 if( bTextLines &&
7078 (
7079 ( eUnderline != LINESTYLE_NONE && eUnderline != LINESTYLE_DONTKNOW ) ||
7080 ( eOverline != LINESTYLE_NONE && eOverline != LINESTYLE_DONTKNOW ) ||
7081 ( eStrikeout != STRIKEOUT_NONE && eStrikeout != STRIKEOUT_DONTKNOW )
7082 )
7083 )
7084 {
7085 bool bUnderlineAbove = m_aCurrentPDFState.m_aFont.IsUnderlineAbove();
7086 if( m_aCurrentPDFState.m_aFont.IsWordLineMode() )
7087 {
7088 basegfx::B2DPoint aStartPt;
7089 double nWidth = 0;
7090 nIndex = 0;
7091 while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex))
7092 {
7093 if (!pGlyph->IsSpacing())
7094 {
7095 if( !nWidth )
7096 aStartPt = aPos;
7097
7098 nWidth += pGlyph->newWidth();
7099 }
7100 else if( nWidth > 0 )
7101 {
7102 drawTextLine( SubPixelToLogic(aStartPt),
7103 ImplDevicePixelToLogicWidth( nWidth ),
7104 eStrikeout, eUnderline, eOverline, bUnderlineAbove );
7105 nWidth = 0;
7106 }
7107 }
7108
7109 if( nWidth > 0 )
7110 {
7111 drawTextLine( SubPixelToLogic(aStartPt),
7112 ImplDevicePixelToLogicWidth( nWidth ),
7113 eStrikeout, eUnderline, eOverline, bUnderlineAbove );
7114 }
7115 }
7116 else
7117 {
7118 basegfx::B2DPoint aStartPt = rLayout.GetDrawPosition();
7119 int nWidth = rLayout.GetTextWidth();
7120 drawTextLine( SubPixelToLogic(aStartPt),
7121 ImplDevicePixelToLogicWidth( nWidth ),
7122 eStrikeout, eUnderline, eOverline, bUnderlineAbove );
7123 }
7124 }
7125
7126 // write eventual emphasis marks
7127 if( !(m_aCurrentPDFState.m_aFont.GetEmphasisMark() & FontEmphasisMark::Style) )
7128 return;
7129
7130 push( PushFlags::ALL );
7131
7132 aLine.setLength( 0 );
7133 aLine.append( "q\n" );
7134
7135 FontEmphasisMark nEmphMark = m_aCurrentPDFState.m_aFont.GetEmphasisMarkStyle();
7136
7137 tools::Long nEmphHeight;
7138 if ( nEmphMark & FontEmphasisMark::PosBelow )
7139 nEmphHeight = GetEmphasisDescent();
7140 else
7141 nEmphHeight = GetEmphasisAscent();
7142
7143 vcl::font::EmphasisMark aEmphasisMark(nEmphMark, ImplDevicePixelToLogicWidth(nEmphHeight), GetDPIY());
7144 if ( aEmphasisMark.IsShapePolyLine() )
7145 {
7146 setLineColor( m_aCurrentPDFState.m_aFont.GetColor() );
7147 setFillColor( COL_TRANSPARENT );
7148 }
7149 else
7150 {
7151 setFillColor( m_aCurrentPDFState.m_aFont.GetColor() );
7152 setLineColor( COL_TRANSPARENT );
7153 }
7154
7155 writeBuffer( aLine );
7156
7157 Point aOffset(0,0);
7158 Point aOffsetVert(0,0);
7159
7160 if ( nEmphMark & FontEmphasisMark::PosBelow )
7161 {
7162 aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset() );
7163 aOffsetVert = aOffset;
7164 }
7165 else
7166 {
7167 aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetAscent() + aEmphasisMark.GetYOffset()) );
7168 // Todo: use ideographic em-box or ideographic character face information.
7169 aOffsetVert.AdjustY(-(GetFontInstance()->mxFontMetric->GetAscent() +
7170 GetFontInstance()->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset()));
7171 }
7172
7173 tools::Long nEmphWidth2 = aEmphasisMark.GetWidth() / 2;
7174 tools::Long nEmphHeight2 = nEmphHeight / 2;
7175 aOffset += Point( nEmphWidth2, nEmphHeight2 );
7176
7177 if ( eAlign == ALIGN_BOTTOM )
7178 aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetDescent()) );
7179 else if ( eAlign == ALIGN_TOP )
7180 aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetAscent() );
7181
7182 basegfx::B2DRectangle aRectangle;
7183 nIndex = 0;
7184 while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pGlyphFont))
7185 {
7186 if (!pGlyph->GetGlyphBoundRect(pGlyphFont, aRectangle))
7187 continue;
7188
7189 if (!pGlyph->IsSpacing())
7190 {
7191 basegfx::B2DPoint aAdjOffset;
7192 if (pGlyph->IsVertical())
7193 {
7194 aAdjOffset = basegfx::B2DPoint(aOffsetVert.X(), aOffsetVert.Y());
7195 aAdjOffset.adjustX((-pGlyph->origWidth() + aEmphasisMark.GetWidth()) / 2);
7196 }
7197 else
7198 {
7199 aAdjOffset = basegfx::B2DPoint(aOffset.X(), aOffset.Y());
7200 aAdjOffset.adjustX(aRectangle.getMinX() + (aRectangle.getWidth() - aEmphasisMark.GetWidth()) / 2 );
7201 }
7202
7203 aAdjOffset = aRotScale.transform( aAdjOffset );
7204
7205 aAdjOffset -= basegfx::B2DPoint(nEmphWidth2, nEmphHeight2);
7206
7207 basegfx::B2DPoint aMarkDevPos(aPos);
7208 aMarkDevPos += aAdjOffset;
7209 Point aMarkPos = SubPixelToLogic(aMarkDevPos);
7210 drawEmphasisMark( aMarkPos.X(), aMarkPos.Y(),
7211 aEmphasisMark.GetShape(), aEmphasisMark.IsShapePolyLine(),
7212 aEmphasisMark.GetRect1(), aEmphasisMark.GetRect2() );
7213 }
7214 }
7215
7216 writeBuffer( "Q\n" );
7217 pop();
7218
7219 }
7220
drawEmphasisMark(tools::Long nX,tools::Long nY,const tools::PolyPolygon & rPolyPoly,bool bPolyLine,const tools::Rectangle & rRect1,const tools::Rectangle & rRect2)7221 void PDFWriterImpl::drawEmphasisMark( tools::Long nX, tools::Long nY,
7222 const tools::PolyPolygon& rPolyPoly, bool bPolyLine,
7223 const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 )
7224 {
7225 // TODO: pass nWidth as width of this mark
7226 // long nWidth = 0;
7227
7228 if ( rPolyPoly.Count() )
7229 {
7230 if ( bPolyLine )
7231 {
7232 tools::Polygon aPoly = rPolyPoly.GetObject( 0 );
7233 aPoly.Move( nX, nY );
7234 drawPolyLine( aPoly );
7235 }
7236 else
7237 {
7238 tools::PolyPolygon aPolyPoly = rPolyPoly;
7239 aPolyPoly.Move( nX, nY );
7240 drawPolyPolygon( aPolyPoly );
7241 }
7242 }
7243
7244 if ( !rRect1.IsEmpty() )
7245 {
7246 tools::Rectangle aRect( Point( nX+rRect1.Left(),
7247 nY+rRect1.Top() ), rRect1.GetSize() );
7248 drawRectangle( aRect );
7249 }
7250
7251 if ( !rRect2.IsEmpty() )
7252 {
7253 tools::Rectangle aRect( Point( nX+rRect2.Left(),
7254 nY+rRect2.Top() ), rRect2.GetSize() );
7255
7256 drawRectangle( aRect );
7257 }
7258 }
7259
drawText(const Point & rPos,const OUString & rText,sal_Int32 nIndex,sal_Int32 nLen,bool bTextLines)7260 void PDFWriterImpl::drawText( const Point& rPos, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen, bool bTextLines )
7261 {
7262 MARK( "drawText" );
7263
7264 updateGraphicsState();
7265
7266 // get a layout from the OutputDevice's SalGraphics
7267 // this also enforces font substitution and sets the font on SalGraphics
7268 const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->
7269 GetLayoutGlyphs( this, rText, nIndex, nLen );
7270 std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos,
7271 0, {}, {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs );
7272 if( pLayout )
7273 {
7274 drawLayout( *pLayout, rText, bTextLines );
7275 }
7276 }
7277
drawTextArray(const Point & rPos,const OUString & rText,KernArraySpan pDXArray,std::span<const sal_Bool> pKashidaArray,sal_Int32 nIndex,sal_Int32 nLen,sal_Int32 nLayoutContextIndex,sal_Int32 nLayoutContextLen)7278 void PDFWriterImpl::drawTextArray(const Point& rPos, const OUString& rText, KernArraySpan pDXArray,
7279 std::span<const sal_Bool> pKashidaArray, sal_Int32 nIndex,
7280 sal_Int32 nLen, sal_Int32 nLayoutContextIndex,
7281 sal_Int32 nLayoutContextLen)
7282 {
7283 MARK( "drawText with array" );
7284
7285 updateGraphicsState();
7286
7287 // get a layout from the OutputDevice's SalGraphics
7288 // this also enforces font substitution and sets the font on SalGraphics
7289 std::unique_ptr<SalLayout> pLayout;
7290 if (nLayoutContextIndex >= 0)
7291 {
7292 const auto* layoutGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(
7293 this, rText, nLayoutContextIndex, nLayoutContextLen, nIndex, nIndex + nLen);
7294 pLayout = ImplLayout(rText, nLayoutContextIndex, nLayoutContextLen, rPos, 0, pDXArray,
7295 pKashidaArray, SalLayoutFlags::UnclusteredGlyphs, nullptr,
7296 layoutGlyphs, /*nDrawOriginCluster=*/nIndex,
7297 /*nDrawMinCharPos=*/nIndex, /*nDrawEndCharPos=*/nIndex + nLen);
7298 }
7299 else
7300 {
7301 const auto* layoutGlyphs
7302 = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(this, rText, nIndex, nLen);
7303 pLayout = ImplLayout(rText, nIndex, nLen, rPos, 0, pDXArray, pKashidaArray,
7304 SalLayoutFlags::NONE, nullptr, layoutGlyphs);
7305 }
7306
7307 if( pLayout )
7308 {
7309 drawLayout( *pLayout, rText, true );
7310 }
7311 }
7312
drawStretchText(const Point & rPos,sal_Int32 nWidth,const OUString & rText,sal_Int32 nIndex,sal_Int32 nLen)7313 void PDFWriterImpl::drawStretchText( const Point& rPos, sal_Int32 nWidth, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen )
7314 {
7315 MARK( "drawStretchText" );
7316
7317 updateGraphicsState();
7318
7319 // get a layout from the OutputDevice's SalGraphics
7320 // this also enforces font substitution and sets the font on SalGraphics
7321 const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->
7322 GetLayoutGlyphs( this, rText, nIndex, nLen, nWidth );
7323 std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, nWidth,
7324 {}, {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs );
7325 if( pLayout )
7326 {
7327 drawLayout( *pLayout, rText, true );
7328 }
7329 }
7330
drawText(const tools::Rectangle & rRect,const OUString & rOrigStr,DrawTextFlags nStyle)7331 void PDFWriterImpl::drawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle )
7332 {
7333 tools::Long nWidth = rRect.GetWidth();
7334 tools::Long nHeight = rRect.GetHeight();
7335
7336 if ( nWidth <= 0 || nHeight <= 0 )
7337 return;
7338
7339 MARK( "drawText with rectangle" );
7340
7341 updateGraphicsState();
7342
7343 // clip with rectangle
7344 OStringBuffer aLine;
7345 aLine.append( "q " );
7346 m_aPages.back().appendRect( rRect, aLine );
7347 aLine.append( " W* n\n" );
7348 writeBuffer( aLine );
7349
7350 // if disabled text is needed, put in here
7351
7352 Point aPos = rRect.TopLeft();
7353
7354 tools::Long nTextHeight = GetTextHeight();
7355
7356 OUString aStr = rOrigStr;
7357 if ( nStyle & DrawTextFlags::Mnemonic )
7358 aStr = removeMnemonicFromString(aStr);
7359
7360 // multiline text
7361 if ( nStyle & DrawTextFlags::MultiLine )
7362 {
7363 ImplMultiTextLineInfo aMultiLineInfo;
7364 sal_Int32 i;
7365 sal_Int32 nFormatLines;
7366
7367 if ( nTextHeight )
7368 {
7369 vcl::DefaultTextLayout aLayout( *this );
7370 OUString aLastLine;
7371 aLayout.GetTextLines( rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle );
7372 sal_Int32 nLines = nHeight/nTextHeight;
7373 nFormatLines = aMultiLineInfo.Count();
7374 if ( !nLines )
7375 nLines = 1;
7376 if ( nFormatLines > nLines )
7377 {
7378 if ( nStyle & DrawTextFlags::EndEllipsis )
7379 {
7380 // handle last line
7381 nFormatLines = nLines-1;
7382
7383 ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( nFormatLines );
7384 aLastLine = convertLineEnd(aStr.copy(rLineInfo.GetIndex()), LINEEND_LF);
7385 // replace line feed by space
7386 aLastLine = aLastLine.replace('\n', ' ');
7387 aLastLine = GetEllipsisString( aLastLine, nWidth, nStyle );
7388 nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom);
7389 nStyle |= DrawTextFlags::Top;
7390 }
7391 }
7392
7393 // vertical alignment
7394 if ( nStyle & DrawTextFlags::Bottom )
7395 aPos.AdjustY(nHeight-(nFormatLines*nTextHeight) );
7396 else if ( nStyle & DrawTextFlags::VCenter )
7397 aPos.AdjustY((nHeight-(nFormatLines*nTextHeight))/2 );
7398
7399 // draw all lines excluding the last
7400 for ( i = 0; i < nFormatLines; i++ )
7401 {
7402 ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i );
7403 if ( nStyle & DrawTextFlags::Right )
7404 aPos.AdjustX(nWidth-rLineInfo.GetWidth() );
7405 else if ( nStyle & DrawTextFlags::Center )
7406 aPos.AdjustX((nWidth-rLineInfo.GetWidth())/2 );
7407 sal_Int32 nIndex = rLineInfo.GetIndex();
7408 sal_Int32 nLineLen = rLineInfo.GetLen();
7409 drawText( aPos, aStr, nIndex, nLineLen );
7410 // mnemonics should not appear in documents,
7411 // if the need arises, put them in here
7412 aPos.AdjustY(nTextHeight );
7413 aPos.setX( rRect.Left() );
7414 }
7415
7416 // output last line left adjusted since it was shortened
7417 if (!aLastLine.isEmpty())
7418 drawText( aPos, aLastLine, 0, aLastLine.getLength() );
7419 }
7420 }
7421 else
7422 {
7423 tools::Long nTextWidth = GetTextWidth( aStr );
7424
7425 // Evt. Text kuerzen
7426 if ( nTextWidth > nWidth )
7427 {
7428 if ( nStyle & (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis) )
7429 {
7430 aStr = GetEllipsisString( aStr, nWidth, nStyle );
7431 nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right);
7432 nStyle |= DrawTextFlags::Left;
7433 nTextWidth = GetTextWidth( aStr );
7434 }
7435 }
7436
7437 // vertical alignment
7438 if ( nStyle & DrawTextFlags::Right )
7439 aPos.AdjustX(nWidth-nTextWidth );
7440 else if ( nStyle & DrawTextFlags::Center )
7441 aPos.AdjustX((nWidth-nTextWidth)/2 );
7442
7443 if ( nStyle & DrawTextFlags::Bottom )
7444 aPos.AdjustY(nHeight-nTextHeight );
7445 else if ( nStyle & DrawTextFlags::VCenter )
7446 aPos.AdjustY((nHeight-nTextHeight)/2 );
7447
7448 // mnemonics should be inserted here if the need arises
7449
7450 // draw the actual text
7451 drawText( aPos, aStr, 0, aStr.getLength() );
7452 }
7453
7454 // reset clip region to original value
7455 aLine.setLength( 0 );
7456 aLine.append( "Q\n" );
7457 writeBuffer( aLine );
7458 }
7459
drawLine(const Point & rStart,const Point & rStop)7460 void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop )
7461 {
7462 MARK( "drawLine" );
7463
7464 updateGraphicsState();
7465
7466 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
7467 return;
7468
7469 OStringBuffer aLine;
7470 m_aPages.back().appendPoint( rStart, aLine );
7471 aLine.append( " m " );
7472 m_aPages.back().appendPoint( rStop, aLine );
7473 aLine.append( " l S\n" );
7474
7475 writeBuffer( aLine );
7476 }
7477
drawLine(const Point & rStart,const Point & rStop,const LineInfo & rInfo)7478 void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo )
7479 {
7480 MARK( "drawLine with LineInfo" );
7481 updateGraphicsState();
7482
7483 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
7484 return;
7485
7486 if( rInfo.GetStyle() == LineStyle::Solid && rInfo.GetWidth() < 2 )
7487 {
7488 drawLine( rStart, rStop );
7489 return;
7490 }
7491
7492 OStringBuffer aLine;
7493
7494 aLine.append( "q " );
7495 if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
7496 {
7497 m_aPages.back().appendPoint( rStart, aLine );
7498 aLine.append( " m " );
7499 m_aPages.back().appendPoint( rStop, aLine );
7500 aLine.append( " l S Q\n" );
7501
7502 writeBuffer( aLine );
7503 }
7504 else
7505 {
7506 PDFWriter::ExtLineInfo aInfo;
7507 convertLineInfoToExtLineInfo( rInfo, aInfo );
7508 Point aPolyPoints[2] = { rStart, rStop };
7509 tools::Polygon aPoly( 2, aPolyPoints );
7510 drawPolyLine( aPoly, aInfo );
7511 }
7512 }
7513
7514 #define HCONV( x ) ImplDevicePixelToLogicHeight( x )
7515
drawWaveTextLine(OStringBuffer & aLine,tools::Long nWidth,FontLineStyle eTextLine,Color aColor,bool bIsAbove)7516 void PDFWriterImpl::drawWaveTextLine( OStringBuffer& aLine, tools::Long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
7517 {
7518 // note: units in pFontInstance are ref device pixel
7519 const LogicalFontInstance* pFontInstance = GetFontInstance();
7520 tools::Long nLineHeight = 0;
7521 tools::Long nLinePos = 0;
7522
7523 appendStrokingColor( aColor, aLine );
7524 aLine.append( "\n" );
7525
7526 if ( bIsAbove )
7527 {
7528 if ( !pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() )
7529 ImplInitAboveTextLineSize();
7530 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() );
7531 nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineOffset() );
7532 }
7533 else
7534 {
7535 if ( !pFontInstance->mxFontMetric->GetWavelineUnderlineSize() )
7536 ImplInitTextLineSize();
7537 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineSize() );
7538 nLinePos = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineOffset() );
7539 }
7540 if ( (eTextLine == LINESTYLE_SMALLWAVE) && (nLineHeight > 3) )
7541 nLineHeight = 3;
7542
7543 tools::Long nLineWidth = GetDPIX()/450;
7544 if ( ! nLineWidth )
7545 nLineWidth = 1;
7546
7547 if ( eTextLine == LINESTYLE_BOLDWAVE )
7548 nLineWidth = 3*nLineWidth;
7549
7550 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineWidth), aLine );
7551 aLine.append( " w " );
7552
7553 if ( eTextLine == LINESTYLE_DOUBLEWAVE )
7554 {
7555 tools::Long nOrgLineHeight = nLineHeight;
7556 nLineHeight /= 3;
7557 if ( nLineHeight < 2 )
7558 {
7559 if ( nOrgLineHeight > 1 )
7560 nLineHeight = 2;
7561 else
7562 nLineHeight = 1;
7563 }
7564 tools::Long nLineDY = nOrgLineHeight-(nLineHeight*2);
7565 if ( nLineDY < nLineWidth )
7566 nLineDY = nLineWidth;
7567 tools::Long nLineDY2 = nLineDY/2;
7568 if ( !nLineDY2 )
7569 nLineDY2 = 1;
7570
7571 nLinePos -= nLineWidth-nLineDY2;
7572
7573 m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
7574
7575 nLinePos += nLineWidth+nLineDY;
7576 m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
7577 }
7578 else
7579 {
7580 if ( eTextLine != LINESTYLE_BOLDWAVE )
7581 nLinePos -= nLineWidth/2;
7582 m_aPages.back().appendWaveLine( nWidth, -nLinePos, nLineHeight, aLine );
7583 }
7584 }
7585
drawStraightTextLine(OStringBuffer & aLine,tools::Long nWidth,FontLineStyle eTextLine,Color aColor,bool bIsAbove)7586 void PDFWriterImpl::drawStraightTextLine( OStringBuffer& aLine, tools::Long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
7587 {
7588 // note: units in pFontInstance are ref device pixel
7589 const LogicalFontInstance* pFontInstance = GetFontInstance();
7590 tools::Long nLineHeight = 0;
7591 tools::Long nLinePos = 0;
7592 tools::Long nLinePos2 = 0;
7593
7594 if ( eTextLine > LINESTYLE_BOLDWAVE )
7595 eTextLine = LINESTYLE_SINGLE;
7596
7597 switch ( eTextLine )
7598 {
7599 case LINESTYLE_SINGLE:
7600 case LINESTYLE_DOTTED:
7601 case LINESTYLE_DASH:
7602 case LINESTYLE_LONGDASH:
7603 case LINESTYLE_DASHDOT:
7604 case LINESTYLE_DASHDOTDOT:
7605 if ( bIsAbove )
7606 {
7607 if ( !pFontInstance->mxFontMetric->GetAboveUnderlineSize() )
7608 ImplInitAboveTextLineSize();
7609 nLineHeight = pFontInstance->mxFontMetric->GetAboveUnderlineSize();
7610 nLinePos = pFontInstance->mxFontMetric->GetAboveUnderlineOffset();
7611 }
7612 else
7613 {
7614 if ( !pFontInstance->mxFontMetric->GetUnderlineSize() )
7615 ImplInitTextLineSize();
7616 nLineHeight = pFontInstance->mxFontMetric->GetUnderlineSize();
7617 nLinePos = pFontInstance->mxFontMetric->GetUnderlineOffset();
7618 }
7619 break;
7620 case LINESTYLE_BOLD:
7621 case LINESTYLE_BOLDDOTTED:
7622 case LINESTYLE_BOLDDASH:
7623 case LINESTYLE_BOLDLONGDASH:
7624 case LINESTYLE_BOLDDASHDOT:
7625 case LINESTYLE_BOLDDASHDOTDOT:
7626 if ( bIsAbove )
7627 {
7628 if ( !pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() )
7629 ImplInitAboveTextLineSize();
7630 nLineHeight = pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize();
7631 nLinePos = pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset();
7632 }
7633 else
7634 {
7635 if ( !pFontInstance->mxFontMetric->GetBoldUnderlineSize() )
7636 ImplInitTextLineSize();
7637 nLineHeight = pFontInstance->mxFontMetric->GetBoldUnderlineSize();
7638 nLinePos = pFontInstance->mxFontMetric->GetBoldUnderlineOffset();
7639 }
7640 break;
7641 case LINESTYLE_DOUBLE:
7642 if ( bIsAbove )
7643 {
7644 if ( !pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() )
7645 ImplInitAboveTextLineSize();
7646 nLineHeight = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize();
7647 nLinePos = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1();
7648 nLinePos2 = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2();
7649 }
7650 else
7651 {
7652 if ( !pFontInstance->mxFontMetric->GetDoubleUnderlineSize() )
7653 ImplInitTextLineSize();
7654 nLineHeight = pFontInstance->mxFontMetric->GetDoubleUnderlineSize();
7655 nLinePos = pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1();
7656 nLinePos2 = pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2();
7657 }
7658 break;
7659 default:
7660 break;
7661 }
7662
7663 if ( !nLineHeight )
7664 return;
7665
7666 // tdf#154235
7667 // nLinePos/nLinePos2 is the distance from baseline to the top of the line,
7668 // while in PDF we stroke the line so the position is to the middle of the
7669 // line, we add half of nLineHeight to account for that.
7670 auto nOffset = nLineHeight / 2;
7671 if (m_aCurrentPDFState.m_aFont.IsOutline() && eTextLine == LINESTYLE_SINGLE)
7672 {
7673 // Except when outlining, as now we are drawing a rectangle, so
7674 // nLinePos is the bottom of the rectangle, so need to add nLineHeight
7675 // to it.
7676 nOffset = nLineHeight;
7677 }
7678
7679 nLineHeight = HCONV(nLineHeight);
7680 nLinePos = HCONV(nLinePos + nOffset);
7681 nLinePos2 = HCONV(nLinePos2 + nOffset);
7682
7683 // outline attribute ?
7684 if (m_aCurrentPDFState.m_aFont.IsOutline() && eTextLine == LINESTYLE_SINGLE)
7685 {
7686 appendStrokingColor(aColor, aLine); // stroke with text color
7687 aLine.append( " " );
7688 appendNonStrokingColor(COL_WHITE, aLine); // fill with white
7689 aLine.append( "\n" );
7690 aLine.append( "0.25 w \n" ); // same line thickness as in drawLayout
7691
7692 // draw rectangle instead
7693 aLine.append( "0 " );
7694 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7695 aLine.append( " " );
7696 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
7697 aLine.append( ' ' );
7698 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7699 aLine.append( " re h B\n" );
7700 return;
7701 }
7702
7703
7704 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7705 aLine.append( " w " );
7706 appendStrokingColor( aColor, aLine );
7707 aLine.append( "\n" );
7708
7709 switch ( eTextLine )
7710 {
7711 case LINESTYLE_DOTTED:
7712 case LINESTYLE_BOLDDOTTED:
7713 aLine.append( "[ " );
7714 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7715 aLine.append( " ] 0 d\n" );
7716 break;
7717 case LINESTYLE_DASH:
7718 case LINESTYLE_LONGDASH:
7719 case LINESTYLE_BOLDDASH:
7720 case LINESTYLE_BOLDLONGDASH:
7721 {
7722 sal_Int32 nDashLength = 4*nLineHeight;
7723 sal_Int32 nVoidLength = 2*nLineHeight;
7724 if ( ( eTextLine == LINESTYLE_LONGDASH ) || ( eTextLine == LINESTYLE_BOLDLONGDASH ) )
7725 nDashLength = 8*nLineHeight;
7726
7727 aLine.append( "[ " );
7728 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7729 aLine.append( ' ' );
7730 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7731 aLine.append( " ] 0 d\n" );
7732 }
7733 break;
7734 case LINESTYLE_DASHDOT:
7735 case LINESTYLE_BOLDDASHDOT:
7736 {
7737 sal_Int32 nDashLength = 4*nLineHeight;
7738 sal_Int32 nVoidLength = 2*nLineHeight;
7739 aLine.append( "[ " );
7740 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7741 aLine.append( ' ' );
7742 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7743 aLine.append( ' ' );
7744 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7745 aLine.append( ' ' );
7746 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7747 aLine.append( " ] 0 d\n" );
7748 }
7749 break;
7750 case LINESTYLE_DASHDOTDOT:
7751 case LINESTYLE_BOLDDASHDOTDOT:
7752 {
7753 sal_Int32 nDashLength = 4*nLineHeight;
7754 sal_Int32 nVoidLength = 2*nLineHeight;
7755 aLine.append( "[ " );
7756 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7757 aLine.append( ' ' );
7758 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7759 aLine.append( ' ' );
7760 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7761 aLine.append( ' ' );
7762 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7763 aLine.append( ' ' );
7764 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7765 aLine.append( ' ' );
7766 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7767 aLine.append( " ] 0 d\n" );
7768 }
7769 break;
7770 default:
7771 break;
7772 }
7773
7774 aLine.append( "0 " );
7775 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7776 aLine.append( " m " );
7777 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
7778 aLine.append( ' ' );
7779 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7780 aLine.append( " l S\n" );
7781 if ( eTextLine == LINESTYLE_DOUBLE )
7782 {
7783 aLine.append( "0 " );
7784 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7785 aLine.append( " m " );
7786 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
7787 aLine.append( ' ' );
7788 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7789 aLine.append( " l S\n" );
7790 }
7791
7792 }
7793
drawStrikeoutLine(OStringBuffer & aLine,tools::Long nWidth,FontStrikeout eStrikeout,Color aColor)7794 void PDFWriterImpl::drawStrikeoutLine( OStringBuffer& aLine, tools::Long nWidth, FontStrikeout eStrikeout, Color aColor )
7795 {
7796 // note: units in pFontInstance are ref device pixel
7797 const LogicalFontInstance* pFontInstance = GetFontInstance();
7798 tools::Long nLineHeight = 0;
7799 tools::Long nLinePos = 0;
7800 tools::Long nLinePos2 = 0;
7801
7802 if ( eStrikeout > STRIKEOUT_X )
7803 eStrikeout = STRIKEOUT_SINGLE;
7804
7805 switch ( eStrikeout )
7806 {
7807 case STRIKEOUT_SINGLE:
7808 if ( !pFontInstance->mxFontMetric->GetStrikeoutSize() )
7809 ImplInitTextLineSize();
7810 nLineHeight = pFontInstance->mxFontMetric->GetStrikeoutSize();
7811 nLinePos = pFontInstance->mxFontMetric->GetStrikeoutOffset();
7812 break;
7813 case STRIKEOUT_BOLD:
7814 if ( !pFontInstance->mxFontMetric->GetBoldStrikeoutSize() )
7815 ImplInitTextLineSize();
7816 nLineHeight = pFontInstance->mxFontMetric->GetBoldStrikeoutSize();
7817 nLinePos = pFontInstance->mxFontMetric->GetBoldStrikeoutOffset();
7818 break;
7819 case STRIKEOUT_DOUBLE:
7820 if ( !pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() )
7821 ImplInitTextLineSize();
7822 nLineHeight = pFontInstance->mxFontMetric->GetDoubleStrikeoutSize();
7823 nLinePos = pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1();
7824 nLinePos2 = pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2();
7825 break;
7826 default:
7827 break;
7828 }
7829
7830 if ( !nLineHeight )
7831 return;
7832
7833 // tdf#154235
7834 // nLinePos/nLinePos2 is the distance from baseline to the bottom of the line,
7835 // while in PDF we stroke the line so the position is to the middle of the
7836 // line, we add half of nLineHeight to account for that.
7837 nLinePos = HCONV(nLinePos + nLineHeight / 2);
7838 nLinePos2 = HCONV(nLinePos2 + nLineHeight / 2);
7839 nLineHeight = HCONV(nLineHeight);
7840
7841 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7842 aLine.append( " w " );
7843 appendStrokingColor( aColor, aLine );
7844 aLine.append( "\n" );
7845
7846 aLine.append( "0 " );
7847 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7848 aLine.append( " m " );
7849 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
7850 aLine.append( ' ' );
7851 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7852 aLine.append( " l S\n" );
7853
7854 if ( eStrikeout == STRIKEOUT_DOUBLE )
7855 {
7856 aLine.append( "0 " );
7857 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7858 aLine.append( " m " );
7859 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
7860 aLine.append( ' ' );
7861 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7862 aLine.append( " l S\n" );
7863 }
7864
7865 }
7866
drawStrikeoutChar(const Point & rPos,tools::Long nWidth,FontStrikeout eStrikeout)7867 void PDFWriterImpl::drawStrikeoutChar( const Point& rPos, tools::Long nWidth, FontStrikeout eStrikeout )
7868 {
7869 //See qadevOOo/testdocs/StrikeThrough.odt for examples if you need
7870 //to tweak this
7871
7872 OUString aStrikeoutChar = eStrikeout == STRIKEOUT_SLASH ? u"/"_ustr : u"X"_ustr;
7873 OUString aStrikeout = aStrikeoutChar;
7874 while( GetTextWidth( aStrikeout ) < nWidth )
7875 aStrikeout += aStrikeout;
7876
7877 // do not get broader than nWidth modulo 1 character
7878 while( GetTextWidth( aStrikeout ) >= nWidth )
7879 aStrikeout = aStrikeout.copy(1);
7880 aStrikeout += aStrikeoutChar;
7881 bool bShadow = m_aCurrentPDFState.m_aFont.IsShadow();
7882 if ( bShadow )
7883 {
7884 Font aFont = m_aCurrentPDFState.m_aFont;
7885 aFont.SetShadow( false );
7886 setFont( aFont );
7887 updateGraphicsState();
7888 }
7889
7890 // strikeout string is left aligned non-CTL text
7891 vcl::text::ComplexTextLayoutFlags nOrigTLM = GetLayoutMode();
7892 SetLayoutMode(vcl::text::ComplexTextLayoutFlags::BiDiStrong);
7893
7894 push( PushFlags::CLIPREGION );
7895 FontMetric aRefDevFontMetric = GetFontMetric();
7896 tools::Rectangle aRect;
7897 aRect.SetLeft( rPos.X() );
7898 aRect.SetRight( aRect.Left()+nWidth );
7899 aRect.SetBottom( rPos.Y()+aRefDevFontMetric.GetDescent() );
7900 aRect.SetTop( rPos.Y()-aRefDevFontMetric.GetAscent() );
7901
7902 const LogicalFontInstance* pFontInstance = GetFontInstance();
7903 if (pFontInstance->mnOrientation)
7904 {
7905 tools::Polygon aPoly( aRect );
7906 aPoly.Rotate( rPos, pFontInstance->mnOrientation);
7907 aRect = aPoly.GetBoundRect();
7908 }
7909
7910 intersectClipRegion( aRect );
7911 drawText( rPos, aStrikeout, 0, aStrikeout.getLength(), false );
7912 pop();
7913
7914 SetLayoutMode( nOrigTLM );
7915
7916 if ( bShadow )
7917 {
7918 Font aFont = m_aCurrentPDFState.m_aFont;
7919 aFont.SetShadow( true );
7920 setFont( aFont );
7921 updateGraphicsState();
7922 }
7923 }
7924
drawTextLine(const Point & rPos,tools::Long nWidth,FontStrikeout eStrikeout,FontLineStyle eUnderline,FontLineStyle eOverline,bool bUnderlineAbove)7925 void PDFWriterImpl::drawTextLine( const Point& rPos, tools::Long nWidth, FontStrikeout eStrikeout, FontLineStyle eUnderline, FontLineStyle eOverline, bool bUnderlineAbove )
7926 {
7927 if ( !nWidth ||
7928 ( ((eStrikeout == STRIKEOUT_NONE)||(eStrikeout == STRIKEOUT_DONTKNOW)) &&
7929 ((eUnderline == LINESTYLE_NONE)||(eUnderline == LINESTYLE_DONTKNOW)) &&
7930 ((eOverline == LINESTYLE_NONE)||(eOverline == LINESTYLE_DONTKNOW)) ) )
7931 return;
7932
7933 MARK( "drawTextLine" );
7934 updateGraphicsState();
7935
7936 // note: units in pFontInstance are ref device pixel
7937 const LogicalFontInstance* pFontInstance = GetFontInstance();
7938 Color aUnderlineColor = m_aCurrentPDFState.m_aTextLineColor;
7939 Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
7940 Color aStrikeoutColor = m_aCurrentPDFState.m_aFont.GetColor();
7941 bool bStrikeoutDone = false;
7942 bool bUnderlineDone = false;
7943 bool bOverlineDone = false;
7944
7945 if ( (eStrikeout == STRIKEOUT_SLASH) || (eStrikeout == STRIKEOUT_X) )
7946 {
7947 drawStrikeoutChar( rPos, nWidth, eStrikeout );
7948 bStrikeoutDone = true;
7949 }
7950
7951 Point aPos( rPos );
7952 TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
7953 if( eAlign == ALIGN_TOP )
7954 aPos.AdjustY(HCONV( pFontInstance->mxFontMetric->GetAscent() ));
7955 else if( eAlign == ALIGN_BOTTOM )
7956 aPos.AdjustY( -HCONV( pFontInstance->mxFontMetric->GetDescent() ) );
7957
7958 OStringBuffer aLine( 512 );
7959 // save GS
7960 aLine.append( "q " );
7961
7962 // rotate and translate matrix
7963 double fAngle = toRadians(m_aCurrentPDFState.m_aFont.GetOrientation());
7964 Matrix3 aMat;
7965 aMat.rotate( fAngle );
7966 aMat.translate( aPos.X(), aPos.Y() );
7967 m_aPages.back().appendMatrix3(aMat, aLine);
7968 aLine.append( " cm\n" );
7969
7970 if ( aUnderlineColor.IsTransparent() )
7971 aUnderlineColor = aStrikeoutColor;
7972
7973 if ( aOverlineColor.IsTransparent() )
7974 aOverlineColor = aStrikeoutColor;
7975
7976 if ( (eUnderline == LINESTYLE_SMALLWAVE) ||
7977 (eUnderline == LINESTYLE_WAVE) ||
7978 (eUnderline == LINESTYLE_DOUBLEWAVE) ||
7979 (eUnderline == LINESTYLE_BOLDWAVE) )
7980 {
7981 drawWaveTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
7982 bUnderlineDone = true;
7983 }
7984
7985 if ( (eOverline == LINESTYLE_SMALLWAVE) ||
7986 (eOverline == LINESTYLE_WAVE) ||
7987 (eOverline == LINESTYLE_DOUBLEWAVE) ||
7988 (eOverline == LINESTYLE_BOLDWAVE) )
7989 {
7990 drawWaveTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
7991 bOverlineDone = true;
7992 }
7993
7994 if ( !bUnderlineDone )
7995 {
7996 drawStraightTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
7997 }
7998
7999 if ( !bOverlineDone )
8000 {
8001 drawStraightTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
8002 }
8003
8004 if ( !bStrikeoutDone )
8005 {
8006 drawStrikeoutLine( aLine, nWidth, eStrikeout, aStrikeoutColor );
8007 }
8008
8009 aLine.append( "Q\n" );
8010 writeBuffer( aLine );
8011 }
8012
drawPolygon(const tools::Polygon & rPoly)8013 void PDFWriterImpl::drawPolygon( const tools::Polygon& rPoly )
8014 {
8015 MARK( "drawPolygon" );
8016
8017 updateGraphicsState();
8018
8019 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
8020 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
8021 return;
8022
8023 int nPoints = rPoly.GetSize();
8024 OStringBuffer aLine( 20 * nPoints );
8025 m_aPages.back().appendPolygon( rPoly, aLine );
8026 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
8027 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
8028 aLine.append( "B*\n" );
8029 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
8030 aLine.append( "S\n" );
8031 else
8032 aLine.append( "f*\n" );
8033
8034 writeBuffer( aLine );
8035 }
8036
drawPolyPolygon(const tools::PolyPolygon & rPolyPoly)8037 void PDFWriterImpl::drawPolyPolygon( const tools::PolyPolygon& rPolyPoly )
8038 {
8039 MARK( "drawPolyPolygon" );
8040
8041 updateGraphicsState();
8042
8043 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
8044 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
8045 return;
8046
8047 int nPolygons = rPolyPoly.Count();
8048
8049 OStringBuffer aLine( 40 * nPolygons );
8050 m_aPages.back().appendPolyPolygon( rPolyPoly, aLine );
8051 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
8052 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
8053 aLine.append( "B*\n" );
8054 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
8055 aLine.append( "S\n" );
8056 else
8057 aLine.append( "f*\n" );
8058
8059 writeBuffer( aLine );
8060 }
8061
drawTransparent(const tools::PolyPolygon & rPolyPoly,sal_uInt32 nTransparentPercent)8062 void PDFWriterImpl::drawTransparent( const tools::PolyPolygon& rPolyPoly, sal_uInt32 nTransparentPercent )
8063 {
8064 SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
8065 nTransparentPercent = nTransparentPercent % 100;
8066
8067 MARK( "drawTransparent" );
8068
8069 updateGraphicsState();
8070
8071 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
8072 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
8073 return;
8074
8075 if( m_bIsPDF_A1 || m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
8076 {
8077 m_aErrors.insert( m_bIsPDF_A1 ?
8078 PDFWriter::Warning_Transparency_Omitted_PDFA :
8079 PDFWriter::Warning_Transparency_Omitted_PDF13 );
8080
8081 drawPolyPolygon( rPolyPoly );
8082 return;
8083 }
8084
8085 // create XObject
8086 m_aTransparentObjects.emplace_back( );
8087 // FIXME: polygons with beziers may yield incorrect bound rect
8088 m_aTransparentObjects.back().m_aBoundRect = rPolyPoly.GetBoundRect();
8089 // convert rectangle to default user space
8090 m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
8091 m_aTransparentObjects.back().m_nObject = createObject();
8092 m_aTransparentObjects.back().m_nExtGStateObject = createObject();
8093 m_aTransparentObjects.back().m_fAlpha = static_cast<double>(100-nTransparentPercent) / 100.0;
8094 m_aTransparentObjects.back().m_pContentStream.reset(new SvMemoryStream( 256, 256 ));
8095 // create XObject's content stream
8096 OStringBuffer aContent( 256 );
8097 m_aPages.back().appendPolyPolygon( rPolyPoly, aContent );
8098 if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT &&
8099 m_aCurrentPDFState.m_aFillColor != COL_TRANSPARENT )
8100 aContent.append( " B*\n" );
8101 else if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT )
8102 aContent.append( " S\n" );
8103 else
8104 aContent.append( " f*\n" );
8105 m_aTransparentObjects.back().m_pContentStream->WriteBytes(
8106 aContent.getStr(), aContent.getLength() );
8107
8108 OStringBuffer aObjName( 16 );
8109 aObjName.append( "Tr" );
8110 aObjName.append( m_aTransparentObjects.back().m_nObject );
8111 OString aTrName( aObjName.makeStringAndClear() );
8112 aObjName.append( "EGS" );
8113 aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
8114 OString aExtName( aObjName.makeStringAndClear() );
8115
8116 OString aLine =
8117 // insert XObject
8118 "q /" +
8119 aExtName +
8120 " gs /" +
8121 aTrName +
8122 " Do Q\n";
8123 writeBuffer( aLine );
8124
8125 pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject );
8126 pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
8127 }
8128
pushResource(ResourceKind eKind,const OString & rResource,sal_Int32 nObject,ResourceDict & rResourceDict,std::list<StreamRedirect> & rOutputStreams)8129 void PDFWriterImpl::pushResource(ResourceKind eKind, const OString& rResource, sal_Int32 nObject, ResourceDict& rResourceDict, std::list<StreamRedirect>& rOutputStreams)
8130 {
8131 if( nObject < 0 )
8132 return;
8133
8134 switch( eKind )
8135 {
8136 case ResourceKind::XObject:
8137 rResourceDict.m_aXObjects[rResource] = nObject;
8138 if (!rOutputStreams.empty())
8139 rOutputStreams.front().m_aResourceDict.m_aXObjects[rResource] = nObject;
8140 break;
8141 case ResourceKind::ExtGState:
8142 rResourceDict.m_aExtGStates[rResource] = nObject;
8143 if (!rOutputStreams.empty())
8144 rOutputStreams.front().m_aResourceDict.m_aExtGStates[rResource] = nObject;
8145 break;
8146 case ResourceKind::Shading:
8147 rResourceDict.m_aShadings[rResource] = nObject;
8148 if (!rOutputStreams.empty())
8149 rOutputStreams.front().m_aResourceDict.m_aShadings[rResource] = nObject;
8150 break;
8151 case ResourceKind::Pattern:
8152 rResourceDict.m_aPatterns[rResource] = nObject;
8153 if (!rOutputStreams.empty())
8154 rOutputStreams.front().m_aResourceDict.m_aPatterns[rResource] = nObject;
8155 break;
8156 }
8157 }
8158
pushResource(ResourceKind eKind,const OString & rResource,sal_Int32 nObject)8159 void PDFWriterImpl::pushResource( ResourceKind eKind, const OString& rResource, sal_Int32 nObject )
8160 {
8161 pushResource(eKind, rResource, nObject, m_aGlobalResourceDict, m_aOutputStreams);
8162 }
8163
beginRedirect(SvStream * pStream,const tools::Rectangle & rTargetRect)8164 void PDFWriterImpl::beginRedirect( SvStream* pStream, const tools::Rectangle& rTargetRect )
8165 {
8166 push( PushFlags::ALL );
8167
8168 // force reemitting clip region inside the new stream, and
8169 // prevent emitting an unbalanced "Q" at the start
8170 clearClipRegion();
8171 // this is needed to point m_aCurrentPDFState at the pushed state
8172 // ... but it's pointless to actually write into the "outer" stream here!
8173 updateGraphicsState(Mode::NOWRITE);
8174
8175 m_aOutputStreams.push_front( StreamRedirect() );
8176 m_aOutputStreams.front().m_pStream = pStream;
8177 m_aOutputStreams.front().m_aMapMode = m_aMapMode;
8178
8179 if( !rTargetRect.IsEmpty() )
8180 {
8181 m_aOutputStreams.front().m_aTargetRect =
8182 lcl_convert( m_aGraphicsStack.front().m_aMapMode,
8183 m_aMapMode,
8184 this,
8185 rTargetRect );
8186 Point aDelta = m_aOutputStreams.front().m_aTargetRect.BottomLeft();
8187 tools::Long nPageHeight = pointToPixel(m_aPages[m_nCurrentPage].getHeight());
8188 aDelta.setY( -(nPageHeight - m_aOutputStreams.front().m_aTargetRect.Bottom()) );
8189 m_aMapMode.SetOrigin( m_aMapMode.GetOrigin() + aDelta );
8190 }
8191
8192 // setup graphics state for independent object stream
8193
8194 // force reemitting colors
8195 m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
8196 m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
8197 }
8198
endRedirect()8199 SvStream* PDFWriterImpl::endRedirect()
8200 {
8201 SvStream* pStream = nullptr;
8202 if( ! m_aOutputStreams.empty() )
8203 {
8204 pStream = m_aOutputStreams.front().m_pStream;
8205 m_aMapMode = m_aOutputStreams.front().m_aMapMode;
8206 m_aOutputStreams.pop_front();
8207 }
8208
8209 pop();
8210
8211 m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
8212 m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
8213
8214 // needed after pop() to set m_aCurrentPDFState
8215 updateGraphicsState(Mode::NOWRITE);
8216
8217 return pStream;
8218 }
8219
beginTransparencyGroup()8220 void PDFWriterImpl::beginTransparencyGroup()
8221 {
8222 updateGraphicsState();
8223 if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
8224 beginRedirect( new SvMemoryStream( 1024, 1024 ), tools::Rectangle() );
8225 }
8226
endTransparencyGroup(const tools::Rectangle & rBoundingBox,sal_uInt32 nTransparentPercent)8227 void PDFWriterImpl::endTransparencyGroup( const tools::Rectangle& rBoundingBox, sal_uInt32 nTransparentPercent )
8228 {
8229 SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
8230 nTransparentPercent = nTransparentPercent % 100;
8231
8232 if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
8233 return;
8234
8235 // create XObject
8236 m_aTransparentObjects.emplace_back( );
8237 m_aTransparentObjects.back().m_aBoundRect = rBoundingBox;
8238 // convert rectangle to default user space
8239 m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
8240 m_aTransparentObjects.back().m_nObject = createObject();
8241 m_aTransparentObjects.back().m_fAlpha = static_cast<double>(100-nTransparentPercent) / 100.0;
8242 // get XObject's content stream
8243 m_aTransparentObjects.back().m_pContentStream.reset( static_cast<SvMemoryStream*>(endRedirect()) );
8244 m_aTransparentObjects.back().m_nExtGStateObject = createObject();
8245
8246 OStringBuffer aObjName( 16 );
8247 aObjName.append( "Tr" );
8248 aObjName.append( m_aTransparentObjects.back().m_nObject );
8249 OString aTrName( aObjName.makeStringAndClear() );
8250 aObjName.append( "EGS" );
8251 aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
8252 OString aExtName( aObjName.makeStringAndClear() );
8253
8254 OString aLine =
8255 // insert XObject
8256 "q /" +
8257 aExtName +
8258 " gs /" +
8259 aTrName +
8260 " Do Q\n";
8261 writeBuffer( aLine );
8262
8263 pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject );
8264 pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
8265
8266 }
8267
drawRectangle(const tools::Rectangle & rRect)8268 void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect )
8269 {
8270 MARK( "drawRectangle" );
8271
8272 updateGraphicsState();
8273
8274 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
8275 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
8276 return;
8277
8278 OStringBuffer aLine( 40 );
8279 m_aPages.back().appendRect( rRect, aLine );
8280
8281 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
8282 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
8283 aLine.append( " B*\n" );
8284 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
8285 aLine.append( " S\n" );
8286 else
8287 aLine.append( " f*\n" );
8288
8289 writeBuffer( aLine );
8290 }
8291
drawRectangle(const tools::Rectangle & rRect,sal_uInt32 nHorzRound,sal_uInt32 nVertRound)8292 void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound )
8293 {
8294 MARK( "drawRectangle with rounded edges" );
8295
8296 if( !nHorzRound && !nVertRound )
8297 drawRectangle( rRect );
8298
8299 updateGraphicsState();
8300
8301 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
8302 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
8303 return;
8304
8305 if( nHorzRound > static_cast<sal_uInt32>(rRect.GetWidth())/2 )
8306 nHorzRound = rRect.GetWidth()/2;
8307 if( nVertRound > static_cast<sal_uInt32>(rRect.GetHeight())/2 )
8308 nVertRound = rRect.GetHeight()/2;
8309
8310 Point aPoints[16];
8311 const double kappa = 0.5522847498;
8312 const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(nHorzRound))+0.5);
8313 const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(nVertRound))+0.5);
8314
8315 aPoints[1] = Point( rRect.Left() + nHorzRound, rRect.Top() );
8316 aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() );
8317 aPoints[2] = Point( rRect.Right()+1 - nHorzRound, aPoints[1].Y() );
8318 aPoints[3] = Point( aPoints[2].X()+kx, aPoints[2].Y() );
8319
8320 aPoints[5] = Point( rRect.Right()+1, rRect.Top()+nVertRound );
8321 aPoints[4] = Point( aPoints[5].X(), aPoints[5].Y()-ky );
8322 aPoints[6] = Point( aPoints[5].X(), rRect.Bottom()+1 - nVertRound );
8323 aPoints[7] = Point( aPoints[6].X(), aPoints[6].Y()+ky );
8324
8325 aPoints[9] = Point( rRect.Right()+1-nHorzRound, rRect.Bottom()+1 );
8326 aPoints[8] = Point( aPoints[9].X()+kx, aPoints[9].Y() );
8327 aPoints[10] = Point( rRect.Left() + nHorzRound, aPoints[9].Y() );
8328 aPoints[11] = Point( aPoints[10].X()-kx, aPoints[10].Y() );
8329
8330 aPoints[13] = Point( rRect.Left(), rRect.Bottom()+1-nVertRound );
8331 aPoints[12] = Point( aPoints[13].X(), aPoints[13].Y()+ky );
8332 aPoints[14] = Point( rRect.Left(), rRect.Top()+nVertRound );
8333 aPoints[15] = Point( aPoints[14].X(), aPoints[14].Y()-ky );
8334
8335 OStringBuffer aLine( 80 );
8336 m_aPages.back().appendPoint( aPoints[1], aLine );
8337 aLine.append( " m " );
8338 m_aPages.back().appendPoint( aPoints[2], aLine );
8339 aLine.append( " l " );
8340 m_aPages.back().appendPoint( aPoints[3], aLine );
8341 aLine.append( ' ' );
8342 m_aPages.back().appendPoint( aPoints[4], aLine );
8343 aLine.append( ' ' );
8344 m_aPages.back().appendPoint( aPoints[5], aLine );
8345 aLine.append( " c\n" );
8346 m_aPages.back().appendPoint( aPoints[6], aLine );
8347 aLine.append( " l " );
8348 m_aPages.back().appendPoint( aPoints[7], aLine );
8349 aLine.append( ' ' );
8350 m_aPages.back().appendPoint( aPoints[8], aLine );
8351 aLine.append( ' ' );
8352 m_aPages.back().appendPoint( aPoints[9], aLine );
8353 aLine.append( " c\n" );
8354 m_aPages.back().appendPoint( aPoints[10], aLine );
8355 aLine.append( " l " );
8356 m_aPages.back().appendPoint( aPoints[11], aLine );
8357 aLine.append( ' ' );
8358 m_aPages.back().appendPoint( aPoints[12], aLine );
8359 aLine.append( ' ' );
8360 m_aPages.back().appendPoint( aPoints[13], aLine );
8361 aLine.append( " c\n" );
8362 m_aPages.back().appendPoint( aPoints[14], aLine );
8363 aLine.append( " l " );
8364 m_aPages.back().appendPoint( aPoints[15], aLine );
8365 aLine.append( ' ' );
8366 m_aPages.back().appendPoint( aPoints[0], aLine );
8367 aLine.append( ' ' );
8368 m_aPages.back().appendPoint( aPoints[1], aLine );
8369 aLine.append( " c " );
8370
8371 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
8372 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
8373 aLine.append( "b*\n" );
8374 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
8375 aLine.append( "s\n" );
8376 else
8377 aLine.append( "f*\n" );
8378
8379 writeBuffer( aLine );
8380 }
8381
drawEllipse(const tools::Rectangle & rRect)8382 void PDFWriterImpl::drawEllipse( const tools::Rectangle& rRect )
8383 {
8384 MARK( "drawEllipse" );
8385
8386 updateGraphicsState();
8387
8388 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
8389 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
8390 return;
8391
8392 Point aPoints[12];
8393 const double kappa = 0.5522847498;
8394 const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetWidth())/2.0)+0.5);
8395 const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetHeight())/2.0)+0.5);
8396
8397 aPoints[1] = Point( rRect.Left() + rRect.GetWidth()/2, rRect.Top() );
8398 aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() );
8399 aPoints[2] = Point( aPoints[1].X() + kx, aPoints[1].Y() );
8400
8401 aPoints[4] = Point( rRect.Right()+1, rRect.Top() + rRect.GetHeight()/2 );
8402 aPoints[3] = Point( aPoints[4].X(), aPoints[4].Y() - ky );
8403 aPoints[5] = Point( aPoints[4].X(), aPoints[4].Y() + ky );
8404
8405 aPoints[7] = Point( rRect.Left() + rRect.GetWidth()/2, rRect.Bottom()+1 );
8406 aPoints[6] = Point( aPoints[7].X() + kx, aPoints[7].Y() );
8407 aPoints[8] = Point( aPoints[7].X() - kx, aPoints[7].Y() );
8408
8409 aPoints[10] = Point( rRect.Left(), rRect.Top() + rRect.GetHeight()/2 );
8410 aPoints[9] = Point( aPoints[10].X(), aPoints[10].Y() + ky );
8411 aPoints[11] = Point( aPoints[10].X(), aPoints[10].Y() - ky );
8412
8413 OStringBuffer aLine( 80 );
8414 m_aPages.back().appendPoint( aPoints[1], aLine );
8415 aLine.append( " m " );
8416 m_aPages.back().appendPoint( aPoints[2], aLine );
8417 aLine.append( ' ' );
8418 m_aPages.back().appendPoint( aPoints[3], aLine );
8419 aLine.append( ' ' );
8420 m_aPages.back().appendPoint( aPoints[4], aLine );
8421 aLine.append( " c\n" );
8422 m_aPages.back().appendPoint( aPoints[5], aLine );
8423 aLine.append( ' ' );
8424 m_aPages.back().appendPoint( aPoints[6], aLine );
8425 aLine.append( ' ' );
8426 m_aPages.back().appendPoint( aPoints[7], aLine );
8427 aLine.append( " c\n" );
8428 m_aPages.back().appendPoint( aPoints[8], aLine );
8429 aLine.append( ' ' );
8430 m_aPages.back().appendPoint( aPoints[9], aLine );
8431 aLine.append( ' ' );
8432 m_aPages.back().appendPoint( aPoints[10], aLine );
8433 aLine.append( " c\n" );
8434 m_aPages.back().appendPoint( aPoints[11], aLine );
8435 aLine.append( ' ' );
8436 m_aPages.back().appendPoint( aPoints[0], aLine );
8437 aLine.append( ' ' );
8438 m_aPages.back().appendPoint( aPoints[1], aLine );
8439 aLine.append( " c " );
8440
8441 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
8442 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
8443 aLine.append( "b*\n" );
8444 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
8445 aLine.append( "s\n" );
8446 else
8447 aLine.append( "f*\n" );
8448
8449 writeBuffer( aLine );
8450 }
8451
calcAngle(const tools::Rectangle & rRect,const Point & rPoint)8452 static double calcAngle( const tools::Rectangle& rRect, const Point& rPoint )
8453 {
8454 Point aOrigin((rRect.Left()+rRect.Right()+1)/2,
8455 (rRect.Top()+rRect.Bottom()+1)/2);
8456 Point aPoint = rPoint - aOrigin;
8457
8458 double fX = static_cast<double>(aPoint.X());
8459 double fY = static_cast<double>(-aPoint.Y());
8460
8461 if ((rRect.GetHeight() == 0) || (rRect.GetWidth() == 0))
8462 throw o3tl::divide_by_zero();
8463
8464 if( rRect.GetWidth() > rRect.GetHeight() )
8465 fY = fY*(static_cast<double>(rRect.GetWidth())/static_cast<double>(rRect.GetHeight()));
8466 else if( rRect.GetHeight() > rRect.GetWidth() )
8467 fX = fX*(static_cast<double>(rRect.GetHeight())/static_cast<double>(rRect.GetWidth()));
8468 return atan2( fY, fX );
8469 }
8470
drawArc(const tools::Rectangle & rRect,const Point & rStart,const Point & rStop,bool bWithPie,bool bWithChord)8471 void PDFWriterImpl::drawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop, bool bWithPie, bool bWithChord )
8472 {
8473 MARK( "drawArc" );
8474
8475 updateGraphicsState();
8476
8477 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
8478 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
8479 return;
8480
8481 // calculate start and stop angles
8482 const double fStartAngle = calcAngle( rRect, rStart );
8483 double fStopAngle = calcAngle( rRect, rStop );
8484 while( fStopAngle < fStartAngle )
8485 fStopAngle += 2.0*M_PI;
8486 const int nFragments = static_cast<int>((fStopAngle-fStartAngle)/(M_PI/2.0))+1;
8487 const double fFragmentDelta = (fStopAngle-fStartAngle)/static_cast<double>(nFragments);
8488 const double kappa = fabs( 4.0 * (1.0-cos(fFragmentDelta/2.0))/sin(fFragmentDelta/2.0) / 3.0);
8489 const double halfWidth = static_cast<double>(rRect.GetWidth())/2.0;
8490 const double halfHeight = static_cast<double>(rRect.GetHeight())/2.0;
8491
8492 const Point aCenter( (rRect.Left()+rRect.Right()+1)/2,
8493 (rRect.Top()+rRect.Bottom()+1)/2 );
8494
8495 OStringBuffer aLine( 30*nFragments );
8496 Point aPoint( static_cast<int>(halfWidth * cos(fStartAngle) ),
8497 -static_cast<int>(halfHeight * sin(fStartAngle) ) );
8498 aPoint += aCenter;
8499 m_aPages.back().appendPoint( aPoint, aLine );
8500 aLine.append( " m " );
8501 if( !basegfx::fTools::equal(fStartAngle, fStopAngle) )
8502 {
8503 for( int i = 0; i < nFragments; i++ )
8504 {
8505 const double fStartFragment = fStartAngle + static_cast<double>(i)*fFragmentDelta;
8506 const double fStopFragment = fStartFragment + fFragmentDelta;
8507 aPoint = Point( static_cast<int>(halfWidth * (cos(fStartFragment) - kappa*sin(fStartFragment) ) ),
8508 -static_cast<int>(halfHeight * (sin(fStartFragment) + kappa*cos(fStartFragment) ) ) );
8509 aPoint += aCenter;
8510 m_aPages.back().appendPoint( aPoint, aLine );
8511 aLine.append( ' ' );
8512
8513 aPoint = Point( static_cast<int>(halfWidth * (cos(fStopFragment) + kappa*sin(fStopFragment) ) ),
8514 -static_cast<int>(halfHeight * (sin(fStopFragment) - kappa*cos(fStopFragment) ) ) );
8515 aPoint += aCenter;
8516 m_aPages.back().appendPoint( aPoint, aLine );
8517 aLine.append( ' ' );
8518
8519 aPoint = Point( static_cast<int>(halfWidth * cos(fStopFragment) ),
8520 -static_cast<int>(halfHeight * sin(fStopFragment) ) );
8521 aPoint += aCenter;
8522 m_aPages.back().appendPoint( aPoint, aLine );
8523 aLine.append( " c\n" );
8524 }
8525 }
8526 if( bWithChord || bWithPie )
8527 {
8528 if( bWithPie )
8529 {
8530 m_aPages.back().appendPoint( aCenter, aLine );
8531 aLine.append( " l " );
8532 }
8533 aLine.append( "h " );
8534 }
8535 if( ! bWithChord && ! bWithPie )
8536 aLine.append( "S\n" );
8537 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
8538 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
8539 aLine.append( "B*\n" );
8540 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
8541 aLine.append( "S\n" );
8542 else
8543 aLine.append( "f*\n" );
8544
8545 writeBuffer( aLine );
8546 }
8547
drawPolyLine(const tools::Polygon & rPoly)8548 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly )
8549 {
8550 MARK( "drawPolyLine" );
8551
8552 sal_uInt16 nPoints = rPoly.GetSize();
8553 if( nPoints < 2 )
8554 return;
8555
8556 updateGraphicsState();
8557
8558 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
8559 return;
8560
8561 OStringBuffer aLine( 20 * nPoints );
8562 m_aPages.back().appendPolygon( rPoly, aLine, rPoly[0] == rPoly[nPoints-1] );
8563 aLine.append( "S\n" );
8564
8565 writeBuffer( aLine );
8566 }
8567
drawPolyLine(const tools::Polygon & rPoly,const LineInfo & rInfo)8568 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const LineInfo& rInfo )
8569 {
8570 MARK( "drawPolyLine with LineInfo" );
8571
8572 updateGraphicsState();
8573
8574 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
8575 return;
8576
8577 OStringBuffer aLine;
8578 aLine.append( "q " );
8579 if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
8580 {
8581 writeBuffer( aLine );
8582 drawPolyLine( rPoly );
8583 writeBuffer( "Q\n" );
8584 }
8585 else
8586 {
8587 PDFWriter::ExtLineInfo aInfo;
8588 convertLineInfoToExtLineInfo( rInfo, aInfo );
8589 drawPolyLine( rPoly, aInfo );
8590 }
8591 }
8592
convertLineInfoToExtLineInfo(const LineInfo & rIn,PDFWriter::ExtLineInfo & rOut)8593 void PDFWriterImpl::convertLineInfoToExtLineInfo( const LineInfo& rIn, PDFWriter::ExtLineInfo& rOut )
8594 {
8595 SAL_WARN_IF( rIn.GetStyle() != LineStyle::Dash, "vcl.pdfwriter", "invalid conversion" );
8596 rOut.m_fLineWidth = rIn.GetWidth();
8597 rOut.m_fTransparency = 0.0;
8598 rOut.m_eCap = PDFWriter::capButt;
8599 rOut.m_eJoin = PDFWriter::joinMiter;
8600 rOut.m_fMiterLimit = 10;
8601 rOut.m_aDashArray = rIn.GetDotDashArray();
8602
8603 // add LineJoin
8604 switch(rIn.GetLineJoin())
8605 {
8606 case basegfx::B2DLineJoin::Bevel :
8607 {
8608 rOut.m_eJoin = PDFWriter::joinBevel;
8609 break;
8610 }
8611 // Pdf has no 'none' lineJoin, default is miter
8612 case basegfx::B2DLineJoin::NONE :
8613 case basegfx::B2DLineJoin::Miter :
8614 {
8615 rOut.m_eJoin = PDFWriter::joinMiter;
8616 break;
8617 }
8618 case basegfx::B2DLineJoin::Round :
8619 {
8620 rOut.m_eJoin = PDFWriter::joinRound;
8621 break;
8622 }
8623 }
8624
8625 // add LineCap
8626 switch(rIn.GetLineCap())
8627 {
8628 default: /* css::drawing::LineCap_BUTT */
8629 {
8630 rOut.m_eCap = PDFWriter::capButt;
8631 break;
8632 }
8633 case css::drawing::LineCap_ROUND:
8634 {
8635 rOut.m_eCap = PDFWriter::capRound;
8636 break;
8637 }
8638 case css::drawing::LineCap_SQUARE:
8639 {
8640 rOut.m_eCap = PDFWriter::capSquare;
8641 break;
8642 }
8643 }
8644 }
8645
drawPolyLine(const tools::Polygon & rPoly,const PDFWriter::ExtLineInfo & rInfo)8646 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const PDFWriter::ExtLineInfo& rInfo )
8647 {
8648 MARK( "drawPolyLine with ExtLineInfo" );
8649
8650 updateGraphicsState();
8651
8652 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
8653 return;
8654
8655 if( rInfo.m_fTransparency >= 1.0 )
8656 return;
8657
8658 if( rInfo.m_fTransparency != 0.0 )
8659 beginTransparencyGroup();
8660
8661 OStringBuffer aLine;
8662 aLine.append( "q " );
8663 m_aPages.back().appendMappedLength( rInfo.m_fLineWidth, aLine );
8664 aLine.append( " w" );
8665 if( rInfo.m_aDashArray.size() < 10 ) // implementation limit of acrobat reader
8666 {
8667 switch( rInfo.m_eCap )
8668 {
8669 default:
8670 case PDFWriter::capButt: aLine.append( " 0 J" );break;
8671 case PDFWriter::capRound: aLine.append( " 1 J" );break;
8672 case PDFWriter::capSquare: aLine.append( " 2 J" );break;
8673 }
8674 switch( rInfo.m_eJoin )
8675 {
8676 default:
8677 case PDFWriter::joinMiter:
8678 {
8679 double fLimit = rInfo.m_fMiterLimit;
8680 if( rInfo.m_fLineWidth < rInfo.m_fMiterLimit )
8681 fLimit = fLimit / rInfo.m_fLineWidth;
8682 if( fLimit < 1.0 )
8683 fLimit = 1.0;
8684 aLine.append( " 0 j " );
8685 appendDouble( fLimit, aLine );
8686 aLine.append( " M" );
8687 }
8688 break;
8689 case PDFWriter::joinRound: aLine.append( " 1 j" );break;
8690 case PDFWriter::joinBevel: aLine.append( " 2 j" );break;
8691 }
8692 if( !rInfo.m_aDashArray.empty() )
8693 {
8694 aLine.append( " [ " );
8695 for (auto const& dash : rInfo.m_aDashArray)
8696 {
8697 m_aPages.back().appendMappedLength( dash, aLine );
8698 aLine.append( ' ' );
8699 }
8700 aLine.append( "] 0 d" );
8701 }
8702 aLine.append( "\n" );
8703 writeBuffer( aLine );
8704 drawPolyLine( rPoly );
8705 }
8706 else
8707 {
8708 basegfx::B2DPolygon aPoly(rPoly.getB2DPolygon());
8709 basegfx::B2DPolyPolygon aPolyPoly;
8710
8711 basegfx::utils::applyLineDashing(aPoly, rInfo.m_aDashArray, &aPolyPoly);
8712
8713 // Old applyLineDashing subdivided the polygon. New one will create bezier curve segments.
8714 // To mimic old behaviour, apply subdivide here. If beziers shall be written (better quality)
8715 // this line needs to be removed and the loop below adapted accordingly
8716 aPolyPoly = basegfx::utils::adaptiveSubdivideByAngle(aPolyPoly);
8717
8718 const sal_uInt32 nPolygonCount(aPolyPoly.count());
8719
8720 for( sal_uInt32 nPoly = 0; nPoly < nPolygonCount; nPoly++ )
8721 {
8722 aLine.append( (nPoly != 0 && (nPoly & 7) == 0) ? "\n" : " " );
8723 aPoly = aPolyPoly.getB2DPolygon( nPoly );
8724 const sal_uInt32 nPointCount(aPoly.count());
8725
8726 if(nPointCount)
8727 {
8728 const sal_uInt32 nEdgeCount(aPoly.isClosed() ? nPointCount : nPointCount - 1);
8729 basegfx::B2DPoint aCurrent(aPoly.getB2DPoint(0));
8730
8731 for(sal_uInt32 a(0); a < nEdgeCount; a++)
8732 {
8733 if( a > 0 )
8734 aLine.append( " " );
8735 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
8736 const basegfx::B2DPoint aNext(aPoly.getB2DPoint(nNextIndex));
8737
8738 m_aPages.back().appendPoint( Point( basegfx::fround<tools::Long>(aCurrent.getX()),
8739 basegfx::fround<tools::Long>(aCurrent.getY()) ),
8740 aLine );
8741 aLine.append( " m " );
8742 m_aPages.back().appendPoint( Point( basegfx::fround<tools::Long>(aNext.getX()),
8743 basegfx::fround<tools::Long>(aNext.getY()) ),
8744 aLine );
8745 aLine.append( " l" );
8746
8747 // prepare next edge
8748 aCurrent = aNext;
8749 }
8750 }
8751 }
8752 aLine.append( " S " );
8753 writeBuffer( aLine );
8754 }
8755 writeBuffer( "Q\n" );
8756
8757 if( rInfo.m_fTransparency == 0.0 )
8758 return;
8759
8760 // FIXME: actually this may be incorrect with bezier polygons
8761 tools::Rectangle aBoundRect( rPoly.GetBoundRect() );
8762 // avoid clipping with thick lines
8763 if( rInfo.m_fLineWidth > 0.0 )
8764 {
8765 sal_Int32 nLW = sal_Int32(rInfo.m_fLineWidth);
8766 aBoundRect.AdjustTop( -nLW );
8767 aBoundRect.AdjustLeft( -nLW );
8768 aBoundRect.AdjustRight(nLW );
8769 aBoundRect.AdjustBottom(nLW );
8770 }
8771 endTransparencyGroup( aBoundRect, static_cast<sal_uInt16>(100.0*rInfo.m_fTransparency) );
8772 }
8773
drawPixel(const Point & rPoint,const Color & rColor)8774 void PDFWriterImpl::drawPixel( const Point& rPoint, const Color& rColor )
8775 {
8776 MARK( "drawPixel" );
8777
8778 Color aColor = ( rColor == COL_TRANSPARENT ? m_aGraphicsStack.front().m_aLineColor : rColor );
8779
8780 if( aColor == COL_TRANSPARENT )
8781 return;
8782
8783 // pixels are drawn in line color, so have to set
8784 // the nonstroking color to line color
8785 Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
8786 setFillColor( aColor );
8787
8788 updateGraphicsState();
8789
8790 OStringBuffer aLine( 20 );
8791 m_aPages.back().appendPoint( rPoint, aLine );
8792 aLine.append( ' ' );
8793 appendDouble( 1.0/double(GetDPIX()), aLine );
8794 aLine.append( ' ' );
8795 appendDouble( 1.0/double(GetDPIY()), aLine );
8796 aLine.append( " re f\n" );
8797 writeBuffer( aLine );
8798
8799 setFillColor( aOldFillColor );
8800 }
8801
writeTransparentObject(TransparencyEmit & rObject)8802 void PDFWriterImpl::writeTransparentObject( TransparencyEmit& rObject )
8803 {
8804 if (!updateObject(rObject.m_nObject))
8805 return;
8806
8807 bool bFlateFilter = compressStream( rObject.m_pContentStream.get() );
8808 sal_uInt64 nSize = rObject.m_pContentStream->TellEnd();
8809 rObject.m_pContentStream->Seek( STREAM_SEEK_TO_BEGIN );
8810 if (g_bDebugDisableCompression)
8811 {
8812 emitComment( "PDFWriterImpl::writeTransparentObject" );
8813 }
8814 OStringBuffer aLine( 512 );
8815 if (!updateObject(rObject.m_nObject))
8816 return;
8817 aLine.append( rObject.m_nObject );
8818 aLine.append( " 0 obj\n"
8819 "<</Type/XObject\n"
8820 "/Subtype/Form\n"
8821 "/BBox[ " );
8822 appendFixedInt( rObject.m_aBoundRect.Left(), aLine );
8823 aLine.append( ' ' );
8824 appendFixedInt( rObject.m_aBoundRect.Top(), aLine );
8825 aLine.append( ' ' );
8826 appendFixedInt( rObject.m_aBoundRect.Right(), aLine );
8827 aLine.append( ' ' );
8828 appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aLine );
8829 aLine.append( " ]\n" );
8830 if( ! m_bIsPDF_A1 )
8831 {
8832 // 7.8.3 Resource dicts are required for content streams
8833 aLine.append( "/Resources " );
8834 aLine.append( getResourceDictObj() );
8835 aLine.append( " 0 R\n" );
8836
8837 aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/K true>>\n" );
8838 }
8839
8840 aLine.append( "/Length " );
8841 aLine.append( static_cast<sal_Int32>(nSize) );
8842 aLine.append( "\n" );
8843 if( bFlateFilter )
8844 aLine.append( "/Filter/FlateDecode\n" );
8845 aLine.append( ">>\n"
8846 "stream\n" );
8847 if (!writeBuffer(aLine))
8848 return;
8849 checkAndEnableStreamEncryption( rObject.m_nObject );
8850 if (!writeBufferBytes(rObject.m_pContentStream->GetData(), nSize))
8851 return;
8852 disableStreamEncryption();
8853 aLine.setLength( 0 );
8854 aLine.append( "\n"
8855 "endstream\n"
8856 "endobj\n\n" );
8857 if (!writeBuffer(aLine))
8858 return;
8859
8860 // write ExtGState dict for this XObject
8861 aLine.setLength( 0 );
8862 aLine.append( rObject.m_nExtGStateObject );
8863 aLine.append( " 0 obj\n"
8864 "<<" );
8865
8866 if( m_bIsPDF_A1 )
8867 {
8868 aLine.append( "/CA 1.0/ca 1.0" );
8869 m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
8870 }
8871 else
8872 {
8873 aLine.append( "/CA " );
8874 appendDouble( rObject.m_fAlpha, aLine );
8875 aLine.append( "\n"
8876 " /ca " );
8877 appendDouble( rObject.m_fAlpha, aLine );
8878 }
8879 aLine.append( "\n" );
8880
8881 aLine.append( ">>\n"
8882 "endobj\n\n" );
8883 if (!updateObject(rObject.m_nExtGStateObject)) return;
8884 if (!writeBuffer(aLine)) return;
8885 }
8886
writeGradientFunction(GradientEmit const & rObject)8887 bool PDFWriterImpl::writeGradientFunction( GradientEmit const & rObject )
8888 {
8889 // LO internal gradient -> PDF shading type:
8890 // * css::awt::GradientStyle_LINEAR: axial shading, using sampled-function with 2 samples
8891 // [t=0:colorStart, t=1:colorEnd]
8892 // * css::awt::GradientStyle_AXIAL: axial shading, using sampled-function with 3 samples
8893 // [t=0:colorEnd, t=0.5:colorStart, t=1:colorEnd]
8894 // * other styles: function shading with aSize.Width() * aSize.Height() samples
8895 sal_Int32 nFunctionObject = createObject();
8896 if (!updateObject(nFunctionObject))
8897 return false;
8898
8899 ScopedVclPtrInstance< VirtualDevice > aDev;
8900 aDev->SetOutputSizePixel( rObject.m_aSize );
8901 aDev->SetMapMode( MapMode( MapUnit::MapPixel ) );
8902 if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
8903 aDev->SetDrawMode( aDev->GetDrawMode() |
8904 ( DrawModeFlags::GrayLine | DrawModeFlags::GrayFill | DrawModeFlags::GrayText |
8905 DrawModeFlags::GrayBitmap | DrawModeFlags::GrayGradient ) );
8906 aDev->DrawGradient( tools::Rectangle( Point( 0, 0 ), rObject.m_aSize ), rObject.m_aGradient );
8907
8908 Bitmap aSample = aDev->GetBitmap( Point( 0, 0 ), rObject.m_aSize );
8909 BitmapScopedReadAccess pAccess(aSample);
8910
8911 Size aSize = aSample.GetSizePixel();
8912
8913 sal_Int32 nStreamLengthObject = createObject();
8914 if (g_bDebugDisableCompression)
8915 {
8916 emitComment( "PDFWriterImpl::writeGradientFunction" );
8917 }
8918 OStringBuffer aLine( 120 );
8919 aLine.append( nFunctionObject );
8920 aLine.append( " 0 obj\n"
8921 "<</FunctionType 0\n");
8922 switch (rObject.m_aGradient.GetStyle())
8923 {
8924 case css::awt::GradientStyle_LINEAR:
8925 case css::awt::GradientStyle_AXIAL:
8926 aLine.append("/Domain[ 0 1]\n");
8927 break;
8928 default:
8929 aLine.append("/Domain[ 0 1 0 1]\n");
8930 }
8931 aLine.append("/Size[ " );
8932 switch (rObject.m_aGradient.GetStyle())
8933 {
8934 case css::awt::GradientStyle_LINEAR:
8935 aLine.append('2');
8936 break;
8937 case css::awt::GradientStyle_AXIAL:
8938 aLine.append('3');
8939 break;
8940 default:
8941 aLine.append( static_cast<sal_Int32>(aSize.Width()) );
8942 aLine.append( ' ' );
8943 aLine.append( static_cast<sal_Int32>(aSize.Height()) );
8944 }
8945 aLine.append( " ]\n"
8946 "/BitsPerSample 8\n"
8947 "/Range[ 0 1 0 1 0 1 ]\n"
8948 "/Order 3\n"
8949 "/Length " );
8950 aLine.append( nStreamLengthObject );
8951 if (!g_bDebugDisableCompression)
8952 aLine.append( " 0 R\n"
8953 "/Filter/FlateDecode"
8954 ">>\n"
8955 "stream\n" );
8956 else
8957 aLine.append( " 0 R\n"
8958 ">>\n"
8959 "stream\n" );
8960 if (!writeBuffer(aLine))
8961 return false;
8962
8963 sal_uInt64 nStartStreamPos = 0;
8964 if (osl::File::E_None != m_aFile.getPos(nStartStreamPos))
8965 return false;
8966
8967 checkAndEnableStreamEncryption( nFunctionObject );
8968 beginCompression();
8969 sal_uInt8 aCol[3];
8970 switch (rObject.m_aGradient.GetStyle())
8971 {
8972 case css::awt::GradientStyle_AXIAL:
8973 aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
8974 aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
8975 aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
8976 if (!writeBufferBytes(aCol, 3))
8977 return false;
8978 [[fallthrough]];
8979 case css::awt::GradientStyle_LINEAR:
8980 {
8981 aCol[0] = rObject.m_aGradient.GetStartColor().GetRed();
8982 aCol[1] = rObject.m_aGradient.GetStartColor().GetGreen();
8983 aCol[2] = rObject.m_aGradient.GetStartColor().GetBlue();
8984 if (!writeBufferBytes(aCol, 3))
8985 return false;
8986
8987 aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
8988 aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
8989 aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
8990 if (!writeBufferBytes(aCol, 3))
8991 return false;
8992 break;
8993 }
8994 default:
8995 for( int y = aSize.Height()-1; y >= 0; y-- )
8996 {
8997 for( tools::Long x = 0; x < aSize.Width(); x++ )
8998 {
8999 BitmapColor aColor = pAccess->GetColor( y, x );
9000 aCol[0] = aColor.GetRed();
9001 aCol[1] = aColor.GetGreen();
9002 aCol[2] = aColor.GetBlue();
9003 if (!writeBufferBytes(aCol, 3))
9004 return false;
9005 }
9006 }
9007 }
9008 endCompression();
9009 disableStreamEncryption();
9010
9011 sal_uInt64 nEndStreamPos = 0;
9012 if (osl::File::E_None != m_aFile.getPos(nEndStreamPos))
9013 return false;
9014
9015 aLine.setLength( 0 );
9016 aLine.append( "\nendstream\nendobj\n\n" );
9017 if (!writeBuffer(aLine)) return false;
9018
9019 // write stream length
9020 if (!updateObject(nStreamLengthObject)) return false;
9021 aLine.setLength( 0 );
9022 aLine.append( nStreamLengthObject );
9023 aLine.append( " 0 obj\n" );
9024 aLine.append( static_cast<sal_Int64>(nEndStreamPos-nStartStreamPos) );
9025 aLine.append( "\nendobj\n\n" );
9026 if (!writeBuffer(aLine)) return false;
9027
9028 if (!updateObject(rObject.m_nObject)) return false;
9029 aLine.setLength( 0 );
9030 aLine.append( rObject.m_nObject );
9031 aLine.append( " 0 obj\n");
9032 switch (rObject.m_aGradient.GetStyle())
9033 {
9034 case css::awt::GradientStyle_LINEAR:
9035 case css::awt::GradientStyle_AXIAL:
9036 aLine.append("<</ShadingType 2\n");
9037 break;
9038 default:
9039 aLine.append("<</ShadingType 1\n");
9040 }
9041 aLine.append("/ColorSpace/DeviceRGB\n"
9042 "/AntiAlias true\n");
9043
9044 // Determination of shading axis
9045 // See: OutputDevice::ImplDrawLinearGradient for reference
9046 tools::Rectangle aRect;
9047 aRect.SetLeft(0);
9048 aRect.SetTop(0);
9049 aRect.SetRight( aSize.Width() );
9050 aRect.SetBottom( aSize.Height() );
9051
9052 tools::Rectangle aBoundRect;
9053 Point aCenter;
9054 Degree10 nAngle = rObject.m_aGradient.GetAngle() % 3600_deg10;
9055 rObject.m_aGradient.GetBoundRect( aRect, aBoundRect, aCenter );
9056
9057 const bool bLinear = (rObject.m_aGradient.GetStyle() == css::awt::GradientStyle_LINEAR);
9058 double fBorder = aBoundRect.GetHeight() * rObject.m_aGradient.GetBorder() / 100.0;
9059 if ( !bLinear )
9060 {
9061 fBorder /= 2.0;
9062 }
9063
9064 aBoundRect.AdjustBottom( -fBorder );
9065 if (!bLinear)
9066 {
9067 aBoundRect.AdjustTop(fBorder );
9068 }
9069
9070 switch (rObject.m_aGradient.GetStyle())
9071 {
9072 case css::awt::GradientStyle_LINEAR:
9073 case css::awt::GradientStyle_AXIAL:
9074 {
9075 aLine.append("/Domain[ 0 1 ]\n"
9076 "/Coords[ " );
9077 tools::Polygon aPoly( 2 );
9078 aPoly[0] = aBoundRect.BottomCenter();
9079 aPoly[1] = aBoundRect.TopCenter();
9080 aPoly.Rotate( aCenter, 3600_deg10 - nAngle );
9081
9082 aLine.append( static_cast<sal_Int32>(aPoly[0].X()) );
9083 aLine.append( " " );
9084 aLine.append( static_cast<sal_Int32>(aPoly[0].Y()) );
9085 aLine.append( " " );
9086 aLine.append( static_cast<sal_Int32>(aPoly[1].X()));
9087 aLine.append( " ");
9088 aLine.append( static_cast<sal_Int32>(aPoly[1].Y()));
9089 aLine.append( " ]\n");
9090 aLine.append("/Extend [true true]\n");
9091 break;
9092 }
9093 default:
9094 aLine.append("/Domain[ 0 1 0 1 ]\n"
9095 "/Matrix[ " );
9096 aLine.append( static_cast<sal_Int32>(aSize.Width()) );
9097 aLine.append( " 0 0 " );
9098 aLine.append( static_cast<sal_Int32>(aSize.Height()) );
9099 aLine.append( " 0 0 ]\n");
9100 }
9101 aLine.append("/Function " );
9102 aLine.append( nFunctionObject );
9103 aLine.append( " 0 R\n"
9104 ">>\n"
9105 "endobj\n\n" );
9106 return writeBuffer( aLine );
9107 }
9108
writeJPG(const JPGEmit & rObject)9109 void PDFWriterImpl::writeJPG( const JPGEmit& rObject )
9110 {
9111 if (rObject.m_aReferenceXObject.hasExternalPDFData() && !m_aContext.UseReferenceXObject)
9112 {
9113 writeReferenceXObject(rObject.m_aReferenceXObject);
9114 return;
9115 }
9116
9117 if (!rObject.m_pStream) return;
9118 if (!updateObject(rObject.m_nObject)) return;
9119
9120 sal_uInt64 nLength = rObject.m_pStream->TellEnd();
9121 rObject.m_pStream->Seek( STREAM_SEEK_TO_BEGIN );
9122
9123 sal_Int32 nMaskObject = 0;
9124 if( !rObject.m_aAlphaMask.IsEmpty() )
9125 {
9126 if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4
9127 && !m_bIsPDF_A1)
9128 {
9129 nMaskObject = createObject();
9130 }
9131 else if( m_bIsPDF_A1 )
9132 m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
9133 else if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
9134 m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDF13 );
9135
9136 }
9137 if (g_bDebugDisableCompression)
9138 {
9139 emitComment( "PDFWriterImpl::writeJPG" );
9140 }
9141
9142 OStringBuffer aLine(200);
9143 aLine.append( rObject.m_nObject );
9144 aLine.append( " 0 obj\n"
9145 "<</Type/XObject/Subtype/Image/Width " );
9146 aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Width()) );
9147 aLine.append( " /Height " );
9148 aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Height()) );
9149 aLine.append( " /BitsPerComponent 8 " );
9150 if( rObject.m_bTrueColor )
9151 aLine.append( "/ColorSpace/DeviceRGB" );
9152 else
9153 aLine.append( "/ColorSpace/DeviceGray" );
9154 aLine.append( "/Filter/DCTDecode/Length " );
9155 aLine.append( static_cast<sal_Int64>(nLength) );
9156 if( nMaskObject )
9157 {
9158 aLine.append(" /SMask ");
9159 aLine.append( nMaskObject );
9160 aLine.append( " 0 R " );
9161 }
9162 aLine.append( ">>\nstream\n" );
9163 if (!writeBuffer(aLine))
9164 return;
9165
9166 checkAndEnableStreamEncryption( rObject.m_nObject );
9167 if (!writeBufferBytes(rObject.m_pStream->GetData(), nLength))
9168 return;
9169 disableStreamEncryption();
9170
9171 aLine.setLength( 0 );
9172 if (!writeBuffer("\nendstream\nendobj\n\n"))
9173 return;
9174
9175 if( nMaskObject )
9176 writeBitmapMaskObject( nMaskObject, rObject.m_aAlphaMask );
9177
9178 writeReferenceXObject(rObject.m_aReferenceXObject);
9179 }
9180
writeReferenceXObject(const ReferenceXObjectEmit & rEmit)9181 void PDFWriterImpl::writeReferenceXObject(const ReferenceXObjectEmit& rEmit)
9182 {
9183 if (rEmit.m_nFormObject <= 0)
9184 return;
9185
9186 // Count /Matrix and /BBox.
9187 // vcl::ImportPDF() uses getDefaultPdfResolutionDpi to set the desired
9188 // rendering DPI so we have to take into account that here too.
9189 static const double fResolutionDPI = vcl::pdf::getDefaultPdfResolutionDpi();
9190 static const double fMagicScaleFactor = PDF_INSERT_MAGIC_SCALE_FACTOR;
9191
9192 sal_Int32 nOldDPIX = GetDPIX();
9193 sal_Int32 nOldDPIY = GetDPIY();
9194 SetDPIX(fResolutionDPI);
9195 SetDPIY(fResolutionDPI);
9196 Size aSize = PixelToLogic(rEmit.m_aPixelSize, MapMode(m_aMapMode.GetMapUnit()));
9197 SetDPIX(nOldDPIX);
9198 SetDPIY(nOldDPIY);
9199 double fScaleX = 1.0 / aSize.Width();
9200 double fScaleY = 1.0 / aSize.Height();
9201
9202 sal_Int32 nWrappedFormObject = 0;
9203 if (!m_aContext.UseReferenceXObject)
9204 {
9205 // tdf#156842 increase scale for external PDF data
9206 // Multiply PDF_INSERT_MAGIC_SCALE_FACTOR for platforms like macOS
9207 // that scale all images by this number.
9208 // This fix also allows the CppunitTest_vcl_pdfexport to run
9209 // successfully on macOS.
9210 fScaleX = fMagicScaleFactor / aSize.Width();
9211 fScaleY = fMagicScaleFactor / aSize.Height();
9212
9213 // Parse the PDF data, we need that to write the PDF dictionary of our
9214 // object.
9215 if (rEmit.m_nExternalPDFDataIndex < 0)
9216 return;
9217 auto& rExternalPDFStream = m_aExternalPDFStreams.get(rEmit.m_nExternalPDFDataIndex);
9218 auto& pPDFDocument = rExternalPDFStream.getPDFDocument();
9219 if (!pPDFDocument)
9220 {
9221 // Couldn't parse the document and can't continue
9222 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: failed to parse the document");
9223 return;
9224 }
9225
9226 std::vector<filter::PDFObjectElement*> aPages = pPDFDocument->GetPages();
9227 if (aPages.empty())
9228 {
9229 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no pages");
9230 return;
9231 }
9232
9233 size_t nPageIndex = rEmit.m_nExternalPDFPageIndex >= 0 ? rEmit.m_nExternalPDFPageIndex : 0;
9234
9235 filter::PDFObjectElement* pPage = aPages[nPageIndex];
9236 if (!pPage)
9237 {
9238 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no page");
9239 return;
9240 }
9241
9242 // Get the copied resource map, so we can use that to skip objects we already copied
9243 auto& rCopiedResourcesMap = rExternalPDFStream.getCopiedResources();
9244
9245 // Add page mapping to the copied resources map.
9246 // Needed if we reference the current page and we want to prevent copying the page
9247 // if it is referenced.
9248 rCopiedResourcesMap.emplace(pPage->GetObjectValue(), m_aPages.back().m_nPageObject);
9249
9250 double aOrigin[2] = { 0.0, 0.0 };
9251
9252 // tdf#160714 use crop box for bounds of embedded PDF object
9253 // If there is no crop box, fallback to the media box just to be safe.
9254 auto* pBoundsArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("CropBox"_ostr));
9255 if (!pBoundsArray)
9256 pBoundsArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("MediaBox"_ostr));
9257 if (pBoundsArray)
9258 {
9259 const auto& rElements = pBoundsArray->GetElements();
9260 if (rElements.size() >= 4)
9261 {
9262 // get x1, y1 of the rectangle.
9263 for (sal_Int32 nIdx = 0; nIdx < 2; ++nIdx)
9264 {
9265 if (const auto* pNumElement = dynamic_cast<filter::PDFNumberElement*>(rElements[nIdx]))
9266 aOrigin[nIdx] = pNumElement->GetValue();
9267 }
9268 }
9269 }
9270
9271 std::vector<filter::PDFObjectElement*> aContentStreams;
9272 if (filter::PDFObjectElement* pContentStream = pPage->LookupObject("Contents"_ostr))
9273 aContentStreams.push_back(pContentStream);
9274 else if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Contents"_ostr)))
9275 {
9276 for (const auto pElement : pArray->GetElements())
9277 {
9278 auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
9279 if (!pReference)
9280 continue;
9281
9282 filter::PDFObjectElement* pObject = pReference->LookupObject();
9283 if (!pObject)
9284 continue;
9285
9286 aContentStreams.push_back(pObject);
9287 }
9288 }
9289
9290 if (aContentStreams.empty())
9291 {
9292 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no content stream");
9293 return;
9294 }
9295
9296 // Merge link and widget annotations from pPage to our page.
9297 mergeAnnotationsFromExternalPage(pPage, rCopiedResourcesMap);
9298
9299 nWrappedFormObject = createObject();
9300 // Write the form XObject wrapped below. This is a separate object from
9301 // the wrapper, this way there is no need to alter the stream contents.
9302
9303 OStringBuffer aLine;
9304 aLine.append(nWrappedFormObject);
9305 aLine.append(" 0 obj\n");
9306 aLine.append("<< /Type /XObject");
9307 aLine.append(" /Subtype /Form");
9308
9309 tools::Long nWidth = aSize.Width();
9310 tools::Long nHeight = aSize.Height();
9311 basegfx::B2DRange aBBox(0, 0, aSize.Width(), aSize.Height());
9312 if (auto pRotate = dynamic_cast<filter::PDFNumberElement*>(pPage->Lookup("Rotate"_ostr)))
9313 {
9314 // The original page was rotated, then construct a transformation matrix which does the
9315 // same with our form object.
9316 sal_Int32 nRotAngle = static_cast<sal_Int32>(pRotate->GetValue()) % 360;
9317 // /Rotate is clockwise, matrix rotate is counter-clockwise.
9318 sal_Int32 nAngle = -1 * nRotAngle;
9319
9320 // The bounding box just rotates.
9321 basegfx::B2DHomMatrix aBBoxMat;
9322 aBBoxMat.rotate(basegfx::deg2rad(pRotate->GetValue()));
9323 aBBox.transform(aBBoxMat);
9324
9325 // Now transform the object: rotate around the center and make sure that the rotation
9326 // doesn't affect the aspect ratio.
9327 basegfx::B2DHomMatrix aMat;
9328 aMat.translate((-0.5 * aBBox.getWidth() / fMagicScaleFactor) - aOrigin[0], (-0.5 * aBBox.getHeight() / fMagicScaleFactor) - aOrigin[1]);
9329 aMat.rotate(basegfx::deg2rad(nAngle));
9330 aMat.translate(0.5 * nWidth / fMagicScaleFactor, 0.5 * nHeight / fMagicScaleFactor);
9331
9332 aLine.append(" /Matrix [ ");
9333 aLine.append(aMat.a());
9334 aLine.append(" ");
9335 aLine.append(aMat.b());
9336 aLine.append(" ");
9337 aLine.append(aMat.c());
9338 aLine.append(" ");
9339 aLine.append(aMat.d());
9340 aLine.append(" ");
9341 aLine.append(aMat.e());
9342 aLine.append(" ");
9343 aLine.append(aMat.f());
9344 aLine.append(" ] ");
9345 }
9346
9347 PDFObjectCopier aCopier(*this);
9348 aCopier.copyPageResources(pPage, aLine, rCopiedResourcesMap);
9349
9350 aLine.append(" /BBox [ ");
9351 aLine.append(aOrigin[0]);
9352 aLine.append(' ');
9353 aLine.append(aOrigin[1]);
9354 aLine.append(' ');
9355 aLine.append(aBBox.getWidth() + aOrigin[0]);
9356 aLine.append(' ');
9357 aLine.append(aBBox.getHeight() + aOrigin[1]);
9358 aLine.append(" ]");
9359
9360 if (!g_bDebugDisableCompression)
9361 aLine.append(" /Filter/FlateDecode");
9362 aLine.append(" /Length ");
9363
9364 SvMemoryStream aStream;
9365 bool bCompressed = false;
9366 bool bIsTaggedNonReferenceXObject = m_aContext.Tagged && !m_aContext.UseReferenceXObject;
9367 sal_Int32 nLength = PDFObjectCopier::copyPageStreams(aContentStreams, aStream, bCompressed,
9368 bIsTaggedNonReferenceXObject);
9369 aLine.append(nLength);
9370
9371 aLine.append(">>\nstream\n");
9372 if (g_bDebugDisableCompression)
9373 {
9374 emitComment("PDFWriterImpl::writeReferenceXObject, WrappedFormObject");
9375 }
9376 if (!updateObject(nWrappedFormObject))
9377 return;
9378 if (!writeBuffer(aLine))
9379 return;
9380 aLine.setLength(0);
9381
9382 checkAndEnableStreamEncryption(nWrappedFormObject);
9383 // Copy the original page streams to the form XObject stream.
9384 aLine.append(static_cast<const char*>(aStream.GetData()), aStream.GetSize());
9385 if (!writeBuffer(aLine))
9386 return;
9387 aLine.setLength(0);
9388 disableStreamEncryption();
9389
9390 aLine.append("\nendstream\nendobj\n\n");
9391 if (!writeBuffer(aLine))
9392 return;
9393 }
9394
9395 OStringBuffer aLine;
9396 if (g_bDebugDisableCompression)
9397 {
9398 emitComment("PDFWriterImpl::writeReferenceXObject, FormObject");
9399 }
9400 if (!updateObject(rEmit.m_nFormObject))
9401 return;
9402
9403 // Now have all the info to write the form XObject.
9404 aLine.append(rEmit.m_nFormObject);
9405 aLine.append(" 0 obj\n");
9406 aLine.append("<< /Type /XObject");
9407 aLine.append(" /Subtype /Form");
9408 aLine.append(" /Resources << /XObject<<");
9409
9410 sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject;
9411 aLine.append(" /Im");
9412 aLine.append(nObject);
9413 aLine.append(" ");
9414 aLine.append(nObject);
9415 aLine.append(" 0 R");
9416
9417 aLine.append(">> >>");
9418 aLine.append(" /Matrix [ ");
9419 appendDouble(fScaleX, aLine);
9420 aLine.append(" 0 0 ");
9421 appendDouble(fScaleY, aLine);
9422 aLine.append(" 0 0 ]");
9423 aLine.append(" /BBox [ 0 0 ");
9424 // tdf#157680 reduce size by magic scale factor in /BBox
9425 aLine.append(aSize.Width() / fMagicScaleFactor);
9426 aLine.append(" ");
9427 // tdf#157680 reduce size by magic scale factor in /BBox
9428 aLine.append(aSize.Height() / fMagicScaleFactor);
9429 aLine.append(" ]\n");
9430
9431 if (m_aContext.UseReferenceXObject && rEmit.m_nEmbeddedObject > 0)
9432 {
9433 // Write the reference dictionary.
9434 aLine.append("/Ref<< /F << /Type /Filespec /F (<embedded file>) ");
9435 if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
9436 { // ISO 14289-1:2014, Clause: 7.11
9437 aLine.append("/UF (<embedded file>) ");
9438 }
9439 aLine.append("/EF << /F ");
9440 aLine.append(rEmit.m_nEmbeddedObject);
9441 aLine.append(" 0 R >> >> /Page 0 >>\n");
9442 }
9443
9444 aLine.append("/Length ");
9445
9446 OStringBuffer aStream;
9447 aStream.append("q ");
9448 if (m_aContext.UseReferenceXObject)
9449 {
9450 // Reference XObject markup is used, just refer to the fallback bitmap
9451 // here.
9452 aStream.append(aSize.Width());
9453 aStream.append(" 0 0 ");
9454 aStream.append(aSize.Height());
9455 aStream.append(" 0 0 cm\n");
9456 aStream.append("/Im");
9457 aStream.append(rEmit.m_nBitmapObject);
9458 aStream.append(" Do\n");
9459 }
9460 else
9461 {
9462 // Reset line width to the default.
9463 aStream.append(" 1 w\n");
9464
9465 // vcl::RenderPDFBitmaps() effectively renders a white background for transparent input, be
9466 // consistent with that.
9467 aStream.append("1 1 1 rg\n");
9468 aStream.append("0 0 ");
9469 aStream.append(aSize.Width());
9470 aStream.append(" ");
9471 aStream.append(aSize.Height());
9472 aStream.append(" re\n");
9473 aStream.append("f*\n");
9474
9475 // Reset non-stroking color in case the XObject uses the default
9476 aStream.append("0 0 0 rg\n");
9477 // No reference XObject, draw the form XObject containing the original
9478 // page streams.
9479 aStream.append("/Im");
9480 aStream.append(nWrappedFormObject);
9481 aStream.append(" Do\n");
9482 }
9483 aStream.append("Q");
9484 aLine.append(aStream.getLength());
9485
9486 aLine.append(">>\nstream\n");
9487 if (!writeBuffer(aLine))
9488 return;
9489 aLine.setLength(0);
9490
9491 checkAndEnableStreamEncryption(rEmit.m_nFormObject);
9492 aLine.append(aStream.getStr());
9493 if (!writeBuffer(aLine))
9494 return;
9495 aLine.setLength(0);
9496 disableStreamEncryption();
9497
9498 aLine.append("\nendstream\nendobj\n\n");
9499 if (!writeBuffer(aLine))
9500 return;
9501 }
9502
9503 namespace
9504 {
9505
getRootParent(filter::PDFObjectElement * pObject)9506 sal_Int32 getRootParent(filter::PDFObjectElement* pObject)
9507 {
9508 auto* pReference = dynamic_cast<filter::PDFReferenceElement*>(pObject->Lookup("Parent"_ostr));
9509 if (!pReference)
9510 return pObject->GetObjectValue();
9511
9512 auto* pParent = pReference->LookupObject();
9513 return getRootParent(pParent);
9514 }
9515
9516 } // end anonymous
9517
mergeAnnotationsFromExternalPage(filter::PDFObjectElement * pPage,std::map<sal_Int32,sal_Int32> & rCopiedResourcesMap)9518 void PDFWriterImpl::mergeAnnotationsFromExternalPage(filter::PDFObjectElement* pPage, std::map<sal_Int32, sal_Int32>& rCopiedResourcesMap)
9519 {
9520 auto* pResult = pPage->Lookup("Annots"_ostr);
9521 filter::PDFArrayElement* pArray = nullptr;
9522 // If the Annots array is a reference - get the array from the referenced object
9523 auto pAnnotsReference = dynamic_cast<filter::PDFReferenceElement*>(pResult);
9524 if (pAnnotsReference)
9525 {
9526 filter::PDFObjectElement* pObject = pAnnotsReference->LookupObject();
9527 pArray = pObject->GetArray();
9528 }
9529 else
9530 {
9531 // Not a reference so is it an array
9532 pArray = dynamic_cast<filter::PDFArrayElement*>(pResult);
9533 }
9534
9535 // Have we found our /Annots array?
9536 if (!pArray)
9537 return;
9538
9539 std::unordered_set<sal_Int32> aAlreadyCopied;
9540 PDFObjectCopier aCopier(*this);
9541 SvMemoryStream& rDocBuffer = pPage->GetDocument().GetEditBuffer();
9542
9543 for (const auto pElement : pArray->GetElements())
9544 {
9545 auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
9546 if (!pReference)
9547 continue;
9548
9549 filter::PDFObjectElement* pObject = pReference->LookupObject();
9550 if (!pObject)
9551 continue;
9552
9553 // Get the /Type and the /Subtype
9554 auto pType = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
9555 auto pSubtype = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Subtype"_ostr));
9556
9557 // Is it a /Annot we want to copy?
9558 if (pType && pType->GetValue() == "Annot" && pSubtype)
9559 {
9560 bool bIsLink = pSubtype->GetValue() == "Link";
9561 bool bIsWidget = pSubtype->GetValue() == "Widget";
9562
9563 // is link or widget
9564 if (!bIsLink && !bIsWidget)
9565 continue;
9566
9567 // Copy over the annotation and refer to its new id.
9568 sal_Int32 nNewId = aCopier.copyExternalResource(rDocBuffer, *pObject, rCopiedResourcesMap);
9569 m_aPages.back().m_aAnnotations.push_back(nNewId);
9570
9571 if (!bIsWidget)
9572 continue;
9573
9574 // Find the root
9575 sal_Int32 nRootID = getRootParent(pObject);
9576
9577 auto aIterator = rCopiedResourcesMap.find(nRootID);
9578 if (aIterator == rCopiedResourcesMap.end()) // Can't find the mapped ID ?
9579 continue;
9580
9581 nNewId = aIterator->second;
9582
9583 // Ignore if we added the ID already
9584 if (aAlreadyCopied.find(nNewId) == aAlreadyCopied.end())
9585 {
9586 // Add new entry into copied widgets vector
9587 auto& rCopiedWidget = m_aCopiedWidgets.emplace_back();
9588 rCopiedWidget.m_nObject = nNewId;
9589 aAlreadyCopied.emplace(nNewId);
9590 }
9591 }
9592 }
9593
9594 }
9595
writeBitmapObject(const BitmapEmit & rObject)9596 bool PDFWriterImpl::writeBitmapObject( const BitmapEmit& rObject )
9597 {
9598 if (rObject.m_aReferenceXObject.hasExternalPDFData() && !m_aContext.UseReferenceXObject)
9599 {
9600 writeReferenceXObject(rObject.m_aReferenceXObject);
9601 return true;
9602 }
9603
9604 if (!updateObject(rObject.m_nObject))
9605 return false;
9606
9607 bool bWriteMask = false;
9608 if( rObject.m_aBitmap.HasAlpha() )
9609 {
9610 if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
9611 bWriteMask = true;
9612 // else draw without alpha channel
9613 }
9614
9615 BitmapScopedReadAccess pAccess(rObject.m_aBitmap);
9616
9617 bool bTrueColor = true;
9618 sal_Int32 nBitsPerComponent = 0;
9619 auto const ePixelFormat = rObject.m_aBitmap.getPixelFormat();
9620 switch (ePixelFormat)
9621 {
9622 case vcl::PixelFormat::N8_BPP:
9623 bTrueColor = false;
9624 nBitsPerComponent = vcl::pixelFormatBitCount(ePixelFormat);
9625 break;
9626 case vcl::PixelFormat::N24_BPP:
9627 case vcl::PixelFormat::N32_BPP:
9628 bTrueColor = true;
9629 nBitsPerComponent = 8;
9630 break;
9631 case vcl::PixelFormat::INVALID:
9632 return false;
9633 }
9634
9635 sal_Int32 nStreamLengthObject = createObject();
9636 sal_Int32 nMaskObject = 0;
9637
9638 if (g_bDebugDisableCompression)
9639 {
9640 emitComment( "PDFWriterImpl::writeBitmapObject" );
9641 }
9642 OStringBuffer aLine(1024);
9643 aLine.append( rObject.m_nObject );
9644 aLine.append( " 0 obj\n"
9645 "<</Type/XObject/Subtype/Image/Width " );
9646 aLine.append( static_cast<sal_Int32>(rObject.m_aBitmap.GetSizePixel().Width()) );
9647 aLine.append( "/Height " );
9648 aLine.append( static_cast<sal_Int32>(rObject.m_aBitmap.GetSizePixel().Height()) );
9649 aLine.append( "/BitsPerComponent " );
9650 aLine.append( nBitsPerComponent );
9651 aLine.append( "/Length " );
9652 aLine.append( nStreamLengthObject );
9653 aLine.append( " 0 R\n" );
9654 if (!g_bDebugDisableCompression)
9655 {
9656 if( nBitsPerComponent != 1 )
9657 {
9658 aLine.append( "/Filter/FlateDecode" );
9659 }
9660 else
9661 {
9662 aLine.append( "/Filter/CCITTFaxDecode/DecodeParms<</K -1/BlackIs1 true/Columns " );
9663 aLine.append( static_cast<sal_Int32>(rObject.m_aBitmap.GetSizePixel().Width()) );
9664 aLine.append( ">>\n" );
9665 }
9666 }
9667 aLine.append( "/ColorSpace" );
9668 if( bTrueColor )
9669 aLine.append( "/DeviceRGB\n" );
9670 else
9671 {
9672 aLine.append( "[ /Indexed/DeviceRGB " );
9673 aLine.append( static_cast<sal_Int32>(pAccess->GetPaletteEntryCount()-1) );
9674 aLine.append( "\n<" );
9675 if (m_aContext.Encryption.canEncrypt())
9676 {
9677 enableStringEncryption(rObject.m_nObject);
9678 //check encryption buffer size
9679 m_vEncryptionBuffer.resize(pAccess->GetPaletteEntryCount()*3);
9680 int nChar = 0;
9681 //fill the encryption buffer
9682 for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
9683 {
9684 const BitmapColor& rColor = pAccess->GetPaletteColor( i );
9685 m_vEncryptionBuffer[nChar++] = rColor.GetRed();
9686 m_vEncryptionBuffer[nChar++] = rColor.GetGreen();
9687 m_vEncryptionBuffer[nChar++] = rColor.GetBlue();
9688 }
9689 //encrypt the colorspace lookup table
9690 std::vector<sal_uInt8> aOutputBuffer(nChar);
9691 m_pPDFEncryptor->encrypt(m_vEncryptionBuffer.data(), nChar, aOutputBuffer, nChar);
9692 //now queue the data for output
9693 COSWriter::appendHexArray(aOutputBuffer.data(), aOutputBuffer.size(), aLine);
9694 }
9695 else //no encryption requested (PDF/A-1a program flow drops here)
9696 {
9697 for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
9698 {
9699 const BitmapColor& rColor = pAccess->GetPaletteColor( i );
9700 COSWriter::appendHex( rColor.GetRed(), aLine );
9701 COSWriter::appendHex( rColor.GetGreen(), aLine );
9702 COSWriter::appendHex( rColor.GetBlue(), aLine );
9703 }
9704 }
9705 aLine.append( ">\n]\n" );
9706 }
9707
9708 if (!m_bIsPDF_A1)
9709 {
9710 if( bWriteMask )
9711 {
9712 nMaskObject = createObject();
9713 aLine.append( "/SMask " );
9714 aLine.append( nMaskObject );
9715 aLine.append( " 0 R\n" );
9716 }
9717 }
9718 else if( m_bIsPDF_A1 && bWriteMask )
9719 m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
9720
9721 aLine.append( ">>\n"
9722 "stream\n" );
9723 if (!writeBuffer(aLine)) return false;
9724 sal_uInt64 nStartPos = 0;
9725 if (osl::File::E_None != m_aFile.getPos(nStartPos))
9726 return false;
9727
9728 checkAndEnableStreamEncryption( rObject.m_nObject );
9729 if (!g_bDebugDisableCompression && nBitsPerComponent == 1)
9730 {
9731 writeG4Stream(pAccess.get());
9732 }
9733 else
9734 {
9735 beginCompression();
9736 if( ! bTrueColor || pAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb )
9737 {
9738 //With PDF bitmaps, each row is padded to a BYTE boundary (multiple of 8 bits).
9739 const int nScanLineBytes = ((pAccess->GetBitCount() * pAccess->Width()) + 7U) / 8U;
9740
9741 for( tools::Long i = 0, nHeight = pAccess->Height(); i < nHeight; i++ )
9742 {
9743 if (!writeBufferBytes(pAccess->GetScanline(i), nScanLineBytes))
9744 return false;
9745 }
9746 }
9747 else
9748 {
9749 const int nScanLineBytes = pAccess->Width()*3;
9750 std::unique_ptr<sal_uInt8[]> xCol(new sal_uInt8[nScanLineBytes]);
9751 for( tools::Long y = 0, nHeight = pAccess->Height(); y < nHeight; y++ )
9752 {
9753 for( tools::Long x = 0, nWidth = pAccess->Width(); x < nWidth; x++ )
9754 {
9755 BitmapColor aColor = pAccess->GetColor( y, x );
9756 xCol[3*x+0] = aColor.GetRed();
9757 xCol[3*x+1] = aColor.GetGreen();
9758 xCol[3*x+2] = aColor.GetBlue();
9759 }
9760 if (!writeBufferBytes(xCol.get(), nScanLineBytes))
9761 return false;
9762 }
9763 }
9764 endCompression();
9765 }
9766 disableStreamEncryption();
9767
9768 sal_uInt64 nEndPos = 0;
9769 if (osl::File::E_None != m_aFile.getPos(nEndPos))
9770 return false;
9771 aLine.setLength( 0 );
9772 aLine.append( "\nendstream\nendobj\n\n" );
9773 if (!writeBuffer(aLine)) return false;
9774 if (!updateObject(nStreamLengthObject)) return false;
9775 aLine.setLength( 0 );
9776 aLine.append( nStreamLengthObject );
9777 aLine.append( " 0 obj\n" );
9778 aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
9779 aLine.append( "\nendobj\n\n" );
9780 if (!writeBuffer(aLine)) return false;
9781
9782 if( nMaskObject )
9783 return writeBitmapMaskObject( nMaskObject, rObject.m_aBitmap.CreateAlphaMask() );
9784
9785 writeReferenceXObject(rObject.m_aReferenceXObject);
9786
9787 return true;
9788 }
9789
writeBitmapMaskObject(sal_Int32 nMaskObject,const AlphaMask & rAlphaMask)9790 bool PDFWriterImpl::writeBitmapMaskObject( sal_Int32 nMaskObject, const AlphaMask& rAlphaMask )
9791 {
9792 assert( rAlphaMask.GetBitmap().getPixelFormat() == vcl::PixelFormat::N8_BPP );
9793
9794 if (!updateObject(nMaskObject))
9795 return false;
9796
9797 Bitmap aBitmap;
9798 if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
9799 {
9800 aBitmap = rAlphaMask.GetBitmap();
9801 aBitmap.Convert( BmpConversion::N1BitThreshold );
9802 }
9803 else
9804 {
9805 aBitmap = rAlphaMask.GetBitmap();
9806 }
9807
9808 const sal_Int32 nBitsPerComponent = 8;
9809
9810 sal_Int32 nStreamLengthObject = createObject();
9811
9812 if (g_bDebugDisableCompression)
9813 {
9814 emitComment( "PDFWriterImpl::writeBitmapObject" );
9815 }
9816 OStringBuffer aLine(1024);
9817 aLine.append( nMaskObject );
9818 aLine.append( " 0 obj\n"
9819 "<</Type/XObject/Subtype/Image/Width " );
9820 aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Width()) );
9821 aLine.append( "/Height " );
9822 aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Height()) );
9823 aLine.append( "/BitsPerComponent " );
9824 aLine.append( nBitsPerComponent );
9825 aLine.append( "/Length " );
9826 aLine.append( nStreamLengthObject );
9827 aLine.append( " 0 R\n" );
9828 if (!g_bDebugDisableCompression)
9829 {
9830 aLine.append( "/Filter/FlateDecode" );
9831 }
9832 aLine.append( "/ColorSpace/DeviceGray\n"
9833 "/Decode [ 1 0 ]\n" );
9834
9835 aLine.append( ">>\n"
9836 "stream\n" );
9837 if (!writeBuffer(aLine)) return false;
9838 sal_uInt64 nStartPos = 0;
9839 if (osl::File::E_None != m_aFile.getPos(nStartPos))
9840 return false;
9841
9842 checkAndEnableStreamEncryption( nMaskObject );
9843 beginCompression();
9844 BitmapScopedReadAccess pAccess(aBitmap);
9845 //With PDF bitmaps, each row is padded to a BYTE boundary (multiple of 8 bits).
9846 const int nScanLineBytes = ((pAccess->GetBitCount() * pAccess->Width()) + 7U) / 8U;
9847 // we have alpha, but we want to output transparency, so we need to invert the data
9848 std::unique_ptr<sal_uInt8[]> pInvertedBytes = std::make_unique<sal_uInt8[]>(nScanLineBytes);
9849 for( tools::Long i = 0, nHeight = pAccess->Height(); i < nHeight; i++ )
9850 {
9851 const Scanline pScanline = pAccess->GetScanline(i);
9852 std::copy(pScanline, pScanline + nScanLineBytes, pInvertedBytes.get());
9853 for (auto p = pInvertedBytes.get(); p < pInvertedBytes.get() + nScanLineBytes; ++p)
9854 *p = ~(*p);
9855 if (!writeBufferBytes(pInvertedBytes.get(), nScanLineBytes))
9856 return false;
9857 }
9858 endCompression();
9859 disableStreamEncryption();
9860
9861 sal_uInt64 nEndPos = 0;
9862 if (osl::File::E_None != m_aFile.getPos(nEndPos))
9863 return false;
9864 aLine.setLength( 0 );
9865 aLine.append( "\nendstream\nendobj\n\n" );
9866 if (!writeBuffer(aLine)) return false;
9867 if (!updateObject(nStreamLengthObject)) return false;
9868 aLine.setLength( 0 );
9869 aLine.append( nStreamLengthObject );
9870 aLine.append( " 0 obj\n" );
9871 aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
9872 aLine.append( "\nendobj\n\n" );
9873 if (!writeBuffer(aLine)) return false;
9874
9875 return true;
9876 }
9877
createEmbeddedFile(const Graphic & rGraphic,ReferenceXObjectEmit & rEmit,sal_Int32 nBitmapObject)9878 void PDFWriterImpl::createEmbeddedFile(const Graphic& rGraphic, ReferenceXObjectEmit& rEmit, sal_Int32 nBitmapObject)
9879 {
9880 // The bitmap object is always a valid identifier, even if the graphic has
9881 // no pdf data.
9882 rEmit.m_nBitmapObject = nBitmapObject;
9883
9884 if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf)
9885 return;
9886
9887 BinaryDataContainer const & rDataContainer = rGraphic.getVectorGraphicData()->getBinaryDataContainer();
9888
9889 if (m_aContext.UseReferenceXObject)
9890 {
9891 // Store the original PDF data as an embedded file.
9892 auto nObjectID = addEmbeddedFile(rDataContainer);
9893 rEmit.m_nEmbeddedObject = nObjectID;
9894 }
9895 else
9896 {
9897 sal_Int32 aIndex = m_aExternalPDFStreams.store(rDataContainer);
9898 rEmit.m_nExternalPDFPageIndex = rGraphic.getVectorGraphicData()->getPageIndex();
9899 rEmit.m_nExternalPDFDataIndex = aIndex;
9900 }
9901
9902 rEmit.m_nFormObject = createObject();
9903 rEmit.m_aPixelSize = rGraphic.GetSizePixel();
9904 }
9905
drawJPGBitmap(SvStream & rDCTData,bool bIsTrueColor,const Size & rSizePixel,const tools::Rectangle & rTargetArea,const AlphaMask & rAlphaMask,const Graphic & rGraphic)9906 void PDFWriterImpl::drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const Size& rSizePixel, const tools::Rectangle& rTargetArea, const AlphaMask& rAlphaMask, const Graphic& rGraphic )
9907 {
9908 MARK( "drawJPGBitmap" );
9909
9910 OStringBuffer aLine( 80 );
9911 updateGraphicsState();
9912
9913 // #i40055# sanity check
9914 if( ! (rTargetArea.GetWidth() && rTargetArea.GetHeight() ) )
9915 return;
9916 if( ! (rSizePixel.Width() && rSizePixel.Height()) )
9917 return;
9918
9919 rDCTData.Seek( 0 );
9920 if( bIsTrueColor && m_aContext.ColorMode == PDFWriter::DrawGreyscale )
9921 {
9922 // need to convert to grayscale;
9923 // load stream to bitmap and draw the bitmap instead
9924 Graphic aGraphic;
9925 GraphicConverter::Import( rDCTData, aGraphic, ConvertDataFormat::JPG );
9926 if( !rAlphaMask.IsEmpty() && rAlphaMask.GetSizePixel() == aGraphic.GetSizePixel() )
9927 {
9928 Bitmap aBmp( aGraphic.GetBitmap().CreateColorBitmap() );
9929 BitmapEx aBmpEx( aBmp, rAlphaMask );
9930 drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), Bitmap(aBmpEx) );
9931 }
9932 else
9933 drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aGraphic.GetBitmap() );
9934 return;
9935 }
9936
9937 std::unique_ptr<SvMemoryStream> pStream(new SvMemoryStream);
9938 pStream->WriteStream( rDCTData );
9939 pStream->Seek( STREAM_SEEK_TO_END );
9940
9941 BitmapID aID;
9942 aID.m_aPixelSize = rSizePixel;
9943 aID.m_nSize = pStream->Tell();
9944 pStream->Seek( STREAM_SEEK_TO_BEGIN );
9945 aID.m_nChecksum = rtl_crc32( 0, pStream->GetData(), aID.m_nSize );
9946 if( ! rAlphaMask.IsEmpty() )
9947 aID.m_nMaskChecksum = rAlphaMask.GetChecksum();
9948
9949 std::vector< JPGEmit >::const_iterator it = std::find_if(m_aJPGs.begin(), m_aJPGs.end(),
9950 [&](const JPGEmit& arg) { return aID == arg.m_aID; });
9951 if( it == m_aJPGs.end() )
9952 {
9953 m_aJPGs.emplace( m_aJPGs.begin() );
9954 JPGEmit& rEmit = m_aJPGs.front();
9955 if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject)
9956 rEmit.m_nObject = createObject();
9957 rEmit.m_aID = aID;
9958 rEmit.m_pStream = std::move( pStream );
9959 rEmit.m_bTrueColor = bIsTrueColor;
9960 if( !rAlphaMask.IsEmpty() && rAlphaMask.GetSizePixel() == rSizePixel )
9961 rEmit.m_aAlphaMask = rAlphaMask;
9962 createEmbeddedFile(rGraphic, rEmit.m_aReferenceXObject, rEmit.m_nObject);
9963
9964 it = m_aJPGs.begin();
9965 }
9966
9967 aLine.append( "q " );
9968 sal_Int32 nCheckWidth = 0;
9969 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetWidth()), aLine, false, &nCheckWidth );
9970 aLine.append( " 0 0 " );
9971 sal_Int32 nCheckHeight = 0;
9972 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetHeight()), aLine, true, &nCheckHeight );
9973 aLine.append( ' ' );
9974 m_aPages.back().appendPoint( rTargetArea.BottomLeft(), aLine );
9975 aLine.append( " cm\n/Im" );
9976 sal_Int32 nObject = it->m_aReferenceXObject.getObject();
9977 aLine.append(nObject);
9978 aLine.append( " Do Q\n" );
9979 if( nCheckWidth == 0 || nCheckHeight == 0 )
9980 {
9981 // #i97512# avoid invalid current matrix
9982 aLine.setLength( 0 );
9983 aLine.append( "\n%jpeg image /Im" );
9984 aLine.append( it->m_nObject );
9985 aLine.append( " scaled to zero size, omitted\n" );
9986 }
9987 writeBuffer( aLine );
9988
9989 OString aObjName = "Im" + OString::number(nObject);
9990 pushResource( ResourceKind::XObject, aObjName, nObject );
9991
9992 }
9993
drawBitmap(const Point & rDestPoint,const Size & rDestSize,const BitmapEmit & rBitmap,const Color & rFillColor)9994 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEmit& rBitmap, const Color& rFillColor )
9995 {
9996 OStringBuffer& rLine = drawBitmapLine;
9997 rLine.setLength(0);
9998 updateGraphicsState();
9999
10000 rLine.append( "q " );
10001 if( rFillColor != COL_TRANSPARENT )
10002 {
10003 appendNonStrokingColor( rFillColor, rLine );
10004 rLine.append( ' ' );
10005 }
10006 sal_Int32 nCheckWidth = 0;
10007 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Width()), rLine, false, &nCheckWidth );
10008 rLine.append( " 0 0 " );
10009 sal_Int32 nCheckHeight = 0;
10010 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Height()), rLine, true, &nCheckHeight );
10011 rLine.append( ' ' );
10012 m_aPages.back().appendPoint( rDestPoint + Point( 0, rDestSize.Height()-1 ), rLine );
10013 rLine.append( " cm\n/Im" );
10014 sal_Int32 nObject = rBitmap.m_aReferenceXObject.getObject();
10015 rLine.append(nObject);
10016 rLine.append( " Do Q\n" );
10017 if( nCheckWidth == 0 || nCheckHeight == 0 )
10018 {
10019 // #i97512# avoid invalid current matrix
10020 rLine.setLength( 0 );
10021 rLine.append( "\n%bitmap image /Im" );
10022 rLine.append( rBitmap.m_nObject );
10023 rLine.append( " scaled to zero size, omitted\n" );
10024 }
10025 writeBuffer( rLine );
10026 }
10027
createBitmapEmit(const Bitmap & i_rBitmap,const Graphic & rGraphic,std::list<BitmapEmit> & rBitmaps,ResourceDict & rResourceDict,std::list<StreamRedirect> & rOutputStreams)10028 const BitmapEmit& PDFWriterImpl::createBitmapEmit(const Bitmap& i_rBitmap, const Graphic& rGraphic, std::list<BitmapEmit>& rBitmaps, ResourceDict& rResourceDict, std::list<StreamRedirect>& rOutputStreams)
10029 {
10030 Bitmap aBitmap( i_rBitmap );
10031 auto ePixelFormat = aBitmap.getPixelFormat();
10032 if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
10033 aBitmap.Convert(BmpConversion::N8BitGreys);
10034 BitmapID aID;
10035 aID.m_aPixelSize = aBitmap.GetSizePixel();
10036 aID.m_nSize = vcl::pixelFormatBitCount(ePixelFormat);
10037 aID.m_nChecksum = BitmapEx(aBitmap).GetBitmap().GetChecksum();
10038 aID.m_nMaskChecksum = 0;
10039 if( aBitmap.HasAlpha() )
10040 aID.m_nMaskChecksum = aBitmap.CreateAlphaMask().GetChecksum();
10041 std::list<BitmapEmit>::const_iterator it = std::find_if(rBitmaps.begin(), rBitmaps.end(),
10042 [&](const BitmapEmit& arg) { return aID == arg.m_aID; });
10043 if (it == rBitmaps.end())
10044 {
10045 rBitmaps.push_front(BitmapEmit());
10046 rBitmaps.front().m_aID = aID;
10047 rBitmaps.front().m_aBitmap = std::move(aBitmap);
10048 if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject)
10049 rBitmaps.front().m_nObject = createObject();
10050 createEmbeddedFile(rGraphic, rBitmaps.front().m_aReferenceXObject, rBitmaps.front().m_nObject);
10051 it = rBitmaps.begin();
10052 }
10053
10054 sal_Int32 nObject = it->m_aReferenceXObject.getObject();
10055 OString aObjName = "Im" + OString::number(nObject);
10056 pushResource(ResourceKind::XObject, aObjName, nObject, rResourceDict, rOutputStreams);
10057
10058 return *it;
10059 }
10060
createBitmapEmit(const Bitmap & i_rBitmap,const Graphic & rGraphic)10061 const BitmapEmit& PDFWriterImpl::createBitmapEmit( const Bitmap& i_rBitmap, const Graphic& rGraphic )
10062 {
10063 return createBitmapEmit(i_rBitmap, rGraphic, m_aBitmaps, m_aGlobalResourceDict, m_aOutputStreams);
10064 }
10065
drawBitmap(const Point & rDestPoint,const Size & rDestSize,const Bitmap & rBitmap,const Graphic & rGraphic)10066 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap, const Graphic& rGraphic )
10067 {
10068 MARK( "drawBitmap (Bitmap)" );
10069
10070 // #i40055# sanity check
10071 if( ! (rDestSize.Width() && rDestSize.Height()) )
10072 return;
10073
10074 const BitmapEmit& rEmit = createBitmapEmit( rBitmap, rGraphic );
10075 drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
10076 }
10077
drawBitmap(const Point & rDestPoint,const Size & rDestSize,const Bitmap & rBitmap)10078 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap )
10079 {
10080 MARK( "drawBitmap (BitmapEx)" );
10081
10082 // #i40055# sanity check
10083 if( ! (rDestSize.Width() && rDestSize.Height()) )
10084 return;
10085
10086 const BitmapEmit& rEmit = createBitmapEmit( rBitmap, Graphic() );
10087 drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
10088 }
10089
createGradient(const Gradient & rGradient,const Size & rSize)10090 sal_Int32 PDFWriterImpl::createGradient( const Gradient& rGradient, const Size& rSize )
10091 {
10092 Size aPtSize( lcl_convert( m_aGraphicsStack.front().m_aMapMode,
10093 MapMode( MapUnit::MapPoint ),
10094 this,
10095 rSize ) );
10096 // check if we already have this gradient
10097 // rounding to point will generally lose some pixels
10098 // round up to point boundary
10099 aPtSize.AdjustWidth( 1 );
10100 aPtSize.AdjustHeight( 1 );
10101 std::list< GradientEmit >::const_iterator it = std::find_if(m_aGradients.begin(), m_aGradients.end(),
10102 [&](const GradientEmit& arg) { return ((rGradient == arg.m_aGradient) && (aPtSize == arg.m_aSize) ); });
10103
10104 if( it == m_aGradients.end() )
10105 {
10106 m_aGradients.push_front( GradientEmit() );
10107 m_aGradients.front().m_aGradient = rGradient;
10108 m_aGradients.front().m_nObject = createObject();
10109 m_aGradients.front().m_aSize = aPtSize;
10110 it = m_aGradients.begin();
10111 }
10112
10113 OStringBuffer aObjName( 16 );
10114 aObjName.append( 'P' );
10115 aObjName.append( it->m_nObject );
10116 pushResource( ResourceKind::Shading, aObjName.makeStringAndClear(), it->m_nObject );
10117
10118 return it->m_nObject;
10119 }
10120
drawGradient(const tools::Rectangle & rRect,const Gradient & rGradient)10121 void PDFWriterImpl::drawGradient( const tools::Rectangle& rRect, const Gradient& rGradient )
10122 {
10123 MARK( "drawGradient (Rectangle)" );
10124
10125 sal_Int32 nGradient = createGradient( rGradient, rRect.GetSize() );
10126
10127 Point aTranslate( rRect.BottomLeft() );
10128 aTranslate += Point( 0, 1 );
10129
10130 updateGraphicsState();
10131
10132 OStringBuffer aLine( 80 );
10133 aLine.append( "q 1 0 0 1 " );
10134 m_aPages.back().appendPoint( aTranslate, aLine );
10135 aLine.append( " cm " );
10136 // if a stroke is appended reset the clip region before stroke
10137 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
10138 aLine.append( "q " );
10139 aLine.append( "0 0 " );
10140 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
10141 aLine.append( ' ' );
10142 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
10143 aLine.append( " re W n\n" );
10144
10145 aLine.append( "/P" );
10146 aLine.append( nGradient );
10147 aLine.append( " sh " );
10148 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
10149 {
10150 aLine.append( "Q 0 0 " );
10151 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
10152 aLine.append( ' ' );
10153 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
10154 aLine.append( " re S " );
10155 }
10156 aLine.append( "Q\n" );
10157 writeBuffer( aLine );
10158 }
10159
drawHatch(const tools::PolyPolygon & rPolyPoly,const Hatch & rHatch)10160 void PDFWriterImpl::drawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch )
10161 {
10162 MARK( "drawHatch" );
10163
10164 updateGraphicsState();
10165
10166 if( rPolyPoly.Count() )
10167 {
10168 tools::PolyPolygon aPolyPoly( rPolyPoly );
10169
10170 aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME );
10171 push( PushFlags::LINECOLOR );
10172 setLineColor( rHatch.GetColor() );
10173 DrawHatch( aPolyPoly, rHatch, false );
10174 pop();
10175 }
10176 }
10177
drawWallpaper(const tools::Rectangle & rRect,const Wallpaper & rWall)10178 void PDFWriterImpl::drawWallpaper( const tools::Rectangle& rRect, const Wallpaper& rWall )
10179 {
10180 MARK( "drawWallpaper" );
10181
10182 bool bDrawColor = false;
10183 bool bDrawGradient = false;
10184 bool bDrawBitmap = false;
10185
10186 Bitmap aBitmap;
10187 Point aBmpPos = rRect.TopLeft();
10188 Size aBmpSize;
10189 if( rWall.IsBitmap() )
10190 {
10191 aBitmap = rWall.GetBitmap();
10192 aBmpSize = lcl_convert( aBitmap.GetPrefMapMode(),
10193 getMapMode(),
10194 this,
10195 aBitmap.GetPrefSize() );
10196 tools::Rectangle aRect( rRect );
10197 if( rWall.IsRect() )
10198 {
10199 aRect = rWall.GetRect();
10200 aBmpPos = aRect.TopLeft();
10201 aBmpSize = aRect.GetSize();
10202 }
10203 if( rWall.GetStyle() != WallpaperStyle::Scale )
10204 {
10205 if( rWall.GetStyle() != WallpaperStyle::Tile )
10206 {
10207 bDrawBitmap = true;
10208 if( rWall.IsGradient() )
10209 bDrawGradient = true;
10210 else
10211 bDrawColor = true;
10212 switch( rWall.GetStyle() )
10213 {
10214 case WallpaperStyle::TopLeft:
10215 break;
10216 case WallpaperStyle::Top:
10217 aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
10218 break;
10219 case WallpaperStyle::Left:
10220 aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
10221 break;
10222 case WallpaperStyle::TopRight:
10223 aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
10224 break;
10225 case WallpaperStyle::Center:
10226 aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
10227 aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
10228 break;
10229 case WallpaperStyle::Right:
10230 aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
10231 aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
10232 break;
10233 case WallpaperStyle::BottomLeft:
10234 aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
10235 break;
10236 case WallpaperStyle::Bottom:
10237 aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
10238 aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
10239 break;
10240 case WallpaperStyle::BottomRight:
10241 aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
10242 aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
10243 break;
10244 default: ;
10245 }
10246 }
10247 else
10248 {
10249 // push the bitmap
10250 const BitmapEmit& rEmit = createBitmapEmit( aBitmap, Graphic() );
10251
10252 // convert to page coordinates; this needs to be done here
10253 // since the emit does not know the page anymore
10254 tools::Rectangle aConvertRect( aBmpPos, aBmpSize );
10255 m_aPages.back().convertRect( aConvertRect );
10256
10257 OString aImageName = "Im" + OString::number( rEmit.m_nObject );
10258
10259 // push the pattern
10260 OStringBuffer aTilingStream( 32 );
10261 appendFixedInt( aConvertRect.GetWidth(), aTilingStream );
10262 aTilingStream.append( " 0 0 " );
10263 appendFixedInt( aConvertRect.GetHeight(), aTilingStream );
10264 aTilingStream.append( " 0 0 cm\n/" );
10265 aTilingStream.append( aImageName );
10266 aTilingStream.append( " Do\n" );
10267
10268 m_aTilings.emplace_back( );
10269 m_aTilings.back().m_nObject = createObject();
10270 m_aTilings.back().m_aRectangle = tools::Rectangle( Point( 0, 0 ), aConvertRect.GetSize() );
10271 m_aTilings.back().m_pTilingStream.reset(new SvMemoryStream());
10272 m_aTilings.back().m_pTilingStream->WriteBytes(
10273 aTilingStream.getStr(), aTilingStream.getLength() );
10274 // phase the tiling so wallpaper begins on upper left
10275 if ((aConvertRect.GetWidth() == 0) || (aConvertRect.GetHeight() == 0))
10276 throw o3tl::divide_by_zero();
10277 m_aTilings.back().m_aTransform.matrix[2] = double(aConvertRect.Left() % aConvertRect.GetWidth()) / fDivisor;
10278 m_aTilings.back().m_aTransform.matrix[5] = double(aConvertRect.Top() % aConvertRect.GetHeight()) / fDivisor;
10279 m_aTilings.back().m_aResources.m_aXObjects[aImageName] = rEmit.m_nObject;
10280
10281 updateGraphicsState();
10282
10283 OStringBuffer aObjName( 16 );
10284 aObjName.append( 'P' );
10285 aObjName.append( m_aTilings.back().m_nObject );
10286 OString aPatternName( aObjName.makeStringAndClear() );
10287 pushResource( ResourceKind::Pattern, aPatternName, m_aTilings.back().m_nObject );
10288
10289 // fill a rRect with the pattern
10290 OStringBuffer aLine( 100 );
10291 aLine.append( "q /Pattern cs /" );
10292 aLine.append( aPatternName );
10293 aLine.append( " scn " );
10294 m_aPages.back().appendRect( rRect, aLine );
10295 aLine.append( " f Q\n" );
10296 writeBuffer( aLine );
10297 }
10298 }
10299 else
10300 {
10301 aBmpPos = aRect.TopLeft();
10302 aBmpSize = aRect.GetSize();
10303 bDrawBitmap = true;
10304 }
10305
10306 if( aBitmap.HasAlpha() )
10307 {
10308 if( rWall.IsGradient() )
10309 bDrawGradient = true;
10310 else
10311 bDrawColor = true;
10312 }
10313 }
10314 else if( rWall.IsGradient() )
10315 bDrawGradient = true;
10316 else
10317 bDrawColor = true;
10318
10319 if( bDrawGradient )
10320 {
10321 drawGradient( rRect, rWall.GetGradient() );
10322 }
10323 if( bDrawColor )
10324 {
10325 Color aOldLineColor = m_aGraphicsStack.front().m_aLineColor;
10326 Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
10327 setLineColor( COL_TRANSPARENT );
10328 setFillColor( rWall.GetColor() );
10329 drawRectangle( rRect );
10330 setLineColor( aOldLineColor );
10331 setFillColor( aOldFillColor );
10332 }
10333 if( bDrawBitmap )
10334 {
10335 // set temporary clip region since aBmpPos and aBmpSize
10336 // may be outside rRect
10337 OStringBuffer aLine( 20 );
10338 aLine.append( "q " );
10339 m_aPages.back().appendRect( rRect, aLine );
10340 aLine.append( " W n\n" );
10341 writeBuffer( aLine );
10342 drawBitmap( aBmpPos, aBmpSize, aBitmap );
10343 writeBuffer( "Q\n" );
10344 }
10345 }
10346
updateGraphicsState(Mode const mode)10347 void PDFWriterImpl::updateGraphicsState(Mode const mode)
10348 {
10349 OStringBuffer& rLine = updateGraphicsStateLine;
10350 rLine.setLength(0);
10351 GraphicsState& rNewState = m_aGraphicsStack.front();
10352 // first set clip region since it might invalidate everything else
10353
10354 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::ClipRegion )
10355 {
10356 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::ClipRegion;
10357
10358 if( m_aCurrentPDFState.m_bClipRegion != rNewState.m_bClipRegion ||
10359 ( rNewState.m_bClipRegion && m_aCurrentPDFState.m_aClipRegion != rNewState.m_aClipRegion ) )
10360 {
10361 if( m_aCurrentPDFState.m_bClipRegion )
10362 {
10363 rLine.append( "Q " );
10364 // invalidate everything but the clip region
10365 m_aCurrentPDFState = GraphicsState();
10366 rNewState.m_nUpdateFlags = ~GraphicsStateUpdateFlags::ClipRegion;
10367 }
10368 if( rNewState.m_bClipRegion )
10369 {
10370 // clip region is always stored in private PDF mapmode
10371 MapMode aNewMapMode = std::move(rNewState.m_aMapMode);
10372 rNewState.m_aMapMode = m_aMapMode;
10373 SetMapMode( rNewState.m_aMapMode );
10374 m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
10375
10376 rLine.append("q ");
10377 if ( rNewState.m_aClipRegion.count() )
10378 {
10379 m_aPages.back().appendPolyPolygon( rNewState.m_aClipRegion, rLine );
10380 }
10381 else
10382 {
10383 // tdf#130150 Need to revert tdf#99680, that breaks the
10384 // rule that an set but empty clip-region clips everything
10385 // aka draws nothing -> nothing is in an empty clip-region
10386 rLine.append( "0 0 m h " ); // NULL clip, i.e. nothing visible
10387 }
10388 rLine.append( "W* n\n" );
10389
10390 rNewState.m_aMapMode = std::move(aNewMapMode);
10391 SetMapMode( rNewState.m_aMapMode );
10392 m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
10393 }
10394 }
10395 }
10396
10397 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::MapMode )
10398 {
10399 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::MapMode;
10400 SetMapMode( rNewState.m_aMapMode );
10401 }
10402
10403 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::Font )
10404 {
10405 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::Font;
10406 SetFont( rNewState.m_aFont );
10407 }
10408
10409 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LayoutMode )
10410 {
10411 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LayoutMode;
10412 SetLayoutMode( rNewState.m_nLayoutMode );
10413 }
10414
10415 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::DigitLanguage )
10416 {
10417 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::DigitLanguage;
10418 SetDigitLanguage( rNewState.m_aDigitLanguage );
10419 }
10420
10421 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LineColor )
10422 {
10423 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LineColor;
10424 if( m_aCurrentPDFState.m_aLineColor != rNewState.m_aLineColor &&
10425 rNewState.m_aLineColor != COL_TRANSPARENT )
10426 {
10427 appendStrokingColor( rNewState.m_aLineColor, rLine );
10428 rLine.append( "\n" );
10429 }
10430 }
10431
10432 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::FillColor )
10433 {
10434 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::FillColor;
10435 if( m_aCurrentPDFState.m_aFillColor != rNewState.m_aFillColor &&
10436 rNewState.m_aFillColor != COL_TRANSPARENT )
10437 {
10438 appendNonStrokingColor( rNewState.m_aFillColor, rLine );
10439 rLine.append( "\n" );
10440 }
10441 }
10442
10443 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::TransparentPercent )
10444 {
10445 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::TransparentPercent;
10446 if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
10447 {
10448 // TODO: switch extended graphicsstate
10449 }
10450 }
10451
10452 // everything is up to date now
10453 m_aCurrentPDFState = m_aGraphicsStack.front();
10454 if ((mode != Mode::NOWRITE) && !rLine.isEmpty())
10455 writeBuffer( rLine );
10456 }
10457
10458 /* #i47544# imitate OutputDevice behaviour:
10459 * if a font with a nontransparent color is set, it overwrites the current
10460 * text color. OTOH setting the text color will overwrite the color of the font.
10461 */
setFont(const vcl::Font & rFont)10462 void PDFWriterImpl::setFont( const vcl::Font& rFont )
10463 {
10464 Color aColor = rFont.GetColor();
10465 if( aColor == COL_TRANSPARENT )
10466 aColor = m_aGraphicsStack.front().m_aFont.GetColor();
10467 m_aGraphicsStack.front().m_aFont = rFont;
10468 m_aGraphicsStack.front().m_aFont.SetColor( aColor );
10469 m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font;
10470 }
10471
push(PushFlags nFlags)10472 void PDFWriterImpl::push( PushFlags nFlags )
10473 {
10474 OSL_ENSURE( !m_aGraphicsStack.empty(), "invalid graphics stack" );
10475 m_aGraphicsStack.push_front( m_aGraphicsStack.front() );
10476 m_aGraphicsStack.front().m_nFlags = nFlags;
10477 }
10478
pop()10479 void PDFWriterImpl::pop()
10480 {
10481 OSL_ENSURE( m_aGraphicsStack.size() > 1, "pop without push" );
10482 if( m_aGraphicsStack.size() < 2 )
10483 return;
10484
10485 GraphicsState aState = m_aGraphicsStack.front();
10486 m_aGraphicsStack.pop_front();
10487 GraphicsState& rOld = m_aGraphicsStack.front();
10488
10489 // move those parameters back that were not pushed
10490 // in the first place
10491 if( ! (aState.m_nFlags & PushFlags::LINECOLOR) )
10492 setLineColor( aState.m_aLineColor );
10493 if( ! (aState.m_nFlags & PushFlags::FILLCOLOR) )
10494 setFillColor( aState.m_aFillColor );
10495 if( ! (aState.m_nFlags & PushFlags::FONT) )
10496 setFont( aState.m_aFont );
10497 if( ! (aState.m_nFlags & PushFlags::TEXTCOLOR) )
10498 setTextColor( aState.m_aFont.GetColor() );
10499 if( ! (aState.m_nFlags & PushFlags::MAPMODE) )
10500 setMapMode( aState.m_aMapMode );
10501 if( ! (aState.m_nFlags & PushFlags::CLIPREGION) )
10502 {
10503 // do not use setClipRegion here
10504 // it would convert again assuming the current mapmode
10505 rOld.m_aClipRegion = aState.m_aClipRegion;
10506 rOld.m_bClipRegion = aState.m_bClipRegion;
10507 }
10508 if( ! (aState.m_nFlags & PushFlags::TEXTLINECOLOR ) )
10509 setTextLineColor( aState.m_aTextLineColor );
10510 if( ! (aState.m_nFlags & PushFlags::OVERLINECOLOR ) )
10511 setOverlineColor( aState.m_aOverlineColor );
10512 if( ! (aState.m_nFlags & PushFlags::TEXTALIGN ) )
10513 setTextAlign( aState.m_aFont.GetAlignment() );
10514 if( ! (aState.m_nFlags & PushFlags::TEXTFILLCOLOR) )
10515 setTextFillColor( aState.m_aFont.GetFillColor() );
10516 if( ! (aState.m_nFlags & PushFlags::REFPOINT) )
10517 {
10518 // what ?
10519 }
10520 // invalidate graphics state
10521 m_aGraphicsStack.front().m_nUpdateFlags = GraphicsStateUpdateFlags::All;
10522 }
10523
setMapMode(const MapMode & rMapMode)10524 void PDFWriterImpl::setMapMode( const MapMode& rMapMode )
10525 {
10526 m_aGraphicsStack.front().m_aMapMode = rMapMode;
10527 SetMapMode( rMapMode );
10528 m_aCurrentPDFState.m_aMapMode = rMapMode;
10529 }
10530
setClipRegion(const basegfx::B2DPolyPolygon & rRegion)10531 void PDFWriterImpl::setClipRegion( const basegfx::B2DPolyPolygon& rRegion )
10532 {
10533 // tdf#130150 improve coordinate manipulations to double precision transformations
10534 const basegfx::B2DHomMatrix aCurrentTransform(
10535 GetInverseViewTransformation(m_aMapMode) * GetViewTransformation(m_aGraphicsStack.front().m_aMapMode));
10536 basegfx::B2DPolyPolygon aRegion(rRegion);
10537
10538 aRegion.transform(aCurrentTransform);
10539 m_aGraphicsStack.front().m_aClipRegion = std::move(aRegion);
10540 m_aGraphicsStack.front().m_bClipRegion = true;
10541 m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10542 }
10543
moveClipRegion(sal_Int32 nX,sal_Int32 nY)10544 void PDFWriterImpl::moveClipRegion( sal_Int32 nX, sal_Int32 nY )
10545 {
10546 if( !(m_aGraphicsStack.front().m_bClipRegion && m_aGraphicsStack.front().m_aClipRegion.count()) )
10547 return;
10548
10549 // tdf#130150 improve coordinate manipulations to double precision transformations
10550 basegfx::B2DHomMatrix aConvertA;
10551
10552 if(MapUnit::MapPixel == m_aGraphicsStack.front().m_aMapMode.GetMapUnit())
10553 {
10554 aConvertA = GetInverseViewTransformation(m_aMapMode);
10555 }
10556 else
10557 {
10558 aConvertA = LogicToLogic(m_aGraphicsStack.front().m_aMapMode, m_aMapMode);
10559 }
10560
10561 basegfx::B2DPoint aB2DPointA(nX, nY);
10562 basegfx::B2DPoint aB2DPointB(0.0, 0.0);
10563 aB2DPointA *= aConvertA;
10564 aB2DPointB *= aConvertA;
10565 aB2DPointA -= aB2DPointB;
10566 basegfx::B2DHomMatrix aMat;
10567
10568 aMat.translate(aB2DPointA.getX(), aB2DPointA.getY());
10569 m_aGraphicsStack.front().m_aClipRegion.transform( aMat );
10570 m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10571 }
10572
intersectClipRegion(const tools::Rectangle & rRect)10573 void PDFWriterImpl::intersectClipRegion( const tools::Rectangle& rRect )
10574 {
10575 basegfx::B2DPolyPolygon aRect( basegfx::utils::createPolygonFromRect(
10576 vcl::unotools::b2DRectangleFromRectangle(rRect) ) );
10577 intersectClipRegion( aRect );
10578 }
10579
intersectClipRegion(const basegfx::B2DPolyPolygon & rRegion)10580 void PDFWriterImpl::intersectClipRegion( const basegfx::B2DPolyPolygon& rRegion )
10581 {
10582 // tdf#130150 improve coordinate manipulations to double precision transformations
10583 const basegfx::B2DHomMatrix aCurrentTransform(
10584 GetInverseViewTransformation(m_aMapMode) * GetViewTransformation(m_aGraphicsStack.front().m_aMapMode));
10585 basegfx::B2DPolyPolygon aRegion(rRegion);
10586
10587 aRegion.transform(aCurrentTransform);
10588 m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10589
10590 if( m_aGraphicsStack.front().m_bClipRegion )
10591 {
10592 basegfx::B2DPolyPolygon aOld( basegfx::utils::prepareForPolygonOperation( m_aGraphicsStack.front().m_aClipRegion ) );
10593 aRegion = basegfx::utils::prepareForPolygonOperation( aRegion );
10594 m_aGraphicsStack.front().m_aClipRegion = basegfx::utils::solvePolygonOperationAnd( aOld, aRegion );
10595 }
10596 else
10597 {
10598 m_aGraphicsStack.front().m_aClipRegion = std::move(aRegion);
10599 m_aGraphicsStack.front().m_bClipRegion = true;
10600 }
10601 }
10602
createNote(const tools::Rectangle & rRect,const tools::Rectangle & rPopupRect,const pdf::PDFNote & rNote,sal_Int32 nPageNr)10603 sal_Int32 PDFWriterImpl::createNote(const tools::Rectangle& rRect,
10604 const tools::Rectangle& rPopupRect, const pdf::PDFNote& rNote,
10605 sal_Int32 nPageNr)
10606 {
10607 if (nPageNr < 0)
10608 nPageNr = m_nCurrentPage;
10609
10610 if (nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size())
10611 return -1;
10612
10613 sal_Int32 nRet = m_aNotes.size();
10614
10615 m_aNotes.emplace_back();
10616 auto & rNoteEntry = m_aNotes.back();
10617 rNoteEntry.m_nObject = createObject();
10618 rNoteEntry.m_aPopUpAnnotation.m_nObject = createObject();
10619 rNoteEntry.m_aPopUpAnnotation.m_nParentObject = rNoteEntry.m_nObject;
10620 rNoteEntry.m_aPopUpAnnotation.m_aRect = rPopupRect;
10621 rNoteEntry.m_aContents = rNote;
10622 rNoteEntry.m_aRect = rRect;
10623 rNoteEntry.m_nPage = nPageNr;
10624 // convert to default user space now, since the mapmode may change
10625 m_aPages[nPageNr].convertRect(rNoteEntry.m_aRect);
10626 m_aPages[nPageNr].convertRect(rNoteEntry.m_aPopUpAnnotation.m_aRect);
10627
10628 // insert note to page's annotation list
10629 m_aPages[nPageNr].m_aAnnotations.push_back(rNoteEntry.m_nObject);
10630 m_aPages[nPageNr].m_aAnnotations.push_back(rNoteEntry.m_aPopUpAnnotation.m_nObject);
10631
10632 return nRet;
10633 }
10634
createLink(const tools::Rectangle & rRect,sal_Int32 nPageNr,OUString const & rAltText)10635 sal_Int32 PDFWriterImpl::createLink(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText)
10636 {
10637 if( nPageNr < 0 )
10638 nPageNr = m_nCurrentPage;
10639
10640 if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
10641 return -1;
10642
10643 sal_Int32 nRet = m_aLinks.size();
10644
10645 m_aLinks.emplace_back(rAltText);
10646 m_aLinks.back().m_nObject = createObject();
10647 m_aLinks.back().m_nPage = nPageNr;
10648 m_aLinks.back().m_aRect = rRect;
10649 // convert to default user space now, since the mapmode may change
10650 m_aPages[nPageNr].convertRect( m_aLinks.back().m_aRect );
10651
10652 // insert link to page's annotation list
10653 m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aLinks.back().m_nObject );
10654
10655 return nRet;
10656 }
10657
createScreen(const tools::Rectangle & rRect,sal_Int32 nPageNr,OUString const & rAltText,OUString const & rMimeType)10658 sal_Int32 PDFWriterImpl::createScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText, OUString const& rMimeType)
10659 {
10660 if (nPageNr < 0)
10661 nPageNr = m_nCurrentPage;
10662
10663 if (nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size())
10664 return -1;
10665
10666 sal_Int32 nRet = m_aScreens.size();
10667
10668 m_aScreens.emplace_back(rAltText, rMimeType);
10669 m_aScreens.back().m_nObject = createObject();
10670 m_aScreens.back().m_nPage = nPageNr;
10671 m_aScreens.back().m_aRect = rRect;
10672 // Convert to default user space now, since the mapmode may change.
10673 m_aPages[nPageNr].convertRect(m_aScreens.back().m_aRect);
10674
10675 // Insert link to page's annotation list.
10676 m_aPages[nPageNr].m_aAnnotations.push_back(m_aScreens.back().m_nObject);
10677
10678 return nRet;
10679 }
10680
createNamedDest(const OUString & sDestName,const tools::Rectangle & rRect,sal_Int32 nPageNr,PDFWriter::DestAreaType eType)10681 sal_Int32 PDFWriterImpl::createNamedDest( const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10682 {
10683 if( nPageNr < 0 )
10684 nPageNr = m_nCurrentPage;
10685
10686 if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
10687 return -1;
10688
10689 sal_Int32 nRet = m_aNamedDests.size();
10690
10691 m_aNamedDests.emplace_back( );
10692 m_aNamedDests.back().m_aDestName = sDestName;
10693 m_aNamedDests.back().m_nPage = nPageNr;
10694 m_aNamedDests.back().m_eType = eType;
10695 m_aNamedDests.back().m_aRect = rRect;
10696 // convert to default user space now, since the mapmode may change
10697 m_aPages[nPageNr].convertRect( m_aNamedDests.back().m_aRect );
10698
10699 return nRet;
10700 }
10701
createDest(const tools::Rectangle & rRect,sal_Int32 nPageNr,PDFWriter::DestAreaType eType)10702 sal_Int32 PDFWriterImpl::createDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10703 {
10704 if( nPageNr < 0 )
10705 nPageNr = m_nCurrentPage;
10706
10707 if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
10708 return -1;
10709
10710 sal_Int32 nRet = m_aDests.size();
10711
10712 m_aDests.emplace_back( );
10713 m_aDests.back().m_nPage = nPageNr;
10714 m_aDests.back().m_eType = eType;
10715 m_aDests.back().m_aRect = rRect;
10716 // convert to default user space now, since the mapmode may change
10717 m_aPages[nPageNr].convertRect( m_aDests.back().m_aRect );
10718
10719 return nRet;
10720 }
10721
registerDestReference(sal_Int32 nDestId,const tools::Rectangle & rRect,sal_Int32 nPageNr,PDFWriter::DestAreaType eType)10722 sal_Int32 PDFWriterImpl::registerDestReference( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10723 {
10724 m_aDestinationIdTranslation[ nDestId ] = createDest( rRect, nPageNr, eType );
10725 return m_aDestinationIdTranslation[ nDestId ];
10726 }
10727
setLinkDest(sal_Int32 nLinkId,sal_Int32 nDestId)10728 void PDFWriterImpl::setLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
10729 {
10730 if( nLinkId < 0 || o3tl::make_unsigned(nLinkId) >= m_aLinks.size() )
10731 return;
10732 if( nDestId < 0 || o3tl::make_unsigned(nDestId) >= m_aDests.size() )
10733 return;
10734
10735 m_aLinks[ nLinkId ].m_nDest = nDestId;
10736 }
10737
setLinkURL(sal_Int32 nLinkId,const OUString & rURL)10738 void PDFWriterImpl::setLinkURL( sal_Int32 nLinkId, const OUString& rURL )
10739 {
10740 if( nLinkId < 0 || o3tl::make_unsigned(nLinkId) >= m_aLinks.size() )
10741 return;
10742
10743 m_aLinks[ nLinkId ].m_nDest = -1;
10744
10745 using namespace ::com::sun::star;
10746
10747 if (!m_xTrans.is())
10748 {
10749 const uno::Reference< uno::XComponentContext >& xContext( comphelper::getProcessComponentContext() );
10750 m_xTrans = util::URLTransformer::create(xContext);
10751 }
10752
10753 util::URL aURL;
10754 aURL.Complete = rURL;
10755
10756 m_xTrans->parseStrict( aURL );
10757
10758 m_aLinks[ nLinkId ].m_aURL = aURL.Complete;
10759 }
10760
setScreenURL(sal_Int32 nScreenId,const OUString & rURL)10761 void PDFWriterImpl::setScreenURL(sal_Int32 nScreenId, const OUString& rURL)
10762 {
10763 if (nScreenId < 0 || o3tl::make_unsigned(nScreenId) >= m_aScreens.size())
10764 return;
10765
10766 m_aScreens[nScreenId].m_aURL = rURL;
10767 }
10768
setScreenStream(sal_Int32 nScreenId,const OUString & rURL)10769 void PDFWriterImpl::setScreenStream(sal_Int32 nScreenId, const OUString& rURL)
10770 {
10771 if (nScreenId < 0 || o3tl::make_unsigned(nScreenId) >= m_aScreens.size())
10772 return;
10773
10774 m_aScreens[nScreenId].m_aTempFileURL = rURL;
10775 m_aScreens[nScreenId].m_nTempFileObject = createObject();
10776 }
10777
setLinkPropertyId(sal_Int32 nLinkId,sal_Int32 nPropertyId)10778 void PDFWriterImpl::setLinkPropertyId( sal_Int32 nLinkId, sal_Int32 nPropertyId )
10779 {
10780 m_aLinkPropertyMap[ nPropertyId ] = nLinkId;
10781 }
10782
createOutlineItem(sal_Int32 nParent,std::u16string_view rText,sal_Int32 nDestID)10783 sal_Int32 PDFWriterImpl::createOutlineItem( sal_Int32 nParent, std::u16string_view rText, sal_Int32 nDestID )
10784 {
10785 // create new item
10786 sal_Int32 nNewItem = m_aOutline.size();
10787 m_aOutline.emplace_back( );
10788
10789 // set item attributes
10790 setOutlineItemParent( nNewItem, nParent );
10791 setOutlineItemText( nNewItem, rText );
10792 setOutlineItemDest( nNewItem, nDestID );
10793
10794 return nNewItem;
10795 }
10796
setOutlineItemParent(sal_Int32 nItem,sal_Int32 nNewParent)10797 void PDFWriterImpl::setOutlineItemParent( sal_Int32 nItem, sal_Int32 nNewParent )
10798 {
10799 if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() )
10800 return;
10801
10802 if( nNewParent < 0 || o3tl::make_unsigned(nNewParent) >= m_aOutline.size() || nNewParent == nItem )
10803 {
10804 nNewParent = 0;
10805 }
10806 // insert item to new parent's list of children
10807 m_aOutline[ nNewParent ].m_aChildren.push_back( nItem );
10808 }
10809
setOutlineItemText(sal_Int32 nItem,std::u16string_view rText)10810 void PDFWriterImpl::setOutlineItemText( sal_Int32 nItem, std::u16string_view rText )
10811 {
10812 if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() )
10813 return;
10814
10815 m_aOutline[ nItem ].m_aTitle = psp::WhitespaceToSpace( rText );
10816 }
10817
setOutlineItemDest(sal_Int32 nItem,sal_Int32 nDestID)10818 void PDFWriterImpl::setOutlineItemDest( sal_Int32 nItem, sal_Int32 nDestID )
10819 {
10820 if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() ) // item does not exist
10821 return;
10822 if( nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size() ) // dest does not exist
10823 return;
10824 m_aOutline[nItem].m_nDestID = nDestID;
10825 }
10826
getStructureTag(vcl::pdf::StructElement eType)10827 const char* PDFWriterImpl::getStructureTag(vcl::pdf::StructElement eType)
10828 {
10829 using namespace vcl::pdf;
10830
10831 static constexpr auto constTagStrings = frozen::make_unordered_map<StructElement, const char*>({
10832 { StructElement::NonStructElement, "NonStruct" },
10833 { StructElement::Document, "Document" },
10834 { StructElement::Part, "Part" },
10835 { StructElement::Article, "Art" },
10836 { StructElement::Section, "Sect" },
10837 { StructElement::Division, "Div" },
10838 { StructElement::BlockQuote, "BlockQuote" },
10839 { StructElement::Caption, "Caption" },
10840 { StructElement::TOC, "TOC" },
10841 { StructElement::TOCI, "TOCI" },
10842 { StructElement::Index, "Index" },
10843 { StructElement::Paragraph, "P" },
10844 { StructElement::Heading, "H" },
10845 { StructElement::H1, "H1" },
10846 { StructElement::H2, "H2" },
10847 { StructElement::H3, "H3" },
10848 { StructElement::H4, "H4" },
10849 { StructElement::H5, "H5" },
10850 { StructElement::H6, "H6" },
10851 { StructElement::List, "L" },
10852 { StructElement::ListItem, "LI" },
10853 { StructElement::LILabel, "Lbl" },
10854 { StructElement::LIBody, "LBody" },
10855 { StructElement::Table, "Table" },
10856 { StructElement::TableRow, "TR" },
10857 { StructElement::TableHeader, "TH" },
10858 { StructElement::TableData, "TD" },
10859 { StructElement::Span, "Span" },
10860 { StructElement::Quote, "Quote" },
10861 { StructElement::Note, "Note" },
10862 { StructElement::Reference, "Reference" },
10863 { StructElement::BibEntry, "BibEntry" },
10864 { StructElement::Code, "Code" },
10865 { StructElement::Link, "Link" },
10866 { StructElement::Annot, "Annot" },
10867 { StructElement::Ruby, "Ruby" },
10868 { StructElement::RB, "RB" },
10869 { StructElement::RT, "RT" },
10870 { StructElement::RP, "RP" },
10871 { StructElement::Warichu, "Warichu" },
10872 { StructElement::WT, "WT" },
10873 { StructElement::WP, "WP" },
10874 { StructElement::Figure, "Figure" },
10875 { StructElement::Formula, "Formula"},
10876 { StructElement::Form, "Form" },
10877 { StructElement::Title, "Title" },
10878 { StructElement::Emphasis, "Em" },
10879 { StructElement::Strong, "Strong" },
10880 });
10881
10882 // First handle fallbacks for elements that were added in a certain PDF version
10883
10884 // PDF 1.5 fallbacks
10885 if (m_aContext.Version < PDFWriter::PDFVersion::PDF_1_5 && eType == StructElement::Annot)
10886 eType = StructElement::Figure;
10887
10888 // PDF 2.0 fallbacks
10889 if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0)
10890 {
10891 switch (eType)
10892 {
10893 case StructElement::Title:
10894 eType = StructElement::Paragraph; break;
10895 case StructElement::Emphasis:
10896 eType = StructElement::Span; break;
10897 case StructElement::Strong:
10898 eType = StructElement::Span; break;
10899 default:
10900 break;
10901 }
10902 }
10903
10904 auto iterator = constTagStrings.find(eType);
10905
10906 if (iterator == constTagStrings.end())
10907 return "Div";
10908
10909 return iterator->second;
10910 }
10911
addRoleMap(const OString & aAlias,vcl::pdf::StructElement eType)10912 void PDFWriterImpl::addRoleMap(const OString& aAlias, vcl::pdf::StructElement eType)
10913 {
10914 OString aTag = getStructureTag(eType);
10915 // For PDF/UA it's not allowed to map an alias with the same name.
10916 // Not aware of a reason for doing it in any case, so just don't do it.
10917 if (aAlias != aTag)
10918 m_aRoleMap[aAlias] = aTag;
10919 }
10920
beginStructureElementMCSeq()10921 void PDFWriterImpl::beginStructureElementMCSeq()
10922 {
10923 assert(m_nCurrentStructElement == 0 || m_aStructure[m_nCurrentStructElement].m_oType);
10924 if( m_bEmitStructure &&
10925 m_nCurrentStructElement > 0 && // StructTreeRoot
10926 // Document = SwPageFrame => this is not *inside* the page content
10927 // stream so do not emit MCID!
10928 m_aStructure[m_nCurrentStructElement].m_oType &&
10929 *m_aStructure[m_nCurrentStructElement].m_oType != vcl::pdf::StructElement::Document &&
10930 ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
10931 )
10932 {
10933 PDFStructureElement& rEle = m_aStructure[ m_nCurrentStructElement ];
10934 OStringBuffer aLine( 128 );
10935 sal_Int32 nMCID = m_aPages[ m_nCurrentPage ].m_aMCIDParents.size();
10936 aLine.append( "/" );
10937 if( !rEle.m_aAlias.isEmpty() )
10938 aLine.append( rEle.m_aAlias );
10939 else
10940 aLine.append( getStructureTag(*rEle.m_oType) );
10941 aLine.append( "<</MCID " );
10942 aLine.append( nMCID );
10943 aLine.append( ">>BDC\n" );
10944 writeBuffer( aLine );
10945
10946 // update the element's content list
10947 SAL_INFO("vcl.pdfwriter", "beginning marked content id " << nMCID << " on page object "
10948 << m_aPages[ m_nCurrentPage ].m_nPageObject << ", structure first page = "
10949 << rEle.m_nFirstPageObject);
10950 rEle.m_aKids.emplace_back(MCIDReference{m_aPages[m_nCurrentPage].m_nPageObject, nMCID});
10951 // update the page's mcid parent list
10952 m_aPages[ m_nCurrentPage ].m_aMCIDParents.push_back( rEle.m_nObject );
10953 // mark element MC sequence as open
10954 rEle.m_bOpenMCSeq = true;
10955 }
10956 // handle artifacts
10957 else if( ! m_bEmitStructure && m_aContext.Tagged &&
10958 m_nCurrentStructElement > 0 &&
10959 m_aStructure[m_nCurrentStructElement].m_oType &&
10960 *m_aStructure[m_nCurrentStructElement].m_oType == vcl::pdf::StructElement::NonStructElement &&
10961 ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
10962 )
10963 {
10964 OString aLine = "/Artifact "_ostr;
10965 writeBuffer( aLine );
10966 // emit property list if requested
10967 OStringBuffer buf;
10968 for (auto const& rAttr : m_aStructure[m_nCurrentStructElement].m_aAttributes)
10969 {
10970 appendStructureAttributeLine(rAttr.first, rAttr.second, buf, false);
10971 }
10972 if (buf.isEmpty())
10973 {
10974 writeBuffer("BMC\n");
10975 }
10976 else
10977 {
10978 writeBuffer("<<");
10979 writeBuffer(buf);
10980 writeBuffer(">> BDC\n");
10981 }
10982 // mark element MC sequence as open
10983 m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = true;
10984 }
10985 }
10986
endStructureElementMCSeq(EndMode const endMode)10987 void PDFWriterImpl::endStructureElementMCSeq(EndMode const endMode)
10988 {
10989 if (m_nCurrentStructElement > 0 // not StructTreeRoot
10990 && m_aStructure[m_nCurrentStructElement].m_oType
10991 && (m_bEmitStructure
10992 || (endMode != EndMode::OnlyStruct
10993 && m_aStructure[m_nCurrentStructElement].m_oType == vcl::pdf::StructElement::NonStructElement))
10994 && m_aStructure[m_nCurrentStructElement].m_bOpenMCSeq)
10995 {
10996 writeBuffer( "EMC\n" );
10997 m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = false;
10998 }
10999 }
11000
checkEmitStructure()11001 bool PDFWriterImpl::checkEmitStructure()
11002 {
11003 bool bEmit = false;
11004 if( m_aContext.Tagged )
11005 {
11006 bEmit = true;
11007 sal_Int32 nEle = m_nCurrentStructElement;
11008 while( nEle > 0 && o3tl::make_unsigned(nEle) < m_aStructure.size() )
11009 {
11010 if (m_aStructure[nEle].m_oType
11011 && *m_aStructure[nEle].m_oType == vcl::pdf::StructElement::NonStructElement)
11012 {
11013 bEmit = false;
11014 break;
11015 }
11016 nEle = m_aStructure[ nEle ].m_nParentElement;
11017 }
11018 }
11019 return bEmit;
11020 }
11021
ensureStructureElement()11022 sal_Int32 PDFWriterImpl::ensureStructureElement()
11023 {
11024 if( ! m_aContext.Tagged )
11025 return -1;
11026
11027 sal_Int32 nNewId = sal_Int32(m_aStructure.size());
11028
11029 // use m_nCurrentStructElement as temporary parent
11030 m_aStructure.emplace_back(nNewId, m_nCurrentStructElement, m_aPages[m_nCurrentPage].m_nPageObject);
11031
11032 m_aStructure[ m_nCurrentStructElement ].m_aChildren.push_back( nNewId );
11033 return nNewId;
11034 }
11035
initStructureElement(sal_Int32 const id,vcl::pdf::StructElement const eType,std::u16string_view const rAlias)11036 void PDFWriterImpl::initStructureElement(sal_Int32 const id,
11037 vcl::pdf::StructElement const eType, std::u16string_view const rAlias)
11038 {
11039 if( ! m_aContext.Tagged )
11040 return;
11041
11042 if( m_nCurrentStructElement == 0 &&
11043 eType != vcl::pdf::StructElement::Document && eType != vcl::pdf::StructElement::NonStructElement )
11044 {
11045 // struct tree root hit, but not beginning document
11046 // this might happen with setCurrentStructureElement
11047 // silently insert structure into document again if one properly exists
11048 if( ! m_aStructure[ 0 ].m_aChildren.empty() )
11049 {
11050 const std::vector< sal_Int32 >& rRootChildren = m_aStructure[0].m_aChildren;
11051 auto it = std::find_if(rRootChildren.begin(), rRootChildren.end(),
11052 [&](sal_Int32 nElement) {
11053 return m_aStructure[nElement].m_oType
11054 && *m_aStructure[nElement].m_oType == vcl::pdf::StructElement::Document; });
11055 if( it != rRootChildren.end() )
11056 {
11057 m_nCurrentStructElement = *it;
11058 SAL_WARN( "vcl.pdfwriter", "Structure element inserted to StructTreeRoot that is not a document" );
11059 }
11060 else {
11061 OSL_FAIL( "document structure in disorder !" );
11062 }
11063 }
11064 else {
11065 OSL_FAIL( "PDF document structure MUST be contained in a Document element" );
11066 }
11067 }
11068
11069 PDFStructureElement& rEle = m_aStructure[id];
11070 assert(!rEle.m_oType);
11071 rEle.m_oType.emplace(eType);
11072 // remove it from its possibly placeholder parent; append to real parent
11073 auto const it(std::find(m_aStructure[rEle.m_nParentElement].m_aChildren.begin(),
11074 m_aStructure[rEle.m_nParentElement].m_aChildren.end(), id));
11075 assert(it != m_aStructure[rEle.m_nParentElement].m_aChildren.end());
11076 m_aStructure[rEle.m_nParentElement].m_aChildren.erase(it);
11077 rEle.m_nParentElement = m_nCurrentStructElement;
11078 rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject;
11079 m_aStructure[m_nCurrentStructElement].m_aChildren.push_back(id);
11080
11081 // handle alias names
11082 if( !rAlias.empty() && eType != vcl::pdf::StructElement::NonStructElement )
11083 {
11084 OStringBuffer aNameBuf( rAlias.size() );
11085 COSWriter::appendName( rAlias, aNameBuf );
11086 OString aAliasName( aNameBuf.makeStringAndClear() );
11087 rEle.m_aAlias = aAliasName;
11088 addRoleMap(aAliasName, eType);
11089 }
11090
11091 if (m_bEmitStructure && eType != vcl::pdf::StructElement::NonStructElement) // don't create nonexistent objects
11092 {
11093 rEle.m_nObject = createObject();
11094 // update parent's kids list
11095 m_aStructure[ rEle.m_nParentElement ].m_aKids.emplace_back(ObjReference{rEle.m_nObject});
11096 // ISO 14289-1:2014, Clause: 7.9
11097 if (*rEle.m_oType == vcl::pdf::StructElement::Note)
11098 {
11099 m_StructElemObjsWithID.insert(rEle.m_nObject);
11100 }
11101 }
11102 }
11103
beginStructureElement(sal_Int32 const id)11104 void PDFWriterImpl::beginStructureElement(sal_Int32 const id)
11105 {
11106 if( m_nCurrentPage < 0 )
11107 return;
11108
11109 if( ! m_aContext.Tagged )
11110 return;
11111
11112 assert(id != -1 && "cid#1538888 doesn't consider above m_aContext.Tagged");
11113
11114 // close eventual current MC sequence
11115 endStructureElementMCSeq(EndMode::OnlyStruct);
11116
11117 PDFStructureElement& rEle = m_aStructure[id];
11118 m_StructElementStack.push(m_nCurrentStructElement);
11119 m_nCurrentStructElement = id;
11120
11121 if (g_bDebugDisableCompression)
11122 {
11123 OStringBuffer aLine( "beginStructureElement " );
11124 aLine.append( m_nCurrentStructElement );
11125 aLine.append( ": " );
11126 aLine.append( rEle.m_oType
11127 ? getStructureTag(*rEle.m_oType)
11128 : "<placeholder>" );
11129 if( !rEle.m_aAlias.isEmpty() )
11130 {
11131 aLine.append( " aliased as \"" );
11132 aLine.append( rEle.m_aAlias );
11133 aLine.append( '\"' );
11134 }
11135 emitComment( aLine.getStr() );
11136 }
11137
11138 // check whether to emit structure henceforth
11139 m_bEmitStructure = checkEmitStructure();
11140 }
11141
endStructureElement()11142 void PDFWriterImpl::endStructureElement()
11143 {
11144 if( m_nCurrentPage < 0 )
11145 return;
11146
11147 if( ! m_aContext.Tagged )
11148 return;
11149
11150 if( m_nCurrentStructElement == 0 )
11151 {
11152 // hit the struct tree root, that means there is an endStructureElement
11153 // without corresponding beginStructureElement
11154 return;
11155 }
11156
11157 // end the marked content sequence
11158 endStructureElementMCSeq();
11159
11160 OStringBuffer aLine;
11161 if (g_bDebugDisableCompression)
11162 {
11163 aLine.append( "endStructureElement " );
11164 aLine.append( m_nCurrentStructElement );
11165 aLine.append( ": " );
11166 aLine.append( m_aStructure[m_nCurrentStructElement].m_oType
11167 ? getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
11168 : "<placeholder>" );
11169 if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
11170 {
11171 aLine.append( " aliased as \"" );
11172 aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
11173 aLine.append( '\"' );
11174 }
11175 }
11176
11177 // "end" the structure element, the parent becomes current element
11178 m_nCurrentStructElement = m_StructElementStack.top();
11179 m_StructElementStack.pop();
11180
11181 // check whether to emit structure henceforth
11182 m_bEmitStructure = checkEmitStructure();
11183
11184 if (g_bDebugDisableCompression && m_bEmitStructure)
11185 {
11186 emitComment( aLine.getStr() );
11187 }
11188 }
11189
11190 namespace {
11191
removePlaceholderSEImpl(std::vector<PDFStructureElement> & rStructure,std::vector<sal_Int32>::iterator & rParentIt)11192 void removePlaceholderSEImpl(std::vector<PDFStructureElement> & rStructure,
11193 std::vector<sal_Int32>::iterator & rParentIt)
11194 {
11195 PDFStructureElement& rEle(rStructure[*rParentIt]);
11196 removePlaceholderSE(rStructure, rEle);
11197
11198 if (!rEle.m_oType)
11199 {
11200 // Placeholder was not initialised - should not happen when printing
11201 // a full page, but might if a selection is printed, which can be only
11202 // a shape without its anchor.
11203 // Handle this by moving the children to the parent SE.
11204 PDFStructureElement & rParent(rStructure[rEle.m_nParentElement]);
11205 rParentIt = rParent.m_aChildren.erase(rParentIt);
11206 std::vector<sal_Int32> children;
11207 for (auto const child : rEle.m_aChildren)
11208 {
11209 PDFStructureElement& rChild = rStructure[child];
11210 rChild.m_nParentElement = rEle.m_nParentElement;
11211 children.push_back(rChild.m_nOwnElement);
11212 }
11213 rParentIt = rParent.m_aChildren.insert(rParentIt, children.begin(), children.end())
11214 + children.size();
11215 }
11216 else
11217 {
11218 ++rParentIt;
11219 }
11220
11221 }
11222
removePlaceholderSE(std::vector<PDFStructureElement> & rStructure,PDFStructureElement & rEle)11223 void removePlaceholderSE(std::vector<PDFStructureElement> & rStructure, PDFStructureElement& rEle)
11224 {
11225 for (auto it = rEle.m_aChildren.begin(); it != rEle.m_aChildren.end(); )
11226 {
11227 removePlaceholderSEImpl(rStructure, it);
11228 }
11229 }
11230
11231 } // end anonymous namespace
11232
11233 /*
11234 * This function adds an internal structure list container to overcome the 8191 elements array limitation
11235 * in kids element emission.
11236 * Recursive function
11237 *
11238 */
addInternalStructureContainer(PDFStructureElement & rEle)11239 void PDFWriterImpl::addInternalStructureContainer( PDFStructureElement& rEle )
11240 {
11241 if (rEle.m_nOwnElement != rEle.m_nParentElement
11242 && *rEle.m_oType == vcl::pdf::StructElement::NonStructElement)
11243 {
11244 return;
11245 }
11246
11247 for (auto const& child : rEle.m_aChildren)
11248 {
11249 assert(child > 0 && o3tl::make_unsigned(child) < m_aStructure.size());
11250 if( child > 0 && o3tl::make_unsigned(child) < m_aStructure.size() )
11251 {
11252 PDFStructureElement& rChild = m_aStructure[ child ];
11253 if (*rChild.m_oType != vcl::pdf::StructElement::NonStructElement)
11254 {
11255 //triggered when a child of the rEle element is found
11256 assert(rChild.m_nParentElement == rEle.m_nOwnElement);
11257 if( rChild.m_nParentElement == rEle.m_nOwnElement )
11258 addInternalStructureContainer( rChild );//examine the child
11259 else
11260 {
11261 OSL_FAIL( "PDFWriterImpl::addInternalStructureContainer: invalid child structure element" );
11262 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure element with id " << child );
11263 }
11264 }
11265 }
11266 else
11267 {
11268 OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
11269 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure id " << child );
11270 }
11271 }
11272
11273 if( rEle.m_nOwnElement == rEle.m_nParentElement )
11274 return;
11275
11276 if( rEle.m_aKids.empty() )
11277 return;
11278
11279 if( rEle.m_aKids.size() <= ncMaxPDFArraySize ) return;
11280
11281 //then we need to add the containers for the kids elements
11282 // a list to be used for the new kid element
11283 std::list< PDFStructureElementKid > aNewKids;
11284 std::vector< sal_Int32 > aNewChildren;
11285
11286 // add Div in RoleMap, in case no one else did (TODO: is it needed? Is it dangerous?)
11287 OString aAliasName("Div"_ostr);
11288 addRoleMap(aAliasName, vcl::pdf::StructElement::Division);
11289
11290 while( rEle.m_aKids.size() > ncMaxPDFArraySize )
11291 {
11292 sal_Int32 nCurrentStructElement = rEle.m_nOwnElement;
11293 sal_Int32 nNewId = sal_Int32(m_aStructure.size());
11294 m_aStructure.emplace_back( );
11295 PDFStructureElement& rEleNew = m_aStructure.back();
11296 rEleNew.m_aAlias = aAliasName;
11297 rEleNew.m_oType.emplace(vcl::pdf::StructElement::Division); // a new Div type container
11298 rEleNew.m_nOwnElement = nNewId;
11299 rEleNew.m_nParentElement = nCurrentStructElement;
11300 //inherit the same page as the first child to be reparented
11301 rEleNew.m_nFirstPageObject = m_aStructure[ rEle.m_aChildren.front() ].m_nFirstPageObject;
11302 rEleNew.m_nObject = createObject();//assign a PDF object number
11303 //add the object to the kid list of the parent
11304 aNewKids.emplace_back(ObjReference{rEleNew.m_nObject});
11305 aNewChildren.push_back( nNewId );
11306
11307 std::vector< sal_Int32 >::iterator aChildEndIt( rEle.m_aChildren.begin() );
11308 std::list< PDFStructureElementKid >::iterator aKidEndIt( rEle.m_aKids.begin() );
11309 advance( aChildEndIt, ncMaxPDFArraySize );
11310 advance( aKidEndIt, ncMaxPDFArraySize );
11311
11312 rEleNew.m_aKids.splice( rEleNew.m_aKids.begin(),
11313 rEle.m_aKids,
11314 rEle.m_aKids.begin(),
11315 aKidEndIt );
11316 rEleNew.m_aChildren.insert( rEleNew.m_aChildren.begin(),
11317 rEle.m_aChildren.begin(),
11318 aChildEndIt );
11319 rEle.m_aChildren.erase( rEle.m_aChildren.begin(), aChildEndIt );
11320
11321 // set the kid's new parent
11322 for (auto const& child : rEleNew.m_aChildren)
11323 {
11324 m_aStructure[ child ].m_nParentElement = nNewId;
11325 }
11326 }
11327 //finally add the new kids resulting from the container added
11328 rEle.m_aKids.insert( rEle.m_aKids.begin(), aNewKids.begin(), aNewKids.end() );
11329 rEle.m_aChildren.insert( rEle.m_aChildren.begin(), aNewChildren.begin(), aNewChildren.end() );
11330 }
11331
setCurrentStructureElement(sal_Int32 nEle)11332 bool PDFWriterImpl::setCurrentStructureElement( sal_Int32 nEle )
11333 {
11334 bool bSuccess = false;
11335
11336 if( m_aContext.Tagged && nEle >= 0 && o3tl::make_unsigned(nEle) < m_aStructure.size() )
11337 {
11338 // end eventual previous marked content sequence
11339 endStructureElementMCSeq();
11340
11341 m_nCurrentStructElement = nEle;
11342 m_bEmitStructure = checkEmitStructure();
11343 if (g_bDebugDisableCompression)
11344 {
11345 OStringBuffer aLine( "setCurrentStructureElement " );
11346 aLine.append( m_nCurrentStructElement );
11347 aLine.append( ": " );
11348 aLine.append( m_aStructure[m_nCurrentStructElement].m_oType
11349 ? getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
11350 : "<placeholder>" );
11351 if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
11352 {
11353 aLine.append( " aliased as \"" );
11354 aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
11355 aLine.append( '\"' );
11356 }
11357 if( ! m_bEmitStructure )
11358 aLine.append( " (inside NonStruct)" );
11359 emitComment( aLine.getStr() );
11360 }
11361 bSuccess = true;
11362 }
11363
11364 return bSuccess;
11365 }
11366
setStructureAttribute(enum PDFWriter::StructAttribute eAttr,enum PDFWriter::StructAttributeValue eVal)11367 bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr, enum PDFWriter::StructAttributeValue eVal )
11368 {
11369 if( !m_aContext.Tagged )
11370 return false;
11371
11372 assert(m_aStructure[m_nCurrentStructElement].m_oType);
11373 bool bInsert = false;
11374 if (m_nCurrentStructElement > 0
11375 && (m_bEmitStructure
11376 // allow it for topmost non-structured element
11377 || (m_aContext.Tagged
11378 && (0 == m_aStructure[m_nCurrentStructElement].m_nParentElement
11379 || !m_aStructure[m_aStructure[m_nCurrentStructElement].m_nParentElement].m_oType
11380 || *m_aStructure[m_aStructure[m_nCurrentStructElement].m_nParentElement].m_oType != vcl::pdf::StructElement::NonStructElement))))
11381 {
11382 vcl::pdf::StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType;
11383 switch( eAttr )
11384 {
11385 case PDFWriter::Placement:
11386 if( eVal == PDFWriter::Block ||
11387 eVal == PDFWriter::Inline ||
11388 eVal == PDFWriter::Before ||
11389 eVal == PDFWriter::Start ||
11390 eVal == PDFWriter::End )
11391 bInsert = true;
11392 break;
11393 case PDFWriter::WritingMode:
11394 if( eVal == PDFWriter::LrTb ||
11395 eVal == PDFWriter::RlTb ||
11396 eVal == PDFWriter::TbRl )
11397 {
11398 bInsert = true;
11399 }
11400 break;
11401 case PDFWriter::TextAlign:
11402 if( eVal == PDFWriter::Start ||
11403 eVal == PDFWriter::Center ||
11404 eVal == PDFWriter::End ||
11405 eVal == PDFWriter::Justify )
11406 {
11407 if (eType == vcl::pdf::StructElement::Paragraph ||
11408 eType == vcl::pdf::StructElement::Title ||
11409 eType == vcl::pdf::StructElement::Heading ||
11410 eType == vcl::pdf::StructElement::H1 ||
11411 eType == vcl::pdf::StructElement::H2 ||
11412 eType == vcl::pdf::StructElement::H3 ||
11413 eType == vcl::pdf::StructElement::H4 ||
11414 eType == vcl::pdf::StructElement::H5 ||
11415 eType == vcl::pdf::StructElement::H6 ||
11416 eType == vcl::pdf::StructElement::List ||
11417 eType == vcl::pdf::StructElement::ListItem ||
11418 eType == vcl::pdf::StructElement::LILabel ||
11419 eType == vcl::pdf::StructElement::LIBody ||
11420 eType == vcl::pdf::StructElement::Table ||
11421 eType == vcl::pdf::StructElement::TableRow ||
11422 eType == vcl::pdf::StructElement::TableHeader ||
11423 eType == vcl::pdf::StructElement::TableData)
11424 {
11425 bInsert = true;
11426 }
11427 }
11428 break;
11429 case PDFWriter::Width:
11430 case PDFWriter::Height:
11431 if( eVal == PDFWriter::Auto )
11432 {
11433 if (eType == vcl::pdf::StructElement::Figure ||
11434 eType == vcl::pdf::StructElement::Formula ||
11435 eType == vcl::pdf::StructElement::Form ||
11436 eType == vcl::pdf::StructElement::Table ||
11437 eType == vcl::pdf::StructElement::TableHeader ||
11438 eType == vcl::pdf::StructElement::TableData)
11439 {
11440 bInsert = true;
11441 }
11442 }
11443 break;
11444 case PDFWriter::BlockAlign:
11445 if( eVal == PDFWriter::Before ||
11446 eVal == PDFWriter::Middle ||
11447 eVal == PDFWriter::After ||
11448 eVal == PDFWriter::Justify )
11449 {
11450 if (eType == vcl::pdf::StructElement::TableHeader ||
11451 eType == vcl::pdf::StructElement::TableData)
11452 {
11453 bInsert = true;
11454 }
11455 }
11456 break;
11457 case PDFWriter::InlineAlign:
11458 if( eVal == PDFWriter::Start ||
11459 eVal == PDFWriter::Center ||
11460 eVal == PDFWriter::End )
11461 {
11462 if (eType == vcl::pdf::StructElement::TableHeader ||
11463 eType == vcl::pdf::StructElement::TableData)
11464 {
11465 bInsert = true;
11466 }
11467 }
11468 break;
11469 case PDFWriter::LineHeight:
11470 if( eVal == PDFWriter::Normal ||
11471 eVal == PDFWriter::Auto )
11472 {
11473 // only for ILSE and BLSE
11474 if (eType == vcl::pdf::StructElement::Paragraph ||
11475 eType == vcl::pdf::StructElement::Title ||
11476 eType == vcl::pdf::StructElement::Heading ||
11477 eType == vcl::pdf::StructElement::H1 ||
11478 eType == vcl::pdf::StructElement::H2 ||
11479 eType == vcl::pdf::StructElement::H3 ||
11480 eType == vcl::pdf::StructElement::H4 ||
11481 eType == vcl::pdf::StructElement::H5 ||
11482 eType == vcl::pdf::StructElement::H6 ||
11483 eType == vcl::pdf::StructElement::List ||
11484 eType == vcl::pdf::StructElement::ListItem ||
11485 eType == vcl::pdf::StructElement::LILabel ||
11486 eType == vcl::pdf::StructElement::LIBody ||
11487 eType == vcl::pdf::StructElement::Table ||
11488 eType == vcl::pdf::StructElement::TableRow ||
11489 eType == vcl::pdf::StructElement::TableHeader ||
11490 eType == vcl::pdf::StructElement::TableData ||
11491 eType == vcl::pdf::StructElement::Span ||
11492 eType == vcl::pdf::StructElement::Quote ||
11493 eType == vcl::pdf::StructElement::Emphasis ||
11494 eType == vcl::pdf::StructElement::Strong ||
11495 eType == vcl::pdf::StructElement::Note ||
11496 eType == vcl::pdf::StructElement::Reference ||
11497 eType == vcl::pdf::StructElement::BibEntry ||
11498 eType == vcl::pdf::StructElement::Code ||
11499 eType == vcl::pdf::StructElement::Link)
11500 {
11501 bInsert = true;
11502 }
11503 }
11504 break;
11505 case PDFWriter::TextDecorationType:
11506 if( eVal == PDFWriter::NONE ||
11507 eVal == PDFWriter::Underline ||
11508 eVal == PDFWriter::Overline ||
11509 eVal == PDFWriter::LineThrough )
11510 {
11511 // only for ILSE and BLSE
11512 if (eType == vcl::pdf::StructElement::Paragraph ||
11513 eType == vcl::pdf::StructElement::Title ||
11514 eType == vcl::pdf::StructElement::Heading ||
11515 eType == vcl::pdf::StructElement::H1 ||
11516 eType == vcl::pdf::StructElement::H2 ||
11517 eType == vcl::pdf::StructElement::H3 ||
11518 eType == vcl::pdf::StructElement::H4 ||
11519 eType == vcl::pdf::StructElement::H5 ||
11520 eType == vcl::pdf::StructElement::H6 ||
11521 eType == vcl::pdf::StructElement::List ||
11522 eType == vcl::pdf::StructElement::ListItem ||
11523 eType == vcl::pdf::StructElement::LILabel ||
11524 eType == vcl::pdf::StructElement::LIBody ||
11525 eType == vcl::pdf::StructElement::Table ||
11526 eType == vcl::pdf::StructElement::TableRow ||
11527 eType == vcl::pdf::StructElement::TableHeader ||
11528 eType == vcl::pdf::StructElement::TableData ||
11529 eType == vcl::pdf::StructElement::Span ||
11530 eType == vcl::pdf::StructElement::Quote ||
11531 eType == vcl::pdf::StructElement::Emphasis ||
11532 eType == vcl::pdf::StructElement::Strong ||
11533 eType == vcl::pdf::StructElement::Note ||
11534 eType == vcl::pdf::StructElement::Reference ||
11535 eType == vcl::pdf::StructElement::BibEntry ||
11536 eType == vcl::pdf::StructElement::Code ||
11537 eType == vcl::pdf::StructElement::Link)
11538 {
11539 bInsert = true;
11540 }
11541 }
11542 break;
11543 case PDFWriter::Scope:
11544 if (eVal == PDFWriter::Row || eVal == PDFWriter::Column || eVal == PDFWriter::Both)
11545 {
11546 if (eType == vcl::pdf::StructElement::TableHeader
11547 && PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version)
11548 {
11549 bInsert = true;
11550 }
11551 }
11552 break;
11553 case PDFWriter::Type:
11554 if (eVal == PDFWriter::Pagination || eVal == PDFWriter::Layout || eVal == PDFWriter::Page)
11555 // + Background for PDF >= 1.7
11556 {
11557 if (eType == vcl::pdf::StructElement::NonStructElement)
11558 {
11559 bInsert = true;
11560 }
11561 }
11562 break;
11563 case PDFWriter::Subtype:
11564 if (eVal == PDFWriter::Header || eVal == PDFWriter::Footer || eVal == PDFWriter::Watermark)
11565 {
11566 if (eType == vcl::pdf::StructElement::NonStructElement
11567 && PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
11568 {
11569 bInsert = true;
11570 }
11571 }
11572 break;
11573 case PDFWriter::Role:
11574 if (eVal == PDFWriter::Rb || eVal == PDFWriter::Cb || eVal == PDFWriter::Pb || eVal == PDFWriter::Tv)
11575 {
11576 if (eType == vcl::pdf::StructElement::Form
11577 && PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
11578 {
11579 bInsert = true;
11580 }
11581 }
11582 break;
11583 case PDFWriter::RubyAlign:
11584 if (eVal == PDFWriter::RStart || eVal == PDFWriter::RCenter || eVal == PDFWriter::REnd || eVal == PDFWriter::RJustify || eVal == PDFWriter::RDistribute)
11585 {
11586 if (eType == vcl::pdf::StructElement::RT
11587 && PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version)
11588 {
11589 bInsert = true;
11590 }
11591 }
11592 break;
11593 case PDFWriter::RubyPosition:
11594 if (eVal == PDFWriter::RBefore || eVal == PDFWriter::RAfter || eVal == PDFWriter::RWarichu || eVal == PDFWriter::RInline)
11595 {
11596 if (eType == vcl::pdf::StructElement::RT
11597 && PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version)
11598 {
11599 bInsert = true;
11600 }
11601 }
11602 break;
11603 case PDFWriter::ListNumbering:
11604 if( eVal == PDFWriter::NONE ||
11605 eVal == PDFWriter::Disc ||
11606 eVal == PDFWriter::Circle ||
11607 eVal == PDFWriter::Square ||
11608 eVal == PDFWriter::Decimal ||
11609 eVal == PDFWriter::UpperRoman ||
11610 eVal == PDFWriter::LowerRoman ||
11611 eVal == PDFWriter::UpperAlpha ||
11612 eVal == PDFWriter::LowerAlpha )
11613 {
11614 if( eType == vcl::pdf::StructElement::List )
11615 bInsert = true;
11616 }
11617 break;
11618 default: break;
11619 }
11620 }
11621
11622 if( bInsert )
11623 m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( eVal );
11624 else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
11625 SAL_INFO("vcl.pdfwriter",
11626 "rejecting setStructureAttribute( " << getAttributeTag( eAttr )
11627 << ", " << getAttributeValueTag( eVal )
11628 << " ) on " << getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
11629 << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias
11630 << ") element");
11631
11632 return bInsert;
11633 }
11634
setStructureAttributeNumerical(enum PDFWriter::StructAttribute eAttr,sal_Int32 nValue)11635 bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttribute eAttr, sal_Int32 nValue )
11636 {
11637 if( ! m_aContext.Tagged )
11638 return false;
11639
11640 assert(m_aStructure[m_nCurrentStructElement].m_oType);
11641 bool bInsert = false;
11642 if( m_nCurrentStructElement > 0 && m_bEmitStructure )
11643 {
11644 if( eAttr == PDFWriter::Language )
11645 {
11646 m_aStructure[ m_nCurrentStructElement ].m_aLocale = LanguageTag( LanguageType(nValue) ).getLocale();
11647 return true;
11648 }
11649
11650 vcl::pdf::StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType;
11651 switch( eAttr )
11652 {
11653 case PDFWriter::SpaceBefore:
11654 case PDFWriter::SpaceAfter:
11655 case PDFWriter::StartIndent:
11656 case PDFWriter::EndIndent:
11657 // just for BLSE
11658 if (eType == vcl::pdf::StructElement::Paragraph ||
11659 eType == vcl::pdf::StructElement::Title ||
11660 eType == vcl::pdf::StructElement::Heading ||
11661 eType == vcl::pdf::StructElement::H1 ||
11662 eType == vcl::pdf::StructElement::H2 ||
11663 eType == vcl::pdf::StructElement::H3 ||
11664 eType == vcl::pdf::StructElement::H4 ||
11665 eType == vcl::pdf::StructElement::H5 ||
11666 eType == vcl::pdf::StructElement::H6 ||
11667 eType == vcl::pdf::StructElement::List ||
11668 eType == vcl::pdf::StructElement::ListItem ||
11669 eType == vcl::pdf::StructElement::LILabel ||
11670 eType == vcl::pdf::StructElement::LIBody ||
11671 eType == vcl::pdf::StructElement::Table ||
11672 eType == vcl::pdf::StructElement::TableRow ||
11673 eType == vcl::pdf::StructElement::TableHeader ||
11674 eType == vcl::pdf::StructElement::TableData)
11675 {
11676 bInsert = true;
11677 }
11678 break;
11679 case PDFWriter::TextIndent:
11680 // paragraph like BLSE and additional elements
11681 if (eType == vcl::pdf::StructElement::Paragraph ||
11682 eType == vcl::pdf::StructElement::Title ||
11683 eType == vcl::pdf::StructElement::Heading ||
11684 eType == vcl::pdf::StructElement::H1 ||
11685 eType == vcl::pdf::StructElement::H2 ||
11686 eType == vcl::pdf::StructElement::H3 ||
11687 eType == vcl::pdf::StructElement::H4 ||
11688 eType == vcl::pdf::StructElement::H5 ||
11689 eType == vcl::pdf::StructElement::H6 ||
11690 eType == vcl::pdf::StructElement::LILabel ||
11691 eType == vcl::pdf::StructElement::LIBody ||
11692 eType == vcl::pdf::StructElement::TableHeader ||
11693 eType == vcl::pdf::StructElement::TableData)
11694 {
11695 bInsert = true;
11696 }
11697 break;
11698 case PDFWriter::Width:
11699 case PDFWriter::Height:
11700 if (eType == vcl::pdf::StructElement::Figure ||
11701 eType == vcl::pdf::StructElement::Formula ||
11702 eType == vcl::pdf::StructElement::Form ||
11703 eType == vcl::pdf::StructElement::Table ||
11704 eType == vcl::pdf::StructElement::TableHeader ||
11705 eType == vcl::pdf::StructElement::TableData)
11706 {
11707 bInsert = true;
11708 }
11709 break;
11710 case PDFWriter::LineHeight:
11711 case PDFWriter::BaselineShift:
11712 // only for ILSE and BLSE
11713 if (eType == vcl::pdf::StructElement::Paragraph ||
11714 eType == vcl::pdf::StructElement::Title ||
11715 eType == vcl::pdf::StructElement::Heading ||
11716 eType == vcl::pdf::StructElement::H1 ||
11717 eType == vcl::pdf::StructElement::H2 ||
11718 eType == vcl::pdf::StructElement::H3 ||
11719 eType == vcl::pdf::StructElement::H4 ||
11720 eType == vcl::pdf::StructElement::H5 ||
11721 eType == vcl::pdf::StructElement::H6 ||
11722 eType == vcl::pdf::StructElement::List ||
11723 eType == vcl::pdf::StructElement::ListItem ||
11724 eType == vcl::pdf::StructElement::LILabel ||
11725 eType == vcl::pdf::StructElement::LIBody ||
11726 eType == vcl::pdf::StructElement::Table ||
11727 eType == vcl::pdf::StructElement::TableRow ||
11728 eType == vcl::pdf::StructElement::TableHeader ||
11729 eType == vcl::pdf::StructElement::TableData ||
11730 eType == vcl::pdf::StructElement::Span ||
11731 eType == vcl::pdf::StructElement::Quote ||
11732 eType == vcl::pdf::StructElement::Emphasis ||
11733 eType == vcl::pdf::StructElement::Strong ||
11734 eType == vcl::pdf::StructElement::Note ||
11735 eType == vcl::pdf::StructElement::Reference ||
11736 eType == vcl::pdf::StructElement::BibEntry ||
11737 eType == vcl::pdf::StructElement::Code ||
11738 eType == vcl::pdf::StructElement::Link)
11739 {
11740 bInsert = true;
11741 }
11742 break;
11743 case PDFWriter::RowSpan:
11744 case PDFWriter::ColSpan:
11745 // only for table cells
11746 if (eType == vcl::pdf::StructElement::TableHeader ||
11747 eType == vcl::pdf::StructElement::TableData)
11748 {
11749 bInsert = true;
11750 }
11751 break;
11752 case PDFWriter::LinkAnnotation:
11753 if (eType == vcl::pdf::StructElement::Link)
11754 bInsert = true;
11755 break;
11756 case PDFWriter::NoteAnnotation:
11757 if (eType == vcl::pdf::StructElement::Annot)
11758 bInsert = true;
11759 break;
11760 default: break;
11761 }
11762 }
11763
11764 if( bInsert )
11765 m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( nValue );
11766 else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
11767 SAL_INFO("vcl.pdfwriter",
11768 "rejecting setStructureAttributeNumerical( " << getAttributeTag( eAttr )
11769 << ", " << static_cast<int>(nValue)
11770 << " ) on " << getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
11771 << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias
11772 << ") element");
11773
11774 return bInsert;
11775 }
11776
setStructureBoundingBox(const tools::Rectangle & rRect)11777 void PDFWriterImpl::setStructureBoundingBox( const tools::Rectangle& rRect )
11778 {
11779 sal_Int32 nPageNr = m_nCurrentPage;
11780 if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() || !m_aContext.Tagged )
11781 return;
11782
11783 if( !(m_nCurrentStructElement > 0 && m_bEmitStructure) )
11784 return;
11785
11786 assert(m_aStructure[m_nCurrentStructElement].m_oType);
11787 vcl::pdf::StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType;
11788 if (eType == vcl::pdf::StructElement::Figure ||
11789 eType == vcl::pdf::StructElement::Formula ||
11790 eType == vcl::pdf::StructElement::Form ||
11791 eType == vcl::pdf::StructElement::Division ||
11792 eType == vcl::pdf::StructElement::Table)
11793 {
11794 m_aStructure[ m_nCurrentStructElement ].m_aBBox = rRect;
11795 // convert to default user space now, since the mapmode may change
11796 m_aPages[nPageNr].convertRect( m_aStructure[ m_nCurrentStructElement ].m_aBBox );
11797 }
11798 }
11799
setStructureAnnotIds(::std::vector<sal_Int32> const & rAnnotIds)11800 void PDFWriterImpl::setStructureAnnotIds(::std::vector<sal_Int32> const& rAnnotIds)
11801 {
11802 assert(!(m_nCurrentPage < 0 || m_aPages.size() <= o3tl::make_unsigned(m_nCurrentPage)));
11803
11804 if (!m_aContext.Tagged || m_nCurrentStructElement <= 0 || !m_bEmitStructure)
11805 {
11806 return;
11807 }
11808
11809 m_aStructure[m_nCurrentStructElement].m_AnnotIds = rAnnotIds;
11810 }
11811
setActualText(const OUString & rText)11812 void PDFWriterImpl::setActualText( const OUString& rText )
11813 {
11814 if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
11815 {
11816 m_aStructure[ m_nCurrentStructElement ].m_aActualText = rText;
11817 }
11818 }
11819
setAlternateText(const OUString & rText)11820 void PDFWriterImpl::setAlternateText( const OUString& rText )
11821 {
11822 if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
11823 {
11824 m_aStructure[ m_nCurrentStructElement ].m_aAltText = rText;
11825 }
11826 }
11827
setPageTransition(PDFWriter::PageTransition eType,sal_uInt32 nMilliSec,sal_Int32 nPageNr)11828 void PDFWriterImpl::setPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr )
11829 {
11830 if( nPageNr < 0 )
11831 nPageNr = m_nCurrentPage;
11832
11833 if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
11834 return;
11835
11836 m_aPages[ nPageNr ].m_eTransition = eType;
11837 m_aPages[ nPageNr ].m_nTransTime = nMilliSec;
11838 }
11839
ensureUniqueRadioOnValues()11840 void PDFWriterImpl::ensureUniqueRadioOnValues()
11841 {
11842 // loop over radio groups
11843 for (auto const& group : m_aRadioGroupWidgets)
11844 {
11845 PDFWidget& rGroupWidget = m_aWidgets[ group.second ];
11846 // check whether all kids have a unique OnValue
11847 std::unordered_map< OUString, sal_Int32 > aOnValues;
11848 bool bIsUnique = true;
11849 for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11850 {
11851 const OUString& rVal = m_aWidgets[nKidIndex].m_aOnValue;
11852 SAL_INFO("vcl.pdfwriter", "OnValue: " << rVal);
11853 if( aOnValues.find( rVal ) == aOnValues.end() )
11854 {
11855 aOnValues[ rVal ] = 1;
11856 }
11857 else
11858 {
11859 bIsUnique = false;
11860 break;
11861 }
11862 }
11863 if( ! bIsUnique )
11864 {
11865 SAL_INFO("vcl.pdfwriter", "enforcing unique OnValues" );
11866 // make unique by using ascending OnValues
11867 int nKid = 0;
11868 for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11869 {
11870 PDFWidget& rKid = m_aWidgets[nKidIndex];
11871 rKid.m_aOnValue = OUString::number( nKid+1 );
11872 if( rKid.m_aValue != "Off" )
11873 rKid.m_aValue = rKid.m_aOnValue;
11874 ++nKid;
11875 }
11876 }
11877 // finally move the "Yes" appearance to the OnValue appearance
11878 for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11879 {
11880 PDFWidget& rKid = m_aWidgets[nKidIndex];
11881 if ( !rKid.m_aOnValue.isEmpty() )
11882 {
11883 auto app_it = rKid.m_aAppearances.find( "N"_ostr );
11884 if( app_it != rKid.m_aAppearances.end() )
11885 {
11886 auto stream_it = app_it->second.find( "Yes"_ostr );
11887 if( stream_it != app_it->second.end() )
11888 {
11889 SvMemoryStream* pStream = stream_it->second;
11890 app_it->second.erase( stream_it );
11891 OStringBuffer aBuf( rKid.m_aOnValue.getLength()*2 );
11892 COSWriter::appendName( rKid.m_aOnValue, aBuf );
11893 (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
11894 }
11895 else
11896 SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Yes\" stream" );
11897 }
11898 }
11899
11900 if ( !rKid.m_aOffValue.isEmpty() )
11901 {
11902 auto app_it = rKid.m_aAppearances.find( "N"_ostr );
11903 if( app_it != rKid.m_aAppearances.end() )
11904 {
11905 auto stream_it = app_it->second.find( "Off"_ostr );
11906 if( stream_it != app_it->second.end() )
11907 {
11908 SvMemoryStream* pStream = stream_it->second;
11909 app_it->second.erase( stream_it );
11910 OStringBuffer aBuf( rKid.m_aOffValue.getLength()*2 );
11911 COSWriter::appendName( rKid.m_aOffValue, aBuf );
11912 (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
11913 }
11914 else
11915 SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Off\" stream" );
11916 }
11917 }
11918
11919 // update selected radio button
11920 if( rKid.m_aValue != "Off" )
11921 {
11922 rGroupWidget.m_aValue = rKid.m_aValue;
11923 }
11924 }
11925 }
11926 }
11927
findRadioGroupWidget(const PDFWriter::RadioButtonWidget & rBtn)11928 sal_Int32 PDFWriterImpl::findRadioGroupWidget( const PDFWriter::RadioButtonWidget& rBtn )
11929 {
11930 sal_Int32 nRadioGroupWidget = -1;
11931
11932 std::map< sal_Int32, sal_Int32 >::const_iterator it = m_aRadioGroupWidgets.find( rBtn.RadioGroup );
11933
11934 if( it == m_aRadioGroupWidgets.end() )
11935 {
11936 m_aRadioGroupWidgets[ rBtn.RadioGroup ] = nRadioGroupWidget =
11937 sal_Int32(m_aWidgets.size());
11938
11939 // new group, insert the radiobutton
11940 m_aWidgets.emplace_back( );
11941 m_aWidgets.back().m_nObject = createObject();
11942 m_aWidgets.back().m_nPage = m_nCurrentPage;
11943 m_aWidgets.back().m_eType = PDFWriter::RadioButton;
11944 m_aWidgets.back().m_nRadioGroup = rBtn.RadioGroup;
11945 m_aWidgets.back().m_nFlags |= 0x0000C000; // NoToggleToOff and Radio bits
11946
11947 createWidgetFieldName( sal_Int32(m_aWidgets.size()-1), rBtn );
11948 }
11949 else
11950 nRadioGroupWidget = it->second;
11951
11952 return nRadioGroupWidget;
11953 }
11954
createControl(const PDFWriter::AnyWidget & rControl,sal_Int32 nPageNr)11955 sal_Int32 PDFWriterImpl::createControl( const PDFWriter::AnyWidget& rControl, sal_Int32 nPageNr )
11956 {
11957 if( nPageNr < 0 )
11958 nPageNr = m_nCurrentPage;
11959
11960 if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
11961 return -1;
11962
11963 sal_Int32 nNewWidget = m_aWidgets.size();
11964 m_aWidgets.emplace_back( );
11965
11966 m_aWidgets.back().m_nObject = createObject();
11967 m_aWidgets.back().m_aRect = rControl.Location;
11968 m_aWidgets.back().m_nPage = nPageNr;
11969 m_aWidgets.back().m_eType = rControl.getType();
11970
11971 sal_Int32 nRadioGroupWidget = -1;
11972 // for unknown reasons the radio buttons of a radio group must not have a
11973 // field name, else the buttons are in fact check boxes -
11974 // that is multiple buttons of the radio group can be selected
11975 if( rControl.getType() == PDFWriter::RadioButton )
11976 nRadioGroupWidget = findRadioGroupWidget( static_cast<const PDFWriter::RadioButtonWidget&>(rControl) );
11977 else
11978 {
11979 createWidgetFieldName( nNewWidget, rControl );
11980 }
11981
11982 // caution: m_aWidgets must not be changed after here or rNewWidget may be invalid
11983 PDFWidget& rNewWidget = m_aWidgets[nNewWidget];
11984 rNewWidget.m_aDescription = rControl.Description;
11985 rNewWidget.m_aText = rControl.Text;
11986 rNewWidget.m_nTextStyle = rControl.TextStyle &
11987 ( DrawTextFlags::Left | DrawTextFlags::Center | DrawTextFlags::Right | DrawTextFlags::Top |
11988 DrawTextFlags::VCenter | DrawTextFlags::Bottom |
11989 DrawTextFlags::MultiLine | DrawTextFlags::WordBreak );
11990 rNewWidget.m_nTabOrder = rControl.TabOrder;
11991
11992 // various properties are set via the flags (/Ff) property of the field dict
11993 if( rControl.ReadOnly )
11994 rNewWidget.m_nFlags |= 1;
11995 if( rControl.getType() == PDFWriter::PushButton )
11996 {
11997 const PDFWriter::PushButtonWidget& rBtn = static_cast<const PDFWriter::PushButtonWidget&>(rControl);
11998 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11999 rNewWidget.m_nTextStyle =
12000 DrawTextFlags::Center | DrawTextFlags::VCenter |
12001 DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
12002
12003 rNewWidget.m_nFlags |= 0x00010000;
12004 if( !rBtn.URL.isEmpty() )
12005 rNewWidget.m_aListEntries.push_back( rBtn.URL );
12006 rNewWidget.m_bSubmit = rBtn.Submit;
12007 rNewWidget.m_bSubmitGet = rBtn.SubmitGet;
12008 rNewWidget.m_nDest = rBtn.Dest;
12009 createDefaultPushButtonAppearance( rNewWidget, rBtn );
12010 }
12011 else if( rControl.getType() == PDFWriter::RadioButton )
12012 {
12013 const PDFWriter::RadioButtonWidget& rBtn = static_cast<const PDFWriter::RadioButtonWidget&>(rControl);
12014 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
12015 rNewWidget.m_nTextStyle =
12016 DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
12017 /* PDF sees a RadioButton group as one radio button with
12018 * children which are in turn check boxes
12019 *
12020 * so we need to create a radio button on demand for a new group
12021 * and insert a checkbox for each RadioButtonWidget as its child
12022 */
12023 rNewWidget.m_eType = PDFWriter::CheckBox;
12024 rNewWidget.m_nRadioGroup = rBtn.RadioGroup;
12025
12026 SAL_WARN_IF( nRadioGroupWidget < 0 || o3tl::make_unsigned(nRadioGroupWidget) >= m_aWidgets.size(), "vcl.pdfwriter", "no radio group parent" );
12027
12028 PDFWidget& rRadioButton = m_aWidgets[nRadioGroupWidget];
12029 rRadioButton.m_aKids.push_back( rNewWidget.m_nObject );
12030 rRadioButton.m_aKidsIndex.push_back( nNewWidget );
12031 rNewWidget.m_nParent = rRadioButton.m_nObject;
12032
12033 rNewWidget.m_aValue = "Off";
12034 rNewWidget.m_aOnValue = rBtn.OnValue;
12035 rNewWidget.m_aOffValue = rBtn.OffValue;
12036 if( rRadioButton.m_aValue.isEmpty() && rBtn.Selected )
12037 {
12038 rNewWidget.m_aValue = rNewWidget.m_aOnValue;
12039 rRadioButton.m_aValue = rNewWidget.m_aOnValue;
12040 }
12041 createDefaultRadioButtonAppearance( rNewWidget, rBtn );
12042
12043 // union rect of radio group
12044 tools::Rectangle aRect = rNewWidget.m_aRect;
12045 m_aPages[ nPageNr ].convertRect( aRect );
12046 rRadioButton.m_aRect.Union( aRect );
12047 }
12048 else if( rControl.getType() == PDFWriter::CheckBox )
12049 {
12050 const PDFWriter::CheckBoxWidget& rBox = static_cast<const PDFWriter::CheckBoxWidget&>(rControl);
12051 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
12052 rNewWidget.m_nTextStyle =
12053 DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
12054
12055 rNewWidget.m_aValue
12056 = rBox.Checked ? std::u16string_view(u"Yes") : std::u16string_view(u"Off" );
12057 rNewWidget.m_aOnValue = rBox.OnValue;
12058 rNewWidget.m_aOffValue = rBox.OffValue;
12059 // create default appearance before m_aRect gets transformed
12060 createDefaultCheckBoxAppearance( rNewWidget, rBox );
12061 }
12062 else if( rControl.getType() == PDFWriter::ListBox )
12063 {
12064 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
12065 rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
12066
12067 const PDFWriter::ListBoxWidget& rLstBox = static_cast<const PDFWriter::ListBoxWidget&>(rControl);
12068 rNewWidget.m_aListEntries = rLstBox.Entries;
12069 rNewWidget.m_aSelectedEntries = rLstBox.SelectedEntries;
12070 rNewWidget.m_aValue = rLstBox.Text;
12071 if( rLstBox.DropDown )
12072 rNewWidget.m_nFlags |= 0x00020000;
12073 if (rLstBox.MultiSelect && !rLstBox.DropDown)
12074 rNewWidget.m_nFlags |= 0x00200000;
12075
12076 createDefaultListBoxAppearance( rNewWidget, rLstBox );
12077 }
12078 else if( rControl.getType() == PDFWriter::ComboBox )
12079 {
12080 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
12081 rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
12082
12083 const PDFWriter::ComboBoxWidget& rBox = static_cast<const PDFWriter::ComboBoxWidget&>(rControl);
12084 rNewWidget.m_aValue = rBox.Text;
12085 rNewWidget.m_aListEntries = rBox.Entries;
12086 rNewWidget.m_nFlags |= 0x00060000; // combo and edit flag
12087
12088 PDFWriter::ListBoxWidget aLBox;
12089 aLBox.Name = rBox.Name;
12090 aLBox.Description = rBox.Description;
12091 aLBox.Text = rBox.Text;
12092 aLBox.TextStyle = rBox.TextStyle;
12093 aLBox.ReadOnly = rBox.ReadOnly;
12094 aLBox.Border = rBox.Border;
12095 aLBox.BorderColor = rBox.BorderColor;
12096 aLBox.Background = rBox.Background;
12097 aLBox.BackgroundColor = rBox.BackgroundColor;
12098 aLBox.TextFont = rBox.TextFont;
12099 aLBox.TextColor = rBox.TextColor;
12100 aLBox.DropDown = true;
12101 aLBox.MultiSelect = false;
12102 aLBox.Entries = rBox.Entries;
12103
12104 createDefaultListBoxAppearance( rNewWidget, aLBox );
12105 }
12106 else if( rControl.getType() == PDFWriter::Edit )
12107 {
12108 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
12109 rNewWidget.m_nTextStyle = DrawTextFlags::Left | DrawTextFlags::VCenter;
12110
12111 const PDFWriter::EditWidget& rEdit = static_cast<const PDFWriter::EditWidget&>(rControl);
12112 if( rEdit.MultiLine )
12113 {
12114 rNewWidget.m_nFlags |= 0x00001000;
12115 rNewWidget.m_nTextStyle |= DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
12116 }
12117 if( rEdit.Password )
12118 rNewWidget.m_nFlags |= 0x00002000;
12119 if (rEdit.FileSelect)
12120 rNewWidget.m_nFlags |= 0x00100000;
12121 rNewWidget.m_nMaxLen = rEdit.MaxLen;
12122 rNewWidget.m_nFormat = rEdit.Format;
12123 rNewWidget.m_aCurrencySymbol = rEdit.CurrencySymbol;
12124 rNewWidget.m_nDecimalAccuracy = rEdit.DecimalAccuracy;
12125 rNewWidget.m_bPrependCurrencySymbol = rEdit.PrependCurrencySymbol;
12126 rNewWidget.m_aTimeFormat = rEdit.TimeFormat;
12127 rNewWidget.m_aDateFormat = rEdit.DateFormat;
12128 rNewWidget.m_aValue = rEdit.Text;
12129
12130 createDefaultEditAppearance( rNewWidget, rEdit );
12131 }
12132 #if HAVE_FEATURE_NSS
12133 else if( rControl.getType() == PDFWriter::Signature)
12134 {
12135 rNewWidget.m_aRect = tools::Rectangle(0, 0, 0, 0);
12136
12137 m_nSignatureObject = createObject();
12138 rNewWidget.m_aValue = OUString::number( m_nSignatureObject );
12139 rNewWidget.m_aValue += " 0 R";
12140 // let's add a fake appearance
12141 rNewWidget.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = new SvMemoryStream();
12142 }
12143 #endif
12144
12145 // if control is a signature, do not convert coordinates since we
12146 // need /Rect [ 0 0 0 0 ]
12147 if ( rControl.getType() != PDFWriter::Signature )
12148 {
12149 // convert to default user space now, since the mapmode may change
12150 // note: create default appearances before m_aRect gets transformed
12151 m_aPages[ nPageNr ].convertRect( rNewWidget.m_aRect );
12152 }
12153
12154 // insert widget to page's annotation list
12155 m_aPages[ nPageNr ].m_aAnnotations.push_back( rNewWidget.m_nObject );
12156
12157 return nNewWidget;
12158 }
12159
MARK(const char * pString)12160 void PDFWriterImpl::MARK( const char* pString )
12161 {
12162 beginStructureElementMCSeq();
12163 if (g_bDebugDisableCompression)
12164 emitComment( pString );
12165 }
12166
getObject() const12167 sal_Int32 ReferenceXObjectEmit::getObject() const
12168 {
12169 if (m_nFormObject > 0)
12170 return m_nFormObject;
12171 else
12172 return m_nBitmapObject;
12173 }
12174 }
12175
12176 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
12177