xref: /core/vcl/source/gdi/pdfwriter_impl.cxx (revision 6294ecd7)
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 <config_features.h>
21 
22 #include <sal/types.h>
23 
24 #include <math.h>
25 #include <algorithm>
26 
27 #include <lcms2.h>
28 
29 #include <basegfx/matrix/b2dhommatrix.hxx>
30 #include <basegfx/polygon/b2dpolygon.hxx>
31 #include <basegfx/polygon/b2dpolygontools.hxx>
32 #include <basegfx/polygon/b2dpolypolygon.hxx>
33 #include <basegfx/polygon/b2dpolypolygoncutter.hxx>
34 #include <basegfx/polygon/b2dpolypolygontools.hxx>
35 #include <memory>
36 #include <com/sun/star/io/XOutputStream.hpp>
37 #include <com/sun/star/util/URL.hpp>
38 #include <com/sun/star/util/URLTransformer.hpp>
39 #include <comphelper/processfactory.hxx>
40 #include <comphelper/string.hxx>
41 #include <cppuhelper/implbase.hxx>
42 #include <i18nlangtag/languagetag.hxx>
43 #include <o3tl/numeric.hxx>
44 #include <officecfg/Office/Common.hxx>
45 #include <osl/file.hxx>
46 #include <osl/thread.h>
47 #include <rtl/digest.h>
48 #include <rtl/ustrbuf.hxx>
49 #include <sal/log.hxx>
50 #include <svl/urihelper.hxx>
51 #include <tools/fract.hxx>
52 #include <tools/helpers.hxx>
53 #include <tools/stream.hxx>
54 #include <tools/urlobj.hxx>
55 #include <tools/zcodec.hxx>
56 #include <svl/cryptosign.hxx>
57 #include <vcl/bitmapex.hxx>
58 #include <vcl/bitmapaccess.hxx>
59 #include <vcl/canvastools.hxx>
60 #include <vcl/cvtgrf.hxx>
61 #include <vcl/fontcharmap.hxx>
62 #include <vcl/lineinfo.hxx>
63 #include <vcl/metric.hxx>
64 #include <vcl/settings.hxx>
65 #include <strhelper.hxx>
66 #include <vcl/svapp.hxx>
67 #include <vcl/virdev.hxx>
68 #include <vcl/filter/pdfdocument.hxx>
69 #include <comphelper/hash.hxx>
70 
71 #include <fontsubset.hxx>
72 #include <PhysicalFontFace.hxx>
73 #include <salgdi.hxx>
74 #include <textlayout.hxx>
75 #include <textlineinfo.hxx>
76 #include <bitmapwriteaccess.hxx>
77 #include <impglyphitem.hxx>
78 #include <pdf/XmpMetadata.hxx>
79 #include <pdf/objectcopier.hxx>
80 
81 #include "pdfwriter_impl.hxx"
82 
83 #ifdef _WIN32
84 // WinCrypt headers for PDF signing
85 // Note: this uses Windows 7 APIs and requires the relevant data types
86 #include <prewin.h>
87 #include <wincrypt.h>
88 #include <postwin.h>
89 #endif
90 
91 #include <config_eot.h>
92 
93 #if ENABLE_EOT
94 #include <libeot/libeot.h>
95 #endif
96 
97 using namespace::com::sun::star;
98 
99 static bool g_bDebugDisableCompression = getenv("VCL_DEBUG_DISABLE_PDFCOMPRESSION");
100 
101 #if HAVE_FEATURE_NSS
102 // Is this length truly the maximum possible, or just a number that
103 // seemed large enough when the author tested this (with some type of
104 // certificates)? I suspect the latter.
105 
106 // Used to be 0x4000 = 16384, but a sample signed PDF (produced by
107 // some other software) provided by the customer has a signature
108 // content that is 30000 bytes. The SampleSignedPDFDocument.pdf from
109 // Adobe has one that is 21942 bytes. So let's be careful. Pity this
110 // can't be dynamic, at least not without restructuring the code. Also
111 // note that the checks in the code for this being too small
112 // apparently are broken, if this overflows you end up with an invalid
113 // PDF. Need to fix that.
114 
115 #define MAX_SIGNATURE_CONTENT_LENGTH 50000
116 #endif
117 
118 namespace
119 {
120 
121 constexpr sal_Int32 nLog10Divisor = 3;
122 constexpr double fDivisor = 1000.0;
123 
124 constexpr double pixelToPoint(double px)
125 {
126     return px / fDivisor;
127 }
128 
129 constexpr sal_Int32 pointToPixel(double pt)
130 {
131     return sal_Int32(pt * fDivisor);
132 }
133 
134 void appendHex(sal_Int8 nInt, OStringBuffer& rBuffer)
135 {
136     static const char pHexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7',
137                                            '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
138     rBuffer.append( pHexDigits[ (nInt >> 4) & 15 ] );
139     rBuffer.append( pHexDigits[ nInt & 15 ] );
140 }
141 
142 void appendName( const OUString& rStr, OStringBuffer& rBuffer )
143 {
144 // FIXME i59651 add a check for max length of 127 chars? Per PDF spec 1.4, appendix C.1
145 // I guess than when reading the #xx sequence it will count for a single character.
146     OString aStr( OUStringToOString( rStr, RTL_TEXTENCODING_UTF8 ) );
147     int nLen = aStr.getLength();
148     for( int i = 0; i < nLen; i++ )
149     {
150         /*  #i16920# PDF recommendation: output UTF8, any byte
151          *  outside the interval [33(=ASCII'!');126(=ASCII'~')]
152          *  should be escaped hexadecimal
153          *  for the sake of ghostscript which also reads PDF
154          *  but has a narrower acceptance rate we only pass
155          *  alphanumerics and '-' literally.
156          */
157         if( (aStr[i] >= 'A' && aStr[i] <= 'Z' ) ||
158             (aStr[i] >= 'a' && aStr[i] <= 'z' ) ||
159             (aStr[i] >= '0' && aStr[i] <= '9' ) ||
160             aStr[i] == '-' )
161         {
162             rBuffer.append( aStr[i] );
163         }
164         else
165         {
166             rBuffer.append( '#' );
167             appendHex( static_cast<sal_Int8>(aStr[i]), rBuffer );
168         }
169     }
170 }
171 
172 void appendName( const char* pStr, OStringBuffer& rBuffer )
173 {
174     // FIXME i59651 see above
175     while( pStr && *pStr )
176     {
177         if( (*pStr >= 'A' && *pStr <= 'Z' ) ||
178             (*pStr >= 'a' && *pStr <= 'z' ) ||
179             (*pStr >= '0' && *pStr <= '9' ) ||
180             *pStr == '-' )
181         {
182             rBuffer.append( *pStr );
183         }
184         else
185         {
186             rBuffer.append( '#' );
187             appendHex( static_cast<sal_Int8>(*pStr), rBuffer );
188         }
189         pStr++;
190     }
191 }
192 
193 //used only to emit encoded passwords
194 void appendLiteralString( const char* pStr, sal_Int32 nLength, OStringBuffer& rBuffer )
195 {
196     while( nLength )
197     {
198         switch( *pStr )
199         {
200         case '\n' :
201             rBuffer.append( "\\n" );
202             break;
203         case '\r' :
204             rBuffer.append( "\\r" );
205             break;
206         case '\t' :
207             rBuffer.append( "\\t" );
208             break;
209         case '\b' :
210             rBuffer.append( "\\b" );
211             break;
212         case '\f' :
213             rBuffer.append( "\\f" );
214             break;
215         case '(' :
216         case ')' :
217         case '\\' :
218             rBuffer.append( "\\" );
219             rBuffer.append( static_cast<char>(*pStr) );
220             break;
221         default:
222             rBuffer.append( static_cast<char>(*pStr) );
223             break;
224         }
225         pStr++;
226         nLength--;
227     }
228 }
229 
230 /*
231  * Convert a string before using it.
232  *
233  * This string conversion function is needed because the destination name
234  * in a PDF file seen through an Internet browser should be
235  * specially crafted, in order to be used directly by the browser.
236  * In this way the fragment part of a hyperlink to a PDF file (e.g. something
237  * as 'test1/test2/a-file.pdf\#thefragment) will be (hopefully) interpreted by the
238  * PDF reader (currently only Adobe Reader plug-in seems to be working that way) called
239  * from inside the Internet browser as: 'open the file test1/test2/a-file.pdf
240  * and go to named destination thefragment using default zoom'.
241  * The conversion is needed because in case of a fragment in the form: Slide%201
242  * (meaning Slide 1) as it is converted obeying the Inet rules, it will become Slide25201
243  * using this conversion, in both the generated named destinations, fragment and GoToR
244  * destination.
245  *
246  * The names for destinations are name objects and so they don't need to be encrypted
247  * even though they expose the content of PDF file (e.g. guessing the PDF content from the
248  * destination name).
249  *
250  * Further limitation: it is advisable to use standard ASCII characters for
251  * OOo bookmarks.
252 */
253 void appendDestinationName( const OUString& rString, OStringBuffer& rBuffer )
254 {
255     const sal_Unicode* pStr = rString.getStr();
256     sal_Int32 nLen = rString.getLength();
257     for( int i = 0; i < nLen; i++ )
258     {
259         sal_Unicode aChar = pStr[i];
260         if( (aChar >= '0' && aChar <= '9' ) ||
261             (aChar >= 'a' && aChar <= 'z' ) ||
262             (aChar >= 'A' && aChar <= 'Z' ) ||
263             aChar == '-' )
264         {
265             rBuffer.append(static_cast<char>(aChar));
266         }
267         else
268         {
269             sal_Int8 aValueHigh = sal_Int8(aChar >> 8);
270             if(aValueHigh > 0)
271                 appendHex( aValueHigh, rBuffer );
272             appendHex( static_cast<sal_Int8>(aChar & 255 ), rBuffer );
273         }
274     }
275 }
276 
277 } // end anonymous namespace
278 
279 namespace vcl
280 {
281 const sal_uInt8 PDFWriterImpl::s_nPadString[32] =
282 {
283     0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
284     0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A
285 };
286 
287 namespace
288 {
289 
290 template < class GEOMETRY >
291 GEOMETRY lcl_convert( const MapMode& _rSource, const MapMode& _rDest, OutputDevice* _pPixelConversion, const GEOMETRY& _rObject )
292 {
293     GEOMETRY aPoint;
294     if ( MapUnit::MapPixel == _rSource.GetMapUnit() )
295     {
296         aPoint = _pPixelConversion->PixelToLogic( _rObject, _rDest );
297     }
298     else
299     {
300         aPoint = OutputDevice::LogicToLogic( _rObject, _rSource, _rDest );
301     }
302     return aPoint;
303 }
304 
305 } // end anonymous namespace
306 
307 void PDFWriter::AppendUnicodeTextString(const OUString& rString, OStringBuffer& rBuffer)
308 {
309     rBuffer.append( "FEFF" );
310     const sal_Unicode* pStr = rString.getStr();
311     sal_Int32 nLen = rString.getLength();
312     for( int i = 0; i < nLen; i++ )
313     {
314         sal_Unicode aChar = pStr[i];
315         appendHex( static_cast<sal_Int8>(aChar >> 8), rBuffer );
316         appendHex( static_cast<sal_Int8>(aChar & 255 ), rBuffer );
317     }
318 }
319 
320 void PDFWriterImpl::createWidgetFieldName( sal_Int32 i_nWidgetIndex, const PDFWriter::AnyWidget& i_rControl )
321 {
322     /* #i80258# previously we use appendName here
323        however we need a slightly different coding scheme than the normal
324        name encoding for field names
325     */
326     const OUString& rName = (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2) ? i_rControl.Name : i_rControl.Text;
327     OString aStr( OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ) );
328     int nLen = aStr.getLength();
329 
330     OStringBuffer aBuffer( rName.getLength()+64 );
331     for( int i = 0; i < nLen; i++ )
332     {
333         /*  #i16920# PDF recommendation: output UTF8, any byte
334          *  outside the interval [32(=ASCII' ');126(=ASCII'~')]
335          *  should be escaped hexadecimal
336          */
337         if( aStr[i] >= 32 && aStr[i] <= 126 )
338             aBuffer.append( aStr[i] );
339         else
340         {
341             aBuffer.append( '#' );
342             appendHex( static_cast<sal_Int8>(aStr[i]), aBuffer );
343         }
344     }
345 
346     OString aFullName( aBuffer.makeStringAndClear() );
347 
348     /* #i82785# create hierarchical fields down to the for each dot in i_rName */
349     sal_Int32 nTokenIndex = 0, nLastTokenIndex = 0;
350     OString aPartialName;
351     OString aDomain;
352     do
353     {
354         nLastTokenIndex = nTokenIndex;
355         aPartialName = aFullName.getToken( 0, '.', nTokenIndex );
356         if( nTokenIndex != -1 )
357         {
358             // find or create a hierarchical field
359             // first find the fully qualified name up to this field
360             aDomain = aFullName.copy( 0, nTokenIndex-1 );
361             std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
362             if( it == m_aFieldNameMap.end() )
363             {
364                  // create new hierarchy field
365                 sal_Int32 nNewWidget = m_aWidgets.size();
366                 m_aWidgets.emplace_back( );
367                 m_aWidgets[nNewWidget].m_nObject = createObject();
368                 m_aWidgets[nNewWidget].m_eType = PDFWriter::Hierarchy;
369                 m_aWidgets[nNewWidget].m_aName = aPartialName;
370                 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
371                 m_aFieldNameMap[aDomain] = nNewWidget;
372                 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
373                 if( nLastTokenIndex > 0 )
374                 {
375                     // this field is not a root field and
376                     // needs to be inserted to its parent
377                     OString aParentDomain( aDomain.copy( 0, nLastTokenIndex-1 ) );
378                     it = m_aFieldNameMap.find( aParentDomain );
379                     OSL_ENSURE( it != m_aFieldNameMap.end(), "field name not found" );
380                     if( it != m_aFieldNameMap.end()  )
381                     {
382                         OSL_ENSURE( it->second < sal_Int32(m_aWidgets.size()), "invalid field number entry" );
383                         if( it->second < sal_Int32(m_aWidgets.size()) )
384                         {
385                             PDFWidget& rParentField( m_aWidgets[it->second] );
386                             rParentField.m_aKids.push_back( m_aWidgets[nNewWidget].m_nObject );
387                             rParentField.m_aKidsIndex.push_back( nNewWidget );
388                             m_aWidgets[nNewWidget].m_nParent = rParentField.m_nObject;
389                         }
390                     }
391                 }
392             }
393             else if( m_aWidgets[it->second].m_eType != PDFWriter::Hierarchy )
394             {
395                 // this is invalid, someone tries to have a terminal field as parent
396                 // example: a button with the name foo.bar exists and
397                 // another button is named foo.bar.no
398                 // workaround: put the second terminal field as much up in the hierarchy as
399                 // necessary to have a non-terminal field as parent (or none at all)
400                 // since it->second already is terminal, we just need to use its parent
401                 aDomain.clear();
402                 aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
403                 if( nLastTokenIndex > 0 )
404                 {
405                     aDomain = aFullName.copy( 0, nLastTokenIndex-1 );
406                     aFullName = aDomain + "." + aPartialName;
407                 }
408                 else
409                     aFullName = aPartialName;
410                 break;
411             }
412         }
413     } while( nTokenIndex != -1 );
414 
415     // insert widget into its hierarchy field
416     if( !aDomain.isEmpty() )
417     {
418         std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
419         if( it != m_aFieldNameMap.end() )
420         {
421             OSL_ENSURE( it->second >= 0 && it->second < sal_Int32( m_aWidgets.size() ), "invalid field index" );
422             if( it->second >= 0 && it->second < sal_Int32(m_aWidgets.size()) )
423             {
424                 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[it->second].m_nObject;
425                 m_aWidgets[it->second].m_aKids.push_back( m_aWidgets[i_nWidgetIndex].m_nObject);
426                 m_aWidgets[it->second].m_aKidsIndex.push_back( i_nWidgetIndex );
427             }
428         }
429     }
430 
431     if( aPartialName.isEmpty() )
432     {
433         // how funny, an empty field name
434         if( i_rControl.getType() == PDFWriter::RadioButton )
435         {
436             aPartialName  = "RadioGroup" +
437                 OString::number( static_cast<const PDFWriter::RadioButtonWidget&>(i_rControl).RadioGroup );
438         }
439         else
440             aPartialName = OString( "Widget" );
441     }
442 
443     if( ! m_aContext.AllowDuplicateFieldNames )
444     {
445         std::unordered_map<OString, sal_Int32>::iterator it = m_aFieldNameMap.find( aFullName );
446 
447         if( it != m_aFieldNameMap.end() ) // not unique
448         {
449             std::unordered_map< OString, sal_Int32 >::const_iterator check_it;
450             OString aTry;
451             sal_Int32 nTry = 2;
452             do
453             {
454                 OStringBuffer aUnique( aFullName.getLength() + 16 );
455                 aUnique.append( aFullName );
456                 aUnique.append( '_' );
457                 aUnique.append( nTry++ );
458                 aTry = aUnique.makeStringAndClear();
459                 check_it = m_aFieldNameMap.find( aTry );
460             } while( check_it != m_aFieldNameMap.end() );
461             aFullName = aTry;
462             m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
463             aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
464         }
465         else
466             m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
467     }
468 
469     // finally
470     m_aWidgets[i_nWidgetIndex].m_aName = aPartialName;
471 }
472 
473 namespace
474 {
475 
476 void appendFixedInt( sal_Int32 nValue, OStringBuffer& rBuffer )
477 {
478     if( nValue < 0 )
479     {
480         rBuffer.append( '-' );
481         nValue = -nValue;
482     }
483     sal_Int32 nFactor = 1, nDiv = nLog10Divisor;
484     while( nDiv-- )
485         nFactor *= 10;
486 
487     sal_Int32 nInt = nValue / nFactor;
488     rBuffer.append( nInt );
489     if (nFactor > 1 && nValue % nFactor)
490     {
491         rBuffer.append( '.' );
492         do
493         {
494             nFactor /= 10;
495             rBuffer.append((nValue / nFactor) % 10);
496         }
497         while (nFactor > 1 && nValue % nFactor); // omit trailing zeros
498     }
499 }
500 
501 // appends a double. PDF does not accept exponential format, only fixed point
502 void appendDouble( double fValue, OStringBuffer& rBuffer, sal_Int32 nPrecision = 5 )
503 {
504     bool bNeg = false;
505     if( fValue < 0.0 )
506     {
507         bNeg = true;
508         fValue=-fValue;
509     }
510 
511     sal_Int64 nInt = static_cast<sal_Int64>(fValue);
512     fValue -= static_cast<double>(nInt);
513     // optimizing hardware may lead to a value of 1.0 after the subtraction
514     if( rtl::math::approxEqual(fValue, 1.0) || log10( 1.0-fValue ) <= -nPrecision )
515     {
516         nInt++;
517         fValue = 0.0;
518     }
519     sal_Int64 nFrac = 0;
520     if( fValue )
521     {
522         fValue *= pow( 10.0, static_cast<double>(nPrecision) );
523         nFrac = static_cast<sal_Int64>(fValue);
524     }
525     if( bNeg && ( nInt || nFrac ) )
526         rBuffer.append( '-' );
527     rBuffer.append( nInt );
528     if( nFrac )
529     {
530         int i;
531         rBuffer.append( '.' );
532         sal_Int64 nBound = static_cast<sal_Int64>(pow( 10.0, nPrecision - 1.0 )+0.5);
533         for ( i = 0; ( i < nPrecision ) && nFrac; i++ )
534         {
535             sal_Int64 nNumb = nFrac / nBound;
536             nFrac -= nNumb * nBound;
537             rBuffer.append( nNumb );
538             nBound /= 10;
539         }
540     }
541 }
542 
543 void appendColor( const Color& rColor, OStringBuffer& rBuffer, bool bConvertToGrey )
544 {
545 
546     if( rColor != COL_TRANSPARENT )
547     {
548         if( bConvertToGrey )
549         {
550             sal_uInt8 cByte = rColor.GetLuminance();
551             appendDouble( static_cast<double>(cByte) / 255.0, rBuffer );
552         }
553         else
554         {
555             appendDouble( static_cast<double>(rColor.GetRed()) / 255.0, rBuffer );
556             rBuffer.append( ' ' );
557             appendDouble( static_cast<double>(rColor.GetGreen()) / 255.0, rBuffer );
558             rBuffer.append( ' ' );
559             appendDouble( static_cast<double>(rColor.GetBlue()) / 255.0, rBuffer );
560         }
561     }
562 }
563 
564 } // end anonymous namespace
565 
566 void PDFWriterImpl::appendStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
567 {
568     if( rColor != COL_TRANSPARENT )
569     {
570         bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
571         appendColor( rColor, rBuffer, bGrey );
572         rBuffer.append( bGrey ? " G" : " RG" );
573     }
574 }
575 
576 void PDFWriterImpl::appendNonStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
577 {
578     if( rColor != COL_TRANSPARENT )
579     {
580         bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
581         appendColor( rColor, rBuffer, bGrey );
582         rBuffer.append( bGrey ? " g" : " rg" );
583     }
584 }
585 
586 namespace
587 {
588 
589 void appendPdfTimeDate(OStringBuffer & rBuffer,
590     sal_Int16 year, sal_uInt16 month, sal_uInt16 day, sal_uInt16 hours, sal_uInt16 minutes, sal_uInt16 seconds, sal_Int32 tzDelta)
591 {
592     rBuffer.append("D:");
593     rBuffer.append(char('0' + ((year / 1000) % 10)));
594     rBuffer.append(char('0' + ((year / 100) % 10)));
595     rBuffer.append(char('0' + ((year / 10) % 10)));
596     rBuffer.append(char('0' + (year % 10)));
597     rBuffer.append(char('0' + ((month / 10) % 10)));
598     rBuffer.append(char('0' + (month % 10)));
599     rBuffer.append(char('0' + ((day / 10) % 10)));
600     rBuffer.append(char('0' + (day % 10)));
601     rBuffer.append(char('0' + ((hours / 10) % 10)));
602     rBuffer.append(char('0' + (hours % 10)));
603     rBuffer.append(char('0' + ((minutes / 10) % 10)));
604     rBuffer.append(char('0' + (minutes % 10)));
605     rBuffer.append(char('0' + ((seconds / 10) % 10)));
606     rBuffer.append(char('0' + (seconds % 10)));
607 
608     if (tzDelta == 0)
609     {
610         rBuffer.append("Z");
611     }
612     else
613     {
614         if (tzDelta > 0 )
615             rBuffer.append("+");
616         else
617         {
618             rBuffer.append("-");
619             tzDelta = -tzDelta;
620         }
621 
622         rBuffer.append(char('0' + ((tzDelta / 36000) % 10)));
623         rBuffer.append(char('0' + ((tzDelta / 3600) % 10)));
624         rBuffer.append("'");
625         rBuffer.append(char('0' + ((tzDelta / 600) % 6)));
626         rBuffer.append(char('0' + ((tzDelta / 60) % 10)));
627     }
628 }
629 
630 } // end namespace
631 
632 PDFPage::PDFPage( PDFWriterImpl* pWriter, double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
633         :
634         m_pWriter( pWriter ),
635         m_nPageWidth( nPageWidth ),
636         m_nPageHeight( nPageHeight ),
637         m_nUserUnit( 1 ),
638         m_eOrientation( eOrientation ),
639         m_nPageObject( 0 ),  // invalid object number
640         m_nStreamLengthObject( 0 ),
641         m_nBeginStreamPos( 0 ),
642         m_eTransition( PDFWriter::PageTransition::Regular ),
643         m_nTransTime( 0 )
644 {
645     // object ref must be only ever updated in emit()
646     m_nPageObject = m_pWriter->createObject();
647 
648     switch (m_pWriter->m_aContext.Version)
649     {
650         case PDFWriter::PDFVersion::PDF_1_6:
651             m_nUserUnit = std::ceil(std::max(nPageWidth, nPageHeight) / 14400.0);
652             break;
653         default:
654             // 1.2 -> 1.5
655             break;
656     }
657 }
658 
659 void PDFPage::beginStream()
660 {
661     if (g_bDebugDisableCompression)
662     {
663         m_pWriter->emitComment("PDFWriterImpl::PDFPage::beginStream, +");
664     }
665     m_aStreamObjects.push_back(m_pWriter->createObject());
666     if( ! m_pWriter->updateObject( m_aStreamObjects.back() ) )
667         return;
668 
669     m_nStreamLengthObject = m_pWriter->createObject();
670     // write content stream header
671     OStringBuffer aLine;
672     aLine.append( m_aStreamObjects.back() );
673     aLine.append( " 0 obj\n<</Length " );
674     aLine.append( m_nStreamLengthObject );
675     aLine.append( " 0 R" );
676     if (!g_bDebugDisableCompression)
677         aLine.append( "/Filter/FlateDecode" );
678     aLine.append( ">>\nstream\n" );
679     if( ! m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() ) )
680         return;
681     if (osl::File::E_None != m_pWriter->m_aFile.getPos(m_nBeginStreamPos))
682     {
683         m_pWriter->m_aFile.close();
684         m_pWriter->m_bOpen = false;
685     }
686     if (!g_bDebugDisableCompression)
687         m_pWriter->beginCompression();
688     m_pWriter->checkAndEnableStreamEncryption( m_aStreamObjects.back() );
689 }
690 
691 void PDFPage::endStream()
692 {
693     if (!g_bDebugDisableCompression)
694         m_pWriter->endCompression();
695     sal_uInt64 nEndStreamPos;
696     if (osl::File::E_None != m_pWriter->m_aFile.getPos(nEndStreamPos))
697     {
698         m_pWriter->m_aFile.close();
699         m_pWriter->m_bOpen = false;
700         return;
701     }
702     m_pWriter->disableStreamEncryption();
703     if( ! m_pWriter->writeBuffer( "\nendstream\nendobj\n\n", 19 ) )
704         return;
705     // emit stream length object
706     if( ! m_pWriter->updateObject( m_nStreamLengthObject ) )
707         return;
708     OString aLine =
709         OString::number( m_nStreamLengthObject ) +
710         " 0 obj\n"  +
711         OString::number( static_cast<sal_Int64>(nEndStreamPos-m_nBeginStreamPos) ) +
712         "\nendobj\n\n";
713     m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() );
714 }
715 
716 bool PDFPage::emit(sal_Int32 nParentObject )
717 {
718     m_pWriter->MARK("PDFPage::emit");
719     // emit page object
720     if( ! m_pWriter->updateObject( m_nPageObject ) )
721         return false;
722     OStringBuffer aLine;
723 
724     aLine.append( m_nPageObject );
725     aLine.append( " 0 obj\n"
726                   "<</Type/Page/Parent " );
727     aLine.append( nParentObject );
728     aLine.append( " 0 R" );
729     aLine.append( "/Resources " );
730     aLine.append( m_pWriter->getResourceDictObj() );
731     aLine.append( " 0 R" );
732     if( m_nPageWidth && m_nPageHeight )
733     {
734         aLine.append( "/MediaBox[0 0 " );
735         aLine.append(m_nPageWidth / m_nUserUnit);
736         aLine.append( ' ' );
737         aLine.append(m_nPageHeight / m_nUserUnit);
738         aLine.append( "]" );
739         if (m_nUserUnit > 1)
740         {
741             aLine.append("\n/UserUnit ");
742             aLine.append(m_nUserUnit);
743         }
744     }
745     switch( m_eOrientation )
746     {
747         case PDFWriter::Orientation::Portrait: aLine.append( "/Rotate 0\n" );break;
748         case PDFWriter::Orientation::Inherit:  break;
749     }
750     int nAnnots = m_aAnnotations.size();
751     if( nAnnots > 0 )
752     {
753         aLine.append( "/Annots[\n" );
754         for( int i = 0; i < nAnnots; i++ )
755         {
756             aLine.append( m_aAnnotations[i] );
757             aLine.append( " 0 R" );
758             aLine.append( ((i+1)%15) ? " " : "\n" );
759         }
760         aLine.append( "]\n" );
761     }
762     if( !m_aMCIDParents.empty() )
763     {
764         OStringBuffer aStructParents( 1024 );
765         aStructParents.append( "[ " );
766         int nParents = m_aMCIDParents.size();
767         for( int i = 0; i < nParents; i++ )
768         {
769             aStructParents.append( m_aMCIDParents[i] );
770             aStructParents.append( " 0 R" );
771             aStructParents.append( ((i%10) == 9) ? "\n" : " " );
772         }
773         aStructParents.append( "]" );
774         m_pWriter->m_aStructParentTree.push_back( aStructParents.makeStringAndClear() );
775 
776         aLine.append( "/StructParents " );
777         aLine.append( sal_Int32(m_pWriter->m_aStructParentTree.size()-1) );
778         aLine.append( "\n" );
779     }
780     if( m_eTransition != PDFWriter::PageTransition::Regular && m_nTransTime > 0 )
781     {
782         // transition duration
783         aLine.append( "/Trans<</D " );
784         appendDouble( static_cast<double>(m_nTransTime)/1000.0, aLine, 3 );
785         aLine.append( "\n" );
786         const char *pStyle = nullptr, *pDm = nullptr, *pM = nullptr, *pDi = nullptr;
787         switch( m_eTransition )
788         {
789             case PDFWriter::PageTransition::SplitHorizontalInward:
790                 pStyle = "Split"; pDm = "H"; pM = "I"; break;
791             case PDFWriter::PageTransition::SplitHorizontalOutward:
792                 pStyle = "Split"; pDm = "H"; pM = "O"; break;
793             case PDFWriter::PageTransition::SplitVerticalInward:
794                 pStyle = "Split"; pDm = "V"; pM = "I"; break;
795             case PDFWriter::PageTransition::SplitVerticalOutward:
796                 pStyle = "Split"; pDm = "V"; pM = "O"; break;
797             case PDFWriter::PageTransition::BlindsHorizontal:
798                 pStyle = "Blinds"; pDm = "H"; break;
799             case PDFWriter::PageTransition::BlindsVertical:
800                 pStyle = "Blinds"; pDm = "V"; break;
801             case PDFWriter::PageTransition::BoxInward:
802                 pStyle = "Box"; pM = "I"; break;
803             case PDFWriter::PageTransition::BoxOutward:
804                 pStyle = "Box"; pM = "O"; break;
805             case PDFWriter::PageTransition::WipeLeftToRight:
806                 pStyle = "Wipe"; pDi = "0"; break;
807             case PDFWriter::PageTransition::WipeBottomToTop:
808                 pStyle = "Wipe"; pDi = "90"; break;
809             case PDFWriter::PageTransition::WipeRightToLeft:
810                 pStyle = "Wipe"; pDi = "180"; break;
811             case PDFWriter::PageTransition::WipeTopToBottom:
812                 pStyle = "Wipe"; pDi = "270"; break;
813             case PDFWriter::PageTransition::Dissolve:
814                 pStyle = "Dissolve"; break;
815             case PDFWriter::PageTransition::Regular:
816                 break;
817         }
818         // transition style
819         if( pStyle )
820         {
821             aLine.append( "/S/" );
822             aLine.append( pStyle );
823             aLine.append( "\n" );
824         }
825         if( pDm )
826         {
827             aLine.append( "/Dm/" );
828             aLine.append( pDm );
829             aLine.append( "\n" );
830         }
831         if( pM )
832         {
833             aLine.append( "/M/" );
834             aLine.append( pM );
835             aLine.append( "\n" );
836         }
837         if( pDi  )
838         {
839             aLine.append( "/Di " );
840             aLine.append( pDi );
841             aLine.append( "\n" );
842         }
843         aLine.append( ">>\n" );
844     }
845     if( m_pWriter->getVersion() > PDFWriter::PDFVersion::PDF_1_3 && ! m_pWriter->m_bIsPDF_A1 )
846     {
847         aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/I true>>" );
848     }
849     aLine.append( "/Contents" );
850     unsigned int nStreamObjects = m_aStreamObjects.size();
851     if( nStreamObjects > 1 )
852         aLine.append( '[' );
853     for(sal_Int32 i : m_aStreamObjects)
854     {
855         aLine.append( ' ' );
856         aLine.append( i );
857         aLine.append( " 0 R" );
858     }
859     if( nStreamObjects > 1 )
860         aLine.append( ']' );
861     aLine.append( ">>\nendobj\n\n" );
862     return m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() );
863 }
864 
865 void PDFPage::appendPoint( const Point& rPoint, OStringBuffer& rBuffer ) const
866 {
867     Point aPoint( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
868                                m_pWriter->m_aMapMode,
869                                m_pWriter,
870                                rPoint ) );
871 
872     sal_Int32 nValue    = aPoint.X();
873 
874     appendFixedInt( nValue, rBuffer );
875 
876     rBuffer.append( ' ' );
877 
878     nValue      = pointToPixel(getHeight()) - aPoint.Y();
879 
880     appendFixedInt( nValue, rBuffer );
881 }
882 
883 void PDFPage::appendPixelPoint( const basegfx::B2DPoint& rPoint, OStringBuffer& rBuffer ) const
884 {
885     double fValue   = pixelToPoint(rPoint.getX());
886 
887     appendDouble( fValue, rBuffer, nLog10Divisor );
888     rBuffer.append( ' ' );
889     fValue      = getHeight() - pixelToPoint(rPoint.getY());
890     appendDouble( fValue, rBuffer, nLog10Divisor );
891 }
892 
893 void PDFPage::appendRect( const tools::Rectangle& rRect, OStringBuffer& rBuffer ) const
894 {
895     appendPoint( rRect.BottomLeft() + Point( 0, 1 ), rBuffer );
896     rBuffer.append( ' ' );
897     appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), rBuffer, false );
898     rBuffer.append( ' ' );
899     appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), rBuffer );
900     rBuffer.append( " re" );
901 }
902 
903 void PDFPage::convertRect( tools::Rectangle& rRect ) const
904 {
905     Point aLL = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
906                              m_pWriter->m_aMapMode,
907                              m_pWriter,
908                              rRect.BottomLeft() + Point( 0, 1 )
909                              );
910     Size aSize = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
911                               m_pWriter->m_aMapMode,
912                               m_pWriter,
913                               rRect.GetSize() );
914     rRect.SetLeft( aLL.X() );
915     rRect.SetRight( aLL.X() + aSize.Width() );
916     rRect.SetTop( pointToPixel(getHeight()) - aLL.Y() );
917     rRect.SetBottom( rRect.Top() + aSize.Height() );
918 }
919 
920 void PDFPage::appendPolygon( const tools::Polygon& rPoly, OStringBuffer& rBuffer, bool bClose ) const
921 {
922     sal_uInt16 nPoints = rPoly.GetSize();
923     /*
924      *  #108582# applications do weird things
925      */
926     sal_uInt32 nBufLen = rBuffer.getLength();
927     if( nPoints > 0 )
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 }
964 
965 void PDFPage::appendPolygon( const basegfx::B2DPolygon& rPoly, OStringBuffer& rBuffer ) const
966 {
967     basegfx::B2DPolygon aPoly( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
968                                             m_pWriter->m_aMapMode,
969                                             m_pWriter,
970                                             rPoly ) );
971 
972     if( basegfx::utils::isRectangle( aPoly ) )
973     {
974         basegfx::B2DRange aRange( aPoly.getB2DRange() );
975         basegfx::B2DPoint aBL( aRange.getMinX(), aRange.getMaxY() );
976         appendPixelPoint( aBL, rBuffer );
977         rBuffer.append( ' ' );
978         appendMappedLength( aRange.getWidth(), rBuffer, false, nLog10Divisor );
979         rBuffer.append( ' ' );
980         appendMappedLength( aRange.getHeight(), rBuffer, true, nLog10Divisor );
981         rBuffer.append( " re\n" );
982         return;
983     }
984     sal_uInt32 nPoints = aPoly.count();
985     if( nPoints > 0 )
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 }
1039 
1040 void PDFPage::appendPolyPolygon( const tools::PolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
1041 {
1042     sal_uInt16 nPolygons = rPolyPoly.Count();
1043     for( sal_uInt16 n = 0; n < nPolygons; n++ )
1044         appendPolygon( rPolyPoly[n], rBuffer );
1045 }
1046 
1047 void PDFPage::appendPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
1048 {
1049     for(auto const& rPolygon : rPolyPoly)
1050         appendPolygon( rPolygon, rBuffer );
1051 }
1052 
1053 void PDFPage::appendMappedLength( sal_Int32 nLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32* pOutLength ) const
1054 {
1055     sal_Int32 nValue = nLength;
1056     if ( nLength < 0 )
1057     {
1058         rBuffer.append( '-' );
1059         nValue = -nLength;
1060     }
1061     Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1062                              m_pWriter->m_aMapMode,
1063                              m_pWriter,
1064                              Size( nValue, nValue ) ) );
1065     nValue = bVertical ? aSize.Height() : aSize.Width();
1066     if( pOutLength )
1067         *pOutLength = ((nLength < 0 ) ? -nValue : nValue);
1068 
1069     appendFixedInt( nValue, rBuffer );
1070 }
1071 
1072 void PDFPage::appendMappedLength( double fLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32 nPrecision ) const
1073 {
1074     Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1075                              m_pWriter->m_aMapMode,
1076                              m_pWriter,
1077                              Size( 1000, 1000 ) ) );
1078     fLength *= pixelToPoint(static_cast<double>(bVertical ? aSize.Height() : aSize.Width()) / 1000.0);
1079     appendDouble( fLength, rBuffer, nPrecision );
1080 }
1081 
1082 bool PDFPage::appendLineInfo( const LineInfo& rInfo, OStringBuffer& rBuffer ) const
1083 {
1084     if(LineStyle::Dash == rInfo.GetStyle() && rInfo.GetDashLen() != rInfo.GetDotLen())
1085     {
1086         // dashed and non-degraded case, check for implementation limits of dash array
1087         // in PDF reader apps (e.g. acroread)
1088         if(2 * (rInfo.GetDashCount() + rInfo.GetDotCount()) > 10)
1089         {
1090             return false;
1091         }
1092     }
1093 
1094     if(basegfx::B2DLineJoin::NONE != rInfo.GetLineJoin())
1095     {
1096         // LineJoin used, ExtLineInfo required
1097         return false;
1098     }
1099 
1100     if(css::drawing::LineCap_BUTT != rInfo.GetLineCap())
1101     {
1102         // LineCap used, ExtLineInfo required
1103         return false;
1104     }
1105 
1106     if( rInfo.GetStyle() == LineStyle::Dash )
1107     {
1108         rBuffer.append( "[ " );
1109         if( rInfo.GetDashLen() == rInfo.GetDotLen() ) // degraded case
1110         {
1111             appendMappedLength( rInfo.GetDashLen(), rBuffer );
1112             rBuffer.append( ' ' );
1113             appendMappedLength( rInfo.GetDistance(), rBuffer );
1114             rBuffer.append( ' ' );
1115         }
1116         else
1117         {
1118             for( int n = 0; n < rInfo.GetDashCount(); n++ )
1119             {
1120                 appendMappedLength( rInfo.GetDashLen(), rBuffer );
1121                 rBuffer.append( ' ' );
1122                 appendMappedLength( rInfo.GetDistance(), rBuffer );
1123                 rBuffer.append( ' ' );
1124             }
1125             for( int m = 0; m < rInfo.GetDotCount(); m++ )
1126             {
1127                 appendMappedLength( rInfo.GetDotLen(), rBuffer );
1128                 rBuffer.append( ' ' );
1129                 appendMappedLength( rInfo.GetDistance(), rBuffer );
1130                 rBuffer.append( ' ' );
1131             }
1132         }
1133         rBuffer.append( "] 0 d\n" );
1134     }
1135 
1136     if( rInfo.GetWidth() > 1 )
1137     {
1138         appendMappedLength( rInfo.GetWidth(), rBuffer );
1139         rBuffer.append( " w\n" );
1140     }
1141     else if( rInfo.GetWidth() == 0 )
1142     {
1143         // "pixel" line
1144         appendDouble( 72.0/double(m_pWriter->GetDPIX()), rBuffer );
1145         rBuffer.append( " w\n" );
1146     }
1147 
1148     return true;
1149 }
1150 
1151 void PDFPage::appendWaveLine( sal_Int32 nWidth, sal_Int32 nY, sal_Int32 nDelta, OStringBuffer& rBuffer ) const
1152 {
1153     if( nWidth <= 0 )
1154         return;
1155     if( nDelta < 1 )
1156         nDelta = 1;
1157 
1158     rBuffer.append( "0 " );
1159     appendMappedLength( nY, rBuffer );
1160     rBuffer.append( " m\n" );
1161     for( sal_Int32 n = 0; n < nWidth; )
1162     {
1163         n += nDelta;
1164         appendMappedLength( n, rBuffer, false );
1165         rBuffer.append( ' ' );
1166         appendMappedLength( nDelta+nY, rBuffer );
1167         rBuffer.append( ' ' );
1168         n += nDelta;
1169         appendMappedLength( n, rBuffer, false );
1170         rBuffer.append( ' ' );
1171         appendMappedLength( nY, rBuffer );
1172         rBuffer.append( " v " );
1173         if( n < nWidth )
1174         {
1175             n += nDelta;
1176             appendMappedLength( n, rBuffer, false );
1177             rBuffer.append( ' ' );
1178             appendMappedLength( nY-nDelta, rBuffer );
1179             rBuffer.append( ' ' );
1180             n += nDelta;
1181             appendMappedLength( n, rBuffer, false );
1182             rBuffer.append( ' ' );
1183             appendMappedLength( nY, rBuffer );
1184             rBuffer.append( " v\n" );
1185         }
1186     }
1187     rBuffer.append( "S\n" );
1188 }
1189 
1190 void PDFPage::appendMatrix3(Matrix3 const & rMatrix, OStringBuffer& rBuffer)
1191 {
1192     appendDouble(rMatrix.get(0), rBuffer);
1193     rBuffer.append(' ');
1194     appendDouble(rMatrix.get(1), rBuffer);
1195     rBuffer.append(' ');
1196     appendDouble(rMatrix.get(2), rBuffer);
1197     rBuffer.append(' ');
1198     appendDouble(rMatrix.get(3), rBuffer);
1199     rBuffer.append(' ');
1200     appendPoint(Point(long(rMatrix.get(4)), long(rMatrix.get(5))), rBuffer);
1201 }
1202 
1203 double PDFPage::getHeight() const
1204 {
1205     double fRet = m_nPageHeight ? m_nPageHeight : vcl::pdf::g_nInheritedPageHeight;
1206 
1207     if (m_nUserUnit > 1)
1208     {
1209         fRet /= m_nUserUnit;
1210     }
1211 
1212     return fRet;
1213 }
1214 
1215 PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext,
1216                                const css::uno::Reference< css::beans::XMaterialHolder >& xEnc,
1217                                PDFWriter& i_rOuterFace)
1218         : VirtualDevice(Application::GetDefaultDevice(), DeviceFormat::DEFAULT, DeviceFormat::NONE, OUTDEV_PDF),
1219         m_aMapMode( MapUnit::MapPoint, Point(), Fraction( 1, pointToPixel(1) ), Fraction( 1, pointToPixel(1) ) ),
1220         m_nCurrentStructElement( 0 ),
1221         m_bEmitStructure( true ),
1222         m_nNextFID( 1 ),
1223         m_aPDFBmpCache(
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_aCipher( nullptr ),
1237         m_nKeyLength(0),
1238         m_nRC4KeyLength(0),
1239         m_bEncryptThisStream( false ),
1240         m_nAccessPermissions(0),
1241         m_bIsPDF_A1( false ),
1242         m_bIsPDF_A2( false ),
1243         m_bIsPDF_UA( false ),
1244         m_bIsPDF_A3( false ),
1245         m_rOuterFace( i_rOuterFace )
1246 {
1247     m_aStructure.emplace_back( );
1248     m_aStructure[0].m_nOwnElement       = 0;
1249     m_aStructure[0].m_nParentElement    = 0;
1250 
1251     Font aFont;
1252     aFont.SetFamilyName( "Times" );
1253     aFont.SetFontSize( Size( 0, 12 ) );
1254 
1255     GraphicsState aState;
1256     aState.m_aMapMode       = m_aMapMode;
1257     aState.m_aFont          = aFont;
1258     m_aGraphicsStack.push_front( aState );
1259 
1260     osl::File::RC aError = m_aFile.open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create);
1261     if (aError != osl::File::E_None)
1262     {
1263         if (aError == osl::File::E_EXIST)
1264         {
1265             aError = m_aFile.open(osl_File_OpenFlag_Write);
1266             if (aError == osl::File::E_None)
1267                 aError = m_aFile.setSize(0);
1268         }
1269     }
1270     if (aError != osl::File::E_None)
1271         return;
1272 
1273     m_bOpen = true;
1274 
1275     // setup DocInfo
1276     setupDocInfo();
1277 
1278     /* prepare the cypher engine, can be done in CTOR, free in DTOR */
1279     m_aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream );
1280 
1281     if( xEnc.is() )
1282         prepareEncryption( xEnc );
1283 
1284     if( m_aContext.Encryption.Encrypt() )
1285     {
1286         // sanity check
1287         if( m_aContext.Encryption.OValue.size() != ENCRYPTED_PWD_SIZE ||
1288             m_aContext.Encryption.UValue.size() != ENCRYPTED_PWD_SIZE ||
1289             m_aContext.Encryption.EncryptionKey.size() != MAXIMUM_RC4_KEY_LENGTH
1290            )
1291         {
1292             // the field lengths are invalid ? This was not setup by initEncryption.
1293             // do not encrypt after all
1294             m_aContext.Encryption.OValue.clear();
1295             m_aContext.Encryption.UValue.clear();
1296             OSL_ENSURE( false, "encryption data failed sanity check, encryption disabled" );
1297         }
1298         else // setup key lengths
1299             m_nAccessPermissions = computeAccessPermissions( m_aContext.Encryption, m_nKeyLength, m_nRC4KeyLength );
1300     }
1301 
1302     // write header
1303     OStringBuffer aBuffer( 20 );
1304     aBuffer.append( "%PDF-" );
1305     switch( m_aContext.Version )
1306     {
1307         case PDFWriter::PDFVersion::PDF_1_2: aBuffer.append( "1.2" );break;
1308         case PDFWriter::PDFVersion::PDF_1_3: aBuffer.append( "1.3" );break;
1309         case PDFWriter::PDFVersion::PDF_A_1:
1310         case PDFWriter::PDFVersion::PDF_1_4: aBuffer.append( "1.4" );break;
1311         case PDFWriter::PDFVersion::PDF_1_5: aBuffer.append( "1.5" );break;
1312         default:
1313         case PDFWriter::PDFVersion::PDF_1_6: aBuffer.append( "1.6" );break;
1314     }
1315     // append something binary as comment (suggested in PDF Reference)
1316     aBuffer.append( "\n%\303\244\303\274\303\266\303\237\n" );
1317     if( !writeBuffer( aBuffer.getStr(), aBuffer.getLength() ) )
1318     {
1319         m_aFile.close();
1320         m_bOpen = false;
1321         return;
1322     }
1323 
1324     // insert outline root
1325     m_aOutline.emplace_back( );
1326 
1327     m_bIsPDF_A1 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_1);
1328     if( m_bIsPDF_A1 )
1329         m_aContext.Version = PDFWriter::PDFVersion::PDF_1_4; //meaning we need PDF 1.4, PDF/A flavour
1330 
1331     m_bIsPDF_A2 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_2);
1332     if( m_bIsPDF_A2 )
1333         m_aContext.Version = PDFWriter::PDFVersion::PDF_1_6; //we could even use 1.7 features
1334 
1335     m_bIsPDF_A3 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_3);
1336     if( m_bIsPDF_A3 )
1337         m_aContext.Version = PDFWriter::PDFVersion::PDF_1_6; //we could even use 1.7 features
1338 
1339     if (m_aContext.UniversalAccessibilityCompliance)
1340     {
1341         m_bIsPDF_UA = true;
1342         m_aContext.Tagged = true;
1343     }
1344 
1345     if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 )
1346         SetReferenceDevice( VirtualDevice::RefDevMode::PDF1 );
1347     else
1348         SetReferenceDevice( m_aContext.DPIx, m_aContext.DPIy );
1349 
1350     SetOutputSizePixel( Size( 640, 480 ) );
1351     SetMapMode(MapMode(MapUnit::MapMM));
1352 }
1353 
1354 PDFWriterImpl::~PDFWriterImpl()
1355 {
1356     disposeOnce();
1357 }
1358 
1359 void PDFWriterImpl::dispose()
1360 {
1361     if( m_aCipher )
1362         rtl_cipher_destroyARCFOUR( m_aCipher );
1363     m_aPages.clear();
1364     VirtualDevice::dispose();
1365 }
1366 
1367 void PDFWriterImpl::setupDocInfo()
1368 {
1369     std::vector< sal_uInt8 > aId;
1370     m_aCreationDateString = PDFWriter::GetDateTime();
1371     computeDocumentIdentifier( aId, m_aContext.DocumentInfo, m_aCreationDateString, m_aCreationMetaDateString );
1372     if( m_aContext.Encryption.DocumentIdentifier.empty() )
1373         m_aContext.Encryption.DocumentIdentifier = aId;
1374 }
1375 
1376 OString PDFWriter::GetDateTime()
1377 {
1378     OStringBuffer aRet;
1379 
1380     TimeValue aTVal, aGMT;
1381     oslDateTime aDT;
1382     osl_getSystemTime(&aGMT);
1383     osl_getLocalTimeFromSystemTime(&aGMT, &aTVal);
1384     osl_getDateTimeFromTimeValue(&aTVal, &aDT);
1385 
1386     sal_Int32 nDelta = aTVal.Seconds-aGMT.Seconds;
1387 
1388     appendPdfTimeDate(aRet, aDT.Year, aDT.Month, aDT.Day, aDT.Hours, aDT.Minutes, aDT.Seconds, nDelta);
1389 
1390     aRet.append("'");
1391     return aRet.makeStringAndClear();
1392 }
1393 
1394 void PDFWriterImpl::computeDocumentIdentifier( std::vector< sal_uInt8 >& o_rIdentifier,
1395                                                const vcl::PDFWriter::PDFDocInfo& i_rDocInfo,
1396                                                const OString& i_rCString1,
1397                                                OString& o_rCString2
1398                                                )
1399 {
1400     o_rIdentifier.clear();
1401 
1402     //build the document id
1403     OString aInfoValuesOut;
1404     OStringBuffer aID( 1024 );
1405     if( !i_rDocInfo.Title.isEmpty() )
1406         PDFWriter::AppendUnicodeTextString(i_rDocInfo.Title, aID);
1407     if( !i_rDocInfo.Author.isEmpty() )
1408         PDFWriter::AppendUnicodeTextString(i_rDocInfo.Author, aID);
1409     if( !i_rDocInfo.Subject.isEmpty() )
1410         PDFWriter::AppendUnicodeTextString(i_rDocInfo.Subject, aID);
1411     if( !i_rDocInfo.Keywords.isEmpty() )
1412         PDFWriter::AppendUnicodeTextString(i_rDocInfo.Keywords, aID);
1413     if( !i_rDocInfo.Creator.isEmpty() )
1414         PDFWriter::AppendUnicodeTextString(i_rDocInfo.Creator, aID);
1415     if( !i_rDocInfo.Producer.isEmpty() )
1416         PDFWriter::AppendUnicodeTextString(i_rDocInfo.Producer, aID);
1417 
1418     TimeValue aTVal, aGMT;
1419     oslDateTime aDT;
1420     osl_getSystemTime( &aGMT );
1421     osl_getLocalTimeFromSystemTime( &aGMT, &aTVal );
1422     osl_getDateTimeFromTimeValue( &aTVal, &aDT );
1423     OStringBuffer aCreationMetaDateString(64);
1424 
1425     // i59651: we fill the Metadata date string as well, if PDF/A is requested
1426     // according to ISO 19005-1:2005 6.7.3 the date is corrected for
1427     // local time zone offset UTC only, whereas Acrobat 8 seems
1428     // to use the localtime notation only
1429     // according to a recommendation in XMP Specification (Jan 2004, page 75)
1430     // the Acrobat way seems the right approach
1431     aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Year/1000)%10)) );
1432     aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Year/100)%10)) );
1433     aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Year/10)%10)) );
1434     aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Year)%10)) );
1435     aCreationMetaDateString.append( "-" );
1436     aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Month/10)%10)) );
1437     aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Month)%10)) );
1438     aCreationMetaDateString.append( "-" );
1439     aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Day/10)%10)) );
1440     aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Day)%10)) );
1441     aCreationMetaDateString.append( "T" );
1442     aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Hours/10)%10)) );
1443     aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Hours)%10)) );
1444     aCreationMetaDateString.append( ":" );
1445     aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Minutes/10)%10)) );
1446     aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Minutes)%10)) );
1447     aCreationMetaDateString.append( ":" );
1448     aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Seconds/10)%10)) );
1449     aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Seconds)%10)) );
1450 
1451     sal_uInt32 nDelta = 0;
1452     if( aGMT.Seconds > aTVal.Seconds )
1453     {
1454         nDelta = aGMT.Seconds-aTVal.Seconds;
1455         aCreationMetaDateString.append( "-" );
1456     }
1457     else if( aGMT.Seconds < aTVal.Seconds )
1458     {
1459         nDelta = aTVal.Seconds-aGMT.Seconds;
1460         aCreationMetaDateString.append( "+" );
1461     }
1462     else
1463     {
1464         aCreationMetaDateString.append( "Z" );
1465 
1466     }
1467     if( nDelta )
1468     {
1469         aCreationMetaDateString.append( static_cast<char>('0' + ((nDelta/36000)%10)) );
1470         aCreationMetaDateString.append( static_cast<char>('0' + ((nDelta/3600)%10)) );
1471         aCreationMetaDateString.append( ":" );
1472         aCreationMetaDateString.append( static_cast<char>('0' + ((nDelta/600)%6)) );
1473         aCreationMetaDateString.append( static_cast<char>('0' + ((nDelta/60)%10)) );
1474     }
1475     aID.append( i_rCString1.getStr(), i_rCString1.getLength() );
1476 
1477     aInfoValuesOut = aID.makeStringAndClear();
1478     o_rCString2 = aCreationMetaDateString.makeStringAndClear();
1479 
1480     ::comphelper::Hash aDigest(::comphelper::HashType::MD5);
1481     aDigest.update(reinterpret_cast<unsigned char const*>(&aGMT), sizeof(aGMT));
1482     aDigest.update(reinterpret_cast<unsigned char const*>(aInfoValuesOut.getStr()), aInfoValuesOut.getLength());
1483     //the binary form of the doc id is needed for encryption stuff
1484     o_rIdentifier = aDigest.finalize();
1485 }
1486 
1487 /* i12626 methods */
1488 /*
1489 check if the Unicode string must be encrypted or not, perform the requested task,
1490 append the string as unicode hex, encrypted if needed
1491  */
1492 inline void PDFWriterImpl::appendUnicodeTextStringEncrypt( const OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
1493 {
1494     rOutBuffer.append( "<" );
1495     if( m_aContext.Encryption.Encrypt() )
1496     {
1497         const sal_Unicode* pStr = rInString.getStr();
1498         sal_Int32 nLen = rInString.getLength();
1499         //prepare a unicode string, encrypt it
1500         enableStringEncryption( nInObjectNumber );
1501         sal_uInt8 *pCopy = m_vEncryptionBuffer.data();
1502         sal_Int32 nChars = 2 + (nLen * 2);
1503         m_vEncryptionBuffer.resize(nChars);
1504         *pCopy++ = 0xFE;
1505         *pCopy++ = 0xFF;
1506         // we need to prepare a byte stream from the unicode string buffer
1507         for( int i = 0; i < nLen; i++ )
1508         {
1509             sal_Unicode aUnChar = pStr[i];
1510             *pCopy++ = static_cast<sal_uInt8>( aUnChar >> 8 );
1511             *pCopy++ = static_cast<sal_uInt8>( aUnChar & 255 );
1512         }
1513         //encrypt in place
1514         rtl_cipher_encodeARCFOUR( m_aCipher, m_vEncryptionBuffer.data(), nChars, m_vEncryptionBuffer.data(), nChars );
1515         //now append, hexadecimal (appendHex), the encrypted result
1516         for(int i = 0; i < nChars; i++)
1517             appendHex( m_vEncryptionBuffer[i], rOutBuffer );
1518     }
1519     else
1520         PDFWriter::AppendUnicodeTextString(rInString, rOutBuffer);
1521     rOutBuffer.append( ">" );
1522 }
1523 
1524 inline void PDFWriterImpl::appendLiteralStringEncrypt( OStringBuffer const & rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
1525 {
1526     rOutBuffer.append( "(" );
1527     sal_Int32 nChars = rInString.getLength();
1528     //check for encryption, if ok, encrypt the string, then convert with appndLiteralString
1529     if( m_aContext.Encryption.Encrypt() )
1530     {
1531         m_vEncryptionBuffer.resize(nChars);
1532         //encrypt the string in a buffer, then append it
1533         enableStringEncryption( nInObjectNumber );
1534         rtl_cipher_encodeARCFOUR( m_aCipher, rInString.getStr(), nChars, m_vEncryptionBuffer.data(), nChars );
1535         appendLiteralString( reinterpret_cast<char*>(m_vEncryptionBuffer.data()), nChars, rOutBuffer );
1536     }
1537     else
1538         appendLiteralString( rInString.getStr(), nChars , rOutBuffer );
1539     rOutBuffer.append( ")" );
1540 }
1541 
1542 inline void PDFWriterImpl::appendLiteralStringEncrypt( const OString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
1543 {
1544     OStringBuffer aBufferString( rInString );
1545     appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer);
1546 }
1547 
1548 void PDFWriterImpl::appendLiteralStringEncrypt( const OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer, rtl_TextEncoding nEnc )
1549 {
1550     OString aBufferString( OUStringToOString( rInString, nEnc ) );
1551     sal_Int32 nLen = aBufferString.getLength();
1552     OStringBuffer aBuf( nLen );
1553     const char* pT = aBufferString.getStr();
1554 
1555     for( sal_Int32 i = 0; i < nLen; i++, pT++ )
1556     {
1557         if( (*pT & 0x80) == 0 )
1558             aBuf.append( *pT );
1559         else
1560         {
1561             aBuf.append( '<' );
1562             appendHex( *pT, aBuf );
1563             aBuf.append( '>' );
1564         }
1565     }
1566     aBufferString = aBuf.makeStringAndClear();
1567     appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer);
1568 }
1569 
1570 /* end i12626 methods */
1571 
1572 void PDFWriterImpl::emitComment( const char* pComment )
1573 {
1574     OString aLine = "% " + rtl::OStringView(pComment) + "\n";
1575     writeBuffer( aLine.getStr(), aLine.getLength() );
1576 }
1577 
1578 bool PDFWriterImpl::compressStream( SvMemoryStream* pStream )
1579 {
1580     if (!g_bDebugDisableCompression)
1581     {
1582         sal_uLong nEndPos = pStream->TellEnd();
1583         pStream->Seek( STREAM_SEEK_TO_BEGIN );
1584         ZCodec aCodec( 0x4000, 0x4000 );
1585         SvMemoryStream aStream;
1586         aCodec.BeginCompression();
1587         aCodec.Write( aStream, static_cast<const sal_uInt8*>(pStream->GetData()), nEndPos );
1588         aCodec.EndCompression();
1589         nEndPos = aStream.Tell();
1590         pStream->Seek( STREAM_SEEK_TO_BEGIN );
1591         aStream.Seek( STREAM_SEEK_TO_BEGIN );
1592         pStream->SetStreamSize( nEndPos );
1593         pStream->WriteBytes( aStream.GetData(), nEndPos );
1594         return true;
1595     }
1596     else
1597         return false;
1598 }
1599 
1600 void PDFWriterImpl::beginCompression()
1601 {
1602     if (!g_bDebugDisableCompression)
1603     {
1604         m_pCodec = std::make_unique<ZCodec>( 0x4000, 0x4000 );
1605         m_pMemStream = std::make_unique<SvMemoryStream>();
1606         m_pCodec->BeginCompression();
1607     }
1608 }
1609 
1610 void PDFWriterImpl::endCompression()
1611 {
1612     if (!g_bDebugDisableCompression && m_pCodec)
1613     {
1614         m_pCodec->EndCompression();
1615         m_pCodec.reset();
1616         sal_uInt64 nLen = m_pMemStream->Tell();
1617         m_pMemStream->Seek( 0 );
1618         writeBuffer( m_pMemStream->GetData(), nLen );
1619         m_pMemStream.reset();
1620     }
1621 }
1622 
1623 bool PDFWriterImpl::writeBuffer( const void* pBuffer, sal_uInt64 nBytes )
1624 {
1625     if( ! m_bOpen ) // we are already down the drain
1626         return false;
1627 
1628     if( ! nBytes ) // huh ?
1629         return true;
1630 
1631     if( !m_aOutputStreams.empty() )
1632     {
1633         m_aOutputStreams.front().m_pStream->Seek( STREAM_SEEK_TO_END );
1634         m_aOutputStreams.front().m_pStream->WriteBytes(
1635                 pBuffer, sal::static_int_cast<std::size_t>(nBytes));
1636         return true;
1637     }
1638 
1639     sal_uInt64 nWritten;
1640     if( m_pCodec )
1641     {
1642         m_pCodec->Write( *m_pMemStream, static_cast<const sal_uInt8*>(pBuffer), static_cast<sal_uLong>(nBytes) );
1643         nWritten = nBytes;
1644     }
1645     else
1646     {
1647         bool  buffOK = true;
1648         if( m_bEncryptThisStream )
1649         {
1650             /* implement the encryption part of the PDF spec encryption algorithm 3.1 */
1651             m_vEncryptionBuffer.resize(nBytes);
1652             if( buffOK )
1653                 rtl_cipher_encodeARCFOUR( m_aCipher,
1654                                           pBuffer, static_cast<sal_Size>(nBytes),
1655                                           m_vEncryptionBuffer.data(), static_cast<sal_Size>(nBytes) );
1656         }
1657 
1658         const void* pWriteBuffer = ( m_bEncryptThisStream && buffOK ) ? m_vEncryptionBuffer.data()  : pBuffer;
1659         m_DocDigest.update(static_cast<unsigned char const*>(pWriteBuffer), static_cast<sal_uInt32>(nBytes));
1660 
1661         if (m_aFile.write(pWriteBuffer, nBytes, nWritten) != osl::File::E_None)
1662             nWritten = 0;
1663 
1664         if( nWritten != nBytes )
1665         {
1666             m_aFile.close();
1667             m_bOpen = false;
1668         }
1669     }
1670 
1671     return nWritten == nBytes;
1672 }
1673 
1674 void PDFWriterImpl::newPage( double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
1675 {
1676     endPage();
1677     m_nCurrentPage = m_aPages.size();
1678     m_aPages.emplace_back(this, nPageWidth, nPageHeight, eOrientation );
1679 
1680     sal_Int32 nUserUnit = m_aPages.back().m_nUserUnit;
1681     if (nUserUnit > 1)
1682     {
1683         m_aMapMode = MapMode(MapUnit::MapPoint, Point(), Fraction(nUserUnit, pointToPixel(1)),
1684                              Fraction(nUserUnit, pointToPixel(1)));
1685     }
1686 
1687     m_aPages.back().beginStream();
1688 
1689     // setup global graphics state
1690     // linewidth is "1 pixel" by default
1691     OStringBuffer aBuf( 16 );
1692     appendDouble( 72.0/double(GetDPIX()), aBuf );
1693     aBuf.append( " w\n" );
1694     writeBuffer( aBuf.getStr(), aBuf.getLength() );
1695 }
1696 
1697 void PDFWriterImpl::endPage()
1698 {
1699     if( m_aPages.empty() )
1700         return;
1701 
1702     // close eventual MC sequence
1703     endStructureElementMCSeq();
1704 
1705     // sanity check
1706     if( !m_aOutputStreams.empty() )
1707     {
1708         OSL_FAIL( "redirection across pages !!!" );
1709         m_aOutputStreams.clear(); // leak !
1710         m_aMapMode.SetOrigin( Point() );
1711     }
1712 
1713     m_aGraphicsStack.clear();
1714     m_aGraphicsStack.emplace_back( );
1715 
1716     // this should pop the PDF graphics stack if necessary
1717     updateGraphicsState();
1718 
1719     m_aPages.back().endStream();
1720 
1721     // reset the default font
1722     Font aFont;
1723     aFont.SetFamilyName( "Times" );
1724     aFont.SetFontSize( Size( 0, 12 ) );
1725 
1726     m_aCurrentPDFState = m_aGraphicsStack.front();
1727     m_aGraphicsStack.front().m_aFont =  aFont;
1728 
1729     for (auto & bitmap : m_aBitmaps)
1730     {
1731         if( ! bitmap.m_aBitmap.IsEmpty() )
1732         {
1733             writeBitmapObject(bitmap);
1734             bitmap.m_aBitmap = BitmapEx();
1735         }
1736     }
1737     for (auto & jpeg : m_aJPGs)
1738     {
1739         if( jpeg.m_pStream )
1740         {
1741             writeJPG( jpeg );
1742             jpeg.m_pStream.reset();
1743             jpeg.m_aMask = Bitmap();
1744         }
1745     }
1746     for (auto & item : m_aTransparentObjects)
1747     {
1748         if( item.m_pContentStream )
1749         {
1750             writeTransparentObject(item);
1751             item.m_pContentStream.reset();
1752         }
1753     }
1754 
1755 }
1756 
1757 sal_Int32 PDFWriterImpl::createObject()
1758 {
1759     m_aObjects.push_back( ~0U );
1760     return m_aObjects.size();
1761 }
1762 
1763 bool PDFWriterImpl::updateObject( sal_Int32 n )
1764 {
1765     if( ! m_bOpen )
1766         return false;
1767 
1768     sal_uInt64 nOffset = ~0U;
1769     osl::File::RC aError = m_aFile.getPos(nOffset);
1770     SAL_WARN_IF( aError != osl::File::E_None, "vcl.pdfwriter", "could not register object" );
1771     if (aError != osl::File::E_None)
1772     {
1773         m_aFile.close();
1774         m_bOpen = false;
1775     }
1776     m_aObjects[ n-1 ] = nOffset;
1777     return aError == osl::File::E_None;
1778 }
1779 
1780 #define CHECK_RETURN( x ) if( !(x) ) return 0
1781 #define CHECK_RETURN2( x ) if( !(x) ) return
1782 
1783 sal_Int32 PDFWriterImpl::emitStructParentTree( sal_Int32 nObject )
1784 {
1785     if( nObject > 0 )
1786     {
1787         OStringBuffer aLine( 1024 );
1788 
1789         aLine.append( nObject );
1790         aLine.append( " 0 obj\n"
1791                       "<</Nums[\n" );
1792         sal_Int32 nTreeItems = m_aStructParentTree.size();
1793         for( sal_Int32 n = 0; n < nTreeItems; n++ )
1794         {
1795             aLine.append( n );
1796             aLine.append( ' ' );
1797             aLine.append( m_aStructParentTree[n] );
1798             aLine.append( "\n" );
1799         }
1800         aLine.append( "]>>\nendobj\n\n" );
1801         CHECK_RETURN( updateObject( nObject ) );
1802         CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
1803     }
1804     return nObject;
1805 }
1806 
1807 const char* PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr )
1808 {
1809     static std::map< PDFWriter::StructAttribute, const char* > aAttributeStrings;
1810     // fill maps once
1811     if( aAttributeStrings.empty() )
1812     {
1813         aAttributeStrings[ PDFWriter::Placement ]           = "Placement";
1814         aAttributeStrings[ PDFWriter::WritingMode ]         = "WritingMode";
1815         aAttributeStrings[ PDFWriter::SpaceBefore ]         = "SpaceBefore";
1816         aAttributeStrings[ PDFWriter::SpaceAfter ]          = "SpaceAfter";
1817         aAttributeStrings[ PDFWriter::StartIndent ]         = "StartIndent";
1818         aAttributeStrings[ PDFWriter::EndIndent ]           = "EndIndent";
1819         aAttributeStrings[ PDFWriter::TextIndent ]          = "TextIndent";
1820         aAttributeStrings[ PDFWriter::TextAlign ]           = "TextAlign";
1821         aAttributeStrings[ PDFWriter::Width ]               = "Width";
1822         aAttributeStrings[ PDFWriter::Height ]              = "Height";
1823         aAttributeStrings[ PDFWriter::BlockAlign ]          = "BlockAlign";
1824         aAttributeStrings[ PDFWriter::InlineAlign ]         = "InlineAlign";
1825         aAttributeStrings[ PDFWriter::LineHeight ]          = "LineHeight";
1826         aAttributeStrings[ PDFWriter::BaselineShift ]       = "BaselineShift";
1827         aAttributeStrings[ PDFWriter::TextDecorationType ]  = "TextDecorationType";
1828         aAttributeStrings[ PDFWriter::ListNumbering ]       = "ListNumbering";
1829         aAttributeStrings[ PDFWriter::RowSpan ]             = "RowSpan";
1830         aAttributeStrings[ PDFWriter::ColSpan ]             = "ColSpan";
1831         aAttributeStrings[ PDFWriter::LinkAnnotation ]      = "LinkAnnotation";
1832     }
1833 
1834     std::map< PDFWriter::StructAttribute, const char* >::const_iterator it =
1835         aAttributeStrings.find( eAttr );
1836 
1837     if( it == aAttributeStrings.end() )
1838         SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttribute " << eAttr);
1839 
1840     return it != aAttributeStrings.end() ? it->second : "";
1841 }
1842 
1843 const char* PDFWriterImpl::getAttributeValueTag( PDFWriter::StructAttributeValue eVal )
1844 {
1845     static std::map< PDFWriter::StructAttributeValue, const char* > aValueStrings;
1846 
1847     if( aValueStrings.empty() )
1848     {
1849         aValueStrings[ PDFWriter::NONE ]                    = "None";
1850         aValueStrings[ PDFWriter::Block ]                   = "Block";
1851         aValueStrings[ PDFWriter::Inline ]                  = "Inline";
1852         aValueStrings[ PDFWriter::Before ]                  = "Before";
1853         aValueStrings[ PDFWriter::After ]                   = "After";
1854         aValueStrings[ PDFWriter::Start ]                   = "Start";
1855         aValueStrings[ PDFWriter::End ]                     = "End";
1856         aValueStrings[ PDFWriter::LrTb ]                    = "LrTb";
1857         aValueStrings[ PDFWriter::RlTb ]                    = "RlTb";
1858         aValueStrings[ PDFWriter::TbRl ]                    = "TbRl";
1859         aValueStrings[ PDFWriter::Center ]                  = "Center";
1860         aValueStrings[ PDFWriter::Justify ]                 = "Justify";
1861         aValueStrings[ PDFWriter::Auto ]                    = "Auto";
1862         aValueStrings[ PDFWriter::Middle ]                  = "Middle";
1863         aValueStrings[ PDFWriter::Normal ]                  = "Normal";
1864         aValueStrings[ PDFWriter::Underline ]               = "Underline";
1865         aValueStrings[ PDFWriter::Overline ]                = "Overline";
1866         aValueStrings[ PDFWriter::LineThrough ]             = "LineThrough";
1867         aValueStrings[ PDFWriter::Disc ]                    = "Disc";
1868         aValueStrings[ PDFWriter::Circle ]                  = "Circle";
1869         aValueStrings[ PDFWriter::Square ]                  = "Square";
1870         aValueStrings[ PDFWriter::Decimal ]                 = "Decimal";
1871         aValueStrings[ PDFWriter::UpperRoman ]              = "UpperRoman";
1872         aValueStrings[ PDFWriter::LowerRoman ]              = "LowerRoman";
1873         aValueStrings[ PDFWriter::UpperAlpha ]              = "UpperAlpha";
1874         aValueStrings[ PDFWriter::LowerAlpha ]              = "LowerAlpha";
1875     }
1876 
1877     std::map< PDFWriter::StructAttributeValue, const char* >::const_iterator it =
1878         aValueStrings.find( eVal );
1879 
1880     if( it == aValueStrings.end() )
1881         SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttributeValue " << eVal);
1882 
1883     return it != aValueStrings.end() ? it->second : "";
1884 }
1885 
1886 static void appendStructureAttributeLine( PDFWriter::StructAttribute i_eAttr, const PDFStructureAttribute& i_rVal, OStringBuffer& o_rLine, bool i_bIsFixedInt )
1887 {
1888     o_rLine.append( "/" );
1889     o_rLine.append( PDFWriterImpl::getAttributeTag( i_eAttr ) );
1890 
1891     if( i_rVal.eValue != PDFWriter::Invalid )
1892     {
1893         o_rLine.append( "/" );
1894         o_rLine.append( PDFWriterImpl::getAttributeValueTag( i_rVal.eValue ) );
1895     }
1896     else
1897     {
1898         // numerical value
1899         o_rLine.append( " " );
1900         if( i_bIsFixedInt )
1901             appendFixedInt( i_rVal.nValue, o_rLine );
1902         else
1903             o_rLine.append( i_rVal.nValue );
1904     }
1905     o_rLine.append( "\n" );
1906 }
1907 
1908 OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle )
1909 {
1910     // create layout, list and table attribute sets
1911     OStringBuffer aLayout(256), aList(64), aTable(64);
1912     for (auto const& attribute : i_rEle.m_aAttributes)
1913     {
1914         if( attribute.first == PDFWriter::ListNumbering )
1915             appendStructureAttributeLine( attribute.first, attribute.second, aList, true );
1916         else if( attribute.first == PDFWriter::RowSpan ||
1917                  attribute.first == PDFWriter::ColSpan )
1918             appendStructureAttributeLine( attribute.first, attribute.second, aTable, false );
1919         else if( attribute.first == PDFWriter::LinkAnnotation )
1920         {
1921             sal_Int32 nLink = attribute.second.nValue;
1922             std::map< sal_Int32, sal_Int32 >::const_iterator link_it =
1923                 m_aLinkPropertyMap.find( nLink );
1924             if( link_it != m_aLinkPropertyMap.end() )
1925                 nLink = link_it->second;
1926             if( nLink >= 0 && nLink < static_cast<sal_Int32>(m_aLinks.size()) )
1927             {
1928                 // update struct parent of link
1929                 OString aStructParentEntry =
1930                     OString::number( i_rEle.m_nObject ) +
1931                     " 0 R";
1932                 m_aStructParentTree.push_back( aStructParentEntry );
1933                 m_aLinks[ nLink ].m_nStructParent = m_aStructParentTree.size()-1;
1934 
1935                 sal_Int32 nRefObject = createObject();
1936                 if (updateObject(nRefObject))
1937                 {
1938                     OString aRef =
1939                         OString::number( nRefObject ) +
1940                         " 0 obj\n"
1941                         "<</Type/OBJR/Obj " +
1942                         OString::number( m_aLinks[ nLink ].m_nObject ) +
1943                         " 0 R>>\n"
1944                         "endobj\n\n";
1945                     writeBuffer( aRef.getStr(), aRef.getLength() );
1946                 }
1947 
1948                 i_rEle.m_aKids.emplace_back( nRefObject );
1949             }
1950             else
1951             {
1952                 OSL_FAIL( "unresolved link id for Link structure" );
1953                 SAL_INFO("vcl.pdfwriter", "unresolved link id " << nLink << " for Link structure");
1954                 if (g_bDebugDisableCompression)
1955                 {
1956                     OString aLine = "unresolved link id " +
1957                             OString::number( nLink ) +
1958                             " for Link structure";
1959                     emitComment( aLine.getStr() );
1960                 }
1961             }
1962         }
1963         else
1964             appendStructureAttributeLine( attribute.first, attribute.second, aLayout, true );
1965     }
1966     if( ! i_rEle.m_aBBox.IsEmpty() )
1967     {
1968         aLayout.append( "/BBox[" );
1969         appendFixedInt( i_rEle.m_aBBox.Left(), aLayout );
1970         aLayout.append( " " );
1971         appendFixedInt( i_rEle.m_aBBox.Top(), aLayout );
1972         aLayout.append( " " );
1973         appendFixedInt( i_rEle.m_aBBox.Right(), aLayout );
1974         aLayout.append( " " );
1975         appendFixedInt( i_rEle.m_aBBox.Bottom(), aLayout );
1976         aLayout.append( "]\n" );
1977     }
1978 
1979     std::vector< sal_Int32 > aAttribObjects;
1980     if( !aLayout.isEmpty() )
1981     {
1982         aAttribObjects.push_back( createObject() );
1983         if (updateObject( aAttribObjects.back() ))
1984         {
1985             OStringBuffer aObj( 64 );
1986             aObj.append( aAttribObjects.back() );
1987             aObj.append( " 0 obj\n"
1988                          "<</O/Layout\n" );
1989             aLayout.append( ">>\nendobj\n\n" );
1990             writeBuffer( aObj.getStr(), aObj.getLength() );
1991             writeBuffer( aLayout.getStr(), aLayout.getLength() );
1992         }
1993     }
1994     if( !aList.isEmpty() )
1995     {
1996         aAttribObjects.push_back( createObject() );
1997         if (updateObject( aAttribObjects.back() ))
1998         {
1999             OStringBuffer aObj( 64 );
2000             aObj.append( aAttribObjects.back() );
2001             aObj.append( " 0 obj\n"
2002                          "<</O/List\n" );
2003             aList.append( ">>\nendobj\n\n" );
2004             writeBuffer( aObj.getStr(), aObj.getLength() );
2005             writeBuffer( aList.getStr(), aList.getLength() );
2006         }
2007     }
2008     if( !aTable.isEmpty() )
2009     {
2010         aAttribObjects.push_back( createObject() );
2011         if (updateObject( aAttribObjects.back() ))
2012         {
2013             OStringBuffer aObj( 64 );
2014             aObj.append( aAttribObjects.back() );
2015             aObj.append( " 0 obj\n"
2016                          "<</O/Table\n" );
2017             aTable.append( ">>\nendobj\n\n" );
2018             writeBuffer( aObj.getStr(), aObj.getLength() );
2019             writeBuffer( aTable.getStr(), aTable.getLength() );
2020         }
2021     }
2022 
2023     OStringBuffer aRet( 64 );
2024     if( aAttribObjects.size() > 1 )
2025         aRet.append( " [" );
2026     for (auto const& attrib : aAttribObjects)
2027     {
2028         aRet.append( " " );
2029         aRet.append( attrib );
2030         aRet.append( " 0 R" );
2031     }
2032     if( aAttribObjects.size() > 1 )
2033         aRet.append( " ]" );
2034     return aRet.makeStringAndClear();
2035 }
2036 
2037 sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle )
2038 {
2039     if(
2040        // do not emit NonStruct and its children
2041        rEle.m_eType == PDFWriter::NonStructElement &&
2042        rEle.m_nOwnElement != rEle.m_nParentElement // but of course emit the struct tree root
2043        )
2044         return 0;
2045 
2046     for (auto const& child : rEle.m_aChildren)
2047     {
2048         if( child > 0 && child < sal_Int32(m_aStructure.size()) )
2049         {
2050             PDFStructureElement& rChild = m_aStructure[ child ];
2051             if( rChild.m_eType != PDFWriter::NonStructElement )
2052             {
2053                 if( rChild.m_nParentElement == rEle.m_nOwnElement )
2054                     emitStructure( rChild );
2055                 else
2056                 {
2057                     OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure element" );
2058                     SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure element with id " << child);
2059                 }
2060             }
2061         }
2062         else
2063         {
2064             OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
2065             SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure id " << child);
2066         }
2067     }
2068 
2069     OStringBuffer aLine( 512 );
2070     aLine.append( rEle.m_nObject );
2071     aLine.append( " 0 obj\n"
2072                   "<</Type" );
2073     sal_Int32 nParentTree = -1;
2074     if( rEle.m_nOwnElement == rEle.m_nParentElement )
2075     {
2076         nParentTree = createObject();
2077         CHECK_RETURN( nParentTree );
2078         aLine.append( "/StructTreeRoot\n" );
2079         aLine.append( "/ParentTree " );
2080         aLine.append( nParentTree );
2081         aLine.append( " 0 R\n" );
2082         if( ! m_aRoleMap.empty() )
2083         {
2084             aLine.append( "/RoleMap<<" );
2085             for (auto const& role : m_aRoleMap)
2086             {
2087                 aLine.append( '/' );
2088                 aLine.append(role.first);
2089                 aLine.append( '/' );
2090                 aLine.append( role.second );
2091                 aLine.append( '\n' );
2092             }
2093             aLine.append( ">>\n" );
2094         }
2095     }
2096     else
2097     {
2098         aLine.append( "/StructElem\n"
2099                       "/S/" );
2100         if( !rEle.m_aAlias.isEmpty() )
2101             aLine.append( rEle.m_aAlias );
2102         else
2103             aLine.append( getStructureTag( rEle.m_eType ) );
2104         aLine.append( "\n"
2105                       "/P " );
2106         aLine.append( m_aStructure[ rEle.m_nParentElement ].m_nObject );
2107         aLine.append( " 0 R\n"
2108                       "/Pg " );
2109         aLine.append( rEle.m_nFirstPageObject );
2110         aLine.append( " 0 R\n" );
2111         if( !rEle.m_aActualText.isEmpty() )
2112         {
2113             aLine.append( "/ActualText" );
2114             appendUnicodeTextStringEncrypt( rEle.m_aActualText, rEle.m_nObject, aLine );
2115             aLine.append( "\n" );
2116         }
2117         if( !rEle.m_aAltText.isEmpty() )
2118         {
2119             aLine.append( "/Alt" );
2120             appendUnicodeTextStringEncrypt( rEle.m_aAltText, rEle.m_nObject, aLine );
2121             aLine.append( "\n" );
2122         }
2123     }
2124     if( (! rEle.m_aBBox.IsEmpty()) || (! rEle.m_aAttributes.empty()) )
2125     {
2126         OString aAttribs =  emitStructureAttributes( rEle );
2127         if( !aAttribs.isEmpty() )
2128         {
2129             aLine.append( "/A" );
2130             aLine.append( aAttribs );
2131             aLine.append( "\n" );
2132         }
2133     }
2134     if( !rEle.m_aLocale.Language.isEmpty() )
2135     {
2136         /* PDF allows only RFC 3066, which is only partly BCP 47 and does not
2137          * include script tags and others.
2138          * http://pdf.editme.com/pdfua-naturalLanguageSpecification
2139          * http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf#page=886
2140          * https://www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf#M13.9.19332.1Heading.97.Natural.Language.Specification
2141          * */
2142         LanguageTag aLanguageTag( rEle.m_aLocale);
2143         OUString aLanguage, aScript, aCountry;
2144         aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
2145         if (!aLanguage.isEmpty())
2146         {
2147             OUStringBuffer aLocBuf( 16 );
2148             aLocBuf.append( aLanguage );
2149             if( !aCountry.isEmpty() )
2150             {
2151                 aLocBuf.append( '-' );
2152                 aLocBuf.append( aCountry );
2153             }
2154             aLine.append( "/Lang" );
2155             appendLiteralStringEncrypt( aLocBuf.makeStringAndClear(), rEle.m_nObject, aLine );
2156             aLine.append( "\n" );
2157         }
2158     }
2159     if( ! rEle.m_aKids.empty() )
2160     {
2161         unsigned int i = 0;
2162         aLine.append( "/K[" );
2163         for (auto const& kid : rEle.m_aKids)
2164         {
2165             if( kid.nMCID == -1 )
2166             {
2167                 aLine.append( kid.nObject );
2168                 aLine.append( " 0 R" );
2169                 aLine.append( ( (i & 15) == 15 ) ? "\n" : " " );
2170             }
2171             else
2172             {
2173                 if( kid.nObject == rEle.m_nFirstPageObject )
2174                 {
2175                     aLine.append( kid.nMCID );
2176                     aLine.append( " " );
2177                 }
2178                 else
2179                 {
2180                     aLine.append( "<</Type/MCR/Pg " );
2181                     aLine.append( kid.nObject );
2182                     aLine.append( " 0 R /MCID " );
2183                     aLine.append( kid.nMCID );
2184                     aLine.append( ">>\n" );
2185                 }
2186             }
2187             ++i;
2188         }
2189         aLine.append( "]\n" );
2190     }
2191     aLine.append( ">>\nendobj\n\n" );
2192 
2193     CHECK_RETURN( updateObject( rEle.m_nObject ) );
2194     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2195 
2196     CHECK_RETURN( emitStructParentTree( nParentTree ) );
2197 
2198     return rEle.m_nObject;
2199 }
2200 
2201 bool PDFWriterImpl::emitGradients()
2202 {
2203     for (auto const& gradient : m_aGradients)
2204     {
2205         if ( !writeGradientFunction( gradient ) ) return false;
2206     }
2207     return true;
2208 }
2209 
2210 bool PDFWriterImpl::emitTilings()
2211 {
2212     OStringBuffer aTilingObj( 1024 );
2213 
2214     for (auto & tiling : m_aTilings)
2215     {
2216         SAL_WARN_IF( !tiling.m_pTilingStream, "vcl.pdfwriter", "tiling without stream" );
2217         if( ! tiling.m_pTilingStream )
2218             continue;
2219 
2220         aTilingObj.setLength( 0 );
2221 
2222         if (g_bDebugDisableCompression)
2223         {
2224             emitComment( "PDFWriterImpl::emitTilings" );
2225         }
2226 
2227         sal_Int32 nX = static_cast<sal_Int32>(tiling.m_aRectangle.Left());
2228         sal_Int32 nY = static_cast<sal_Int32>(tiling.m_aRectangle.Top());
2229         sal_Int32 nW = static_cast<sal_Int32>(tiling.m_aRectangle.GetWidth());
2230         sal_Int32 nH = static_cast<sal_Int32>(tiling.m_aRectangle.GetHeight());
2231         if( tiling.m_aCellSize.Width() == 0 )
2232             tiling.m_aCellSize.setWidth( nW );
2233         if( tiling.m_aCellSize.Height() == 0 )
2234             tiling.m_aCellSize.setHeight( nH );
2235 
2236         bool bDeflate = compressStream( tiling.m_pTilingStream.get() );
2237         sal_uInt64 const nTilingStreamSize = tiling.m_pTilingStream->TellEnd();
2238         tiling.m_pTilingStream->Seek( STREAM_SEEK_TO_BEGIN );
2239 
2240         // write pattern object
2241         aTilingObj.append( tiling.m_nObject );
2242         aTilingObj.append( " 0 obj\n" );
2243         aTilingObj.append( "<</Type/Pattern/PatternType 1\n"
2244                            "/PaintType 1\n"
2245                            "/TilingType 2\n"
2246                            "/BBox[" );
2247         appendFixedInt( nX, aTilingObj );
2248         aTilingObj.append( ' ' );
2249         appendFixedInt( nY, aTilingObj );
2250         aTilingObj.append( ' ' );
2251         appendFixedInt( nX+nW, aTilingObj );
2252         aTilingObj.append( ' ' );
2253         appendFixedInt( nY+nH, aTilingObj );
2254         aTilingObj.append( "]\n"
2255                            "/XStep " );
2256         appendFixedInt( tiling.m_aCellSize.Width(), aTilingObj );
2257         aTilingObj.append( "\n"
2258                            "/YStep " );
2259         appendFixedInt( tiling.m_aCellSize.Height(), aTilingObj );
2260         aTilingObj.append( "\n" );
2261         if( tiling.m_aTransform.matrix[0] != 1.0 ||
2262             tiling.m_aTransform.matrix[1] != 0.0 ||
2263             tiling.m_aTransform.matrix[3] != 0.0 ||
2264             tiling.m_aTransform.matrix[4] != 1.0 ||
2265             tiling.m_aTransform.matrix[2] != 0.0 ||
2266             tiling.m_aTransform.matrix[5] != 0.0 )
2267         {
2268             aTilingObj.append( "/Matrix [" );
2269             // TODO: scaling, mirroring on y, etc
2270             appendDouble( tiling.m_aTransform.matrix[0], aTilingObj );
2271             aTilingObj.append( ' ' );
2272             appendDouble( tiling.m_aTransform.matrix[1], aTilingObj );
2273             aTilingObj.append( ' ' );
2274             appendDouble( tiling.m_aTransform.matrix[3], aTilingObj );
2275             aTilingObj.append( ' ' );
2276             appendDouble( tiling.m_aTransform.matrix[4], aTilingObj );
2277             aTilingObj.append( ' ' );
2278             appendDouble( tiling.m_aTransform.matrix[2], aTilingObj );
2279             aTilingObj.append( ' ' );
2280             appendDouble( tiling.m_aTransform.matrix[5], aTilingObj );
2281             aTilingObj.append( "]\n" );
2282         }
2283         aTilingObj.append( "/Resources" );
2284         tiling.m_aResources.append( aTilingObj, getFontDictObject() );
2285         if( bDeflate )
2286             aTilingObj.append( "/Filter/FlateDecode" );
2287         aTilingObj.append( "/Length " );
2288         aTilingObj.append( static_cast<sal_Int32>(nTilingStreamSize) );
2289         aTilingObj.append( ">>\nstream\n" );
2290         if ( !updateObject( tiling.m_nObject ) ) return false;
2291         if ( !writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) ) return false;
2292         checkAndEnableStreamEncryption( tiling.m_nObject );
2293         bool written = writeBuffer( tiling.m_pTilingStream->GetData(), nTilingStreamSize );
2294         tiling.m_pTilingStream.reset();
2295         if( !written )
2296             return false;
2297         disableStreamEncryption();
2298         aTilingObj.setLength( 0 );
2299         aTilingObj.append( "\nendstream\nendobj\n\n" );
2300         if ( !writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) ) return false;
2301     }
2302     return true;
2303 }
2304 
2305 sal_Int32 PDFWriterImpl::emitBuildinFont(const pdf::BuildinFontFace* pFD, sal_Int32 nFontObject)
2306 {
2307     if( !pFD )
2308         return 0;
2309     const pdf::BuildinFont& rBuildinFont = pFD->GetBuildinFont();
2310 
2311     OStringBuffer aLine( 1024 );
2312 
2313     if( nFontObject <= 0 )
2314         nFontObject = createObject();
2315     CHECK_RETURN( updateObject( nFontObject ) );
2316     aLine.append( nFontObject );
2317     aLine.append( " 0 obj\n"
2318                   "<</Type/Font/Subtype/Type1/BaseFont/" );
2319     appendName( rBuildinFont.m_pPSName, aLine );
2320     aLine.append( "\n" );
2321     if( rBuildinFont.m_eCharSet == RTL_TEXTENCODING_MS_1252 )
2322          aLine.append( "/Encoding/WinAnsiEncoding\n" );
2323     aLine.append( ">>\nendobj\n\n" );
2324     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2325     return nFontObject;
2326 }
2327 
2328 std::map< sal_Int32, sal_Int32 > PDFWriterImpl::emitSystemFont( const PhysicalFontFace* pFont, EmbedFont const & rEmbed )
2329 {
2330     std::map< sal_Int32, sal_Int32 > aRet;
2331 
2332     sal_Int32 nFontDescriptor = 0;
2333     OString aSubType( "/Type1" );
2334     FontSubsetInfo aInfo;
2335     // fill in dummy values
2336     aInfo.m_nAscent = 1000;
2337     aInfo.m_nDescent = 200;
2338     aInfo.m_nCapHeight = 1000;
2339     aInfo.m_aFontBBox = tools::Rectangle( Point( -200, -200 ), Size( 1700, 1700 ) );
2340     aInfo.m_aPSName = pFont->GetFamilyName();
2341     sal_Int32 pWidths[256] = {};
2342 
2343     SalGraphics *pGraphics = GetGraphics();
2344 
2345     assert(pGraphics);
2346 
2347     aSubType = OString( "/TrueType" );
2348     std::vector< sal_Int32 > aGlyphWidths;
2349     Ucs2UIntMap aUnicodeMap;
2350     pGraphics->GetGlyphWidths( pFont, false, aGlyphWidths, aUnicodeMap );
2351 
2352     OUString aTmpName;
2353     osl_createTempFile( nullptr, nullptr, &aTmpName.pData );
2354     sal_GlyphId aGlyphIds[ 256 ] = {};
2355     sal_uInt8 pEncoding[ 256 ] = {};
2356     sal_Int32 pDuWidths[ 256 ] = {};
2357 
2358     for( sal_Ucs c = 32; c < 256; c++ )
2359     {
2360         pEncoding[c] = c;
2361         aGlyphIds[c] = 0;
2362         if( aUnicodeMap.find( c ) != aUnicodeMap.end() )
2363             pWidths[ c ] = aGlyphWidths[ aUnicodeMap[ c ] ];
2364     }
2365     //TODO: surely this is utterly broken because aGlyphIds is just all zeros, if we
2366     //had the right glyphids here then I imagine we could replace pDuWidths with
2367     //pWidths and remove pWidths assignment above. i.e. start with the glyph ids
2368     //and map those to unicode rather than try and reverse map them ?
2369     pGraphics->CreateFontSubset( aTmpName, pFont, aGlyphIds, pEncoding, pDuWidths, 256, aInfo );
2370     osl_removeFile( aTmpName.pData );
2371 
2372     // write font descriptor
2373     nFontDescriptor = emitFontDescriptor( pFont, aInfo, 0, 0 );
2374     if( nFontDescriptor )
2375     {
2376         // write font object
2377         sal_Int32 nObject = createObject();
2378         if( updateObject( nObject ) )
2379         {
2380             OStringBuffer aLine( 1024 );
2381             aLine.append( nObject );
2382             aLine.append( " 0 obj\n"
2383                           "<</Type/Font/Subtype" );
2384             aLine.append( aSubType );
2385             aLine.append( "/BaseFont/" );
2386             appendName( aInfo.m_aPSName, aLine );
2387             aLine.append( "\n" );
2388             if( !pFont->IsSymbolFont() )
2389                 aLine.append( "/Encoding/WinAnsiEncoding\n" );
2390             aLine.append( "/FirstChar 32 /LastChar 255\n"
2391                           "/Widths[" );
2392             for( int i = 32; i < 256; i++ )
2393             {
2394                 aLine.append( pWidths[i] );
2395                 aLine.append( ((i&15) == 15) ? "\n" : " " );
2396             }
2397             aLine.append( "]\n"
2398                           "/FontDescriptor " );
2399             aLine.append( nFontDescriptor );
2400             aLine.append( " 0 R>>\n"
2401                           "endobj\n\n" );
2402             writeBuffer( aLine.getStr(), aLine.getLength() );
2403 
2404             aRet[ rEmbed.m_nNormalFontID ] = nObject;
2405         }
2406     }
2407 
2408     return aRet;
2409 }
2410 
2411 typedef int ThreeInts[3];
2412 static bool getPfbSegmentLengths( const unsigned char* pFontBytes, int nByteLen,
2413     ThreeInts& rSegmentLengths )
2414 {
2415     if( !pFontBytes || (nByteLen < 0) )
2416         return false;
2417     const unsigned char* pPtr = pFontBytes;
2418     const unsigned char* pEnd = pFontBytes + nByteLen;
2419 
2420     for(int & rSegmentLength : rSegmentLengths) {
2421         // read segment1 header
2422         if( pPtr+6 >= pEnd )
2423             return false;
2424         if( (pPtr[0] != 0x80) || (pPtr[1] >= 0x03) )
2425             return false;
2426         const int nLen = (pPtr[5]<<24) + (pPtr[4]<<16) + (pPtr[3]<<8) + pPtr[2];
2427         if( nLen <= 0)
2428             return false;
2429         rSegmentLength = nLen;
2430         pPtr += nLen + 6;
2431     }
2432 
2433     // read segment-end header
2434     if( pPtr+2 >= pEnd )
2435         return false;
2436     if( (pPtr[0] != 0x80) || (pPtr[1] != 0x03) )
2437         return false;
2438 
2439     return true;
2440 }
2441 
2442 static void appendSubsetName( int nSubsetID, const OUString& rPSName, OStringBuffer& rBuffer )
2443 {
2444     if( nSubsetID )
2445     {
2446         for( int i = 0; i < 6; i++ )
2447         {
2448             int nOffset = nSubsetID % 26;
2449             nSubsetID /= 26;
2450             rBuffer.append( static_cast<char>('A'+nOffset) );
2451         }
2452         rBuffer.append( '+' );
2453     }
2454     appendName( rPSName, rBuffer );
2455 }
2456 
2457 sal_Int32 PDFWriterImpl::createToUnicodeCMap( sal_uInt8 const * pEncoding,
2458                                               const sal_Ucs* pCodeUnits,
2459                                               const sal_Int32* pCodeUnitsPerGlyph,
2460                                               const sal_Int32* pEncToUnicodeIndex,
2461                                               int nGlyphs )
2462 {
2463     int nMapped = 0;
2464     for (int n = 0; n < nGlyphs; ++n)
2465         if( pCodeUnits[pEncToUnicodeIndex[n]] && pCodeUnitsPerGlyph[n] )
2466             nMapped++;
2467 
2468     if( nMapped == 0 )
2469         return 0;
2470 
2471     sal_Int32 nStream = createObject();
2472     CHECK_RETURN( updateObject( nStream ) );
2473 
2474     OStringBuffer aContents( 1024 );
2475     aContents.append(
2476                      "/CIDInit/ProcSet findresource begin\n"
2477                      "12 dict begin\n"
2478                      "begincmap\n"
2479                      "/CIDSystemInfo<<\n"
2480                      "/Registry (Adobe)\n"
2481                      "/Ordering (UCS)\n"
2482                      "/Supplement 0\n"
2483                      ">> def\n"
2484                      "/CMapName/Adobe-Identity-UCS def\n"
2485                      "/CMapType 2 def\n"
2486                      "1 begincodespacerange\n"
2487                      "<00> <FF>\n"
2488                      "endcodespacerange\n"
2489                      );
2490     int nCount = 0;
2491     for (int n = 0; n < nGlyphs; ++n)
2492     {
2493         if( pCodeUnits[pEncToUnicodeIndex[n]] && pCodeUnitsPerGlyph[n] )
2494         {
2495             if( (nCount % 100) == 0 )
2496             {
2497                 if( nCount )
2498                     aContents.append( "endbfchar\n" );
2499                 aContents.append( static_cast<sal_Int32>(std::min(nMapped-nCount, 100)) );
2500                 aContents.append( " beginbfchar\n" );
2501             }
2502             aContents.append( '<' );
2503             appendHex( static_cast<sal_Int8>(pEncoding[n]), aContents );
2504             aContents.append( "> <" );
2505             // TODO: handle code points>U+FFFF
2506             sal_Int32 nIndex = pEncToUnicodeIndex[n];
2507             for( sal_Int32 j = 0; j < pCodeUnitsPerGlyph[n]; j++ )
2508             {
2509                 appendHex( static_cast<sal_Int8>(pCodeUnits[nIndex + j] / 256), aContents );
2510                 appendHex( static_cast<sal_Int8>(pCodeUnits[nIndex + j] & 255), aContents );
2511             }
2512             aContents.append( ">\n" );
2513             nCount++;
2514         }
2515     }
2516     aContents.append( "endbfchar\n"
2517                       "endcmap\n"
2518                       "CMapName currentdict /CMap defineresource pop\n"
2519                       "end\n"
2520                       "end\n" );
2521     SvMemoryStream aStream;
2522     if (!g_bDebugDisableCompression)
2523     {
2524         ZCodec aCodec( 0x4000, 0x4000 );
2525         aCodec.BeginCompression();
2526         aCodec.Write( aStream, reinterpret_cast<const sal_uInt8*>(aContents.getStr()), aContents.getLength() );
2527         aCodec.EndCompression();
2528     }
2529 
2530     if (g_bDebugDisableCompression)
2531     {
2532         emitComment( "PDFWriterImpl::createToUnicodeCMap" );
2533     }
2534     OStringBuffer aLine( 40 );
2535 
2536     aLine.append( nStream );
2537     aLine.append( " 0 obj\n<</Length " );
2538     sal_Int32 nLen = 0;
2539     if (!g_bDebugDisableCompression)
2540     {
2541         nLen = static_cast<sal_Int32>(aStream.Tell());
2542         aStream.Seek( 0 );
2543         aLine.append( nLen );
2544         aLine.append( "/Filter/FlateDecode" );
2545     }
2546     else
2547         aLine.append( aContents.getLength() );
2548     aLine.append( ">>\nstream\n" );
2549     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2550     checkAndEnableStreamEncryption( nStream );
2551     if (!g_bDebugDisableCompression)
2552     {
2553         CHECK_RETURN( writeBuffer( aStream.GetData(), nLen ) );
2554     }
2555     else
2556     {
2557         CHECK_RETURN( writeBuffer( aContents.getStr(), aContents.getLength() ) );
2558     }
2559     disableStreamEncryption();
2560     aLine.setLength( 0 );
2561     aLine.append( "\nendstream\n"
2562                   "endobj\n\n" );
2563     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2564     return nStream;
2565 }
2566 
2567 sal_Int32 PDFWriterImpl::emitFontDescriptor( const PhysicalFontFace* pFont, FontSubsetInfo const & rInfo, sal_Int32 nSubsetID, sal_Int32 nFontStream )
2568 {
2569     OStringBuffer aLine( 1024 );
2570     // get font flags, see PDF reference 1.4 p. 358
2571     // possibly characters outside Adobe standard encoding
2572     // so set Symbolic flag
2573     sal_Int32 nFontFlags = (1<<2);
2574     if( pFont->GetItalic() == ITALIC_NORMAL || pFont->GetItalic() == ITALIC_OBLIQUE )
2575         nFontFlags |= (1 << 6);
2576     if( pFont->GetPitch() == PITCH_FIXED )
2577         nFontFlags |= 1;
2578     if( pFont->GetFamilyType() == FAMILY_SCRIPT )
2579         nFontFlags |= (1 << 3);
2580     else if( pFont->GetFamilyType() == FAMILY_ROMAN )
2581         nFontFlags |= (1 << 1);
2582 
2583     sal_Int32 nFontDescriptor = createObject();
2584     CHECK_RETURN( updateObject( nFontDescriptor ) );
2585     aLine.setLength( 0 );
2586     aLine.append( nFontDescriptor );
2587     aLine.append( " 0 obj\n"
2588                   "<</Type/FontDescriptor/FontName/" );
2589     appendSubsetName( nSubsetID, rInfo.m_aPSName, aLine );
2590     aLine.append( "\n"
2591                   "/Flags " );
2592     aLine.append( nFontFlags );
2593     aLine.append( "\n"
2594                   "/FontBBox[" );
2595     // note: Top and Bottom are reversed in VCL and PDF rectangles
2596     aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.TopLeft().X()) );
2597     aLine.append( ' ' );
2598     aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.TopLeft().Y()) );
2599     aLine.append( ' ' );
2600     aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.BottomRight().X()) );
2601     aLine.append( ' ' );
2602     aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.BottomRight().Y()+1) );
2603     aLine.append( "]/ItalicAngle " );
2604     if( pFont->GetItalic() == ITALIC_OBLIQUE || pFont->GetItalic() == ITALIC_NORMAL )
2605         aLine.append( "-30" );
2606     else
2607         aLine.append( "0" );
2608     aLine.append( "\n"
2609                   "/Ascent " );
2610     aLine.append( static_cast<sal_Int32>(rInfo.m_nAscent) );
2611     aLine.append( "\n"
2612                   "/Descent " );
2613     aLine.append( static_cast<sal_Int32>(-rInfo.m_nDescent) );
2614     aLine.append( "\n"
2615                   "/CapHeight " );
2616     aLine.append( static_cast<sal_Int32>(rInfo.m_nCapHeight) );
2617     // According to PDF reference 1.4 StemV is required
2618     // seems a tad strange to me, but well ...
2619     aLine.append( "\n"
2620                   "/StemV 80\n" );
2621     if( nFontStream )
2622     {
2623         aLine.append( "/FontFile" );
2624         switch( rInfo.m_nFontType )
2625         {
2626             case FontType::SFNT_TTF:
2627                 aLine.append( '2' );
2628                 break;
2629             case FontType::TYPE1_PFA:
2630             case FontType::TYPE1_PFB:
2631             case FontType::ANY_TYPE1:
2632                 break;
2633             default:
2634                 OSL_FAIL( "unknown fonttype in PDF font descriptor" );
2635                 return 0;
2636         }
2637         aLine.append( ' ' );
2638         aLine.append( nFontStream );
2639         aLine.append( " 0 R\n" );
2640     }
2641     aLine.append( ">>\n"
2642                   "endobj\n\n" );
2643     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2644 
2645     return nFontDescriptor;
2646 }
2647 
2648 void PDFWriterImpl::appendBuildinFontsToDict( OStringBuffer& rDict ) const
2649 {
2650     for (auto const& item : m_aBuildinFontToObjectMap)
2651     {
2652         rDict.append( pdf::BuildinFontFace::Get(item.first).getNameObject() );
2653         rDict.append( ' ' );
2654         rDict.append( item.second );
2655         rDict.append( " 0 R" );
2656     }
2657 }
2658 
2659 bool PDFWriterImpl::emitFonts()
2660 {
2661     SalGraphics *pGraphics = GetGraphics();
2662 
2663     if (!pGraphics)
2664         return false;
2665 
2666     OStringBuffer aLine( 1024 );
2667 
2668     std::map< sal_Int32, sal_Int32 > aFontIDToObject;
2669 
2670     OUString aTmpName;
2671     osl_createTempFile( nullptr, nullptr, &aTmpName.pData );
2672     for (const auto & subset : m_aSubsets)
2673     {
2674         for (auto & s_subset :subset.second.m_aSubsets)
2675         {
2676             sal_GlyphId aGlyphIds[ 256 ] = {};
2677             sal_Int32 pWidths[ 256 ];
2678             sal_uInt8 pEncoding[ 256 ] = {};
2679             sal_Int32 pEncToUnicodeIndex[ 256 ] = {};
2680             sal_Int32 pCodeUnitsPerGlyph[ 256 ] = {};
2681             std::vector<sal_Ucs> aCodeUnits;
2682             aCodeUnits.reserve( 256 );
2683             int nGlyphs = 1;
2684             // fill arrays and prepare encoding index map
2685             sal_Int32 nToUnicodeStream = 0;
2686 
2687             for (auto const& item : s_subset.m_aMapping)
2688             {
2689                 sal_uInt8 nEnc = item.second.getGlyphId();
2690 
2691                 SAL_WARN_IF( aGlyphIds[nEnc] != 0 || pEncoding[nEnc] != 0, "vcl.pdfwriter", "duplicate glyph" );
2692                 SAL_WARN_IF( nEnc > s_subset.m_aMapping.size(), "vcl.pdfwriter", "invalid glyph encoding" );
2693 
2694                 aGlyphIds[ nEnc ] = item.first;
2695                 pEncoding[ nEnc ] = nEnc;
2696                 pEncToUnicodeIndex[ nEnc ] = static_cast<sal_Int32>(aCodeUnits.size());
2697                 pCodeUnitsPerGlyph[ nEnc ] = item.second.countCodes();
2698                 for( sal_Int32 n = 0; n < pCodeUnitsPerGlyph[ nEnc ]; n++ )
2699                     aCodeUnits.push_back( item.second.getCode( n ) );
2700                 if( item.second.getCode(0) )
2701                     nToUnicodeStream = 1;
2702                 if( nGlyphs < 256 )
2703                     nGlyphs++;
2704                 else
2705                 {
2706                     OSL_FAIL( "too many glyphs for subset" );
2707                 }
2708             }
2709             FontSubsetInfo aSubsetInfo;
2710             if( pGraphics->CreateFontSubset( aTmpName, subset.first, aGlyphIds, pEncoding, pWidths, nGlyphs, aSubsetInfo ) )
2711             {
2712                 // create font stream
2713                 osl::File aFontFile(aTmpName);
2714                 if (osl::File::E_None != aFontFile.open(osl_File_OpenFlag_Read)) return false;
2715                 // get file size
2716                 sal_uInt64 nLength1;
2717                 if ( osl::File::E_None != aFontFile.setPos(osl_Pos_End, 0) ) return false;
2718                 if ( osl::File::E_None != aFontFile.getPos(nLength1) ) return false;
2719                 if ( osl::File::E_None != aFontFile.setPos(osl_Pos_Absolut, 0) ) return false;
2720 
2721                 if (g_bDebugDisableCompression)
2722                 {
2723                     emitComment( "PDFWriterImpl::emitFonts" );
2724                 }
2725                 sal_Int32 nFontStream = createObject();
2726                 sal_Int32 nStreamLengthObject = createObject();
2727                 if ( !updateObject( nFontStream ) ) return false;
2728                 aLine.setLength( 0 );
2729                 aLine.append( nFontStream );
2730                 aLine.append( " 0 obj\n"
2731                              "<</Length " );
2732                 aLine.append( nStreamLengthObject );
2733                 if (!g_bDebugDisableCompression)
2734                     aLine.append( " 0 R"
2735                                  "/Filter/FlateDecode"
2736                                  "/Length1 " );
2737                 else
2738                     aLine.append( " 0 R"
2739                                  "/Length1 " );
2740 
2741                 sal_uInt64 nStartPos = 0;
2742                 if( aSubsetInfo.m_nFontType == FontType::SFNT_TTF )
2743                 {
2744                     aLine.append( static_cast<sal_Int32>(nLength1) );
2745 
2746                     aLine.append( ">>\n"
2747                                  "stream\n" );
2748                     if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
2749                     if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
2750 
2751                     // copy font file
2752                     beginCompression();
2753                     checkAndEnableStreamEncryption( nFontStream );
2754                     sal_Bool bEOF = false;
2755                     do
2756                     {
2757                         char buf[8192];
2758                         sal_uInt64 nRead;
2759                         if ( osl::File::E_None != aFontFile.read(buf, sizeof(buf), nRead) ) return false;
2760                         if ( !writeBuffer( buf, nRead ) ) return false;
2761                         if ( osl::File::E_None != aFontFile.isEndOfFile(&bEOF) ) return false;
2762                     } while( ! bEOF );
2763                 }
2764                 else if( aSubsetInfo.m_nFontType & FontType::CFF_FONT)
2765                 {
2766                     // TODO: implement
2767                     OSL_FAIL( "PDFWriterImpl does not support CFF-font subsets yet!" );
2768                 }
2769                 else if( aSubsetInfo.m_nFontType & FontType::TYPE1_PFB) // TODO: also support PFA?
2770                 {
2771                     std::unique_ptr<unsigned char[]> xBuffer(new unsigned char[nLength1]);
2772 
2773                     sal_uInt64 nBytesRead = 0;
2774                     if ( osl::File::E_None != aFontFile.read(xBuffer.get(), nLength1, nBytesRead) ) return false;
2775                     SAL_WARN_IF( nBytesRead!=nLength1, "vcl.pdfwriter", "PDF-FontSubset read incomplete!" );
2776                     if ( osl::File::E_None != aFontFile.setPos(osl_Pos_Absolut, 0) ) return false;
2777                     // get the PFB-segment lengths
2778                     ThreeInts aSegmentLengths = {0,0,0};
2779                     getPfbSegmentLengths(xBuffer.get(), static_cast<int>(nBytesRead), aSegmentLengths);
2780                     // the lengths below are mandatory for PDF-exported Type1 fonts
2781                     // because the PFB segment headers get stripped! WhyOhWhy.
2782                     aLine.append( static_cast<sal_Int32>(aSegmentLengths[0]) );
2783                     aLine.append( "/Length2 " );
2784                     aLine.append( static_cast<sal_Int32>(aSegmentLengths[1]) );
2785                     aLine.append( "/Length3 " );
2786                     aLine.append( static_cast<sal_Int32>(aSegmentLengths[2]) );
2787 
2788                     aLine.append( ">>\n"
2789                                  "stream\n" );
2790                     if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
2791                     if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
2792 
2793                     // emit PFB-sections without section headers
2794                     beginCompression();
2795                     checkAndEnableStreamEncryption( nFontStream );
2796                     if ( !writeBuffer( &xBuffer[6], aSegmentLengths[0] ) ) return false;
2797                     if ( !writeBuffer( &xBuffer[12] + aSegmentLengths[0], aSegmentLengths[1] ) ) return false;
2798                     if ( !writeBuffer( &xBuffer[18] + aSegmentLengths[0] + aSegmentLengths[1], aSegmentLengths[2] ) ) return false;
2799                 }
2800                 else
2801                 {
2802                     SAL_INFO("vcl.pdfwriter", "PDF: CreateFontSubset result in not yet supported format=" << static_cast<int>(aSubsetInfo.m_nFontType));
2803                     aLine.append( "0 >>\nstream\n" );
2804                 }
2805 
2806                 endCompression();
2807                 disableStreamEncryption();
2808                 // close the file
2809                 aFontFile.close();
2810 
2811                 sal_uInt64 nEndPos = 0;
2812                 if ( osl::File::E_None != m_aFile.getPos(nEndPos) ) return false;
2813                 // end the stream
2814                 aLine.setLength( 0 );
2815                 aLine.append( "\nendstream\nendobj\n\n" );
2816                 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
2817 
2818                 // emit stream length object
2819                 if ( !updateObject( nStreamLengthObject ) ) return false;
2820                 aLine.setLength( 0 );
2821                 aLine.append( nStreamLengthObject );
2822                 aLine.append( " 0 obj\n" );
2823                 aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
2824                 aLine.append( "\nendobj\n\n" );
2825                 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
2826 
2827                 // write font descriptor
2828                 sal_Int32 nFontDescriptor = emitFontDescriptor( subset.first, aSubsetInfo, s_subset.m_nFontID, nFontStream );
2829 
2830                 if( nToUnicodeStream )
2831                     nToUnicodeStream = createToUnicodeCMap( pEncoding, aCodeUnits.data(), pCodeUnitsPerGlyph, pEncToUnicodeIndex, nGlyphs );
2832 
2833                 sal_Int32 nFontObject = createObject();
2834                 if ( !updateObject( nFontObject ) ) return false;
2835                 aLine.setLength( 0 );
2836                 aLine.append( nFontObject );
2837 
2838                 aLine.append( " 0 obj\n" );
2839                 aLine.append( (aSubsetInfo.m_nFontType & FontType::ANY_TYPE1) ?
2840                              "<</Type/Font/Subtype/Type1/BaseFont/" :
2841                              "<</Type/Font/Subtype/TrueType/BaseFont/" );
2842                 appendSubsetName( s_subset.m_nFontID, aSubsetInfo.m_aPSName, aLine );
2843                 aLine.append( "\n"
2844                              "/FirstChar 0\n"
2845                              "/LastChar " );
2846                 aLine.append( static_cast<sal_Int32>(nGlyphs-1) );
2847                 aLine.append( "\n"
2848                              "/Widths[" );
2849                 for( int i = 0; i < nGlyphs; i++ )
2850                 {
2851                     aLine.append( pWidths[ i ] );
2852                     aLine.append( ((i & 15) == 15) ? "\n" : " " );
2853                 }
2854                 aLine.append( "]\n"
2855                              "/FontDescriptor " );
2856                 aLine.append( nFontDescriptor );
2857                 aLine.append( " 0 R\n" );
2858                 if( nToUnicodeStream )
2859                 {
2860                     aLine.append( "/ToUnicode " );
2861                     aLine.append( nToUnicodeStream );
2862                     aLine.append( " 0 R\n" );
2863                 }
2864                 aLine.append( ">>\n"
2865                              "endobj\n\n" );
2866                 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
2867 
2868                 aFontIDToObject[ s_subset.m_nFontID ] = nFontObject;
2869             }
2870             else
2871             {
2872                 const PhysicalFontFace* pFont = subset.first;
2873                 OStringBuffer aErrorComment( 256 );
2874                 aErrorComment.append( "CreateFontSubset failed for font \"" );
2875                 aErrorComment.append( OUStringToOString( pFont->GetFamilyName(), RTL_TEXTENCODING_UTF8 ) );
2876                 aErrorComment.append( '\"' );
2877                 if( pFont->GetItalic() == ITALIC_NORMAL )
2878                     aErrorComment.append( " italic" );
2879                 else if( pFont->GetItalic() == ITALIC_OBLIQUE )
2880                     aErrorComment.append( " oblique" );
2881                 aErrorComment.append( " weight=" );
2882                 aErrorComment.append( sal_Int32(pFont->GetWeight()) );
2883                 emitComment( aErrorComment.getStr() );
2884             }
2885         }
2886     }
2887     osl_removeFile( aTmpName.pData );
2888 
2889     // emit system fonts
2890     for (auto const& systemFont : m_aSystemFonts)
2891     {
2892         std::map< sal_Int32, sal_Int32 > aObjects = emitSystemFont( systemFont.first, systemFont.second );
2893         for (auto const& item : aObjects)
2894         {
2895             if ( !item.second ) return false;
2896             aFontIDToObject[ item.first ] = item.second;
2897         }
2898     }
2899 
2900     OStringBuffer aFontDict( 1024 );
2901     aFontDict.append( getFontDictObject() );
2902     aFontDict.append( " 0 obj\n"
2903                      "<<" );
2904     int ni = 0;
2905     for (auto const& itemMap : aFontIDToObject)
2906     {
2907         aFontDict.append( "/F" );
2908         aFontDict.append( itemMap.first );
2909         aFontDict.append( ' ' );
2910         aFontDict.append( itemMap.second );
2911         aFontDict.append( " 0 R" );
2912         if( ((++ni) & 7) == 0 )
2913             aFontDict.append( '\n' );
2914     }
2915     // emit builtin font for widget appearances / variable text
2916     for (auto & item : m_aBuildinFontToObjectMap)
2917     {
2918         rtl::Reference<pdf::BuildinFontFace> aData(new pdf::BuildinFontFace(item.first));
2919         item.second = emitBuildinFont( aData.get(), item.second );
2920     }
2921 
2922     appendBuildinFontsToDict(aFontDict);
2923     aFontDict.append( "\n>>\nendobj\n\n" );
2924 
2925     if ( !updateObject( getFontDictObject() ) ) return false;
2926     if ( !writeBuffer( aFontDict.getStr(), aFontDict.getLength() ) ) return false;
2927     return true;
2928 }
2929 
2930 sal_Int32 PDFWriterImpl::emitResources()
2931 {
2932     // emit shadings
2933     if( ! m_aGradients.empty() )
2934         CHECK_RETURN( emitGradients() );
2935     // emit tilings
2936     if( ! m_aTilings.empty() )
2937         CHECK_RETURN( emitTilings() );
2938 
2939     // emit font dict
2940     CHECK_RETURN( emitFonts() );
2941 
2942     // emit Resource dict
2943     OStringBuffer aLine( 512 );
2944     sal_Int32 nResourceDict = getResourceDictObj();
2945     CHECK_RETURN( updateObject( nResourceDict ) );
2946     aLine.setLength( 0 );
2947     aLine.append( nResourceDict );
2948     aLine.append( " 0 obj\n" );
2949     m_aGlobalResourceDict.append( aLine, getFontDictObject() );
2950     aLine.append( "endobj\n\n" );
2951     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2952     return nResourceDict;
2953 }
2954 
2955 sal_Int32 PDFWriterImpl::updateOutlineItemCount( std::vector< sal_Int32 >& rCounts,
2956                                                  sal_Int32 nItemLevel,
2957                                                  sal_Int32 nCurrentItemId )
2958 {
2959     /* The /Count number of an item is
2960        positive: the number of visible subitems
2961        negative: the negative number of subitems that will become visible if
2962                  the item gets opened
2963        see PDF ref 1.4 p 478
2964     */
2965 
2966     sal_Int32 nCount = 0;
2967 
2968     if( m_aContext.OpenBookmarkLevels < 0           || // all levels are visible
2969         m_aContext.OpenBookmarkLevels >= nItemLevel    // this level is visible
2970       )
2971     {
2972         PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
2973         sal_Int32 nChildren = rItem.m_aChildren.size();
2974         for( sal_Int32 i = 0; i < nChildren; i++ )
2975             nCount += updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
2976         rCounts[nCurrentItemId] = nCount;
2977         // return 1 (this item) + visible sub items
2978         if( nCount < 0 )
2979             nCount = 0;
2980         nCount++;
2981     }
2982     else
2983     {
2984         // this bookmark level is invisible
2985         PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
2986         sal_Int32 nChildren = rItem.m_aChildren.size();
2987         rCounts[ nCurrentItemId ] = -sal_Int32(rItem.m_aChildren.size());
2988         for( sal_Int32 i = 0; i < nChildren; i++ )
2989             updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
2990         nCount = -1;
2991     }
2992 
2993     return nCount;
2994 }
2995 
2996 sal_Int32 PDFWriterImpl::emitOutline()
2997 {
2998     int i, nItems = m_aOutline.size();
2999 
3000     // do we have an outline at all ?
3001     if( nItems < 2 )
3002         return 0;
3003 
3004     // reserve object numbers for all outline items
3005     for( i = 0; i < nItems; ++i )
3006         m_aOutline[i].m_nObject = createObject();
3007 
3008     // update all parent, next and prev object ids
3009     for( i = 0; i < nItems; ++i )
3010     {
3011         PDFOutlineEntry& rItem = m_aOutline[i];
3012         int nChildren = rItem.m_aChildren.size();
3013 
3014         if( nChildren )
3015         {
3016             for( int n = 0; n < nChildren; ++n )
3017             {
3018                 PDFOutlineEntry& rChild = m_aOutline[ rItem.m_aChildren[n] ];
3019 
3020                 rChild.m_nParentObject = rItem.m_nObject;
3021                 rChild.m_nPrevObject = (n > 0) ? m_aOutline[ rItem.m_aChildren[n-1] ].m_nObject : 0;
3022                 rChild.m_nNextObject = (n < nChildren-1) ? m_aOutline[ rItem.m_aChildren[n+1] ].m_nObject : 0;
3023             }
3024 
3025         }
3026     }
3027 
3028     // calculate Count entries for all items
3029     std::vector< sal_Int32 > aCounts( nItems );
3030     updateOutlineItemCount( aCounts, 0, 0 );
3031 
3032     // emit hierarchy
3033     for( i = 0; i < nItems; ++i )
3034     {
3035         PDFOutlineEntry& rItem = m_aOutline[i];
3036         OStringBuffer aLine( 1024 );
3037 
3038         CHECK_RETURN( updateObject( rItem.m_nObject ) );
3039         aLine.append( rItem.m_nObject );
3040         aLine.append( " 0 obj\n" );
3041         aLine.append( "<<" );
3042         // number of visible children (all levels)
3043         if( i > 0 || aCounts[0] > 0 )
3044         {
3045             aLine.append( "/Count " );
3046             aLine.append( aCounts[i] );
3047         }
3048         if( ! rItem.m_aChildren.empty() )
3049         {
3050             // children list: First, Last
3051             aLine.append( "/First " );
3052             aLine.append( m_aOutline[rItem.m_aChildren.front()].m_nObject );
3053             aLine.append( " 0 R/Last " );
3054             aLine.append( m_aOutline[rItem.m_aChildren.back()].m_nObject );
3055             aLine.append( " 0 R\n" );
3056         }
3057         if( i > 0 )
3058         {
3059             // Title, Dest, Parent, Prev, Next
3060             aLine.append( "/Title" );
3061             appendUnicodeTextStringEncrypt( rItem.m_aTitle, rItem.m_nObject, aLine );
3062             aLine.append( "\n" );
3063             // Dest is not required
3064             if( rItem.m_nDestID >= 0 && rItem.m_nDestID < static_cast<sal_Int32>(m_aDests.size()) )
3065             {
3066                 aLine.append( "/Dest" );
3067                 appendDest( rItem.m_nDestID, aLine );
3068             }
3069             aLine.append( "/Parent " );
3070             aLine.append( rItem.m_nParentObject );
3071             aLine.append( " 0 R" );
3072             if( rItem.m_nPrevObject )
3073             {
3074                 aLine.append( "/Prev " );
3075                 aLine.append( rItem.m_nPrevObject );
3076                 aLine.append( " 0 R" );
3077             }
3078             if( rItem.m_nNextObject )
3079             {
3080                 aLine.append( "/Next " );
3081                 aLine.append( rItem.m_nNextObject );
3082                 aLine.append( " 0 R" );
3083             }
3084         }
3085         aLine.append( ">>\nendobj\n\n" );
3086         CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3087     }
3088 
3089     return m_aOutline[0].m_nObject;
3090 }
3091 
3092 #undef CHECK_RETURN
3093 #define CHECK_RETURN( x ) if( !x ) return false
3094 
3095 bool PDFWriterImpl::appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer )
3096 {
3097     if( nDestID < 0 || nDestID >= static_cast<sal_Int32>(m_aDests.size()) )
3098     {
3099         SAL_INFO("vcl.pdfwriter", "ERROR: invalid dest " << static_cast<int>(nDestID) << " requested");
3100         return false;
3101     }
3102 
3103     const PDFDest& rDest        = m_aDests[ nDestID ];
3104     const PDFPage& rDestPage    = m_aPages[ rDest.m_nPage ];
3105 
3106     rBuffer.append( '[' );
3107     rBuffer.append( rDestPage.m_nPageObject );
3108     rBuffer.append( " 0 R" );
3109 
3110     switch( rDest.m_eType )
3111     {
3112         case PDFWriter::DestAreaType::XYZ:
3113         default:
3114             rBuffer.append( "/XYZ " );
3115             appendFixedInt( rDest.m_aRect.Left(), rBuffer );
3116             rBuffer.append( ' ' );
3117             appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
3118             rBuffer.append( " 0" );
3119             break;
3120         case PDFWriter::DestAreaType::FitRectangle:
3121             rBuffer.append( "/FitR " );
3122             appendFixedInt( rDest.m_aRect.Left(), rBuffer );
3123             rBuffer.append( ' ' );
3124             appendFixedInt( rDest.m_aRect.Top(), rBuffer );
3125             rBuffer.append( ' ' );
3126             appendFixedInt( rDest.m_aRect.Right(), rBuffer );
3127             rBuffer.append( ' ' );
3128             appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
3129             break;
3130     }
3131     rBuffer.append( ']' );
3132 
3133     return true;
3134 }
3135 
3136 bool PDFWriterImpl::emitScreenAnnotations()
3137 {
3138     int nAnnots = m_aScreens.size();
3139     for (int i = 0; i < nAnnots; i++)
3140     {
3141         const PDFScreen& rScreen = m_aScreens[i];
3142 
3143         OStringBuffer aLine;
3144         bool bEmbed = false;
3145         if (!rScreen.m_aTempFileURL.isEmpty())
3146         {
3147             bEmbed = true;
3148             if (!updateObject(rScreen.m_nTempFileObject))
3149                 continue;
3150 
3151             SvFileStream aFileStream(rScreen.m_aTempFileURL, StreamMode::READ);
3152             SvMemoryStream aMemoryStream;
3153             aMemoryStream.WriteStream(aFileStream);
3154 
3155             aLine.append(rScreen.m_nTempFileObject);
3156             aLine.append(" 0 obj\n");
3157             aLine.append("<< /Type /EmbeddedFile /Length ");
3158             aLine.append(static_cast<sal_Int64>(aMemoryStream.GetSize()));
3159             aLine.append(" >>\nstream\n");
3160             CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
3161             aLine.setLength(0);
3162 
3163             CHECK_RETURN(writeBuffer(aMemoryStream.GetData(), aMemoryStream.GetSize()));
3164 
3165             aLine.append("\nendstream\nendobj\n\n");
3166             CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
3167             aLine.setLength(0);
3168         }
3169 
3170         if (!updateObject(rScreen.m_nObject))
3171             continue;
3172 
3173         // Annot dictionary.
3174         aLine.append(rScreen.m_nObject);
3175         aLine.append(" 0 obj\n");
3176         aLine.append("<</Type/Annot");
3177         aLine.append("/Subtype/Screen/Rect[");
3178         appendFixedInt(rScreen.m_aRect.Left(), aLine);
3179         aLine.append(' ');
3180         appendFixedInt(rScreen.m_aRect.Top(), aLine);
3181         aLine.append(' ');
3182         appendFixedInt(rScreen.m_aRect.Right(), aLine);
3183         aLine.append(' ');
3184         appendFixedInt(rScreen.m_aRect.Bottom(), aLine);
3185         aLine.append("]");
3186 
3187         // Action dictionary.
3188         aLine.append("/A<</Type/Action /S/Rendition /AN ");
3189         aLine.append(rScreen.m_nObject);
3190         aLine.append(" 0 R ");
3191 
3192         // Rendition dictionary.
3193         aLine.append("/R<</Type/Rendition /S/MR ");
3194 
3195         // MediaClip dictionary.
3196         aLine.append("/C<</Type/MediaClip /S/MCD ");
3197         if (bEmbed)
3198         {
3199             aLine.append("/D << /Type /Filespec /F (<embedded file>) /EF << /F ");
3200             aLine.append(rScreen.m_nTempFileObject);
3201             aLine.append(" 0 R >> >>");
3202         }
3203         else
3204         {
3205             // Linked.
3206             aLine.append("/D << /Type /Filespec /FS /URL /F ");
3207             appendLiteralStringEncrypt(rScreen.m_aURL, rScreen.m_nObject, aLine, osl_getThreadTextEncoding());
3208             aLine.append(" >>");
3209         }
3210         // Allow playing the video via a tempfile.
3211         aLine.append("/P <</TF (TEMPACCESS)>>");
3212         // Until the real MIME type (instead of application/vnd.sun.star.media) is available here.
3213         aLine.append("/CT (video/mp4)");
3214         aLine.append(">>");
3215 
3216         // End Rendition dictionary by requesting play/pause/stop controls.
3217         aLine.append("/P<</BE<</C true >>>>");
3218         aLine.append(">>");
3219 
3220         // End Action dictionary.
3221         aLine.append("/OP 0 >>");
3222 
3223         // End Annot dictionary.
3224         aLine.append("/P ");
3225         aLine.append(m_aPages[rScreen.m_nPage].m_nPageObject);
3226         aLine.append(" 0 R\n>>\nendobj\n\n");
3227         CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
3228     }
3229 
3230     return true;
3231 }
3232 
3233 bool PDFWriterImpl::emitLinkAnnotations()
3234 {
3235     MARK("PDFWriterImpl::emitLinkAnnotations");
3236     int nAnnots = m_aLinks.size();
3237     for( int i = 0; i < nAnnots; i++ )
3238     {
3239         const PDFLink& rLink            = m_aLinks[i];
3240         if( ! updateObject( rLink.m_nObject ) )
3241             continue;
3242 
3243         OStringBuffer aLine( 1024 );
3244         aLine.append( rLink.m_nObject );
3245         aLine.append( " 0 obj\n" );
3246 // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
3247 // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
3248         aLine.append( "<</Type/Annot" );
3249         if( m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3)
3250             aLine.append( "/F 4" );
3251         aLine.append( "/Subtype/Link/Border[0 0 0]/Rect[" );
3252 
3253         appendFixedInt( rLink.m_aRect.Left()-7, aLine );//the +7 to have a better shape of the border rectangle
3254         aLine.append( ' ' );
3255         appendFixedInt( rLink.m_aRect.Top(), aLine );
3256         aLine.append( ' ' );
3257         appendFixedInt( rLink.m_aRect.Right()+7, aLine );//the +7 to have a better shape of the border rectangle
3258         aLine.append( ' ' );
3259         appendFixedInt( rLink.m_aRect.Bottom(), aLine );
3260         aLine.append( "]" );
3261         if( rLink.m_nDest >= 0 )
3262         {
3263             aLine.append( "/Dest" );
3264             appendDest( rLink.m_nDest, aLine );
3265         }
3266         else
3267         {
3268 /*
3269 destination is external to the document, so
3270 we check in the following sequence:
3271 
3272  if target type is neither .pdf, nor .od[tpgs], then
3273           check if relative or absolute and act accordingly (use URI or 'launch application' as requested)
3274                              end processing
3275  else if target is .od[tpgs]: then
3276       if conversion of type from od[tpgs]  to pdf is requested, convert it and this becomes the new target file
3277       processing continue
3278 
3279  if (new)target is .pdf : then
3280      if GotToR is requested, then
3281            convert the target in GoToR where the fragment of the URI is
3282            considered the named destination in the target file, set relative or absolute as requested
3283      else strip the fragment from URL and then set URI or 'launch application' as requested
3284 */
3285 
3286 // FIXME: check if the decode mechanisms for URL processing throughout this implementation
3287 // are the correct one!!
3288 
3289 // extract target file type
3290             auto url(URIHelper::resolveIdnaHost(rLink.m_aURL));
3291 
3292             INetURLObject aDocumentURL( m_aContext.BaseURL );
3293             INetURLObject aTargetURL( url );
3294             bool bSetGoToRMode = false;
3295             bool    bTargetHasPDFExtension = false;
3296             INetProtocol eTargetProtocol = aTargetURL.GetProtocol();
3297             bool    bIsUNCPath = false;
3298 
3299             // check if the protocol is a known one, or if there is no protocol at all (on target only)
3300             // if there is no protocol, make the target relative to the current document directory
3301             // getting the needed URL information from the current document path
3302             if( eTargetProtocol == INetProtocol::NotValid )
3303             {
3304                 if( url.getLength() > 4 && url.startsWith("\\\\\\\\"))
3305                 {
3306                     bIsUNCPath = true;
3307                 }
3308                 else
3309                 {
3310                     INetURLObject aNewBase( aDocumentURL );//duplicate document URL
3311                     aNewBase.removeSegment(); //remove last segment from it, obtaining the base URL of the
3312                                               //target document
3313                     aNewBase.insertName( url );
3314                     aTargetURL = aNewBase;//reassign the new target URL
3315                     //recompute the target protocol, with the new URL
3316                     //normal URL processing resumes
3317                     eTargetProtocol = aTargetURL.GetProtocol();
3318                 }
3319             }
3320 
3321             OUString aFileExtension = aTargetURL.GetFileExtension();
3322 
3323             // Check if the URL ends in '/': if yes it's a directory,
3324             // it will be forced to a URI link.
3325             // possibly a malformed URI, leave it as it is, force as URI
3326             if( aTargetURL.hasFinalSlash() )
3327                 m_aContext.DefaultLinkAction = PDFWriter::URIAction;
3328 
3329             if( !aFileExtension.isEmpty() )
3330             {
3331                 if( m_aContext.ConvertOOoTargetToPDFTarget )
3332                 {
3333                     bool bChangeFileExtensionToPDF = false;
3334                     //examine the file type (.odm .odt. .odp, odg, ods)
3335                     if( aFileExtension.equalsIgnoreAsciiCase( "odm" ) )
3336                         bChangeFileExtensionToPDF = true;
3337                     if( aFileExtension.equalsIgnoreAsciiCase( "odt" ) )
3338                         bChangeFileExtensionToPDF = true;
3339                     else if( aFileExtension.equalsIgnoreAsciiCase( "odp" ) )
3340                         bChangeFileExtensionToPDF = true;
3341                     else if( aFileExtension.equalsIgnoreAsciiCase( "odg" ) )
3342                         bChangeFileExtensionToPDF = true;
3343                     else if( aFileExtension.equalsIgnoreAsciiCase( "ods" ) )
3344                         bChangeFileExtensionToPDF = true;
3345                     if( bChangeFileExtensionToPDF )
3346                         aTargetURL.setExtension("pdf" );
3347                 }
3348                 //check if extension is pdf, see if GoToR should be forced
3349                 bTargetHasPDFExtension = aTargetURL.GetFileExtension().equalsIgnoreAsciiCase( "pdf" );
3350                 if( m_aContext.ForcePDFAction && bTargetHasPDFExtension )
3351                     bSetGoToRMode = true;
3352             }
3353             //prepare the URL, if relative or not
3354             INetProtocol eBaseProtocol = aDocumentURL.GetProtocol();
3355             //queue the string common to all types of actions
3356             aLine.append( "/A<</Type/Action/S");
3357             if( bIsUNCPath ) // handle Win UNC paths
3358             {
3359                 aLine.append( "/Launch/Win<</F" );
3360                 // INetURLObject is not good with UNC paths, use original path
3361                 appendLiteralStringEncrypt( url, rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
3362                 aLine.append( ">>" );
3363             }
3364             else
3365             {
3366                 bool bSetRelative = false;
3367                 bool bFileSpec = false;
3368                 //check if relative file link is requested and if the protocol is 'file://'
3369                 if( m_aContext.RelFsys && eBaseProtocol == eTargetProtocol && eTargetProtocol == INetProtocol::File )
3370                     bSetRelative = true;
3371 
3372                 OUString aFragment = aTargetURL.GetMark( INetURLObject::DecodeMechanism::NONE /*DecodeMechanism::WithCharset*/ ); //fragment as is,
3373                 if( !bSetGoToRMode )
3374                 {
3375                     switch( m_aContext.DefaultLinkAction )
3376                     {
3377                     default:
3378                     case PDFWriter::URIAction :
3379                     case PDFWriter::URIActionDestination :
3380                         aLine.append( "/URI/URI" );
3381                         break;
3382                     case PDFWriter::LaunchAction:
3383                         // now:
3384                         // if a launch action is requested and the hyperlink target has a fragment
3385                         // and the target file does not have a pdf extension, or it's not a 'file:://'
3386                         // protocol then force the uri action on it
3387                         // This code will permit the correct opening of application on web pages,
3388                         // the one that normally have fragments (but I may be wrong...)
3389                         // and will force the use of URI when the protocol is not file:
3390                         if( (!aFragment.isEmpty() && !bTargetHasPDFExtension) ||
3391                                         eTargetProtocol != INetProtocol::File )
3392                         {
3393                             aLine.append( "/URI/URI" );
3394                         }
3395                         else
3396                         {
3397                             aLine.append( "/Launch/F" );
3398                             bFileSpec = true;
3399                         }
3400                         break;
3401                     }
3402                 }
3403 
3404                 //fragment are encoded in the same way as in the named destination processing
3405                 if( bSetGoToRMode )
3406                 {
3407                     //add the fragment
3408                     OUString aURLNoMark = aTargetURL.GetURLNoMark( INetURLObject::DecodeMechanism::WithCharset );
3409                     aLine.append("/GoToR");
3410                     aLine.append("/F");
3411                     appendLiteralStringEncrypt( bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURLNoMark,
3412                                                                                          INetURLObject::EncodeMechanism::WasEncoded,
3413                                                                                          INetURLObject::DecodeMechanism::WithCharset ) :
3414                                                                    aURLNoMark, rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
3415                     if( !aFragment.isEmpty() )
3416                     {
3417                         aLine.append("/D/");
3418                         appendDestinationName( aFragment , aLine );
3419                     }
3420                 }
3421                 else
3422                 {
3423                     // change the fragment to accommodate the bookmark (only if the file extension
3424                     // is PDF and the requested action is of the correct type)
3425                     if(m_aContext.DefaultLinkAction == PDFWriter::URIActionDestination &&
3426                                bTargetHasPDFExtension && !aFragment.isEmpty() )
3427                     {
3428                         OStringBuffer aLineLoc( 1024 );
3429                         appendDestinationName( aFragment , aLineLoc );
3430                         //substitute the fragment
3431                         aTargetURL.SetMark( OStringToOUString(aLineLoc.makeStringAndClear(), RTL_TEXTENCODING_ASCII_US) );
3432                     }
3433                     OUString aURL = aTargetURL.GetMainURL( bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE );
3434                     appendLiteralStringEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURL,
3435                                                                                         INetURLObject::EncodeMechanism::WasEncoded,
3436                                                                                             bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE
3437                                                                                             ) :
3438                                                                                aURL , rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
3439                 }
3440             }
3441             aLine.append( ">>\n" );
3442         }
3443         if( rLink.m_nStructParent > 0 )
3444         {
3445             aLine.append( "/StructParent " );
3446             aLine.append( rLink.m_nStructParent );
3447         }
3448         aLine.append( ">>\nendobj\n\n" );
3449         CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3450     }
3451 
3452     return true;
3453 }
3454 
3455 namespace
3456 {
3457 
3458 void appendAnnotationRect(tools::Rectangle const & rRectangle, OStringBuffer & aLine)
3459 {
3460     aLine.append("/Rect[");
3461     appendFixedInt(rRectangle.Left(), aLine);
3462     aLine.append(' ');
3463     appendFixedInt(rRectangle.Top(), aLine);
3464     aLine.append(' ');
3465     appendFixedInt(rRectangle.Right(), aLine);
3466     aLine.append(' ');
3467     appendFixedInt(rRectangle.Bottom(), aLine);
3468     aLine.append("] ");
3469 }
3470 
3471 void appendObjectID(sal_Int32 nObjectID, OStringBuffer & aLine)
3472 {
3473     aLine.append(nObjectID);
3474     aLine.append(" 0 obj\n");
3475 }
3476 
3477 void appendObjectReference(sal_Int32 nObjectID, OStringBuffer & aLine)
3478 {
3479     aLine.append(nObjectID);
3480     aLine.append(" 0 R ");
3481 }
3482 
3483 } // end anonymous namespace
3484 
3485 void PDFWriterImpl::emitTextAnnotationLine(OStringBuffer & aLine, PDFNoteEntry const & rNote)
3486 {
3487     appendObjectID(rNote.m_nObject, aLine);
3488 
3489     aLine.append("<</Type /Annot /Subtype /Text ");
3490 
3491 // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
3492 // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
3493     if (m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3)
3494         aLine.append("/F 4 ");
3495 
3496     appendAnnotationRect(rNote.m_aRect, aLine);
3497 
3498     aLine.append("/Popup ");
3499     appendObjectReference(rNote.m_aPopUpAnnotation.m_nObject, aLine);
3500 
3501     auto & rDateTime = rNote.m_aContents.maModificationDate;
3502 
3503     aLine.append("/M (");
3504     appendPdfTimeDate(aLine, rDateTime.Year, rDateTime.Month, rDateTime.Day, rDateTime.Hours, rDateTime.Minutes, rDateTime.Seconds, 0);
3505     aLine.append(") ");
3506 
3507     // contents of the note (type text string)
3508     aLine.append("/Contents ");
3509     appendUnicodeTextStringEncrypt(rNote.m_aContents.Contents, rNote.m_nObject, aLine);
3510     aLine.append("\n");
3511 
3512     // optional title
3513     if (!rNote.m_aContents.Title.isEmpty())
3514     {
3515         aLine.append("/T ");
3516         appendUnicodeTextStringEncrypt(rNote.m_aContents.Title, rNote.m_nObject, aLine);
3517         aLine.append("\n");
3518     }
3519     aLine.append(">>\n");
3520     aLine.append("endobj\n\n");
3521 }
3522 
3523 void PDFWriterImpl::emitPopupAnnotationLine(OStringBuffer & aLine, PDFPopupAnnotation const & rPopUp)
3524 {
3525     appendObjectID(rPopUp.m_nObject, aLine);
3526     aLine.append("<</Type /Annot /Subtype /Popup ");
3527     aLine.append("/Parent ");
3528     appendObjectReference(rPopUp.m_nParentObject, aLine);
3529     aLine.append(">>\n");
3530     aLine.append("endobj\n\n");
3531 }
3532 
3533 bool PDFWriterImpl::emitNoteAnnotations()
3534 {
3535     // emit note annotations
3536     int nAnnots = m_aNotes.size();
3537     for( int i = 0; i < nAnnots; i++ )
3538     {
3539         const PDFNoteEntry& rNote = m_aNotes[i];
3540         const PDFPopupAnnotation& rPopUp = rNote.m_aPopUpAnnotation;
3541 
3542         {
3543             if (!updateObject(rNote.m_nObject))
3544                 return false;
3545 
3546             OStringBuffer aLine(1024);
3547 
3548             emitTextAnnotationLine(aLine, rNote);
3549 
3550             if (!writeBuffer(aLine.getStr(), aLine.getLength()))
3551                 return false;
3552         }
3553 
3554         {
3555 
3556             if (!updateObject(rPopUp.m_nObject))
3557                 return false;
3558 
3559             OStringBuffer aLine(1024);
3560 
3561             emitPopupAnnotationLine(aLine, rPopUp);
3562 
3563             if (!writeBuffer(aLine.getStr(), aLine.getLength()))
3564                 return false;
3565         }
3566     }
3567     return true;
3568 }
3569 
3570 Font PDFWriterImpl::replaceFont( const vcl::Font& rControlFont, const vcl::Font&  rAppSetFont )
3571 {
3572     bool bAdjustSize = false;
3573 
3574     Font aFont( rControlFont );
3575     if( aFont.GetFamilyName().isEmpty() )
3576     {
3577         aFont = rAppSetFont;
3578         if( rControlFont.GetFontHeight() )
3579             aFont.SetFontSize( Size( 0, rControlFont.GetFontHeight() ) );
3580         else
3581             bAdjustSize = true;
3582         if( rControlFont.GetItalic() != ITALIC_DONTKNOW )
3583             aFont.SetItalic( rControlFont.GetItalic() );
3584         if( rControlFont.GetWeight() != WEIGHT_DONTKNOW )
3585             aFont.SetWeight( rControlFont.GetWeight() );
3586     }
3587     else if( ! aFont.GetFontHeight() )
3588     {
3589         aFont.SetFontSize( rAppSetFont.GetFontSize() );
3590         bAdjustSize = true;
3591     }
3592     if( bAdjustSize )
3593     {
3594         Size aFontSize = aFont.GetFontSize();
3595         OutputDevice* pDefDev = Application::GetDefaultDevice();
3596         aFontSize = OutputDevice::LogicToLogic( aFontSize, pDefDev->GetMapMode(), getMapMode() );
3597         aFont.SetFontSize( aFontSize );
3598     }
3599     return aFont;
3600 }
3601 
3602 sal_Int32 PDFWriterImpl::getBestBuildinFont( const vcl::Font& rFont )
3603 {
3604     sal_Int32 nBest = 4; // default to Helvetica
3605 
3606     if (rFont.GetFamilyType() == FAMILY_ROMAN)
3607     {
3608         // Serif: default to Times-Roman.
3609         nBest = 8;
3610     }
3611 
3612     OUString aFontName( rFont.GetFamilyName() );
3613     aFontName = aFontName.toAsciiLowerCase();
3614 
3615     if( aFontName.indexOf( "times" ) != -1 )
3616         nBest = 8;
3617     else if( aFontName.indexOf( "courier" ) != -1 )
3618         nBest = 0;
3619     else if( aFontName.indexOf( "dingbats" ) != -1 )
3620         nBest = 13;
3621     else if( aFontName.indexOf( "symbol" ) != -1 )
3622         nBest = 12;
3623     if( nBest < 12 )
3624     {
3625         if( rFont.GetItalic() == ITALIC_OBLIQUE || rFont.GetItalic() == ITALIC_NORMAL )
3626             nBest += 1;
3627         if( rFont.GetWeight() > WEIGHT_MEDIUM )
3628             nBest += 2;
3629     }
3630 
3631     if( m_aBuildinFontToObjectMap.find( nBest ) == m_aBuildinFontToObjectMap.end() )
3632         m_aBuildinFontToObjectMap[ nBest ] = createObject();
3633 
3634     return nBest;
3635 }
3636 
3637 static const Color& replaceColor( const Color& rCol1, const Color& rCol2 )
3638 {
3639     return (rCol1 == COL_TRANSPARENT) ? rCol2 : rCol1;
3640 }
3641 
3642 void PDFWriterImpl::createDefaultPushButtonAppearance( PDFWidget& rButton, const PDFWriter::PushButtonWidget& rWidget )
3643 {
3644     const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
3645 
3646     // save graphics state
3647     push( PushFlags::ALL );
3648 
3649     // transform relative to control's coordinates since an
3650     // appearance stream is a form XObject
3651     // this relies on the m_aRect member of rButton NOT already being transformed
3652     // to default user space
3653     if( rWidget.Background || rWidget.Border )
3654     {
3655         setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetLightColor() ) : COL_TRANSPARENT );
3656         setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetDialogColor() ) : COL_TRANSPARENT );
3657         drawRectangle( rWidget.Location );
3658     }
3659     // prepare font to use
3660     Font aFont = replaceFont( rWidget.TextFont, rSettings.GetPushButtonFont() );
3661     setFont( aFont );
3662     setTextColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ) );
3663 
3664     drawText( rButton.m_aRect, rButton.m_aText, rButton.m_nTextStyle );
3665 
3666     // create DA string while local mapmode is still in place
3667     // (that is before endRedirect())
3668     OStringBuffer aDA( 256 );
3669     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ), aDA );
3670     Font aDummyFont( "Helvetica", aFont.GetFontSize() );
3671     sal_Int32 nDummyBuildin = getBestBuildinFont( aDummyFont );
3672     aDA.append( ' ' );
3673     aDA.append(pdf::BuildinFontFace::Get(nDummyBuildin).getNameObject());
3674     aDA.append( ' ' );
3675     m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
3676     aDA.append( " Tf" );
3677     rButton.m_aDAString = aDA.makeStringAndClear();
3678 
3679     pop();
3680 
3681     rButton.m_aAppearances[ "N" ][ "Standard" ] = new SvMemoryStream();
3682 
3683     /* seems like a bad hack but at least works in both AR5 and 6:
3684        we draw the button ourselves and tell AR
3685        the button would be totally transparent with no text
3686 
3687        One would expect that simply setting a normal appearance
3688        should suffice, but no, as soon as the user actually presses
3689        the button and an action is tied to it (gasp! a button that
3690        does something) the appearance gets replaced by some crap that AR
3691        creates on the fly even if no DA or MK is given. On AR6 at least
3692        the DA and MK work as expected, but on AR5 this creates a region
3693        filled with the background color but nor text. Urgh.
3694     */
3695     rButton.m_aMKDict = "/BC [] /BG [] /CA";
3696     rButton.m_aMKDictCAString = "";
3697 }
3698 
3699 Font PDFWriterImpl::drawFieldBorder( PDFWidget& rIntern,
3700                                      const PDFWriter::AnyWidget& rWidget,
3701                                      const StyleSettings& rSettings )
3702 {
3703     Font aFont = replaceFont( rWidget.TextFont, rSettings.GetFieldFont() );
3704 
3705     if( rWidget.Background || rWidget.Border )
3706     {
3707         if( rWidget.Border && rWidget.BorderColor == COL_TRANSPARENT )
3708         {
3709             sal_Int32 nDelta = GetDPIX() / 500;
3710             if( nDelta < 1 )
3711                 nDelta = 1;
3712             setLineColor( COL_TRANSPARENT );
3713             tools::Rectangle aRect = rIntern.m_aRect;
3714             setFillColor( rSettings.GetLightBorderColor() );
3715             drawRectangle( aRect );
3716             aRect.AdjustLeft(nDelta ); aRect.AdjustTop(nDelta );
3717             aRect.AdjustRight( -nDelta ); aRect.AdjustBottom( -nDelta );
3718             setFillColor( rSettings.GetFieldColor() );
3719             drawRectangle( aRect );
3720             setFillColor( rSettings.GetLightColor() );
3721             drawRectangle( tools::Rectangle( Point( aRect.Left(), aRect.Bottom()-nDelta ), aRect.BottomRight() ) );
3722             drawRectangle( tools::Rectangle( Point( aRect.Right()-nDelta, aRect.Top() ), aRect.BottomRight() ) );
3723             setFillColor( rSettings.GetDarkShadowColor() );
3724             drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Left()+nDelta, aRect.Bottom() ) ) );
3725             drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Right(), aRect.Top()+nDelta ) ) );
3726         }
3727         else
3728         {
3729             setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetShadowColor() ) : COL_TRANSPARENT );
3730             setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
3731             drawRectangle( rIntern.m_aRect );
3732         }
3733 
3734         if( rWidget.Border )
3735         {
3736             // adjust edit area accounting for border
3737             sal_Int32 nDelta = aFont.GetFontHeight()/4;
3738             if( nDelta < 1 )
3739                 nDelta = 1;
3740             rIntern.m_aRect.AdjustLeft(nDelta );
3741             rIntern.m_aRect.AdjustTop(nDelta );
3742             rIntern.m_aRect.AdjustRight( -nDelta );
3743             rIntern.m_aRect.AdjustBottom( -nDelta );
3744         }
3745     }
3746     return aFont;
3747 }
3748 
3749 void PDFWriterImpl::createDefaultEditAppearance( PDFWidget& rEdit, const PDFWriter::EditWidget& rWidget )
3750 {
3751     const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
3752     SvMemoryStream* pEditStream = new SvMemoryStream( 1024, 1024 );
3753 
3754     push( PushFlags::ALL );
3755 
3756     // prepare font to use, draw field border
3757     Font aFont = drawFieldBorder( rEdit, rWidget, rSettings );
3758     // Get the built-in font which is closest to aFont.
3759     sal_Int32 nBest = getBestBuildinFont(aFont);
3760 
3761     // prepare DA string
3762     OStringBuffer aDA( 32 );
3763     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
3764     aDA.append( ' ' );
3765     aDA.append(pdf::BuildinFontFace::Get(nBest).getNameObject());
3766 
3767     OStringBuffer aDR( 32 );
3768     aDR.append( "/Font " );
3769     aDR.append( getFontDictObject() );
3770     aDR.append( " 0 R" );
3771     rEdit.m_aDRDict = aDR.makeStringAndClear();
3772     aDA.append( ' ' );
3773     m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
3774     aDA.append( " Tf" );
3775 
3776     /*  create an empty appearance stream, let the viewer create
3777         the appearance at runtime. This is because AR5 seems to
3778         paint the widget appearance always, and a dynamically created
3779         appearance on top of it. AR6 is well behaved in that regard, so
3780         that behaviour seems to be a bug. Anyway this empty appearance
3781         relies on /NeedAppearances in the AcroForm dictionary set to "true"
3782      */
3783     beginRedirect( pEditStream, rEdit.m_aRect );
3784     OString aAppearance = "/Tx BMC\nEMC\n";
3785     writeBuffer( aAppearance.getStr(), aAppearance.getLength() );
3786 
3787     endRedirect();
3788     pop();
3789 
3790     rEdit.m_aAppearances[ "N" ][ "Standard" ] = pEditStream;
3791 
3792     rEdit.m_aDAString = aDA.makeStringAndClear();
3793 }
3794 
3795 void PDFWriterImpl::createDefaultListBoxAppearance( PDFWidget& rBox, const PDFWriter::ListBoxWidget& rWidget )
3796 {
3797     const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
3798     SvMemoryStream* pListBoxStream = new SvMemoryStream( 1024, 1024 );
3799 
3800     push( PushFlags::ALL );
3801 
3802     // prepare font to use, draw field border
3803     Font aFont = drawFieldBorder( rBox, rWidget, rSettings );
3804     sal_Int32 nBest = getSystemFont( aFont );
3805 
3806     beginRedirect( pListBoxStream, rBox.m_aRect );
3807 
3808     setLineColor( COL_TRANSPARENT );
3809     setFillColor( replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) );
3810     drawRectangle( rBox.m_aRect );
3811 
3812     // empty appearance, see createDefaultEditAppearance for reference
3813     OString aAppearance = "/Tx BMC\nEMC\n";
3814     writeBuffer( aAppearance.getStr(), aAppearance.getLength() );
3815 
3816     endRedirect();
3817     pop();
3818 
3819     rBox.m_aAppearances[ "N" ][ "Standard" ] = pListBoxStream;
3820 
3821     // prepare DA string
3822     OStringBuffer aDA( 256 );
3823     // prepare DA string
3824     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
3825     aDA.append( ' ' );
3826     aDA.append( "/F" );
3827     aDA.append( nBest );
3828 
3829     OStringBuffer aDR( 32 );
3830     aDR.append( "/Font " );
3831     aDR.append( getFontDictObject() );
3832     aDR.append( " 0 R" );
3833     rBox.m_aDRDict = aDR.makeStringAndClear();
3834     aDA.append( ' ' );
3835     m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
3836     aDA.append( " Tf" );
3837     rBox.m_aDAString = aDA.makeStringAndClear();
3838 }
3839 
3840 void PDFWriterImpl::createDefaultCheckBoxAppearance( PDFWidget& rBox, const PDFWriter::CheckBoxWidget& rWidget )
3841 {
3842     const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
3843 
3844     // save graphics state
3845     push( PushFlags::ALL );
3846 
3847     if( rWidget.Background || rWidget.Border )
3848     {
3849         setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
3850         setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
3851         drawRectangle( rBox.m_aRect );
3852     }
3853 
3854     Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
3855     setFont( aFont );
3856     Size aFontSize = aFont.GetFontSize();
3857     if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
3858         aFontSize.setHeight( rBox.m_aRect.GetHeight() );
3859     sal_Int32 nDelta = aFontSize.Height()/10;
3860     if( nDelta < 1 )
3861         nDelta = 1;
3862 
3863     tools::Rectangle aCheckRect, aTextRect;
3864     {
3865         aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
3866         aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
3867         aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
3868         aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
3869 
3870         // #i74206# handle small controls without text area
3871         while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
3872         {
3873             aCheckRect.AdjustRight( -nDelta );
3874             aCheckRect.AdjustTop(nDelta/2 );
3875             aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
3876         }
3877 
3878         aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
3879         aTextRect.SetTop( rBox.m_aRect.Top() );
3880         aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
3881         aTextRect.SetBottom( rBox.m_aRect.Bottom() );
3882     }
3883     setLineColor( COL_BLACK );
3884     setFillColor( COL_TRANSPARENT );
3885     OStringBuffer aLW( 32 );
3886     aLW.append( "q " );
3887     m_aPages[m_nCurrentPage].appendMappedLength( nDelta, aLW );
3888     aLW.append( " w " );
3889     writeBuffer( aLW.getStr(), aLW.getLength() );
3890     drawRectangle( aCheckRect );
3891     writeBuffer( " Q\n", 3 );
3892     setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
3893     drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
3894 
3895     pop();
3896 
3897     OStringBuffer aDA( 256 );
3898 
3899     // tdf#93853 don't rely on Zapf (or any other 'standard' font)
3900     // being present, but our own OpenSymbol - N.B. PDF/A for good
3901     // reasons require even the standard PS fonts to be embedded!
3902     Push();
3903     SetFont( Font( OUString( "OpenSymbol" ), aFont.GetFontSize() ) );
3904     FontCharMapRef pMap;
3905     GetFontCharMap(pMap);
3906     const LogicalFontInstance* pFontInstance = GetFontInstance();
3907     const PhysicalFontFace* pDevFont = pFontInstance->GetFontFace();
3908     Pop();
3909 
3910     // make sure OpenSymbol is embedded, and includes our checkmark
3911     const sal_Unicode cMark=0x2713;
3912     const GlyphItem aItem(0, 0, pMap->GetGlyphIndex(cMark),
3913                           Point(), GlyphItemFlags::NONE, 0, 0,
3914                           const_cast<LogicalFontInstance*>(pFontInstance));
3915     const std::vector<sal_Ucs> aCodeUnits={ cMark };
3916     sal_uInt8 nMappedGlyph;
3917     sal_Int32 nMappedFontObject;
3918     registerGlyph(&aItem, pDevFont, aCodeUnits, nMappedGlyph, nMappedFontObject);
3919 
3920     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
3921     aDA.append( ' ' );
3922     aDA.append( "/F" );
3923     aDA.append( nMappedFontObject );
3924     aDA.append( " 0 Tf" );
3925 
3926     OStringBuffer aDR( 32 );
3927     aDR.append( "/Font " );
3928     aDR.append( getFontDictObject() );
3929     aDR.append( " 0 R" );
3930     rBox.m_aDRDict = aDR.makeStringAndClear();
3931     rBox.m_aDAString = aDA.makeStringAndClear();
3932     rBox.m_aMKDict = "/CA";
3933     rBox.m_aMKDictCAString = "8";
3934     rBox.m_aRect = aCheckRect;
3935 
3936     // create appearance streams
3937     sal_Int32 nCharXOffset = 1000 - 787; // metrics from OpenSymbol
3938     nCharXOffset *= aCheckRect.GetHeight();
3939     nCharXOffset /= 2000;
3940     sal_Int32 nCharYOffset = 1000 - (820-143); // metrics from Zapf
3941     nCharYOffset *= aCheckRect.GetHeight();
3942     nCharYOffset /= 2000;
3943 
3944     // write 'checked' appearance stream
3945     SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
3946     beginRedirect( pCheckStream, aCheckRect );
3947     aDA.append( "/Tx BMC\nq BT\n" );
3948     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
3949     aDA.append( ' ' );
3950     aDA.append( "/F" );
3951     aDA.append( nMappedFontObject );
3952     aDA.append( ' ' );
3953     m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
3954     aDA.append( " Tf\n" );
3955     m_aPages[ m_nCurrentPage ].appendMappedLength( nCharXOffset, aDA );
3956     aDA.append( " " );
3957     m_aPages[ m_nCurrentPage ].appendMappedLength( nCharYOffset, aDA );
3958     aDA.append( " Td <" );
3959     appendHex( nMappedGlyph, aDA );
3960     aDA.append( "> Tj\nET\nQ\nEMC\n" );
3961     writeBuffer( aDA.getStr(), aDA.getLength() );
3962     endRedirect();
3963     rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream;
3964 
3965     // write 'unchecked' appearance stream
3966     SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
3967     beginRedirect( pUncheckStream, aCheckRect );
3968     writeBuffer( "/Tx BMC\nEMC\n", 12 );
3969     endRedirect();
3970     rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream;
3971 }
3972 
3973 void PDFWriterImpl::createDefaultRadioButtonAppearance( PDFWidget& rBox, const PDFWriter::RadioButtonWidget& rWidget )
3974 {
3975     const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
3976 
3977     // save graphics state
3978     push( PushFlags::ALL );
3979 
3980     if( rWidget.Background || rWidget.Border )
3981     {
3982         setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
3983         setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
3984         drawRectangle( rBox.m_aRect );
3985     }
3986 
3987     Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
3988     setFont( aFont );
3989     Size aFontSize = aFont.GetFontSize();
3990     if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
3991         aFontSize.setHeight( rBox.m_aRect.GetHeight() );
3992     sal_Int32 nDelta = aFontSize.Height()/10;
3993     if( nDelta < 1 )
3994         nDelta = 1;
3995 
3996     tools::Rectangle aCheckRect, aTextRect;
3997     {
3998         aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
3999         aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
4000         aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
4001         aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
4002 
4003         // #i74206# handle small controls without text area
4004         while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
4005         {
4006             aCheckRect.AdjustRight( -nDelta );
4007             aCheckRect.AdjustTop(nDelta/2 );
4008             aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
4009         }
4010 
4011         aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
4012         aTextRect.SetTop( rBox.m_aRect.Top() );
4013         aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
4014         aTextRect.SetBottom( rBox.m_aRect.Bottom() );
4015     }
4016     setLineColor( COL_BLACK );
4017     setFillColor( COL_TRANSPARENT );
4018     OStringBuffer aLW( 32 );
4019     aLW.append( "q " );
4020     m_aPages[ m_nCurrentPage ].appendMappedLength( nDelta, aLW );
4021     aLW.append( " w " );
4022     writeBuffer( aLW.getStr(), aLW.getLength() );
4023     drawEllipse( aCheckRect );
4024     writeBuffer( " Q\n", 3 );
4025     setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4026     drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
4027 
4028     pop();
4029 
4030     OStringBuffer aDA( 256 );
4031     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
4032     rBox.m_aDAString = aDA.makeStringAndClear();
4033     //to encrypt this (el)
4034     rBox.m_aMKDict = "/CA";
4035     //after this assignment, to m_aMKDic cannot be added anything
4036     rBox.m_aMKDictCAString = "l";
4037 
4038     rBox.m_aRect = aCheckRect;
4039 
4040     // create appearance streams
4041     push( PushFlags::ALL);
4042     SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
4043 
4044     beginRedirect( pCheckStream, aCheckRect );
4045     aDA.append( "/Tx BMC\nq BT\n" );
4046     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
4047     aDA.append( ' ' );
4048     m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
4049     aDA.append( " 0 0 Td\nET\nQ\n" );
4050     writeBuffer( aDA.getStr(), aDA.getLength() );
4051     setFillColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4052     setLineColor( COL_TRANSPARENT );
4053     aCheckRect.AdjustLeft(3*nDelta );
4054     aCheckRect.AdjustTop(3*nDelta );
4055     aCheckRect.AdjustBottom( -(3*nDelta) );
4056     aCheckRect.AdjustRight( -(3*nDelta) );
4057     drawEllipse( aCheckRect );
4058     writeBuffer( "\nEMC\n", 5 );
4059     endRedirect();
4060 
4061     pop();
4062     rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream;
4063 
4064     SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
4065     beginRedirect( pUncheckStream, aCheckRect );
4066     writeBuffer( "/Tx BMC\nEMC\n", 12 );
4067     endRedirect();
4068     rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream;
4069 }
4070 
4071 bool PDFWriterImpl::emitAppearances( PDFWidget& rWidget, OStringBuffer& rAnnotDict )
4072 {
4073     // TODO: check and insert default streams
4074     OString aStandardAppearance;
4075     switch( rWidget.m_eType )
4076     {
4077         case PDFWriter::CheckBox:
4078             aStandardAppearance = OUStringToOString( rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US );
4079             break;
4080         default:
4081             break;
4082     }
4083 
4084     if( !rWidget.m_aAppearances.empty() )
4085     {
4086         rAnnotDict.append( "/AP<<\n" );
4087         for (auto & dict_item : rWidget.m_aAppearances)
4088         {
4089             rAnnotDict.append( "/" );
4090             rAnnotDict.append( dict_item.first );
4091             bool bUseSubDict = (dict_item.second.size() > 1);
4092 
4093             // PDF/A requires sub-dicts for /FT/Btn objects (clause
4094             // 6.3.3)
4095             if( m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3)
4096             {
4097                 if( rWidget.m_eType == PDFWriter::RadioButton ||
4098                     rWidget.m_eType == PDFWriter::CheckBox ||
4099                     rWidget.m_eType == PDFWriter::PushButton )
4100                 {
4101                     bUseSubDict = true;
4102                 }
4103             }
4104 
4105             rAnnotDict.append( bUseSubDict ? "<<" : " " );
4106 
4107             for (auto const& stream_item : dict_item.second)
4108             {
4109                 SvMemoryStream* pApppearanceStream = stream_item.second;
4110                 dict_item.second[ stream_item.first ] = nullptr;
4111 
4112                 bool bDeflate = compressStream( pApppearanceStream );
4113 
4114                 sal_Int64 nStreamLen = pApppearanceStream->TellEnd();
4115                 pApppearanceStream->Seek( STREAM_SEEK_TO_BEGIN );
4116                 sal_Int32 nObject = createObject();
4117                 CHECK_RETURN( updateObject( nObject ) );
4118                 if (g_bDebugDisableCompression)
4119                 {
4120                     emitComment( "PDFWriterImpl::emitAppearances" );
4121                 }
4122                 OStringBuffer aLine;
4123                 aLine.append( nObject );
4124 
4125                 aLine.append( " 0 obj\n"
4126                               "<</Type/XObject\n"
4127                               "/Subtype/Form\n"
4128                               "/BBox[0 0 " );
4129                 appendFixedInt( rWidget.m_aRect.GetWidth()-1, aLine );
4130                 aLine.append( " " );
4131                 appendFixedInt( rWidget.m_aRect.GetHeight()-1, aLine );
4132                 aLine.append( "]\n"
4133                               "/Resources " );
4134                 aLine.append( getResourceDictObj() );
4135                 aLine.append( " 0 R\n"
4136                               "/Length " );
4137                 aLine.append( nStreamLen );
4138                 aLine.append( "\n" );
4139                 if( bDeflate )
4140                     aLine.append( "/Filter/FlateDecode\n" );
4141                 aLine.append( ">>\nstream\n" );
4142                 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
4143                 checkAndEnableStreamEncryption( nObject );
4144                 CHECK_RETURN( writeBuffer( pApppearanceStream->GetData(), nStreamLen ) );
4145                 disableStreamEncryption();
4146                 CHECK_RETURN( writeBuffer( "\nendstream\nendobj\n\n", 19 ) );
4147 
4148                 if( bUseSubDict )
4149                 {
4150                     rAnnotDict.append( " /" );
4151                     rAnnotDict.append( stream_item.first );
4152                     rAnnotDict.append( " " );
4153                 }
4154                 rAnnotDict.append( nObject );
4155                 rAnnotDict.append( " 0 R" );
4156 
4157                 delete pApppearanceStream;
4158             }
4159 
4160             rAnnotDict.append( bUseSubDict ? ">>\n" : "\n" );
4161         }
4162         rAnnotDict.append( ">>\n" );
4163         if( !aStandardAppearance.isEmpty() )
4164         {
4165             rAnnotDict.append( "/AS /" );
4166             rAnnotDict.append( aStandardAppearance );
4167             rAnnotDict.append( "\n" );
4168         }
4169     }
4170 
4171     return true;
4172 }
4173 
4174 bool PDFWriterImpl::emitWidgetAnnotations()
4175 {
4176     ensureUniqueRadioOnValues();
4177 
4178     int nAnnots = m_aWidgets.size();
4179     for( int a = 0; a < nAnnots; a++ )
4180     {
4181         PDFWidget& rWidget = m_aWidgets[a];
4182 
4183         OStringBuffer aLine( 1024 );
4184         OStringBuffer aValue( 256 );
4185         aLine.append( rWidget.m_nObject );
4186         aLine.append( " 0 obj\n"
4187                       "<<" );
4188         if( rWidget.m_eType != PDFWriter::Hierarchy )
4189         {
4190             // emit widget annotation only for terminal fields
4191             if( rWidget.m_aKids.empty() )
4192             {
4193                 int iRectMargin;
4194 
4195                 aLine.append( "/Type/Annot/Subtype/Widget/F " );
4196 
4197                 if (rWidget.m_eType == PDFWriter::Signature)
4198                 {
4199                     aLine.append( "132\n" ); // Print & Locked
4200                     iRectMargin = 0;
4201                 }
4202                 else
4203                 {
4204                     aLine.append( "4\n" );
4205                     iRectMargin = 1;
4206                 }
4207 
4208                 aLine.append("/Rect[" );
4209                 appendFixedInt( rWidget.m_aRect.Left()-iRectMargin, aLine );
4210                 aLine.append( ' ' );
4211                 appendFixedInt( rWidget.m_aRect.Top()+iRectMargin, aLine );
4212                 aLine.append( ' ' );
4213                 appendFixedInt( rWidget.m_aRect.Right()+iRectMargin, aLine );
4214                 aLine.append( ' ' );
4215                 appendFixedInt( rWidget.m_aRect.Bottom()-iRectMargin, aLine );
4216                 aLine.append( "]\n" );
4217             }
4218             aLine.append( "/FT/" );
4219             switch( rWidget.m_eType )
4220             {
4221                 case PDFWriter::RadioButton:
4222                 case PDFWriter::CheckBox:
4223                     // for radio buttons only the RadioButton field, not the
4224                     // CheckBox children should have a value, else acrobat reader
4225                     // does not always check the right button
4226                     // of course real check boxes (not belonging to a radio group)
4227                     // need their values, too
4228                     if( rWidget.m_eType == PDFWriter::RadioButton || rWidget.m_nRadioGroup < 0 )
4229                     {
4230                         aValue.append( "/" );
4231                         // check for radio group with all buttons unpressed
4232                         if( rWidget.m_aValue.isEmpty() )
4233                             aValue.append( "Off" );
4234                         else
4235                             appendName( rWidget.m_aValue, aValue );
4236                     }
4237                     [[fallthrough]];
4238                 case PDFWriter::PushButton:
4239                     aLine.append( "Btn" );
4240                     break;
4241                 case PDFWriter::ListBox:
4242                     if( rWidget.m_nFlags & 0x200000 ) // multiselect
4243                     {
4244                         aValue.append( "[" );
4245                         for( size_t i = 0; i < rWidget.m_aSelectedEntries.size(); i++ )
4246                         {
4247                             sal_Int32 nEntry = rWidget.m_aSelectedEntries[i];
4248                             if( nEntry >= 0 && nEntry < sal_Int32(rWidget.m_aListEntries.size()) )
4249                                 appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ nEntry ], rWidget.m_nObject, aValue );
4250                         }
4251                         aValue.append( "]" );
4252                     }
4253                     else if( !rWidget.m_aSelectedEntries.empty() &&
4254                              rWidget.m_aSelectedEntries[0] >= 0 &&
4255                              rWidget.m_aSelectedEntries[0] < sal_Int32(rWidget.m_aListEntries.size()) )
4256                     {
4257                         appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ rWidget.m_aSelectedEntries[0] ], rWidget.m_nObject, aValue );
4258                     }
4259                     else
4260                         appendUnicodeTextStringEncrypt( OUString(), rWidget.m_nObject, aValue );
4261                     aLine.append( "Ch" );
4262                     break;
4263                 case PDFWriter::ComboBox:
4264                     appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue );
4265                     aLine.append( "Ch" );
4266                     break;
4267                 case PDFWriter::Edit:
4268                     aLine.append( "Tx" );
4269                     appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue );
4270                     break;
4271                 case PDFWriter::Signature:
4272                     aLine.append( "Sig" );
4273                     aValue.append(OUStringToOString(rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US));
4274                     break;
4275                 case PDFWriter::Hierarchy: // make the compiler happy
4276                     break;
4277             }
4278             aLine.append( "\n" );
4279             aLine.append( "/P " );
4280             aLine.append( m_aPages[ rWidget.m_nPage ].m_nPageObject );
4281             aLine.append( " 0 R\n" );
4282         }
4283         if( rWidget.m_nParent )
4284         {
4285             aLine.append( "/Parent " );
4286             aLine.append( rWidget.m_nParent );
4287             aLine.append( " 0 R\n" );
4288         }
4289         if( !rWidget.m_aKids.empty() )
4290         {
4291             aLine.append( "/Kids[" );
4292             for( size_t i = 0; i < rWidget.m_aKids.size(); i++ )
4293             {
4294                 aLine.append( rWidget.m_aKids[i] );
4295                 aLine.append( " 0 R" );
4296                 aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
4297             }
4298             aLine.append( "]\n" );
4299         }
4300         if( !rWidget.m_aName.isEmpty() )
4301         {
4302             aLine.append( "/T" );
4303             appendLiteralStringEncrypt( rWidget.m_aName, rWidget.m_nObject, aLine );
4304             aLine.append( "\n" );
4305         }
4306         if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2 && !rWidget.m_aDescription.isEmpty() )
4307         {
4308             // the alternate field name should be unicode able since it is
4309             // supposed to be used in UI
4310             aLine.append( "/TU" );
4311             appendUnicodeTextStringEncrypt( rWidget.m_aDescription, rWidget.m_nObject, aLine );
4312             aLine.append( "\n" );
4313         }
4314 
4315         if( rWidget.m_nFlags )
4316         {
4317             aLine.append( "/Ff " );
4318             aLine.append( rWidget.m_nFlags );
4319             aLine.append( "\n" );
4320         }
4321         if( !aValue.isEmpty() )
4322         {
4323             OString aVal = aValue.makeStringAndClear();
4324             aLine.append( "/V " );
4325             aLine.append( aVal );
4326             aLine.append( "\n"
4327                           "/DV " );
4328             aLine.append( aVal );
4329             aLine.append( "\n" );
4330         }
4331         if( rWidget.m_eType == PDFWriter::ListBox || rWidget.m_eType == PDFWriter::ComboBox )
4332         {
4333             sal_Int32 nTI = -1;
4334             aLine.append( "/Opt[\n" );
4335             sal_Int32 i = 0;
4336             for (auto const& entry : rWidget.m_aListEntries)
4337             {
4338                 appendUnicodeTextStringEncrypt( entry, rWidget.m_nObject, aLine );
4339                 aLine.append( "\n" );
4340                 if( entry == rWidget.m_aValue )
4341                     nTI = i;
4342                 ++i;
4343             }
4344             aLine.append( "]\n" );
4345             if( nTI > 0 )
4346             {
4347                 aLine.append( "/TI " );
4348                 aLine.append( nTI );
4349                 aLine.append( "\n" );
4350                 if( rWidget.m_nFlags & 0x200000 ) // Multiselect
4351                 {
4352                     aLine.append( "/I [" );
4353                     aLine.append( nTI );
4354                     aLine.append( "]\n" );
4355                 }
4356             }
4357         }
4358         if( rWidget.m_eType == PDFWriter::Edit && rWidget.m_nMaxLen > 0 )
4359         {
4360             aLine.append( "/MaxLen " );
4361             aLine.append( rWidget.m_nMaxLen );
4362             aLine.append( "\n" );
4363         }
4364         if( rWidget.m_eType == PDFWriter::PushButton )
4365         {
4366             if(!m_bIsPDF_A1)
4367             {
4368                 OStringBuffer aDest;
4369                 if( rWidget.m_nDest != -1 && appendDest( m_aDestinationIdTranslation[ rWidget.m_nDest ], aDest ) )
4370                 {
4371                     aLine.append( "/AA<</D<</Type/Action/S/GoTo/D " );
4372                     aLine.append( aDest.makeStringAndClear() );
4373                     aLine.append( ">>>>\n" );
4374                 }
4375                 else if( rWidget.m_aListEntries.empty() )
4376                 {
4377                     if( !m_bIsPDF_A2 && !m_bIsPDF_A3 )
4378                     {
4379                         // create a reset form action
4380                         aLine.append( "/AA<</D<</Type/Action/S/ResetForm>>>>\n" );
4381                     }
4382                 }
4383                 else if( rWidget.m_bSubmit )
4384                 {
4385                     // create a submit form action
4386                     aLine.append( "/AA<</D<</Type/Action/S/SubmitForm/F" );
4387                     appendLiteralStringEncrypt( rWidget.m_aListEntries.front(), rWidget.m_nObject, aLine, osl_getThreadTextEncoding() );
4388                     aLine.append( "/Flags " );
4389 
4390                     sal_Int32 nFlags = 0;
4391                     switch( m_aContext.SubmitFormat )
4392                     {
4393                     case PDFWriter::HTML:
4394                         nFlags |= 4;
4395                         break;
4396                     case PDFWriter::XML:
4397                         if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
4398                             nFlags |= 32;
4399                         break;
4400                     case PDFWriter::PDF:
4401                         if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
4402                             nFlags |= 256;
4403                         break;
4404                     case PDFWriter::FDF:
4405                     default:
4406                         break;
4407                     }
4408                     if( rWidget.m_bSubmitGet )
4409                         nFlags |= 8;
4410                     aLine.append( nFlags );
4411                     aLine.append( ">>>>\n" );
4412                 }
4413                 else
4414                 {
4415                     // create a URI action
4416                     aLine.append( "/AA<</D<</Type/Action/S/URI/URI(" );
4417                     aLine.append( OUStringToOString( rWidget.m_aListEntries.front(), RTL_TEXTENCODING_ASCII_US ) );
4418                     aLine.append( ")>>>>\n" );
4419                 }
4420             }
4421             else
4422                 m_aErrors.insert( PDFWriter::Warning_FormAction_Omitted_PDFA );
4423         }
4424         if( !rWidget.m_aDAString.isEmpty() )
4425         {
4426             if( !rWidget.m_aDRDict.isEmpty() )
4427             {
4428                 aLine.append( "/DR<<" );
4429                 aLine.append( rWidget.m_aDRDict );
4430                 aLine.append( ">>\n" );
4431             }
4432             else
4433             {
4434                 aLine.append( "/DR<</Font<<" );
4435                 appendBuildinFontsToDict( aLine );
4436                 aLine.append( ">>>>\n" );
4437             }
4438             aLine.append( "/DA" );
4439             appendLiteralStringEncrypt( rWidget.m_aDAString, rWidget.m_nObject, aLine );
4440             aLine.append( "\n" );
4441             if( rWidget.m_nTextStyle & DrawTextFlags::Center )
4442                 aLine.append( "/Q 1\n" );
4443             else if( rWidget.m_nTextStyle & DrawTextFlags::Right )
4444                 aLine.append( "/Q 2\n" );
4445         }
4446         // appearance characteristics for terminal fields
4447         // which are supposed to have an appearance constructed
4448         // by the viewer application
4449         if( !rWidget.m_aMKDict.isEmpty() )
4450         {
4451             aLine.append( "/MK<<" );
4452             aLine.append( rWidget.m_aMKDict );
4453             //add the CA string, encrypting it
4454             appendLiteralStringEncrypt(rWidget.m_aMKDictCAString, rWidget.m_nObject, aLine);
4455             aLine.append( ">>\n" );
4456         }
4457 
4458         CHECK_RETURN( emitAppearances( rWidget, aLine ) );
4459 
4460         aLine.append( ">>\n"
4461                       "endobj\n\n" );
4462         CHECK_RETURN( updateObject( rWidget.m_nObject ) );
4463         CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
4464     }
4465     return true;
4466 }
4467 
4468 bool PDFWriterImpl::emitAnnotations()
4469 {
4470     if( m_aPages.empty() )
4471         return false;
4472 
4473     CHECK_RETURN( emitLinkAnnotations() );
4474     CHECK_RETURN(emitScreenAnnotations());
4475     CHECK_RETURN( emitNoteAnnotations() );
4476     CHECK_RETURN( emitWidgetAnnotations() );
4477 
4478     return true;
4479 }
4480 
4481 bool PDFWriterImpl::emitEmbeddedFiles()
4482 {
4483     for (auto& rEmbeddedFile : m_aEmbeddedFiles)
4484     {
4485         if (!updateObject(rEmbeddedFile.m_nObject))
4486             continue;
4487 
4488         OStringBuffer aLine;
4489         aLine.append(rEmbeddedFile.m_nObject);
4490         aLine.append(" 0 obj\n");
4491         aLine.append("<< /Type /EmbeddedFile /Length ");
4492         aLine.append(static_cast<sal_Int64>(rEmbeddedFile.m_pData->size()));
4493         aLine.append(" >>\nstream\n");
4494         CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
4495         aLine.setLength(0);
4496 
4497         CHECK_RETURN(writeBuffer(rEmbeddedFile.m_pData->data(), rEmbeddedFile.m_pData->size()));
4498 
4499         aLine.append("\nendstream\nendobj\n\n");
4500         CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
4501     }
4502     return true;
4503 }
4504 
4505 #undef CHECK_RETURN
4506 #define CHECK_RETURN( x ) if( !x ) return false
4507 
4508 bool PDFWriterImpl::emitCatalog()
4509 {
4510     // build page tree
4511     // currently there is only one node that contains all leaves
4512 
4513     // first create a page tree node id
4514     sal_Int32 nTreeNode = createObject();
4515 
4516     // emit global resource dictionary (page emit needs it)
4517     CHECK_RETURN( emitResources() );
4518 
4519     // emit all pages
4520     for (auto & page : m_aPages)
4521         if( ! page.emit( nTreeNode ) )
4522             return false;
4523 
4524     sal_Int32 nNamedDestinationsDictionary = emitNamedDestinations();
4525 
4526     sal_Int32 nOutlineDict = emitOutline();
4527 
4528     // emit Output intent
4529     sal_Int32 nOutputIntentObject = emitOutputIntent();
4530 
4531     // emit metadata
4532     sal_Int32 nMetadataObject = emitDocumentMetadata();
4533 
4534     sal_Int32 nStructureDict = 0;
4535     if(m_aStructure.size() > 1)
4536     {
4537         // check if dummy structure containers are needed
4538         addInternalStructureContainer(m_aStructure[0]);
4539         nStructureDict = m_aStructure[0].m_nObject = createObject();
4540         emitStructure( m_aStructure[ 0 ] );
4541     }
4542 
4543     // adjust tree node file offset
4544     if( ! updateObject( nTreeNode ) )
4545         return false;
4546 
4547     // emit tree node
4548     OStringBuffer aLine( 2048 );
4549     aLine.append( nTreeNode );
4550     aLine.append( " 0 obj\n" );
4551     aLine.append( "<</Type/Pages\n" );
4552     aLine.append( "/Resources " );
4553     aLine.append( getResourceDictObj() );
4554     aLine.append( " 0 R\n" );
4555 
4556     sal_Int32 nMediaBoxWidth = 0;
4557     sal_Int32 nMediaBoxHeight = 0;
4558     sal_Int32 nUserUnit = 1;
4559     if( m_aPages.empty() ) // sanity check, this should not happen
4560     {
4561         nMediaBoxWidth = g_nInheritedPageWidth;
4562         nMediaBoxHeight = g_nInheritedPageHeight;
4563     }
4564     else
4565     {
4566         for (auto const& page : m_aPages)
4567         {
4568             if( page.m_nPageWidth > nMediaBoxWidth )
4569             {
4570                 nMediaBoxWidth = page.m_nPageWidth;
4571                 nUserUnit = page.m_nUserUnit;
4572             }
4573             if( page.m_nPageHeight > nMediaBoxHeight )
4574             {
4575                 nMediaBoxHeight = page.m_nPageHeight;
4576                 nUserUnit = page.m_nUserUnit;
4577             }
4578         }
4579     }
4580     aLine.append( "/MediaBox[ 0 0 " );
4581     aLine.append(nMediaBoxWidth / nUserUnit);
4582     aLine.append( ' ' );
4583     aLine.append(nMediaBoxHeight / nUserUnit);
4584     aLine.append(" ]\n");
4585     if (nUserUnit > 1)
4586     {
4587         aLine.append("/UserUnit ");
4588         aLine.append(nUserUnit);
4589         aLine.append("\n");
4590     }
4591     aLine.append("/Kids[ ");
4592     unsigned int i = 0;
4593     for (const auto & page : m_aPages)
4594     {
4595         aLine.append( page.m_nPageObject );
4596         aLine.append( " 0 R" );
4597         aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
4598         ++i;
4599     }
4600     aLine.append( "]\n"
4601                   "/Count " );
4602     aLine.append( static_cast<sal_Int32>(m_aPages.size()) );
4603     aLine.append( ">>\n"
4604                   "endobj\n\n" );
4605     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
4606 
4607     // emit annotation objects
4608     CHECK_RETURN( emitAnnotations() );
4609     CHECK_RETURN( emitEmbeddedFiles() );
4610 
4611     // emit Catalog
4612     m_nCatalogObject = createObject();
4613     if( ! updateObject( m_nCatalogObject ) )
4614         return false;
4615     aLine.setLength( 0 );
4616     aLine.append( m_nCatalogObject );
4617     aLine.append( " 0 obj\n"
4618                   "<</Type/Catalog/Pages " );
4619     aLine.append( nTreeNode );
4620     aLine.append( " 0 R\n" );
4621 
4622     // check if there are named destinations to emit (root must be inside the catalog)
4623     if( nNamedDestinationsDictionary )
4624     {
4625         aLine.append("/Dests ");
4626         aLine.append( nNamedDestinationsDictionary );
4627         aLine.append( " 0 R\n" );
4628     }
4629 
4630     if( m_aContext.PageLayout != PDFWriter::DefaultLayout )
4631         switch(  m_aContext.PageLayout )
4632         {
4633         default :
4634         case  PDFWriter::SinglePage :
4635             aLine.append( "/PageLayout/SinglePage\n" );
4636             break;
4637         case  PDFWriter::Continuous :
4638             aLine.append( "/PageLayout/OneColumn\n" );
4639             break;
4640         case  PDFWriter::ContinuousFacing :
4641             // the flag m_aContext.FirstPageLeft below is used to set the page on the left side
4642             aLine.append( "/PageLayout/TwoColumnRight\n" );//odd page on the right side
4643             break;
4644         }
4645     if( m_aContext.PDFDocumentMode != PDFWriter::ModeDefault && !m_aContext.OpenInFullScreenMode )
4646         switch(  m_aContext.PDFDocumentMode )
4647         {
4648         default :
4649             aLine.append( "/PageMode/UseNone\n" );
4650             break;
4651         case PDFWriter::UseOutlines :
4652             aLine.append( "/PageMode/UseOutlines\n" ); //document is opened with outline pane open
4653             break;
4654         case PDFWriter::UseThumbs :
4655             aLine.append( "/PageMode/UseThumbs\n" ); //document is opened with thumbnails pane open
4656             break;
4657         }
4658     else if( m_aContext.OpenInFullScreenMode )
4659         aLine.append( "/PageMode/FullScreen\n" ); //document is opened full screen
4660 
4661     OStringBuffer aInitPageRef;
4662     if( m_aContext.InitialPage >= 0 && m_aContext.InitialPage < static_cast<sal_Int32>(m_aPages.size()) )
4663     {
4664         aInitPageRef.append( m_aPages[m_aContext.InitialPage].m_nPageObject );
4665         aInitPageRef.append( " 0 R" );
4666     }
4667     else
4668         aInitPageRef.append( "0" );
4669 
4670     switch( m_aContext.PDFDocumentAction )
4671     {
4672     case PDFWriter::ActionDefault :     //do nothing, this is the Acrobat default
4673     default:
4674         if( aInitPageRef.getLength() > 1 )
4675         {
4676             aLine.append( "/OpenAction[" );
4677             aLine.append( aInitPageRef.makeStringAndClear() );
4678             aLine.append( " /XYZ null null 0]\n" );
4679         }
4680         break;
4681     case PDFWriter::FitInWindow :
4682         aLine.append( "/OpenAction[" );
4683         aLine.append( aInitPageRef.makeStringAndClear() );
4684         aLine.append( " /Fit]\n" ); //Open fit page
4685         break;
4686     case PDFWriter::FitWidth :
4687         aLine.append( "/OpenAction[" );
4688         aLine.append( aInitPageRef.makeStringAndClear() );
4689         aLine.append( " /FitH " );
4690         aLine.append( g_nInheritedPageHeight );//Open fit width
4691         aLine.append( "]\n" );
4692         break;
4693     case PDFWriter::FitVisible :
4694         aLine.append( "/OpenAction[" );
4695         aLine.append( aInitPageRef.makeStringAndClear() );
4696         aLine.append( " /FitBH " );
4697         aLine.append( g_nInheritedPageHeight );//Open fit visible
4698         aLine.append( "]\n" );
4699         break;
4700     case PDFWriter::ActionZoom :
4701         aLine.append( "/OpenAction[" );
4702         aLine.append( aInitPageRef.makeStringAndClear() );
4703         aLine.append( " /XYZ null null " );
4704         if( m_aContext.Zoom >= 50 && m_aContext.Zoom <= 1600 )
4705             aLine.append( static_cast<double>(m_aContext.Zoom)/100.0 );
4706         else
4707             aLine.append( "0" );
4708         aLine.append( "]\n" );
4709         break;
4710     }
4711 
4712     // viewer preferences, if we had some, then emit
4713     if( m_aContext.HideViewerToolbar ||
4714         ( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 && !m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle ) ||
4715         m_aContext.HideViewerMenubar ||
4716         m_aContext.HideViewerWindowControls || m_aContext.FitWindow ||
4717         m_aContext.CenterWindow || (m_aContext.FirstPageLeft  &&  m_aContext.PageLayout == PDFWriter::ContinuousFacing ) ||
4718         m_aContext.OpenInFullScreenMode )
4719     {
4720         aLine.append( "/ViewerPreferences<<" );
4721         if( m_aContext.HideViewerToolbar )
4722             aLine.append( "/HideToolbar true\n" );
4723         if( m_aContext.HideViewerMenubar )
4724             aLine.append( "/HideMenubar true\n" );
4725         if( m_aContext.HideViewerWindowControls )
4726             aLine.append( "/HideWindowUI true\n" );
4727         if( m_aContext.FitWindow )
4728             aLine.append( "/FitWindow true\n" );
4729         if( m_aContext.CenterWindow )
4730             aLine.append( "/CenterWindow true\n" );
4731         if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 && !m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle )
4732             aLine.append( "/DisplayDocTitle true\n" );
4733         if( m_aContext.FirstPageLeft &&  m_aContext.PageLayout == PDFWriter::ContinuousFacing )
4734             aLine.append( "/Direction/R2L\n" );
4735         if( m_aContext.OpenInFullScreenMode )
4736             switch( m_aContext.PDFDocumentMode )
4737             {
4738             default :
4739             case PDFWriter::ModeDefault :
4740                 aLine.append( "/NonFullScreenPageMode/UseNone\n" );
4741                 break;
4742             case PDFWriter::UseOutlines :
4743                 aLine.append( "/NonFullScreenPageMode/UseOutlines\n" );
4744                 break;
4745             case PDFWriter::UseThumbs :
4746                 aLine.append( "/NonFullScreenPageMode/UseThumbs\n" );
4747                 break;
4748             }
4749         aLine.append( ">>\n" );
4750     }
4751 
4752     if( nOutlineDict )
4753     {
4754         aLine.append( "/Outlines " );
4755         aLine.append( nOutlineDict );
4756         aLine.append( " 0 R\n" );
4757     }
4758     if( nStructureDict )
4759     {
4760         aLine.append( "/StructTreeRoot " );
4761         aLine.append( nStructureDict );
4762         aLine.append( " 0 R\n" );
4763     }
4764     if( !m_aContext.DocumentLocale.Language.isEmpty() )
4765     {
4766         /* PDF allows only RFC 3066, see above in emitStructure(). */
4767         LanguageTag aLanguageTag( m_aContext.DocumentLocale);
4768         OUString aLanguage, aScript, aCountry;
4769         aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
4770         if (!aLanguage.isEmpty())
4771         {
4772             OUStringBuffer aLocBuf( 16 );
4773             aLocBuf.append( aLanguage );
4774             if( !aCountry.isEmpty() )
4775             {
4776                 aLocBuf.append( '-' );
4777                 aLocBuf.append( aCountry );
4778             }
4779             aLine.append( "/Lang" );
4780             appendLiteralStringEncrypt( aLocBuf.makeStringAndClear(), m_nCatalogObject, aLine );
4781             aLine.append( "\n" );
4782         }
4783     }
4784     if( m_aContext.Tagged && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
4785     {
4786         aLine.append( "/MarkInfo<</Marked true>>\n" );
4787     }
4788     if( !m_aWidgets.empty() )
4789     {
4790         aLine.append( "/AcroForm<</Fields[\n" );
4791         int nWidgets = m_aWidgets.size();
4792         int nOut = 0;
4793         for( int j = 0; j < nWidgets; j++ )
4794         {
4795             // output only root fields
4796             if( m_aWidgets[j].m_nParent < 1 )
4797             {
4798                 aLine.append( m_aWidgets[j].m_nObject );
4799                 aLine.append( (nOut++ % 5)==4 ? " 0 R\n" : " 0 R " );
4800             }
4801         }
4802         aLine.append( "\n]" );
4803 
4804 #if HAVE_FEATURE_NSS
4805         if (m_nSignatureObject != -1)
4806             aLine.append( "/SigFlags 3");
4807 #endif
4808 
4809         aLine.append( "/DR " );
4810         aLine.append( getResourceDictObj() );
4811         aLine.append( " 0 R" );
4812         // NeedAppearances must not be used if PDF is signed
4813         if( m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3
4814 #if HAVE_FEATURE_NSS
4815             || ( m_nSignatureObject != -1 )
4816 #endif
4817             )
4818             aLine.append( ">>\n" );
4819         else
4820             aLine.append( "/NeedAppearances true>>\n" );
4821     }
4822 
4823     //check if there is a Metadata object
4824     if( nOutputIntentObject )
4825     {
4826         aLine.append("/OutputIntents[");
4827         aLine.append( nOutputIntentObject );
4828         aLine.append( " 0 R]" );
4829     }
4830 
4831     if( nMetadataObject )
4832     {
4833         aLine.append("/Metadata ");
4834         aLine.append( nMetadataObject );
4835         aLine.append( " 0 R" );
4836     }
4837 
4838     aLine.append( ">>\n"
4839                   "endobj\n\n" );
4840     return writeBuffer( aLine.getStr(), aLine.getLength() );
4841 }
4842 
4843 #if HAVE_FEATURE_NSS
4844 
4845 bool PDFWriterImpl::emitSignature()
4846 {
4847     if( !updateObject( m_nSignatureObject ) )
4848         return false;
4849 
4850     OStringBuffer aLine( 0x5000 );
4851     aLine.append( m_nSignatureObject );
4852     aLine.append( " 0 obj\n" );
4853     aLine.append("<</Contents <" );
4854 
4855     sal_uInt64 nOffset = ~0U;
4856     CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nOffset) ) );
4857 
4858     m_nSignatureContentOffset = nOffset + aLine.getLength();
4859 
4860     // reserve some space for the PKCS#7 object
4861     OStringBuffer aContentFiller( MAX_SIGNATURE_CONTENT_LENGTH );
4862     comphelper::string::padToLength(aContentFiller, MAX_SIGNATURE_CONTENT_LENGTH, '0');
4863     aLine.append( aContentFiller.makeStringAndClear() );
4864     aLine.append( ">\n/Type/Sig/SubFilter/adbe.pkcs7.detached");
4865 
4866     if( !m_aContext.DocumentInfo.Author.isEmpty() )
4867     {
4868         aLine.append( "/Name" );
4869         appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Author, m_nSignatureObject, aLine );
4870     }
4871 
4872     aLine.append( " /M ");
4873     appendLiteralStringEncrypt( m_aCreationDateString, m_nSignatureObject, aLine );
4874 
4875     aLine.append( " /ByteRange [ 0 ");
4876     aLine.append( m_nSignatureContentOffset - 1 );
4877     aLine.append( " " );
4878     aLine.append( m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1 );
4879     aLine.append( " " );
4880 
4881     m_nSignatureLastByteRangeNoOffset = nOffset + aLine.getLength();
4882 
4883     // mark the last ByteRange no and add some space. Now, we don't know
4884     // how many bytes we need for this ByteRange value
4885     // The real value will be overwritten in the finalizeSignature method
4886     OStringBuffer aByteRangeFiller( 100  );
4887     comphelper::string::padToLength(aByteRangeFiller, 100, ' ');
4888     aLine.append( aByteRangeFiller.makeStringAndClear() );
4889     aLine.append("  /Filter/Adobe.PPKMS");
4890 
4891     //emit reason, location and contactinfo
4892     if ( !m_aContext.SignReason.isEmpty() )
4893     {
4894         aLine.append("/Reason");
4895         appendUnicodeTextStringEncrypt( m_aContext.SignReason, m_nSignatureObject, aLine );
4896     }
4897 
4898     if ( !m_aContext.SignLocation.isEmpty() )
4899     {
4900         aLine.append("/Location");
4901         appendUnicodeTextStringEncrypt( m_aContext.SignLocation, m_nSignatureObject, aLine );
4902     }
4903 
4904     if ( !m_aContext.SignContact.isEmpty() )
4905     {
4906         aLine.append("/ContactInfo");
4907         appendUnicodeTextStringEncrypt( m_aContext.SignContact, m_nSignatureObject, aLine );
4908     }
4909 
4910     aLine.append(" >>\nendobj\n\n" );
4911 
4912     return writeBuffer( aLine.getStr(), aLine.getLength() );
4913 }
4914 
4915 bool PDFWriterImpl::finalizeSignature()
4916 {
4917     if (!m_aContext.SignCertificate.is())
4918         return false;
4919 
4920     // 1- calculate last ByteRange value
4921     sal_uInt64 nOffset = ~0U;
4922     CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nOffset) ) );
4923 
4924     sal_Int64 nLastByteRangeNo = nOffset - (m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1);
4925 
4926     // 2- overwrite the value to the m_nSignatureLastByteRangeNoOffset position
4927     sal_uInt64 nWritten = 0;
4928     CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureLastByteRangeNoOffset) ) );
4929     OString aByteRangeNo = OString::number( nLastByteRangeNo ) + " ]";
4930 
4931     if (m_aFile.write(aByteRangeNo.getStr(), aByteRangeNo.getLength(), nWritten) != osl::File::E_None)
4932     {
4933         CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset)) );
4934         return false;
4935     }
4936 
4937     // 3- create the PKCS#7 object using NSS
4938 
4939     // Prepare buffer and calculate PDF file digest
4940     CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, 0)) );
4941 
4942     std::unique_ptr<char[]> buffer1(new char[m_nSignatureContentOffset + 1]);
4943     sal_uInt64 bytesRead1;
4944 
4945     //FIXME: Check if hash is calculated from the correct byterange
4946     if (osl::File::E_None != m_aFile.read(buffer1.get(), m_nSignatureContentOffset - 1 , bytesRead1) ||
4947         bytesRead1 != static_cast<sal_uInt64>(m_nSignatureContentOffset) - 1)
4948     {
4949         SAL_WARN("vcl.pdfwriter", "First buffer read failed");
4950         return false;
4951     }
4952 
4953     std::unique_ptr<char[]> buffer2(new char[nLastByteRangeNo + 1]);
4954     sal_uInt64 bytesRead2;
4955 
4956     if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1) ||
4957         osl::File::E_None != m_aFile.read(buffer2.get(), nLastByteRangeNo, bytesRead2) ||
4958         bytesRead2 != static_cast<sal_uInt64>(nLastByteRangeNo))
4959     {
4960         SAL_WARN("vcl.pdfwriter", "Second buffer read failed");
4961         return false;
4962     }
4963 
4964     OStringBuffer aCMSHexBuffer;
4965     svl::crypto::Signing aSigning(m_aContext.SignCertificate);
4966     aSigning.AddDataRange(buffer1.get(), bytesRead1);
4967     aSigning.AddDataRange(buffer2.get(), bytesRead2);
4968     aSigning.SetSignTSA(m_aContext.SignTSA);
4969     aSigning.SetSignPassword(m_aContext.SignPassword);
4970     if (!aSigning.Sign(aCMSHexBuffer))
4971     {
4972         SAL_WARN("vcl.pdfwriter", "PDFWriter::Sign() failed");
4973         return false;
4974     }
4975 
4976     assert(aCMSHexBuffer.getLength() <= MAX_SIGNATURE_CONTENT_LENGTH);
4977 
4978     // Set file pointer to the m_nSignatureContentOffset, we're ready to overwrite PKCS7 object
4979     nWritten = 0;
4980     CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset)) );
4981     m_aFile.write(aCMSHexBuffer.getStr(), aCMSHexBuffer.getLength(), nWritten);
4982 
4983     return osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset);
4984 }
4985 
4986 #endif //HAVE_FEATURE_NSS
4987 
4988 sal_Int32 PDFWriterImpl::emitInfoDict( )
4989 {
4990     sal_Int32 nObject = createObject();
4991 
4992     if( updateObject( nObject ) )
4993     {
4994         OStringBuffer aLine( 1024 );
4995         aLine.append( nObject );
4996         aLine.append( " 0 obj\n"
4997                       "<<" );
4998         if( !m_aContext.DocumentInfo.Title.isEmpty() )
4999         {
5000             aLine.append( "/Title" );
5001             appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Title, nObject, aLine );
5002             aLine.append( "\n" );
5003         }
5004         if( !m_aContext.DocumentInfo.Author.isEmpty() )
5005         {
5006             aLine.append( "/Author" );
5007             appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Author, nObject, aLine );
5008             aLine.append( "\n" );
5009         }
5010         if( !m_aContext.DocumentInfo.Subject.isEmpty() )
5011         {
5012             aLine.append( "/Subject" );
5013             appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Subject, nObject, aLine );
5014             aLine.append( "\n" );
5015         }
5016         if( !m_aContext.DocumentInfo.Keywords.isEmpty() )
5017         {
5018             aLine.append( "/Keywords" );
5019             appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Keywords, nObject, aLine );
5020             aLine.append( "\n" );
5021         }
5022         if( !m_aContext.DocumentInfo.Creator.isEmpty() )
5023         {
5024             aLine.append( "/Creator" );
5025             appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Creator, nObject, aLine );
5026             aLine.append( "\n" );
5027         }
5028         if( !m_aContext.DocumentInfo.Producer.isEmpty() )
5029         {
5030             aLine.append( "/Producer" );
5031             appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Producer, nObject, aLine );
5032             aLine.append( "\n" );
5033         }
5034 
5035         aLine.append( "/CreationDate" );
5036         appendLiteralStringEncrypt( m_aCreationDateString, nObject, aLine );
5037         aLine.append( ">>\nendobj\n\n" );
5038         if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
5039             nObject = 0;
5040     }
5041     else
5042         nObject = 0;
5043 
5044     return nObject;
5045 }
5046 
5047 // Part of this function may be shared with method appendDest.
5048 sal_Int32 PDFWriterImpl::emitNamedDestinations()
5049 {
5050     sal_Int32  nCount = m_aNamedDests.size();
5051     if( nCount <= 0 )
5052         return 0;//define internal error
5053 
5054     //get the object number for all the destinations
5055     sal_Int32 nObject = createObject();
5056 
5057     if( updateObject( nObject ) )
5058     {
5059         //emit the dictionary
5060         OStringBuffer aLine( 1024 );
5061         aLine.append( nObject );
5062         aLine.append( " 0 obj\n"
5063                       "<<" );
5064 
5065         sal_Int32  nDestID;
5066         for( nDestID = 0; nDestID < nCount; nDestID++ )
5067         {
5068             const PDFNamedDest& rDest   = m_aNamedDests[ nDestID ];
5069             // In order to correctly function both under an Internet browser and
5070             // directly with a reader (provided the reader has the feature) we
5071             // need to set the name of the destination the same way it will be encoded
5072             // in an Internet link
5073             INetURLObject aLocalURL( "http://ahost.ax" ); //dummy location, won't be used
5074             aLocalURL.SetMark( rDest.m_aDestName );
5075 
5076             const OUString aName   = aLocalURL.GetMark( INetURLObject::DecodeMechanism::NONE ); //same coding as
5077             // in link creation ( see PDFWriterImpl::emitLinkAnnotations )
5078             const PDFPage& rDestPage    = m_aPages[ rDest.m_nPage ];
5079 
5080             aLine.append( '/' );
5081             appendDestinationName( aName, aLine ); // this conversion must be done when forming the link to target ( see in emitCatalog )
5082             aLine.append( '[' ); // the '[' can be emitted immediately, because the appendDestinationName function
5083                                  //maps the preceding character properly
5084             aLine.append( rDestPage.m_nPageObject );
5085             aLine.append( " 0 R" );
5086 
5087             switch( rDest.m_eType )
5088             {
5089             case PDFWriter::DestAreaType::XYZ:
5090             default:
5091                 aLine.append( "/XYZ " );
5092                 appendFixedInt( rDest.m_aRect.Left(), aLine );
5093                 aLine.append( ' ' );
5094                 appendFixedInt( rDest.m_aRect.Bottom(), aLine );
5095                 aLine.append( " 0" );
5096                 break;
5097             case PDFWriter::DestAreaType::FitRectangle:
5098                 aLine.append( "/FitR " );
5099                 appendFixedInt( rDest.m_aRect.Left(), aLine );
5100                 aLine.append( ' ' );
5101                 appendFixedInt( rDest.m_aRect.Top(), aLine );
5102                 aLine.append( ' ' );
5103                 appendFixedInt( rDest.m_aRect.Right(), aLine );
5104                 aLine.append( ' ' );
5105                 appendFixedInt( rDest.m_aRect.Bottom(), aLine );
5106                 break;
5107             }
5108             aLine.append( "]\n" );
5109         }
5110 
5111         //close
5112         aLine.append( ">>\nendobj\n\n" );
5113         if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
5114             nObject = 0;
5115     }
5116     else
5117         nObject = 0;
5118 
5119     return nObject;
5120 }
5121 
5122 // emits the output intent dictionary
5123 sal_Int32 PDFWriterImpl::emitOutputIntent()
5124 {
5125     if( !m_bIsPDF_A1 && !m_bIsPDF_A2 && !m_bIsPDF_A3 )
5126         return 0;
5127 
5128     //emit the sRGB standard profile, in ICC format, in a stream, per IEC61966-2.1
5129 
5130     OStringBuffer aLine( 1024 );
5131     sal_Int32 nICCObject = createObject();
5132     sal_Int32 nStreamLengthObject = createObject();
5133 
5134     aLine.append( nICCObject );
5135 // sRGB has 3 colors, hence /N 3 below (PDF 1.4 table 4.16)
5136     aLine.append( " 0 obj\n<</N 3/Length " );
5137     aLine.append( nStreamLengthObject );
5138     aLine.append( " 0 R" );
5139     if (!g_bDebugDisableCompression)
5140         aLine.append( "/Filter/FlateDecode" );
5141     aLine.append( ">>\nstream\n" );
5142     if ( !updateObject( nICCObject ) ) return 0;
5143     if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0;
5144     //get file position
5145     sal_uInt64 nBeginStreamPos = 0;
5146     if (osl::File::E_None != m_aFile.getPos(nBeginStreamPos))
5147         return 0;
5148     beginCompression();
5149     checkAndEnableStreamEncryption( nICCObject );
5150     cmsHPROFILE hProfile = cmsCreate_sRGBProfile();
5151     //force ICC profile version 2.1
5152     cmsSetProfileVersion(hProfile, 2.1);
5153     cmsUInt32Number nBytesNeeded = 0;
5154     cmsSaveProfileToMem(hProfile, nullptr, &nBytesNeeded);
5155     if (!nBytesNeeded)
5156       return 0;
5157     std::vector<unsigned char> aBuffer(nBytesNeeded);
5158     cmsSaveProfileToMem(hProfile, aBuffer.data(), &nBytesNeeded);
5159     cmsCloseProfile(hProfile);
5160     bool written = writeBuffer( aBuffer.data(), static_cast<sal_Int32>(aBuffer.size()) );
5161     disableStreamEncryption();
5162     endCompression();
5163 
5164     sal_uInt64 nEndStreamPos = 0;
5165     if (m_aFile.getPos(nEndStreamPos) != osl::File::E_None)
5166         return 0;
5167 
5168     if( !written )
5169         return 0;
5170     if( ! writeBuffer( "\nendstream\nendobj\n\n", 19 ) )
5171         return 0 ;
5172     aLine.setLength( 0 );
5173 
5174     //emit the stream length   object
5175     if ( !updateObject( nStreamLengthObject ) ) return 0;
5176     aLine.setLength( 0 );
5177     aLine.append( nStreamLengthObject );
5178     aLine.append( " 0 obj\n" );
5179     aLine.append( static_cast<sal_Int64>(nEndStreamPos-nBeginStreamPos) );
5180     aLine.append( "\nendobj\n\n" );
5181     if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0;
5182     aLine.setLength( 0 );
5183 
5184     //emit the OutputIntent dictionary
5185     sal_Int32 nOIObject = createObject();
5186     if ( !updateObject( nOIObject ) ) return 0;
5187     aLine.append( nOIObject );
5188     aLine.append( " 0 obj\n"
5189                   "<</Type/OutputIntent/S/GTS_PDFA1/OutputConditionIdentifier");
5190 
5191     appendLiteralStringEncrypt( OUStringLiteral("sRGB IEC61966-2.1") ,nOIObject, aLine );
5192     aLine.append("/DestOutputProfile ");
5193     aLine.append( nICCObject );
5194     aLine.append( " 0 R>>\nendobj\n\n" );
5195     if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0;
5196 
5197     return nOIObject;
5198 }
5199 
5200 // formats the string for the XML stream
5201 static void escapeStringXML( const OUString& rStr, OUString &rValue)
5202 {
5203     const sal_Unicode* pUni = rStr.getStr();
5204     int nLen = rStr.getLength();
5205     for( ; nLen; nLen--, pUni++ )
5206     {
5207         switch( *pUni )
5208         {
5209         case u'&':
5210             rValue += "&amp;";
5211         break;
5212         case u'<':
5213             rValue += "&lt;";
5214         break;
5215         case u'>':
5216             rValue += "&gt;";
5217         break;
5218         case u'\'':
5219             rValue += "&apos;";
5220         break;
5221         case u'"':
5222             rValue += "&quot;";
5223         break;
5224         default:
5225             rValue += OUStringChar( *pUni );
5226             break;
5227         }
5228     }
5229 }
5230 
5231 // emits the document metadata
5232 sal_Int32 PDFWriterImpl::emitDocumentMetadata()
5233 {
5234     if( !m_bIsPDF_A1 && !m_bIsPDF_A2 && !m_bIsPDF_A3 )
5235         return 0;
5236 
5237     //get the object number for all the destinations
5238     sal_Int32 nObject = createObject();
5239 
5240     if( updateObject( nObject ) )
5241     {
5242         pdf::XmpMetadata aMetadata;
5243 
5244         if (m_bIsPDF_A1)
5245             aMetadata.mnPDF_A = 1;
5246         else if (m_bIsPDF_A2)
5247             aMetadata.mnPDF_A = 2;
5248         else if (m_bIsPDF_A3)
5249             aMetadata.mnPDF_A = 3;
5250 
5251         aMetadata.mbPDF_UA = m_bIsPDF_UA;
5252 
5253         if (!m_aContext.DocumentInfo.Title.isEmpty())
5254         {
5255             OUString aTempString;
5256             escapeStringXML(m_aContext.DocumentInfo.Title, aTempString);
5257             aMetadata.msTitle = OUStringToOString(aTempString, RTL_TEXTENCODING_UTF8);
5258         }
5259         if (!m_aContext.DocumentInfo.Author.isEmpty())
5260         {
5261             OUString aTempString;
5262             escapeStringXML(m_aContext.DocumentInfo.Author, aTempString);
5263             aMetadata.msAuthor = OUStringToOString(aTempString, RTL_TEXTENCODING_UTF8);
5264         }
5265         if (!m_aContext.DocumentInfo.Subject.isEmpty())
5266         {
5267             OUString aTempString;
5268             escapeStringXML(m_aContext.DocumentInfo.Subject, aTempString);
5269             aMetadata.msSubject = OUStringToOString(aTempString, RTL_TEXTENCODING_UTF8);
5270         }
5271         if (!m_aContext.DocumentInfo.Producer.isEmpty())
5272         {
5273             OUString aTempString;
5274             escapeStringXML(m_aContext.DocumentInfo.Producer, aTempString);
5275             aMetadata.msProducer = OUStringToOString(aTempString, RTL_TEXTENCODING_UTF8);
5276         }
5277         if (!m_aContext.DocumentInfo.Keywords.isEmpty())
5278         {
5279             OUString aTempString;
5280             escapeStringXML(m_aContext.DocumentInfo.Keywords, aTempString);
5281             aMetadata.msKeywords = OUStringToOString(aTempString, RTL_TEXTENCODING_UTF8);
5282         }
5283 
5284         OStringBuffer aMetadataObj( 1024 );
5285 
5286         aMetadataObj.append( nObject );
5287         aMetadataObj.append( " 0 obj\n" );
5288 
5289         aMetadataObj.append( "<</Type/Metadata/Subtype/XML/Length " );
5290 
5291         aMetadataObj.append( sal_Int32(aMetadata.getSize()) );
5292         aMetadataObj.append( ">>\nstream\n" );
5293         if ( !writeBuffer( aMetadataObj.getStr(), aMetadataObj.getLength() ) )
5294             return 0;
5295         //emit the stream
5296         if ( !writeBuffer( aMetadata.getData(), aMetadata.getSize() ) )
5297             return 0;
5298 
5299         aMetadataObj.setLength( 0 );
5300         aMetadataObj.append( "\nendstream\nendobj\n\n" );
5301         if( ! writeBuffer( aMetadataObj.getStr(), aMetadataObj.getLength() ) )
5302             nObject = 0;
5303     }
5304     else
5305         nObject = 0;
5306 
5307     return nObject;
5308 }
5309 
5310 bool PDFWriterImpl::emitTrailer()
5311 {
5312     // emit doc info
5313     sal_Int32 nDocInfoObject = emitInfoDict( );
5314 
5315     sal_Int32 nSecObject = 0;
5316 
5317     if( m_aContext.Encryption.Encrypt() )
5318     {
5319         //emit the security information
5320         //must be emitted as indirect dictionary object, since
5321         //Acrobat Reader 5 works only with this kind of implementation
5322         nSecObject = createObject();
5323 
5324         if( updateObject( nSecObject ) )
5325         {
5326             OStringBuffer aLineS( 1024 );
5327             aLineS.append( nSecObject );
5328             aLineS.append( " 0 obj\n"
5329                            "<</Filter/Standard/V " );
5330             // check the version
5331             aLineS.append( "2/Length 128/R 3" );
5332 
5333             // emit the owner password, must not be encrypted
5334             aLineS.append( "/O(" );
5335             appendLiteralString( reinterpret_cast<char*>(m_aContext.Encryption.OValue.data()), sal_Int32(m_aContext.Encryption.OValue.size()), aLineS );
5336             aLineS.append( ")/U(" );
5337             appendLiteralString( reinterpret_cast<char*>(m_aContext.Encryption.UValue.data()), sal_Int32(m_aContext.Encryption.UValue.size()), aLineS );
5338             aLineS.append( ")/P " );// the permission set
5339             aLineS.append( m_nAccessPermissions );
5340             aLineS.append( ">>\nendobj\n\n" );
5341             if( !writeBuffer( aLineS.getStr(), aLineS.getLength() ) )
5342                 nSecObject = 0;
5343         }
5344         else
5345             nSecObject = 0;
5346     }
5347     // emit xref table
5348     // remember start
5349     sal_uInt64 nXRefOffset = 0;
5350     CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nXRefOffset )) );
5351     CHECK_RETURN( writeBuffer( "xref\n", 5 ) );
5352 
5353     sal_Int32 nObjects = m_aObjects.size();
5354     OStringBuffer aLine;
5355     aLine.append( "0 " );
5356     aLine.append( static_cast<sal_Int32>(nObjects+1) );
5357     aLine.append( "\n" );
5358     aLine.append( "0000000000 65535 f \n" );
5359     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
5360 
5361     for( sal_Int32 i = 0; i < nObjects; i++ )
5362     {
5363         aLine.setLength( 0 );
5364         OString aOffset = OString::number( m_aObjects[i] );
5365         for( sal_Int32 j = 0; j < (10-aOffset.getLength()); j++ )
5366             aLine.append( '0' );
5367         aLine.append( aOffset );
5368         aLine.append( " 00000 n \n" );
5369         SAL_WARN_IF( aLine.getLength() != 20, "vcl.pdfwriter", "invalid xref entry" );
5370         CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
5371     }
5372 
5373     // prepare document checksum
5374     OStringBuffer aDocChecksum( 2*RTL_DIGEST_LENGTH_MD5+1 );
5375     ::std::vector<unsigned char> const nMD5Sum(m_DocDigest.finalize());
5376     for (sal_uInt8 i : nMD5Sum)
5377         appendHex( i, aDocChecksum );
5378     // document id set in setDocInfo method
5379     // emit trailer
5380     aLine.setLength( 0 );
5381     aLine.append( "trailer\n"
5382                   "<</Size " );
5383     aLine.append( static_cast<sal_Int32>(nObjects+1) );
5384     aLine.append( "/Root " );
5385     aLine.append( m_nCatalogObject );
5386     aLine.append( " 0 R\n" );
5387     if( nSecObject )
5388     {
5389         aLine.append( "/Encrypt ");
5390         aLine.append( nSecObject );
5391         aLine.append( " 0 R\n" );
5392     }
5393     if( nDocInfoObject )
5394     {
5395         aLine.append( "/Info " );
5396         aLine.append( nDocInfoObject );
5397         aLine.append( " 0 R\n" );
5398     }
5399     if( ! m_aContext.Encryption.DocumentIdentifier.empty() )
5400     {
5401         aLine.append( "/ID [ <" );
5402         for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
5403         {
5404             appendHex( sal_Int8(item), aLine );
5405         }
5406         aLine.append( ">\n"
5407                       "<" );
5408         for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
5409         {
5410             appendHex( sal_Int8(item), aLine );
5411         }
5412         aLine.append( "> ]\n" );
5413     }
5414     if( !aDocChecksum.isEmpty() )
5415     {
5416         aLine.append( "/DocChecksum /" );
5417         aLine.append( aDocChecksum.makeStringAndClear() );
5418         aLine.append( "\n" );
5419     }
5420     if( !m_aAdditionalStreams.empty() )
5421     {
5422         aLine.append( "/AdditionalStreams [" );
5423         for(const PDFAddStream & rAdditionalStream : m_aAdditionalStreams)
5424         {
5425             aLine.append( "/" );
5426             appendName( rAdditionalStream.m_aMimeType, aLine );
5427             aLine.append( " " );
5428             aLine.append( rAdditionalStream.m_nStreamObject );
5429             aLine.append( " 0 R\n" );
5430         }
5431         aLine.append( "]\n" );
5432     }
5433     aLine.append( ">>\n"
5434                   "startxref\n" );
5435     aLine.append( static_cast<sal_Int64>(nXRefOffset) );
5436     aLine.append( "\n"
5437                   "%%EOF\n" );
5438     return writeBuffer( aLine.getStr(), aLine.getLength() );
5439 }
5440 
5441 namespace {
5442 
5443 struct AnnotationSortEntry
5444 {
5445     sal_Int32 nTabOrder;
5446     sal_Int32 nObject;
5447     sal_Int32 nWidgetIndex;
5448 
5449     AnnotationSortEntry( sal_Int32 nTab, sal_Int32 nObj, sal_Int32 nI ) :
5450         nTabOrder( nTab ),
5451         nObject( nObj ),
5452         nWidgetIndex( nI )
5453     {}
5454 };
5455 
5456 struct AnnotSortContainer
5457 {
5458     std::set< sal_Int32 >               aObjects;
5459     std::vector< AnnotationSortEntry >    aSortedAnnots;
5460 };
5461 
5462 struct AnnotSorterLess
5463 {
5464     std::vector<PDFWidget>& m_rWidgets;
5465 
5466     explicit AnnotSorterLess( std::vector<PDFWidget>& rWidgets ) : m_rWidgets( rWidgets ) {}
5467 
5468     bool operator()( const AnnotationSortEntry& rLeft, const AnnotationSortEntry& rRight )
5469     {
5470         if( rLeft.nTabOrder < rRight.nTabOrder )
5471             return true;
5472         if( rRight.nTabOrder < rLeft.nTabOrder )
5473             return false;
5474         if( rLeft.nWidgetIndex < 0 && rRight.nWidgetIndex < 0 )
5475             return false;
5476         if( rRight.nWidgetIndex < 0 )
5477             return true;
5478         if( rLeft.nWidgetIndex < 0 )
5479             return false;
5480         // remember: widget rects are in PDF coordinates, so they are ordered down up
5481         if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() >
5482             m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() )
5483             return true;
5484         if( m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() >
5485             m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() )
5486             return false;
5487         if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Left() <
5488             m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Left() )
5489             return true;
5490         return false;
5491     }
5492 };
5493 
5494 }
5495 
5496 void PDFWriterImpl::sortWidgets()
5497 {
5498     // sort widget annotations on each page as per their
5499     // TabOrder attribute
5500     std::unordered_map< sal_Int32, AnnotSortContainer > sorted;
5501     int nWidgets = m_aWidgets.size();
5502     for( int nW = 0; nW < nWidgets; nW++ )
5503     {
5504         const PDFWidget& rWidget = m_aWidgets[nW];
5505         if( rWidget.m_nPage >= 0 )
5506         {
5507             AnnotSortContainer& rCont = sorted[ rWidget.m_nPage ];
5508             // optimize vector allocation
5509             if( rCont.aSortedAnnots.empty() )
5510                 rCont.aSortedAnnots.reserve( m_aPages[ rWidget.m_nPage ].m_aAnnotations.size() );
5511             // insert widget to tab sorter
5512             // RadioButtons are not page annotations, only their individual check boxes are
5513             if( rWidget.m_eType != PDFWriter::RadioButton )
5514             {
5515                 rCont.aObjects.insert( rWidget.m_nObject );
5516                 rCont.aSortedAnnots.emplace_back( rWidget.m_nTabOrder, rWidget.m_nObject, nW );
5517             }
5518         }
5519     }
5520     for (auto & item : sorted)
5521     {
5522         // append entries for non widget annotations
5523         PDFPage& rPage = m_aPages[ item.first ];
5524         unsigned int nAnnots = rPage.m_aAnnotations.size();
5525         for( unsigned int nA = 0; nA < nAnnots; nA++ )
5526             if( item.second.aObjects.find( rPage.m_aAnnotations[nA] ) == item.second.aObjects.end())
5527                 item.second.aSortedAnnots.emplace_back( 10000, rPage.m_aAnnotations[nA], -1 );
5528 
5529         AnnotSorterLess aLess( m_aWidgets );
5530         std::stable_sort( item.second.aSortedAnnots.begin(), item.second.aSortedAnnots.end(), aLess );
5531         // sanity check
5532         if( item.second.aSortedAnnots.size() == nAnnots)
5533         {
5534             for( unsigned int nA = 0; nA < nAnnots; nA++ )
5535                 rPage.m_aAnnotations[nA] = item.second.aSortedAnnots[nA].nObject;
5536         }
5537         else
5538         {
5539             SAL_WARN( "vcl.pdfwriter", "wrong number of sorted annotations" );
5540             SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::sortWidgets(): wrong number of sorted assertions "
5541                      "on page nr " << item.first << ", " <<
5542                      static_cast<long int>(item.second.aSortedAnnots.size()) << " sorted and " <<
5543                      static_cast<long int>(nAnnots) << " unsorted");
5544         }
5545     }
5546 
5547     // FIXME: implement tab order in structure tree for PDF 1.5
5548 }
5549 
5550 class PDFStreamIf :
5551         public cppu::WeakImplHelper< css::io::XOutputStream >
5552 {
5553     VclPtr<PDFWriterImpl>  m_pWriter;
5554     bool            m_bWrite;
5555     public:
5556     explicit PDFStreamIf( PDFWriterImpl* pWriter ) : m_pWriter( pWriter ), m_bWrite( true ) {}
5557 
5558     virtual void SAL_CALL writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override;
5559     virtual void SAL_CALL flush() override;
5560     virtual void SAL_CALL closeOutput() override;
5561 };
5562 
5563 void SAL_CALL  PDFStreamIf::writeBytes( const css::uno::Sequence< sal_Int8 >& aData )
5564 {
5565     if( m_bWrite && aData.hasElements() )
5566     {
5567         sal_Int32 nBytes = aData.getLength();
5568         m_pWriter->writeBuffer( aData.getConstArray(), nBytes );
5569     }
5570 }
5571 
5572 void SAL_CALL PDFStreamIf::flush()
5573 {
5574 }
5575 
5576 void SAL_CALL PDFStreamIf::closeOutput()
5577 {
5578     m_bWrite = false;
5579 }
5580 
5581 bool PDFWriterImpl::emitAdditionalStreams()
5582 {
5583     unsigned int nStreams = m_aAdditionalStreams.size();
5584     for( unsigned int i = 0; i < nStreams; i++ )
5585     {
5586         PDFAddStream& rStream = m_aAdditionalStreams[i];
5587         rStream.m_nStreamObject = createObject();
5588         sal_Int32 nSizeObject = createObject();
5589 
5590         if( ! updateObject( rStream.m_nStreamObject ) )
5591             return false;
5592 
5593         OStringBuffer aLine;
5594         aLine.append( rStream.m_nStreamObject );
5595         aLine.append( " 0 obj\n<</Length " );
5596         aLine.append( nSizeObject );
5597         aLine.append( " 0 R" );
5598         if( rStream.m_bCompress )
5599             aLine.append( "/Filter/FlateDecode" );
5600         aLine.append( ">>\nstream\n" );
5601         if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
5602             return false;
5603         sal_uInt64 nBeginStreamPos = 0, nEndStreamPos = 0;
5604         if( osl::File::E_None != m_aFile.getPos(nBeginStreamPos) )
5605         {
5606             m_aFile.close();
5607             m_bOpen = false;
5608         }
5609         if( rStream.m_bCompress )
5610             beginCompression();
5611 
5612         checkAndEnableStreamEncryption( rStream.m_nStreamObject );
5613         css::uno::Reference< css::io::XOutputStream > xStream( new PDFStreamIf( this ) );
5614         assert(rStream.m_pStream);
5615         if (!rStream.m_pStream)
5616             return false;
5617         rStream.m_pStream->write( xStream );
5618         xStream.clear();
5619         delete rStream.m_pStream;
5620         rStream.m_pStream = nullptr;
5621         disableStreamEncryption();
5622 
5623         if( rStream.m_bCompress )
5624             endCompression();
5625 
5626         if (osl::File::E_None != m_aFile.getPos(nEndStreamPos))
5627         {
5628             m_aFile.close();
5629             m_bOpen = false;
5630             return false;
5631         }
5632         if( ! writeBuffer( "\nendstream\nendobj\n\n", 19 ) )
5633             return false ;
5634         // emit stream length object
5635         if( ! updateObject( nSizeObject ) )
5636             return false;
5637         aLine.setLength( 0 );
5638         aLine.append( nSizeObject );
5639         aLine.append( " 0 obj\n" );
5640         aLine.append( static_cast<sal_Int64>(nEndStreamPos-nBeginStreamPos) );
5641         aLine.append( "\nendobj\n\n" );
5642         if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
5643             return false;
5644     }
5645     return true;
5646 }
5647 
5648 bool PDFWriterImpl::emit()
5649 {
5650     endPage();
5651 
5652     // resort structure tree and annotations if necessary
5653     // needed for widget tab order
5654     sortWidgets();
5655 
5656 #if HAVE_FEATURE_NSS
5657     if( m_aContext.SignPDF )
5658     {
5659         // sign the document
5660         PDFWriter::SignatureWidget aSignature;
5661         aSignature.Name = "Signature1";
5662         createControl( aSignature, 0 );
5663     }
5664 #endif
5665 
5666     // emit additional streams
5667     CHECK_RETURN( emitAdditionalStreams() );
5668 
5669     // emit catalog
5670     CHECK_RETURN( emitCatalog() );
5671 
5672 #if HAVE_FEATURE_NSS
5673     if (m_nSignatureObject != -1) // if document is signed, emit sigdict
5674     {
5675         if( !emitSignature() )
5676         {
5677             m_aErrors.insert( PDFWriter::Error_Signature_Failed );
5678             return false;
5679         }
5680     }
5681 #endif
5682 
5683     // emit trailer
5684     CHECK_RETURN( emitTrailer() );
5685 
5686 #if HAVE_FEATURE_NSS
5687     if (m_nSignatureObject != -1) // finalize the signature
5688     {
5689         if( !finalizeSignature() )
5690         {
5691             m_aErrors.insert( PDFWriter::Error_Signature_Failed );
5692             return false;
5693         }
5694     }
5695 #endif
5696 
5697     m_aFile.close();
5698     m_bOpen = false;
5699 
5700     return true;
5701 }
5702 
5703 
5704 sal_Int32 PDFWriterImpl::getSystemFont( const vcl::Font& i_rFont )
5705 {
5706     Push();
5707 
5708     SetFont( i_rFont );
5709 
5710     const PhysicalFontFace* pDevFont = GetFontInstance()->GetFontFace();
5711     sal_Int32 nFontID = 0;
5712     auto it = m_aSystemFonts.find( pDevFont );
5713     if( it != m_aSystemFonts.end() )
5714         nFontID = it->second.m_nNormalFontID;
5715     else
5716     {
5717         nFontID = m_nNextFID++;
5718         m_aSystemFonts[ pDevFont ] = EmbedFont();
5719         m_aSystemFonts[ pDevFont ].m_nNormalFontID = nFontID;
5720     }
5721 
5722     Pop();
5723     return nFontID;
5724 }
5725 
5726 void PDFWriterImpl::registerGlyph(const GlyphItem* pGlyph,
5727                                   const PhysicalFontFace* pFont,
5728                                   const std::vector<sal_Ucs>& rCodeUnits,
5729                                   sal_uInt8& nMappedGlyph,
5730                                   sal_Int32& nMappedFontObject)
5731 {
5732     const int nFontGlyphId = pGlyph->glyphId();
5733     FontSubset& rSubset = m_aSubsets[ pFont ];
5734     // search for font specific glyphID
5735     auto it = rSubset.m_aMapping.find( nFontGlyphId );
5736     if( it != rSubset.m_aMapping.end() )
5737     {
5738         nMappedFontObject = it->second.m_nFontID;
5739         nMappedGlyph = it->second.m_nSubsetGlyphID;
5740     }
5741     else
5742     {
5743         // create new subset if necessary
5744         if( rSubset.m_aSubsets.empty()
5745         || (rSubset.m_aSubsets.back().m_aMapping.size() > 254) )
5746         {
5747             rSubset.m_aSubsets.emplace_back( m_nNextFID++ );
5748         }
5749 
5750         // copy font id
5751         nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID;
5752         // create new glyph in subset
5753         sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(rSubset.m_aSubsets.back().m_aMapping.size()+1);
5754         nMappedGlyph = nNewId;
5755 
5756         // add new glyph to emitted font subset
5757         GlyphEmit& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[ nFontGlyphId ];
5758         rNewGlyphEmit.setGlyphId( nNewId );
5759         for (const auto nCode : rCodeUnits)
5760             rNewGlyphEmit.addCode(nCode);
5761 
5762         // add new glyph to font mapping
5763         Glyph& rNewGlyph = rSubset.m_aMapping[ nFontGlyphId ];
5764         rNewGlyph.m_nFontID = nMappedFontObject;
5765         rNewGlyph.m_nSubsetGlyphID = nNewId;
5766     }
5767 }
5768 
5769 void PDFWriterImpl::drawRelief( SalLayout& rLayout, const OUString& rText, bool bTextLines )
5770 {
5771     push( PushFlags::ALL );
5772 
5773     FontRelief eRelief = m_aCurrentPDFState.m_aFont.GetRelief();
5774 
5775     Color aTextColor = m_aCurrentPDFState.m_aFont.GetColor();
5776     Color aTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
5777     Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
5778     Color aReliefColor( COL_LIGHTGRAY );
5779     if( aTextColor == COL_BLACK )
5780         aTextColor = COL_WHITE;
5781     if( aTextLineColor == COL_BLACK )
5782         aTextLineColor = COL_WHITE;
5783     if( aOverlineColor == COL_BLACK )
5784         aOverlineColor = COL_WHITE;
5785     // coverity[copy_paste_error : FALSE] - aReliefColor depending on aTextColor is correct
5786     if( aTextColor == COL_WHITE )
5787         aReliefColor = COL_BLACK;
5788 
5789     Font aSetFont = m_aCurrentPDFState.m_aFont;
5790     aSetFont.SetRelief( FontRelief::NONE );
5791     aSetFont.SetShadow( false );
5792 
5793     aSetFont.SetColor( aReliefColor );
5794     setTextLineColor( aReliefColor );
5795     setOverlineColor( aReliefColor );
5796     setFont( aSetFont );
5797     long nOff = 1 + GetDPIX()/300;
5798     if( eRelief == FontRelief::Engraved )
5799         nOff = -nOff;
5800 
5801     rLayout.DrawOffset() += Point( nOff, nOff );
5802     updateGraphicsState();
5803     drawLayout( rLayout, rText, bTextLines );
5804 
5805     rLayout.DrawOffset() -= Point( nOff, nOff );
5806     setTextLineColor( aTextLineColor );
5807     setOverlineColor( aOverlineColor );
5808     aSetFont.SetColor( aTextColor );
5809     setFont( aSetFont );
5810     updateGraphicsState();
5811     drawLayout( rLayout, rText, bTextLines );
5812 
5813     // clean up the mess
5814     pop();
5815 }
5816 
5817 void PDFWriterImpl::drawShadow( SalLayout& rLayout, const OUString& rText, bool bTextLines )
5818 {
5819     Font aSaveFont = m_aCurrentPDFState.m_aFont;
5820     Color aSaveTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
5821     Color aSaveOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
5822 
5823     Font& rFont = m_aCurrentPDFState.m_aFont;
5824     if( rFont.GetColor() == COL_BLACK || rFont.GetColor().GetLuminance() < 8 )
5825         rFont.SetColor( COL_LIGHTGRAY );
5826     else
5827         rFont.SetColor( COL_BLACK );
5828     rFont.SetShadow( false );
5829     rFont.SetOutline( false );
5830     setFont( rFont );
5831     setTextLineColor( rFont.GetColor() );
5832     setOverlineColor( rFont.GetColor() );
5833     updateGraphicsState();
5834 
5835     long nOff = 1 + ((GetFontInstance()->mnLineHeight-24)/24);
5836     if( rFont.IsOutline() )
5837         nOff++;
5838     rLayout.DrawBase() += Point( nOff, nOff );
5839     drawLayout( rLayout, rText, bTextLines );
5840     rLayout.DrawBase() -= Point( nOff, nOff );
5841 
5842     setFont( aSaveFont );
5843     setTextLineColor( aSaveTextLineColor );
5844     setOverlineColor( aSaveOverlineColor );
5845     updateGraphicsState();
5846 }
5847 
5848 void PDFWriterImpl::drawVerticalGlyphs(
5849         const std::vector<PDFGlyph>& rGlyphs,
5850         OStringBuffer& rLine,
5851         const Point& rAlignOffset,
5852         const Matrix3& rRotScale,
5853         double fAngle,
5854         double fXScale,
5855         double fSkew,
5856         sal_Int32 nFontHeight )
5857 {
5858     long nXOffset = 0;
5859     Point aCurPos( rGlyphs[0].m_aPos );
5860     aCurPos = PixelToLogic( aCurPos );
5861     aCurPos += rAlignOffset;
5862     for( size_t i = 0; i < rGlyphs.size(); i++ )
5863     {
5864         // have to emit each glyph on its own
5865         double fDeltaAngle = 0.0;
5866         double fYScale = 1.0;
5867         double fTempXScale = fXScale;
5868         double fSkewB = fSkew;
5869         double fSkewA = 0.0;
5870 
5871         Point aDeltaPos;
5872         if (rGlyphs[i].m_pGlyph->IsVertical())
5873         {
5874             fDeltaAngle = M_PI/2.0;
5875             aDeltaPos.setX( GetFontMetric().GetAscent() );
5876             aDeltaPos.setY( static_cast<int>(static_cast<double>(GetFontMetric().GetDescent()) * fXScale) );
5877             fYScale = fXScale;
5878             fTempXScale = 1.0;
5879             fSkewA = -fSkewB;
5880             fSkewB = 0.0;
5881         }
5882         aDeltaPos += PixelToLogic( Point( static_cast<int>(static_cast<double>(nXOffset)/fXScale), 0 ) ) - PixelToLogic( Point() );
5883         if( i < rGlyphs.size()-1 )
5884         // #i120627# the text on the Y axis is reversed when export ppt file to PDF format
5885         {
5886             long nOffsetX = rGlyphs[i+1].m_aPos.X() - rGlyphs[i].m_aPos.X();
5887             long nOffsetY = rGlyphs[i+1].m_aPos.Y() - rGlyphs[i].m_aPos.Y();
5888             nXOffset += static_cast<int>(sqrt(double(nOffsetX*nOffsetX + nOffsetY*nOffsetY)));
5889         }
5890         if (!rGlyphs[i].m_pGlyph->glyphId())
5891             continue;
5892 
5893         aDeltaPos = rRotScale.transform( aDeltaPos );
5894 
5895         Matrix3 aMat;
5896         if( fSkewB != 0.0 || fSkewA != 0.0 )
5897             aMat.skew( fSkewA, fSkewB );
5898         aMat.scale( fTempXScale, fYScale );
5899         aMat.rotate( fAngle+fDeltaAngle );
5900         aMat.translate( aCurPos.X()+aDeltaPos.X(), aCurPos.Y()+aDeltaPos.Y() );
5901         m_aPages.back().appendMatrix3(aMat, rLine);
5902         rLine.append( " Tm" );
5903         if( i == 0 || rGlyphs[i-1].m_nMappedFontId != rGlyphs[i].m_nMappedFontId )
5904         {
5905             rLine.append( " /F" );
5906             rLine.append( rGlyphs[i].m_nMappedFontId );
5907             rLine.append( ' ' );
5908             m_aPages.back().appendMappedLength( nFontHeight, rLine );
5909             rLine.append( " Tf" );
5910         }
5911         rLine.append( "<" );
5912         appendHex( rGlyphs[i].m_nMappedGlyphId, rLine );
5913         rLine.append( ">Tj\n" );
5914     }
5915 }
5916 
5917 void PDFWriterImpl::drawHorizontalGlyphs(
5918         const std::vector<PDFGlyph>& rGlyphs,
5919         OStringBuffer& rLine,
5920         const Point& rAlignOffset,
5921         bool bFirst,
5922         double fAngle,
5923         double fXScale,
5924         double fSkew,
5925         sal_Int32 nFontHeight,
5926         sal_Int32 nPixelFontHeight
5927         )
5928 {
5929     // horizontal (= normal) case
5930 
5931     // fill in  run end indices
5932     // end is marked by index of the first glyph of the next run
5933     // a run is marked by same mapped font id and same Y position
5934     std::vector< sal_uInt32 > aRunEnds;
5935     aRunEnds.reserve( rGlyphs.size() );
5936     for( size_t i = 1; i < rGlyphs.size(); i++ )
5937     {
5938         if( rGlyphs[i].m_nMappedFontId != rGlyphs[i-1].m_nMappedFontId ||
5939             rGlyphs[i].m_aPos.Y() != rGlyphs[i-1].m_aPos.Y() )
5940         {
5941             aRunEnds.push_back(i);
5942         }
5943     }
5944     // last run ends at last glyph
5945     aRunEnds.push_back( rGlyphs.size() );
5946 
5947     // loop over runs of the same font
5948     sal_uInt32 nBeginRun = 0;
5949     for( size_t nRun = 0; nRun < aRunEnds.size(); nRun++ )
5950     {
5951         // setup text matrix
5952         Point aCurPos = rGlyphs[nBeginRun].m_aPos;
5953         // back transformation to current coordinate system
5954         aCurPos = PixelToLogic( aCurPos );
5955         aCurPos += rAlignOffset;
5956         // the first run can be set with "Td" operator
5957         // subsequent use of that operator would move
5958         // the textline matrix relative to what was set before
5959         // making use of that would drive us into rounding issues
5960         Matrix3 aMat;
5961         if( bFirst && nRun == 0 && fAngle == 0.0 && fXScale == 1.0 && fSkew == 0.0 )
5962         {
5963             m_aPages.back().appendPoint( aCurPos, rLine );
5964             rLine.append( " Td " );
5965         }
5966         else
5967         {
5968             if( fSkew != 0.0 )
5969                 aMat.skew( 0.0, fSkew );
5970             aMat.scale( fXScale, 1.0 );
5971             aMat.rotate( fAngle );
5972             aMat.translate( aCurPos.X(), aCurPos.Y() );
5973             m_aPages.back().appendMatrix3(aMat, rLine);
5974             rLine.append( " Tm\n" );
5975         }
5976         // set up correct font
5977         rLine.append( "/F" );
5978         rLine.append( rGlyphs[nBeginRun].m_nMappedFontId );
5979         rLine.append( ' ' );
5980         m_aPages.back().appendMappedLength( nFontHeight, rLine );
5981         rLine.append( " Tf" );
5982 
5983         // output glyphs using Tj or TJ
5984         OStringBuffer aKernedLine( 256 ), aUnkernedLine( 256 );
5985         aKernedLine.append( "[<" );
5986         aUnkernedLine.append( '<' );
5987         appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aKernedLine );
5988         appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aUnkernedLine );
5989 
5990         aMat.invert();
5991         bool bNeedKern = false;
5992         for( sal_uInt32 nPos = nBeginRun+1; nPos < aRunEnds[nRun]; nPos++ )
5993         {
5994             appendHex( rGlyphs[nPos].m_nMappedGlyphId, aUnkernedLine );
5995             // check if default glyph positioning is sufficient
5996             const Point aThisPos = aMat.transform( rGlyphs[nPos].m_aPos );
5997             const Point aPrevPos = aMat.transform( rGlyphs[nPos-1].m_aPos );
5998             double fAdvance = aThisPos.X() - aPrevPos.X();
5999             fAdvance *= 1000.0 / nPixelFontHeight;
6000             const double fAdjustment = rGlyphs[nPos-1].m_nNativeWidth - fAdvance + 0.5;
6001             SAL_WARN_IF(
6002                 fAdjustment < SAL_MIN_INT32 || fAdjustment > SAL_MAX_INT32, "vcl.pdfwriter",
6003                 "adjustment " << fAdjustment << " outside 32-bit int");
6004             const sal_Int32 nAdjustment = static_cast<sal_Int32>(
6005                 std::clamp(fAdjustment, double(SAL_MIN_INT32), double(SAL_MAX_INT32)));
6006             if( nAdjustment != 0 )
6007             {
6008                 // apply individual glyph positioning
6009                 bNeedKern = true;
6010                 aKernedLine.append( ">" );
6011                 aKernedLine.append( nAdjustment );
6012                 aKernedLine.append( "<" );
6013             }
6014             appendHex( rGlyphs[nPos].m_nMappedGlyphId, aKernedLine );
6015         }
6016         aKernedLine.append( ">]TJ\n" );
6017         aUnkernedLine.append( ">Tj\n" );
6018         rLine.append(
6019             (bNeedKern ? aKernedLine : aUnkernedLine).makeStringAndClear() );
6020 
6021         // set beginning of next run
6022         nBeginRun = aRunEnds[nRun];
6023     }
6024 }
6025 
6026 void PDFWriterImpl::drawLayout( SalLayout& rLayout, const OUString& rText, bool bTextLines )
6027 {
6028     // relief takes precedence over shadow (see outdev3.cxx)
6029     if(  m_aCurrentPDFState.m_aFont.GetRelief() != FontRelief::NONE )
6030     {
6031         drawRelief( rLayout, rText, bTextLines );
6032         return;
6033     }
6034     else if( m_aCurrentPDFState.m_aFont.IsShadow() )
6035         drawShadow( rLayout, rText, bTextLines );
6036 
6037     OStringBuffer aLine( 512 );
6038 
6039     const int nMaxGlyphs = 256;
6040 
6041     std::vector<sal_Ucs> aCodeUnits;
6042     bool bVertical = m_aCurrentPDFState.m_aFont.IsVertical();
6043     int nIndex = 0;
6044     double fXScale = 1.0;
6045     double fSkew = 0.0;
6046     sal_Int32 nPixelFontHeight = GetFontInstance()->GetFontSelectPattern().mnHeight;
6047     TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
6048 
6049     // transform font height back to current units
6050     // note: the layout calculates in outdevs device pixel !!
6051     sal_Int32 nFontHeight = ImplDevicePixelToLogicHeight( nPixelFontHeight );
6052     if( m_aCurrentPDFState.m_aFont.GetAverageFontWidth() )
6053     {
6054         Font aFont( m_aCurrentPDFState.m_aFont );
6055         aFont.SetAverageFontWidth( 0 );
6056         FontMetric aMetric = GetFontMetric( aFont );
6057         if( aMetric.GetAverageFontWidth() != m_aCurrentPDFState.m_aFont.GetAverageFontWidth() )
6058         {
6059             fXScale =
6060                 static_cast<double>(m_aCurrentPDFState.m_aFont.GetAverageFontWidth()) /
6061                 static_cast<double>(aMetric.GetAverageFontWidth());
6062         }
6063     }
6064 
6065     // perform artificial italics if necessary
6066     if( ( m_aCurrentPDFState.m_aFont.GetItalic() == ITALIC_NORMAL ||
6067           m_aCurrentPDFState.m_aFont.GetItalic() == ITALIC_OBLIQUE ) &&
6068         ( GetFontInstance()->GetFontFace()->GetItalic() != ITALIC_NORMAL &&
6069            GetFontInstance()->GetFontFace()->GetItalic() != ITALIC_OBLIQUE )
6070         )
6071     {
6072         fSkew = M_PI/12.0;
6073     }
6074 
6075     // if the mapmode is distorted we need to adjust for that also
6076     if( m_aCurrentPDFState.m_aMapMode.GetScaleX() != m_aCurrentPDFState.m_aMapMode.GetScaleY() )
6077     {
6078         fXScale *= double(m_aCurrentPDFState.m_aMapMode.GetScaleX()) / double(m_aCurrentPDFState.m_aMapMode.GetScaleY());
6079     }
6080 
6081     int nAngle = m_aCurrentPDFState.m_aFont.GetOrientation();
6082     // normalize angles
6083     while( nAngle < 0 )
6084         nAngle += 3600;
6085     nAngle = nAngle % 3600;
6086     double fAngle = static_cast<double>(nAngle) * M_PI / 1800.0;
6087 
6088     Matrix3 aRotScale;
6089     aRotScale.scale( fXScale, 1.0 );
6090     if( fAngle != 0.0 )
6091         aRotScale.rotate( -fAngle );
6092 
6093     bool bPop = false;
6094     bool bABold = false;
6095     // artificial bold necessary ?
6096     if( GetFontInstance()->GetFontFace()->GetWeight() <= WEIGHT_MEDIUM &&
6097         GetFontInstance()->GetFontSelectPattern().GetWeight() > WEIGHT_MEDIUM )
6098     {
6099         aLine.append("q ");
6100         bPop = true;
6101         bABold = true;
6102     }
6103     // setup text colors (if necessary)
6104     Color aStrokeColor( COL_TRANSPARENT );
6105     Color aNonStrokeColor( COL_TRANSPARENT );
6106 
6107     if( m_aCurrentPDFState.m_aFont.IsOutline() )
6108     {
6109         aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6110         aNonStrokeColor = COL_WHITE;
6111     }
6112     else
6113         aNonStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6114     if( bABold )
6115         aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6116 
6117     if( aStrokeColor != COL_TRANSPARENT && aStrokeColor != m_aCurrentPDFState.m_aLineColor )
6118     {
6119         if( ! bPop )
6120             aLine.append( "q " );
6121         bPop = true;
6122         appendStrokingColor( aStrokeColor, aLine );
6123         aLine.append( "\n" );
6124     }
6125     if( aNonStrokeColor != COL_TRANSPARENT && aNonStrokeColor != m_aCurrentPDFState.m_aFillColor )
6126     {
6127         if( ! bPop )
6128             aLine.append( "q " );
6129         bPop = true;
6130         appendNonStrokingColor( aNonStrokeColor, aLine );
6131         aLine.append( "\n" );
6132     }
6133 
6134     // begin text object
6135     aLine.append( "BT\n" );
6136     // outline attribute ?
6137     if( m_aCurrentPDFState.m_aFont.IsOutline() || bABold )
6138     {
6139         // set correct text mode, set stroke width
6140         aLine.append( "2 Tr " ); // fill, then stroke
6141 
6142         if( m_aCurrentPDFState.m_aFont.IsOutline() )
6143         {
6144             // unclear what to do in case of outline and artificial bold
6145             // for the time being outline wins
6146             aLine.append( "0.25 w \n" );
6147         }
6148         else
6149         {
6150             double fW = static_cast<double>(m_aCurrentPDFState.m_aFont.GetFontHeight()) / 30.0;
6151             m_aPages.back().appendMappedLength( fW, aLine );
6152             aLine.append ( " w\n" );
6153         }
6154     }
6155 
6156     FontMetric aRefDevFontMetric = GetFontMetric();
6157     const PhysicalFontFace* pDevFont = GetFontInstance()->GetFontFace();
6158     const GlyphItem* pGlyph = nullptr;
6159     const PhysicalFontFace* pFallbackFont = nullptr;
6160 
6161     // collect the glyphs into a single array
6162     std::vector< PDFGlyph > aGlyphs;
6163     aGlyphs.reserve( nMaxGlyphs );
6164     // first get all the glyphs and register them; coordinates still in Pixel
6165     Point aPos;
6166     while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pFallbackFont))
6167     {
6168         const auto* pFont = pFallbackFont ? pFallbackFont : pDevFont;
6169 
6170         aCodeUnits.clear();
6171 
6172         // tdf#66597, tdf#115117
6173         //
6174         // Here is how we embed textual content in PDF files, to allow for
6175         // better text extraction for complex and typography-rich text.
6176         //
6177         // * If there is many to one or many to many mapping, use an
6178         //   ActualText span embedding the original string, since ToUnicode
6179         //   can’t handle these.
6180         // * If the one glyph is used for several Unicode code points, also
6181         //   use ActualText since ToUnicode can map each glyph in the font
6182         //   only once.
6183         // * Limit ActualText to single cluster at a time, since using it
6184         //   for whole words or sentences breaks text selection and
6185         //   highlighting in PDF viewers (there will be no way to tell
6186         //   which glyphs belong to which characters).
6187         // * Keep generating (now) redundant ToUnicode entries for
6188         //   compatibility with old tools not supporting ActualText.
6189 
6190         assert(pGlyph->charCount() >= 0);
6191         for (int n = 0; n < pGlyph->charCount(); n++)
6192             aCodeUnits.push_back(rText[pGlyph->charPos() + n]);
6193 
6194         bool bUseActualText = false;
6195 
6196         // If this is a start of complex cluster, use ActualText.
6197         if (pGlyph->IsClusterStart())
6198             bUseActualText = true;
6199 
6200         // Or part of a complex cluster, will be handled by the ActualText
6201         // of its cluster start.
6202         if (pGlyph->IsInCluster())
6203             assert(aCodeUnits.empty());
6204 
6205         // A glyph can’t have more than one ToUnicode entry, use ActualText
6206         // instead.
6207         if (!aCodeUnits.empty() && !bUseActualText)
6208         {
6209             for (const auto& rSubset : m_aSubsets[pFont].m_aSubsets)
6210             {
6211                 const auto& it = rSubset.m_aMapping.find(pGlyph->glyphId());
6212                 if (it != rSubset.m_aMapping.cend() && it->second.codes() != aCodeUnits)
6213                 {
6214                     bUseActualText = true;
6215                     aCodeUnits.clear();
6216                 }
6217             }
6218         }
6219 
6220         assert(!aCodeUnits.empty() || bUseActualText || pGlyph->IsInCluster());
6221 
6222         sal_uInt8 nMappedGlyph;
6223         sal_Int32 nMappedFontObject;
6224         registerGlyph(pGlyph, pFont, aCodeUnits, nMappedGlyph, nMappedFontObject);
6225 
6226         sal_Int32 nGlyphWidth = 0;
6227         SalGraphics *pGraphics = GetGraphics();
6228         if (pGraphics)
6229             nGlyphWidth = m_aFontCache.getGlyphWidth(pFont,
6230                                                      pGlyph->glyphId(),
6231                                                      pGlyph->IsVertical(),
6232                                                      pGraphics);
6233 
6234         int nCharPos = -1;
6235         if (bUseActualText || pGlyph->IsInCluster())
6236             nCharPos = pGlyph->charPos();
6237 
6238         aGlyphs.emplace_back(aPos,
6239                              pGlyph,
6240                              nGlyphWidth,
6241                              nMappedFontObject,
6242                              nMappedGlyph,
6243                              nCharPos);
6244     }
6245 
6246     // Avoid fill color when map mode is in pixels, the below code assumes
6247     // logic map mode.
6248     bool bPixel = m_aCurrentPDFState.m_aMapMode.GetMapUnit() == MapUnit::MapPixel;
6249     if (m_aCurrentPDFState.m_aFont.GetFillColor() != COL_TRANSPARENT && !bPixel)
6250     {
6251         // PDF doesn't have a text fill color, so draw a rectangle before
6252         // drawing the actual text.
6253         push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR);
6254         setFillColor(m_aCurrentPDFState.m_aFont.GetFillColor());
6255         // Avoid border around the rectangle for Writer shape text.
6256         setLineColor(COL_TRANSPARENT);
6257 
6258         // The rectangle is the bounding box of the text, but also includes
6259         // ascent / descent to match the on-screen rendering.
6260         tools::Rectangle aRectangle;
6261         // This is the top left of the text without ascent / descent.
6262         aRectangle.SetPos(PixelToLogic(rLayout.GetDrawPosition()));
6263         aRectangle.setY(aRectangle.getY() - aRefDevFontMetric.GetAscent());
6264         aRectangle.SetSize(PixelToLogic(Size(rLayout.GetTextWidth(), 0)));
6265         // This includes ascent / descent.
6266         aRectangle.setHeight(aRefDevFontMetric.GetLineHeight());
6267 
6268         const LogicalFontInstance* pFontInstance = GetFontInstance();
6269         if (pFontInstance->mnOrientation)
6270         {
6271             // Adapt rectangle for rotated text.
6272             tools::Polygon aPolygon(aRectangle);
6273             aPolygon.Rotate(PixelToLogic(rLayout.GetDrawPosition()), pFontInstance->mnOrientation);
6274             drawPolygon(aPolygon);
6275         }
6276         else
6277             drawRectangle(aRectangle);
6278 
6279         pop();
6280     }
6281 
6282     Point aAlignOffset;
6283     if ( eAlign == ALIGN_BOTTOM )
6284         aAlignOffset.AdjustY( -(aRefDevFontMetric.GetDescent()) );
6285     else if ( eAlign == ALIGN_TOP )
6286         aAlignOffset.AdjustY(aRefDevFontMetric.GetAscent() );
6287     if( aAlignOffset.X() || aAlignOffset.Y() )
6288         aAlignOffset = aRotScale.transform( aAlignOffset );
6289 
6290     /* #159153# do not emit an empty glyph vector; this can happen if e.g. the original
6291        string contained only one of the UTF16 BOMs
6292     */
6293     if( ! aGlyphs.empty() )
6294     {
6295         size_t nStart = 0;
6296         size_t nEnd = 0;
6297         while (nStart < aGlyphs.size())
6298         {
6299             while (nEnd < aGlyphs.size() && aGlyphs[nEnd].m_nCharPos == aGlyphs[nStart].m_nCharPos)
6300                 nEnd++;
6301 
6302             std::vector<PDFGlyph> aRun(aGlyphs.begin() + nStart, aGlyphs.begin() + nEnd);
6303 
6304             int nCharPos, nCharCount;
6305             if (!aRun.front().m_pGlyph->IsRTLGlyph())
6306             {
6307                 nCharPos = aRun.front().m_nCharPos;
6308                 nCharCount = aRun.front().m_pGlyph->charCount();
6309             }
6310             else
6311             {
6312                 nCharPos = aRun.back().m_nCharPos;
6313                 nCharCount = aRun.back().m_pGlyph->charCount();
6314             }
6315 
6316             if (nCharPos >= 0 && nCharCount)
6317             {
6318                 aLine.append("/Span<</ActualText<FEFF");
6319                 for (int i = 0; i < nCharCount; i++)
6320                 {
6321                     sal_Unicode aChar = rText[nCharPos + i];
6322                     appendHex(static_cast<sal_Int8>(aChar >> 8), aLine);
6323                     appendHex(static_cast<sal_Int8>(aChar & 255), aLine);
6324                 }
6325                 aLine.append( ">>>\nBDC\n" );
6326             }
6327 
6328             if (bVertical)
6329                 drawVerticalGlyphs(aRun, aLine, aAlignOffset, aRotScale, fAngle, fXScale, fSkew, nFontHeight);
6330             else
6331                 drawHorizontalGlyphs(aRun, aLine, aAlignOffset, nStart == 0, fAngle, fXScale, fSkew, nFontHeight, nPixelFontHeight);
6332 
6333             if (nCharPos >= 0 && nCharCount)
6334                 aLine.append( "EMC\n" );
6335 
6336             nStart = nEnd;
6337         }
6338     }
6339 
6340     // end textobject
6341     aLine.append( "ET\n" );
6342     if( bPop )
6343         aLine.append( "Q\n" );
6344 
6345     writeBuffer( aLine.getStr(), aLine.getLength() );
6346 
6347     // draw eventual textlines
6348     FontStrikeout eStrikeout = m_aCurrentPDFState.m_aFont.GetStrikeout();
6349     FontLineStyle eUnderline = m_aCurrentPDFState.m_aFont.GetUnderline();
6350     FontLineStyle eOverline  = m_aCurrentPDFState.m_aFont.GetOverline();
6351     if( bTextLines &&
6352         (
6353          ( eUnderline != LINESTYLE_NONE && eUnderline != LINESTYLE_DONTKNOW ) ||
6354          ( eOverline  != LINESTYLE_NONE && eOverline  != LINESTYLE_DONTKNOW ) ||
6355          ( eStrikeout != STRIKEOUT_NONE && eStrikeout != STRIKEOUT_DONTKNOW )
6356          )
6357         )
6358     {
6359         bool bUnderlineAbove = m_aCurrentPDFState.m_aFont.IsUnderlineAbove();
6360         if( m_aCurrentPDFState.m_aFont.IsWordLineMode() )
6361         {
6362             Point aStartPt;
6363             sal_Int32 nWidth = 0;
6364             nIndex = 0;
6365             while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex))
6366             {
6367                 if (!pGlyph->IsSpacing())
6368                 {
6369                     if( !nWidth )
6370                         aStartPt = aPos;
6371 
6372                     nWidth += pGlyph->m_nNewWidth;
6373                 }
6374                 else if( nWidth > 0 )
6375                 {
6376                     drawTextLine( PixelToLogic( aStartPt ),
6377                                   ImplDevicePixelToLogicWidth( nWidth ),
6378                                   eStrikeout, eUnderline, eOverline, bUnderlineAbove );
6379                     nWidth = 0;
6380                 }
6381             }
6382 
6383             if( nWidth > 0 )
6384             {
6385                 drawTextLine( PixelToLogic( aStartPt ),
6386                               ImplDevicePixelToLogicWidth( nWidth ),
6387                               eStrikeout, eUnderline, eOverline, bUnderlineAbove );
6388             }
6389         }
6390         else
6391         {
6392             Point aStartPt = rLayout.GetDrawPosition();
6393             int nWidth = rLayout.GetTextWidth() / rLayout.GetUnitsPerPixel();
6394             drawTextLine( PixelToLogic( aStartPt ),
6395                           ImplDevicePixelToLogicWidth( nWidth ),
6396                           eStrikeout, eUnderline, eOverline, bUnderlineAbove );
6397         }
6398     }
6399 
6400     // write eventual emphasis marks
6401     if( !(m_aCurrentPDFState.m_aFont.GetEmphasisMark() & FontEmphasisMark::Style) )
6402         return;
6403 
6404     tools::PolyPolygon             aEmphPoly;
6405     tools::Rectangle               aEmphRect1;
6406     tools::Rectangle               aEmphRect2;
6407     long                    nEmphYOff;
6408     long                    nEmphWidth;
6409     long                    nEmphHeight;
6410     bool                    bEmphPolyLine;
6411     FontEmphasisMark        nEmphMark;
6412 
6413     push( PushFlags::ALL );
6414 
6415     aLine.setLength( 0 );
6416     aLine.append( "q\n" );
6417 
6418     nEmphMark = OutputDevice::ImplGetEmphasisMarkStyle( m_aCurrentPDFState.m_aFont );
6419     if ( nEmphMark & FontEmphasisMark::PosBelow )
6420         nEmphHeight = GetEmphasisDescent();
6421     else
6422         nEmphHeight = GetEmphasisAscent();
6423     ImplGetEmphasisMark( aEmphPoly,
6424                                              bEmphPolyLine,
6425                                              aEmphRect1,
6426                                              aEmphRect2,
6427                                              nEmphYOff,
6428                                              nEmphWidth,
6429                                              nEmphMark,
6430                                              ImplDevicePixelToLogicWidth(nEmphHeight) );
6431     if ( bEmphPolyLine )
6432     {
6433         setLineColor( m_aCurrentPDFState.m_aFont.GetColor() );
6434         setFillColor( COL_TRANSPARENT );
6435     }
6436     else
6437     {
6438         setFillColor( m_aCurrentPDFState.m_aFont.GetColor() );
6439         setLineColor( COL_TRANSPARENT );
6440     }
6441     writeBuffer( aLine.getStr(), aLine.getLength() );
6442 
6443     Point aOffset(0,0);
6444 
6445     if ( nEmphMark & FontEmphasisMark::PosBelow )
6446         aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetDescent() + nEmphYOff );
6447     else
6448         aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetAscent() + nEmphYOff) );
6449 
6450     long nEmphWidth2     = nEmphWidth / 2;
6451     long nEmphHeight2    = nEmphHeight / 2;
6452     aOffset += Point( nEmphWidth2, nEmphHeight2 );
6453 
6454     if ( eAlign == ALIGN_BOTTOM )
6455         aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetDescent()) );
6456     else if ( eAlign == ALIGN_TOP )
6457         aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetAscent() );
6458 
6459     nIndex = 0;
6460     while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex))
6461     {
6462         if (pGlyph->IsSpacing())
6463         {
6464             Point aAdjOffset = aOffset;
6465             aAdjOffset.AdjustX((pGlyph->m_nNewWidth - nEmphWidth) / 2 );
6466             aAdjOffset = aRotScale.transform( aAdjOffset );
6467 
6468             aAdjOffset -= Point( nEmphWidth2, nEmphHeight2 );
6469 
6470             aPos += aAdjOffset;
6471             aPos = PixelToLogic( aPos );
6472             drawEmphasisMark( aPos.X(), aPos.Y(),
6473                               aEmphPoly, bEmphPolyLine,
6474                               aEmphRect1, aEmphRect2 );
6475         }
6476     }
6477 
6478     writeBuffer( "Q\n", 2 );
6479     pop();
6480 
6481 }
6482 
6483 void PDFWriterImpl::drawEmphasisMark( long nX, long nY,
6484                                       const tools::PolyPolygon& rPolyPoly, bool bPolyLine,
6485                                       const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 )
6486 {
6487     // TODO: pass nWidth as width of this mark
6488     // long nWidth = 0;
6489 
6490     if ( rPolyPoly.Count() )
6491     {
6492         if ( bPolyLine )
6493         {
6494             tools::Polygon aPoly = rPolyPoly.GetObject( 0 );
6495             aPoly.Move( nX, nY );
6496             drawPolyLine( aPoly );
6497         }
6498         else
6499         {
6500             tools::PolyPolygon aPolyPoly = rPolyPoly;
6501             aPolyPoly.Move( nX, nY );
6502             drawPolyPolygon( aPolyPoly );
6503         }
6504     }
6505 
6506     if ( !rRect1.IsEmpty() )
6507     {
6508         tools::Rectangle aRect( Point( nX+rRect1.Left(),
6509                                 nY+rRect1.Top() ), rRect1.GetSize() );
6510         drawRectangle( aRect );
6511     }
6512 
6513     if ( !rRect2.IsEmpty() )
6514     {
6515         tools::Rectangle aRect( Point( nX+rRect2.Left(),
6516                                 nY+rRect2.Top() ), rRect2.GetSize() );
6517 
6518         drawRectangle( aRect );
6519     }
6520 }
6521 
6522 void PDFWriterImpl::drawText( const Point& rPos, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen, bool bTextLines )
6523 {
6524     MARK( "drawText" );
6525 
6526     updateGraphicsState();
6527 
6528     // get a layout from the OutputDevice's SalGraphics
6529     // this also enforces font substitution and sets the font on SalGraphics
6530     std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos );
6531     if( pLayout )
6532     {
6533         drawLayout( *pLayout, rText, bTextLines );
6534     }
6535 }
6536 
6537 void PDFWriterImpl::drawTextArray( const Point& rPos, const OUString& rText, const long* pDXArray, sal_Int32 nIndex, sal_Int32 nLen )
6538 {
6539     MARK( "drawText with array" );
6540 
6541     updateGraphicsState();
6542 
6543     // get a layout from the OutputDevice's SalGraphics
6544     // this also enforces font substitution and sets the font on SalGraphics
6545     std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, 0, pDXArray );
6546     if( pLayout )
6547     {
6548         drawLayout( *pLayout, rText, true );
6549     }
6550 }
6551 
6552 void PDFWriterImpl::drawStretchText( const Point& rPos, sal_uLong nWidth, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen )
6553 {
6554     MARK( "drawStretchText" );
6555 
6556     updateGraphicsState();
6557 
6558     // get a layout from the OutputDevice's SalGraphics
6559     // this also enforces font substitution and sets the font on SalGraphics
6560     std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, nWidth );
6561     if( pLayout )
6562     {
6563         drawLayout( *pLayout, rText, true );
6564     }
6565 }
6566 
6567 void PDFWriterImpl::drawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle )
6568 {
6569     long        nWidth          = rRect.GetWidth();
6570     long        nHeight         = rRect.GetHeight();
6571 
6572     if ( nWidth <= 0 || nHeight <= 0 )
6573         return;
6574 
6575     MARK( "drawText with rectangle" );
6576 
6577     updateGraphicsState();
6578 
6579     // clip with rectangle
6580     OStringBuffer aLine;
6581     aLine.append( "q " );
6582     m_aPages.back().appendRect( rRect, aLine );
6583     aLine.append( " W* n\n" );
6584     writeBuffer( aLine.getStr(), aLine.getLength() );
6585 
6586     // if disabled text is needed, put in here
6587 
6588     Point       aPos            = rRect.TopLeft();
6589 
6590     long        nTextHeight     = GetTextHeight();
6591     sal_Int32   nMnemonicPos    = -1;
6592 
6593     OUString aStr = rOrigStr;
6594     if ( nStyle & DrawTextFlags::Mnemonic )
6595         aStr = OutputDevice::GetNonMnemonicString( aStr, nMnemonicPos );
6596 
6597     // multiline text
6598     if ( nStyle & DrawTextFlags::MultiLine )
6599     {
6600         OUString           aLastLine;
6601         ImplMultiTextLineInfo   aMultiLineInfo;
6602         ImplTextLineInfo*       pLineInfo;
6603         sal_Int32               i;
6604         sal_Int32               nLines;
6605         sal_Int32               nFormatLines;
6606 
6607         if ( nTextHeight )
6608         {
6609             vcl::DefaultTextLayout aLayout( *this );
6610             OutputDevice::ImplGetTextLines( aMultiLineInfo, nWidth, aStr, nStyle, aLayout );
6611             nLines = nHeight/nTextHeight;
6612             nFormatLines = aMultiLineInfo.Count();
6613             if ( !nLines )
6614                 nLines = 1;
6615             if ( nFormatLines > nLines )
6616             {
6617                 if ( nStyle & DrawTextFlags::EndEllipsis )
6618                 {
6619                     // handle last line
6620                     nFormatLines = nLines-1;
6621 
6622                     pLineInfo = aMultiLineInfo.GetLine( nFormatLines );
6623                     aLastLine = convertLineEnd(aStr.copy(pLineInfo->GetIndex()), LINEEND_LF);
6624                     // replace line feed by space
6625                     aLastLine = aLastLine.replace('\n', ' ');
6626                     aLastLine = GetEllipsisString( aLastLine, nWidth, nStyle );
6627                     nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom);
6628                     nStyle |= DrawTextFlags::Top;
6629                 }
6630             }
6631 
6632             // vertical alignment
6633             if ( nStyle & DrawTextFlags::Bottom )
6634                 aPos.AdjustY(nHeight-(nFormatLines*nTextHeight) );
6635             else if ( nStyle & DrawTextFlags::VCenter )
6636                 aPos.AdjustY((nHeight-(nFormatLines*nTextHeight))/2 );
6637 
6638             // draw all lines excluding the last
6639             for ( i = 0; i < nFormatLines; i++ )
6640             {
6641                 pLineInfo = aMultiLineInfo.GetLine( i );
6642                 if ( nStyle & DrawTextFlags::Right )
6643                     aPos.AdjustX(nWidth-pLineInfo->GetWidth() );
6644                 else if ( nStyle & DrawTextFlags::Center )
6645                     aPos.AdjustX((nWidth-pLineInfo->GetWidth())/2 );
6646                 sal_Int32 nIndex = pLineInfo->GetIndex();
6647                 sal_Int32 nLineLen = pLineInfo->GetLen();
6648                 drawText( aPos, aStr, nIndex, nLineLen );
6649                 // mnemonics should not appear in documents,
6650                 // if the need arises, put them in here
6651                 aPos.AdjustY(nTextHeight );
6652                 aPos.setX( rRect.Left() );
6653             }
6654 
6655             // output last line left adjusted since it was shortened
6656             if (!aLastLine.isEmpty())
6657                 drawText( aPos, aLastLine, 0, aLastLine.getLength() );
6658         }
6659     }
6660     else
6661     {
6662         long nTextWidth = GetTextWidth( aStr );
6663 
6664         // Evt. Text kuerzen
6665         if ( nTextWidth > nWidth )
6666         {
6667             if ( nStyle & (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis) )
6668             {
6669                 aStr = GetEllipsisString( aStr, nWidth, nStyle );
6670                 nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right);
6671                 nStyle |= DrawTextFlags::Left;
6672                 nTextWidth = GetTextWidth( aStr );
6673             }
6674         }
6675 
6676         // vertical alignment
6677         if ( nStyle & DrawTextFlags::Right )
6678             aPos.AdjustX(nWidth-nTextWidth );
6679         else if ( nStyle & DrawTextFlags::Center )
6680             aPos.AdjustX((nWidth-nTextWidth)/2 );
6681 
6682         if ( nStyle & DrawTextFlags::Bottom )
6683             aPos.AdjustY(nHeight-nTextHeight );
6684         else if ( nStyle & DrawTextFlags::VCenter )
6685             aPos.AdjustY((nHeight-nTextHeight)/2 );
6686 
6687         // mnemonics should be inserted here if the need arises
6688 
6689         // draw the actual text
6690         drawText( aPos, aStr, 0, aStr.getLength() );
6691     }
6692 
6693     // reset clip region to original value
6694     aLine.setLength( 0 );
6695     aLine.append( "Q\n" );
6696     writeBuffer( aLine.getStr(), aLine.getLength() );
6697 }
6698 
6699 void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop )
6700 {
6701     MARK( "drawLine" );
6702 
6703     updateGraphicsState();
6704 
6705     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
6706         return;
6707 
6708     OStringBuffer aLine;
6709     m_aPages.back().appendPoint( rStart, aLine );
6710     aLine.append( " m " );
6711     m_aPages.back().appendPoint( rStop, aLine );
6712     aLine.append( " l S\n" );
6713 
6714     writeBuffer( aLine.getStr(), aLine.getLength() );
6715 }
6716 
6717 void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo )
6718 {
6719     MARK( "drawLine with LineInfo" );
6720     updateGraphicsState();
6721 
6722     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
6723         return;
6724 
6725     if( rInfo.GetStyle() == LineStyle::Solid && rInfo.GetWidth() < 2 )
6726     {
6727         drawLine( rStart, rStop );
6728         return;
6729     }
6730 
6731     OStringBuffer aLine;
6732 
6733     aLine.append( "q " );
6734     if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
6735     {
6736         m_aPages.back().appendPoint( rStart, aLine );
6737         aLine.append( " m " );
6738         m_aPages.back().appendPoint( rStop, aLine );
6739         aLine.append( " l S Q\n" );
6740 
6741         writeBuffer( aLine.getStr(), aLine.getLength() );
6742     }
6743     else
6744     {
6745         PDFWriter::ExtLineInfo aInfo;
6746         convertLineInfoToExtLineInfo( rInfo, aInfo );
6747         Point aPolyPoints[2] = { rStart, rStop };
6748         tools::Polygon aPoly( 2, aPolyPoints );
6749         drawPolyLine( aPoly, aInfo );
6750     }
6751 }
6752 
6753 #define HCONV( x ) ImplDevicePixelToLogicHeight( x )
6754 
6755 void PDFWriterImpl::drawWaveTextLine( OStringBuffer& aLine, long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
6756 {
6757     // note: units in pFontInstance are ref device pixel
6758     const LogicalFontInstance*  pFontInstance = GetFontInstance();
6759     long            nLineHeight = 0;
6760     long            nLinePos = 0;
6761 
6762     appendStrokingColor( aColor, aLine );
6763     aLine.append( "\n" );
6764 
6765     if ( bIsAbove )
6766     {
6767         if ( !pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() )
6768             ImplInitAboveTextLineSize();
6769         nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() );
6770         nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineOffset() );
6771     }
6772     else
6773     {
6774         if ( !pFontInstance->mxFontMetric->GetWavelineUnderlineSize() )
6775             ImplInitTextLineSize();
6776         nLineHeight = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineSize() );
6777         nLinePos = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineOffset() );
6778     }
6779     if ( (eTextLine == LINESTYLE_SMALLWAVE) && (nLineHeight > 3) )
6780         nLineHeight = 3;
6781 
6782     long nLineWidth = GetDPIX()/450;
6783     if ( ! nLineWidth )
6784         nLineWidth = 1;
6785 
6786     if ( eTextLine == LINESTYLE_BOLDWAVE )
6787         nLineWidth = 3*nLineWidth;
6788 
6789     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineWidth), aLine );
6790     aLine.append( " w " );
6791 
6792     if ( eTextLine == LINESTYLE_DOUBLEWAVE )
6793     {
6794         long nOrgLineHeight = nLineHeight;
6795         nLineHeight /= 3;
6796         if ( nLineHeight < 2 )
6797         {
6798             if ( nOrgLineHeight > 1 )
6799                 nLineHeight = 2;
6800             else
6801                 nLineHeight = 1;
6802         }
6803         long nLineDY = nOrgLineHeight-(nLineHeight*2);
6804         if ( nLineDY < nLineWidth )
6805             nLineDY = nLineWidth;
6806         long nLineDY2 = nLineDY/2;
6807         if ( !nLineDY2 )
6808             nLineDY2 = 1;
6809 
6810         nLinePos -= nLineWidth-nLineDY2;
6811 
6812         m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
6813 
6814         nLinePos += nLineWidth+nLineDY;
6815         m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
6816     }
6817     else
6818     {
6819         if ( eTextLine != LINESTYLE_BOLDWAVE )
6820             nLinePos -= nLineWidth/2;
6821         m_aPages.back().appendWaveLine( nWidth, -nLinePos, nLineHeight, aLine );
6822     }
6823 }
6824 
6825 void PDFWriterImpl::drawStraightTextLine( OStringBuffer& aLine, long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
6826 {
6827     // note: units in pFontInstance are ref device pixel
6828     const LogicalFontInstance*  pFontInstance = GetFontInstance();
6829     long            nLineHeight = 0;
6830     long            nLinePos  = 0;
6831     long            nLinePos2 = 0;
6832 
6833     if ( eTextLine > LINESTYLE_BOLDWAVE )
6834         eTextLine = LINESTYLE_SINGLE;
6835 
6836     switch ( eTextLine )
6837     {
6838         case LINESTYLE_SINGLE:
6839         case LINESTYLE_DOTTED:
6840         case LINESTYLE_DASH:
6841         case LINESTYLE_LONGDASH:
6842         case LINESTYLE_DASHDOT:
6843         case LINESTYLE_DASHDOTDOT:
6844             if ( bIsAbove )
6845             {
6846                 if ( !pFontInstance->mxFontMetric->GetAboveUnderlineSize() )
6847                     ImplInitAboveTextLineSize();
6848                 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveUnderlineSize() );
6849                 nLinePos    = HCONV( pFontInstance->mxFontMetric->GetAboveUnderlineOffset() );
6850             }
6851             else
6852             {
6853                 if ( !pFontInstance->mxFontMetric->GetUnderlineSize() )
6854                     ImplInitTextLineSize();
6855                 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetUnderlineSize() );
6856                 nLinePos    = HCONV( pFontInstance->mxFontMetric->GetUnderlineOffset() );
6857             }
6858             break;
6859         case LINESTYLE_BOLD:
6860         case LINESTYLE_BOLDDOTTED:
6861         case LINESTYLE_BOLDDASH:
6862         case LINESTYLE_BOLDLONGDASH:
6863         case LINESTYLE_BOLDDASHDOT:
6864         case LINESTYLE_BOLDDASHDOTDOT:
6865             if ( bIsAbove )
6866             {
6867                 if ( !pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() )
6868                     ImplInitAboveTextLineSize();
6869                 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() );
6870                 nLinePos    = HCONV( pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset() );
6871             }
6872             else
6873             {
6874                 if ( !pFontInstance->mxFontMetric->GetBoldUnderlineSize() )
6875                     ImplInitTextLineSize();
6876                 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetBoldUnderlineSize() );
6877                 nLinePos    = HCONV( pFontInstance->mxFontMetric->GetBoldUnderlineOffset() );
6878                 nLinePos += nLineHeight/2;
6879             }
6880             break;
6881         case LINESTYLE_DOUBLE:
6882             if ( bIsAbove )
6883             {
6884                 if ( !pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() )
6885                     ImplInitAboveTextLineSize();
6886                 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() );
6887                 nLinePos    = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1() );
6888                 nLinePos2   = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2() );
6889             }
6890             else
6891             {
6892                 if ( !pFontInstance->mxFontMetric->GetDoubleUnderlineSize() )
6893                     ImplInitTextLineSize();
6894                 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineSize() );
6895                 nLinePos    = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1() );
6896                 nLinePos2   = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2() );
6897             }
6898             break;
6899         default:
6900             break;
6901     }
6902 
6903     if ( !nLineHeight )
6904         return;
6905 
6906     // outline attribute ?
6907     if (m_aCurrentPDFState.m_aFont.IsOutline() && eTextLine == LINESTYLE_SINGLE)
6908     {
6909         appendStrokingColor(aColor, aLine); // stroke with text color
6910         aLine.append( " " );
6911         appendNonStrokingColor(COL_WHITE, aLine); // fill with white
6912         aLine.append( "\n" );
6913         aLine.append( "0.25 w \n" ); // same line thickness as in drawLayout
6914 
6915         // draw rectangle instead
6916         aLine.append( "0 " );
6917         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos * 1.5), aLine );
6918         aLine.append( " " );
6919         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
6920         aLine.append( ' ' );
6921         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
6922         aLine.append( " re h B\n" );
6923         return;
6924     }
6925 
6926     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
6927     aLine.append( " w " );
6928     appendStrokingColor( aColor, aLine );
6929     aLine.append( "\n" );
6930 
6931     switch ( eTextLine )
6932     {
6933         case LINESTYLE_DOTTED:
6934         case LINESTYLE_BOLDDOTTED:
6935             aLine.append( "[ " );
6936             m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
6937             aLine.append( " ] 0 d\n" );
6938             break;
6939         case LINESTYLE_DASH:
6940         case LINESTYLE_LONGDASH:
6941         case LINESTYLE_BOLDDASH:
6942         case LINESTYLE_BOLDLONGDASH:
6943             {
6944                 sal_Int32 nDashLength = 4*nLineHeight;
6945                 sal_Int32 nVoidLength = 2*nLineHeight;
6946                 if ( ( eTextLine == LINESTYLE_LONGDASH ) || ( eTextLine == LINESTYLE_BOLDLONGDASH ) )
6947                     nDashLength = 8*nLineHeight;
6948 
6949                 aLine.append( "[ " );
6950                 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
6951                 aLine.append( ' ' );
6952                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
6953                 aLine.append( " ] 0 d\n" );
6954             }
6955             break;
6956         case LINESTYLE_DASHDOT:
6957         case LINESTYLE_BOLDDASHDOT:
6958             {
6959                 sal_Int32 nDashLength = 4*nLineHeight;
6960                 sal_Int32 nVoidLength = 2*nLineHeight;
6961                 aLine.append( "[ " );
6962                 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
6963                 aLine.append( ' ' );
6964                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
6965                 aLine.append( ' ' );
6966                 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
6967                 aLine.append( ' ' );
6968                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
6969                 aLine.append( " ] 0 d\n" );
6970             }
6971             break;
6972         case LINESTYLE_DASHDOTDOT:
6973         case LINESTYLE_BOLDDASHDOTDOT:
6974             {
6975                 sal_Int32 nDashLength = 4*nLineHeight;
6976                 sal_Int32 nVoidLength = 2*nLineHeight;
6977                 aLine.append( "[ " );
6978                 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
6979                 aLine.append( ' ' );
6980                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
6981                 aLine.append( ' ' );
6982                 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
6983                 aLine.append( ' ' );
6984                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
6985                 aLine.append( ' ' );
6986                 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
6987                 aLine.append( ' ' );
6988                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
6989                 aLine.append( " ] 0 d\n" );
6990             }
6991             break;
6992         default:
6993             break;
6994     }
6995 
6996     aLine.append( "0 " );
6997     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
6998     aLine.append( " m " );
6999     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
7000     aLine.append( ' ' );
7001     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7002     aLine.append( " l S\n" );
7003     if ( eTextLine == LINESTYLE_DOUBLE )
7004     {
7005         aLine.append( "0 " );
7006         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7007         aLine.append( " m " );
7008         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
7009         aLine.append( ' ' );
7010         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7011         aLine.append( " l S\n" );
7012     }
7013 
7014 }
7015 
7016 void PDFWriterImpl::drawStrikeoutLine( OStringBuffer& aLine, long nWidth, FontStrikeout eStrikeout, Color aColor )
7017 {
7018     // note: units in pFontInstance are ref device pixel
7019     const LogicalFontInstance*  pFontInstance = GetFontInstance();
7020     long            nLineHeight = 0;
7021     long            nLinePos  = 0;
7022     long            nLinePos2 = 0;
7023 
7024     if ( eStrikeout > STRIKEOUT_X )
7025         eStrikeout = STRIKEOUT_SINGLE;
7026 
7027     switch ( eStrikeout )
7028     {
7029         case STRIKEOUT_SINGLE:
7030             if ( !pFontInstance->mxFontMetric->GetStrikeoutSize() )
7031                 ImplInitTextLineSize();
7032             nLineHeight = HCONV( pFontInstance->mxFontMetric->GetStrikeoutSize() );
7033             nLinePos    = HCONV( pFontInstance->mxFontMetric->GetStrikeoutOffset() );
7034             break;
7035         case STRIKEOUT_BOLD:
7036             if ( !pFontInstance->mxFontMetric->GetBoldStrikeoutSize() )
7037                 ImplInitTextLineSize();
7038             nLineHeight = HCONV( pFontInstance->mxFontMetric->GetBoldStrikeoutSize() );
7039             nLinePos    = HCONV( pFontInstance->mxFontMetric->GetBoldStrikeoutOffset() );
7040             break;
7041         case STRIKEOUT_DOUBLE:
7042             if ( !pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() )
7043                 ImplInitTextLineSize();
7044             nLineHeight = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() );
7045             nLinePos    = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1() );
7046             nLinePos2   = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2() );
7047             break;
7048         default:
7049             break;
7050     }
7051 
7052     if ( !nLineHeight )
7053         return;
7054 
7055     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7056     aLine.append( " w " );
7057     appendStrokingColor( aColor, aLine );
7058     aLine.append( "\n" );
7059 
7060     aLine.append( "0 " );
7061     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7062     aLine.append( " m " );
7063     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
7064     aLine.append( ' ' );
7065     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7066     aLine.append( " l S\n" );
7067 
7068     if ( eStrikeout == STRIKEOUT_DOUBLE )
7069     {
7070         aLine.append( "0 " );
7071         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7072         aLine.append( " m " );
7073         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
7074         aLine.append( ' ' );
7075         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7076         aLine.append( " l S\n" );
7077     }
7078 
7079 }
7080 
7081 void PDFWriterImpl::drawStrikeoutChar( const Point& rPos, long nWidth, FontStrikeout eStrikeout )
7082 {
7083     //See qadevOOo/testdocs/StrikeThrough.odt for examples if you need
7084     //to tweak this
7085 
7086     OUString aStrikeoutChar = eStrikeout == STRIKEOUT_SLASH ? OUString( "/" ) : OUString( "X" );
7087     OUString aStrikeout = aStrikeoutChar;
7088     while( GetTextWidth( aStrikeout ) < nWidth )
7089         aStrikeout += aStrikeout;
7090 
7091     // do not get broader than nWidth modulo 1 character
7092     while( GetTextWidth( aStrikeout ) >= nWidth )
7093         aStrikeout = aStrikeout.replaceAt( 0, 1, "" );
7094     aStrikeout += aStrikeoutChar;
7095     bool bShadow = m_aCurrentPDFState.m_aFont.IsShadow();
7096     if ( bShadow )
7097     {
7098         Font aFont = m_aCurrentPDFState.m_aFont;
7099         aFont.SetShadow( false );
7100         setFont( aFont );
7101         updateGraphicsState();
7102     }
7103 
7104     // strikeout string is left aligned non-CTL text
7105     ComplexTextLayoutFlags nOrigTLM = GetLayoutMode();
7106     SetLayoutMode(ComplexTextLayoutFlags::BiDiStrong);
7107 
7108     push( PushFlags::CLIPREGION );
7109     FontMetric aRefDevFontMetric = GetFontMetric();
7110     tools::Rectangle aRect;
7111     aRect.SetLeft( rPos.X() );
7112     aRect.SetRight( aRect.Left()+nWidth );
7113     aRect.SetBottom( rPos.Y()+aRefDevFontMetric.GetDescent() );
7114     aRect.SetTop( rPos.Y()-aRefDevFontMetric.GetAscent() );
7115 
7116     const LogicalFontInstance* pFontInstance = GetFontInstance();
7117     if (pFontInstance->mnOrientation)
7118     {
7119         tools::Polygon aPoly( aRect );
7120         aPoly.Rotate( rPos, pFontInstance->mnOrientation);
7121         aRect = aPoly.GetBoundRect();
7122     }
7123 
7124     intersectClipRegion( aRect );
7125     drawText( rPos, aStrikeout, 0, aStrikeout.getLength(), false );
7126     pop();
7127 
7128     SetLayoutMode( nOrigTLM );
7129 
7130     if ( bShadow )
7131     {
7132         Font aFont = m_aCurrentPDFState.m_aFont;
7133         aFont.SetShadow( true );
7134         setFont( aFont );
7135         updateGraphicsState();
7136     }
7137 }
7138 
7139 void PDFWriterImpl::drawTextLine( const Point& rPos, long nWidth, FontStrikeout eStrikeout, FontLineStyle eUnderline, FontLineStyle eOverline, bool bUnderlineAbove )
7140 {
7141     if ( !nWidth ||
7142          ( ((eStrikeout == STRIKEOUT_NONE)||(eStrikeout == STRIKEOUT_DONTKNOW)) &&
7143            ((eUnderline == LINESTYLE_NONE)||(eUnderline == LINESTYLE_DONTKNOW)) &&
7144            ((eOverline  == LINESTYLE_NONE)||(eOverline  == LINESTYLE_DONTKNOW)) ) )
7145         return;
7146 
7147     MARK( "drawTextLine" );
7148     updateGraphicsState();
7149 
7150     // note: units in pFontInstance are ref device pixel
7151     const LogicalFontInstance* pFontInstance = GetFontInstance();
7152     Color           aUnderlineColor = m_aCurrentPDFState.m_aTextLineColor;
7153     Color           aOverlineColor  = m_aCurrentPDFState.m_aOverlineColor;
7154     Color           aStrikeoutColor = m_aCurrentPDFState.m_aFont.GetColor();
7155     bool            bStrikeoutDone = false;
7156     bool            bUnderlineDone = false;
7157     bool            bOverlineDone  = false;
7158 
7159     if ( (eStrikeout == STRIKEOUT_SLASH) || (eStrikeout == STRIKEOUT_X) )
7160     {
7161         drawStrikeoutChar( rPos, nWidth, eStrikeout );
7162         bStrikeoutDone = true;
7163     }
7164 
7165     Point aPos( rPos );
7166     TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
7167     if( eAlign == ALIGN_TOP )
7168         aPos.AdjustY(HCONV( pFontInstance->mxFontMetric->GetAscent() ));
7169     else if( eAlign == ALIGN_BOTTOM )
7170         aPos.AdjustY( -HCONV( pFontInstance->mxFontMetric->GetDescent() ) );
7171 
7172     OStringBuffer aLine( 512 );
7173     // save GS
7174     aLine.append( "q " );
7175 
7176     // rotate and translate matrix
7177     double fAngle = static_cast<double>(m_aCurrentPDFState.m_aFont.GetOrientation()) * M_PI / 1800.0;
7178     Matrix3 aMat;
7179     aMat.rotate( fAngle );
7180     aMat.translate( aPos.X(), aPos.Y() );
7181     m_aPages.back().appendMatrix3(aMat, aLine);
7182     aLine.append( " cm\n" );
7183 
7184     if ( aUnderlineColor.GetTransparency() != 0 )
7185         aUnderlineColor = aStrikeoutColor;
7186 
7187     if ( (eUnderline == LINESTYLE_SMALLWAVE) ||
7188          (eUnderline == LINESTYLE_WAVE) ||
7189          (eUnderline == LINESTYLE_DOUBLEWAVE) ||
7190          (eUnderline == LINESTYLE_BOLDWAVE) )
7191     {
7192         drawWaveTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
7193         bUnderlineDone = true;
7194     }
7195 
7196     if ( (eOverline == LINESTYLE_SMALLWAVE) ||
7197          (eOverline == LINESTYLE_WAVE) ||
7198          (eOverline == LINESTYLE_DOUBLEWAVE) ||
7199          (eOverline == LINESTYLE_BOLDWAVE) )
7200     {
7201         drawWaveTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
7202         bOverlineDone = true;
7203     }
7204 
7205     if ( !bUnderlineDone )
7206     {
7207         drawStraightTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
7208     }
7209 
7210     if ( !bOverlineDone )
7211     {
7212         drawStraightTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
7213     }
7214 
7215     if ( !bStrikeoutDone )
7216     {
7217         drawStrikeoutLine( aLine, nWidth, eStrikeout, aStrikeoutColor );
7218     }
7219 
7220     aLine.append( "Q\n" );
7221     writeBuffer( aLine.getStr(), aLine.getLength() );
7222 }
7223 
7224 void PDFWriterImpl::drawPolygon( const tools::Polygon& rPoly )
7225 {
7226     MARK( "drawPolygon" );
7227 
7228     updateGraphicsState();
7229 
7230     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7231         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7232         return;
7233 
7234     int nPoints = rPoly.GetSize();
7235     OStringBuffer aLine( 20 * nPoints );
7236     m_aPages.back().appendPolygon( rPoly, aLine );
7237     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7238         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7239         aLine.append( "B*\n" );
7240     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7241         aLine.append( "S\n" );
7242     else
7243         aLine.append( "f*\n" );
7244 
7245     writeBuffer( aLine.getStr(), aLine.getLength() );
7246 }
7247 
7248 void PDFWriterImpl::drawPolyPolygon( const tools::PolyPolygon& rPolyPoly )
7249 {
7250     MARK( "drawPolyPolygon" );
7251 
7252     updateGraphicsState();
7253 
7254     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7255         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7256         return;
7257 
7258     int nPolygons = rPolyPoly.Count();
7259 
7260     OStringBuffer aLine( 40 * nPolygons );
7261     m_aPages.back().appendPolyPolygon( rPolyPoly, aLine );
7262     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7263         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7264         aLine.append( "B*\n" );
7265     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7266         aLine.append( "S\n" );
7267     else
7268         aLine.append( "f*\n" );
7269 
7270     writeBuffer( aLine.getStr(), aLine.getLength() );
7271 }
7272 
7273 void PDFWriterImpl::drawTransparent( const tools::PolyPolygon& rPolyPoly, sal_uInt32 nTransparentPercent )
7274 {
7275     SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
7276     nTransparentPercent = nTransparentPercent % 100;
7277 
7278     MARK( "drawTransparent" );
7279 
7280     updateGraphicsState();
7281 
7282     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7283         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7284         return;
7285 
7286     if( m_bIsPDF_A1 || m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
7287     {
7288         m_aErrors.insert( m_bIsPDF_A1 ?
7289                           PDFWriter::Warning_Transparency_Omitted_PDFA :
7290                           PDFWriter::Warning_Transparency_Omitted_PDF13 );
7291 
7292         drawPolyPolygon( rPolyPoly );
7293         return;
7294     }
7295 
7296     // create XObject
7297     m_aTransparentObjects.emplace_back( );
7298     // FIXME: polygons with beziers may yield incorrect bound rect
7299     m_aTransparentObjects.back().m_aBoundRect     = rPolyPoly.GetBoundRect();
7300     // convert rectangle to default user space
7301     m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
7302     m_aTransparentObjects.back().m_nObject          = createObject();
7303     m_aTransparentObjects.back().m_nExtGStateObject = createObject();
7304     m_aTransparentObjects.back().m_fAlpha           = static_cast<double>(100-nTransparentPercent) / 100.0;
7305     m_aTransparentObjects.back().m_pContentStream.reset(new SvMemoryStream( 256, 256 ));
7306     // create XObject's content stream
7307     OStringBuffer aContent( 256 );
7308     m_aPages.back().appendPolyPolygon( rPolyPoly, aContent );
7309     if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT &&
7310         m_aCurrentPDFState.m_aFillColor != COL_TRANSPARENT )
7311         aContent.append( " B*\n" );
7312     else if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT )
7313         aContent.append( " S\n" );
7314     else
7315         aContent.append( " f*\n" );
7316     m_aTransparentObjects.back().m_pContentStream->WriteBytes(
7317         aContent.getStr(), aContent.getLength() );
7318 
7319     OStringBuffer aObjName( 16 );
7320     aObjName.append( "Tr" );
7321     aObjName.append( m_aTransparentObjects.back().m_nObject );
7322     OString aTrName( aObjName.makeStringAndClear() );
7323     aObjName.append( "EGS" );
7324     aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
7325     OString aExtName( aObjName.makeStringAndClear() );
7326 
7327     OString aLine =
7328     // insert XObject
7329         "q /" +
7330         aExtName +
7331         " gs /" +
7332         aTrName +
7333         " Do Q\n";
7334     writeBuffer( aLine.getStr(), aLine.getLength() );
7335 
7336     pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject );
7337     pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
7338 }
7339 
7340 void PDFWriterImpl::pushResource( ResourceKind eKind, const OString& rResource, sal_Int32 nObject )
7341 {
7342     if( nObject >= 0 )
7343     {
7344         switch( eKind )
7345         {
7346             case ResourceKind::XObject:
7347                 m_aGlobalResourceDict.m_aXObjects[ rResource ] = nObject;
7348                 if( ! m_aOutputStreams.empty() )
7349                     m_aOutputStreams.front().m_aResourceDict.m_aXObjects[ rResource ] = nObject;
7350                 break;
7351             case ResourceKind::ExtGState:
7352                 m_aGlobalResourceDict.m_aExtGStates[ rResource ] = nObject;
7353                 if( ! m_aOutputStreams.empty() )
7354                     m_aOutputStreams.front().m_aResourceDict.m_aExtGStates[ rResource ] = nObject;
7355                 break;
7356             case ResourceKind::Shading:
7357                 m_aGlobalResourceDict.m_aShadings[ rResource ] = nObject;
7358                 if( ! m_aOutputStreams.empty() )
7359                     m_aOutputStreams.front().m_aResourceDict.m_aShadings[ rResource ] = nObject;
7360                 break;
7361             case ResourceKind::Pattern:
7362                 m_aGlobalResourceDict.m_aPatterns[ rResource ] = nObject;
7363                 if( ! m_aOutputStreams.empty() )
7364                     m_aOutputStreams.front().m_aResourceDict.m_aPatterns[ rResource ] = nObject;
7365                 break;
7366         }
7367     }
7368 }
7369 
7370 void PDFWriterImpl::beginRedirect( SvStream* pStream, const tools::Rectangle& rTargetRect )
7371 {
7372     push( PushFlags::ALL );
7373 
7374     // force reemitting clip region inside the new stream, and
7375     // prevent emitting an unbalanced "Q" at the start
7376     clearClipRegion();
7377     // this is needed to point m_aCurrentPDFState at the pushed state
7378     // ... but it's pointless to actually write into the "outer" stream here!
7379     updateGraphicsState(Mode::NOWRITE);
7380 
7381     m_aOutputStreams.push_front( StreamRedirect() );
7382     m_aOutputStreams.front().m_pStream = pStream;
7383     m_aOutputStreams.front().m_aMapMode = m_aMapMode;
7384 
7385     if( !rTargetRect.IsEmpty() )
7386     {
7387         m_aOutputStreams.front().m_aTargetRect =
7388             lcl_convert( m_aGraphicsStack.front().m_aMapMode,
7389                          m_aMapMode,
7390                          this,
7391                          rTargetRect );
7392         Point aDelta = m_aOutputStreams.front().m_aTargetRect.BottomLeft();
7393         long nPageHeight = pointToPixel(m_aPages[m_nCurrentPage].getHeight());
7394         aDelta.setY( -(nPageHeight - m_aOutputStreams.front().m_aTargetRect.Bottom()) );
7395         m_aMapMode.SetOrigin( m_aMapMode.GetOrigin() + aDelta );
7396     }
7397 
7398     // setup graphics state for independent object stream
7399 
7400     // force reemitting colors
7401     m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
7402     m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
7403 }
7404 
7405 SvStream* PDFWriterImpl::endRedirect()
7406 {
7407     SvStream* pStream = nullptr;
7408     if( ! m_aOutputStreams.empty() )
7409     {
7410         pStream     = m_aOutputStreams.front().m_pStream;
7411         m_aMapMode  = m_aOutputStreams.front().m_aMapMode;
7412         m_aOutputStreams.pop_front();
7413     }
7414 
7415     pop();
7416 
7417     m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
7418     m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
7419 
7420     // needed after pop() to set m_aCurrentPDFState
7421     updateGraphicsState(Mode::NOWRITE);
7422 
7423     return pStream;
7424 }
7425 
7426 void PDFWriterImpl::beginTransparencyGroup()
7427 {
7428     updateGraphicsState();
7429     if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
7430         beginRedirect( new SvMemoryStream( 1024, 1024 ), tools::Rectangle() );
7431 }
7432 
7433 void PDFWriterImpl::endTransparencyGroup( const tools::Rectangle& rBoundingBox, sal_uInt32 nTransparentPercent )
7434 {
7435     SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
7436     nTransparentPercent = nTransparentPercent % 100;
7437 
7438     if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
7439         return;
7440 
7441     // create XObject
7442     m_aTransparentObjects.emplace_back( );
7443     m_aTransparentObjects.back().m_aBoundRect   = rBoundingBox;
7444     // convert rectangle to default user space
7445     m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
7446     m_aTransparentObjects.back().m_nObject      = createObject();
7447     m_aTransparentObjects.back().m_fAlpha       = static_cast<double>(100-nTransparentPercent) / 100.0;
7448     // get XObject's content stream
7449     m_aTransparentObjects.back().m_pContentStream.reset( static_cast<SvMemoryStream*>(endRedirect()) );
7450     m_aTransparentObjects.back().m_nExtGStateObject = createObject();
7451 
7452     OStringBuffer aObjName( 16 );
7453     aObjName.append( "Tr" );
7454     aObjName.append( m_aTransparentObjects.back().m_nObject );
7455     OString aTrName( aObjName.makeStringAndClear() );
7456     aObjName.append( "EGS" );
7457     aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
7458     OString aExtName( aObjName.makeStringAndClear() );
7459 
7460     OString aLine =
7461     // insert XObject
7462         "q /" +
7463         aExtName +
7464         " gs /" +
7465         aTrName +
7466         " Do Q\n";
7467     writeBuffer( aLine.getStr(), aLine.getLength() );
7468 
7469     pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject );
7470     pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
7471 
7472 }
7473 
7474 void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect )
7475 {
7476     MARK( "drawRectangle" );
7477 
7478     updateGraphicsState();
7479 
7480     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7481         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7482         return;
7483 
7484     OStringBuffer aLine( 40 );
7485     m_aPages.back().appendRect( rRect, aLine );
7486 
7487     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7488         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7489         aLine.append( " B*\n" );
7490     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7491         aLine.append( " S\n" );
7492     else
7493         aLine.append( " f*\n" );
7494 
7495     writeBuffer( aLine.getStr(), aLine.getLength() );
7496 }
7497 
7498 void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound )
7499 {
7500     MARK( "drawRectangle with rounded edges" );
7501 
7502     if( !nHorzRound && !nVertRound )
7503         drawRectangle( rRect );
7504 
7505     updateGraphicsState();
7506 
7507     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7508         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7509         return;
7510 
7511     if( nHorzRound > static_cast<sal_uInt32>(rRect.GetWidth())/2 )
7512         nHorzRound = rRect.GetWidth()/2;
7513     if( nVertRound > static_cast<sal_uInt32>(rRect.GetHeight())/2 )
7514         nVertRound = rRect.GetHeight()/2;
7515 
7516     Point aPoints[16];
7517     const double kappa = 0.5522847498;
7518     const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(nHorzRound))+0.5);
7519     const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(nVertRound))+0.5);
7520 
7521     aPoints[1]  = Point( rRect.TopLeft().X() + nHorzRound, rRect.TopLeft().Y() );
7522     aPoints[0]  = Point( aPoints[1].X() - kx, aPoints[1].Y() );
7523     aPoints[2]  = Point( rRect.TopRight().X()+1 - nHorzRound, aPoints[1].Y() );
7524     aPoints[3]  = Point( aPoints[2].X()+kx, aPoints[2].Y() );
7525 
7526     aPoints[5]  = Point( rRect.TopRight().X()+1, rRect.TopRight().Y()+nVertRound );
7527     aPoints[4]  = Point( aPoints[5].X(), aPoints[5].Y()-ky );
7528     aPoints[6]  = Point( aPoints[5].X(), rRect.BottomRight().Y()+1 - nVertRound );
7529     aPoints[7]  = Point( aPoints[6].X(), aPoints[6].Y()+ky );
7530 
7531     aPoints[9]  = Point( rRect.BottomRight().X()+1-nHorzRound, rRect.BottomRight().Y()+1 );
7532     aPoints[8]  = Point( aPoints[9].X()+kx, aPoints[9].Y() );
7533     aPoints[10] = Point( rRect.BottomLeft().X() + nHorzRound, aPoints[9].Y() );
7534     aPoints[11] = Point( aPoints[10].X()-kx, aPoints[10].Y() );
7535 
7536     aPoints[13] = Point( rRect.BottomLeft().X(), rRect.BottomLeft().Y()+1-nVertRound );
7537     aPoints[12] = Point( aPoints[13].X(), aPoints[13].Y()+ky );
7538     aPoints[14] = Point( rRect.TopLeft().X(), rRect.TopLeft().Y()+nVertRound );
7539     aPoints[15] = Point( aPoints[14].X(), aPoints[14].Y()-ky );
7540 
7541     OStringBuffer aLine( 80 );
7542     m_aPages.back().appendPoint( aPoints[1], aLine );
7543     aLine.append( " m " );
7544     m_aPages.back().appendPoint( aPoints[2], aLine );
7545     aLine.append( " l " );
7546     m_aPages.back().appendPoint( aPoints[3], aLine );
7547     aLine.append( ' ' );
7548     m_aPages.back().appendPoint( aPoints[4], aLine );
7549     aLine.append( ' ' );
7550     m_aPages.back().appendPoint( aPoints[5], aLine );
7551     aLine.append( " c\n" );
7552     m_aPages.back().appendPoint( aPoints[6], aLine );
7553     aLine.append( " l " );
7554     m_aPages.back().appendPoint( aPoints[7], aLine );
7555     aLine.append( ' ' );
7556     m_aPages.back().appendPoint( aPoints[8], aLine );
7557     aLine.append( ' ' );
7558     m_aPages.back().appendPoint( aPoints[9], aLine );
7559     aLine.append( " c\n" );
7560     m_aPages.back().appendPoint( aPoints[10], aLine );
7561     aLine.append( " l " );
7562     m_aPages.back().appendPoint( aPoints[11], aLine );
7563     aLine.append( ' ' );
7564     m_aPages.back().appendPoint( aPoints[12], aLine );
7565     aLine.append( ' ' );
7566     m_aPages.back().appendPoint( aPoints[13], aLine );
7567     aLine.append( " c\n" );
7568     m_aPages.back().appendPoint( aPoints[14], aLine );
7569     aLine.append( " l " );
7570     m_aPages.back().appendPoint( aPoints[15], aLine );
7571     aLine.append( ' ' );
7572     m_aPages.back().appendPoint( aPoints[0], aLine );
7573     aLine.append( ' ' );
7574     m_aPages.back().appendPoint( aPoints[1], aLine );
7575     aLine.append( " c " );
7576 
7577     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7578         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7579         aLine.append( "b*\n" );
7580     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7581         aLine.append( "s\n" );
7582     else
7583         aLine.append( "f*\n" );
7584 
7585     writeBuffer( aLine.getStr(), aLine.getLength() );
7586 }
7587 
7588 void PDFWriterImpl::drawEllipse( const tools::Rectangle& rRect )
7589 {
7590     MARK( "drawEllipse" );
7591 
7592     updateGraphicsState();
7593 
7594     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7595         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7596         return;
7597 
7598     Point aPoints[12];
7599     const double kappa = 0.5522847498;
7600     const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetWidth())/2.0)+0.5);
7601     const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetHeight())/2.0)+0.5);
7602 
7603     aPoints[1]  = Point( rRect.TopLeft().X() + rRect.GetWidth()/2, rRect.TopLeft().Y() );
7604     aPoints[0]  = Point( aPoints[1].X() - kx, aPoints[1].Y() );
7605     aPoints[2]  = Point( aPoints[1].X() + kx, aPoints[1].Y() );
7606 
7607     aPoints[4]  = Point( rRect.TopRight().X()+1, rRect.TopRight().Y() + rRect.GetHeight()/2 );
7608     aPoints[3]  = Point( aPoints[4].X(), aPoints[4].Y() - ky );
7609     aPoints[5]  = Point( aPoints[4].X(), aPoints[4].Y() + ky );
7610 
7611     aPoints[7]  = Point( rRect.BottomLeft().X() + rRect.GetWidth()/2, rRect.BottomLeft().Y()+1 );
7612     aPoints[6]  = Point( aPoints[7].X() + kx, aPoints[7].Y() );
7613     aPoints[8]  = Point( aPoints[7].X() - kx, aPoints[7].Y() );
7614 
7615     aPoints[10] = Point( rRect.TopLeft().X(), rRect.TopLeft().Y() + rRect.GetHeight()/2 );
7616     aPoints[9]  = Point( aPoints[10].X(), aPoints[10].Y() + ky );
7617     aPoints[11] = Point( aPoints[10].X(), aPoints[10].Y() - ky );
7618 
7619     OStringBuffer aLine( 80 );
7620     m_aPages.back().appendPoint( aPoints[1], aLine );
7621     aLine.append( " m " );
7622     m_aPages.back().appendPoint( aPoints[2], aLine );
7623     aLine.append( ' ' );
7624     m_aPages.back().appendPoint( aPoints[3], aLine );
7625     aLine.append( ' ' );
7626     m_aPages.back().appendPoint( aPoints[4], aLine );
7627     aLine.append( " c\n" );
7628     m_aPages.back().appendPoint( aPoints[5], aLine );
7629     aLine.append( ' ' );
7630     m_aPages.back().appendPoint( aPoints[6], aLine );
7631     aLine.append( ' ' );
7632     m_aPages.back().appendPoint( aPoints[7], aLine );
7633     aLine.append( " c\n" );
7634     m_aPages.back().appendPoint( aPoints[8], aLine );
7635     aLine.append( ' ' );
7636     m_aPages.back().appendPoint( aPoints[9], aLine );
7637     aLine.append( ' ' );
7638     m_aPages.back().appendPoint( aPoints[10], aLine );
7639     aLine.append( " c\n" );
7640     m_aPages.back().appendPoint( aPoints[11], aLine );
7641     aLine.append( ' ' );
7642     m_aPages.back().appendPoint( aPoints[0], aLine );
7643     aLine.append( ' ' );
7644     m_aPages.back().appendPoint( aPoints[1], aLine );
7645     aLine.append( " c " );
7646 
7647     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7648         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7649         aLine.append( "b*\n" );
7650     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7651         aLine.append( "s\n" );
7652     else
7653         aLine.append( "f*\n" );
7654 
7655     writeBuffer( aLine.getStr(), aLine.getLength() );
7656 }
7657 
7658 static double calcAngle( const tools::Rectangle& rRect, const Point& rPoint )
7659 {
7660     Point aOrigin((rRect.Left()+rRect.Right()+1)/2,
7661                   (rRect.Top()+rRect.Bottom()+1)/2);
7662     Point aPoint = rPoint - aOrigin;
7663 
7664     double fX = static_cast<double>(aPoint.X());
7665     double fY = static_cast<double>(-aPoint.Y());
7666 
7667     if ((rRect.GetHeight() == 0) || (rRect.GetWidth() == 0))
7668         throw o3tl::divide_by_zero();
7669 
7670     if( rRect.GetWidth() > rRect.GetHeight() )
7671         fY = fY*(static_cast<double>(rRect.GetWidth())/static_cast<double>(rRect.GetHeight()));
7672     else if( rRect.GetHeight() > rRect.GetWidth() )
7673         fX = fX*(static_cast<double>(rRect.GetHeight())/static_cast<double>(rRect.GetWidth()));
7674     return atan2( fY, fX );
7675 }
7676 
7677 void PDFWriterImpl::drawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop, bool bWithPie, bool bWithChord )
7678 {
7679     MARK( "drawArc" );
7680 
7681     updateGraphicsState();
7682 
7683     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7684         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7685         return;
7686 
7687     // calculate start and stop angles
7688     const double fStartAngle = calcAngle( rRect, rStart );
7689     double fStopAngle  = calcAngle( rRect, rStop );
7690     while( fStopAngle < fStartAngle )
7691         fStopAngle += 2.0*M_PI;
7692     const int nFragments = static_cast<int>((fStopAngle-fStartAngle)/(M_PI/2.0))+1;
7693     const double fFragmentDelta = (fStopAngle-fStartAngle)/static_cast<double>(nFragments);
7694     const double kappa = fabs( 4.0 * (1.0-cos(fFragmentDelta/2.0))/sin(fFragmentDelta/2.0) / 3.0);
7695     const double halfWidth = static_cast<double>(rRect.GetWidth())/2.0;
7696     const double halfHeight = static_cast<double>(rRect.GetHeight())/2.0;
7697 
7698     const Point aCenter( (rRect.Left()+rRect.Right()+1)/2,
7699                          (rRect.Top()+rRect.Bottom()+1)/2 );
7700 
7701     OStringBuffer aLine( 30*nFragments );
7702     Point aPoint( static_cast<int>(halfWidth * cos(fStartAngle) ),
7703                   -static_cast<int>(halfHeight * sin(fStartAngle) ) );
7704     aPoint += aCenter;
7705     m_aPages.back().appendPoint( aPoint, aLine );
7706     aLine.append( " m " );
7707     if( !basegfx::fTools::equal(fStartAngle, fStopAngle) )
7708     {
7709         for( int i = 0; i < nFragments; i++ )
7710         {
7711             const double fStartFragment = fStartAngle + static_cast<double>(i)*fFragmentDelta;
7712             const double fStopFragment = fStartFragment + fFragmentDelta;
7713             aPoint = Point( static_cast<int>(halfWidth * (cos(fStartFragment) - kappa*sin(fStartFragment) ) ),
7714                             -static_cast<int>(halfHeight * (sin(fStartFragment) + kappa*cos(fStartFragment) ) ) );
7715             aPoint += aCenter;
7716             m_aPages.back().appendPoint( aPoint, aLine );
7717             aLine.append( ' ' );
7718 
7719             aPoint = Point( static_cast<int>(halfWidth * (cos(fStopFragment) + kappa*sin(fStopFragment) ) ),
7720                             -static_cast<int>(halfHeight * (sin(fStopFragment) - kappa*cos(fStopFragment) ) ) );
7721             aPoint += aCenter;
7722             m_aPages.back().appendPoint( aPoint, aLine );
7723             aLine.append( ' ' );
7724 
7725             aPoint = Point( static_cast<int>(halfWidth * cos(fStopFragment) ),
7726                             -static_cast<int>(halfHeight * sin(fStopFragment) ) );
7727             aPoint += aCenter;
7728             m_aPages.back().appendPoint( aPoint, aLine );
7729             aLine.append( " c\n" );
7730         }
7731     }
7732     if( bWithChord || bWithPie )
7733     {
7734         if( bWithPie )
7735         {
7736             m_aPages.back().appendPoint( aCenter, aLine );
7737             aLine.append( " l " );
7738         }
7739         aLine.append( "h " );
7740     }
7741     if( ! bWithChord && ! bWithPie )
7742         aLine.append( "S\n" );
7743     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7744         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7745         aLine.append( "B*\n" );
7746     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7747         aLine.append( "S\n" );
7748     else
7749         aLine.append( "f*\n" );
7750 
7751     writeBuffer( aLine.getStr(), aLine.getLength() );
7752 }
7753 
7754 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly )
7755 {
7756     MARK( "drawPolyLine" );
7757 
7758     sal_uInt16 nPoints = rPoly.GetSize();
7759     if( nPoints < 2 )
7760         return;
7761 
7762     updateGraphicsState();
7763 
7764     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
7765         return;
7766 
7767     OStringBuffer aLine( 20 * nPoints );
7768     m_aPages.back().appendPolygon( rPoly, aLine, rPoly[0] == rPoly[nPoints-1] );
7769     aLine.append( "S\n" );
7770 
7771     writeBuffer( aLine.getStr(), aLine.getLength() );
7772 }
7773 
7774 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const LineInfo& rInfo )
7775 {
7776     MARK( "drawPolyLine with LineInfo" );
7777 
7778     updateGraphicsState();
7779 
7780     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
7781         return;
7782 
7783     OStringBuffer aLine;
7784     aLine.append( "q " );
7785     if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
7786     {
7787         writeBuffer( aLine.getStr(), aLine.getLength() );
7788         drawPolyLine( rPoly );
7789         writeBuffer( "Q\n", 2 );
7790     }
7791     else
7792     {
7793         PDFWriter::ExtLineInfo aInfo;
7794         convertLineInfoToExtLineInfo( rInfo, aInfo );
7795         drawPolyLine( rPoly, aInfo );
7796     }
7797 }
7798 
7799 void PDFWriterImpl::convertLineInfoToExtLineInfo( const LineInfo& rIn, PDFWriter::ExtLineInfo& rOut )
7800 {
7801     SAL_WARN_IF( rIn.GetStyle() != LineStyle::Dash, "vcl.pdfwriter", "invalid conversion" );
7802     rOut.m_fLineWidth           = rIn.GetWidth();
7803     rOut.m_fTransparency        = 0.0;
7804     rOut.m_eCap                 = PDFWriter::capButt;
7805     rOut.m_eJoin                = PDFWriter::joinMiter;
7806     rOut.m_fMiterLimit          = 10;
7807     rOut.m_aDashArray.clear();
7808 
7809     // add DashDot to DashArray
7810     const int nDashes   = rIn.GetDashCount();
7811     const int nDashLen  = rIn.GetDashLen();
7812     const int nDistance = rIn.GetDistance();
7813 
7814     for( int n  = 0; n < nDashes; n++ )
7815     {
7816         rOut.m_aDashArray.push_back( nDashLen );
7817         rOut.m_aDashArray.push_back( nDistance );
7818     }
7819     const int nDots   = rIn.GetDotCount();
7820     const int nDotLen = rIn.GetDotLen();
7821 
7822     for( int n  = 0; n < nDots; n++ )
7823     {
7824         rOut.m_aDashArray.push_back( nDotLen );
7825         rOut.m_aDashArray.push_back( nDistance );
7826     }
7827 
7828     // add LineJoin
7829     switch(rIn.GetLineJoin())
7830     {
7831         case basegfx::B2DLineJoin::Bevel :
7832         {
7833             rOut.m_eJoin = PDFWriter::joinBevel;
7834             break;
7835         }
7836         // Pdf has no 'none' lineJoin, default is miter
7837         case basegfx::B2DLineJoin::NONE :
7838         case basegfx::B2DLineJoin::Miter :
7839         {
7840             rOut.m_eJoin = PDFWriter::joinMiter;
7841             break;
7842         }
7843         case basegfx::B2DLineJoin::Round :
7844         {
7845             rOut.m_eJoin = PDFWriter::joinRound;
7846             break;
7847         }
7848     }
7849 
7850     // add LineCap
7851     switch(rIn.GetLineCap())
7852     {
7853         default: /* css::drawing::LineCap_BUTT */
7854         {
7855             rOut.m_eCap = PDFWriter::capButt;
7856             break;
7857         }
7858         case css::drawing::LineCap_ROUND:
7859         {
7860             rOut.m_eCap = PDFWriter::capRound;
7861             break;
7862         }
7863         case css::drawing::LineCap_SQUARE:
7864         {
7865             rOut.m_eCap = PDFWriter::capSquare;
7866             break;
7867         }
7868     }
7869 }
7870 
7871 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const PDFWriter::ExtLineInfo& rInfo )
7872 {
7873     MARK( "drawPolyLine with ExtLineInfo" );
7874 
7875     updateGraphicsState();
7876 
7877     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
7878         return;
7879 
7880     if( rInfo.m_fTransparency >= 1.0 )
7881         return;
7882 
7883     if( rInfo.m_fTransparency != 0.0 )
7884         beginTransparencyGroup();
7885 
7886     OStringBuffer aLine;
7887     aLine.append( "q " );
7888     m_aPages.back().appendMappedLength( rInfo.m_fLineWidth, aLine );
7889     aLine.append( " w" );
7890     if( rInfo.m_aDashArray.size() < 10 ) // implementation limit of acrobat reader
7891     {
7892         switch( rInfo.m_eCap )
7893         {
7894             default:
7895             case PDFWriter::capButt:   aLine.append( " 0 J" );break;
7896             case PDFWriter::capRound:  aLine.append( " 1 J" );break;
7897             case PDFWriter::capSquare: aLine.append( " 2 J" );break;
7898         }
7899         switch( rInfo.m_eJoin )
7900         {
7901             default:
7902             case PDFWriter::joinMiter:
7903             {
7904                 double fLimit = rInfo.m_fMiterLimit;
7905                 if( rInfo.m_fLineWidth < rInfo.m_fMiterLimit )
7906                     fLimit = fLimit / rInfo.m_fLineWidth;
7907                 if( fLimit < 1.0 )
7908                     fLimit = 1.0;
7909                 aLine.append( " 0 j " );
7910                 appendDouble( fLimit, aLine );
7911                 aLine.append( " M" );
7912             }
7913             break;
7914             case PDFWriter::joinRound:  aLine.append( " 1 j" );break;
7915             case PDFWriter::joinBevel:  aLine.append( " 2 j" );break;
7916         }
7917         if( !rInfo.m_aDashArray.empty() )
7918         {
7919             aLine.append( " [ " );
7920             for (auto const& dash : rInfo.m_aDashArray)
7921             {
7922                 m_aPages.back().appendMappedLength( dash, aLine );
7923                 aLine.append( ' ' );
7924             }
7925             aLine.append( "] 0 d" );
7926         }
7927         aLine.append( "\n" );
7928         writeBuffer( aLine.getStr(), aLine.getLength() );
7929         drawPolyLine( rPoly );
7930     }
7931     else
7932     {
7933         basegfx::B2DPolygon aPoly(rPoly.getB2DPolygon());
7934         basegfx::B2DPolyPolygon aPolyPoly;
7935 
7936         basegfx::utils::applyLineDashing(aPoly, rInfo.m_aDashArray, &aPolyPoly);
7937 
7938         // Old applyLineDashing subdivided the polygon. New one will create bezier curve segments.
7939         // To mimic old behaviour, apply subdivide here. If beziers shall be written (better quality)
7940         // this line needs to be removed and the loop below adapted accordingly
7941         aPolyPoly = basegfx::utils::adaptiveSubdivideByAngle(aPolyPoly);
7942 
7943         const sal_uInt32 nPolygonCount(aPolyPoly.count());
7944 
7945         for( sal_uInt32 nPoly = 0; nPoly < nPolygonCount; nPoly++ )
7946         {
7947             aLine.append( (nPoly != 0 && (nPoly & 7) == 0) ? "\n" : " " );
7948             aPoly = aPolyPoly.getB2DPolygon( nPoly );
7949             const sal_uInt32 nPointCount(aPoly.count());
7950 
7951             if(nPointCount)
7952             {
7953                 const sal_uInt32 nEdgeCount(aPoly.isClosed() ? nPointCount : nPointCount - 1);
7954                 basegfx::B2DPoint aCurrent(aPoly.getB2DPoint(0));
7955 
7956                 for(sal_uInt32 a(0); a < nEdgeCount; a++)
7957                 {
7958                     if( a > 0 )
7959                         aLine.append( " " );
7960                     const sal_uInt32 nNextIndex((a + 1) % nPointCount);
7961                     const basegfx::B2DPoint aNext(aPoly.getB2DPoint(nNextIndex));
7962 
7963                     m_aPages.back().appendPoint( Point( FRound(aCurrent.getX()),
7964                                                         FRound(aCurrent.getY()) ),
7965                                                  aLine );
7966                     aLine.append( " m " );
7967                     m_aPages.back().appendPoint( Point( FRound(aNext.getX()),
7968                                                         FRound(aNext.getY()) ),
7969                                                  aLine );
7970                     aLine.append( " l" );
7971 
7972                     // prepare next edge
7973                     aCurrent = aNext;
7974                 }
7975             }
7976         }
7977         aLine.append( " S " );
7978         writeBuffer( aLine.getStr(), aLine.getLength() );
7979     }
7980     writeBuffer( "Q\n", 2 );
7981 
7982     if( rInfo.m_fTransparency != 0.0 )
7983     {
7984         // FIXME: actually this may be incorrect with bezier polygons
7985         tools::Rectangle aBoundRect( rPoly.GetBoundRect() );
7986         // avoid clipping with thick lines
7987         if( rInfo.m_fLineWidth > 0.0 )
7988         {
7989             sal_Int32 nLW = sal_Int32(rInfo.m_fLineWidth);
7990             aBoundRect.AdjustTop( -nLW );
7991             aBoundRect.AdjustLeft( -nLW );
7992             aBoundRect.AdjustRight(nLW );
7993             aBoundRect.AdjustBottom(nLW );
7994         }
7995         endTransparencyGroup( aBoundRect, static_cast<sal_uInt16>(100.0*rInfo.m_fTransparency) );
7996     }
7997 }
7998 
7999 void PDFWriterImpl::drawPixel( const Point& rPoint, const Color& rColor )
8000 {
8001     MARK( "drawPixel" );
8002 
8003     Color aColor = ( rColor == COL_TRANSPARENT ? m_aGraphicsStack.front().m_aLineColor : rColor );
8004 
8005     if( aColor == COL_TRANSPARENT )
8006         return;
8007 
8008     // pixels are drawn in line color, so have to set
8009     // the nonstroking color to line color
8010     Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
8011     setFillColor( aColor );
8012 
8013     updateGraphicsState();
8014 
8015     OStringBuffer aLine( 20 );
8016     m_aPages.back().appendPoint( rPoint, aLine );
8017     aLine.append( ' ' );
8018     appendDouble( 1.0/double(GetDPIX()), aLine );
8019     aLine.append( ' ' );
8020     appendDouble( 1.0/double(GetDPIY()), aLine );
8021     aLine.append( " re f\n" );
8022     writeBuffer( aLine.getStr(), aLine.getLength() );
8023 
8024     setFillColor( aOldFillColor );
8025 }
8026 
8027 void PDFWriterImpl::writeTransparentObject( TransparencyEmit& rObject )
8028 {
8029     CHECK_RETURN2( updateObject( rObject.m_nObject ) );
8030 
8031     bool bFlateFilter = compressStream( rObject.m_pContentStream.get() );
8032     sal_uLong nSize = rObject.m_pContentStream->TellEnd();
8033     rObject.m_pContentStream->Seek( STREAM_SEEK_TO_BEGIN );
8034     if (g_bDebugDisableCompression)
8035     {
8036         emitComment( "PDFWriterImpl::writeTransparentObject" );
8037     }
8038     OStringBuffer aLine( 512 );
8039     CHECK_RETURN2( updateObject( rObject.m_nObject ) );
8040     aLine.append( rObject.m_nObject );
8041     aLine.append( " 0 obj\n"
8042                   "<</Type/XObject\n"
8043                   "/Subtype/Form\n"
8044                   "/BBox[ " );
8045     appendFixedInt( rObject.m_aBoundRect.Left(), aLine );
8046     aLine.append( ' ' );
8047     appendFixedInt( rObject.m_aBoundRect.Top(), aLine );
8048     aLine.append( ' ' );
8049     appendFixedInt( rObject.m_aBoundRect.Right(), aLine );
8050     aLine.append( ' ' );
8051     appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aLine );
8052     aLine.append( " ]\n" );
8053     if( ! rObject.m_pSoftMaskStream )
8054     {
8055         if( ! m_bIsPDF_A1 )
8056         {
8057             // 7.8.3 Resource dicts are required for content streams
8058             aLine.append( "/Resources " );
8059             aLine.append( getResourceDictObj() );
8060             aLine.append( " 0 R\n" );
8061 
8062             aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/K true>>\n" );
8063         }
8064     }
8065 
8066     aLine.append( "/Length " );
8067     aLine.append( static_cast<sal_Int32>(nSize) );
8068     aLine.append( "\n" );
8069     if( bFlateFilter )
8070         aLine.append( "/Filter/FlateDecode\n" );
8071     aLine.append( ">>\n"
8072                   "stream\n" );
8073     CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8074     checkAndEnableStreamEncryption( rObject.m_nObject );
8075     CHECK_RETURN2( writeBuffer( rObject.m_pContentStream->GetData(), nSize ) );
8076     disableStreamEncryption();
8077     aLine.setLength( 0 );
8078     aLine.append( "\n"
8079                   "endstream\n"
8080                   "endobj\n\n" );
8081     CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8082 
8083     // write ExtGState dict for this XObject
8084     aLine.setLength( 0 );
8085     aLine.append( rObject.m_nExtGStateObject );
8086     aLine.append( " 0 obj\n"
8087                   "<<" );
8088     if( ! rObject.m_pSoftMaskStream )
8089     {
8090         if( m_bIsPDF_A1 )
8091         {
8092             aLine.append( "/CA 1.0/ca 1.0" );
8093             m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
8094         }
8095         else
8096         {
8097             aLine.append(  "/CA " );
8098             appendDouble( rObject.m_fAlpha, aLine );
8099             aLine.append( "\n"
8100                           "   /ca " );
8101             appendDouble( rObject.m_fAlpha, aLine );
8102         }
8103         aLine.append( "\n" );
8104     }
8105     else
8106     {
8107         if( m_bIsPDF_A1 )
8108         {
8109             aLine.append( "/SMask/None" );
8110             m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
8111         }
8112         else
8113         {
8114             sal_Int32 nMaskSize = static_cast<sal_Int32>(rObject.m_pSoftMaskStream->TellEnd());
8115             rObject.m_pSoftMaskStream->Seek( STREAM_SEEK_TO_BEGIN );
8116             sal_Int32 nMaskObject = createObject();
8117             aLine.append( "/SMask<</Type/Mask/S/Luminosity/G " );
8118             aLine.append( nMaskObject );
8119             aLine.append( " 0 R>>\n" );
8120 
8121             OStringBuffer aMask;
8122             aMask.append( nMaskObject );
8123             aMask.append( " 0 obj\n"
8124                           "<</Type/XObject\n"
8125                           "/Subtype/Form\n"
8126                           "/BBox[" );
8127             appendFixedInt( rObject.m_aBoundRect.Left(), aMask );
8128             aMask.append( ' ' );
8129             appendFixedInt( rObject.m_aBoundRect.Top(), aMask );
8130             aMask.append( ' ' );
8131             appendFixedInt( rObject.m_aBoundRect.Right(), aMask );
8132             aMask.append( ' ' );
8133             appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aMask );
8134             aMask.append( "]\n" );
8135 
8136             // 7.8.3 Resource dicts are required for content streams
8137             aMask.append( "/Resources " );
8138             aMask.append( getResourceDictObj() );
8139             aMask.append( " 0 R\n" );
8140 
8141             aMask.append( "/Group<</S/Transparency/CS/DeviceRGB>>\n" );
8142             aMask.append( "/Length " );
8143             aMask.append( nMaskSize );
8144             aMask.append( ">>\n"
8145                           "stream\n" );
8146             CHECK_RETURN2( updateObject( nMaskObject ) );
8147             checkAndEnableStreamEncryption(  nMaskObject );
8148             CHECK_RETURN2( writeBuffer( aMask.getStr(), aMask.getLength() ) );
8149             CHECK_RETURN2( writeBuffer( rObject.m_pSoftMaskStream->GetData(), nMaskSize ) );
8150             disableStreamEncryption();
8151             aMask.setLength( 0 );
8152             aMask.append( "\nendstream\n"
8153                           "endobj\n\n" );
8154             CHECK_RETURN2( writeBuffer( aMask.getStr(), aMask.getLength() ) );
8155         }
8156     }
8157     aLine.append( ">>\n"
8158                   "endobj\n\n" );
8159     CHECK_RETURN2( updateObject( rObject.m_nExtGStateObject ) );
8160     CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8161 }
8162 
8163 bool PDFWriterImpl::writeGradientFunction( GradientEmit const & rObject )
8164 {
8165     // LO internal gradient -> PDF shading type:
8166     //  * GradientStyle::Linear: axial shading, using sampled-function with 2 samples
8167     //                          [t=0:colorStart, t=1:colorEnd]
8168     //  * GradientStyle::Axial: axial shading, using sampled-function with 3 samples
8169     //                          [t=0:colorEnd, t=0.5:colorStart, t=1:colorEnd]
8170     //  * other styles: function shading with aSize.Width() * aSize.Height() samples
8171     sal_Int32 nFunctionObject = createObject();
8172     CHECK_RETURN( updateObject( nFunctionObject ) );
8173 
8174     ScopedVclPtrInstance< VirtualDevice > aDev;
8175     aDev->SetOutputSizePixel( rObject.m_aSize );
8176     aDev->SetMapMode( MapMode( MapUnit::MapPixel ) );
8177     if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
8178         aDev->SetDrawMode( aDev->GetDrawMode() |
8179                           ( DrawModeFlags::GrayLine | DrawModeFlags::GrayFill | DrawModeFlags::GrayText |
8180                             DrawModeFlags::GrayBitmap | DrawModeFlags::GrayGradient ) );
8181     aDev->DrawGradient( tools::Rectangle( Point( 0, 0 ), rObject.m_aSize ), rObject.m_aGradient );
8182 
8183     Bitmap aSample = aDev->GetBitmap( Point( 0, 0 ), rObject.m_aSize );
8184     Bitmap::ScopedReadAccess pAccess(aSample);
8185 
8186     Size aSize = aSample.GetSizePixel();
8187 
8188     sal_Int32 nStreamLengthObject = createObject();
8189     if (g_bDebugDisableCompression)
8190     {
8191         emitComment( "PDFWriterImpl::writeGradientFunction" );
8192     }
8193     OStringBuffer aLine( 120 );
8194     aLine.append( nFunctionObject );
8195     aLine.append( " 0 obj\n"
8196                   "<</FunctionType 0\n");
8197     switch (rObject.m_aGradient.GetStyle())
8198     {
8199         case GradientStyle::Linear:
8200         case GradientStyle::Axial:
8201             aLine.append("/Domain[ 0 1]\n");
8202             break;
8203         default:
8204             aLine.append("/Domain[ 0 1 0 1]\n");
8205     }
8206     aLine.append("/Size[ " );
8207     switch (rObject.m_aGradient.GetStyle())
8208     {
8209         case GradientStyle::Linear:
8210             aLine.append('2');
8211             break;
8212         case GradientStyle::Axial:
8213             aLine.append('3');
8214             break;
8215         default:
8216             aLine.append( static_cast<sal_Int32>(aSize.Width()) );
8217             aLine.append( ' ' );
8218             aLine.append( static_cast<sal_Int32>(aSize.Height()) );
8219     }
8220     aLine.append( " ]\n"
8221                   "/BitsPerSample 8\n"
8222                   "/Range[ 0 1 0 1 0 1 ]\n"
8223                   "/Order 3\n"
8224                   "/Length " );
8225     aLine.append( nStreamLengthObject );
8226     if (!g_bDebugDisableCompression)
8227         aLine.append( " 0 R\n"
8228                       "/Filter/FlateDecode"
8229                       ">>\n"
8230                       "stream\n" );
8231     else
8232         aLine.append( " 0 R\n"
8233                       ">>\n"
8234                       "stream\n" );
8235     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8236 
8237     sal_uInt64 nStartStreamPos = 0;
8238     CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nStartStreamPos)) );
8239 
8240     checkAndEnableStreamEncryption( nFunctionObject );
8241     beginCompression();
8242     sal_uInt8 aCol[3];
8243     switch (rObject.m_aGradient.GetStyle())
8244     {
8245         case GradientStyle::Axial:
8246             aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
8247             aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
8248             aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
8249             CHECK_RETURN( writeBuffer( aCol, 3 ) );
8250             [[fallthrough]];
8251         case GradientStyle::Linear:
8252         {
8253             aCol[0] = rObject.m_aGradient.GetStartColor().GetRed();
8254             aCol[1] = rObject.m_aGradient.GetStartColor().GetGreen();
8255             aCol[2] = rObject.m_aGradient.GetStartColor().GetBlue();
8256             CHECK_RETURN( writeBuffer( aCol, 3 ) );
8257 
8258             aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
8259             aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
8260             aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
8261             CHECK_RETURN( writeBuffer( aCol, 3 ) );
8262             break;
8263         }
8264         default:
8265             for( int y = aSize.Height()-1; y >= 0; y-- )
8266             {
8267                 for( long x = 0; x < aSize.Width(); x++ )
8268                 {
8269                     BitmapColor aColor = pAccess->GetColor( y, x );
8270                     aCol[0] = aColor.GetRed();
8271                     aCol[1] = aColor.GetGreen();
8272                     aCol[2] = aColor.GetBlue();
8273                     CHECK_RETURN( writeBuffer( aCol, 3 ) );
8274                 }
8275             }
8276     }
8277     endCompression();
8278     disableStreamEncryption();
8279 
8280     sal_uInt64 nEndStreamPos = 0;
8281     CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nEndStreamPos)) );
8282 
8283     aLine.setLength( 0 );
8284     aLine.append( "\nendstream\nendobj\n\n" );
8285     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8286 
8287     // write stream length
8288     CHECK_RETURN( updateObject( nStreamLengthObject ) );
8289     aLine.setLength( 0 );
8290     aLine.append( nStreamLengthObject );
8291     aLine.append( " 0 obj\n" );
8292     aLine.append( static_cast<sal_Int64>(nEndStreamPos-nStartStreamPos) );
8293     aLine.append( "\nendobj\n\n" );
8294     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8295 
8296     CHECK_RETURN( updateObject( rObject.m_nObject ) );
8297     aLine.setLength( 0 );
8298     aLine.append( rObject.m_nObject );
8299     aLine.append( " 0 obj\n");
8300     switch (rObject.m_aGradient.GetStyle())
8301     {
8302         case GradientStyle::Linear:
8303         case GradientStyle::Axial:
8304             aLine.append("<</ShadingType 2\n");
8305             break;
8306         default:
8307             aLine.append("<</ShadingType 1\n");
8308     }
8309     aLine.append("/ColorSpace/DeviceRGB\n"
8310                   "/AntiAlias true\n");
8311 
8312     // Determination of shading axis
8313     // See: OutputDevice::ImplDrawLinearGradient for reference
8314     tools::Rectangle aRect;
8315     aRect.SetLeft(0);
8316     aRect.SetTop(0);
8317     aRect.SetRight( aSize.Width() );
8318     aRect.SetBottom( aSize.Height() );
8319 
8320     tools::Rectangle aBoundRect;
8321     Point     aCenter;
8322     sal_uInt16    nAngle = rObject.m_aGradient.GetAngle() % 3600;
8323     rObject.m_aGradient.GetBoundRect( aRect, aBoundRect, aCenter );
8324 
8325     const bool bLinear = (rObject.m_aGradient.GetStyle() == GradientStyle::Linear);
8326     double fBorder = aBoundRect.GetHeight() * rObject.m_aGradient.GetBorder() / 100.0;
8327     if ( !bLinear )
8328     {
8329         fBorder /= 2.0;
8330     }
8331 
8332     aBoundRect.AdjustBottom( -fBorder );
8333     if (!bLinear)
8334     {
8335         aBoundRect.AdjustTop(fBorder );
8336     }
8337 
8338     switch (rObject.m_aGradient.GetStyle())
8339     {
8340         case GradientStyle::Linear:
8341         case GradientStyle::Axial:
8342         {
8343             aLine.append("/Domain[ 0 1 ]\n"
8344                     "/Coords[ " );
8345             tools::Polygon aPoly( 2 );
8346             aPoly[0] = aBoundRect.BottomCenter();
8347             aPoly[1] = aBoundRect.TopCenter();
8348             aPoly.Rotate( aCenter, 3600 - nAngle );
8349 
8350             aLine.append( static_cast<sal_Int32>(aPoly[0].X()) );
8351             aLine.append( " " );
8352             aLine.append( static_cast<sal_Int32>(aPoly[0].Y()) );
8353             aLine.append( " " );
8354             aLine.append( static_cast<sal_Int32>(aPoly[1].X()));
8355             aLine.append( " ");
8356             aLine.append( static_cast<sal_Int32>(aPoly[1].Y()));
8357             aLine.append( " ]\n");
8358             aLine.append("/Extend [true true]\n");
8359             break;
8360         }
8361         default:
8362             aLine.append("/Domain[ 0 1 0 1 ]\n"
8363                     "/Matrix[ " );
8364             aLine.append( static_cast<sal_Int32>(aSize.Width()) );
8365             aLine.append( " 0 0 " );
8366             aLine.append( static_cast<sal_Int32>(aSize.Height()) );
8367             aLine.append( " 0 0 ]\n");
8368     }
8369     aLine.append("/Function " );
8370     aLine.append( nFunctionObject );
8371     aLine.append( " 0 R\n"
8372                   ">>\n"
8373                   "endobj\n\n" );
8374     return writeBuffer( aLine.getStr(), aLine.getLength() );
8375 }
8376 
8377 void PDFWriterImpl::writeJPG( JPGEmit& rObject )
8378 {
8379     if (!rObject.m_aReferenceXObject.m_aPDFData.empty() && !m_aContext.UseReferenceXObject)
8380     {
8381         writeReferenceXObject(rObject.m_aReferenceXObject);
8382         return;
8383     }
8384 
8385     CHECK_RETURN2( rObject.m_pStream );
8386     CHECK_RETURN2( updateObject( rObject.m_nObject ) );
8387 
8388     sal_Int32 nLength = rObject.m_pStream->TellEnd();
8389     rObject.m_pStream->Seek( STREAM_SEEK_TO_BEGIN );
8390 
8391     sal_Int32 nMaskObject = 0;
8392     if( !!rObject.m_aMask )
8393     {
8394         if( rObject.m_aMask.GetBitCount() == 1 ||
8395             ( rObject.m_aMask.GetBitCount() == 8 && m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 && !m_bIsPDF_A1 )
8396             )
8397         {
8398             nMaskObject = createObject();
8399         }
8400         else if( m_bIsPDF_A1 )
8401             m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
8402         else if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
8403             m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDF13 );
8404 
8405     }
8406     if (g_bDebugDisableCompression)
8407     {
8408         emitComment( "PDFWriterImpl::writeJPG" );
8409     }
8410 
8411     OStringBuffer aLine(200);
8412     aLine.append( rObject.m_nObject );
8413     aLine.append( " 0 obj\n"
8414                   "<</Type/XObject/Subtype/Image/Width " );
8415     aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Width()) );
8416     aLine.append( " /Height " );
8417     aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Height()) );
8418     aLine.append( " /BitsPerComponent 8 " );
8419     if( rObject.m_bTrueColor )
8420         aLine.append( "/ColorSpace/DeviceRGB" );
8421     else
8422         aLine.append( "/ColorSpace/DeviceGray" );
8423     aLine.append( "/Filter/DCTDecode/Length " );
8424     aLine.append( nLength );
8425     if( nMaskObject )
8426     {
8427         aLine.append( rObject.m_aMask.GetBitCount() == 1 ? " /Mask " : " /SMask " );
8428         aLine.append( nMaskObject );
8429         aLine.append( " 0 R " );
8430     }
8431     aLine.append( ">>\nstream\n" );
8432     CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8433 
8434     checkAndEnableStreamEncryption( rObject.m_nObject );
8435     CHECK_RETURN2( writeBuffer( rObject.m_pStream->GetData(), nLength ) );
8436     disableStreamEncryption();
8437 
8438     aLine.setLength( 0 );
8439     aLine.append( "\nendstream\nendobj\n\n" );
8440     CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8441 
8442     if( nMaskObject )
8443     {
8444         BitmapEmit aEmit;
8445         aEmit.m_nObject = nMaskObject;
8446         if( rObject.m_aMask.GetBitCount() == 1 )
8447             aEmit.m_aBitmap = BitmapEx( rObject.m_aMask, rObject.m_aMask );
8448         else if( rObject.m_aMask.GetBitCount() == 8 )
8449             aEmit.m_aBitmap = BitmapEx( rObject.m_aMask, AlphaMask( rObject.m_aMask ) );
8450         writeBitmapObject( aEmit, true );
8451     }
8452 
8453     writeReferenceXObject(rObject.m_aReferenceXObject);
8454 }
8455 
8456 void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
8457 {
8458     if (rEmit.m_nFormObject <= 0)
8459         return;
8460 
8461     // Count /Matrix and /BBox.
8462     // vcl::ImportPDF() works with 96 DPI so use the same values here, too.
8463     sal_Int32 nOldDPIX = GetDPIX();
8464     SetDPIX(96);
8465     sal_Int32 nOldDPIY = GetDPIY();
8466     SetDPIY(96);
8467     Size aSize = PixelToLogic(rEmit.m_aPixelSize, MapMode(m_aMapMode.GetMapUnit()));
8468     SetDPIX(nOldDPIX);
8469     SetDPIY(nOldDPIY);
8470     double fScaleX = 1.0 / aSize.Width();
8471     double fScaleY = 1.0 / aSize.Height();
8472 
8473     sal_Int32 nWrappedFormObject = 0;
8474     if (!m_aContext.UseReferenceXObject)
8475     {
8476         // Parse the PDF data, we need that to write the PDF dictionary of our
8477         // object.
8478         SvMemoryStream aPDFStream;
8479         aPDFStream.WriteBytes(rEmit.m_aPDFData.data(), rEmit.m_aPDFData.size());
8480         aPDFStream.Seek(0);
8481         filter::PDFDocument aPDFDocument;
8482         if (!aPDFDocument.Read(aPDFStream))
8483         {
8484             SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: reading the PDF document failed");
8485             return;
8486         }
8487         std::vector<filter::PDFObjectElement*> aPages = aPDFDocument.GetPages();
8488         if (aPages.empty())
8489         {
8490             SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no pages");
8491             return;
8492         }
8493 
8494         size_t nPageIndex = rEmit.m_nPDFPageIndex >= 0 ? rEmit.m_nPDFPageIndex : 0;
8495 
8496         filter::PDFObjectElement* pPage = aPages[nPageIndex];
8497         if (!pPage)
8498         {
8499             SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no page");
8500             return;
8501         }
8502 
8503         std::vector<filter::PDFObjectElement*> aContentStreams;
8504         if (filter::PDFObjectElement* pContentStream = pPage->LookupObject("Contents"))
8505             aContentStreams.push_back(pContentStream);
8506         else if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Contents")))
8507         {
8508             for (const auto pElement : pArray->GetElements())
8509             {
8510                 auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
8511                 if (!pReference)
8512                     continue;
8513 
8514                 filter::PDFObjectElement* pObject = pReference->LookupObject();
8515                 if (!pObject)
8516                     continue;
8517 
8518                 aContentStreams.push_back(pObject);
8519             }
8520         }
8521 
8522         if (aContentStreams.empty())
8523         {
8524             SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no content stream");
8525             return;
8526         }
8527 
8528         nWrappedFormObject = createObject();
8529         // Write the form XObject wrapped below. This is a separate object from
8530         // the wrapper, this way there is no need to alter the stream contents.
8531 
8532         OStringBuffer aLine;
8533         aLine.append(nWrappedFormObject);
8534         aLine.append(" 0 obj\n");
8535         aLine.append("<< /Type /XObject");
8536         aLine.append(" /Subtype /Form");
8537 
8538         long nWidth = aSize.Width();
8539         long nHeight = aSize.Height();
8540         if (auto pRotate = dynamic_cast<filter::PDFNumberElement*>(pPage->Lookup("Rotate")))
8541         {
8542             // The original page was rotated, then construct a transformation matrix which does the
8543             // same with our form object.
8544             if (rtl::math::approxEqual(pRotate->GetValue(), 90))
8545             {
8546                 std::swap(nWidth, nHeight);
8547                 basegfx::B2DHomMatrix aMat;
8548                 aMat.rotate(basegfx::deg2rad(pRotate->GetValue()));
8549                 // Rotate around the origo (bottom left corner) counter-clockwise, then translate
8550                 // horizontally to effectively keep the bottom left corner unchanged.
8551                 aLine.append(" /Matrix [ ");
8552                 aLine.append(aMat.get(0, 0));
8553                 aLine.append(" ");
8554                 aLine.append(aMat.get(0, 1));
8555                 aLine.append(" ");
8556                 aLine.append(aMat.get(1, 0));
8557                 aLine.append(" ");
8558                 aLine.append(aMat.get(1, 1));
8559                 aLine.append(" 0 ");
8560                 aLine.append(nWidth);
8561                 aLine.append(" ] ");
8562             }
8563         }
8564 
8565         PDFObjectCopier aCopier(*this);
8566         aCopier.copyPageResources(pPage, aLine);
8567         aLine.append(" /BBox [ 0 0 ");
8568         aLine.append(nWidth);
8569         aLine.append(" ");
8570         aLine.append(nHeight);
8571         aLine.append(" ]");
8572 
8573         if (!g_bDebugDisableCompression)
8574             aLine.append(" /Filter/FlateDecode");
8575         aLine.append(" /Length ");
8576 
8577         SvMemoryStream aStream;
8578         bool bCompressed = false;
8579         sal_Int32 nLength = PDFObjectCopier::copyPageStreams(aContentStreams, aStream, bCompressed);
8580         aLine.append(nLength);
8581 
8582         aLine.append(">>\nstream\n");
8583         // Copy the original page streams to the form XObject stream.
8584         aLine.append(static_cast<const char*>(aStream.GetData()), aStream.GetSize());
8585         aLine.append("\nendstream\nendobj\n\n");
8586         if (!updateObject(nWrappedFormObject))
8587             return;
8588         if (!writeBuffer(aLine.getStr(), aLine.getLength()))
8589             return;
8590     }
8591 
8592     OStringBuffer aLine;
8593     if (!updateObject(rEmit.m_nFormObject))
8594         return;
8595 
8596     // Now have all the info to write the form XObject.
8597     aLine.append(rEmit.m_nFormObject);
8598     aLine.append(" 0 obj\n");
8599     aLine.append("<< /Type /XObject");
8600     aLine.append(" /Subtype /Form");
8601     aLine.append(" /Resources << /XObject<<");
8602 
8603     sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject;
8604     aLine.append(" /Im");
8605     aLine.append(nObject);
8606     aLine.append(" ");
8607     aLine.append(nObject);
8608     aLine.append(" 0 R");
8609 
8610     aLine.append(">> >>");
8611     aLine.append(" /Matrix [ ");
8612     appendDouble(fScaleX, aLine);
8613     aLine.append(" 0 0 ");
8614     appendDouble(fScaleY, aLine);
8615     aLine.append(" 0 0 ]");
8616     aLine.append(" /BBox [ 0 0 ");
8617     aLine.append(aSize.Width());
8618     aLine.append(" ");
8619     aLine.append(aSize.Height());
8620     aLine.append(" ]\n");
8621 
8622     if (m_aContext.UseReferenceXObject && rEmit.m_nEmbeddedObject > 0)
8623     {
8624         // Write the reference dictionary.
8625         aLine.append("/Ref<< /F << /Type /Filespec /F (<embedded file>) /EF << /F ");
8626         aLine.append(rEmit.m_nEmbeddedObject);
8627         aLine.append(" 0 R >> >> /Page 0 >>\n");
8628     }
8629 
8630     aLine.append("/Length ");
8631 
8632     OStringBuffer aStream;
8633     aStream.append("q ");
8634     if (m_aContext.UseReferenceXObject)
8635     {
8636         // Reference XObject markup is used, just refer to the fallback bitmap
8637         // here.
8638         aStream.append(aSize.Width());
8639         aStream.append(" 0 0 ");
8640         aStream.append(aSize.Height());
8641         aStream.append(" 0 0 cm\n");
8642         aStream.append("/Im");
8643         aStream.append(rEmit.m_nBitmapObject);
8644         aStream.append(" Do\n");
8645     }
8646     else
8647     {
8648         // Reset line width to the default.
8649         aStream.append(" 1 w\n");
8650 
8651         // vcl::RenderPDFBitmaps() effectively renders a white background for transparent input, be
8652         // consistent with that.
8653         aStream.append("1 1 1 rg\n");
8654         aStream.append("0 0 ");
8655         aStream.append(aSize.Width());
8656         aStream.append(" ");
8657         aStream.append(aSize.Height());
8658         aStream.append(" re\n");
8659         aStream.append("f*\n");
8660 
8661         // No reference XObject, draw the form XObject containing the original
8662         // page streams.
8663         aStream.append("/Im");
8664         aStream.append(nWrappedFormObject);
8665         aStream.append(" Do\n");
8666     }
8667     aStream.append("Q");
8668     aLine.append(aStream.getLength());
8669 
8670     aLine.append(">>\nstream\n");
8671     aLine.append(aStream.getStr());
8672     aLine.append("\nendstream\nendobj\n\n");
8673     CHECK_RETURN2(writeBuffer(aLine.getStr(), aLine.getLength()));
8674 }
8675 
8676 namespace
8677 {
8678     unsigned char reverseByte(unsigned char b)
8679     {
8680         b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
8681         b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
8682         b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
8683         return b;
8684     }
8685 
8686     //tdf#103051 convert any N1BitLsbPal to N1BitMsbPal
8687     Bitmap getExportBitmap(const Bitmap &rBitmap)
8688     {
8689         Bitmap::ScopedReadAccess pAccess(const_cast<Bitmap&>(rBitmap));
8690         const ScanlineFormat eFormat = pAccess->GetScanlineFormat();
8691         if (eFormat != ScanlineFormat::N1BitLsbPal)
8692             return rBitmap;
8693         Bitmap aNewBmp(rBitmap);
8694         BitmapScopedWriteAccess xWriteAcc(aNewBmp);
8695         const int nScanLineBytes = (pAccess->Width() + 7U) / 8U;
8696         for (long nY = 0L; nY < xWriteAcc->Height(); ++nY)
8697         {
8698             Scanline pBitSwap = xWriteAcc->GetScanline(nY);
8699             for (int x = 0; x < nScanLineBytes; ++x)
8700                 pBitSwap[x] = reverseByte(pBitSwap[x]);
8701         }
8702         return aNewBmp;
8703     }
8704 }
8705 
8706 bool PDFWriterImpl::writeBitmapObject( BitmapEmit& rObject, bool bMask )
8707 {
8708     if (!rObject.m_aReferenceXObject.m_aPDFData.empty() && !m_aContext.UseReferenceXObject)
8709     {
8710         writeReferenceXObject(rObject.m_aReferenceXObject);
8711         return true;
8712     }
8713 
8714     CHECK_RETURN( updateObject( rObject.m_nObject ) );
8715 
8716     Bitmap  aBitmap;
8717     Color   aTransparentColor( COL_TRANSPARENT );
8718     bool    bWriteMask = false;
8719     if( ! bMask )
8720     {
8721         aBitmap = getExportBitmap(rObject.m_aBitmap.GetBitmap());
8722         if( rObject.m_aBitmap.IsAlpha() )
8723         {
8724             if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
8725                 bWriteMask = true;
8726             // else draw without alpha channel
8727         }
8728         else
8729         {
8730             switch( rObject.m_aBitmap.GetTransparentType() )
8731             {
8732                 case TransparentType::NONE:
8733                     break;
8734                 case TransparentType::Color:
8735                     aTransparentColor = rObject.m_aBitmap.GetTransparentColor();
8736                     break;
8737                 case TransparentType::Bitmap:
8738                     bWriteMask = true;
8739                     break;
8740             }
8741         }
8742     }
8743     else
8744     {
8745         if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 || ! rObject.m_aBitmap.IsAlpha() )
8746         {
8747             aBitmap = getExportBitmap(rObject.m_aBitmap.GetMask());
8748             aBitmap.Convert( BmpConversion::N1BitThreshold );
8749             SAL_WARN_IF( aBitmap.GetBitCount() != 1, "vcl.pdfwriter", "mask conversion failed" );
8750         }
8751         else if( aBitmap.GetBitCount() != 8 )
8752         {
8753             aBitmap = getExportBitmap(rObject.m_aBitmap.GetAlpha().GetBitmap());
8754             aBitmap.Convert( BmpConversion::N8BitGreys );
8755             SAL_WARN_IF( aBitmap.GetBitCount() != 8, "vcl.pdfwriter", "alpha mask conversion failed" );
8756         }
8757     }
8758 
8759     Bitmap::ScopedReadAccess pAccess(aBitmap);
8760 
8761     bool bTrueColor;
8762     sal_Int32 nBitsPerComponent;
8763     switch( aBitmap.GetBitCount() )
8764     {
8765         case 1:
8766         case 2:
8767         case 4:
8768         case 8:
8769             bTrueColor = false;
8770             nBitsPerComponent = aBitmap.GetBitCount();
8771             break;
8772         default:
8773             bTrueColor = true;
8774             nBitsPerComponent = 8;
8775             break;
8776     }
8777 
8778     sal_Int32 nStreamLengthObject   = createObject();
8779     sal_Int32 nMaskObject           = 0;
8780 
8781     if (g_bDebugDisableCompression)
8782     {
8783         emitComment( "PDFWriterImpl::writeBitmapObject" );
8784     }
8785     OStringBuffer aLine(1024);
8786     aLine.append( rObject.m_nObject );
8787     aLine.append( " 0 obj\n"
8788                   "<</Type/XObject/Subtype/Image/Width " );
8789     aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Width()) );
8790     aLine.append( "/Height " );
8791     aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Height()) );
8792     aLine.append( "/BitsPerComponent " );
8793     aLine.append( nBitsPerComponent );
8794     aLine.append( "/Length " );
8795     aLine.append( nStreamLengthObject );
8796     aLine.append( " 0 R\n" );
8797     if (!g_bDebugDisableCompression)
8798     {
8799         if( nBitsPerComponent != 1 )
8800         {
8801             aLine.append( "/Filter/FlateDecode" );
8802         }
8803         else
8804         {
8805             aLine.append( "/Filter/CCITTFaxDecode/DecodeParms<</K -1/BlackIs1 true/Columns " );
8806             aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Width()) );
8807             aLine.append( ">>\n" );
8808         }
8809     }
8810     if( ! bMask )
8811     {
8812         aLine.append( "/ColorSpace" );
8813         if( bTrueColor )
8814             aLine.append( "/DeviceRGB\n" );
8815         else if( aBitmap.HasGreyPaletteAny() )
8816         {
8817             aLine.append( "/DeviceGray\n" );
8818             if( aBitmap.GetBitCount() == 1 )
8819             {
8820                 // #i47395# 1 bit bitmaps occasionally have an inverted grey palette
8821                 sal_uInt16 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_BLACK ) );
8822                 assert( nBlackIndex == 0 || nBlackIndex == 1);
8823                 sal_uInt16 nWhiteIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_WHITE ) );
8824                 if( pAccess->GetPalette()[nBlackIndex] == BitmapColor( COL_BLACK ) &&
8825                     pAccess->GetPalette()[nWhiteIndex] == BitmapColor( COL_WHITE ) )
8826                 {
8827                     // It is black and white
8828                     if( nBlackIndex == 1 )
8829                         aLine.append( "/Decode[1 0]\n" );
8830                 }
8831                 else
8832                 {
8833                     // It is two levels of grey
8834                     aLine.append( "/Decode[" );
8835                     assert( pAccess->GetPalette()[0].GetRed() == pAccess->GetPalette()[0].GetGreen() &&
8836                             pAccess->GetPalette()[0].GetRed() == pAccess->GetPalette()[0].GetBlue() &&
8837                             pAccess->GetPalette()[1].GetRed() == pAccess->GetPalette()[1].GetGreen() &&
8838                             pAccess->GetPalette()[1].GetRed() == pAccess->GetPalette()[1].GetBlue() );
8839                     aLine.append( pAccess->GetPalette()[0].GetRed() / 255.0 );
8840                     aLine.append( " " );
8841                     aLine.append( pAccess->GetPalette()[1].GetRed() / 255.0 );
8842                     aLine.append( "]\n" );
8843                 }
8844             }
8845         }
8846         else
8847         {
8848             aLine.append( "[ /Indexed/DeviceRGB " );
8849             aLine.append( static_cast<sal_Int32>(pAccess->GetPaletteEntryCount()-1) );
8850             aLine.append( "\n<" );
8851             if( m_aContext.Encryption.Encrypt() )
8852             {
8853                 enableStringEncryption( rObject.m_nObject );
8854                 //check encryption buffer size
8855                 m_vEncryptionBuffer.resize(pAccess->GetPaletteEntryCount()*3);
8856                 int nChar = 0;
8857                 //fill the encryption buffer
8858                 for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
8859                 {
8860                     const BitmapColor& rColor = pAccess->GetPaletteColor( i );
8861                     m_vEncryptionBuffer[nChar++] = rColor.GetRed();
8862                     m_vEncryptionBuffer[nChar++] = rColor.GetGreen();
8863                     m_vEncryptionBuffer[nChar++] = rColor.GetBlue();
8864                 }
8865                 //encrypt the colorspace lookup table
8866                 rtl_cipher_encodeARCFOUR( m_aCipher, m_vEncryptionBuffer.data(), nChar, m_vEncryptionBuffer.data(), nChar );
8867                 //now queue the data for output
8868                 nChar = 0;
8869                 for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
8870                 {
8871                     appendHex(m_vEncryptionBuffer[nChar++], aLine );
8872                     appendHex(m_vEncryptionBuffer[nChar++], aLine );
8873                     appendHex(m_vEncryptionBuffer[nChar++], aLine );
8874                 }
8875             }
8876             else //no encryption requested (PDF/A-1a program flow drops here)
8877             {
8878                 for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
8879                 {
8880                     const BitmapColor& rColor = pAccess->GetPaletteColor( i );
8881                     appendHex( rColor.GetRed(), aLine );
8882                     appendHex( rColor.GetGreen(), aLine );
8883                     appendHex( rColor.GetBlue(), aLine );
8884                 }
8885             }
8886             aLine.append( ">\n]\n" );
8887         }
8888     }
8889     else
8890     {
8891         if( aBitmap.GetBitCount() == 1 )
8892         {
8893             aLine.append( "/ImageMask true\n" );
8894             sal_Int32 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_BLACK ) );
8895             SAL_WARN_IF( nBlackIndex != 0 && nBlackIndex != 1, "vcl.pdfwriter", "wrong black index" );
8896             if( nBlackIndex )
8897                 aLine.append( "/Decode[ 1 0 ]\n" );
8898             else
8899                 aLine.append( "/Decode[ 0 1 ]\n" );
8900         }
8901         else if( aBitmap.GetBitCount() == 8 )
8902         {
8903             aLine.append( "/ColorSpace/DeviceGray\n"
8904                           "/Decode [ 1 0 ]\n" );
8905         }
8906     }
8907 
8908     if( ! bMask && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2 && !m_bIsPDF_A1 )
8909     {
8910         if( bWriteMask )
8911         {
8912             nMaskObject = createObject();
8913             if( rObject.m_aBitmap.IsAlpha() && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
8914                 aLine.append( "/SMask " );
8915             else
8916                 aLine.append( "/Mask " );
8917             aLine.append( nMaskObject );
8918             aLine.append( " 0 R\n" );
8919         }
8920         else if( aTransparentColor != COL_TRANSPARENT )
8921         {
8922             aLine.append( "/Mask[ " );
8923             if( bTrueColor )
8924             {
8925                 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetRed()) );
8926                 aLine.append( ' ' );
8927                 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetRed()) );
8928                 aLine.append( ' ' );
8929                 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetGreen()) );
8930                 aLine.append( ' ' );
8931                 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetGreen()) );
8932                 aLine.append( ' ' );
8933                 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetBlue()) );
8934                 aLine.append( ' ' );
8935                 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetBlue()) );
8936             }
8937             else
8938             {
8939                 sal_Int32 nIndex = pAccess->GetBestPaletteIndex( BitmapColor( aTransparentColor ) );
8940                 aLine.append( nIndex );
8941             }
8942             aLine.append( " ]\n" );
8943         }
8944     }
8945     else if( m_bIsPDF_A1 && (bWriteMask || aTransparentColor != COL_TRANSPARENT) )
8946         m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
8947 
8948     aLine.append( ">>\n"
8949                   "stream\n" );
8950     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8951     sal_uInt64 nStartPos = 0;
8952     CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nStartPos)) );
8953 
8954     checkAndEnableStreamEncryption( rObject.m_nObject );
8955     if (!g_bDebugDisableCompression && nBitsPerComponent == 1)
8956     {
8957         writeG4Stream(pAccess.get());
8958     }
8959     else
8960     {
8961         beginCompression();
8962         if( ! bTrueColor || pAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb )
8963         {
8964             //With PDF bitmaps, each row is padded to a BYTE boundary (multiple of 8 bits).
8965             const int nScanLineBytes = ((pAccess->GetBitCount() * pAccess->Width()) + 7U) / 8U;
8966 
8967             for( long i = 0; i < pAccess->Height(); i++ )
8968             {
8969                 CHECK_RETURN( writeBuffer( pAccess->GetScanline( i ), nScanLineBytes ) );
8970             }
8971         }
8972         else
8973         {
8974             const int nScanLineBytes = pAccess->Width()*3;
8975             std::unique_ptr<sal_uInt8[]> xCol(new sal_uInt8[nScanLineBytes]);
8976             for( long y = 0; y < pAccess->Height(); y++ )
8977             {
8978                 for( long x = 0; x < pAccess->Width(); x++ )
8979                 {
8980                     BitmapColor aColor = pAccess->GetColor( y, x );
8981                     xCol[3*x+0] = aColor.GetRed();
8982                     xCol[3*x+1] = aColor.GetGreen();
8983                     xCol[3*x+2] = aColor.GetBlue();
8984                 }
8985                 CHECK_RETURN(writeBuffer(xCol.get(), nScanLineBytes));
8986             }
8987         }
8988         endCompression();
8989     }
8990     disableStreamEncryption();
8991 
8992     sal_uInt64 nEndPos = 0;
8993     CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nEndPos)) );
8994     aLine.setLength( 0 );
8995     aLine.append( "\nendstream\nendobj\n\n" );
8996     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8997     CHECK_RETURN( updateObject( nStreamLengthObject ) );
8998     aLine.setLength( 0 );
8999     aLine.append( nStreamLengthObject );
9000     aLine.append( " 0 obj\n" );
9001     aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
9002     aLine.append( "\nendobj\n\n" );
9003     CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
9004 
9005     if( nMaskObject )
9006     {
9007         BitmapEmit aEmit;
9008         aEmit.m_nObject             = nMaskObject;
9009         aEmit.m_aBitmap             = rObject.m_aBitmap;
9010         return writeBitmapObject( aEmit, true );
9011     }
9012 
9013     writeReferenceXObject(rObject.m_aReferenceXObject);
9014 
9015     return true;
9016 }
9017 
9018 void PDFWriterImpl::createEmbeddedFile(const Graphic& rGraphic, ReferenceXObjectEmit& rEmit, sal_Int32 nBitmapObject)
9019 {
9020     // The bitmap object is always a valid identifier, even if the graphic has
9021     // no pdf data.
9022     rEmit.m_nBitmapObject = nBitmapObject;
9023 
9024     if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getVectorGraphicDataType() != VectorGraphicDataType::Pdf)
9025         return;
9026 
9027     sal_uInt32 nLength = rGraphic.getVectorGraphicData()->getVectorGraphicDataArrayLength();
9028     auto const & rArray = rGraphic.getVectorGraphicData()->getVectorGraphicDataArray();
9029 
9030     auto pPDFData = std::make_shared<std::vector<sal_Int8>>(rArray.getConstArray(), rArray.getConstArray() + nLength);
9031 
9032     if (m_aContext.UseReferenceXObject)
9033     {
9034         // Store the original PDF data as an embedded file.
9035         m_aEmbeddedFiles.emplace_back();
9036         m_aEmbeddedFiles.back().m_nObject = createObject();
9037         m_aEmbeddedFiles.back().m_pData = pPDFData;
9038         rEmit.m_nEmbeddedObject = m_aEmbeddedFiles.back().m_nObject;
9039     }
9040     else
9041     {
9042         rEmit.m_nPDFPageIndex = rGraphic.getVectorGraphicData()->getPageIndex();
9043         rEmit.m_aPDFData = *pPDFData;
9044     }
9045 
9046     rEmit.m_nFormObject = createObject();
9047     rEmit.m_aPixelSize = rGraphic.GetPrefSize();
9048 }
9049 
9050 void PDFWriterImpl::drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const Size& rSizePixel, const tools::Rectangle& rTargetArea, const Bitmap& rMask, const Graphic& rGraphic )
9051 {
9052     MARK( "drawJPGBitmap" );
9053 
9054     OStringBuffer aLine( 80 );
9055     updateGraphicsState();
9056 
9057     // #i40055# sanity check
9058     if( ! (rTargetArea.GetWidth() && rTargetArea.GetHeight() ) )
9059         return;
9060     if( ! (rSizePixel.Width() && rSizePixel.Height()) )
9061         return;
9062 
9063     rDCTData.Seek( 0 );
9064     if( bIsTrueColor && m_aContext.ColorMode == PDFWriter::DrawGreyscale )
9065     {
9066         // need to convert to grayscale;
9067         // load stream to bitmap and draw the bitmap instead
9068         Graphic aGraphic;
9069         GraphicConverter::Import( rDCTData, aGraphic, ConvertDataFormat::JPG );
9070         if( !!rMask && rMask.GetSizePixel() == aGraphic.GetSizePixel() )
9071         {
9072             Bitmap aBmp( aGraphic.GetBitmapEx().GetBitmap() );
9073             BitmapEx aBmpEx( aBmp, rMask );
9074             drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aBmpEx );
9075         }
9076         else
9077             drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aGraphic.GetBitmapEx() );
9078         return;
9079     }
9080 
9081     std::unique_ptr<SvMemoryStream> pStream(new SvMemoryStream);
9082     pStream->WriteStream( rDCTData );
9083     pStream->Seek( STREAM_SEEK_TO_END );
9084 
9085     BitmapID aID;
9086     aID.m_aPixelSize    = rSizePixel;
9087     aID.m_nSize         = pStream->Tell();
9088     pStream->Seek( STREAM_SEEK_TO_BEGIN );
9089     aID.m_nChecksum     = vcl_get_checksum( 0, pStream->GetData(), aID.m_nSize );
9090     if( ! rMask.IsEmpty() )
9091         aID.m_nMaskChecksum = rMask.GetChecksum();
9092 
9093     std::vector< JPGEmit >::const_iterator it = std::find_if(m_aJPGs.begin(), m_aJPGs.end(),
9094                                              [&](const JPGEmit& arg) { return aID == arg.m_aID; });
9095     if( it == m_aJPGs.end() )
9096     {
9097         m_aJPGs.emplace( m_aJPGs.begin() );
9098         JPGEmit& rEmit = m_aJPGs.front();
9099         if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getVectorGraphicDataType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject)
9100             rEmit.m_nObject = createObject();
9101         rEmit.m_aID         = aID;
9102         rEmit.m_pStream = std::move( pStream );
9103         rEmit.m_bTrueColor  = bIsTrueColor;
9104         if( !! rMask && rMask.GetSizePixel() == rSizePixel )
9105             rEmit.m_aMask   = rMask;
9106         createEmbeddedFile(rGraphic, rEmit.m_aReferenceXObject, rEmit.m_nObject);
9107 
9108         it = m_aJPGs.begin();
9109     }
9110 
9111     aLine.append( "q " );
9112     sal_Int32 nCheckWidth = 0;
9113     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetWidth()), aLine, false, &nCheckWidth );
9114     aLine.append( " 0 0 " );
9115     sal_Int32 nCheckHeight = 0;
9116     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetHeight()), aLine, true, &nCheckHeight );
9117     aLine.append( ' ' );
9118     m_aPages.back().appendPoint( rTargetArea.BottomLeft(), aLine );
9119     aLine.append( " cm\n/Im" );
9120     sal_Int32 nObject = it->m_aReferenceXObject.getObject();
9121     aLine.append(nObject);
9122     aLine.append( " Do Q\n" );
9123     if( nCheckWidth == 0 || nCheckHeight == 0 )
9124     {
9125         // #i97512# avoid invalid current matrix
9126         aLine.setLength( 0 );
9127         aLine.append( "\n%jpeg image /Im" );
9128         aLine.append( it->m_nObject );
9129         aLine.append( " scaled to zero size, omitted\n" );
9130     }
9131     writeBuffer( aLine.getStr(), aLine.getLength() );
9132 
9133     OString aObjName = "Im" + OString::number(nObject);
9134     pushResource( ResourceKind::XObject, aObjName, nObject );
9135 
9136 }
9137 
9138 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEmit& rBitmap, const Color& rFillColor )
9139 {
9140     OStringBuffer aLine( 80 );
9141     updateGraphicsState();
9142 
9143     aLine.append( "q " );
9144     if( rFillColor != COL_TRANSPARENT )
9145     {
9146         appendNonStrokingColor( rFillColor, aLine );
9147         aLine.append( ' ' );
9148     }
9149     sal_Int32 nCheckWidth = 0;
9150     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Width()), aLine, false, &nCheckWidth );
9151     aLine.append( " 0 0 " );
9152     sal_Int32 nCheckHeight = 0;
9153     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Height()), aLine, true, &nCheckHeight );
9154     aLine.append( ' ' );
9155     m_aPages.back().appendPoint( rDestPoint + Point( 0, rDestSize.Height()-1 ), aLine );
9156     aLine.append( " cm\n/Im" );
9157     sal_Int32 nObject = rBitmap.m_aReferenceXObject.getObject();
9158     aLine.append(nObject);
9159     aLine.append( " Do Q\n" );
9160     if( nCheckWidth == 0 || nCheckHeight == 0 )
9161     {
9162         // #i97512# avoid invalid current matrix
9163         aLine.setLength( 0 );
9164         aLine.append( "\n%bitmap image /Im" );
9165         aLine.append( rBitmap.m_nObject );
9166         aLine.append( " scaled to zero size, omitted\n" );
9167     }
9168     writeBuffer( aLine.getStr(), aLine.getLength() );
9169 }
9170 
9171 const BitmapEmit& PDFWriterImpl::createBitmapEmit( const BitmapEx& i_rBitmap, const Graphic& rGraphic )
9172 {
9173     BitmapEx aBitmap( i_rBitmap );
9174     if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
9175     {
9176         BmpConversion eConv = BmpConversion::N8BitGreys;
9177         int nDepth = aBitmap.GetBitmap().GetBitCount();
9178         if( nDepth <= 4 )
9179             eConv = BmpConversion::N4BitGreys;
9180         if( nDepth > 1 )
9181             aBitmap.Convert( eConv );
9182     }
9183     BitmapID aID;
9184     aID.m_aPixelSize        = aBitmap.GetSizePixel();
9185     aID.m_nSize             = aBitmap.GetBitCount();
9186     aID.m_nChecksum         = aBitmap.GetBitmap().GetChecksum();
9187     aID.m_nMaskChecksum     = 0;
9188     if( aBitmap.IsAlpha() )
9189         aID.m_nMaskChecksum = aBitmap.GetAlpha().GetChecksum();
9190     else
9191     {
9192         Bitmap aMask = aBitmap.GetMask();
9193         if( ! aMask.IsEmpty() )
9194             aID.m_nMaskChecksum = aMask.GetChecksum();
9195     }
9196     std::list< BitmapEmit >::const_iterator it = std::find_if(m_aBitmaps.begin(), m_aBitmaps.end(),
9197                                              [&](const BitmapEmit& arg) { return aID == arg.m_aID; });
9198     if( it == m_aBitmaps.end() )
9199     {
9200         m_aBitmaps.push_front( BitmapEmit() );
9201         m_aBitmaps.front().m_aID        = aID;
9202         m_aBitmaps.front().m_aBitmap    = aBitmap;
9203         if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getVectorGraphicDataType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject)
9204             m_aBitmaps.front().m_nObject = createObject();
9205         createEmbeddedFile(rGraphic, m_aBitmaps.front().m_aReferenceXObject, m_aBitmaps.front().m_nObject);
9206         it = m_aBitmaps.begin();
9207     }
9208 
9209     sal_Int32 nObject = it->m_aReferenceXObject.getObject();
9210     OString aObjName = "Im" + OString::number(nObject);
9211     pushResource( ResourceKind::XObject, aObjName, nObject );
9212 
9213     return *it;
9214 }
9215 
9216 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap, const Graphic& rGraphic )
9217 {
9218     MARK( "drawBitmap (Bitmap)" );
9219 
9220     // #i40055# sanity check
9221     if( ! (rDestSize.Width() && rDestSize.Height()) )
9222         return;
9223 
9224     const BitmapEmit& rEmit = createBitmapEmit( BitmapEx( rBitmap ), rGraphic );
9225     drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
9226 }
9227 
9228 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEx& rBitmap )
9229 {
9230     MARK( "drawBitmap (BitmapEx)" );
9231 
9232     // #i40055# sanity check
9233     if( ! (rDestSize.Width() && rDestSize.Height()) )
9234         return;
9235 
9236     const BitmapEmit& rEmit = createBitmapEmit( rBitmap, Graphic() );
9237     drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
9238 }
9239 
9240 sal_Int32 PDFWriterImpl::createGradient( const Gradient& rGradient, const Size& rSize )
9241 {
9242     Size aPtSize( lcl_convert( m_aGraphicsStack.front().m_aMapMode,
9243                                MapMode( MapUnit::MapPoint ),
9244                                this,
9245                                rSize ) );
9246     // check if we already have this gradient
9247     // rounding to point will generally lose some pixels
9248     // round up to point boundary
9249     aPtSize.AdjustWidth( 1 );
9250     aPtSize.AdjustHeight( 1 );
9251     std::list< GradientEmit >::const_iterator it = std::find_if(m_aGradients.begin(), m_aGradients.end(),
9252                                              [&](const GradientEmit& arg) { return ((rGradient == arg.m_aGradient) && (aPtSize == arg.m_aSize) ); });
9253 
9254     if( it == m_aGradients.end() )
9255     {
9256         m_aGradients.push_front( GradientEmit() );
9257         m_aGradients.front().m_aGradient    = rGradient;
9258         m_aGradients.front().m_nObject      = createObject();
9259         m_aGradients.front().m_aSize        = aPtSize;
9260         it = m_aGradients.begin();
9261     }
9262 
9263     OStringBuffer aObjName( 16 );
9264     aObjName.append( 'P' );
9265     aObjName.append( it->m_nObject );
9266     pushResource( ResourceKind::Shading, aObjName.makeStringAndClear(), it->m_nObject );
9267 
9268     return it->m_nObject;
9269 }
9270 
9271 void PDFWriterImpl::drawGradient( const tools::Rectangle& rRect, const Gradient& rGradient )
9272 {
9273     MARK( "drawGradient (Rectangle)" );
9274 
9275     if( m_aContext.Version == PDFWriter::PDFVersion::PDF_1_2 )
9276     {
9277         drawRectangle( rRect );
9278         return;
9279     }
9280 
9281     sal_Int32 nGradient = createGradient( rGradient, rRect.GetSize() );
9282 
9283     Point aTranslate( rRect.BottomLeft() );
9284     aTranslate += Point( 0, 1 );
9285 
9286     updateGraphicsState();
9287 
9288     OStringBuffer aLine( 80 );
9289     aLine.append( "q 1 0 0 1 " );
9290     m_aPages.back().appendPoint( aTranslate, aLine );
9291     aLine.append( " cm " );
9292     // if a stroke is appended reset the clip region before stroke
9293     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
9294         aLine.append( "q " );
9295     aLine.append( "0 0 " );
9296     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
9297     aLine.append( ' ' );
9298     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
9299     aLine.append( " re W n\n" );
9300 
9301     aLine.append( "/P" );
9302     aLine.append( nGradient );
9303     aLine.append( " sh " );
9304     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
9305     {
9306         aLine.append( "Q 0 0 " );
9307         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
9308         aLine.append( ' ' );
9309         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
9310         aLine.append( " re S " );
9311     }
9312     aLine.append( "Q\n" );
9313     writeBuffer( aLine.getStr(), aLine.getLength() );
9314 }
9315 
9316 void PDFWriterImpl::drawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch )
9317 {
9318     MARK( "drawHatch" );
9319 
9320     updateGraphicsState();
9321 
9322     if( rPolyPoly.Count() )
9323     {
9324         tools::PolyPolygon     aPolyPoly( rPolyPoly );
9325 
9326         aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME );
9327         push( PushFlags::LINECOLOR );
9328         setLineColor( rHatch.GetColor() );
9329         DrawHatch( aPolyPoly, rHatch, false );
9330         pop();
9331     }
9332 }
9333 
9334 void PDFWriterImpl::drawWallpaper( const tools::Rectangle& rRect, const Wallpaper& rWall )
9335 {
9336     MARK( "drawWallpaper" );
9337 
9338     bool bDrawColor         = false;
9339     bool bDrawGradient      = false;
9340     bool bDrawBitmap        = false;
9341 
9342     BitmapEx aBitmap;
9343     Point aBmpPos = rRect.TopLeft();
9344     Size aBmpSize;
9345     if( rWall.IsBitmap() )
9346     {
9347         aBitmap = rWall.GetBitmap();
9348         aBmpSize = lcl_convert( aBitmap.GetPrefMapMode(),
9349                                 getMapMode(),
9350                                 this,
9351                                 aBitmap.GetPrefSize() );
9352         tools::Rectangle aRect( rRect );
9353         if( rWall.IsRect() )
9354         {
9355             aRect = rWall.GetRect();
9356             aBmpPos = aRect.TopLeft();
9357             aBmpSize = aRect.GetSize();
9358         }
9359         if( rWall.GetStyle() != WallpaperStyle::Scale )
9360         {
9361             if( rWall.GetStyle() != WallpaperStyle::Tile )
9362             {
9363                 bDrawBitmap     = true;
9364                 if( rWall.IsGradient() )
9365                     bDrawGradient = true;
9366                 else
9367                     bDrawColor = true;
9368                 switch( rWall.GetStyle() )
9369                 {
9370                     case WallpaperStyle::TopLeft:
9371                         break;
9372                     case WallpaperStyle::Top:
9373                         aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
9374                         break;
9375                     case WallpaperStyle::Left:
9376                         aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
9377                         break;
9378                     case WallpaperStyle::TopRight:
9379                         aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
9380                         break;
9381                     case WallpaperStyle::Center:
9382                         aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
9383                         aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
9384                         break;
9385                     case WallpaperStyle::Right:
9386                         aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
9387                         aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
9388                         break;
9389                     case WallpaperStyle::BottomLeft:
9390                         aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
9391                         break;
9392                     case WallpaperStyle::Bottom:
9393                         aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
9394                         aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
9395                         break;
9396                     case WallpaperStyle::BottomRight:
9397                         aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
9398                         aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
9399                         break;
9400                     default: ;
9401                 }
9402             }
9403             else
9404             {
9405                 // push the bitmap
9406                 const BitmapEmit& rEmit = createBitmapEmit( aBitmap, Graphic() );
9407 
9408                 // convert to page coordinates; this needs to be done here
9409                 // since the emit does not know the page anymore
9410                 tools::Rectangle aConvertRect( aBmpPos, aBmpSize );
9411                 m_aPages.back().convertRect( aConvertRect );
9412 
9413                 OString aImageName = "Im" + OString::number( rEmit.m_nObject );
9414 
9415                 // push the pattern
9416                 OStringBuffer aTilingStream( 32 );
9417                 appendFixedInt( aConvertRect.GetWidth(), aTilingStream );
9418                 aTilingStream.append( " 0 0 " );
9419                 appendFixedInt( aConvertRect.GetHeight(), aTilingStream );
9420                 aTilingStream.append( " 0 0 cm\n/" );
9421                 aTilingStream.append( aImageName );
9422                 aTilingStream.append( " Do\n" );
9423 
9424                 m_aTilings.emplace_back( );
9425                 m_aTilings.back().m_nObject         = createObject();
9426                 m_aTilings.back().m_aRectangle      = tools::Rectangle( Point( 0, 0 ), aConvertRect.GetSize() );
9427                 m_aTilings.back().m_pTilingStream.reset(new SvMemoryStream());
9428                 m_aTilings.back().m_pTilingStream->WriteBytes(
9429                     aTilingStream.getStr(), aTilingStream.getLength() );
9430                 // phase the tiling so wallpaper begins on upper left
9431                 if ((aConvertRect.GetWidth() == 0) || (aConvertRect.GetHeight() == 0))
9432                     throw o3tl::divide_by_zero();
9433                 m_aTilings.back().m_aTransform.matrix[2] = double(aConvertRect.Left() % aConvertRect.GetWidth()) / fDivisor;
9434                 m_aTilings.back().m_aTransform.matrix[5] = double(aConvertRect.Top() % aConvertRect.GetHeight()) / fDivisor;
9435                 m_aTilings.back().m_aResources.m_aXObjects[aImageName] = rEmit.m_nObject;
9436 
9437                 updateGraphicsState();
9438 
9439                 OStringBuffer aObjName( 16 );
9440                 aObjName.append( 'P' );
9441                 aObjName.append( m_aTilings.back().m_nObject );
9442                 OString aPatternName( aObjName.makeStringAndClear() );
9443                 pushResource( ResourceKind::Pattern, aPatternName, m_aTilings.back().m_nObject );
9444 
9445                 // fill a rRect with the pattern
9446                 OStringBuffer aLine( 100 );
9447                 aLine.append( "q /Pattern cs /" );
9448                 aLine.append( aPatternName );
9449                 aLine.append( " scn " );
9450                 m_aPages.back().appendRect( rRect, aLine );
9451                 aLine.append( " f Q\n" );
9452                 writeBuffer( aLine.getStr(), aLine.getLength() );
9453             }
9454         }
9455         else
9456         {
9457             aBmpPos     = aRect.TopLeft();
9458             aBmpSize    = aRect.GetSize();
9459             bDrawBitmap = true;
9460         }
9461 
9462         if( aBitmap.IsTransparent() )
9463         {
9464             if( rWall.IsGradient() )
9465                 bDrawGradient = true;
9466             else
9467                 bDrawColor = true;
9468         }
9469     }
9470     else if( rWall.IsGradient() )
9471         bDrawGradient = true;
9472     else
9473         bDrawColor = true;
9474 
9475     if( bDrawGradient )
9476     {
9477         drawGradient( rRect, rWall.GetGradient() );
9478     }
9479     if( bDrawColor )
9480     {
9481         Color aOldLineColor = m_aGraphicsStack.front().m_aLineColor;
9482         Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
9483         setLineColor( COL_TRANSPARENT );
9484         setFillColor( rWall.GetColor() );
9485         drawRectangle( rRect );
9486         setLineColor( aOldLineColor );
9487         setFillColor( aOldFillColor );
9488     }
9489     if( bDrawBitmap )
9490     {
9491         // set temporary clip region since aBmpPos and aBmpSize
9492         // may be outside rRect
9493         OStringBuffer aLine( 20 );
9494         aLine.append( "q " );
9495         m_aPages.back().appendRect( rRect, aLine );
9496         aLine.append( " W n\n" );
9497         writeBuffer( aLine.getStr(), aLine.getLength() );
9498         drawBitmap( aBmpPos, aBmpSize, aBitmap );
9499         writeBuffer( "Q\n", 2 );
9500     }
9501 }
9502 
9503 void PDFWriterImpl::updateGraphicsState(Mode const mode)
9504 {
9505     OStringBuffer aLine( 256 );
9506     GraphicsState& rNewState = m_aGraphicsStack.front();
9507     // first set clip region since it might invalidate everything else
9508 
9509     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::ClipRegion )
9510     {
9511         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::ClipRegion;
9512 
9513         if( m_aCurrentPDFState.m_bClipRegion != rNewState.m_bClipRegion ||
9514             ( rNewState.m_bClipRegion && m_aCurrentPDFState.m_aClipRegion != rNewState.m_aClipRegion ) )
9515         {
9516             if( m_aCurrentPDFState.m_bClipRegion )
9517             {
9518                 aLine.append( "Q " );
9519                 // invalidate everything but the clip region
9520                 m_aCurrentPDFState = GraphicsState();
9521                 rNewState.m_nUpdateFlags = ~GraphicsStateUpdateFlags::ClipRegion;
9522             }
9523             if( rNewState.m_bClipRegion )
9524             {
9525                 // clip region is always stored in private PDF mapmode
9526                 MapMode aNewMapMode = rNewState.m_aMapMode;
9527                 rNewState.m_aMapMode = m_aMapMode;
9528                 SetMapMode( rNewState.m_aMapMode );
9529                 m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
9530 
9531                 aLine.append("q ");
9532                 if ( rNewState.m_aClipRegion.count() )
9533                 {
9534                     m_aPages.back().appendPolyPolygon( rNewState.m_aClipRegion, aLine );
9535                 }
9536                 else
9537                 {
9538                     // tdf#130150 Need to revert tdf#99680, that breaks the
9539                     // rule that an set but empty clip-region clips everything
9540                     // aka draws nothing -> nothing is in an empty clip-region
9541                     aLine.append( "0 0 m h " ); // NULL clip, i.e. nothing visible
9542                 }
9543                 aLine.append( "W* n\n" );
9544 
9545                 rNewState.m_aMapMode = aNewMapMode;
9546                 SetMapMode( rNewState.m_aMapMode );
9547                 m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
9548             }
9549         }
9550     }
9551 
9552     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::MapMode )
9553     {
9554         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::MapMode;
9555         SetMapMode( rNewState.m_aMapMode );
9556     }
9557 
9558     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::Font )
9559     {
9560         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::Font;
9561         SetFont( rNewState.m_aFont );
9562     }
9563 
9564     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LayoutMode )
9565     {
9566         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LayoutMode;
9567         SetLayoutMode( rNewState.m_nLayoutMode );
9568     }
9569 
9570     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::DigitLanguage )
9571     {
9572         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::DigitLanguage;
9573         SetDigitLanguage( rNewState.m_aDigitLanguage );
9574     }
9575 
9576     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LineColor )
9577     {
9578         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LineColor;
9579         if( m_aCurrentPDFState.m_aLineColor != rNewState.m_aLineColor &&
9580             rNewState.m_aLineColor != COL_TRANSPARENT )
9581         {
9582             appendStrokingColor( rNewState.m_aLineColor, aLine );
9583             aLine.append( "\n" );
9584         }
9585     }
9586 
9587     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::FillColor )
9588     {
9589         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::FillColor;
9590         if( m_aCurrentPDFState.m_aFillColor != rNewState.m_aFillColor &&
9591             rNewState.m_aFillColor != COL_TRANSPARENT )
9592         {
9593             appendNonStrokingColor( rNewState.m_aFillColor, aLine );
9594             aLine.append( "\n" );
9595         }
9596     }
9597 
9598     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::TransparentPercent )
9599     {
9600         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::TransparentPercent;
9601         if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
9602         {
9603             // TODO: switch extended graphicsstate
9604         }
9605     }
9606 
9607     // everything is up to date now
9608     m_aCurrentPDFState = m_aGraphicsStack.front();
9609     if ((mode != Mode::NOWRITE) &&  !aLine.isEmpty())
9610         writeBuffer( aLine.getStr(), aLine.getLength() );
9611 }
9612 
9613 /* #i47544# imitate OutputDevice behaviour:
9614 *  if a font with a nontransparent color is set, it overwrites the current
9615 *  text color. OTOH setting the text color will overwrite the color of the font.
9616 */
9617 void PDFWriterImpl::setFont( const vcl::Font& rFont )
9618 {
9619     Color aColor = rFont.GetColor();
9620     if( aColor == COL_TRANSPARENT )
9621         aColor = m_aGraphicsStack.front().m_aFont.GetColor();
9622     m_aGraphicsStack.front().m_aFont = rFont;
9623     m_aGraphicsStack.front().m_aFont.SetColor( aColor );
9624     m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font;
9625 }
9626 
9627 void PDFWriterImpl::push( PushFlags nFlags )
9628 {
9629     OSL_ENSURE( !m_aGraphicsStack.empty(), "invalid graphics stack" );
9630     m_aGraphicsStack.push_front( m_aGraphicsStack.front() );
9631     m_aGraphicsStack.front().m_nFlags = nFlags;
9632 }
9633 
9634 void PDFWriterImpl::pop()
9635 {
9636     OSL_ENSURE( m_aGraphicsStack.size() > 1, "pop without push" );
9637     if( m_aGraphicsStack.size() < 2 )
9638         return;
9639 
9640     GraphicsState aState = m_aGraphicsStack.front();
9641     m_aGraphicsStack.pop_front();
9642     GraphicsState& rOld = m_aGraphicsStack.front();
9643 
9644     // move those parameters back that were not pushed
9645     // in the first place
9646     if( ! (aState.m_nFlags & PushFlags::LINECOLOR) )
9647         setLineColor( aState.m_aLineColor );
9648     if( ! (aState.m_nFlags & PushFlags::FILLCOLOR) )
9649         setFillColor( aState.m_aFillColor );
9650     if( ! (aState.m_nFlags & PushFlags::FONT) )
9651         setFont( aState.m_aFont );
9652     if( ! (aState.m_nFlags & PushFlags::TEXTCOLOR) )
9653         setTextColor( aState.m_aFont.GetColor() );
9654     if( ! (aState.m_nFlags & PushFlags::MAPMODE) )
9655         setMapMode( aState.m_aMapMode );
9656     if( ! (aState.m_nFlags & PushFlags::CLIPREGION) )
9657     {
9658         // do not use setClipRegion here
9659         // it would convert again assuming the current mapmode
9660         rOld.m_aClipRegion = aState.m_aClipRegion;
9661         rOld.m_bClipRegion = aState.m_bClipRegion;
9662     }
9663     if( ! (aState.m_nFlags & PushFlags::TEXTLINECOLOR ) )
9664         setTextLineColor( aState.m_aTextLineColor );
9665     if( ! (aState.m_nFlags & PushFlags::OVERLINECOLOR ) )
9666         setOverlineColor( aState.m_aOverlineColor );
9667     if( ! (aState.m_nFlags & PushFlags::TEXTALIGN ) )
9668         setTextAlign( aState.m_aFont.GetAlignment() );
9669     if( ! (aState.m_nFlags & PushFlags::TEXTFILLCOLOR) )
9670         setTextFillColor( aState.m_aFont.GetFillColor() );
9671     if( ! (aState.m_nFlags & PushFlags::REFPOINT) )
9672     {
9673         // what ?
9674     }
9675     // invalidate graphics state
9676     m_aGraphicsStack.front().m_nUpdateFlags = GraphicsStateUpdateFlags::All;
9677 }
9678 
9679 void PDFWriterImpl::setMapMode( const MapMode& rMapMode )
9680 {
9681     m_aGraphicsStack.front().m_aMapMode = rMapMode;
9682     SetMapMode( rMapMode );
9683     m_aCurrentPDFState.m_aMapMode = rMapMode;
9684 }
9685 
9686 void PDFWriterImpl::setClipRegion( const basegfx::B2DPolyPolygon& rRegion )
9687 {
9688     // tdf#130150 improve coordinate manipulations to double precision transformations
9689     const basegfx::B2DHomMatrix aCurrentTransform(
9690         GetInverseViewTransformation(m_aMapMode) * GetViewTransformation(m_aGraphicsStack.front().m_aMapMode));
9691     basegfx::B2DPolyPolygon aRegion(rRegion);
9692 
9693     aRegion.transform(aCurrentTransform);
9694     m_aGraphicsStack.front().m_aClipRegion = aRegion;
9695     m_aGraphicsStack.front().m_bClipRegion = true;
9696     m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
9697 }
9698 
9699 void PDFWriterImpl::moveClipRegion( sal_Int32 nX, sal_Int32 nY )
9700 {
9701     if( m_aGraphicsStack.front().m_bClipRegion && m_aGraphicsStack.front().m_aClipRegion.count() )
9702     {
9703         // tdf#130150 improve coordinate manipulations to double precision transformations
9704         basegfx::B2DHomMatrix aConvertA;
9705 
9706         if(MapUnit::MapPixel == m_aGraphicsStack.front().m_aMapMode.GetMapUnit())
9707         {
9708             aConvertA = GetInverseViewTransformation(m_aMapMode);
9709         }
9710         else
9711         {
9712             aConvertA = LogicToLogic(m_aGraphicsStack.front().m_aMapMode, m_aMapMode);
9713         }
9714 
9715         basegfx::B2DPoint aB2DPointA(nX, nY);
9716         basegfx::B2DPoint aB2DPointB(0.0, 0.0);
9717         aB2DPointA *= aConvertA;
9718         aB2DPointB *= aConvertA;
9719         aB2DPointA -= aB2DPointB;
9720         basegfx::B2DHomMatrix aMat;
9721 
9722         aMat.translate(aB2DPointA.getX(), aB2DPointA.getY());
9723         m_aGraphicsStack.front().m_aClipRegion.transform( aMat );
9724         m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
9725     }
9726 }
9727 
9728 void PDFWriterImpl::intersectClipRegion( const tools::Rectangle& rRect )
9729 {
9730     basegfx::B2DPolyPolygon aRect( basegfx::utils::createPolygonFromRect(
9731                                     vcl::unotools::b2DRectangleFromRectangle(rRect) ) );
9732     intersectClipRegion( aRect );
9733 }
9734 
9735 void PDFWriterImpl::intersectClipRegion( const basegfx::B2DPolyPolygon& rRegion )
9736 {
9737     // tdf#130150 improve coordinate manipulations to double precision transformations
9738     const basegfx::B2DHomMatrix aCurrentTransform(
9739         GetInverseViewTransformation(m_aMapMode) * GetViewTransformation(m_aGraphicsStack.front().m_aMapMode));
9740     basegfx::B2DPolyPolygon aRegion(rRegion);
9741 
9742     aRegion.transform(aCurrentTransform);
9743     m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
9744 
9745     if( m_aGraphicsStack.front().m_bClipRegion )
9746     {
9747         basegfx::B2DPolyPolygon aOld( basegfx::utils::prepareForPolygonOperation( m_aGraphicsStack.front().m_aClipRegion ) );
9748         aRegion = basegfx::utils::prepareForPolygonOperation( aRegion );
9749         m_aGraphicsStack.front().m_aClipRegion = basegfx::utils::solvePolygonOperationAnd( aOld, aRegion );
9750     }
9751     else
9752     {
9753         m_aGraphicsStack.front().m_aClipRegion = aRegion;
9754         m_aGraphicsStack.front().m_bClipRegion = true;
9755     }
9756 }
9757 
9758 void PDFWriterImpl::createNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr )
9759 {
9760     if (nPageNr < 0)
9761         nPageNr = m_nCurrentPage;
9762 
9763     if (nPageNr < 0 || nPageNr >= sal_Int32(m_aPages.size()))
9764         return;
9765 
9766     m_aNotes.emplace_back();
9767     auto & rNoteEntry = m_aNotes.back();
9768     rNoteEntry.m_nObject = createObject();
9769     rNoteEntry.m_aPopUpAnnotation.m_nObject = createObject();
9770     rNoteEntry.m_aPopUpAnnotation.m_nParentObject = rNoteEntry.m_nObject;
9771     rNoteEntry.m_aContents = rNote;
9772     rNoteEntry.m_aRect = rRect;
9773     // convert to default user space now, since the mapmode may change
9774     m_aPages[nPageNr].convertRect(rNoteEntry.m_aRect);
9775 
9776     // insert note to page's annotation list
9777     m_aPages[nPageNr].m_aAnnotations.push_back(rNoteEntry.m_nObject);
9778     m_aPages[nPageNr].m_aAnnotations.push_back(rNoteEntry.m_aPopUpAnnotation.m_nObject);
9779 }
9780 
9781 sal_Int32 PDFWriterImpl::createLink( const tools::Rectangle& rRect, sal_Int32 nPageNr )
9782 {
9783     if( nPageNr < 0 )
9784         nPageNr = m_nCurrentPage;
9785 
9786     if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
9787         return -1;
9788 
9789     sal_Int32 nRet = m_aLinks.size();
9790 
9791     m_aLinks.emplace_back( );
9792     m_aLinks.back().m_nObject   = createObject();
9793     m_aLinks.back().m_nPage     = nPageNr;
9794     m_aLinks.back().m_aRect     = rRect;
9795     // convert to default user space now, since the mapmode may change
9796     m_aPages[nPageNr].convertRect( m_aLinks.back().m_aRect );
9797 
9798     // insert link to page's annotation list
9799     m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aLinks.back().m_nObject );
9800 
9801     return nRet;
9802 }
9803 
9804 sal_Int32 PDFWriterImpl::createScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr)
9805 {
9806     if (nPageNr < 0)
9807         nPageNr = m_nCurrentPage;
9808 
9809     if (nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()))
9810         return -1;
9811 
9812     sal_Int32 nRet = m_aScreens.size();
9813 
9814     m_aScreens.emplace_back();
9815     m_aScreens.back().m_nObject = createObject();
9816     m_aScreens.back().m_nPage = nPageNr;
9817     m_aScreens.back().m_aRect = rRect;
9818     // Convert to default user space now, since the mapmode may change.
9819     m_aPages[nPageNr].convertRect(m_aScreens.back().m_aRect);
9820 
9821     // Insert link to page's annotation list.
9822     m_aPages[nPageNr].m_aAnnotations.push_back(m_aScreens.back().m_nObject);
9823 
9824     return nRet;
9825 }
9826 
9827 sal_Int32 PDFWriterImpl::createNamedDest( const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
9828 {
9829     if( nPageNr < 0 )
9830         nPageNr = m_nCurrentPage;
9831 
9832     if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
9833         return -1;
9834 
9835     sal_Int32 nRet = m_aNamedDests.size();
9836 
9837     m_aNamedDests.emplace_back( );
9838     m_aNamedDests.back().m_aDestName = sDestName;
9839     m_aNamedDests.back().m_nPage = nPageNr;
9840     m_aNamedDests.back().m_eType = eType;
9841     m_aNamedDests.back().m_aRect = rRect;
9842     // convert to default user space now, since the mapmode may change
9843     m_aPages[nPageNr].convertRect( m_aNamedDests.back().m_aRect );
9844 
9845     return nRet;
9846 }
9847 
9848 sal_Int32 PDFWriterImpl::createDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
9849 {
9850     if( nPageNr < 0 )
9851         nPageNr = m_nCurrentPage;
9852 
9853     if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
9854         return -1;
9855 
9856     sal_Int32 nRet = m_aDests.size();
9857 
9858     m_aDests.emplace_back( );
9859     m_aDests.back().m_nPage = nPageNr;
9860     m_aDests.back().m_eType = eType;
9861     m_aDests.back().m_aRect = rRect;
9862     // convert to default user space now, since the mapmode may change
9863     m_aPages[nPageNr].convertRect( m_aDests.back().m_aRect );
9864 
9865     return nRet;
9866 }
9867 
9868 sal_Int32 PDFWriterImpl::registerDestReference( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
9869 {
9870     m_aDestinationIdTranslation[ nDestId ] = createDest( rRect, nPageNr, eType );
9871     return m_aDestinationIdTranslation[ nDestId ];
9872 }
9873 
9874 void PDFWriterImpl::setLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
9875 {
9876     if( nLinkId < 0 || nLinkId >= static_cast<sal_Int32>(m_aLinks.size()) )
9877         return;
9878     if( nDestId < 0 || nDestId >= static_cast<sal_Int32>(m_aDests.size()) )
9879         return;
9880 
9881     m_aLinks[ nLinkId ].m_nDest = nDestId;
9882 }
9883 
9884 void PDFWriterImpl::setLinkURL( sal_Int32 nLinkId, const OUString& rURL )
9885 {
9886     if( nLinkId < 0 || nLinkId >= static_cast<sal_Int32>(m_aLinks.size()) )
9887         return;
9888 
9889     m_aLinks[ nLinkId ].m_nDest = -1;
9890 
9891     using namespace ::com::sun::star;
9892 
9893     if (!m_xTrans.is())
9894     {
9895         uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
9896         m_xTrans = util::URLTransformer::create(xContext);
9897     }
9898 
9899     util::URL aURL;
9900     aURL.Complete = rURL;
9901 
9902     m_xTrans->parseStrict( aURL );
9903 
9904     m_aLinks[ nLinkId ].m_aURL  = aURL.Complete;
9905 }
9906 
9907 void PDFWriterImpl::setScreenURL(sal_Int32 nScreenId, const OUString& rURL)
9908 {
9909     if (nScreenId < 0 || nScreenId >= static_cast<sal_Int32>(m_aScreens.size()))
9910         return;
9911 
9912     m_aScreens[nScreenId].m_aURL = rURL;
9913 }
9914 
9915 void PDFWriterImpl::setScreenStream(sal_Int32 nScreenId, const OUString& rURL)
9916 {
9917     if (nScreenId < 0 || nScreenId >= static_cast<sal_Int32>(m_aScreens.size()))
9918         return;
9919 
9920     m_aScreens[nScreenId].m_aTempFileURL = rURL;
9921     m_aScreens[nScreenId].m_nTempFileObject = createObject();
9922 }
9923 
9924 void PDFWriterImpl::setLinkPropertyId( sal_Int32 nLinkId, sal_Int32 nPropertyId )
9925 {
9926     m_aLinkPropertyMap[ nPropertyId ] = nLinkId;
9927 }
9928 
9929 sal_Int32 PDFWriterImpl::createOutlineItem( sal_Int32 nParent, const OUString& rText, sal_Int32 nDestID )
9930 {
9931     // create new item
9932     sal_Int32 nNewItem = m_aOutline.size();
9933     m_aOutline.emplace_back( );
9934 
9935     // set item attributes
9936     setOutlineItemParent( nNewItem, nParent );
9937     setOutlineItemText( nNewItem, rText );
9938     setOutlineItemDest( nNewItem, nDestID );
9939 
9940     return nNewItem;
9941 }
9942 
9943 void PDFWriterImpl::setOutlineItemParent( sal_Int32 nItem, sal_Int32 nNewParent )
9944 {
9945     if( nItem < 1 || nItem >= static_cast<sal_Int32>(m_aOutline.size()) )
9946         return;
9947 
9948     if( nNewParent < 0 || nNewParent >= static_cast<sal_Int32>(m_aOutline.size()) || nNewParent == nItem )
9949     {
9950         nNewParent = 0;
9951     }
9952     // insert item to new parent's list of children
9953     m_aOutline[ nNewParent ].m_aChildren.push_back( nItem );
9954 }
9955 
9956 void PDFWriterImpl::setOutlineItemText( sal_Int32 nItem, const OUString& rText )
9957 {
9958     if( nItem < 1 || nItem >= static_cast<sal_Int32>(m_aOutline.size()) )
9959         return;
9960 
9961     m_aOutline[ nItem ].m_aTitle = psp::WhitespaceToSpace( rText );
9962 }
9963 
9964 void PDFWriterImpl::setOutlineItemDest( sal_Int32 nItem, sal_Int32 nDestID )
9965 {
9966     if( nItem < 1 || nItem >= static_cast<sal_Int32>(m_aOutline.size()) ) // item does not exist
9967         return;
9968     if( nDestID < 0 || nDestID >= static_cast<sal_Int32>(m_aDests.size()) ) // dest does not exist
9969         return;
9970     m_aOutline[nItem].m_nDestID = nDestID;
9971 }
9972 
9973 const char* PDFWriterImpl::getStructureTag( PDFWriter::StructElement eType )
9974 {
9975     static std::map< PDFWriter::StructElement, const char* > aTagStrings;
9976     if( aTagStrings.empty() )
9977     {
9978         aTagStrings[ PDFWriter::NonStructElement] = "NonStruct";
9979         aTagStrings[ PDFWriter::Document ]      = "Document";
9980         aTagStrings[ PDFWriter::Part ]          = "Part";
9981         aTagStrings[ PDFWriter::Article ]       = "Art";
9982         aTagStrings[ PDFWriter::Section ]       = "Sect";
9983         aTagStrings[ PDFWriter::Division ]      = "Div";
9984         aTagStrings[ PDFWriter::BlockQuote ]    = "BlockQuote";
9985         aTagStrings[ PDFWriter::Caption ]       = "Caption";
9986         aTagStrings[ PDFWriter::TOC ]           = "TOC";
9987         aTagStrings[ PDFWriter::TOCI ]          = "TOCI";
9988         aTagStrings[ PDFWriter::Index ]         = "Index";
9989         aTagStrings[ PDFWriter::Paragraph ]     = "P";
9990         aTagStrings[ PDFWriter::Heading ]       = "H";
9991         aTagStrings[ PDFWriter::H1 ]            = "H1";
9992         aTagStrings[ PDFWriter::H2 ]            = "H2";
9993         aTagStrings[ PDFWriter::H3 ]            = "H3";
9994         aTagStrings[ PDFWriter::H4 ]            = "H4";
9995         aTagStrings[ PDFWriter::H5 ]            = "H5";
9996         aTagStrings[ PDFWriter::H6 ]            = "H6";
9997         aTagStrings[ PDFWriter::List ]          = "L";
9998         aTagStrings[ PDFWriter::ListItem ]      = "LI";
9999         aTagStrings[ PDFWriter::LILabel ]       = "Lbl";
10000         aTagStrings[ PDFWriter::LIBody ]        = "LBody";
10001         aTagStrings[ PDFWriter::Table ]         = "Table";
10002         aTagStrings[ PDFWriter::TableRow ]      = "TR";
10003         aTagStrings[ PDFWriter::TableHeader ]   = "TH";
10004         aTagStrings[ PDFWriter::TableData ]     = "TD";
10005         aTagStrings[ PDFWriter::Span ]          = "Span";
10006         aTagStrings[ PDFWriter::Quote ]         = "Quote";
10007         aTagStrings[ PDFWriter::Note ]          = "Note";
10008         aTagStrings[ PDFWriter::Reference ]     = "Reference";
10009         aTagStrings[ PDFWriter::BibEntry ]      = "BibEntry";
10010         aTagStrings[ PDFWriter::Code ]          = "Code";
10011         aTagStrings[ PDFWriter::Link ]          = "Link";
10012         aTagStrings[ PDFWriter::Figure ]        = "Figure";
10013         aTagStrings[ PDFWriter::Formula ]       = "Formula";
10014         aTagStrings[ PDFWriter::Form ]          = "Form";
10015     }
10016 
10017     std::map< PDFWriter::StructElement, const char* >::const_iterator it = aTagStrings.find( eType );
10018 
10019     return it != aTagStrings.end() ? it->second : "Div";
10020 }
10021 
10022 void PDFWriterImpl::addRoleMap(OString aAlias, PDFWriter::StructElement eType)
10023 {
10024     OString aTag = getStructureTag(eType);
10025     // For PDF/UA it's not allowed to map an alias with the same name.
10026     // Not sure if this allowed, necessary or recommended otherwise, so
10027     // only enable filtering when PDF/UA is enabled.
10028     if (!m_bIsPDF_UA || aAlias != aTag)
10029         m_aRoleMap[aAlias] = aTag;
10030 }
10031 
10032 void PDFWriterImpl::beginStructureElementMCSeq()
10033 {
10034     if( m_bEmitStructure &&
10035         m_nCurrentStructElement > 0 && // StructTreeRoot
10036         ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
10037         )
10038     {
10039         PDFStructureElement& rEle = m_aStructure[ m_nCurrentStructElement ];
10040         OStringBuffer aLine( 128 );
10041         sal_Int32 nMCID = m_aPages[ m_nCurrentPage ].m_aMCIDParents.size();
10042         aLine.append( "/" );
10043         if( !rEle.m_aAlias.isEmpty() )
10044             aLine.append( rEle.m_aAlias );
10045         else
10046             aLine.append( getStructureTag( rEle.m_eType ) );
10047         aLine.append( "<</MCID " );
10048         aLine.append( nMCID );
10049         aLine.append( ">>BDC\n" );
10050         writeBuffer( aLine.getStr(), aLine.getLength() );
10051 
10052         // update the element's content list
10053         SAL_INFO("vcl.pdfwriter", "beginning marked content id " << nMCID << " on page object "
10054                  << m_aPages[ m_nCurrentPage ].m_nPageObject << ", structure first page = "
10055                  << rEle.m_nFirstPageObject);
10056         rEle.m_aKids.emplace_back( nMCID, m_aPages[m_nCurrentPage].m_nPageObject );
10057         // update the page's mcid parent list
10058         m_aPages[ m_nCurrentPage ].m_aMCIDParents.push_back( rEle.m_nObject );
10059         // mark element MC sequence as open
10060         rEle.m_bOpenMCSeq = true;
10061     }
10062     // handle artifacts
10063     else if( ! m_bEmitStructure && m_aContext.Tagged &&
10064                m_nCurrentStructElement > 0 &&
10065                m_aStructure[ m_nCurrentStructElement ].m_eType == PDFWriter::NonStructElement &&
10066              ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
10067              )
10068     {
10069         OString aLine = "/Artifact BMC\n";
10070         writeBuffer( aLine.getStr(), aLine.getLength() );
10071         // mark element MC sequence as open
10072         m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = true;
10073     }
10074 }
10075 
10076 void PDFWriterImpl::endStructureElementMCSeq()
10077 {
10078     if( m_nCurrentStructElement > 0 && // StructTreeRoot
10079         ( m_bEmitStructure || m_aStructure[ m_nCurrentStructElement ].m_eType == PDFWriter::NonStructElement ) &&
10080         m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // must have an opened MC sequence
10081         )
10082     {
10083         writeBuffer( "EMC\n", 4 );
10084         m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = false;
10085     }
10086 }
10087 
10088 bool PDFWriterImpl::checkEmitStructure()
10089 {
10090     bool bEmit = false;
10091     if( m_aContext.Tagged )
10092     {
10093         bEmit = true;
10094         sal_Int32 nEle = m_nCurrentStructElement;
10095         while( nEle > 0 && nEle < sal_Int32(m_aStructure.size()) )
10096         {
10097             if( m_aStructure[ nEle ].m_eType == PDFWriter::NonStructElement )
10098             {
10099                 bEmit = false;
10100                 break;
10101             }
10102             nEle = m_aStructure[ nEle ].m_nParentElement;
10103         }
10104     }
10105     return bEmit;
10106 }
10107 
10108 sal_Int32 PDFWriterImpl::beginStructureElement( PDFWriter::StructElement eType, const OUString& rAlias )
10109 {
10110     if( m_nCurrentPage < 0 )
10111         return -1;
10112 
10113     if( ! m_aContext.Tagged )
10114         return -1;
10115 
10116     // close eventual current MC sequence
10117     endStructureElementMCSeq();
10118 
10119     if( m_nCurrentStructElement == 0 &&
10120         eType != PDFWriter::Document && eType != PDFWriter::NonStructElement )
10121     {
10122         // struct tree root hit, but not beginning document
10123         // this might happen with setCurrentStructureElement
10124         // silently insert structure into document again if one properly exists
10125         if( ! m_aStructure[ 0 ].m_aChildren.empty() )
10126         {
10127             const std::list< sal_Int32 >& rRootChildren = m_aStructure[0].m_aChildren;
10128             auto it = std::find_if(rRootChildren.begin(), rRootChildren.end(),
10129                 [&](sal_Int32 nElement) { return m_aStructure[ nElement ].m_eType == PDFWriter::Document; });
10130             if( it != rRootChildren.end() )
10131             {
10132                 m_nCurrentStructElement = *it;
10133                 SAL_WARN( "vcl.pdfwriter", "Structure element inserted to StructTreeRoot that is not a document" );
10134             }
10135             else {
10136                 OSL_FAIL( "document structure in disorder !" );
10137             }
10138         }
10139         else {
10140             OSL_FAIL( "PDF document structure MUST be contained in a Document element" );
10141         }
10142     }
10143 
10144     sal_Int32 nNewId = sal_Int32(m_aStructure.size());
10145     m_aStructure.emplace_back( );
10146     PDFStructureElement& rEle = m_aStructure.back();
10147     rEle.m_eType            = eType;
10148     rEle.m_nOwnElement      = nNewId;
10149     rEle.m_nParentElement   = m_nCurrentStructElement;
10150     rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject;
10151     m_aStructure[ m_nCurrentStructElement ].m_aChildren.push_back( nNewId );
10152     m_nCurrentStructElement = nNewId;
10153 
10154     // handle alias names
10155     if( !rAlias.isEmpty() && eType != PDFWriter::NonStructElement )
10156     {
10157         OStringBuffer aNameBuf( rAlias.getLength() );
10158         appendName( rAlias, aNameBuf );
10159         OString aAliasName( aNameBuf.makeStringAndClear() );
10160         rEle.m_aAlias = aAliasName;
10161         addRoleMap(aAliasName, eType);
10162     }
10163 
10164     if (g_bDebugDisableCompression)
10165     {
10166         OStringBuffer aLine( "beginStructureElement " );
10167         aLine.append( m_nCurrentStructElement );
10168         aLine.append( ": " );
10169         aLine.append( getStructureTag( eType ) );
10170         if( !rEle.m_aAlias.isEmpty() )
10171         {
10172             aLine.append( " aliased as \"" );
10173             aLine.append( rEle.m_aAlias );
10174             aLine.append( '\"' );
10175         }
10176         emitComment( aLine.getStr() );
10177     }
10178 
10179     // check whether to emit structure henceforth
10180     m_bEmitStructure = checkEmitStructure();
10181 
10182     if( m_bEmitStructure ) // don't create nonexistent objects
10183     {
10184         rEle.m_nObject      = createObject();
10185         // update parent's kids list
10186         m_aStructure[ rEle.m_nParentElement ].m_aKids.emplace_back(rEle.m_nObject);
10187     }
10188     return nNewId;
10189 }
10190 
10191 void PDFWriterImpl::endStructureElement()
10192 {
10193     if( m_nCurrentPage < 0 )
10194         return;
10195 
10196     if( ! m_aContext.Tagged )
10197         return;
10198 
10199     if( m_nCurrentStructElement == 0 )
10200     {
10201         // hit the struct tree root, that means there is an endStructureElement
10202         // without corresponding beginStructureElement
10203         return;
10204     }
10205 
10206     // end the marked content sequence
10207     endStructureElementMCSeq();
10208 
10209     OStringBuffer aLine;
10210     if (g_bDebugDisableCompression)
10211     {
10212         aLine.append( "endStructureElement " );
10213         aLine.append( m_nCurrentStructElement );
10214         aLine.append( ": " );
10215         aLine.append( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) );
10216         if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
10217         {
10218             aLine.append( " aliased as \"" );
10219             aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
10220             aLine.append( '\"' );
10221         }
10222     }
10223 
10224     // "end" the structure element, the parent becomes current element
10225     m_nCurrentStructElement = m_aStructure[ m_nCurrentStructElement ].m_nParentElement;
10226 
10227     // check whether to emit structure henceforth
10228     m_bEmitStructure = checkEmitStructure();
10229 
10230     if (g_bDebugDisableCompression && m_bEmitStructure)
10231     {
10232         emitComment( aLine.getStr() );
10233     }
10234 }
10235 
10236 /*
10237  * This function adds an internal structure list container to overcome the 8191 elements array limitation
10238  * in kids element emission.
10239  * Recursive function
10240  *
10241  */
10242 void PDFWriterImpl::addInternalStructureContainer( PDFStructureElement& rEle )
10243 {
10244     if( rEle.m_eType == PDFWriter::NonStructElement &&
10245         rEle.m_nOwnElement != rEle.m_nParentElement )
10246         return;
10247 
10248     for (auto const& child : rEle.m_aChildren)
10249     {
10250         if( child > 0 && child < sal_Int32(m_aStructure.size()) )
10251         {
10252             PDFStructureElement& rChild = m_aStructure[ child ];
10253             if( rChild.m_eType != PDFWriter::NonStructElement )
10254             {
10255                 //triggered when a child of the rEle element is found
10256                 if( rChild.m_nParentElement == rEle.m_nOwnElement )
10257                     addInternalStructureContainer( rChild );//examine the child
10258                 else
10259                 {
10260                     OSL_FAIL( "PDFWriterImpl::addInternalStructureContainer: invalid child structure element" );
10261                     SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure element with id " << child );
10262                 }
10263             }
10264         }
10265         else
10266         {
10267             OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
10268             SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure id " << child );
10269         }
10270     }
10271 
10272     if( rEle.m_nOwnElement != rEle.m_nParentElement )
10273     {
10274         if( !rEle.m_aKids.empty() )
10275         {
10276             if( rEle.m_aKids.size() > ncMaxPDFArraySize ) {
10277                 //then we need to add the containers for the kids elements
10278                 // a list to be used for the new kid element
10279                 std::list< PDFStructureElementKid > aNewKids;
10280                 std::list< sal_Int32 > aNewChildren;
10281 
10282                 // add Div in RoleMap, in case no one else did (TODO: is it needed? Is it dangerous?)
10283                 OString aAliasName("Div");
10284                 addRoleMap(aAliasName, PDFWriter::Division);
10285 
10286                 while( rEle.m_aKids.size() > ncMaxPDFArraySize )
10287                 {
10288                     sal_Int32 nCurrentStructElement = rEle.m_nOwnElement;
10289                     sal_Int32 nNewId = sal_Int32(m_aStructure.size());
10290                     m_aStructure.emplace_back( );
10291                     PDFStructureElement& rEleNew = m_aStructure.back();
10292                     rEleNew.m_aAlias            = aAliasName;
10293                     rEleNew.m_eType             = PDFWriter::Division; // a new Div type container
10294                     rEleNew.m_nOwnElement       = nNewId;
10295                     rEleNew.m_nParentElement    = nCurrentStructElement;
10296                     //inherit the same page as the first child to be reparented
10297                     rEleNew.m_nFirstPageObject  = m_aStructure[ rEle.m_aChildren.front() ].m_nFirstPageObject;
10298                     rEleNew.m_nObject           = createObject();//assign a PDF object number
10299                     //add the object to the kid list of the parent
10300                     aNewKids.emplace_back( rEleNew.m_nObject );
10301                     aNewChildren.push_back( nNewId );
10302 
10303                     std::list< sal_Int32 >::iterator aChildEndIt( rEle.m_aChildren.begin() );
10304                     std::list< PDFStructureElementKid >::iterator aKidEndIt( rEle.m_aKids.begin() );
10305                     advance( aChildEndIt, ncMaxPDFArraySize );
10306                     advance( aKidEndIt, ncMaxPDFArraySize );
10307 
10308                     rEleNew.m_aKids.splice( rEleNew.m_aKids.begin(),
10309                                             rEle.m_aKids,
10310                                             rEle.m_aKids.begin(),
10311                                             aKidEndIt );
10312                     rEleNew.m_aChildren.splice( rEleNew.m_aChildren.begin(),
10313                                                 rEle.m_aChildren,
10314                                                 rEle.m_aChildren.begin(),
10315                                                 aChildEndIt );
10316                     // set the kid's new parent
10317                     for (auto const& child : rEleNew.m_aChildren)
10318                     {
10319                         m_aStructure[ child ].m_nParentElement = nNewId;
10320                     }
10321                 }
10322                 //finally add the new kids resulting from the container added
10323                 rEle.m_aKids.insert( rEle.m_aKids.begin(), aNewKids.begin(), aNewKids.end() );
10324                 rEle.m_aChildren.insert( rEle.m_aChildren.begin(), aNewChildren.begin(), aNewChildren.end() );
10325             }
10326         }
10327     }
10328 }
10329 
10330 bool PDFWriterImpl::setCurrentStructureElement( sal_Int32 nEle )
10331 {
10332     bool bSuccess = false;
10333 
10334     if( m_aContext.Tagged && nEle >= 0 && nEle < sal_Int32(m_aStructure.size()) )
10335     {
10336         // end eventual previous marked content sequence
10337         endStructureElementMCSeq();
10338 
10339         m_nCurrentStructElement = nEle;
10340         m_bEmitStructure = checkEmitStructure();
10341         if (g_bDebugDisableCompression)
10342         {
10343             OStringBuffer aLine( "setCurrentStructureElement " );
10344             aLine.append( m_nCurrentStructElement );
10345             aLine.append( ": " );
10346             aLine.append( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) );
10347             if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
10348             {
10349                 aLine.append( " aliased as \"" );
10350                 aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
10351                 aLine.append( '\"' );
10352             }
10353             if( ! m_bEmitStructure )
10354                 aLine.append( " (inside NonStruct)" );
10355             emitComment( aLine.getStr() );
10356         }
10357         bSuccess = true;
10358     }
10359 
10360     return bSuccess;
10361 }
10362 
10363 bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr, enum PDFWriter::StructAttributeValue eVal )
10364 {
10365     if( !m_aContext.Tagged )
10366         return false;
10367 
10368     bool bInsert = false;
10369     if( m_nCurrentStructElement > 0 && m_bEmitStructure )
10370     {
10371         PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
10372         switch( eAttr )
10373         {
10374             case PDFWriter::Placement:
10375                 if( eVal == PDFWriter::Block        ||
10376                     eVal == PDFWriter::Inline       ||
10377                     eVal == PDFWriter::Before       ||
10378                     eVal == PDFWriter::Start        ||
10379                     eVal == PDFWriter::End )
10380                     bInsert = true;
10381                 break;
10382             case PDFWriter::WritingMode:
10383                 if( eVal == PDFWriter::LrTb         ||
10384                     eVal == PDFWriter::RlTb         ||
10385                     eVal == PDFWriter::TbRl )
10386                 {
10387                     bInsert = true;
10388                 }
10389                 break;
10390             case PDFWriter::TextAlign:
10391                 if( eVal == PDFWriter::Start        ||
10392                     eVal == PDFWriter::Center       ||
10393                     eVal == PDFWriter::End          ||
10394                     eVal == PDFWriter::Justify )
10395                 {
10396                     if( eType == PDFWriter::Paragraph   ||
10397                         eType == PDFWriter::Heading     ||
10398                         eType == PDFWriter::H1          ||
10399                         eType == PDFWriter::H2          ||
10400                         eType == PDFWriter::H3          ||
10401                         eType == PDFWriter::H4          ||
10402                         eType == PDFWriter::H5          ||
10403                         eType == PDFWriter::H6          ||
10404                         eType == PDFWriter::List        ||
10405                         eType == PDFWriter::ListItem    ||
10406                         eType == PDFWriter::LILabel     ||
10407                         eType == PDFWriter::LIBody      ||
10408                         eType == PDFWriter::Table       ||
10409                         eType == PDFWriter::TableRow    ||
10410                         eType == PDFWriter::TableHeader ||
10411                         eType == PDFWriter::TableData )
10412                     {
10413                         bInsert = true;
10414                     }
10415                 }
10416                 break;
10417             case PDFWriter::Width:
10418             case PDFWriter::Height:
10419                 if( eVal == PDFWriter::Auto )
10420                 {
10421                     if( eType == PDFWriter::Figure      ||
10422                         eType == PDFWriter::Formula     ||
10423                         eType == PDFWriter::Form        ||
10424                         eType == PDFWriter::Table       ||
10425                         eType == PDFWriter::TableHeader ||
10426                         eType == PDFWriter::TableData )
10427                     {
10428                         bInsert = true;
10429                     }
10430                 }
10431                 break;
10432             case PDFWriter::BlockAlign:
10433                 if( eVal == PDFWriter::Before       ||
10434                     eVal == PDFWriter::Middle       ||
10435                     eVal == PDFWriter::After        ||
10436                     eVal == PDFWriter::Justify )
10437                 {
10438                     if( eType == PDFWriter::TableHeader ||
10439                         eType == PDFWriter::TableData )
10440                     {
10441                         bInsert = true;
10442                     }
10443                 }
10444                 break;
10445             case PDFWriter::InlineAlign:
10446                 if( eVal == PDFWriter::Start        ||
10447                     eVal == PDFWriter::Center       ||
10448                     eVal == PDFWriter::End )
10449                 {
10450                     if( eType == PDFWriter::TableHeader ||
10451                         eType == PDFWriter::TableData )
10452                     {
10453                         bInsert = true;
10454                     }
10455                 }
10456                 break;
10457             case PDFWriter::LineHeight:
10458                 if( eVal == PDFWriter::Normal       ||
10459                     eVal == PDFWriter::Auto )
10460                 {
10461                     // only for ILSE and BLSE
10462                     if( eType == PDFWriter::Paragraph   ||
10463                         eType == PDFWriter::Heading     ||
10464                         eType == PDFWriter::H1          ||
10465                         eType == PDFWriter::H2          ||
10466                         eType == PDFWriter::H3          ||
10467                         eType == PDFWriter::H4          ||
10468                         eType == PDFWriter::H5          ||
10469                         eType == PDFWriter::H6          ||
10470                         eType == PDFWriter::List        ||
10471                         eType == PDFWriter::ListItem    ||
10472                         eType == PDFWriter::LILabel     ||
10473                         eType == PDFWriter::LIBody      ||
10474                         eType == PDFWriter::Table       ||
10475                         eType == PDFWriter::TableRow    ||
10476                         eType == PDFWriter::TableHeader ||
10477                         eType == PDFWriter::TableData   ||
10478                         eType == PDFWriter::Span        ||
10479                         eType == PDFWriter::Quote       ||
10480                         eType == PDFWriter::Note        ||
10481                         eType == PDFWriter::Reference   ||
10482                         eType == PDFWriter::BibEntry    ||
10483                         eType == PDFWriter::Code        ||
10484                         eType == PDFWriter::Link )
10485                     {
10486                         bInsert = true;
10487                     }
10488                 }
10489                 break;
10490             case PDFWriter::TextDecorationType:
10491                 if( eVal == PDFWriter::NONE         ||
10492                     eVal == PDFWriter::Underline    ||
10493                     eVal == PDFWriter::Overline     ||
10494                     eVal == PDFWriter::LineThrough )
10495                 {
10496                     // only for ILSE and BLSE
10497                     if( eType == PDFWriter::Paragraph   ||
10498                         eType == PDFWriter::Heading     ||
10499                         eType == PDFWriter::H1          ||
10500                         eType == PDFWriter::H2          ||
10501                         eType == PDFWriter::H3          ||
10502                         eType == PDFWriter::H4          ||
10503                         eType == PDFWriter::H5          ||
10504                         eType == PDFWriter::H6          ||
10505                         eType == PDFWriter::List        ||
10506                         eType == PDFWriter::ListItem    ||
10507                         eType == PDFWriter::LILabel     ||
10508                         eType == PDFWriter::LIBody      ||
10509                         eType == PDFWriter::Table       ||
10510                         eType == PDFWriter::TableRow    ||
10511                         eType == PDFWriter::TableHeader ||
10512                         eType == PDFWriter::TableData   ||
10513                         eType == PDFWriter::Span        ||
10514                         eType == PDFWriter::Quote       ||
10515                         eType == PDFWriter::Note        ||
10516                         eType == PDFWriter::Reference   ||
10517                         eType == PDFWriter::BibEntry    ||
10518                         eType == PDFWriter::Code        ||
10519                         eType == PDFWriter::Link )
10520                     {
10521                         bInsert = true;
10522                     }
10523                 }
10524                 break;
10525             case PDFWriter::ListNumbering:
10526                 if( eVal == PDFWriter::NONE         ||
10527                     eVal == PDFWriter::Disc         ||
10528                     eVal == PDFWriter::Circle       ||
10529                     eVal == PDFWriter::Square       ||
10530                     eVal == PDFWriter::Decimal      ||
10531                     eVal == PDFWriter::UpperRoman   ||
10532                     eVal == PDFWriter::LowerRoman   ||
10533                     eVal == PDFWriter::UpperAlpha   ||
10534                     eVal == PDFWriter::LowerAlpha )
10535                 {
10536                     if( eType == PDFWriter::List )
10537                         bInsert = true;
10538                 }
10539                 break;
10540             default: break;
10541         }
10542     }
10543 
10544     if( bInsert )
10545         m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( eVal );
10546     else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
10547         SAL_INFO("vcl.pdfwriter",
10548                  "rejecting setStructureAttribute( " << getAttributeTag( eAttr )
10549                  << ", " << getAttributeValueTag( eVal )
10550                  << " ) on " << getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType )
10551                  << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias
10552                  << ") element");
10553 
10554     return bInsert;
10555 }
10556 
10557 bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttribute eAttr, sal_Int32 nValue )
10558 {
10559     if( ! m_aContext.Tagged )
10560         return false;
10561 
10562     bool bInsert = false;
10563     if( m_nCurrentStructElement > 0 && m_bEmitStructure )
10564     {
10565         if( eAttr == PDFWriter::Language )
10566         {
10567             m_aStructure[ m_nCurrentStructElement ].m_aLocale = LanguageTag( LanguageType(nValue) ).getLocale();
10568             return true;
10569         }
10570 
10571         PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
10572         switch( eAttr )
10573         {
10574             case PDFWriter::SpaceBefore:
10575             case PDFWriter::SpaceAfter:
10576             case PDFWriter::StartIndent:
10577             case PDFWriter::EndIndent:
10578                 // just for BLSE
10579                 if( eType == PDFWriter::Paragraph   ||
10580                     eType == PDFWriter::Heading     ||
10581                     eType == PDFWriter::H1          ||
10582                     eType == PDFWriter::H2          ||
10583                     eType == PDFWriter::H3          ||
10584                     eType == PDFWriter::H4          ||
10585                     eType == PDFWriter::H5          ||
10586                     eType == PDFWriter::H6          ||
10587                     eType == PDFWriter::List        ||
10588                     eType == PDFWriter::ListItem    ||
10589                     eType == PDFWriter::LILabel     ||
10590                     eType == PDFWriter::LIBody      ||
10591                     eType == PDFWriter::Table       ||
10592                     eType == PDFWriter::TableRow    ||
10593                     eType == PDFWriter::TableHeader ||
10594                     eType == PDFWriter::TableData )
10595                 {
10596                     bInsert = true;
10597                 }
10598                 break;
10599             case PDFWriter::TextIndent:
10600                 // paragraph like BLSE and additional elements
10601                 if( eType == PDFWriter::Paragraph   ||
10602                     eType == PDFWriter::Heading     ||
10603                     eType == PDFWriter::H1          ||
10604                     eType == PDFWriter::H2          ||
10605                     eType == PDFWriter::H3          ||
10606                     eType == PDFWriter::H4          ||
10607                     eType == PDFWriter::H5          ||
10608                     eType == PDFWriter::H6          ||
10609                     eType == PDFWriter::LILabel     ||
10610                     eType == PDFWriter::LIBody      ||
10611                     eType == PDFWriter::TableHeader ||
10612                     eType == PDFWriter::TableData )
10613                 {
10614                     bInsert = true;
10615                 }
10616                 break;
10617             case PDFWriter::Width:
10618             case PDFWriter::Height:
10619                 if( eType == PDFWriter::Figure      ||
10620                     eType == PDFWriter::Formula     ||
10621                     eType == PDFWriter::Form        ||
10622                     eType == PDFWriter::Table       ||
10623                     eType == PDFWriter::TableHeader ||
10624                     eType == PDFWriter::TableData )
10625                 {
10626                     bInsert = true;
10627                 }
10628                 break;
10629             case PDFWriter::LineHeight:
10630             case PDFWriter::BaselineShift:
10631                 // only for ILSE and BLSE
10632                 if( eType == PDFWriter::Paragraph   ||
10633                     eType == PDFWriter::Heading     ||
10634                     eType == PDFWriter::H1          ||
10635                     eType == PDFWriter::H2          ||
10636                     eType == PDFWriter::H3          ||
10637                     eType == PDFWriter::H4          ||
10638                     eType == PDFWriter::H5          ||
10639                     eType == PDFWriter::H6          ||
10640                     eType == PDFWriter::List        ||
10641                     eType == PDFWriter::ListItem    ||
10642                     eType == PDFWriter::LILabel     ||
10643                     eType == PDFWriter::LIBody      ||
10644                     eType == PDFWriter::Table       ||
10645                     eType == PDFWriter::TableRow    ||
10646                     eType == PDFWriter::TableHeader ||
10647                     eType == PDFWriter::TableData   ||
10648                     eType == PDFWriter::Span        ||
10649                     eType == PDFWriter::Quote       ||
10650                     eType == PDFWriter::Note        ||
10651                     eType == PDFWriter::Reference   ||
10652                     eType == PDFWriter::BibEntry    ||
10653                     eType == PDFWriter::Code        ||
10654                     eType == PDFWriter::Link )
10655                 {
10656                         bInsert = true;
10657                 }
10658                 break;
10659             case PDFWriter::RowSpan:
10660             case PDFWriter::ColSpan:
10661                 // only for table cells
10662                 if( eType == PDFWriter::TableHeader ||
10663                     eType == PDFWriter::TableData )
10664                 {
10665                     bInsert = true;
10666                 }
10667                 break;
10668             case PDFWriter::LinkAnnotation:
10669                 if( eType == PDFWriter::Link )
10670                     bInsert = true;
10671                 break;
10672             default: break;
10673         }
10674     }
10675 
10676     if( bInsert )
10677         m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( nValue );
10678     else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
10679         SAL_INFO("vcl.pdfwriter",
10680                  "rejecting setStructureAttributeNumerical( " << getAttributeTag( eAttr )
10681                  << ", " << static_cast<int>(nValue)
10682                  << " ) on " << getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType )
10683                  << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias
10684                  << ") element");
10685 
10686     return bInsert;
10687 }
10688 
10689 void PDFWriterImpl::setStructureBoundingBox( const tools::Rectangle& rRect )
10690 {
10691     sal_Int32 nPageNr = m_nCurrentPage;
10692     if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) || !m_aContext.Tagged )
10693         return;
10694 
10695     if( m_nCurrentStructElement > 0 && m_bEmitStructure )
10696     {
10697         PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
10698         if( eType == PDFWriter::Figure      ||
10699             eType == PDFWriter::Formula     ||
10700             eType == PDFWriter::Form        ||
10701             eType == PDFWriter::Table )
10702         {
10703             m_aStructure[ m_nCurrentStructElement ].m_aBBox = rRect;
10704             // convert to default user space now, since the mapmode may change
10705             m_aPages[nPageNr].convertRect( m_aStructure[ m_nCurrentStructElement ].m_aBBox );
10706         }
10707     }
10708 }
10709 
10710 void PDFWriterImpl::setActualText( const OUString& rText )
10711 {
10712     if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
10713     {
10714         m_aStructure[ m_nCurrentStructElement ].m_aActualText = rText;
10715     }
10716 }
10717 
10718 void PDFWriterImpl::setAlternateText( const OUString& rText )
10719 {
10720     if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
10721     {
10722         m_aStructure[ m_nCurrentStructElement ].m_aAltText = rText;
10723     }
10724 }
10725 
10726 void PDFWriterImpl::setPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr )
10727 {
10728     if( nPageNr < 0 )
10729         nPageNr = m_nCurrentPage;
10730 
10731     if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
10732         return;
10733 
10734     m_aPages[ nPageNr ].m_eTransition   = eType;
10735     m_aPages[ nPageNr ].m_nTransTime    = nMilliSec;
10736 }
10737 
10738 void PDFWriterImpl::ensureUniqueRadioOnValues()
10739 {
10740     // loop over radio groups
10741     for (auto const& group : m_aRadioGroupWidgets)
10742     {
10743         PDFWidget& rGroupWidget = m_aWidgets[ group.second ];
10744         // check whether all kids have a unique OnValue
10745         std::unordered_map< OUString, sal_Int32 > aOnValues;
10746         bool bIsUnique = true;
10747         for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
10748         {
10749             const OUString& rVal = m_aWidgets[nKidIndex].m_aOnValue;
10750             SAL_INFO("vcl.pdfwriter", "OnValue: " << rVal);
10751             if( aOnValues.find( rVal ) == aOnValues.end() )
10752             {
10753                 aOnValues[ rVal ] = 1;
10754             }
10755             else
10756             {
10757                 bIsUnique = false;
10758                 break;
10759             }
10760         }
10761         if( ! bIsUnique )
10762         {
10763             SAL_INFO("vcl.pdfwriter", "enforcing unique OnValues" );
10764             // make unique by using ascending OnValues
10765             int nKid = 0;
10766             for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
10767             {
10768                 PDFWidget& rKid = m_aWidgets[nKidIndex];
10769                 rKid.m_aOnValue = OUString::number( nKid+1 );
10770                 if( rKid.m_aValue != "Off" )
10771                     rKid.m_aValue = rKid.m_aOnValue;
10772                 ++nKid;
10773             }
10774         }
10775         // finally move the "Yes" appearance to the OnValue appearance
10776         for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
10777         {
10778             PDFWidget& rKid = m_aWidgets[nKidIndex];
10779             auto app_it = rKid.m_aAppearances.find( "N" );
10780             if( app_it != rKid.m_aAppearances.end() )
10781             {
10782                 auto stream_it = app_it->second.find( "Yes" );
10783                 if( stream_it != app_it->second.end() )
10784                 {
10785                     SvMemoryStream* pStream = stream_it->second;
10786                     app_it->second.erase( stream_it );
10787                     OStringBuffer aBuf( rKid.m_aOnValue.getLength()*2 );
10788                     appendName( rKid.m_aOnValue, aBuf );
10789                     (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
10790                 }
10791                 else
10792                     SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Yes\" stream" );
10793             }
10794             // update selected radio button
10795             if( rKid.m_aValue != "Off" )
10796             {
10797                 rGroupWidget.m_aValue = rKid.m_aValue;
10798             }
10799         }
10800     }
10801 }
10802 
10803 sal_Int32 PDFWriterImpl::findRadioGroupWidget( const PDFWriter::RadioButtonWidget& rBtn )
10804 {
10805     sal_Int32 nRadioGroupWidget = -1;
10806 
10807     std::map< sal_Int32, sal_Int32 >::const_iterator it = m_aRadioGroupWidgets.find( rBtn.RadioGroup );
10808 
10809     if( it == m_aRadioGroupWidgets.end() )
10810     {
10811         m_aRadioGroupWidgets[ rBtn.RadioGroup ] = nRadioGroupWidget =
10812             sal_Int32(m_aWidgets.size());
10813 
10814         // new group, insert the radiobutton
10815         m_aWidgets.emplace_back( );
10816         m_aWidgets.back().m_nObject     = createObject();
10817         m_aWidgets.back().m_nPage       = m_nCurrentPage;
10818         m_aWidgets.back().m_eType       = PDFWriter::RadioButton;
10819         m_aWidgets.back().m_nRadioGroup = rBtn.RadioGroup;
10820         m_aWidgets.back().m_nFlags |= 0x0000C000;   // NoToggleToOff and Radio bits
10821 
10822         createWidgetFieldName( sal_Int32(m_aWidgets.size()-1), rBtn );
10823     }
10824     else
10825         nRadioGroupWidget = it->second;
10826 
10827     return nRadioGroupWidget;
10828 }
10829 
10830 sal_Int32 PDFWriterImpl::createControl( const PDFWriter::AnyWidget& rControl, sal_Int32 nPageNr )
10831 {
10832     if( nPageNr < 0 )
10833         nPageNr = m_nCurrentPage;
10834 
10835     if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
10836         return -1;
10837 
10838     bool sigHidden(true);
10839     sal_Int32 nNewWidget = m_aWidgets.size();
10840     m_aWidgets.emplace_back( );
10841 
10842     m_aWidgets.back().m_nObject         = createObject();
10843     m_aWidgets.back().m_aRect           = rControl.Location;
10844     m_aWidgets.back().m_nPage           = nPageNr;
10845     m_aWidgets.back().m_eType           = rControl.getType();
10846 
10847     sal_Int32 nRadioGroupWidget = -1;
10848     // for unknown reasons the radio buttons of a radio group must not have a
10849     // field name, else the buttons are in fact check boxes -
10850     // that is multiple buttons of the radio group can be selected
10851     if( rControl.getType() == PDFWriter::RadioButton )
10852         nRadioGroupWidget = findRadioGroupWidget( static_cast<const PDFWriter::RadioButtonWidget&>(rControl) );
10853     else
10854     {
10855         createWidgetFieldName( nNewWidget, rControl );
10856     }
10857 
10858     // caution: m_aWidgets must not be changed after here or rNewWidget may be invalid
10859     PDFWidget& rNewWidget           = m_aWidgets[nNewWidget];
10860     rNewWidget.m_aDescription       = rControl.Description;
10861     rNewWidget.m_aText              = rControl.Text;
10862     rNewWidget.m_nTextStyle         = rControl.TextStyle &
10863         (  DrawTextFlags::Left | DrawTextFlags::Center | DrawTextFlags::Right | DrawTextFlags::Top |
10864            DrawTextFlags::VCenter | DrawTextFlags::Bottom |
10865            DrawTextFlags::MultiLine | DrawTextFlags::WordBreak  );
10866     rNewWidget.m_nTabOrder          = rControl.TabOrder;
10867 
10868     // various properties are set via the flags (/Ff) property of the field dict
10869     if( rControl.ReadOnly )
10870         rNewWidget.m_nFlags |= 1;
10871     if( rControl.getType() == PDFWriter::PushButton )
10872     {
10873         const PDFWriter::PushButtonWidget& rBtn = static_cast<const PDFWriter::PushButtonWidget&>(rControl);
10874         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
10875             rNewWidget.m_nTextStyle =
10876                 DrawTextFlags::Center | DrawTextFlags::VCenter |
10877                 DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
10878 
10879         rNewWidget.m_nFlags |= 0x00010000;
10880         if( !rBtn.URL.isEmpty() )
10881             rNewWidget.m_aListEntries.push_back( rBtn.URL );
10882         rNewWidget.m_bSubmit    = rBtn.Submit;
10883         rNewWidget.m_bSubmitGet = rBtn.SubmitGet;
10884         rNewWidget.m_nDest      = rBtn.Dest;
10885         createDefaultPushButtonAppearance( rNewWidget, rBtn );
10886     }
10887     else if( rControl.getType() == PDFWriter::RadioButton )
10888     {
10889         const PDFWriter::RadioButtonWidget& rBtn = static_cast<const PDFWriter::RadioButtonWidget&>(rControl);
10890         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
10891             rNewWidget.m_nTextStyle =
10892                 DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
10893         /*  PDF sees a RadioButton group as one radio button with
10894          *  children which are in turn check boxes
10895          *
10896          *  so we need to create a radio button on demand for a new group
10897          *  and insert a checkbox for each RadioButtonWidget as its child
10898          */
10899         rNewWidget.m_eType          = PDFWriter::CheckBox;
10900         rNewWidget.m_nRadioGroup    = rBtn.RadioGroup;
10901 
10902         SAL_WARN_IF( nRadioGroupWidget < 0 || nRadioGroupWidget >= static_cast<sal_Int32>(m_aWidgets.size()), "vcl.pdfwriter", "no radio group parent" );
10903 
10904         PDFWidget& rRadioButton = m_aWidgets[nRadioGroupWidget];
10905         rRadioButton.m_aKids.push_back( rNewWidget.m_nObject );
10906         rRadioButton.m_aKidsIndex.push_back( nNewWidget );
10907         rNewWidget.m_nParent = rRadioButton.m_nObject;
10908 
10909         rNewWidget.m_aValue     = "Off";
10910         rNewWidget.m_aOnValue   = rBtn.OnValue;
10911         if( rRadioButton.m_aValue.isEmpty() && rBtn.Selected )
10912         {
10913             rNewWidget.m_aValue     = rNewWidget.m_aOnValue;
10914             rRadioButton.m_aValue   = rNewWidget.m_aOnValue;
10915         }
10916         createDefaultRadioButtonAppearance( rNewWidget, rBtn );
10917 
10918         // union rect of radio group
10919         tools::Rectangle aRect = rNewWidget.m_aRect;
10920         m_aPages[ nPageNr ].convertRect( aRect );
10921         rRadioButton.m_aRect.Union( aRect );
10922     }
10923     else if( rControl.getType() == PDFWriter::CheckBox )
10924     {
10925         const PDFWriter::CheckBoxWidget& rBox = static_cast<const PDFWriter::CheckBoxWidget&>(rControl);
10926         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
10927             rNewWidget.m_nTextStyle =
10928                 DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
10929 
10930         rNewWidget.m_aValue = rBox.Checked ? OUStringLiteral("Yes") : OUStringLiteral("Off" );
10931         // create default appearance before m_aRect gets transformed
10932         createDefaultCheckBoxAppearance( rNewWidget, rBox );
10933     }
10934     else if( rControl.getType() == PDFWriter::ListBox )
10935     {
10936         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
10937             rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
10938 
10939         const PDFWriter::ListBoxWidget& rLstBox = static_cast<const PDFWriter::ListBoxWidget&>(rControl);
10940         rNewWidget.m_aListEntries     = rLstBox.Entries;
10941         rNewWidget.m_aSelectedEntries = rLstBox.SelectedEntries;
10942         rNewWidget.m_aValue           = rLstBox.Text;
10943         if( rLstBox.DropDown )
10944             rNewWidget.m_nFlags |= 0x00020000;
10945         if( rLstBox.MultiSelect && !rLstBox.DropDown && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
10946             rNewWidget.m_nFlags |= 0x00200000;
10947 
10948         createDefaultListBoxAppearance( rNewWidget, rLstBox );
10949     }
10950     else if( rControl.getType() == PDFWriter::ComboBox )
10951     {
10952         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
10953             rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
10954 
10955         const PDFWriter::ComboBoxWidget& rBox = static_cast<const PDFWriter::ComboBoxWidget&>(rControl);
10956         rNewWidget.m_aValue         = rBox.Text;
10957         rNewWidget.m_aListEntries   = rBox.Entries;
10958         rNewWidget.m_nFlags |= 0x00060000; // combo and edit flag
10959 
10960         PDFWriter::ListBoxWidget aLBox;
10961         aLBox.Name              = rBox.Name;
10962         aLBox.Description       = rBox.Description;
10963         aLBox.Text              = rBox.Text;
10964         aLBox.TextStyle         = rBox.TextStyle;
10965         aLBox.ReadOnly          = rBox.ReadOnly;
10966         aLBox.Border            = rBox.Border;
10967         aLBox.BorderColor       = rBox.BorderColor;
10968         aLBox.Background        = rBox.Background;
10969         aLBox.BackgroundColor   = rBox.BackgroundColor;
10970         aLBox.TextFont          = rBox.TextFont;
10971         aLBox.TextColor         = rBox.TextColor;
10972         aLBox.DropDown          = true;
10973         aLBox.MultiSelect       = false;
10974         aLBox.Entries           = rBox.Entries;
10975 
10976         createDefaultListBoxAppearance( rNewWidget, aLBox );
10977     }
10978     else if( rControl.getType() == PDFWriter::Edit )
10979     {
10980         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
10981             rNewWidget.m_nTextStyle = DrawTextFlags::Left | DrawTextFlags::VCenter;
10982 
10983         const PDFWriter::EditWidget& rEdit = static_cast<const  PDFWriter::EditWidget&>(rControl);
10984         if( rEdit.MultiLine )
10985         {
10986             rNewWidget.m_nFlags |= 0x00001000;
10987             rNewWidget.m_nTextStyle |= DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
10988         }
10989         if( rEdit.Password )
10990             rNewWidget.m_nFlags |= 0x00002000;
10991         if( rEdit.FileSelect && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
10992             rNewWidget.m_nFlags |= 0x00100000;
10993         rNewWidget.m_nMaxLen = rEdit.MaxLen;
10994         rNewWidget.m_aValue = rEdit.Text;
10995 
10996         createDefaultEditAppearance( rNewWidget, rEdit );
10997     }
10998 #if HAVE_FEATURE_NSS
10999     else if( rControl.getType() == PDFWriter::Signature)
11000     {
11001         sigHidden = true;
11002 
11003         rNewWidget.m_aRect = tools::Rectangle(0, 0, 0, 0);
11004 
11005         m_nSignatureObject = createObject();
11006         rNewWidget.m_aValue = OUString::number( m_nSignatureObject );
11007         rNewWidget.m_aValue += " 0 R";
11008         // let's add a fake appearance
11009         rNewWidget.m_aAppearances[ "N" ][ "Standard" ] = new SvMemoryStream();
11010     }
11011 #endif
11012 
11013     // if control is a hidden signature, do not convert coordinates since we
11014     // need /Rect [ 0 0 0 0 ]
11015     if ( ! ( ( rControl.getType() == PDFWriter::Signature ) && sigHidden ) )
11016     {
11017         // convert to default user space now, since the mapmode may change
11018         // note: create default appearances before m_aRect gets transformed
11019         m_aPages[ nPageNr ].convertRect( rNewWidget.m_aRect );
11020     }
11021 
11022     // insert widget to page's annotation list
11023     m_aPages[ nPageNr ].m_aAnnotations.push_back( rNewWidget.m_nObject );
11024 
11025     return nNewWidget;
11026 }
11027 
11028 void PDFWriterImpl::addStream( const OUString& rMimeType, PDFOutputStream* pStream )
11029 {
11030     if( pStream )
11031     {
11032         m_aAdditionalStreams.emplace_back( );
11033         PDFAddStream& rStream = m_aAdditionalStreams.back();
11034         rStream.m_aMimeType = !rMimeType.isEmpty()
11035                               ? rMimeType
11036                               : OUString( "application/octet-stream"  );
11037         rStream.m_pStream = pStream;
11038         rStream.m_bCompress = false;
11039     }
11040 }
11041 
11042 void PDFWriterImpl::MARK( const char* pString )
11043 {
11044     beginStructureElementMCSeq();
11045     if (g_bDebugDisableCompression)
11046         emitComment( pString );
11047 }
11048 
11049 sal_Int32 ReferenceXObjectEmit::getObject() const
11050 {
11051     if (m_nFormObject > 0)
11052         return m_nFormObject;
11053     else
11054         return m_nBitmapObject;
11055 }
11056 }
11057 
11058 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
11059