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