xref: /core/vcl/source/gdi/pdfwriter_impl.cxx (revision c9da81db)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <sal/config.h>
21 #include <config_crypto.h>
22 
23 #include <sal/types.h>
24 
25 #include <math.h>
26 #include <algorithm>
27 #include <string_view>
28 
29 #include <lcms2.h>
30 
31 #include <basegfx/matrix/b2dhommatrix.hxx>
32 #include <basegfx/polygon/b2dpolygon.hxx>
33 #include <basegfx/polygon/b2dpolygontools.hxx>
34 #include <basegfx/polygon/b2dpolypolygon.hxx>
35 #include <basegfx/polygon/b2dpolypolygoncutter.hxx>
36 #include <basegfx/polygon/b2dpolypolygontools.hxx>
37 #include <memory>
38 #include <com/sun/star/io/XOutputStream.hpp>
39 #include <com/sun/star/util/URL.hpp>
40 #include <com/sun/star/util/URLTransformer.hpp>
41 #include <comphelper/processfactory.hxx>
42 #include <comphelper/string.hxx>
43 #include <comphelper/xmlencode.hxx>
44 #include <cppuhelper/implbase.hxx>
45 #include <i18nlangtag/languagetag.hxx>
46 #include <o3tl/numeric.hxx>
47 #include <o3tl/safeint.hxx>
48 #include <o3tl/temporary.hxx>
49 #include <officecfg/Office/Common.hxx>
50 #include <osl/file.hxx>
51 #include <osl/thread.h>
52 #include <rtl/digest.h>
53 #include <rtl/uri.hxx>
54 #include <rtl/ustrbuf.hxx>
55 #include <svl/cryptosign.hxx>
56 #include <sal/log.hxx>
57 #include <svl/urihelper.hxx>
58 #include <tools/fract.hxx>
59 #include <tools/stream.hxx>
60 #include <tools/helpers.hxx>
61 #include <tools/urlobj.hxx>
62 #include <tools/UnitConversion.hxx>
63 #include <tools/zcodec.hxx>
64 #include <unotools/configmgr.hxx>
65 #include <vcl/bitmapex.hxx>
66 #include <vcl/canvastools.hxx>
67 #include <vcl/cvtgrf.hxx>
68 #include <vcl/fontcharmap.hxx>
69 #include <vcl/glyphitemcache.hxx>
70 #include <vcl/kernarray.hxx>
71 #include <vcl/lineinfo.hxx>
72 #include <vcl/metric.hxx>
73 #include <vcl/mnemonic.hxx>
74 #include <vcl/settings.hxx>
75 #include <strhelper.hxx>
76 #include <vcl/svapp.hxx>
77 #include <vcl/virdev.hxx>
78 #include <vcl/filter/pdfdocument.hxx>
79 #include <vcl/filter/PngImageReader.hxx>
80 #include <comphelper/hash.hxx>
81 
82 #include <svdata.hxx>
83 #include <bitmap/BitmapWriteAccess.hxx>
84 #include <fontsubset.hxx>
85 #include <font/EmphasisMark.hxx>
86 #include <font/PhysicalFontFace.hxx>
87 #include <salgdi.hxx>
88 #include <textlayout.hxx>
89 #include <textlineinfo.hxx>
90 #include <impglyphitem.hxx>
91 #include <pdf/XmpMetadata.hxx>
92 #include <pdf/objectcopier.hxx>
93 #include <pdf/pdfwriter_impl.hxx>
94 #include <pdf/PdfConfig.hxx>
95 #include <o3tl/sorted_vector.hxx>
96 #include <frozen/bits/defines.h>
97 #include <frozen/bits/elsa_std.h>
98 #include <frozen/map.h>
99 
100 using namespace::com::sun::star;
101 
102 static bool g_bDebugDisableCompression = getenv("VCL_DEBUG_DISABLE_PDFCOMPRESSION");
103 
104 namespace
105 {
106 
107 constexpr sal_Int32 nLog10Divisor = 3;
108 constexpr double fDivisor = 1000.0;
109 
110 constexpr double pixelToPoint(double px)
111 {
112     return px / fDivisor;
113 }
114 
115 constexpr sal_Int32 pointToPixel(double pt)
116 {
117     return sal_Int32(pt * fDivisor);
118 }
119 
120 void appendObjectID(sal_Int32 nObjectID, OStringBuffer & aLine)
121 {
122     aLine.append(nObjectID);
123     aLine.append(" 0 obj\n");
124 }
125 
126 void appendObjectReference(sal_Int32 nObjectID, OStringBuffer & aLine)
127 {
128     aLine.append(nObjectID);
129     aLine.append(" 0 R ");
130 }
131 
132 void appendHex(sal_Int8 nInt, OStringBuffer& rBuffer)
133 {
134     static const char pHexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7',
135                                            '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
136     rBuffer.append( pHexDigits[ (nInt >> 4) & 15 ] );
137     rBuffer.append( pHexDigits[ nInt & 15 ] );
138 }
139 
140 void appendName( std::u16string_view rStr, OStringBuffer& rBuffer )
141 {
142 // FIXME i59651 add a check for max length of 127 chars? Per PDF spec 1.4, appendix C.1
143 // I guess than when reading the #xx sequence it will count for a single character.
144     OString aStr( OUStringToOString( rStr, RTL_TEXTENCODING_UTF8 ) );
145     int nLen = aStr.getLength();
146     for( int i = 0; i < nLen; i++ )
147     {
148         /*  #i16920# PDF recommendation: output UTF8, any byte
149          *  outside the interval [33(=ASCII'!');126(=ASCII'~')]
150          *  should be escaped hexadecimal
151          *  for the sake of ghostscript which also reads PDF
152          *  but has a narrower acceptance rate we only pass
153          *  alphanumerics and '-' literally.
154          */
155         if( (aStr[i] >= 'A' && aStr[i] <= 'Z' ) ||
156             (aStr[i] >= 'a' && aStr[i] <= 'z' ) ||
157             (aStr[i] >= '0' && aStr[i] <= '9' ) ||
158             aStr[i] == '-' )
159         {
160             rBuffer.append( aStr[i] );
161         }
162         else
163         {
164             rBuffer.append( '#' );
165             appendHex( static_cast<sal_Int8>(aStr[i]), rBuffer );
166         }
167     }
168 }
169 
170 void appendName( const char* pStr, OStringBuffer& rBuffer )
171 {
172     // FIXME i59651 see above
173     while( pStr && *pStr )
174     {
175         if( (*pStr >= 'A' && *pStr <= 'Z' ) ||
176             (*pStr >= 'a' && *pStr <= 'z' ) ||
177             (*pStr >= '0' && *pStr <= '9' ) ||
178             *pStr == '-' )
179         {
180             rBuffer.append( *pStr );
181         }
182         else
183         {
184             rBuffer.append( '#' );
185             appendHex( static_cast<sal_Int8>(*pStr), rBuffer );
186         }
187         pStr++;
188     }
189 }
190 
191 //used only to emit encoded passwords
192 void appendLiteralString( const char* pStr, sal_Int32 nLength, OStringBuffer& rBuffer )
193 {
194     while( nLength )
195     {
196         switch( *pStr )
197         {
198         case '\n' :
199             rBuffer.append( "\\n" );
200             break;
201         case '\r' :
202             rBuffer.append( "\\r" );
203             break;
204         case '\t' :
205             rBuffer.append( "\\t" );
206             break;
207         case '\b' :
208             rBuffer.append( "\\b" );
209             break;
210         case '\f' :
211             rBuffer.append( "\\f" );
212             break;
213         case '(' :
214         case ')' :
215         case '\\' :
216             rBuffer.append( "\\" );
217             rBuffer.append( static_cast<char>(*pStr) );
218             break;
219         default:
220             rBuffer.append( static_cast<char>(*pStr) );
221             break;
222         }
223         pStr++;
224         nLength--;
225     }
226 }
227 
228 /*
229  * Convert a string before using it.
230  *
231  * This string conversion function is needed because the destination name
232  * in a PDF file seen through an Internet browser should be
233  * specially crafted, in order to be used directly by the browser.
234  * In this way the fragment part of a hyperlink to a PDF file (e.g. something
235  * as 'test1/test2/a-file.pdf\#thefragment) will be (hopefully) interpreted by the
236  * PDF reader (currently only Adobe Reader plug-in seems to be working that way) called
237  * from inside the Internet browser as: 'open the file test1/test2/a-file.pdf
238  * and go to named destination thefragment using default zoom'.
239  * The conversion is needed because in case of a fragment in the form: Slide%201
240  * (meaning Slide 1) as it is converted obeying the Inet rules, it will become Slide25201
241  * using this conversion, in both the generated named destinations, fragment and GoToR
242  * destination.
243  *
244  * The names for destinations are name objects and so they don't need to be encrypted
245  * even though they expose the content of PDF file (e.g. guessing the PDF content from the
246  * destination name).
247  *
248  * Further limitation: it is advisable to use standard ASCII characters for
249  * OOo bookmarks.
250 */
251 void appendDestinationName( const OUString& rString, OStringBuffer& rBuffer )
252 {
253     const sal_Unicode* pStr = rString.getStr();
254     sal_Int32 nLen = rString.getLength();
255     for( int i = 0; i < nLen; i++ )
256     {
257         sal_Unicode aChar = pStr[i];
258         if( (aChar >= '0' && aChar <= '9' ) ||
259             (aChar >= 'a' && aChar <= 'z' ) ||
260             (aChar >= 'A' && aChar <= 'Z' ) ||
261             aChar == '-' )
262         {
263             rBuffer.append(static_cast<char>(aChar));
264         }
265         else
266         {
267             sal_Int8 aValueHigh = sal_Int8(aChar >> 8);
268             if(aValueHigh > 0)
269                 appendHex( aValueHigh, rBuffer );
270             appendHex( static_cast<sal_Int8>(aChar & 255 ), rBuffer );
271         }
272     }
273 }
274 
275 } // end anonymous namespace
276 
277 namespace vcl
278 {
279 const sal_uInt8 PDFWriterImpl::s_nPadString[32] =
280 {
281     0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
282     0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A
283 };
284 
285 namespace
286 {
287 
288 template < class GEOMETRY >
289 GEOMETRY lcl_convert( const MapMode& _rSource, const MapMode& _rDest, OutputDevice* _pPixelConversion, const GEOMETRY& _rObject )
290 {
291     GEOMETRY aPoint;
292     if ( MapUnit::MapPixel == _rSource.GetMapUnit() )
293     {
294         aPoint = _pPixelConversion->PixelToLogic( _rObject, _rDest );
295     }
296     else
297     {
298         aPoint = OutputDevice::LogicToLogic( _rObject, _rSource, _rDest );
299     }
300     return aPoint;
301 }
302 
303 void removePlaceholderSE(std::vector<PDFStructureElement> & rStructure, PDFStructureElement& rEle);
304 
305 } // end anonymous namespace
306 
307 void PDFWriter::AppendUnicodeTextString(const OUString& rString, OStringBuffer& rBuffer)
308 {
309     rBuffer.append( "FEFF" );
310     const sal_Unicode* pStr = rString.getStr();
311     sal_Int32 nLen = rString.getLength();
312     for( int i = 0; i < nLen; i++ )
313     {
314         sal_Unicode aChar = pStr[i];
315         appendHex( static_cast<sal_Int8>(aChar >> 8), rBuffer );
316         appendHex( static_cast<sal_Int8>(aChar & 255 ), rBuffer );
317     }
318 }
319 
320 void PDFWriterImpl::createWidgetFieldName( sal_Int32 i_nWidgetIndex, const PDFWriter::AnyWidget& i_rControl )
321 {
322     /* #i80258# previously we use appendName here
323        however we need a slightly different coding scheme than the normal
324        name encoding for field names
325     */
326     const OUString& rName = i_rControl.Name;
327     OString aStr( OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ) );
328     int nLen = aStr.getLength();
329 
330     OStringBuffer aBuffer( rName.getLength()+64 );
331     for( int i = 0; i < nLen; i++ )
332     {
333         /*  #i16920# PDF recommendation: output UTF8, any byte
334          *  outside the interval [32(=ASCII' ');126(=ASCII'~')]
335          *  should be escaped hexadecimal
336          */
337         if( aStr[i] >= 32 && aStr[i] <= 126 )
338             aBuffer.append( aStr[i] );
339         else
340         {
341             aBuffer.append( '#' );
342             appendHex( static_cast<sal_Int8>(aStr[i]), aBuffer );
343         }
344     }
345 
346     OString aFullName( aBuffer.makeStringAndClear() );
347 
348     /* #i82785# create hierarchical fields down to the for each dot in i_rName */
349     sal_Int32 nTokenIndex = 0, nLastTokenIndex = 0;
350     OString aPartialName;
351     OString aDomain;
352     do
353     {
354         nLastTokenIndex = nTokenIndex;
355         aPartialName = aFullName.getToken( 0, '.', nTokenIndex );
356         if( nTokenIndex != -1 )
357         {
358             // find or create a hierarchical field
359             // first find the fully qualified name up to this field
360             aDomain = aFullName.copy( 0, nTokenIndex-1 );
361             std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
362             if( it == m_aFieldNameMap.end() )
363             {
364                  // create new hierarchy field
365                 sal_Int32 nNewWidget = m_aWidgets.size();
366                 m_aWidgets.emplace_back( );
367                 m_aWidgets[nNewWidget].m_nObject = createObject();
368                 m_aWidgets[nNewWidget].m_eType = PDFWriter::Hierarchy;
369                 m_aWidgets[nNewWidget].m_aName = aPartialName;
370                 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
371                 m_aFieldNameMap[aDomain] = nNewWidget;
372                 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
373                 if( nLastTokenIndex > 0 )
374                 {
375                     // this field is not a root field and
376                     // needs to be inserted to its parent
377                     OString aParentDomain( aDomain.copy( 0, nLastTokenIndex-1 ) );
378                     it = m_aFieldNameMap.find( aParentDomain );
379                     OSL_ENSURE( it != m_aFieldNameMap.end(), "field name not found" );
380                     if( it != m_aFieldNameMap.end()  )
381                     {
382                         OSL_ENSURE( it->second < sal_Int32(m_aWidgets.size()), "invalid field number entry" );
383                         if( it->second < sal_Int32(m_aWidgets.size()) )
384                         {
385                             PDFWidget& rParentField( m_aWidgets[it->second] );
386                             rParentField.m_aKids.push_back( m_aWidgets[nNewWidget].m_nObject );
387                             rParentField.m_aKidsIndex.push_back( nNewWidget );
388                             m_aWidgets[nNewWidget].m_nParent = rParentField.m_nObject;
389                         }
390                     }
391                 }
392             }
393             else if( m_aWidgets[it->second].m_eType != PDFWriter::Hierarchy )
394             {
395                 // this is invalid, someone tries to have a terminal field as parent
396                 // example: a button with the name foo.bar exists and
397                 // another button is named foo.bar.no
398                 // workaround: put the second terminal field as much up in the hierarchy as
399                 // necessary to have a non-terminal field as parent (or none at all)
400                 // since it->second already is terminal, we just need to use its parent
401                 aDomain.clear();
402                 aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
403                 if( nLastTokenIndex > 0 )
404                 {
405                     aDomain = aFullName.copy( 0, nLastTokenIndex-1 );
406                     aFullName = aDomain + "." + aPartialName;
407                 }
408                 else
409                     aFullName = aPartialName;
410                 break;
411             }
412         }
413     } while( nTokenIndex != -1 );
414 
415     // insert widget into its hierarchy field
416     if( !aDomain.isEmpty() )
417     {
418         std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
419         if( it != m_aFieldNameMap.end() )
420         {
421             OSL_ENSURE( it->second >= 0 && o3tl::make_unsigned(it->second) < m_aWidgets.size(), "invalid field index" );
422             if( it->second >= 0 && o3tl::make_unsigned(it->second) < m_aWidgets.size() )
423             {
424                 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[it->second].m_nObject;
425                 m_aWidgets[it->second].m_aKids.push_back( m_aWidgets[i_nWidgetIndex].m_nObject);
426                 m_aWidgets[it->second].m_aKidsIndex.push_back( i_nWidgetIndex );
427             }
428         }
429     }
430 
431     if( aPartialName.isEmpty() )
432     {
433         // how funny, an empty field name
434         if( i_rControl.getType() == PDFWriter::RadioButton )
435         {
436             aPartialName  = "RadioGroup" +
437                 OString::number( static_cast<const PDFWriter::RadioButtonWidget&>(i_rControl).RadioGroup );
438         }
439         else
440             aPartialName = OString( "Widget" );
441     }
442 
443     if( ! m_aContext.AllowDuplicateFieldNames )
444     {
445         std::unordered_map<OString, sal_Int32>::iterator it = m_aFieldNameMap.find( aFullName );
446 
447         if( it != m_aFieldNameMap.end() ) // not unique
448         {
449             std::unordered_map< OString, sal_Int32 >::const_iterator check_it;
450             OString aTry;
451             sal_Int32 nTry = 2;
452             do
453             {
454                 aTry = aFullName + "_" + OString::number(nTry++);
455                 check_it = m_aFieldNameMap.find( aTry );
456             } while( check_it != m_aFieldNameMap.end() );
457             aFullName = aTry;
458             m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
459             aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
460         }
461         else
462             m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
463     }
464 
465     // finally
466     m_aWidgets[i_nWidgetIndex].m_aName = aPartialName;
467 }
468 
469 namespace
470 {
471 
472 void appendFixedInt( sal_Int32 nValue, OStringBuffer& rBuffer )
473 {
474     if( nValue < 0 )
475     {
476         rBuffer.append( '-' );
477         nValue = -nValue;
478     }
479     sal_Int32 nFactor = 1, nDiv = nLog10Divisor;
480     while( nDiv-- )
481         nFactor *= 10;
482 
483     sal_Int32 nInt = nValue / nFactor;
484     rBuffer.append( nInt );
485     if (nFactor > 1 && nValue % nFactor)
486     {
487         rBuffer.append( '.' );
488         do
489         {
490             nFactor /= 10;
491             rBuffer.append((nValue / nFactor) % 10);
492         }
493         while (nFactor > 1 && nValue % nFactor); // omit trailing zeros
494     }
495 }
496 
497 // appends a double. PDF does not accept exponential format, only fixed point
498 void appendDouble( double fValue, OStringBuffer& rBuffer, sal_Int32 nPrecision = 10 )
499 {
500     bool bNeg = false;
501     if( fValue < 0.0 )
502     {
503         bNeg = true;
504         fValue=-fValue;
505     }
506 
507     sal_Int64 nInt = static_cast<sal_Int64>(fValue);
508     fValue -= static_cast<double>(nInt);
509     // optimizing hardware may lead to a value of 1.0 after the subtraction
510     if( rtl::math::approxEqual(fValue, 1.0) || log10( 1.0-fValue ) <= -nPrecision )
511     {
512         nInt++;
513         fValue = 0.0;
514     }
515     sal_Int64 nFrac = 0;
516     if( fValue )
517     {
518         fValue *= pow( 10.0, static_cast<double>(nPrecision) );
519         nFrac = static_cast<sal_Int64>(fValue);
520     }
521     if( bNeg && ( nInt || nFrac ) )
522         rBuffer.append( '-' );
523     rBuffer.append( nInt );
524     if( !nFrac )
525         return;
526 
527     int i;
528     rBuffer.append( '.' );
529     sal_Int64 nBound = static_cast<sal_Int64>(pow( 10.0, nPrecision - 1.0 )+0.5);
530     for ( i = 0; ( i < nPrecision ) && nFrac; i++ )
531     {
532         sal_Int64 nNumb = nFrac / nBound;
533         nFrac -= nNumb * nBound;
534         rBuffer.append( nNumb );
535         nBound /= 10;
536     }
537 }
538 
539 void appendColor( const Color& rColor, OStringBuffer& rBuffer, bool bConvertToGrey )
540 {
541 
542     if( rColor == COL_TRANSPARENT )
543         return;
544 
545     if( bConvertToGrey )
546     {
547         sal_uInt8 cByte = rColor.GetLuminance();
548         appendDouble( static_cast<double>(cByte) / 255.0, rBuffer );
549     }
550     else
551     {
552         appendDouble( static_cast<double>(rColor.GetRed()) / 255.0, rBuffer );
553         rBuffer.append( ' ' );
554         appendDouble( static_cast<double>(rColor.GetGreen()) / 255.0, rBuffer );
555         rBuffer.append( ' ' );
556         appendDouble( static_cast<double>(rColor.GetBlue()) / 255.0, rBuffer );
557     }
558 }
559 
560 } // end anonymous namespace
561 
562 void PDFWriterImpl::appendStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
563 {
564     if( rColor != COL_TRANSPARENT )
565     {
566         bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
567         appendColor( rColor, rBuffer, bGrey );
568         rBuffer.append( bGrey ? " G" : " RG" );
569     }
570 }
571 
572 void PDFWriterImpl::appendNonStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
573 {
574     if( rColor != COL_TRANSPARENT )
575     {
576         bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
577         appendColor( rColor, rBuffer, bGrey );
578         rBuffer.append( bGrey ? " g" : " rg" );
579     }
580 }
581 
582 namespace
583 {
584 
585 void appendPdfTimeDate(OStringBuffer & rBuffer,
586     sal_Int16 year, sal_uInt16 month, sal_uInt16 day, sal_uInt16 hours, sal_uInt16 minutes, sal_uInt16 seconds, sal_Int32 tzDelta)
587 {
588     rBuffer.append("D:");
589     rBuffer.append(char('0' + ((year / 1000) % 10)));
590     rBuffer.append(char('0' + ((year / 100) % 10)));
591     rBuffer.append(char('0' + ((year / 10) % 10)));
592     rBuffer.append(char('0' + (year % 10)));
593     rBuffer.append(char('0' + ((month / 10) % 10)));
594     rBuffer.append(char('0' + (month % 10)));
595     rBuffer.append(char('0' + ((day / 10) % 10)));
596     rBuffer.append(char('0' + (day % 10)));
597     rBuffer.append(char('0' + ((hours / 10) % 10)));
598     rBuffer.append(char('0' + (hours % 10)));
599     rBuffer.append(char('0' + ((minutes / 10) % 10)));
600     rBuffer.append(char('0' + (minutes % 10)));
601     rBuffer.append(char('0' + ((seconds / 10) % 10)));
602     rBuffer.append(char('0' + (seconds % 10)));
603 
604     if (tzDelta == 0)
605     {
606         rBuffer.append("Z");
607     }
608     else
609     {
610         if (tzDelta > 0 )
611             rBuffer.append("+");
612         else
613         {
614             rBuffer.append("-");
615             tzDelta = -tzDelta;
616         }
617 
618         rBuffer.append(char('0' + ((tzDelta / 36000) % 10)));
619         rBuffer.append(char('0' + ((tzDelta / 3600) % 10)));
620         rBuffer.append("'");
621         rBuffer.append(char('0' + ((tzDelta / 600) % 6)));
622         rBuffer.append(char('0' + ((tzDelta / 60) % 10)));
623     }
624 }
625 
626 } // end namespace
627 
628 PDFPage::PDFPage( PDFWriterImpl* pWriter, double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
629         :
630         m_pWriter( pWriter ),
631         m_nPageWidth( nPageWidth ),
632         m_nPageHeight( nPageHeight ),
633         m_nUserUnit( 1 ),
634         m_eOrientation( eOrientation ),
635         m_nPageObject( 0 ),  // invalid object number
636         m_nStreamLengthObject( 0 ),
637         m_nBeginStreamPos( 0 ),
638         m_eTransition( PDFWriter::PageTransition::Regular ),
639         m_nTransTime( 0 )
640 {
641     // object ref must be only ever updated in emit()
642     m_nPageObject = m_pWriter->createObject();
643 
644     switch (m_pWriter->m_aContext.Version)
645     {
646         // 1.6 or later
647         default:
648             m_nUserUnit = std::ceil(std::max(nPageWidth, nPageHeight) / 14400.0);
649             break;
650         case PDFWriter::PDFVersion::PDF_1_4:
651         case PDFWriter::PDFVersion::PDF_1_5:
652         case PDFWriter::PDFVersion::PDF_A_1:
653             break;
654     }
655 }
656 
657 void PDFPage::beginStream()
658 {
659     if (g_bDebugDisableCompression)
660     {
661         m_pWriter->emitComment("PDFWriterImpl::PDFPage::beginStream, +");
662     }
663     m_aStreamObjects.push_back(m_pWriter->createObject());
664     if( ! m_pWriter->updateObject( m_aStreamObjects.back() ) )
665         return;
666 
667     m_nStreamLengthObject = m_pWriter->createObject();
668     // write content stream header
669     OStringBuffer aLine(
670         OString::number(m_aStreamObjects.back())
671         + " 0 obj\n<</Length "
672         + OString::number( m_nStreamLengthObject )
673         + " 0 R" );
674     if (!g_bDebugDisableCompression)
675         aLine.append( "/Filter/FlateDecode" );
676     aLine.append( ">>\nstream\n" );
677     if( ! m_pWriter->writeBuffer( aLine ) )
678         return;
679     if (osl::File::E_None != m_pWriter->m_aFile.getPos(m_nBeginStreamPos))
680     {
681         m_pWriter->m_aFile.close();
682         m_pWriter->m_bOpen = false;
683     }
684     if (!g_bDebugDisableCompression)
685         m_pWriter->beginCompression();
686     m_pWriter->checkAndEnableStreamEncryption( m_aStreamObjects.back() );
687 }
688 
689 void PDFPage::endStream()
690 {
691     if (!g_bDebugDisableCompression)
692         m_pWriter->endCompression();
693     sal_uInt64 nEndStreamPos;
694     if (osl::File::E_None != m_pWriter->m_aFile.getPos(nEndStreamPos))
695     {
696         m_pWriter->m_aFile.close();
697         m_pWriter->m_bOpen = false;
698         return;
699     }
700     m_pWriter->disableStreamEncryption();
701     if( ! m_pWriter->writeBuffer( "\nendstream\nendobj\n\n" ) )
702         return;
703     // emit stream length object
704     if( ! m_pWriter->updateObject( m_nStreamLengthObject ) )
705         return;
706     OString aLine =
707         OString::number( m_nStreamLengthObject ) +
708         " 0 obj\n"  +
709         OString::number( static_cast<sal_Int64>(nEndStreamPos-m_nBeginStreamPos) ) +
710         "\nendobj\n\n";
711     m_pWriter->writeBuffer( aLine );
712 }
713 
714 bool PDFPage::emit(sal_Int32 nParentObject )
715 {
716     m_pWriter->MARK("PDFPage::emit");
717     // emit page object
718     if( ! m_pWriter->updateObject( m_nPageObject ) )
719         return false;
720     OStringBuffer aLine(
721         OString::number(m_nPageObject)
722         + " 0 obj\n"
723           "<</Type/Page/Parent "
724         + OString::number(nParentObject)
725         + " 0 R"
726         "/Resources "
727         + OString::number(m_pWriter->getResourceDictObj())
728         + " 0 R" );
729     if( m_nPageWidth && m_nPageHeight )
730     {
731         aLine.append( "/MediaBox[0 0 "
732             + OString::number(m_nPageWidth / m_nUserUnit)
733             + " "
734             + OString::number(m_nPageHeight / m_nUserUnit)
735             + "]" );
736         if (m_nUserUnit > 1)
737         {
738             aLine.append("\n/UserUnit " + OString::number(m_nUserUnit));
739         }
740     }
741     switch( m_eOrientation )
742     {
743         case PDFWriter::Orientation::Portrait: aLine.append( "/Rotate 0\n" );break;
744         case PDFWriter::Orientation::Inherit:  break;
745     }
746     int nAnnots = m_aAnnotations.size();
747     if( nAnnots > 0 )
748     {
749         aLine.append( "/Annots[\n" );
750         for( int i = 0; i < nAnnots; i++ )
751         {
752             aLine.append( OString::number(m_aAnnotations[i])
753                 + " 0 R" );
754             aLine.append( ((i+1)%15) ? " " : "\n" );
755         }
756         aLine.append( "]\n" );
757         if (PDFWriter::PDFVersion::PDF_1_5 <= m_pWriter->m_aContext.Version)
758         {
759             // ISO 14289-1:2014, Clause: 7.18.3
760             aLine.append( "/Tabs/S\n" );
761         }
762     }
763     if( !m_aMCIDParents.empty() )
764     {
765         OStringBuffer aStructParents( 1024 );
766         aStructParents.append( "[ " );
767         int nParents = m_aMCIDParents.size();
768         for( int i = 0; i < nParents; i++ )
769         {
770             aStructParents.append( OString::number(m_aMCIDParents[i])
771                 + " 0 R" );
772             aStructParents.append( ((i%10) == 9) ? "\n" : " " );
773         }
774         aStructParents.append( "]" );
775         m_pWriter->m_aStructParentTree.push_back( aStructParents.makeStringAndClear() );
776 
777         aLine.append( "/StructParents "
778             + OString::number( sal_Int32(m_pWriter->m_aStructParentTree.size()-1) )
779             + "\n" );
780     }
781     if( m_eTransition != PDFWriter::PageTransition::Regular && m_nTransTime > 0 )
782     {
783         // transition duration
784         aLine.append( "/Trans<</D " );
785         appendDouble( static_cast<double>(m_nTransTime)/1000.0, aLine, 3 );
786         aLine.append( "\n" );
787         const char *pStyle = nullptr, *pDm = nullptr, *pM = nullptr, *pDi = nullptr;
788         switch( m_eTransition )
789         {
790             case PDFWriter::PageTransition::SplitHorizontalInward:
791                 pStyle = "Split"; pDm = "H"; pM = "I"; break;
792             case PDFWriter::PageTransition::SplitHorizontalOutward:
793                 pStyle = "Split"; pDm = "H"; pM = "O"; break;
794             case PDFWriter::PageTransition::SplitVerticalInward:
795                 pStyle = "Split"; pDm = "V"; pM = "I"; break;
796             case PDFWriter::PageTransition::SplitVerticalOutward:
797                 pStyle = "Split"; pDm = "V"; pM = "O"; break;
798             case PDFWriter::PageTransition::BlindsHorizontal:
799                 pStyle = "Blinds"; pDm = "H"; break;
800             case PDFWriter::PageTransition::BlindsVertical:
801                 pStyle = "Blinds"; pDm = "V"; break;
802             case PDFWriter::PageTransition::BoxInward:
803                 pStyle = "Box"; pM = "I"; break;
804             case PDFWriter::PageTransition::BoxOutward:
805                 pStyle = "Box"; pM = "O"; break;
806             case PDFWriter::PageTransition::WipeLeftToRight:
807                 pStyle = "Wipe"; pDi = "0"; break;
808             case PDFWriter::PageTransition::WipeBottomToTop:
809                 pStyle = "Wipe"; pDi = "90"; break;
810             case PDFWriter::PageTransition::WipeRightToLeft:
811                 pStyle = "Wipe"; pDi = "180"; break;
812             case PDFWriter::PageTransition::WipeTopToBottom:
813                 pStyle = "Wipe"; pDi = "270"; break;
814             case PDFWriter::PageTransition::Dissolve:
815                 pStyle = "Dissolve"; break;
816             case PDFWriter::PageTransition::Regular:
817                 break;
818         }
819         // transition style
820         if( pStyle )
821         {
822             aLine.append( OString::Concat("/S/") + pStyle + "\n" );
823         }
824         if( pDm )
825         {
826             aLine.append( OString::Concat("/Dm/") + pDm + "\n" );
827         }
828         if( pM )
829         {
830             aLine.append( OString::Concat("/M/") + pM + "\n" );
831         }
832         if( pDi  )
833         {
834             aLine.append( OString::Concat("/Di ") + pDi + "\n" );
835         }
836         aLine.append( ">>\n" );
837     }
838 
839     aLine.append( "/Contents" );
840     unsigned int nStreamObjects = m_aStreamObjects.size();
841     if( nStreamObjects > 1 )
842         aLine.append( '[' );
843     for(sal_Int32 i : m_aStreamObjects)
844     {
845         aLine.append( " " + OString::number( i ) + " 0 R" );
846     }
847     if( nStreamObjects > 1 )
848         aLine.append( ']' );
849     aLine.append( ">>\nendobj\n\n" );
850     return m_pWriter->writeBuffer( aLine );
851 }
852 
853 void PDFPage::appendPoint( const Point& rPoint, OStringBuffer& rBuffer ) const
854 {
855     Point aPoint( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
856                                m_pWriter->m_aMapMode,
857                                m_pWriter,
858                                rPoint ) );
859 
860     sal_Int32 nValue    = aPoint.X();
861 
862     appendFixedInt( nValue, rBuffer );
863 
864     rBuffer.append( ' ' );
865 
866     nValue      = pointToPixel(getHeight()) - aPoint.Y();
867 
868     appendFixedInt( nValue, rBuffer );
869 }
870 
871 void PDFPage::appendPixelPoint( const basegfx::B2DPoint& rPoint, OStringBuffer& rBuffer ) const
872 {
873     double fValue   = pixelToPoint(rPoint.getX());
874 
875     appendDouble( fValue, rBuffer, nLog10Divisor );
876     rBuffer.append( ' ' );
877     fValue      = getHeight() - pixelToPoint(rPoint.getY());
878     appendDouble( fValue, rBuffer, nLog10Divisor );
879 }
880 
881 void PDFPage::appendRect( const tools::Rectangle& rRect, OStringBuffer& rBuffer ) const
882 {
883     appendPoint( rRect.BottomLeft() + Point( 0, 1 ), rBuffer );
884     rBuffer.append( ' ' );
885     appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), rBuffer, false );
886     rBuffer.append( ' ' );
887     appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), rBuffer );
888     rBuffer.append( " re" );
889 }
890 
891 void PDFPage::convertRect( tools::Rectangle& rRect ) const
892 {
893     Point aLL = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
894                              m_pWriter->m_aMapMode,
895                              m_pWriter,
896                              rRect.BottomLeft() + Point( 0, 1 )
897                              );
898     Size aSize = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
899                               m_pWriter->m_aMapMode,
900                               m_pWriter,
901                               rRect.GetSize() );
902     rRect.SetLeft( aLL.X() );
903     rRect.SetRight( aLL.X() + aSize.Width() );
904     rRect.SetTop( pointToPixel(getHeight()) - aLL.Y() );
905     rRect.SetBottom( rRect.Top() + aSize.Height() );
906 }
907 
908 void PDFPage::appendPolygon( const tools::Polygon& rPoly, OStringBuffer& rBuffer, bool bClose ) const
909 {
910     sal_uInt16 nPoints = rPoly.GetSize();
911     /*
912      *  #108582# applications do weird things
913      */
914     sal_uInt32 nBufLen = rBuffer.getLength();
915     if( nPoints <= 0 )
916         return;
917 
918     const PolyFlags* pFlagArray = rPoly.GetConstFlagAry();
919     appendPoint( rPoly[0], rBuffer );
920     rBuffer.append( " m\n" );
921     for( sal_uInt16 i = 1; i < nPoints; i++ )
922     {
923         if( pFlagArray && pFlagArray[i] == PolyFlags::Control && nPoints-i > 2 )
924         {
925             // bezier
926             SAL_WARN_IF( pFlagArray[i+1] != PolyFlags::Control || pFlagArray[i+2] == PolyFlags::Control, "vcl.pdfwriter", "unexpected sequence of control points" );
927             appendPoint( rPoly[i], rBuffer );
928             rBuffer.append( " " );
929             appendPoint( rPoly[i+1], rBuffer );
930             rBuffer.append( " " );
931             appendPoint( rPoly[i+2], rBuffer );
932             rBuffer.append( " c" );
933             i += 2; // add additionally consumed points
934         }
935         else
936         {
937             // line
938             appendPoint( rPoly[i], rBuffer );
939             rBuffer.append( " l" );
940         }
941         if( (rBuffer.getLength() - nBufLen) > 65 )
942         {
943             rBuffer.append( "\n" );
944             nBufLen = rBuffer.getLength();
945         }
946         else
947             rBuffer.append( " " );
948     }
949     if( bClose )
950         rBuffer.append( "h\n" );
951 }
952 
953 void PDFPage::appendPolygon( const basegfx::B2DPolygon& rPoly, OStringBuffer& rBuffer ) const
954 {
955     basegfx::B2DPolygon aPoly( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
956                                             m_pWriter->m_aMapMode,
957                                             m_pWriter,
958                                             rPoly ) );
959 
960     if( basegfx::utils::isRectangle( aPoly ) )
961     {
962         basegfx::B2DRange aRange( aPoly.getB2DRange() );
963         basegfx::B2DPoint aBL( aRange.getMinX(), aRange.getMaxY() );
964         appendPixelPoint( aBL, rBuffer );
965         rBuffer.append( ' ' );
966         appendMappedLength( aRange.getWidth(), rBuffer, false, nLog10Divisor );
967         rBuffer.append( ' ' );
968         appendMappedLength( aRange.getHeight(), rBuffer, true, nLog10Divisor );
969         rBuffer.append( " re\n" );
970         return;
971     }
972     sal_uInt32 nPoints = aPoly.count();
973     if( nPoints <= 0 )
974         return;
975 
976     sal_uInt32 nBufLen = rBuffer.getLength();
977     basegfx::B2DPoint aLastPoint( aPoly.getB2DPoint( 0 ) );
978     appendPixelPoint( aLastPoint, rBuffer );
979     rBuffer.append( " m\n" );
980     for( sal_uInt32 i = 1; i <= nPoints; i++ )
981     {
982         if( i != nPoints || aPoly.isClosed() )
983         {
984             sal_uInt32 nCurPoint  = i % nPoints;
985             sal_uInt32 nLastPoint = i-1;
986             basegfx::B2DPoint aPoint( aPoly.getB2DPoint( nCurPoint ) );
987             if( aPoly.isNextControlPointUsed( nLastPoint ) &&
988                 aPoly.isPrevControlPointUsed( nCurPoint ) )
989             {
990                 appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer );
991                 rBuffer.append( ' ' );
992                 appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer );
993                 rBuffer.append( ' ' );
994                 appendPixelPoint( aPoint, rBuffer );
995                 rBuffer.append( " c" );
996             }
997             else if( aPoly.isNextControlPointUsed( nLastPoint ) )
998             {
999                 appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer );
1000                 rBuffer.append( ' ' );
1001                 appendPixelPoint( aPoint, rBuffer );
1002                 rBuffer.append( " y" );
1003             }
1004             else if( aPoly.isPrevControlPointUsed( nCurPoint ) )
1005             {
1006                 appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer );
1007                 rBuffer.append( ' ' );
1008                 appendPixelPoint( aPoint, rBuffer );
1009                 rBuffer.append( " v" );
1010             }
1011             else
1012             {
1013                 appendPixelPoint( aPoint, rBuffer );
1014                 rBuffer.append( " l" );
1015             }
1016             if( (rBuffer.getLength() - nBufLen) > 65 )
1017             {
1018                 rBuffer.append( "\n" );
1019                 nBufLen = rBuffer.getLength();
1020             }
1021             else
1022                 rBuffer.append( " " );
1023         }
1024     }
1025     rBuffer.append( "h\n" );
1026 }
1027 
1028 void PDFPage::appendPolyPolygon( const tools::PolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
1029 {
1030     sal_uInt16 nPolygons = rPolyPoly.Count();
1031     for( sal_uInt16 n = 0; n < nPolygons; n++ )
1032         appendPolygon( rPolyPoly[n], rBuffer );
1033 }
1034 
1035 void PDFPage::appendPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
1036 {
1037     for(auto const& rPolygon : rPolyPoly)
1038         appendPolygon( rPolygon, rBuffer );
1039 }
1040 
1041 void PDFPage::appendMappedLength( sal_Int32 nLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32* pOutLength ) const
1042 {
1043     sal_Int32 nValue = nLength;
1044     if ( nLength < 0 )
1045     {
1046         rBuffer.append( '-' );
1047         nValue = -nLength;
1048     }
1049     Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1050                              m_pWriter->m_aMapMode,
1051                              m_pWriter,
1052                              Size( nValue, nValue ) ) );
1053     nValue = bVertical ? aSize.Height() : aSize.Width();
1054     if( pOutLength )
1055         *pOutLength = ((nLength < 0 ) ? -nValue : nValue);
1056 
1057     appendFixedInt( nValue, rBuffer );
1058 }
1059 
1060 void PDFPage::appendMappedLength( double fLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32 nPrecision ) const
1061 {
1062     Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1063                              m_pWriter->m_aMapMode,
1064                              m_pWriter,
1065                              Size( 1000, 1000 ) ) );
1066     fLength *= pixelToPoint(static_cast<double>(bVertical ? aSize.Height() : aSize.Width()) / 1000.0);
1067     appendDouble( fLength, rBuffer, nPrecision );
1068 }
1069 
1070 bool PDFPage::appendLineInfo( const LineInfo& rInfo, OStringBuffer& rBuffer ) const
1071 {
1072     if(LineStyle::Dash == rInfo.GetStyle() && rInfo.GetDashLen() != rInfo.GetDotLen())
1073     {
1074         // dashed and non-degraded case, check for implementation limits of dash array
1075         // in PDF reader apps (e.g. acroread)
1076         if(2 * (rInfo.GetDashCount() + rInfo.GetDotCount()) > 10)
1077         {
1078             return false;
1079         }
1080     }
1081 
1082     if(basegfx::B2DLineJoin::NONE != rInfo.GetLineJoin())
1083     {
1084         // LineJoin used, ExtLineInfo required
1085         return false;
1086     }
1087 
1088     if(css::drawing::LineCap_BUTT != rInfo.GetLineCap())
1089     {
1090         // LineCap used, ExtLineInfo required
1091         return false;
1092     }
1093 
1094     if( rInfo.GetStyle() == LineStyle::Dash )
1095     {
1096         rBuffer.append( "[ " );
1097         if( rInfo.GetDashLen() == rInfo.GetDotLen() ) // degraded case
1098         {
1099             appendMappedLength( rInfo.GetDashLen(), rBuffer );
1100             rBuffer.append( ' ' );
1101             appendMappedLength( rInfo.GetDistance(), rBuffer );
1102             rBuffer.append( ' ' );
1103         }
1104         else
1105         {
1106             for( int n = 0; n < rInfo.GetDashCount(); n++ )
1107             {
1108                 appendMappedLength( rInfo.GetDashLen(), rBuffer );
1109                 rBuffer.append( ' ' );
1110                 appendMappedLength( rInfo.GetDistance(), rBuffer );
1111                 rBuffer.append( ' ' );
1112             }
1113             for( int m = 0; m < rInfo.GetDotCount(); m++ )
1114             {
1115                 appendMappedLength( rInfo.GetDotLen(), rBuffer );
1116                 rBuffer.append( ' ' );
1117                 appendMappedLength( rInfo.GetDistance(), rBuffer );
1118                 rBuffer.append( ' ' );
1119             }
1120         }
1121         rBuffer.append( "] 0 d\n" );
1122     }
1123 
1124     if( rInfo.GetWidth() > 1 )
1125     {
1126         appendMappedLength( rInfo.GetWidth(), rBuffer );
1127         rBuffer.append( " w\n" );
1128     }
1129     else if( rInfo.GetWidth() == 0 )
1130     {
1131         // "pixel" line
1132         appendDouble( 72.0/double(m_pWriter->GetDPIX()), rBuffer );
1133         rBuffer.append( " w\n" );
1134     }
1135 
1136     return true;
1137 }
1138 
1139 void PDFPage::appendWaveLine( sal_Int32 nWidth, sal_Int32 nY, sal_Int32 nDelta, OStringBuffer& rBuffer ) const
1140 {
1141     if( nWidth <= 0 )
1142         return;
1143     if( nDelta < 1 )
1144         nDelta = 1;
1145 
1146     rBuffer.append( "0 " );
1147     appendMappedLength( nY, rBuffer );
1148     rBuffer.append( " m\n" );
1149     for( sal_Int32 n = 0; n < nWidth; )
1150     {
1151         n += nDelta;
1152         appendMappedLength( n, rBuffer, false );
1153         rBuffer.append( ' ' );
1154         appendMappedLength( nDelta+nY, rBuffer );
1155         rBuffer.append( ' ' );
1156         n += nDelta;
1157         appendMappedLength( n, rBuffer, false );
1158         rBuffer.append( ' ' );
1159         appendMappedLength( nY, rBuffer );
1160         rBuffer.append( " v " );
1161         if( n < nWidth )
1162         {
1163             n += nDelta;
1164             appendMappedLength( n, rBuffer, false );
1165             rBuffer.append( ' ' );
1166             appendMappedLength( nY-nDelta, rBuffer );
1167             rBuffer.append( ' ' );
1168             n += nDelta;
1169             appendMappedLength( n, rBuffer, false );
1170             rBuffer.append( ' ' );
1171             appendMappedLength( nY, rBuffer );
1172             rBuffer.append( " v\n" );
1173         }
1174     }
1175     rBuffer.append( "S\n" );
1176 }
1177 
1178 void PDFPage::appendMatrix3(Matrix3 const & rMatrix, OStringBuffer& rBuffer)
1179 {
1180     appendDouble(rMatrix.get(0), rBuffer);
1181     rBuffer.append(' ');
1182     appendDouble(rMatrix.get(1), rBuffer);
1183     rBuffer.append(' ');
1184     appendDouble(rMatrix.get(2), rBuffer);
1185     rBuffer.append(' ');
1186     appendDouble(rMatrix.get(3), rBuffer);
1187     rBuffer.append(' ');
1188     appendPoint(Point(tools::Long(rMatrix.get(4)), tools::Long(rMatrix.get(5))), rBuffer);
1189 }
1190 
1191 double PDFPage::getHeight() const
1192 {
1193     double fRet = m_nPageHeight ? m_nPageHeight : vcl::pdf::g_nInheritedPageHeight;
1194 
1195     if (m_nUserUnit > 1)
1196     {
1197         fRet /= m_nUserUnit;
1198     }
1199 
1200     return fRet;
1201 }
1202 
1203 PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext,
1204                                const css::uno::Reference< css::beans::XMaterialHolder >& xEnc,
1205                                PDFWriter& i_rOuterFace)
1206         : VirtualDevice(Application::GetDefaultDevice(), DeviceFormat::WITHOUT_ALPHA, OUTDEV_PDF),
1207         m_aMapMode( MapUnit::MapPoint, Point(), Fraction( 1, pointToPixel(1) ), Fraction( 1, pointToPixel(1) ) ),
1208         m_aWidgetStyleSettings(Application::GetSettings().GetStyleSettings()),
1209         m_nCurrentStructElement( 0 ),
1210         m_bEmitStructure( true ),
1211         m_nNextFID( 1 ),
1212         m_aPDFBmpCache(utl::ConfigManager::IsFuzzing() ? 15 :
1213             officecfg::Office::Common::VCL::PDFExportImageCacheSize::get()),
1214         m_nCurrentPage( -1 ),
1215         m_nCatalogObject(0),
1216         m_nSignatureObject( -1 ),
1217         m_nSignatureContentOffset( 0 ),
1218         m_nSignatureLastByteRangeNoOffset( 0 ),
1219         m_nResourceDict( -1 ),
1220         m_nFontDictObject( -1 ),
1221         m_aContext(rContext),
1222         m_aFile(m_aContext.URL),
1223         m_bOpen(false),
1224         m_DocDigest(::comphelper::HashType::MD5),
1225         m_aCipher( nullptr ),
1226         m_nKeyLength(0),
1227         m_nRC4KeyLength(0),
1228         m_bEncryptThisStream( false ),
1229         m_nAccessPermissions(0),
1230         m_bIsPDF_A1( false ),
1231         m_bIsPDF_A2( false ),
1232         m_bIsPDF_UA( false ),
1233         m_bIsPDF_A3( false ),
1234         m_rOuterFace( i_rOuterFace )
1235 {
1236     m_aStructure.emplace_back( );
1237     m_aStructure[0].m_nOwnElement       = 0;
1238     m_aStructure[0].m_nParentElement    = 0;
1239     //m_StructElementStack.push(0);
1240 
1241     Font aFont;
1242     aFont.SetFamilyName( "Times" );
1243     aFont.SetFontSize( Size( 0, 12 ) );
1244 
1245     // tdf#150786 use the same settings for widgets regardless of theme
1246     m_aWidgetStyleSettings.SetStandardStyles();
1247 
1248     GraphicsState aState;
1249     aState.m_aMapMode       = m_aMapMode;
1250     aState.m_aFont          = aFont;
1251     m_aGraphicsStack.push_front( aState );
1252 
1253     osl::File::RC aError = m_aFile.open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create);
1254     if (aError != osl::File::E_None)
1255     {
1256         if (aError == osl::File::E_EXIST)
1257         {
1258             aError = m_aFile.open(osl_File_OpenFlag_Write);
1259             if (aError == osl::File::E_None)
1260                 aError = m_aFile.setSize(0);
1261         }
1262     }
1263     if (aError != osl::File::E_None)
1264         return;
1265 
1266     m_bOpen = true;
1267 
1268     // setup DocInfo
1269     setupDocInfo();
1270 
1271     /* prepare the cypher engine, can be done in CTOR, free in DTOR */
1272     m_aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream );
1273 
1274     if( xEnc.is() )
1275         prepareEncryption( xEnc );
1276 
1277     if( m_aContext.Encryption.Encrypt() )
1278     {
1279         // sanity check
1280         if( m_aContext.Encryption.OValue.size() != ENCRYPTED_PWD_SIZE ||
1281             m_aContext.Encryption.UValue.size() != ENCRYPTED_PWD_SIZE ||
1282             m_aContext.Encryption.EncryptionKey.size() != MAXIMUM_RC4_KEY_LENGTH
1283            )
1284         {
1285             // the field lengths are invalid ? This was not setup by initEncryption.
1286             // do not encrypt after all
1287             m_aContext.Encryption.OValue.clear();
1288             m_aContext.Encryption.UValue.clear();
1289             OSL_ENSURE( false, "encryption data failed sanity check, encryption disabled" );
1290         }
1291         else // setup key lengths
1292             m_nAccessPermissions = computeAccessPermissions( m_aContext.Encryption, m_nKeyLength, m_nRC4KeyLength );
1293     }
1294 
1295     // write header
1296     OStringBuffer aBuffer( 20 );
1297     aBuffer.append( "%PDF-" );
1298     switch( m_aContext.Version )
1299     {
1300         case PDFWriter::PDFVersion::PDF_A_1:
1301         case PDFWriter::PDFVersion::PDF_1_4: aBuffer.append( "1.4" );break;
1302         case PDFWriter::PDFVersion::PDF_1_5: aBuffer.append( "1.5" );break;
1303         case PDFWriter::PDFVersion::PDF_1_6: aBuffer.append( "1.6" );break;
1304         default:
1305         case PDFWriter::PDFVersion::PDF_A_2:
1306         case PDFWriter::PDFVersion::PDF_A_3:
1307         case PDFWriter::PDFVersion::PDF_1_7: aBuffer.append( "1.7" );break;
1308     }
1309     // append something binary as comment (suggested in PDF Reference)
1310     aBuffer.append( "\n%\303\244\303\274\303\266\303\237\n" );
1311     if( !writeBuffer( aBuffer ) )
1312     {
1313         m_aFile.close();
1314         m_bOpen = false;
1315         return;
1316     }
1317 
1318     // insert outline root
1319     m_aOutline.emplace_back( );
1320 
1321     m_bIsPDF_A1 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_1);
1322     if( m_bIsPDF_A1 )
1323         m_aContext.Version = PDFWriter::PDFVersion::PDF_1_4; //meaning we need PDF 1.4, PDF/A flavour
1324 
1325     m_bIsPDF_A2 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_2);
1326     if( m_bIsPDF_A2 )
1327         m_aContext.Version = PDFWriter::PDFVersion::PDF_1_7;
1328 
1329     m_bIsPDF_A3 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_3);
1330     if( m_bIsPDF_A3 )
1331         m_aContext.Version = PDFWriter::PDFVersion::PDF_1_7;
1332 
1333     if (m_aContext.UniversalAccessibilityCompliance)
1334     {
1335         m_bIsPDF_UA = true;
1336         m_aContext.Tagged = true;
1337     }
1338 
1339     if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 )
1340         SetReferenceDevice( VirtualDevice::RefDevMode::PDF1 );
1341     else
1342         SetReferenceDevice( m_aContext.DPIx, m_aContext.DPIy );
1343 
1344     SetOutputSizePixel( Size( 640, 480 ) );
1345     SetMapMode(MapMode(MapUnit::MapMM));
1346 }
1347 
1348 PDFWriterImpl::~PDFWriterImpl()
1349 {
1350     disposeOnce();
1351 }
1352 
1353 void PDFWriterImpl::dispose()
1354 {
1355     if( m_aCipher )
1356         rtl_cipher_destroyARCFOUR( m_aCipher );
1357     m_aPages.clear();
1358     VirtualDevice::dispose();
1359 }
1360 
1361 bool PDFWriterImpl::ImplNewFont() const
1362 {
1363     const ImplSVData* pSVData = ImplGetSVData();
1364 
1365     if( mxFontCollection == pSVData->maGDIData.mxScreenFontList
1366         ||  mxFontCache == pSVData->maGDIData.mxScreenFontCache )
1367     {
1368         const_cast<vcl::PDFWriterImpl&>(*this).ImplUpdateFontData();
1369     }
1370 
1371     return OutputDevice::ImplNewFont();
1372 }
1373 
1374 void PDFWriterImpl::setupDocInfo()
1375 {
1376     std::vector< sal_uInt8 > aId;
1377     m_aCreationDateString = PDFWriter::GetDateTime();
1378     computeDocumentIdentifier( aId, m_aContext.DocumentInfo, m_aCreationDateString, m_aCreationMetaDateString );
1379     if( m_aContext.Encryption.DocumentIdentifier.empty() )
1380         m_aContext.Encryption.DocumentIdentifier = aId;
1381 }
1382 
1383 OString PDFWriter::GetDateTime()
1384 {
1385     OStringBuffer aRet;
1386 
1387     TimeValue aTVal, aGMT;
1388     oslDateTime aDT;
1389     osl_getSystemTime(&aGMT);
1390     osl_getLocalTimeFromSystemTime(&aGMT, &aTVal);
1391     osl_getDateTimeFromTimeValue(&aTVal, &aDT);
1392 
1393     sal_Int32 nDelta = aTVal.Seconds-aGMT.Seconds;
1394 
1395     appendPdfTimeDate(aRet, aDT.Year, aDT.Month, aDT.Day, aDT.Hours, aDT.Minutes, aDT.Seconds, nDelta);
1396 
1397     aRet.append("'");
1398     return aRet.makeStringAndClear();
1399 }
1400 
1401 void PDFWriterImpl::computeDocumentIdentifier( std::vector< sal_uInt8 >& o_rIdentifier,
1402                                                const vcl::PDFWriter::PDFDocInfo& i_rDocInfo,
1403                                                const OString& i_rCString1,
1404                                                OString& o_rCString2
1405                                                )
1406 {
1407     o_rIdentifier.clear();
1408 
1409     //build the document id
1410     OString aInfoValuesOut;
1411     OStringBuffer aID( 1024 );
1412     if( !i_rDocInfo.Title.isEmpty() )
1413         PDFWriter::AppendUnicodeTextString(i_rDocInfo.Title, aID);
1414     if( !i_rDocInfo.Author.isEmpty() )
1415         PDFWriter::AppendUnicodeTextString(i_rDocInfo.Author, aID);
1416     if( !i_rDocInfo.Subject.isEmpty() )
1417         PDFWriter::AppendUnicodeTextString(i_rDocInfo.Subject, aID);
1418     if( !i_rDocInfo.Keywords.isEmpty() )
1419         PDFWriter::AppendUnicodeTextString(i_rDocInfo.Keywords, aID);
1420     if( !i_rDocInfo.Creator.isEmpty() )
1421         PDFWriter::AppendUnicodeTextString(i_rDocInfo.Creator, aID);
1422     if( !i_rDocInfo.Producer.isEmpty() )
1423         PDFWriter::AppendUnicodeTextString(i_rDocInfo.Producer, aID);
1424 
1425     TimeValue aTVal, aGMT;
1426     oslDateTime aDT;
1427     osl_getSystemTime( &aGMT );
1428     osl_getLocalTimeFromSystemTime( &aGMT, &aTVal );
1429     osl_getDateTimeFromTimeValue( &aTVal, &aDT );
1430     OStringBuffer aCreationMetaDateString(64);
1431 
1432     // i59651: we fill the Metadata date string as well, if PDF/A is requested
1433     // according to ISO 19005-1:2005 6.7.3 the date is corrected for
1434     // local time zone offset UTC only, whereas Acrobat 8 seems
1435     // to use the localtime notation only
1436     // according to a recommendation in XMP Specification (Jan 2004, page 75)
1437     // the Acrobat way seems the right approach
1438     aCreationMetaDateString.append(
1439         OStringChar(static_cast<char>('0' + ((aDT.Year/1000)%10)) )
1440         + OStringChar(static_cast<char>('0' + ((aDT.Year/100)%10)) )
1441         + OStringChar(static_cast<char>('0' + ((aDT.Year/10)%10)) )
1442         + OStringChar(static_cast<char>('0' + ((aDT.Year)%10)) )
1443         + "-"
1444         + OStringChar(static_cast<char>('0' + ((aDT.Month/10)%10)) )
1445         + OStringChar(static_cast<char>('0' + ((aDT.Month)%10)) )
1446         + "-"
1447         + OStringChar(static_cast<char>('0' + ((aDT.Day/10)%10)) )
1448         + OStringChar(static_cast<char>('0' + ((aDT.Day)%10)) )
1449         + "T"
1450         + OStringChar(static_cast<char>('0' + ((aDT.Hours/10)%10)) )
1451         + OStringChar(static_cast<char>('0' + ((aDT.Hours)%10)) )
1452         + ":"
1453         + OStringChar(static_cast<char>('0' + ((aDT.Minutes/10)%10)) )
1454         + OStringChar(static_cast<char>('0' + ((aDT.Minutes)%10)) )
1455         + ":"
1456         + OStringChar(static_cast<char>('0' + ((aDT.Seconds/10)%10)) )
1457         + OStringChar(static_cast<char>('0' + ((aDT.Seconds)%10)) ));
1458 
1459     sal_uInt32 nDelta = 0;
1460     if( aGMT.Seconds > aTVal.Seconds )
1461     {
1462         nDelta = aGMT.Seconds-aTVal.Seconds;
1463         aCreationMetaDateString.append( "-" );
1464     }
1465     else if( aGMT.Seconds < aTVal.Seconds )
1466     {
1467         nDelta = aTVal.Seconds-aGMT.Seconds;
1468         aCreationMetaDateString.append( "+" );
1469     }
1470     else
1471     {
1472         aCreationMetaDateString.append( "Z" );
1473 
1474     }
1475     if( nDelta )
1476     {
1477         aCreationMetaDateString.append(
1478             OStringChar(static_cast<char>('0' + ((nDelta/36000)%10)) )
1479             + OStringChar(static_cast<char>('0' + ((nDelta/3600)%10)) )
1480             + ":"
1481             + OStringChar(static_cast<char>('0' + ((nDelta/600)%6)) )
1482             + OStringChar(static_cast<char>('0' + ((nDelta/60)%10)) ));
1483     }
1484     aID.append( i_rCString1.getStr(), i_rCString1.getLength() );
1485 
1486     aInfoValuesOut = aID.makeStringAndClear();
1487     o_rCString2 = aCreationMetaDateString.makeStringAndClear();
1488 
1489     ::comphelper::Hash aDigest(::comphelper::HashType::MD5);
1490     aDigest.update(reinterpret_cast<unsigned char const*>(&aGMT), sizeof(aGMT));
1491     aDigest.update(reinterpret_cast<unsigned char const*>(aInfoValuesOut.getStr()), aInfoValuesOut.getLength());
1492     //the binary form of the doc id is needed for encryption stuff
1493     o_rIdentifier = aDigest.finalize();
1494 }
1495 
1496 /* i12626 methods */
1497 /*
1498 check if the Unicode string must be encrypted or not, perform the requested task,
1499 append the string as unicode hex, encrypted if needed
1500  */
1501 inline void PDFWriterImpl::appendUnicodeTextStringEncrypt( const OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
1502 {
1503     rOutBuffer.append( "<" );
1504     if( m_aContext.Encryption.Encrypt() )
1505     {
1506         const sal_Unicode* pStr = rInString.getStr();
1507         sal_Int32 nLen = rInString.getLength();
1508         //prepare a unicode string, encrypt it
1509         enableStringEncryption( nInObjectNumber );
1510         sal_uInt8 *pCopy = m_vEncryptionBuffer.data();
1511         sal_Int32 nChars = 2 + (nLen * 2);
1512         m_vEncryptionBuffer.resize(nChars);
1513         *pCopy++ = 0xFE;
1514         *pCopy++ = 0xFF;
1515         // we need to prepare a byte stream from the unicode string buffer
1516         for( int i = 0; i < nLen; i++ )
1517         {
1518             sal_Unicode aUnChar = pStr[i];
1519             *pCopy++ = static_cast<sal_uInt8>( aUnChar >> 8 );
1520             *pCopy++ = static_cast<sal_uInt8>( aUnChar & 255 );
1521         }
1522         //encrypt in place
1523         rtl_cipher_encodeARCFOUR( m_aCipher, m_vEncryptionBuffer.data(), nChars, m_vEncryptionBuffer.data(), nChars );
1524         //now append, hexadecimal (appendHex), the encrypted result
1525         for(int i = 0; i < nChars; i++)
1526             appendHex( m_vEncryptionBuffer[i], rOutBuffer );
1527     }
1528     else
1529         PDFWriter::AppendUnicodeTextString(rInString, rOutBuffer);
1530     rOutBuffer.append( ">" );
1531 }
1532 
1533 inline void PDFWriterImpl::appendLiteralStringEncrypt( std::string_view rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
1534 {
1535     rOutBuffer.append( "(" );
1536     sal_Int32 nChars = rInString.size();
1537     //check for encryption, if ok, encrypt the string, then convert with appndLiteralString
1538     if( m_aContext.Encryption.Encrypt() )
1539     {
1540         m_vEncryptionBuffer.resize(nChars);
1541         //encrypt the string in a buffer, then append it
1542         enableStringEncryption( nInObjectNumber );
1543         rtl_cipher_encodeARCFOUR( m_aCipher, rInString.data(), nChars, m_vEncryptionBuffer.data(), nChars );
1544         appendLiteralString( reinterpret_cast<char*>(m_vEncryptionBuffer.data()), nChars, rOutBuffer );
1545     }
1546     else
1547         appendLiteralString( rInString.data(), nChars , rOutBuffer );
1548     rOutBuffer.append( ")" );
1549 }
1550 
1551 void PDFWriterImpl::appendLiteralStringEncrypt( std::u16string_view rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer, rtl_TextEncoding nEnc )
1552 {
1553     OString aBufferString( OUStringToOString( rInString, nEnc ) );
1554     sal_Int32 nLen = aBufferString.getLength();
1555     OStringBuffer aBuf( nLen );
1556     const char* pT = aBufferString.getStr();
1557 
1558     for( sal_Int32 i = 0; i < nLen; i++, pT++ )
1559     {
1560         if( (*pT & 0x80) == 0 )
1561             aBuf.append( *pT );
1562         else
1563         {
1564             aBuf.append( '<' );
1565             appendHex( *pT, aBuf );
1566             aBuf.append( '>' );
1567         }
1568     }
1569     aBufferString = aBuf.makeStringAndClear();
1570     appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer);
1571 }
1572 
1573 /* end i12626 methods */
1574 
1575 void PDFWriterImpl::emitComment( const char* pComment )
1576 {
1577     OString aLine = OString::Concat("% ") + pComment + "\n";
1578     writeBuffer( aLine );
1579 }
1580 
1581 bool PDFWriterImpl::compressStream( SvMemoryStream* pStream )
1582 {
1583     if (!g_bDebugDisableCompression)
1584     {
1585         sal_uInt64 nEndPos = pStream->TellEnd();
1586         pStream->Seek( STREAM_SEEK_TO_BEGIN );
1587         ZCodec aCodec( 0x4000, 0x4000 );
1588         SvMemoryStream aStream;
1589         aCodec.BeginCompression();
1590         aCodec.Write( aStream, static_cast<const sal_uInt8*>(pStream->GetData()), nEndPos );
1591         aCodec.EndCompression();
1592         nEndPos = aStream.Tell();
1593         pStream->Seek( STREAM_SEEK_TO_BEGIN );
1594         aStream.Seek( STREAM_SEEK_TO_BEGIN );
1595         pStream->SetStreamSize( nEndPos );
1596         pStream->WriteBytes( aStream.GetData(), nEndPos );
1597         return true;
1598     }
1599     else
1600         return false;
1601 }
1602 
1603 void PDFWriterImpl::beginCompression()
1604 {
1605     if (!g_bDebugDisableCompression)
1606     {
1607         m_pCodec = std::make_unique<ZCodec>( 0x4000, 0x4000 );
1608         m_pMemStream = std::make_unique<SvMemoryStream>();
1609         m_pCodec->BeginCompression();
1610     }
1611 }
1612 
1613 void PDFWriterImpl::endCompression()
1614 {
1615     if (!g_bDebugDisableCompression && m_pCodec)
1616     {
1617         m_pCodec->EndCompression();
1618         m_pCodec.reset();
1619         sal_uInt64 nLen = m_pMemStream->Tell();
1620         m_pMemStream->Seek( 0 );
1621         writeBufferBytes( m_pMemStream->GetData(), nLen );
1622         m_pMemStream.reset();
1623     }
1624 }
1625 
1626 bool PDFWriterImpl::writeBufferBytes( const void* pBuffer, sal_uInt64 nBytes )
1627 {
1628     if( ! m_bOpen ) // we are already down the drain
1629         return false;
1630 
1631     if( ! nBytes ) // huh ?
1632         return true;
1633 
1634     if( !m_aOutputStreams.empty() )
1635     {
1636         m_aOutputStreams.front().m_pStream->Seek( STREAM_SEEK_TO_END );
1637         m_aOutputStreams.front().m_pStream->WriteBytes(
1638                 pBuffer, sal::static_int_cast<std::size_t>(nBytes));
1639         return true;
1640     }
1641 
1642     sal_uInt64 nWritten;
1643     if( m_pCodec )
1644     {
1645         m_pCodec->Write( *m_pMemStream, static_cast<const sal_uInt8*>(pBuffer), static_cast<sal_uLong>(nBytes) );
1646         nWritten = nBytes;
1647     }
1648     else
1649     {
1650         bool  buffOK = true;
1651         if( m_bEncryptThisStream )
1652         {
1653             /* implement the encryption part of the PDF spec encryption algorithm 3.1 */
1654             m_vEncryptionBuffer.resize(nBytes);
1655             if( buffOK )
1656                 rtl_cipher_encodeARCFOUR( m_aCipher,
1657                                           pBuffer, static_cast<sal_Size>(nBytes),
1658                                           m_vEncryptionBuffer.data(), static_cast<sal_Size>(nBytes) );
1659         }
1660 
1661         const void* pWriteBuffer = ( m_bEncryptThisStream && buffOK ) ? m_vEncryptionBuffer.data()  : pBuffer;
1662         m_DocDigest.update(static_cast<unsigned char const*>(pWriteBuffer), static_cast<sal_uInt32>(nBytes));
1663 
1664         if (m_aFile.write(pWriteBuffer, nBytes, nWritten) != osl::File::E_None)
1665             nWritten = 0;
1666 
1667         if( nWritten != nBytes )
1668         {
1669             m_aFile.close();
1670             m_bOpen = false;
1671         }
1672     }
1673 
1674     return nWritten == nBytes;
1675 }
1676 
1677 void PDFWriterImpl::newPage( double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
1678 {
1679     endPage();
1680     m_nCurrentPage = m_aPages.size();
1681     m_aPages.emplace_back(this, nPageWidth, nPageHeight, eOrientation );
1682 
1683     const Fraction frac(m_aPages.back().m_nUserUnit, pointToPixel(1));
1684     m_aMapMode = MapMode(MapUnit::MapPoint, Point(), frac, frac);
1685 
1686     m_aPages.back().beginStream();
1687 
1688     // setup global graphics state
1689     // linewidth is "1 pixel" by default
1690     OStringBuffer aBuf( 16 );
1691     appendDouble( 72.0/double(GetDPIX()), aBuf );
1692     aBuf.append( " w\n" );
1693     writeBuffer( aBuf );
1694 }
1695 
1696 void PDFWriterImpl::endPage()
1697 {
1698     if( m_aPages.empty() )
1699         return;
1700 
1701     // close eventual MC sequence
1702     endStructureElementMCSeq();
1703 
1704     // sanity check
1705     if( !m_aOutputStreams.empty() )
1706     {
1707         OSL_FAIL( "redirection across pages !!!" );
1708         m_aOutputStreams.clear(); // leak !
1709         m_aMapMode.SetOrigin( Point() );
1710     }
1711 
1712     m_aGraphicsStack.clear();
1713     m_aGraphicsStack.emplace_back( );
1714 
1715     // this should pop the PDF graphics stack if necessary
1716     updateGraphicsState();
1717 
1718     m_aPages.back().endStream();
1719 
1720     // reset the default font
1721     Font aFont;
1722     aFont.SetFamilyName( "Times" );
1723     aFont.SetFontSize( Size( 0, 12 ) );
1724 
1725     m_aCurrentPDFState = m_aGraphicsStack.front();
1726     m_aGraphicsStack.front().m_aFont =  aFont;
1727 
1728     for (auto & bitmap : m_aBitmaps)
1729     {
1730         if( ! bitmap.m_aBitmap.IsEmpty() )
1731         {
1732             writeBitmapObject(bitmap);
1733             bitmap.m_aBitmap = BitmapEx();
1734         }
1735     }
1736     for (auto & jpeg : m_aJPGs)
1737     {
1738         if( jpeg.m_pStream )
1739         {
1740             writeJPG( jpeg );
1741             jpeg.m_pStream.reset();
1742             jpeg.m_aAlphaMask = AlphaMask();
1743         }
1744     }
1745     for (auto & item : m_aTransparentObjects)
1746     {
1747         if( item.m_pContentStream )
1748         {
1749             writeTransparentObject(item);
1750             item.m_pContentStream.reset();
1751         }
1752     }
1753 
1754 }
1755 
1756 sal_Int32 PDFWriterImpl::createObject()
1757 {
1758     m_aObjects.push_back( ~0U );
1759     return m_aObjects.size();
1760 }
1761 
1762 bool PDFWriterImpl::updateObject( sal_Int32 n )
1763 {
1764     if( ! m_bOpen )
1765         return false;
1766 
1767     sal_uInt64 nOffset = ~0U;
1768     osl::File::RC aError = m_aFile.getPos(nOffset);
1769     SAL_WARN_IF( aError != osl::File::E_None, "vcl.pdfwriter", "could not register object" );
1770     if (aError != osl::File::E_None)
1771     {
1772         m_aFile.close();
1773         m_bOpen = false;
1774     }
1775     m_aObjects[ n-1 ] = nOffset;
1776     return aError == osl::File::E_None;
1777 }
1778 
1779 #define CHECK_RETURN( x ) if( !(x) ) return 0
1780 #define CHECK_RETURN2( x ) if( !(x) ) return
1781 
1782 sal_Int32 PDFWriterImpl::emitStructParentTree( sal_Int32 nObject )
1783 {
1784     if( nObject > 0 )
1785     {
1786         OStringBuffer aLine( 1024 );
1787 
1788         aLine.append( OString::number(nObject)
1789             + " 0 obj\n"
1790               "<</Nums[\n" );
1791         sal_Int32 nTreeItems = m_aStructParentTree.size();
1792         for( sal_Int32 n = 0; n < nTreeItems; n++ )
1793         {
1794             aLine.append( OString::number(n) + " "
1795                 + m_aStructParentTree[n]
1796                 + "\n" );
1797         }
1798         aLine.append( "]>>\nendobj\n\n" );
1799         CHECK_RETURN( updateObject( nObject ) );
1800         CHECK_RETURN( writeBuffer( aLine ) );
1801     }
1802     return nObject;
1803 }
1804 
1805 // every structure element already has a unique object id - just use it for ID
1806 static OString GenerateID(sal_Int32 const nObjectId)
1807 {
1808     return "id" + OString::number(nObjectId);
1809 }
1810 
1811 sal_Int32 PDFWriterImpl::emitStructIDTree(sal_Int32 const nObject)
1812 {
1813     // loosely following PDF 1.7, 10.6.5 Example of Logical Structure, Example 10.15
1814     if (nObject < 0)
1815     {
1816         return nObject;
1817     }
1818     // the name tree entries must be sorted lexicographically.
1819     std::map<OString, sal_Int32> ids;
1820     for (auto n : m_StructElemObjsWithID)
1821     {
1822         ids.emplace(GenerateID(n), n);
1823     }
1824     OStringBuffer buf;
1825     appendObjectID(nObject, buf);
1826     buf.append("<</Names [\n");
1827     for (auto const& it : ids)
1828     {
1829         appendLiteralStringEncrypt(it.first, nObject, buf);
1830         buf.append(" ");
1831         appendObjectReference(it.second, buf);
1832         buf.append("\n");
1833     }
1834     buf.append("] >>\nendobj\n\n");
1835 
1836     CHECK_RETURN( updateObject(nObject) );
1837     CHECK_RETURN( writeBuffer(buf) );
1838 
1839     return nObject;
1840 }
1841 
1842 const char* PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr )
1843 {
1844     static constexpr auto aAttributeStrings = frozen::make_map<PDFWriter::StructAttribute, const char*>({
1845         { PDFWriter::Placement,         "Placement" },
1846         { PDFWriter::WritingMode,       "WritingMode" },
1847         { PDFWriter::SpaceBefore,       "SpaceBefore" },
1848         { PDFWriter::SpaceAfter,        "SpaceAfter" },
1849         { PDFWriter::StartIndent,       "StartIndent" },
1850         { PDFWriter::EndIndent,         "EndIndent" },
1851         { PDFWriter::TextIndent,        "TextIndent" },
1852         { PDFWriter::TextAlign,         "TextAlign" },
1853         { PDFWriter::Width,             "Width" },
1854         { PDFWriter::Height,            "Height" },
1855         { PDFWriter::BlockAlign,        "BlockAlign" },
1856         { PDFWriter::InlineAlign,       "InlineAlign" },
1857         { PDFWriter::LineHeight,        "LineHeight" },
1858         { PDFWriter::BaselineShift,     "BaselineShift" },
1859         { PDFWriter::TextDecorationType,"TextDecorationType" },
1860         { PDFWriter::ListNumbering,     "ListNumbering" },
1861         { PDFWriter::RowSpan,           "RowSpan" },
1862         { PDFWriter::ColSpan,           "ColSpan" },
1863         { PDFWriter::Scope,             "Scope" },
1864         { PDFWriter::Role,              "Role" },
1865         { PDFWriter::Type,              "Type" },
1866         { PDFWriter::Subtype,           "Subtype" },
1867         { PDFWriter::LinkAnnotation,    "LinkAnnotation" }
1868     });
1869 
1870     auto it = aAttributeStrings.find( eAttr );
1871 
1872     if( it == aAttributeStrings.end() )
1873         SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttribute " << eAttr);
1874 
1875     return it != aAttributeStrings.end() ? it->second : "";
1876 }
1877 
1878 const char* PDFWriterImpl::getAttributeValueTag( PDFWriter::StructAttributeValue eVal )
1879 {
1880     static constexpr auto aValueStrings = frozen::make_map<PDFWriter::StructAttributeValue, const char*>({
1881         { PDFWriter::NONE,       "None" },
1882         { PDFWriter::Block,      "Block" },
1883         { PDFWriter::Inline,     "Inline" },
1884         { PDFWriter::Before,     "Before" },
1885         { PDFWriter::After,      "After" },
1886         { PDFWriter::Start,      "Start" },
1887         { PDFWriter::End,        "End" },
1888         { PDFWriter::LrTb,       "LrTb" },
1889         { PDFWriter::RlTb,       "RlTb" },
1890         { PDFWriter::TbRl,       "TbRl" },
1891         { PDFWriter::Center,     "Center" },
1892         { PDFWriter::Justify,    "Justify" },
1893         { PDFWriter::Auto,       "Auto" },
1894         { PDFWriter::Middle,     "Middle" },
1895         { PDFWriter::Normal,     "Normal" },
1896         { PDFWriter::Underline,  "Underline" },
1897         { PDFWriter::Overline,   "Overline" },
1898         { PDFWriter::LineThrough,"LineThrough" },
1899         { PDFWriter::Row,        "Row" },
1900         { PDFWriter::Column,     "Column" },
1901         { PDFWriter::Both,       "Both" },
1902         { PDFWriter::Pagination, "Pagination" },
1903         { PDFWriter::Layout,     "Layout" },
1904         { PDFWriter::Page,       "Page" },
1905         { PDFWriter::Background, "Background" },
1906         { PDFWriter::Header,     "Header" },
1907         { PDFWriter::Footer,     "Footer" },
1908         { PDFWriter::Watermark,  "Watermark" },
1909         { PDFWriter::Rb,         "rb" },
1910         { PDFWriter::Cb,         "cb" },
1911         { PDFWriter::Pb,         "pb" },
1912         { PDFWriter::Tv,         "tv" },
1913         { PDFWriter::Disc,       "Disc" },
1914         { PDFWriter::Circle,     "Circle" },
1915         { PDFWriter::Square,     "Square" },
1916         { PDFWriter::Decimal,    "Decimal" },
1917         { PDFWriter::UpperRoman, "UpperRoman" },
1918         { PDFWriter::LowerRoman, "LowerRoman" },
1919         { PDFWriter::UpperAlpha, "UpperAlpha" },
1920         { PDFWriter::LowerAlpha, "LowerAlpha" }
1921     });
1922 
1923     auto it = aValueStrings.find( eVal );
1924 
1925     if( it == aValueStrings.end() )
1926         SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttributeValue " << eVal);
1927 
1928     return it != aValueStrings.end() ? it->second : "";
1929 }
1930 
1931 static void appendStructureAttributeLine( PDFWriter::StructAttribute i_eAttr, const PDFStructureAttribute& i_rVal, OStringBuffer& o_rLine, bool i_bIsFixedInt )
1932 {
1933     o_rLine.append( "/" );
1934     o_rLine.append( PDFWriterImpl::getAttributeTag( i_eAttr ) );
1935 
1936     if( i_rVal.eValue != PDFWriter::Invalid )
1937     {
1938         o_rLine.append( "/" );
1939         o_rLine.append( PDFWriterImpl::getAttributeValueTag( i_rVal.eValue ) );
1940     }
1941     else
1942     {
1943         // numerical value
1944         o_rLine.append( " " );
1945         if( i_bIsFixedInt )
1946             appendFixedInt( i_rVal.nValue, o_rLine );
1947         else
1948             o_rLine.append( i_rVal.nValue );
1949     }
1950     o_rLine.append( "\n" );
1951 }
1952 
1953 template<typename T>
1954 void PDFWriterImpl::AppendAnnotKid(PDFStructureElement& i_rEle, T & rAnnot)
1955 {
1956     // update struct parent of link
1957     OString const aStructParentEntry(OString::number(i_rEle.m_nObject) + " 0 R");
1958     m_aStructParentTree.push_back( aStructParentEntry );
1959     rAnnot.m_nStructParent = m_aStructParentTree.size()-1;
1960     sal_Int32 const nAnnotObj(rAnnot.m_nObject);
1961 
1962     sal_Int32 const nRefObject = createObject();
1963     if (updateObject(nRefObject))
1964     {
1965         OString aRef =
1966             OString::number( nRefObject ) +
1967             " 0 obj\n"
1968             "<</Type/OBJR/Obj " +
1969             OString::number(nAnnotObj) +
1970             " 0 R>>\n"
1971             "endobj\n\n";
1972         writeBuffer( aRef );
1973     }
1974 
1975     i_rEle.m_aKids.emplace_back( nRefObject );
1976 }
1977 
1978 OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle )
1979 {
1980     // create layout, list and table attribute sets
1981     OStringBuffer aLayout(256), aList(64), aTable(64);
1982     OStringBuffer aPrintField;
1983     for (auto const& attribute : i_rEle.m_aAttributes)
1984     {
1985         if( attribute.first == PDFWriter::ListNumbering )
1986             appendStructureAttributeLine( attribute.first, attribute.second, aList, true );
1987         else if (attribute.first == PDFWriter::Role)
1988         {
1989             appendStructureAttributeLine(attribute.first, attribute.second, aPrintField, true);
1990         }
1991         else if( attribute.first == PDFWriter::RowSpan ||
1992                  attribute.first == PDFWriter::ColSpan ||
1993                  attribute.first == PDFWriter::Scope)
1994         {
1995             appendStructureAttributeLine( attribute.first, attribute.second, aTable, false );
1996         }
1997         else if( attribute.first == PDFWriter::LinkAnnotation )
1998         {
1999             sal_Int32 nLink = attribute.second.nValue;
2000             std::map< sal_Int32, sal_Int32 >::const_iterator link_it =
2001                 m_aLinkPropertyMap.find( nLink );
2002             if( link_it != m_aLinkPropertyMap.end() )
2003                 nLink = link_it->second;
2004             if( nLink >= 0 && o3tl::make_unsigned(nLink) < m_aLinks.size() )
2005             {
2006                 AppendAnnotKid(i_rEle, m_aLinks[nLink]);
2007             }
2008             else
2009             {
2010                 OSL_FAIL( "unresolved link id for Link structure" );
2011                 SAL_INFO("vcl.pdfwriter", "unresolved link id " << nLink << " for Link structure");
2012                 if (g_bDebugDisableCompression)
2013                 {
2014                     OString aLine = "unresolved link id " +
2015                             OString::number( nLink ) +
2016                             " for Link structure";
2017                     emitComment( aLine.getStr() );
2018                 }
2019             }
2020         }
2021         else
2022             appendStructureAttributeLine( attribute.first, attribute.second, aLayout, true );
2023     }
2024     if( ! i_rEle.m_aBBox.IsEmpty() )
2025     {
2026         aLayout.append( "/BBox[" );
2027         appendFixedInt( i_rEle.m_aBBox.Left(), aLayout );
2028         aLayout.append( " " );
2029         appendFixedInt( i_rEle.m_aBBox.Top(), aLayout );
2030         aLayout.append( " " );
2031         appendFixedInt( i_rEle.m_aBBox.Right(), aLayout );
2032         aLayout.append( " " );
2033         appendFixedInt( i_rEle.m_aBBox.Bottom(), aLayout );
2034         aLayout.append( "]\n" );
2035     }
2036 
2037     std::vector< sal_Int32 > aAttribObjects;
2038     auto const WriteAttrs = [&](char const*const pName, OStringBuffer & rBuf)
2039     {
2040         aAttribObjects.push_back( createObject() );
2041         if (updateObject( aAttribObjects.back() ))
2042         {
2043             OStringBuffer aObj( 64 );
2044             aObj.append( aAttribObjects.back() );
2045             aObj.append( " 0 obj\n"
2046                          "<</O");
2047             aObj.append(pName);
2048             aObj.append("\n");
2049             rBuf.append(">>\nendobj\n\n");
2050             writeBuffer(aObj);
2051             writeBuffer(rBuf);
2052         }
2053     };
2054     if( !aLayout.isEmpty() )
2055     {
2056         WriteAttrs("/Layout", aLayout);
2057     }
2058     if( !aList.isEmpty() )
2059     {
2060         WriteAttrs("/List", aList);
2061     }
2062     if (!aPrintField.isEmpty())
2063     {
2064         WriteAttrs("/PrintField", aPrintField);
2065     }
2066     if( !aTable.isEmpty() )
2067     {
2068         WriteAttrs("/Table", aTable);
2069     }
2070 
2071     OStringBuffer aRet( 64 );
2072     if( aAttribObjects.size() > 1 )
2073         aRet.append( " [" );
2074     for (auto const& attrib : aAttribObjects)
2075     {
2076         aRet.append( " "  + OString::number(attrib) + " 0 R" );
2077     }
2078     if( aAttribObjects.size() > 1 )
2079         aRet.append( " ]" );
2080     return aRet.makeStringAndClear();
2081 }
2082 
2083 sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle )
2084 {
2085     assert(rEle.m_nOwnElement == 0 || rEle.m_oType);
2086     if (rEle.m_nOwnElement != rEle.m_nParentElement // emit the struct tree root
2087        // do not emit NonStruct and its children
2088         && *rEle.m_oType == PDFWriter::NonStructElement)
2089     {
2090         return 0;
2091     }
2092 
2093     for (auto const& child : rEle.m_aChildren)
2094     {
2095         if( child > 0 && o3tl::make_unsigned(child) < m_aStructure.size() )
2096         {
2097             PDFStructureElement& rChild = m_aStructure[ child ];
2098             if (*rChild.m_oType != PDFWriter::NonStructElement)
2099             {
2100                 if( rChild.m_nParentElement == rEle.m_nOwnElement )
2101                     emitStructure( rChild );
2102                 else
2103                 {
2104                     OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure element" );
2105                     SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure element with id " << child);
2106                 }
2107             }
2108         }
2109         else
2110         {
2111             OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
2112             SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure id " << child);
2113         }
2114     }
2115 
2116     OStringBuffer aLine( 512 );
2117     aLine.append(
2118         OString::number(rEle.m_nObject)
2119         + " 0 obj\n"
2120           "<</Type" );
2121     sal_Int32 nParentTree = -1;
2122     sal_Int32 nIDTree = -1;
2123     if( rEle.m_nOwnElement == rEle.m_nParentElement )
2124     {
2125         nParentTree = createObject();
2126         CHECK_RETURN( nParentTree );
2127         aLine.append( "/StructTreeRoot\n"
2128             "/ParentTree "
2129             + OString::number(nParentTree)
2130             + " 0 R\n" );
2131         if( ! m_aRoleMap.empty() )
2132         {
2133             aLine.append( "/RoleMap<<" );
2134             for (auto const& role : m_aRoleMap)
2135             {
2136                 aLine.append( "/" + role.first + "/" + role.second + "\n" );
2137             }
2138             aLine.append( ">>\n" );
2139         }
2140         if (!m_StructElemObjsWithID.empty())
2141         {
2142             nIDTree = createObject();
2143             aLine.append("/IDTree ");
2144             appendObjectReference(nIDTree, aLine);
2145             aLine.append("\n");
2146         }
2147     }
2148     else
2149     {
2150         aLine.append( "/StructElem\n"
2151                       "/S/" );
2152         if( !rEle.m_aAlias.isEmpty() )
2153             aLine.append( rEle.m_aAlias );
2154         else
2155             aLine.append( getStructureTag(*rEle.m_oType) );
2156         if (m_StructElemObjsWithID.find(rEle.m_nObject) != m_StructElemObjsWithID.end())
2157         {
2158             aLine.append("\n/ID ");
2159             appendLiteralStringEncrypt(GenerateID(rEle.m_nObject), rEle.m_nObject, aLine);
2160         }
2161         aLine.append(
2162             "\n"
2163             "/P "
2164             + OString::number(m_aStructure[ rEle.m_nParentElement ].m_nObject)
2165             + " 0 R\n"
2166               "/Pg "
2167             + OString::number(rEle.m_nFirstPageObject)
2168             + " 0 R\n" );
2169         if( !rEle.m_aActualText.isEmpty() )
2170         {
2171             aLine.append( "/ActualText" );
2172             appendUnicodeTextStringEncrypt( rEle.m_aActualText, rEle.m_nObject, aLine );
2173             aLine.append( "\n" );
2174         }
2175         if( !rEle.m_aAltText.isEmpty() )
2176         {
2177             aLine.append( "/Alt" );
2178             appendUnicodeTextStringEncrypt( rEle.m_aAltText, rEle.m_nObject, aLine );
2179             aLine.append( "\n" );
2180         }
2181     }
2182     if( (! rEle.m_aBBox.IsEmpty()) || (! rEle.m_aAttributes.empty()) )
2183     {
2184         OString aAttribs =  emitStructureAttributes( rEle );
2185         if( !aAttribs.isEmpty() )
2186         {
2187             aLine.append( "/A" + aAttribs + "\n" );
2188         }
2189     }
2190     if( !rEle.m_aLocale.Language.isEmpty() )
2191     {
2192         /* PDF allows only RFC 3066, which is only partly BCP 47 and does not
2193          * include script tags and others.
2194          * http://pdf.editme.com/pdfua-naturalLanguageSpecification
2195          * http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf#page=886
2196          * https://www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf#M13.9.19332.1Heading.97.Natural.Language.Specification
2197          * */
2198         LanguageTag aLanguageTag( rEle.m_aLocale);
2199         OUString aLanguage, aScript, aCountry;
2200         aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
2201         if (!aLanguage.isEmpty())
2202         {
2203             OUStringBuffer aLocBuf( 16 );
2204             aLocBuf.append( aLanguage );
2205             if( !aCountry.isEmpty() )
2206             {
2207                 aLocBuf.append( "-" + aCountry );
2208             }
2209             aLine.append( "/Lang" );
2210             appendLiteralStringEncrypt( aLocBuf, rEle.m_nObject, aLine );
2211             aLine.append( "\n" );
2212         }
2213     }
2214     if (!rEle.m_AnnotIds.empty())
2215     {
2216         for (auto const id : rEle.m_AnnotIds)
2217         {
2218             auto const it(m_aLinkPropertyMap.find(id));
2219             assert(it != m_aLinkPropertyMap.end());
2220 
2221             if (*rEle.m_oType == PDFWriter::Form)
2222             {
2223                 assert(0 <= it->second && o3tl::make_unsigned(it->second) < m_aWidgets.size());
2224                 AppendAnnotKid(rEle, m_aWidgets[it->second]);
2225             }
2226             else
2227             {
2228                 assert(0 <= it->second && o3tl::make_unsigned(it->second) < m_aScreens.size());
2229                 AppendAnnotKid(rEle, m_aScreens[it->second]);
2230             }
2231         }
2232     }
2233     if( ! rEle.m_aKids.empty() )
2234     {
2235         unsigned int i = 0;
2236         aLine.append( "/K[" );
2237         for (auto const& kid : rEle.m_aKids)
2238         {
2239             if( kid.nMCID == -1 )
2240             {
2241                 aLine.append(
2242                     OString::number(kid.nObject)
2243                     + " 0 R" );
2244                 aLine.append( ( (i & 15) == 15 ) ? "\n" : " " );
2245             }
2246             else
2247             {
2248                 if( kid.nObject == rEle.m_nFirstPageObject )
2249                 {
2250                     aLine.append( OString::number(kid.nMCID) + " " );
2251                 }
2252                 else
2253                 {
2254                     aLine.append(
2255                         "<</Type/MCR/Pg "
2256                         + OString::number(kid.nObject)
2257                         + " 0 R /MCID "
2258                         + OString::number(kid.nMCID)
2259                         + ">>\n" );
2260                 }
2261             }
2262             ++i;
2263         }
2264         aLine.append( "]\n" );
2265     }
2266     aLine.append( ">>\nendobj\n\n" );
2267 
2268     CHECK_RETURN( updateObject( rEle.m_nObject ) );
2269     CHECK_RETURN( writeBuffer( aLine ) );
2270 
2271     CHECK_RETURN( emitStructParentTree( nParentTree ) );
2272     CHECK_RETURN( emitStructIDTree(nIDTree) );
2273 
2274     return rEle.m_nObject;
2275 }
2276 
2277 bool PDFWriterImpl::emitGradients()
2278 {
2279     for (auto const& gradient : m_aGradients)
2280     {
2281         if ( !writeGradientFunction( gradient ) ) return false;
2282     }
2283     return true;
2284 }
2285 
2286 bool PDFWriterImpl::emitTilings()
2287 {
2288     OStringBuffer aTilingObj( 1024 );
2289 
2290     for (auto & tiling : m_aTilings)
2291     {
2292         SAL_WARN_IF( !tiling.m_pTilingStream, "vcl.pdfwriter", "tiling without stream" );
2293         if( ! tiling.m_pTilingStream )
2294             continue;
2295 
2296         aTilingObj.setLength( 0 );
2297 
2298         if (g_bDebugDisableCompression)
2299         {
2300             emitComment( "PDFWriterImpl::emitTilings" );
2301         }
2302 
2303         sal_Int32 nX = static_cast<sal_Int32>(tiling.m_aRectangle.Left());
2304         sal_Int32 nY = static_cast<sal_Int32>(tiling.m_aRectangle.Top());
2305         sal_Int32 nW = static_cast<sal_Int32>(tiling.m_aRectangle.GetWidth());
2306         sal_Int32 nH = static_cast<sal_Int32>(tiling.m_aRectangle.GetHeight());
2307         if( tiling.m_aCellSize.Width() == 0 )
2308             tiling.m_aCellSize.setWidth( nW );
2309         if( tiling.m_aCellSize.Height() == 0 )
2310             tiling.m_aCellSize.setHeight( nH );
2311 
2312         bool bDeflate = compressStream( tiling.m_pTilingStream.get() );
2313         sal_uInt64 const nTilingStreamSize = tiling.m_pTilingStream->TellEnd();
2314         tiling.m_pTilingStream->Seek( STREAM_SEEK_TO_BEGIN );
2315 
2316         // write pattern object
2317         aTilingObj.append(
2318             OString::number(tiling.m_nObject)
2319             + " 0 obj\n"
2320               "<</Type/Pattern/PatternType 1\n"
2321               "/PaintType 1\n"
2322               "/TilingType 2\n"
2323               "/BBox[" );
2324         appendFixedInt( nX, aTilingObj );
2325         aTilingObj.append( ' ' );
2326         appendFixedInt( nY, aTilingObj );
2327         aTilingObj.append( ' ' );
2328         appendFixedInt( nX+nW, aTilingObj );
2329         aTilingObj.append( ' ' );
2330         appendFixedInt( nY+nH, aTilingObj );
2331         aTilingObj.append( "]\n"
2332                            "/XStep " );
2333         appendFixedInt( tiling.m_aCellSize.Width(), aTilingObj );
2334         aTilingObj.append( "\n"
2335                            "/YStep " );
2336         appendFixedInt( tiling.m_aCellSize.Height(), aTilingObj );
2337         aTilingObj.append( "\n" );
2338         if( tiling.m_aTransform.matrix[0] != 1.0 ||
2339             tiling.m_aTransform.matrix[1] != 0.0 ||
2340             tiling.m_aTransform.matrix[3] != 0.0 ||
2341             tiling.m_aTransform.matrix[4] != 1.0 ||
2342             tiling.m_aTransform.matrix[2] != 0.0 ||
2343             tiling.m_aTransform.matrix[5] != 0.0 )
2344         {
2345             aTilingObj.append( "/Matrix [" );
2346             // TODO: scaling, mirroring on y, etc
2347             appendDouble( tiling.m_aTransform.matrix[0], aTilingObj );
2348             aTilingObj.append( ' ' );
2349             appendDouble( tiling.m_aTransform.matrix[1], aTilingObj );
2350             aTilingObj.append( ' ' );
2351             appendDouble( tiling.m_aTransform.matrix[3], aTilingObj );
2352             aTilingObj.append( ' ' );
2353             appendDouble( tiling.m_aTransform.matrix[4], aTilingObj );
2354             aTilingObj.append( ' ' );
2355             appendDouble( tiling.m_aTransform.matrix[2], aTilingObj );
2356             aTilingObj.append( ' ' );
2357             appendDouble( tiling.m_aTransform.matrix[5], aTilingObj );
2358             aTilingObj.append( "]\n" );
2359         }
2360         aTilingObj.append( "/Resources" );
2361         tiling.m_aResources.append( aTilingObj, getFontDictObject() );
2362         if( bDeflate )
2363             aTilingObj.append( "/Filter/FlateDecode" );
2364         aTilingObj.append( "/Length "
2365             + OString::number(static_cast<sal_Int32>(nTilingStreamSize))
2366             + ">>\nstream\n" );
2367         if ( !updateObject( tiling.m_nObject ) ) return false;
2368         if ( !writeBuffer( aTilingObj ) ) return false;
2369         checkAndEnableStreamEncryption( tiling.m_nObject );
2370         bool written = writeBufferBytes( tiling.m_pTilingStream->GetData(), nTilingStreamSize );
2371         tiling.m_pTilingStream.reset();
2372         if( !written )
2373             return false;
2374         disableStreamEncryption();
2375         aTilingObj.setLength( 0 );
2376         aTilingObj.append( "\nendstream\nendobj\n\n" );
2377         if ( !writeBuffer( aTilingObj ) ) return false;
2378     }
2379     return true;
2380 }
2381 
2382 sal_Int32 PDFWriterImpl::emitBuildinFont(const pdf::BuildinFontFace* pFD, sal_Int32 nFontObject)
2383 {
2384     if( !pFD )
2385         return 0;
2386     const pdf::BuildinFont& rBuildinFont = pFD->GetBuildinFont();
2387 
2388     OStringBuffer aLine( 1024 );
2389 
2390     if( nFontObject <= 0 )
2391         nFontObject = createObject();
2392     CHECK_RETURN( updateObject( nFontObject ) );
2393     aLine.append(
2394         OString::number(nFontObject)
2395         + " 0 obj\n"
2396           "<</Type/Font/Subtype/Type1/BaseFont/" );
2397     appendName( rBuildinFont.m_pPSName, aLine );
2398     aLine.append( "\n" );
2399     if( rBuildinFont.m_eCharSet == RTL_TEXTENCODING_MS_1252 )
2400          aLine.append( "/Encoding/WinAnsiEncoding\n" );
2401     aLine.append( ">>\nendobj\n\n" );
2402     CHECK_RETURN( writeBuffer( aLine ) );
2403     return nFontObject;
2404 }
2405 
2406 namespace
2407 {
2408 // Translate units from TT to PS (standard 1/1000)
2409 int XUnits(int nUPEM, int n) { return (n * 1000) / nUPEM; }
2410 }
2411 
2412 std::map< sal_Int32, sal_Int32 > PDFWriterImpl::emitSystemFont( const vcl::font::PhysicalFontFace* pFace, EmbedFont const & rEmbed )
2413 {
2414     std::map< sal_Int32, sal_Int32 > aRet;
2415 
2416     if (g_bDebugDisableCompression)
2417         emitComment("PDFWriterImpl::emitSystemFont");
2418 
2419     FontSubsetInfo aInfo;
2420     // fill in dummy values
2421     aInfo.m_nAscent = 1000;
2422     aInfo.m_nDescent = 200;
2423     aInfo.m_nCapHeight = 1000;
2424     aInfo.m_aFontBBox = tools::Rectangle( Point( -200, -200 ), Size( 1700, 1700 ) );
2425     aInfo.m_aPSName = pFace->GetFamilyName();
2426 
2427     sal_Int32 pWidths[256] = { 0 };
2428     const LogicalFontInstance* pFontInstance = rEmbed.m_pFontInstance;
2429     auto nUPEM = pFace->UnitsPerEm();
2430     for( sal_Ucs c = 32; c < 256; c++ )
2431     {
2432         sal_GlyphId nGlyph = pFontInstance->GetGlyphIndex(c);
2433         pWidths[c] = XUnits(nUPEM, pFontInstance->GetGlyphWidth(nGlyph, false, false));
2434     }
2435 
2436     // We are interested only in filling aInfo
2437     sal_GlyphId aGlyphIds[] = { 0 };
2438     sal_uInt8 pEncoding[] = { 0 };
2439     std::vector<sal_uInt8> aBuffer;
2440     pFace->CreateFontSubset(aBuffer, aGlyphIds, pEncoding, 1, aInfo);
2441 
2442     // write font descriptor
2443     sal_Int32 nFontDescriptor = emitFontDescriptor( pFace, aInfo, 0, 0 );
2444     if( nFontDescriptor )
2445     {
2446         // write font object
2447         sal_Int32 nObject = createObject();
2448         if( updateObject( nObject ) )
2449         {
2450             OStringBuffer aLine( 1024 );
2451             aLine.append(
2452                 OString::number(nObject)
2453                 + " 0 obj\n"
2454                   "<</Type/Font/Subtype/TrueType"
2455                   "/BaseFont/" );
2456             appendName( aInfo.m_aPSName, aLine );
2457             aLine.append( "\n" );
2458             if (!pFace->IsMicrosoftSymbolEncoded())
2459                 aLine.append( "/Encoding/WinAnsiEncoding\n" );
2460             aLine.append( "/FirstChar 32 /LastChar 255\n"
2461                           "/Widths[" );
2462             for( int i = 32; i < 256; i++ )
2463             {
2464                 aLine.append( pWidths[i] );
2465                 aLine.append( ((i&15) == 15) ? "\n" : " " );
2466             }
2467             aLine.append( "]\n"
2468                           "/FontDescriptor "
2469                 + OString::number( nFontDescriptor )
2470                 + " 0 R>>\n"
2471                   "endobj\n\n" );
2472             writeBuffer( aLine );
2473 
2474             aRet[ rEmbed.m_nNormalFontID ] = nObject;
2475         }
2476     }
2477 
2478     return aRet;
2479 }
2480 
2481 namespace
2482 {
2483 uint32_t fillSubsetArrays(const FontEmit& rSubset, sal_GlyphId* pGlyphIds, sal_Int32* pWidths,
2484                           sal_uInt8* pEncoding, sal_Int32* pEncToUnicodeIndex,
2485                           sal_Int32* pCodeUnitsPerGlyph, std::vector<sal_Ucs>& rCodeUnits,
2486                           sal_Int32& nToUnicodeStream)
2487 {
2488     rCodeUnits.reserve(256);
2489 
2490     // if it gets used then it will appear in s_subset.m_aMapping, otherwise 0 is fine
2491     pWidths[0] = 0;
2492 
2493     uint32_t nGlyphs = 1;
2494     for (auto const& item : rSubset.m_aMapping)
2495     {
2496         sal_uInt8 nEnc = item.second.getGlyphId();
2497 
2498         SAL_WARN_IF(pGlyphIds[nEnc] != 0 || pEncoding[nEnc] != 0, "vcl.pdfwriter",
2499                     "duplicate glyph");
2500         SAL_WARN_IF(nEnc > rSubset.m_aMapping.size(), "vcl.pdfwriter", "invalid glyph encoding");
2501 
2502         pGlyphIds[nEnc] = item.first;
2503         pEncoding[nEnc] = nEnc;
2504         pEncToUnicodeIndex[nEnc] = static_cast<sal_Int32>(rCodeUnits.size());
2505         pCodeUnitsPerGlyph[nEnc] = item.second.countCodes();
2506         pWidths[nEnc] = item.second.getGlyphWidth();
2507         for (sal_Int32 n = 0; n < pCodeUnitsPerGlyph[nEnc]; n++)
2508             rCodeUnits.push_back(item.second.getCode(n));
2509         if (item.second.getCode(0))
2510             nToUnicodeStream = 1;
2511         if (nGlyphs < 256)
2512             nGlyphs++;
2513         else
2514             OSL_FAIL("too many glyphs for subset");
2515     }
2516 
2517     return nGlyphs;
2518 }
2519 }
2520 
2521 bool PDFWriterImpl::emitType3Font(const vcl::font::PhysicalFontFace* pFace,
2522                                   const FontSubset& rType3Font,
2523                                   std::map<sal_Int32, sal_Int32>& rFontIDToObject)
2524 {
2525     if (g_bDebugDisableCompression)
2526         emitComment("PDFWriterImpl::emitType3Font");
2527 
2528     const auto& rColorPalettes = pFace->GetColorPalettes();
2529 
2530     FontSubsetInfo aSubsetInfo;
2531     sal_GlyphId pTempGlyphIds[] = { 0 };
2532     sal_uInt8 pTempEncoding[] = { 0 };
2533     std::vector<sal_uInt8> aBuffer;
2534     pFace->CreateFontSubset(aBuffer, pTempGlyphIds, pTempEncoding, 1, aSubsetInfo);
2535 
2536     for (auto& rSubset : rType3Font.m_aSubsets)
2537     {
2538         sal_GlyphId pGlyphIds[256] = {};
2539         sal_Int32 pWidths[256];
2540         sal_uInt8 pEncoding[256] = {};
2541         sal_Int32 pEncToUnicodeIndex[256] = {};
2542         sal_Int32 pCodeUnitsPerGlyph[256] = {};
2543         std::vector<sal_Ucs> aCodeUnits;
2544         sal_Int32 nToUnicodeStream = 0;
2545 
2546         // fill arrays and prepare encoding index map
2547         auto nGlyphs = fillSubsetArrays(rSubset, pGlyphIds, pWidths, pEncoding, pEncToUnicodeIndex,
2548                                         pCodeUnitsPerGlyph, aCodeUnits, nToUnicodeStream);
2549 
2550         // write font descriptor
2551         sal_Int32 nFontDescriptor = 0;
2552         if (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_4)
2553             nFontDescriptor = emitFontDescriptor(pFace, aSubsetInfo, rSubset.m_nFontID, 0);
2554 
2555         if (nToUnicodeStream)
2556             nToUnicodeStream = createToUnicodeCMap(pEncoding, aCodeUnits, pCodeUnitsPerGlyph,
2557                                                    pEncToUnicodeIndex, nGlyphs);
2558 
2559         // write font object
2560         sal_Int32 nFontObject = createObject();
2561         if (!updateObject(nFontObject))
2562             return false;
2563 
2564         OStringBuffer aLine(1024);
2565         aLine.append(
2566             OString::number(nFontObject)
2567             + " 0 obj\n"
2568               "<</Type/Font/Subtype/Type3/Name/");
2569         appendName(aSubsetInfo.m_aPSName, aLine);
2570 
2571         aLine.append(
2572             "\n/FontBBox["
2573         // note: Top and Bottom are reversed in VCL and PDF rectangles
2574             + OString::number(aSubsetInfo.m_aFontBBox.Left())
2575             + " "
2576             + OString::number(aSubsetInfo.m_aFontBBox.Top())
2577             + " "
2578             + OString::number(aSubsetInfo.m_aFontBBox.Right())
2579             + " "
2580             + OString::number(aSubsetInfo.m_aFontBBox.Bottom() + 1)
2581             + "]\n");
2582 
2583         auto nScale = 1. / pFace->UnitsPerEm();
2584         aLine.append(
2585             "/FontMatrix["
2586             + OString::number(nScale)
2587             + " 0 0 "
2588             + OString::number(nScale)
2589             + " 0 0]\n");
2590 
2591         sal_Int32 pGlyphStreams[256] = {};
2592         aLine.append("/CharProcs<<\n");
2593         for (auto i = 1u; i < nGlyphs; i++)
2594         {
2595             auto nStream = createObject();
2596             aLine.append("/"
2597                 + pFace->GetGlyphName(pGlyphIds[i], true)
2598                 + " "
2599                 + OString::number(nStream)
2600                 + " 0 R\n");
2601             pGlyphStreams[i] = nStream;
2602         }
2603         aLine.append(">>\n"
2604 
2605             "/Encoding<</Type/Encoding/Differences[1");
2606         for (auto i = 1u; i < nGlyphs; i++)
2607             aLine.append(" /" + pFace->GetGlyphName(pGlyphIds[i], true));
2608         aLine.append("]>>\n"
2609 
2610             "/FirstChar 0\n"
2611             "/LastChar "
2612             + OString::number(nGlyphs)
2613             + "\n"
2614 
2615             "/Widths[");
2616         for (auto i = 0u; i < nGlyphs; i++)
2617         {
2618             aLine.append(OString::number(pWidths[i]) + " ");
2619         }
2620         aLine.append("]\n");
2621 
2622         if (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_4)
2623         {
2624             aLine.append("/FontDescriptor " + OString::number(nFontDescriptor) + " 0 R\n");
2625         }
2626 
2627         auto nResources = createObject();
2628         aLine.append("/Resources " + OString::number(nResources) + " 0 R\n");
2629 
2630         if (nToUnicodeStream)
2631         {
2632             aLine.append("/ToUnicode " + OString::number(nToUnicodeStream) + " 0 R\n");
2633         }
2634 
2635         aLine.append(">>\n"
2636                      "endobj\n\n");
2637 
2638         if (!writeBuffer(aLine))
2639             return false;
2640 
2641         std::set<sal_Int32> aUsedFonts;
2642         std::list<BitmapEmit> aUsedBitmaps;
2643         std::map<sal_uInt8, sal_Int32> aUsedAlpha;
2644         ResourceDict aResourceDict;
2645         std::list<StreamRedirect> aOutputStreams;
2646 
2647         // Scale for glyph outlines.
2648         double fScaleX = GetDPIX() / 72.;
2649         double fScaleY = GetDPIY() / 72.;
2650 
2651         for (auto i = 1u; i < nGlyphs; i++)
2652         {
2653             auto nStream = pGlyphStreams[i];
2654             if (!updateObject(nStream))
2655                 return false;
2656             OStringBuffer aContents(1024);
2657             aContents.append(OString::number(pWidths[i]) + " 0 d0\n");
2658 
2659             const auto& rGlyph = rSubset.m_aMapping.find(pGlyphIds[i])->second;
2660             const auto& rLayers = rGlyph.getColorLayers();
2661             for (const auto& rLayer : rLayers)
2662             {
2663                 aUsedFonts.insert(rLayer.m_nFontID);
2664 
2665                 aContents.append("q ");
2666                 // 0xFFFF is a special value means foreground color.
2667                 if (rLayer.m_nColorIndex != 0xFFFF)
2668                 {
2669                     auto& rPalette = rColorPalettes[0];
2670                     auto aColor(rPalette[rLayer.m_nColorIndex]);
2671                     appendNonStrokingColor(aColor, aContents);
2672                     aContents.append(" ");
2673                     if (aColor.GetAlpha() != 0xFF
2674                         && m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4)
2675                     {
2676                         auto nAlpha = aColor.GetAlpha();
2677                         OStringBuffer aName(16);
2678                         aName.append("GS");
2679                         appendHex(nAlpha, aName);
2680 
2681                         aContents.append("/" + aName + " gs ");
2682 
2683                         if (aUsedAlpha.find(nAlpha) == aUsedAlpha.end())
2684                         {
2685                             auto nObject = createObject();
2686                             aUsedAlpha[nAlpha] = nObject;
2687                             pushResource(ResourceKind::ExtGState, aName.makeStringAndClear(),
2688                                          nObject, aResourceDict, aOutputStreams);
2689                         }
2690                     }
2691                 }
2692                 aContents.append(
2693                     "BT "
2694                     "/F" + OString::number(rLayer.m_nFontID) + " "
2695                     + OString::number(pFace->UnitsPerEm()) + " Tf "
2696                     "<");
2697                 appendHex(rLayer.m_nSubsetGlyphID, aContents);
2698                 aContents.append(
2699                     ">Tj "
2700                     "ET "
2701                     "Q\n");
2702             }
2703 
2704             tools::Rectangle aRect;
2705             const auto& rBitmapData = rGlyph.getColorBitmap(aRect);
2706             if (!rBitmapData.empty())
2707             {
2708                 SvMemoryStream aStream(const_cast<uint8_t*>(rBitmapData.data()), rBitmapData.size(),
2709                                        StreamMode::READ);
2710                 vcl::PngImageReader aReader(aStream);
2711                 auto aBitmapEmit = createBitmapEmit(std::move(aReader.read()), Graphic(),
2712                                                     aUsedBitmaps, aResourceDict, aOutputStreams);
2713 
2714                 auto nObject = aBitmapEmit.m_aReferenceXObject.getObject();
2715                 aContents.append(
2716                     "q "
2717                     + OString::number(aRect.GetWidth())
2718                     + " 0 0 "
2719                     + OString::number(aRect.GetHeight())
2720                     + " "
2721                     + OString::number(aRect.getX())
2722                     + " "
2723                     + OString::number(aRect.getY())
2724                     + " cm "
2725                       "/Im"
2726                     + OString::number(nObject)
2727                     + " Do Q\n");
2728             }
2729 
2730             const auto& rOutline = rGlyph.getOutline();
2731             if (rOutline.count())
2732             {
2733                 aContents.append("q ");
2734                 appendDouble(fScaleX, aContents);
2735                 aContents.append(" 0 0 ");
2736                 appendDouble(fScaleY, aContents);
2737                 aContents.append(" 0 ");
2738                 appendDouble(m_aPages.back().getHeight() * -fScaleY, aContents, 3);
2739                 aContents.append(" cm\n");
2740                 m_aPages.back().appendPolyPolygon(rOutline, aContents);
2741                 aContents.append("f\n"
2742                                  "Q\n");
2743             }
2744 
2745             aLine.setLength(0);
2746             aLine.append(OString::number(nStream)
2747                 + " 0 obj\n<</Length "
2748                 + OString::number(aContents.getLength())
2749                 + ">>\nstream\n");
2750             if (!writeBuffer(aLine))
2751                 return false;
2752             if (!writeBuffer(aContents))
2753                 return false;
2754             aLine.setLength(0);
2755             aLine.append("endstream\nendobj\n\n");
2756             if (!writeBuffer(aLine))
2757                 return false;
2758         }
2759 
2760         // write font dict
2761         sal_Int32 nFontDict = 0;
2762         if (!aUsedFonts.empty())
2763         {
2764             nFontDict = createObject();
2765             aLine.setLength(0);
2766             aLine.append(OString::number(nFontDict) + " 0 obj\n<<");
2767             for (auto nFontID : aUsedFonts)
2768             {
2769                 aLine.append("/F"
2770                     + OString::number(nFontID)
2771                     + " "
2772                     + OString::number(rFontIDToObject[nFontID])
2773                     + " 0 R");
2774             }
2775             aLine.append(">>\nendobj\n\n");
2776             if (!updateObject(nFontDict))
2777                 return false;
2778             if (!writeBuffer(aLine))
2779                 return false;
2780         }
2781 
2782         // write ExtGState objects
2783         if (!aUsedAlpha.empty())
2784         {
2785             for (const auto & [ nAlpha, nObject ] : aUsedAlpha)
2786             {
2787                 aLine.setLength(0);
2788                 aLine.append(OString::number(nObject) + " 0 obj\n<<");
2789                 if (m_bIsPDF_A1)
2790                 {
2791                     aLine.append("/CA 1.0/ca 1.0");
2792                     m_aErrors.insert(PDFWriter::Warning_Transparency_Omitted_PDFA);
2793                 }
2794                 else
2795                 {
2796                     aLine.append("/CA ");
2797                     appendDouble(nAlpha / 255., aLine);
2798                     aLine.append("/ca ");
2799                     appendDouble(nAlpha / 255., aLine);
2800                 }
2801                 aLine.append(">>\nendobj\n\n");
2802                 if (!updateObject(nObject))
2803                     return false;
2804                 if (!writeBuffer(aLine))
2805                     return false;
2806             }
2807         }
2808 
2809         // write bitmap objects
2810         for (auto& aBitmap : aUsedBitmaps)
2811             writeBitmapObject(aBitmap);
2812 
2813         // write resources dict
2814         aLine.setLength(0);
2815         aLine.append(OString::number(nResources) + " 0 obj\n");
2816         aResourceDict.append(aLine, nFontDict);
2817         aLine.append("endobj\n\n");
2818         if (!updateObject(nResources))
2819             return false;
2820         if (!writeBuffer(aLine))
2821             return false;
2822 
2823         rFontIDToObject[rSubset.m_nFontID] = nFontObject;
2824     }
2825 
2826     return true;
2827 }
2828 
2829 typedef int ThreeInts[3];
2830 static bool getPfbSegmentLengths( const unsigned char* pFontBytes, int nByteLen,
2831     ThreeInts& rSegmentLengths )
2832 {
2833     if( !pFontBytes || (nByteLen < 0) )
2834         return false;
2835     const unsigned char* pPtr = pFontBytes;
2836     const unsigned char* pEnd = pFontBytes + nByteLen;
2837 
2838     for(int & rSegmentLength : rSegmentLengths) {
2839         // read segment1 header
2840         if( pPtr+6 >= pEnd )
2841             return false;
2842         if( (pPtr[0] != 0x80) || (pPtr[1] >= 0x03) )
2843             return false;
2844         const int nLen = (pPtr[5]<<24) + (pPtr[4]<<16) + (pPtr[3]<<8) + pPtr[2];
2845         if( nLen <= 0)
2846             return false;
2847         rSegmentLength = nLen;
2848         pPtr += nLen + 6;
2849     }
2850 
2851     // read segment-end header
2852     if( pPtr+2 >= pEnd )
2853         return false;
2854     if( (pPtr[0] != 0x80) || (pPtr[1] != 0x03) )
2855         return false;
2856 
2857     return true;
2858 }
2859 
2860 static void appendSubsetName( int nSubsetID, std::u16string_view rPSName, OStringBuffer& rBuffer )
2861 {
2862     if( nSubsetID )
2863     {
2864         for( int i = 0; i < 6; i++ )
2865         {
2866             int nOffset = nSubsetID % 26;
2867             nSubsetID /= 26;
2868             rBuffer.append( static_cast<char>('A'+nOffset) );
2869         }
2870         rBuffer.append( '+' );
2871     }
2872     appendName( rPSName, rBuffer );
2873 }
2874 
2875 sal_Int32 PDFWriterImpl::createToUnicodeCMap( sal_uInt8 const * pEncoding,
2876                                               const std::vector<sal_Ucs>& rCodeUnits,
2877                                               const sal_Int32* pCodeUnitsPerGlyph,
2878                                               const sal_Int32* pEncToUnicodeIndex,
2879                                               uint32_t nGlyphs )
2880 {
2881     int nMapped = 0;
2882     for (auto n = 0u; n < nGlyphs; ++n)
2883         if (pCodeUnitsPerGlyph[n] && rCodeUnits[pEncToUnicodeIndex[n]])
2884             nMapped++;
2885 
2886     if( nMapped == 0 )
2887         return 0;
2888 
2889     sal_Int32 nStream = createObject();
2890     CHECK_RETURN( updateObject( nStream ) );
2891 
2892     OStringBuffer aContents( 1024 );
2893     aContents.append(
2894                      "/CIDInit/ProcSet findresource begin\n"
2895                      "12 dict begin\n"
2896                      "begincmap\n"
2897                      "/CIDSystemInfo<<\n"
2898                      "/Registry (Adobe)\n"
2899                      "/Ordering (UCS)\n"
2900                      "/Supplement 0\n"
2901                      ">> def\n"
2902                      "/CMapName/Adobe-Identity-UCS def\n"
2903                      "/CMapType 2 def\n"
2904                      "1 begincodespacerange\n"
2905                      "<00> <FF>\n"
2906                      "endcodespacerange\n"
2907                      );
2908     int nCount = 0;
2909     for (auto n = 0u; n < nGlyphs; ++n)
2910     {
2911         if (pCodeUnitsPerGlyph[n] && rCodeUnits[pEncToUnicodeIndex[n]])
2912         {
2913             if( (nCount % 100) == 0 )
2914             {
2915                 if( nCount )
2916                     aContents.append( "endbfchar\n" );
2917                 aContents.append( OString::number(static_cast<sal_Int32>(std::min(nMapped-nCount, 100)) )
2918                     + " beginbfchar\n" );
2919             }
2920             aContents.append( '<' );
2921             appendHex( static_cast<sal_Int8>(pEncoding[n]), aContents );
2922             aContents.append( "> <" );
2923             // TODO: handle code points>U+FFFF
2924             sal_Int32 nIndex = pEncToUnicodeIndex[n];
2925             for( sal_Int32 j = 0; j < pCodeUnitsPerGlyph[n]; j++ )
2926             {
2927                 appendHex( static_cast<sal_Int8>(rCodeUnits[nIndex + j] / 256), aContents );
2928                 appendHex( static_cast<sal_Int8>(rCodeUnits[nIndex + j] & 255), aContents );
2929             }
2930             aContents.append( ">\n" );
2931             nCount++;
2932         }
2933     }
2934     aContents.append( "endbfchar\n"
2935                       "endcmap\n"
2936                       "CMapName currentdict /CMap defineresource pop\n"
2937                       "end\n"
2938                       "end\n" );
2939     SvMemoryStream aStream;
2940     if (!g_bDebugDisableCompression)
2941     {
2942         ZCodec aCodec( 0x4000, 0x4000 );
2943         aCodec.BeginCompression();
2944         aCodec.Write( aStream, reinterpret_cast<const sal_uInt8*>(aContents.getStr()), aContents.getLength() );
2945         aCodec.EndCompression();
2946     }
2947 
2948     if (g_bDebugDisableCompression)
2949     {
2950         emitComment( "PDFWriterImpl::createToUnicodeCMap" );
2951     }
2952     OStringBuffer aLine( 40 );
2953 
2954     aLine.append( OString::number(nStream ) + " 0 obj\n<</Length " );
2955     sal_uInt64 nLen = 0;
2956     if (!g_bDebugDisableCompression)
2957     {
2958         nLen = aStream.Tell();
2959         aStream.Seek( 0 );
2960         aLine.append( OString::number(nLen) + "/Filter/FlateDecode" );
2961     }
2962     else
2963         aLine.append( aContents.getLength() );
2964     aLine.append( ">>\nstream\n" );
2965     CHECK_RETURN( writeBuffer( aLine ) );
2966     checkAndEnableStreamEncryption( nStream );
2967     if (!g_bDebugDisableCompression)
2968     {
2969         CHECK_RETURN( writeBufferBytes( aStream.GetData(), nLen ) );
2970     }
2971     else
2972     {
2973         CHECK_RETURN( writeBuffer( aContents ) );
2974     }
2975     disableStreamEncryption();
2976     aLine.setLength( 0 );
2977     aLine.append( "\nendstream\n"
2978                   "endobj\n\n" );
2979     CHECK_RETURN( writeBuffer( aLine ) );
2980     return nStream;
2981 }
2982 
2983 sal_Int32 PDFWriterImpl::emitFontDescriptor( const vcl::font::PhysicalFontFace* pFace, FontSubsetInfo const & rInfo, sal_Int32 nSubsetID, sal_Int32 nFontStream )
2984 {
2985     OStringBuffer aLine( 1024 );
2986     // get font flags, see PDF reference 1.4 p. 358
2987     // possibly characters outside Adobe standard encoding
2988     // so set Symbolic flag
2989     sal_Int32 nFontFlags = (1<<2);
2990     if( pFace->GetItalic() == ITALIC_NORMAL || pFace->GetItalic() == ITALIC_OBLIQUE )
2991         nFontFlags |= (1 << 6);
2992     if( pFace->GetPitch() == PITCH_FIXED )
2993         nFontFlags |= 1;
2994     if( pFace->GetFamilyType() == FAMILY_SCRIPT )
2995         nFontFlags |= (1 << 3);
2996     else if( pFace->GetFamilyType() == FAMILY_ROMAN )
2997         nFontFlags |= (1 << 1);
2998 
2999     sal_Int32 nFontDescriptor = createObject();
3000     CHECK_RETURN( updateObject( nFontDescriptor ) );
3001     aLine.setLength( 0 );
3002     aLine.append(
3003         OString::number(nFontDescriptor)
3004         + " 0 obj\n"
3005           "<</Type/FontDescriptor/FontName/" );
3006     appendSubsetName( nSubsetID, rInfo.m_aPSName, aLine );
3007     aLine.append( "\n"
3008                   "/Flags "
3009         + OString::number( nFontFlags )
3010         + "\n"
3011           "/FontBBox["
3012     // note: Top and Bottom are reversed in VCL and PDF rectangles
3013         + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Left()) )
3014         + " "
3015         + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Top()) )
3016         + " "
3017         + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Right()) )
3018         + " "
3019         + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Bottom()+1) )
3020         +  "]/ItalicAngle " );
3021     if( pFace->GetItalic() == ITALIC_OBLIQUE || pFace->GetItalic() == ITALIC_NORMAL )
3022         aLine.append( "-30" );
3023     else
3024         aLine.append( "0" );
3025     aLine.append( "\n"
3026                   "/Ascent "
3027         + OString::number( static_cast<sal_Int32>(rInfo.m_nAscent) )
3028         + "\n"
3029           "/Descent "
3030         + OString::number( static_cast<sal_Int32>(-rInfo.m_nDescent) )
3031         + "\n"
3032           "/CapHeight "
3033         + OString::number( static_cast<sal_Int32>(rInfo.m_nCapHeight) )
3034     // According to PDF reference 1.4 StemV is required
3035     // seems a tad strange to me, but well ...
3036         + "\n"
3037           "/StemV 80\n" );
3038     if( nFontStream )
3039     {
3040         aLine.append( "/FontFile" );
3041         switch( rInfo.m_nFontType )
3042         {
3043             case FontType::SFNT_TTF:
3044                 aLine.append( '2' );
3045                 break;
3046             case FontType::TYPE1_PFA:
3047             case FontType::TYPE1_PFB:
3048             case FontType::ANY_TYPE1:
3049                 break;
3050             default:
3051                 OSL_FAIL( "unknown fonttype in PDF font descriptor" );
3052                 return 0;
3053         }
3054         aLine.append( " " + OString::number(nFontStream) + " 0 R\n" );
3055     }
3056     aLine.append( ">>\n"
3057                   "endobj\n\n" );
3058     CHECK_RETURN( writeBuffer( aLine ) );
3059 
3060     return nFontDescriptor;
3061 }
3062 
3063 void PDFWriterImpl::appendBuildinFontsToDict( OStringBuffer& rDict ) const
3064 {
3065     for (auto const& item : m_aBuildinFontToObjectMap)
3066     {
3067         rDict.append( pdf::BuildinFontFace::Get(item.first).getNameObject() );
3068         rDict.append( ' ' );
3069         rDict.append( item.second );
3070         rDict.append( " 0 R" );
3071     }
3072 }
3073 
3074 bool PDFWriterImpl::emitFonts()
3075 {
3076     OStringBuffer aLine( 1024 );
3077 
3078     std::map< sal_Int32, sal_Int32 > aFontIDToObject;
3079 
3080     for (const auto & subset : m_aSubsets)
3081     {
3082         for (auto & s_subset :subset.second.m_aSubsets)
3083         {
3084             sal_GlyphId pGlyphIds[ 256 ] = {};
3085             sal_Int32 pWidths[ 256 ];
3086             sal_uInt8 pEncoding[ 256 ] = {};
3087             sal_Int32 pEncToUnicodeIndex[ 256 ] = {};
3088             sal_Int32 pCodeUnitsPerGlyph[ 256 ] = {};
3089             std::vector<sal_Ucs> aCodeUnits;
3090             sal_Int32 nToUnicodeStream = 0;
3091 
3092             // fill arrays and prepare encoding index map
3093             auto nGlyphs = fillSubsetArrays(s_subset, pGlyphIds, pWidths, pEncoding, pEncToUnicodeIndex,
3094                                             pCodeUnitsPerGlyph, aCodeUnits, nToUnicodeStream);
3095 
3096             std::vector<sal_uInt8> aBuffer;
3097             FontSubsetInfo aSubsetInfo;
3098             const auto* pFace = subset.first;
3099             if (pFace->CreateFontSubset(aBuffer, pGlyphIds, pEncoding, nGlyphs, aSubsetInfo))
3100             {
3101                 // create font stream
3102                 if (g_bDebugDisableCompression)
3103                 {
3104                     emitComment( "PDFWriterImpl::emitFonts" );
3105                 }
3106                 sal_Int32 nFontStream = createObject();
3107                 sal_Int32 nStreamLengthObject = createObject();
3108                 if ( !updateObject( nFontStream ) ) return false;
3109                 aLine.setLength( 0 );
3110                 aLine.append( OString::number(nFontStream)
3111                     + " 0 obj\n"
3112                       "<</Length "
3113                     + OString::number( nStreamLengthObject ) );
3114                 if (!g_bDebugDisableCompression)
3115                     aLine.append( " 0 R"
3116                                  "/Filter/FlateDecode"
3117                                  "/Length1 " );
3118                 else
3119                     aLine.append( " 0 R"
3120                                  "/Length1 " );
3121 
3122                 sal_uInt64 nStartPos = 0;
3123                 if( aSubsetInfo.m_nFontType == FontType::SFNT_TTF )
3124                 {
3125                     aLine.append( OString::number(static_cast<sal_Int32>(aBuffer.size()))
3126                                + ">>\n"
3127                                  "stream\n" );
3128                     if ( !writeBuffer( aLine ) ) return false;
3129                     if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
3130 
3131                     // copy font file
3132                     beginCompression();
3133                     checkAndEnableStreamEncryption( nFontStream );
3134                     if (!writeBufferBytes(aBuffer.data(), aBuffer.size()))
3135                         return false;
3136                 }
3137                 else if( aSubsetInfo.m_nFontType & FontType::CFF_FONT)
3138                 {
3139                     // TODO: implement
3140                     OSL_FAIL( "PDFWriterImpl does not support CFF-font subsets yet!" );
3141                 }
3142                 else if( aSubsetInfo.m_nFontType & FontType::TYPE1_PFB) // TODO: also support PFA?
3143                 {
3144                     // get the PFB-segment lengths
3145                     ThreeInts aSegmentLengths = {0,0,0};
3146                     getPfbSegmentLengths(aBuffer.data(), static_cast<int>(aBuffer.size()), aSegmentLengths);
3147                     // the lengths below are mandatory for PDF-exported Type1 fonts
3148                     // because the PFB segment headers get stripped! WhyOhWhy.
3149                     aLine.append( OString::number(static_cast<sal_Int32>(aSegmentLengths[0]) )
3150                         + "/Length2 "
3151                         + OString::number( static_cast<sal_Int32>(aSegmentLengths[1]) )
3152                         + "/Length3 "
3153                         + OString::number( static_cast<sal_Int32>(aSegmentLengths[2]) )
3154                         + ">>\n"
3155                           "stream\n" );
3156                     if ( !writeBuffer( aLine ) ) return false;
3157                     if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
3158 
3159                     // emit PFB-sections without section headers
3160                     beginCompression();
3161                     checkAndEnableStreamEncryption( nFontStream );
3162                     if ( !writeBufferBytes( &aBuffer[6], aSegmentLengths[0] ) ) return false;
3163                     if ( !writeBufferBytes( &aBuffer[12] + aSegmentLengths[0], aSegmentLengths[1] ) ) return false;
3164                     if ( !writeBufferBytes( &aBuffer[18] + aSegmentLengths[0] + aSegmentLengths[1], aSegmentLengths[2] ) ) return false;
3165                 }
3166                 else
3167                 {
3168                     SAL_INFO("vcl.pdfwriter", "PDF: CreateFontSubset result in not yet supported format=" << static_cast<int>(aSubsetInfo.m_nFontType));
3169                     aLine.append( "0 >>\nstream\n" );
3170                 }
3171 
3172                 endCompression();
3173                 disableStreamEncryption();
3174 
3175                 sal_uInt64 nEndPos = 0;
3176                 if ( osl::File::E_None != m_aFile.getPos(nEndPos) ) return false;
3177                 // end the stream
3178                 aLine.setLength( 0 );
3179                 aLine.append( "\nendstream\nendobj\n\n" );
3180                 if ( !writeBuffer( aLine ) ) return false;
3181 
3182                 // emit stream length object
3183                 if ( !updateObject( nStreamLengthObject ) ) return false;
3184                 aLine.setLength( 0 );
3185                 aLine.append( OString::number(nStreamLengthObject)
3186                     + " 0 obj\n"
3187                     + OString::number( static_cast<sal_Int64>(nEndPos-nStartPos) )
3188                     + "\nendobj\n\n" );
3189                 if ( !writeBuffer( aLine ) ) return false;
3190 
3191                 // write font descriptor
3192                 sal_Int32 nFontDescriptor = emitFontDescriptor( subset.first, aSubsetInfo, s_subset.m_nFontID, nFontStream );
3193 
3194                 if( nToUnicodeStream )
3195                     nToUnicodeStream = createToUnicodeCMap( pEncoding, aCodeUnits, pCodeUnitsPerGlyph, pEncToUnicodeIndex, nGlyphs );
3196 
3197                 sal_Int32 nFontObject = createObject();
3198                 if ( !updateObject( nFontObject ) ) return false;
3199                 aLine.setLength( 0 );
3200                 aLine.append( OString::number(nFontObject) + " 0 obj\n" );
3201                 aLine.append( (aSubsetInfo.m_nFontType & FontType::ANY_TYPE1) ?
3202                              "<</Type/Font/Subtype/Type1/BaseFont/" :
3203                              "<</Type/Font/Subtype/TrueType/BaseFont/" );
3204                 appendSubsetName( s_subset.m_nFontID, aSubsetInfo.m_aPSName, aLine );
3205                 aLine.append( "\n"
3206                              "/FirstChar 0\n"
3207                              "/LastChar "
3208                     + OString::number( static_cast<sal_Int32>(nGlyphs-1) )
3209                     + "\n"
3210                       "/Widths[" );
3211                 for (auto i = 0u; i < nGlyphs; i++)
3212                 {
3213                     aLine.append( pWidths[ i ] );
3214                     aLine.append( ((i & 15) == 15) ? "\n" : " " );
3215                 }
3216                 aLine.append( "]\n"
3217                              "/FontDescriptor "
3218                     + OString::number( nFontDescriptor )
3219                     + " 0 R\n" );
3220                 if( nToUnicodeStream )
3221                 {
3222                     aLine.append( "/ToUnicode "
3223                         + OString::number( nToUnicodeStream )
3224                         + " 0 R\n" );
3225                 }
3226                 aLine.append( ">>\n"
3227                              "endobj\n\n" );
3228                 if ( !writeBuffer( aLine ) ) return false;
3229 
3230                 aFontIDToObject[ s_subset.m_nFontID ] = nFontObject;
3231             }
3232             else
3233             {
3234                 OStringBuffer aErrorComment( 256 );
3235                 aErrorComment.append( "CreateFontSubset failed for font \""
3236                     + OUStringToOString( pFace->GetFamilyName(), RTL_TEXTENCODING_UTF8 )
3237                     + "\"" );
3238                 if( pFace->GetItalic() == ITALIC_NORMAL )
3239                     aErrorComment.append( " italic" );
3240                 else if( pFace->GetItalic() == ITALIC_OBLIQUE )
3241                     aErrorComment.append( " oblique" );
3242                 aErrorComment.append( " weight=" + OString::number( sal_Int32(pFace->GetWeight()) ) );
3243                 emitComment( aErrorComment.getStr() );
3244             }
3245         }
3246     }
3247 
3248     // emit system fonts
3249     for (auto const& systemFont : m_aSystemFonts)
3250     {
3251         std::map< sal_Int32, sal_Int32 > aObjects = emitSystemFont( systemFont.first, systemFont.second );
3252         for (auto const& item : aObjects)
3253         {
3254             if ( !item.second ) return false;
3255             aFontIDToObject[ item.first ] = item.second;
3256         }
3257     }
3258 
3259     // emit Type3 fonts
3260     for (auto const& it : m_aType3Fonts)
3261     {
3262         if (!emitType3Font(it.first, it.second, aFontIDToObject))
3263             return false;
3264     }
3265 
3266     OStringBuffer aFontDict( 1024 );
3267     aFontDict.append( OString::number(getFontDictObject())
3268         + " 0 obj\n"
3269           "<<" );
3270     int ni = 0;
3271     for (auto const& itemMap : aFontIDToObject)
3272     {
3273         aFontDict.append( "/F"
3274             + OString::number( itemMap.first )
3275             + " "
3276             + OString::number( itemMap.second )
3277             + " 0 R" );
3278         if( ((++ni) & 7) == 0 )
3279             aFontDict.append( '\n' );
3280     }
3281     // emit builtin font for widget appearances / variable text
3282     for (auto & item : m_aBuildinFontToObjectMap)
3283     {
3284         rtl::Reference<pdf::BuildinFontFace> aData(new pdf::BuildinFontFace(item.first));
3285         item.second = emitBuildinFont( aData.get(), item.second );
3286     }
3287 
3288     appendBuildinFontsToDict(aFontDict);
3289     aFontDict.append( "\n>>\nendobj\n\n" );
3290 
3291     if ( !updateObject( getFontDictObject() ) ) return false;
3292     if ( !writeBuffer( aFontDict ) ) return false;
3293     return true;
3294 }
3295 
3296 sal_Int32 PDFWriterImpl::emitResources()
3297 {
3298     // emit shadings
3299     if( ! m_aGradients.empty() )
3300         CHECK_RETURN( emitGradients() );
3301     // emit tilings
3302     if( ! m_aTilings.empty() )
3303         CHECK_RETURN( emitTilings() );
3304 
3305     // emit font dict
3306     CHECK_RETURN( emitFonts() );
3307 
3308     // emit Resource dict
3309     OStringBuffer aLine( 512 );
3310     sal_Int32 nResourceDict = getResourceDictObj();
3311     CHECK_RETURN( updateObject( nResourceDict ) );
3312     aLine.setLength( 0 );
3313     aLine.append( OString::number(nResourceDict)
3314         + " 0 obj\n" );
3315     m_aGlobalResourceDict.append( aLine, getFontDictObject() );
3316     aLine.append( "endobj\n\n" );
3317     CHECK_RETURN( writeBuffer( aLine ) );
3318     return nResourceDict;
3319 }
3320 
3321 sal_Int32 PDFWriterImpl::updateOutlineItemCount( std::vector< sal_Int32 >& rCounts,
3322                                                  sal_Int32 nItemLevel,
3323                                                  sal_Int32 nCurrentItemId )
3324 {
3325     /* The /Count number of an item is
3326        positive: the number of visible subitems
3327        negative: the negative number of subitems that will become visible if
3328                  the item gets opened
3329        see PDF ref 1.4 p 478
3330     */
3331 
3332     sal_Int32 nCount = 0;
3333 
3334     if( m_aContext.OpenBookmarkLevels < 0           || // all levels are visible
3335         m_aContext.OpenBookmarkLevels >= nItemLevel    // this level is visible
3336       )
3337     {
3338         PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
3339         sal_Int32 nChildren = rItem.m_aChildren.size();
3340         for( sal_Int32 i = 0; i < nChildren; i++ )
3341             nCount += updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
3342         rCounts[nCurrentItemId] = nCount;
3343         // return 1 (this item) + visible sub items
3344         if( nCount < 0 )
3345             nCount = 0;
3346         nCount++;
3347     }
3348     else
3349     {
3350         // this bookmark level is invisible
3351         PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
3352         sal_Int32 nChildren = rItem.m_aChildren.size();
3353         rCounts[ nCurrentItemId ] = -sal_Int32(rItem.m_aChildren.size());
3354         for( sal_Int32 i = 0; i < nChildren; i++ )
3355             updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
3356         nCount = -1;
3357     }
3358 
3359     return nCount;
3360 }
3361 
3362 sal_Int32 PDFWriterImpl::emitOutline()
3363 {
3364     int i, nItems = m_aOutline.size();
3365 
3366     // do we have an outline at all ?
3367     if( nItems < 2 )
3368         return 0;
3369 
3370     // reserve object numbers for all outline items
3371     for( i = 0; i < nItems; ++i )
3372         m_aOutline[i].m_nObject = createObject();
3373 
3374     // update all parent, next and prev object ids
3375     for( i = 0; i < nItems; ++i )
3376     {
3377         PDFOutlineEntry& rItem = m_aOutline[i];
3378         int nChildren = rItem.m_aChildren.size();
3379 
3380         if( nChildren )
3381         {
3382             for( int n = 0; n < nChildren; ++n )
3383             {
3384                 PDFOutlineEntry& rChild = m_aOutline[ rItem.m_aChildren[n] ];
3385 
3386                 rChild.m_nParentObject = rItem.m_nObject;
3387                 rChild.m_nPrevObject = (n > 0) ? m_aOutline[ rItem.m_aChildren[n-1] ].m_nObject : 0;
3388                 rChild.m_nNextObject = (n < nChildren-1) ? m_aOutline[ rItem.m_aChildren[n+1] ].m_nObject : 0;
3389             }
3390 
3391         }
3392     }
3393 
3394     // calculate Count entries for all items
3395     std::vector< sal_Int32 > aCounts( nItems );
3396     updateOutlineItemCount( aCounts, 0, 0 );
3397 
3398     // emit hierarchy
3399     for( i = 0; i < nItems; ++i )
3400     {
3401         PDFOutlineEntry& rItem = m_aOutline[i];
3402         OStringBuffer aLine( 1024 );
3403 
3404         CHECK_RETURN( updateObject( rItem.m_nObject ) );
3405         aLine.append( OString::number(rItem.m_nObject)
3406             + " 0 obj\n"
3407               "<<" );
3408         // number of visible children (all levels)
3409         if( i > 0 || aCounts[0] > 0 )
3410         {
3411             aLine.append( "/Count " + OString::number( aCounts[i] ) );
3412         }
3413         if( ! rItem.m_aChildren.empty() )
3414         {
3415             // children list: First, Last
3416             aLine.append( "/First "
3417                 + OString::number( m_aOutline[rItem.m_aChildren.front()].m_nObject )
3418                 + " 0 R/Last "
3419                 + OString::number( m_aOutline[rItem.m_aChildren.back()].m_nObject )
3420                 + " 0 R\n" );
3421         }
3422         if( i > 0 )
3423         {
3424             // Title, Dest, Parent, Prev, Next
3425             aLine.append( "/Title" );
3426             appendUnicodeTextStringEncrypt( rItem.m_aTitle, rItem.m_nObject, aLine );
3427             aLine.append( "\n" );
3428             // Dest is not required
3429             if( rItem.m_nDestID >= 0 && o3tl::make_unsigned(rItem.m_nDestID) < m_aDests.size() )
3430             {
3431                 aLine.append( "/Dest" );
3432                 appendDest( rItem.m_nDestID, aLine );
3433             }
3434             aLine.append( "/Parent "
3435                 + OString::number( rItem.m_nParentObject )
3436                 + " 0 R" );
3437             if( rItem.m_nPrevObject )
3438             {
3439                 aLine.append( "/Prev "
3440                     + OString::number( rItem.m_nPrevObject )
3441                     + " 0 R" );
3442             }
3443             if( rItem.m_nNextObject )
3444             {
3445                 aLine.append( "/Next "
3446                     + OString::number( rItem.m_nNextObject )
3447                     + " 0 R" );
3448             }
3449         }
3450         aLine.append( ">>\nendobj\n\n" );
3451         CHECK_RETURN( writeBuffer( aLine ) );
3452     }
3453 
3454     return m_aOutline[0].m_nObject;
3455 }
3456 
3457 #undef CHECK_RETURN
3458 #define CHECK_RETURN( x ) if( !x ) return false
3459 
3460 bool PDFWriterImpl::appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer )
3461 {
3462     if( nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size() )
3463     {
3464         SAL_INFO("vcl.pdfwriter", "ERROR: invalid dest " << static_cast<int>(nDestID) << " requested");
3465         return false;
3466     }
3467 
3468     const PDFDest& rDest        = m_aDests[ nDestID ];
3469     const PDFPage& rDestPage    = m_aPages[ rDest.m_nPage ];
3470 
3471     rBuffer.append( '[' );
3472     rBuffer.append( rDestPage.m_nPageObject );
3473     rBuffer.append( " 0 R" );
3474 
3475     switch( rDest.m_eType )
3476     {
3477         case PDFWriter::DestAreaType::XYZ:
3478         default:
3479             rBuffer.append( "/XYZ " );
3480             appendFixedInt( rDest.m_aRect.Left(), rBuffer );
3481             rBuffer.append( ' ' );
3482             appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
3483             rBuffer.append( " 0" );
3484             break;
3485         case PDFWriter::DestAreaType::FitRectangle:
3486             rBuffer.append( "/FitR " );
3487             appendFixedInt( rDest.m_aRect.Left(), rBuffer );
3488             rBuffer.append( ' ' );
3489             appendFixedInt( rDest.m_aRect.Top(), rBuffer );
3490             rBuffer.append( ' ' );
3491             appendFixedInt( rDest.m_aRect.Right(), rBuffer );
3492             rBuffer.append( ' ' );
3493             appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
3494             break;
3495     }
3496     rBuffer.append( ']' );
3497 
3498     return true;
3499 }
3500 
3501 void PDFWriterImpl::addDocumentAttachedFile(OUString const& rFileName, OUString const& rMimeType, OUString const& rDescription, std::unique_ptr<PDFOutputStream> rStream)
3502 {
3503     sal_Int32 nObjectID = addEmbeddedFile(std::move(rStream), rMimeType);
3504     auto& rAttachedFile = m_aDocumentAttachedFiles.emplace_back();
3505     rAttachedFile.maFilename = rFileName;
3506     rAttachedFile.maMimeType = rMimeType;
3507     rAttachedFile.maDescription = rDescription;
3508     rAttachedFile.mnEmbeddedFileObjectId = nObjectID;
3509     rAttachedFile.mnObjectId = createObject();
3510 }
3511 
3512 sal_Int32 PDFWriterImpl::addEmbeddedFile(std::unique_ptr<PDFOutputStream> rStream, OUString const& rMimeType)
3513 {
3514     sal_Int32 aObjectID = createObject();
3515     auto& rEmbedded = m_aEmbeddedFiles.emplace_back();
3516     rEmbedded.m_nObject = aObjectID;
3517     rEmbedded.m_aSubType = rMimeType;
3518     rEmbedded.m_pStream = std::move(rStream);
3519     return aObjectID;
3520 }
3521 
3522 sal_Int32 PDFWriterImpl::addEmbeddedFile(BinaryDataContainer const & rDataContainer)
3523 {
3524     sal_Int32 aObjectID = createObject();
3525     m_aEmbeddedFiles.emplace_back();
3526     m_aEmbeddedFiles.back().m_nObject = aObjectID;
3527     m_aEmbeddedFiles.back().m_aDataContainer = rDataContainer;
3528     return aObjectID;
3529 }
3530 
3531 bool PDFWriterImpl::emitScreenAnnotations()
3532 {
3533     int nAnnots = m_aScreens.size();
3534     for (int i = 0; i < nAnnots; i++)
3535     {
3536         const PDFScreen& rScreen = m_aScreens[i];
3537 
3538         OStringBuffer aLine;
3539         bool bEmbed = false;
3540         if (!rScreen.m_aTempFileURL.isEmpty())
3541         {
3542             bEmbed = true;
3543             if (!updateObject(rScreen.m_nTempFileObject))
3544                 continue;
3545 
3546             SvFileStream aFileStream(rScreen.m_aTempFileURL, StreamMode::READ);
3547             SvMemoryStream aMemoryStream;
3548             aMemoryStream.WriteStream(aFileStream);
3549 
3550             aLine.append(rScreen.m_nTempFileObject);
3551             aLine.append(" 0 obj\n");
3552             aLine.append("<< /Type /EmbeddedFile /Length ");
3553             aLine.append(static_cast<sal_Int64>(aMemoryStream.GetSize()));
3554             aLine.append(" >>\nstream\n");
3555             CHECK_RETURN(writeBuffer(aLine));
3556             aLine.setLength(0);
3557 
3558             CHECK_RETURN(writeBufferBytes(aMemoryStream.GetData(), aMemoryStream.GetSize()));
3559 
3560             aLine.append("\nendstream\nendobj\n\n");
3561             CHECK_RETURN(writeBuffer(aLine));
3562             aLine.setLength(0);
3563         }
3564 
3565         if (!updateObject(rScreen.m_nObject))
3566             continue;
3567 
3568         // Annot dictionary.
3569         aLine.append(OString::number(rScreen.m_nObject)
3570             + " 0 obj\n"
3571               "<</Type/Annot"
3572               "/Subtype/Screen/Rect[");
3573         appendFixedInt(rScreen.m_aRect.Left(), aLine);
3574         aLine.append(' ');
3575         appendFixedInt(rScreen.m_aRect.Top(), aLine);
3576         aLine.append(' ');
3577         appendFixedInt(rScreen.m_aRect.Right(), aLine);
3578         aLine.append(' ');
3579         appendFixedInt(rScreen.m_aRect.Bottom(), aLine);
3580         aLine.append("]");
3581 
3582         // Action dictionary.
3583         aLine.append("/A<</Type/Action /S/Rendition /AN "
3584             + OString::number(rScreen.m_nObject)
3585             + " 0 R ");
3586 
3587         // Rendition dictionary.
3588         aLine.append("/R<</Type/Rendition /S/MR ");
3589 
3590         // MediaClip dictionary.
3591         aLine.append("/C<</Type/MediaClip /S/MCD ");
3592         if (bEmbed)
3593         {
3594             aLine.append("\n/D << /Type /Filespec /F (<embedded file>) ");
3595             if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
3596             {   // ISO 14289-1:2014, Clause: 7.11
3597                 aLine.append("/UF (<embedded file>) ");
3598             }
3599             aLine.append("/EF << /F ");
3600             aLine.append(rScreen.m_nTempFileObject);
3601             aLine.append(" 0 R >>");
3602         }
3603         else
3604         {
3605             // Linked.
3606             aLine.append("\n/D << /Type /Filespec /FS /URL /F ");
3607             appendLiteralStringEncrypt(rScreen.m_aURL, rScreen.m_nObject, aLine, osl_getThreadTextEncoding());
3608             if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
3609             {   // ISO 14289-1:2014, Clause: 7.11
3610                 aLine.append("/UF ");
3611                 appendUnicodeTextStringEncrypt(rScreen.m_aURL, rScreen.m_nObject, aLine);
3612             }
3613         }
3614         if (PDFWriter::PDFVersion::PDF_1_6 <= m_aContext.Version
3615             && !rScreen.m_AltText.isEmpty())
3616         {   // ISO 14289-1:2014, Clause: 7.11
3617             aLine.append("/Desc ");
3618             appendUnicodeTextStringEncrypt(rScreen.m_AltText, rScreen.m_nObject, aLine);
3619         }
3620         aLine.append(" >>\n"); // end of /D
3621         // Allow playing the video via a tempfile.
3622         aLine.append("/P <</TF (TEMPACCESS)>>");
3623         // ISO 14289-1:2014, Clause: 7.18.6.2
3624         aLine.append("/CT ");
3625         appendLiteralStringEncrypt(rScreen.m_MimeType, rScreen.m_nObject, aLine);
3626         // ISO 14289-1:2014, Clause: 7.18.6.2
3627         // Alt text is a "Multi-language Text Array"
3628         aLine.append(" /Alt [ () ");
3629         appendUnicodeTextStringEncrypt(rScreen.m_AltText, rScreen.m_nObject, aLine);
3630         aLine.append(" ] "
3631                      ">>");
3632 
3633         // End Rendition dictionary by requesting play/pause/stop controls.
3634         aLine.append("/P<</BE<</C true >>>>"
3635                      ">>");
3636 
3637         // End Action dictionary.
3638         aLine.append("/OP 0 >>");
3639 
3640         if (-1 != rScreen.m_nStructParent)
3641         {
3642             aLine.append("\n/StructParent "
3643                 + OString::number(rScreen.m_nStructParent)
3644                 + "\n");
3645         }
3646 
3647         // End Annot dictionary.
3648         aLine.append("/P "
3649             + OString::number(m_aPages[rScreen.m_nPage].m_nPageObject)
3650             + " 0 R\n>>\nendobj\n\n");
3651         CHECK_RETURN(writeBuffer(aLine));
3652     }
3653 
3654     return true;
3655 }
3656 
3657 bool PDFWriterImpl::emitLinkAnnotations()
3658 {
3659     MARK("PDFWriterImpl::emitLinkAnnotations");
3660     int nAnnots = m_aLinks.size();
3661     for( int i = 0; i < nAnnots; i++ )
3662     {
3663         const PDFLink& rLink            = m_aLinks[i];
3664         if( ! updateObject( rLink.m_nObject ) )
3665             continue;
3666 
3667         OStringBuffer aLine( 1024 );
3668         aLine.append( rLink.m_nObject );
3669         aLine.append( " 0 obj\n" );
3670 // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
3671 // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
3672         aLine.append( "<</Type/Annot" );
3673         if( m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3)
3674             aLine.append( "/F 4" );
3675         aLine.append( "/Subtype/Link/Border[0 0 0]/Rect[" );
3676 
3677         appendFixedInt( rLink.m_aRect.Left()-7, aLine );//the +7 to have a better shape of the border rectangle
3678         aLine.append( ' ' );
3679         appendFixedInt( rLink.m_aRect.Top(), aLine );
3680         aLine.append( ' ' );
3681         appendFixedInt( rLink.m_aRect.Right()+7, aLine );//the +7 to have a better shape of the border rectangle
3682         aLine.append( ' ' );
3683         appendFixedInt( rLink.m_aRect.Bottom(), aLine );
3684         aLine.append( "]" );
3685         // ISO 14289-1:2014, Clause: 7.18.5
3686         aLine.append("/Contents");
3687         appendUnicodeTextStringEncrypt(rLink.m_AltText, rLink.m_nObject, aLine);
3688         if( rLink.m_nDest >= 0 )
3689         {
3690             aLine.append( "/Dest" );
3691             appendDest( rLink.m_nDest, aLine );
3692         }
3693         else
3694         {
3695 /*
3696 destination is external to the document, so
3697 we check in the following sequence:
3698 
3699  if target type is neither .pdf, nor .od[tpgs], then
3700           check if relative or absolute and act accordingly (use URI or 'launch application' as requested)
3701                              end processing
3702  else if target is .od[tpgs]: then
3703       if conversion of type from od[tpgs]  to pdf is requested, convert it and this becomes the new target file
3704       processing continue
3705 
3706  if (new)target is .pdf : then
3707      if GotToR is requested, then
3708            convert the target in GoToR where the fragment of the URI is
3709            considered the named destination in the target file, set relative or absolute as requested
3710      else strip the fragment from URL and then set URI or 'launch application' as requested
3711 */
3712 
3713 // FIXME: check if the decode mechanisms for URL processing throughout this implementation
3714 // are the correct one!!
3715 
3716 // extract target file type
3717             auto url(URIHelper::resolveIdnaHost(rLink.m_aURL));
3718 
3719             INetURLObject aDocumentURL( m_aContext.BaseURL );
3720             INetURLObject aTargetURL( url );
3721             bool bSetGoToRMode = false;
3722             bool    bTargetHasPDFExtension = false;
3723             INetProtocol eTargetProtocol = aTargetURL.GetProtocol();
3724             bool    bIsUNCPath = false;
3725             bool    bUnparsedURI = false;
3726 
3727             // check if the protocol is a known one, or if there is no protocol at all (on target only)
3728             // if there is no protocol, make the target relative to the current document directory
3729             // getting the needed URL information from the current document path
3730             if( eTargetProtocol == INetProtocol::NotValid )
3731             {
3732                 if( url.getLength() > 4 && url.startsWith("\\\\\\\\"))
3733                 {
3734                     bIsUNCPath = true;
3735                 }
3736                 else
3737                 {
3738                     INetURLObject aNewURL(rtl::Uri::convertRelToAbs(
3739                                                 (m_aContext.BaseURL.getLength() > 0 ?
3740                                                     m_aContext.BaseURL :
3741                                                     //use dummy location if empty
3742                                                     u"http://ahost.ax"),
3743                                                 url));
3744                     aTargetURL = aNewURL; //reassign the new target URL
3745 
3746                     //recompute the target protocol, with the new URL
3747                     //normal URL processing resumes
3748                     eTargetProtocol = aTargetURL.GetProtocol();
3749 
3750                     bUnparsedURI = eTargetProtocol == INetProtocol::NotValid;
3751                 }
3752             }
3753 
3754             OUString aFileExtension = aTargetURL.GetFileExtension();
3755 
3756             // Check if the URL ends in '/': if yes it's a directory,
3757             // it will be forced to a URI link.
3758             // possibly a malformed URI, leave it as it is, force as URI
3759             if( aTargetURL.hasFinalSlash() )
3760                 m_aContext.DefaultLinkAction = PDFWriter::URIAction;
3761 
3762             if( !aFileExtension.isEmpty() )
3763             {
3764                 if( m_aContext.ConvertOOoTargetToPDFTarget )
3765                 {
3766                     bool bChangeFileExtensionToPDF = false;
3767                     //examine the file type (.odm .odt. .odp, odg, ods)
3768                     if( aFileExtension.equalsIgnoreAsciiCase( "odm" ) )
3769                         bChangeFileExtensionToPDF = true;
3770                     if( aFileExtension.equalsIgnoreAsciiCase( "odt" ) )
3771                         bChangeFileExtensionToPDF = true;
3772                     else if( aFileExtension.equalsIgnoreAsciiCase( "odp" ) )
3773                         bChangeFileExtensionToPDF = true;
3774                     else if( aFileExtension.equalsIgnoreAsciiCase( "odg" ) )
3775                         bChangeFileExtensionToPDF = true;
3776                     else if( aFileExtension.equalsIgnoreAsciiCase( "ods" ) )
3777                         bChangeFileExtensionToPDF = true;
3778                     if( bChangeFileExtensionToPDF )
3779                         aTargetURL.setExtension(u"pdf" );
3780                 }
3781                 //check if extension is pdf, see if GoToR should be forced
3782                 bTargetHasPDFExtension = aTargetURL.GetFileExtension().equalsIgnoreAsciiCase( "pdf" );
3783                 if( m_aContext.ForcePDFAction && bTargetHasPDFExtension )
3784                     bSetGoToRMode = true;
3785             }
3786             //prepare the URL, if relative or not
3787             INetProtocol eBaseProtocol = aDocumentURL.GetProtocol();
3788             //queue the string common to all types of actions
3789             aLine.append( "/A<</Type/Action/S");
3790             if( bIsUNCPath ) // handle Win UNC paths
3791             {
3792                 aLine.append( "/Launch/Win<</F" );
3793                 // INetURLObject is not good with UNC paths, use original path
3794                 appendLiteralStringEncrypt( url, rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
3795                 aLine.append( ">>" );
3796             }
3797             else
3798             {
3799                 bool bSetRelative = false;
3800                 bool bFileSpec = false;
3801                 //check if relative file link is requested and if the protocol is 'file://'
3802                 if( m_aContext.RelFsys && eBaseProtocol == eTargetProtocol && eTargetProtocol == INetProtocol::File )
3803                     bSetRelative = true;
3804 
3805                 OUString aFragment = aTargetURL.GetMark( INetURLObject::DecodeMechanism::NONE /*DecodeMechanism::WithCharset*/ ); //fragment as is,
3806                 if( !bSetGoToRMode )
3807                 {
3808                     switch( m_aContext.DefaultLinkAction )
3809                     {
3810                     default:
3811                     case PDFWriter::URIAction :
3812                     case PDFWriter::URIActionDestination :
3813                         aLine.append( "/URI/URI" );
3814                         break;
3815                     case PDFWriter::LaunchAction:
3816                         // now:
3817                         // if a launch action is requested and the hyperlink target has a fragment
3818                         // and the target file does not have a pdf extension, or it's not a 'file:://'
3819                         // protocol then force the uri action on it
3820                         // This code will permit the correct opening of application on web pages,
3821                         // the one that normally have fragments (but I may be wrong...)
3822                         // and will force the use of URI when the protocol is not file:
3823                         if( (!aFragment.isEmpty() && !bTargetHasPDFExtension) ||
3824                                         eTargetProtocol != INetProtocol::File )
3825                         {
3826                             aLine.append( "/URI/URI" );
3827                         }
3828                         else
3829                         {
3830                             aLine.append( "/Launch/F" );
3831                             bFileSpec = true;
3832                         }
3833                         break;
3834                     }
3835                 }
3836 
3837                 //fragment are encoded in the same way as in the named destination processing
3838                 if( bSetGoToRMode )
3839                 {
3840                     //add the fragment
3841                     OUString aURLNoMark = aTargetURL.GetURLNoMark( INetURLObject::DecodeMechanism::WithCharset );
3842                     aLine.append("/GoToR");
3843                     aLine.append("/F");
3844                     appendLiteralStringEncrypt( bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURLNoMark,
3845                                                                                          INetURLObject::EncodeMechanism::WasEncoded,
3846                                                                                          INetURLObject::DecodeMechanism::WithCharset ) :
3847                                                                    aURLNoMark, rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
3848                     if( !aFragment.isEmpty() )
3849                     {
3850                         aLine.append("/D/");
3851                         appendDestinationName( aFragment , aLine );
3852                     }
3853                 }
3854                 else
3855                 {
3856                     // change the fragment to accommodate the bookmark (only if the file extension
3857                     // is PDF and the requested action is of the correct type)
3858                     if(m_aContext.DefaultLinkAction == PDFWriter::URIActionDestination &&
3859                                bTargetHasPDFExtension && !aFragment.isEmpty() )
3860                     {
3861                         OStringBuffer aLineLoc( 1024 );
3862                         appendDestinationName( aFragment , aLineLoc );
3863                         //substitute the fragment
3864                         aTargetURL.SetMark( OStringToOUString(aLineLoc, RTL_TEXTENCODING_ASCII_US) );
3865                     }
3866                     OUString aURL = bUnparsedURI ? url :
3867                                                    aTargetURL.GetMainURL( bFileSpec ? INetURLObject::DecodeMechanism::WithCharset :
3868                                                                                       INetURLObject::DecodeMechanism::NONE );
3869                     appendLiteralStringEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURL,
3870                                                                                         INetURLObject::EncodeMechanism::WasEncoded,
3871                                                                                             bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE
3872                                                                                             ) :
3873                                                                                aURL , rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
3874                 }
3875             }
3876             aLine.append( ">>\n" );
3877         }
3878         if (rLink.m_nStructParent != -1)
3879         {
3880             aLine.append( "/StructParent " );
3881             aLine.append( rLink.m_nStructParent );
3882         }
3883         aLine.append( ">>\nendobj\n\n" );
3884         CHECK_RETURN( writeBuffer( aLine ) );
3885     }
3886 
3887     return true;
3888 }
3889 
3890 namespace
3891 {
3892 
3893 void appendAnnotationRect(tools::Rectangle const & rRectangle, OStringBuffer & aLine)
3894 {
3895     aLine.append("/Rect[");
3896     appendFixedInt(rRectangle.Left(), aLine);
3897     aLine.append(' ');
3898     appendFixedInt(rRectangle.Top(), aLine);
3899     aLine.append(' ');
3900     appendFixedInt(rRectangle.Right(), aLine);
3901     aLine.append(' ');
3902     appendFixedInt(rRectangle.Bottom(), aLine);
3903     aLine.append("] ");
3904 }
3905 
3906 } // end anonymous namespace
3907 
3908 void PDFWriterImpl::emitTextAnnotationLine(OStringBuffer & aLine, PDFNoteEntry const & rNote)
3909 {
3910     appendObjectID(rNote.m_nObject, aLine);
3911 
3912     aLine.append("<</Type /Annot /Subtype ");
3913     if (rNote.m_aContents.maPolygons.size() == 1)
3914     {
3915         auto const& rPolygon = rNote.m_aContents.maPolygons[0];
3916         aLine.append(rPolygon.isClosed() ? "/Polygon " : "/Polyline ");
3917         aLine.append("/Vertices [");
3918         for (sal_uInt32 i = 0; i < rPolygon.count(); ++i)
3919         {
3920             appendDouble(convertMm100ToPoint(rPolygon.getB2DPoint(i).getX()), aLine, nLog10Divisor);
3921             aLine.append(" ");
3922             appendDouble(m_aPages[rNote.m_nPage].getHeight()
3923                              - convertMm100ToPoint(rPolygon.getB2DPoint(i).getY()),
3924                          aLine, nLog10Divisor);
3925             aLine.append(" ");
3926         }
3927         aLine.append("] ");
3928         aLine.append("/C [");
3929         appendColor(rNote.m_aContents.annotColor, aLine, false);
3930         aLine.append("] ");
3931         if (rPolygon.isClosed())
3932         {
3933             aLine.append("/IC [");
3934             appendColor(rNote.m_aContents.interiorColor, aLine, false);
3935             aLine.append("] ");
3936         }
3937     }
3938     else if (rNote.m_aContents.maPolygons.size() > 1)
3939     {
3940         aLine.append("/Ink /InkList [");
3941         for (auto const& rPolygon : rNote.m_aContents.maPolygons)
3942         {
3943             aLine.append("[");
3944             for (sal_uInt32 i = 0; i < rPolygon.count(); ++i)
3945             {
3946                 appendDouble(convertMm100ToPoint(rPolygon.getB2DPoint(i).getX()), aLine,
3947                              nLog10Divisor);
3948                 aLine.append(" ");
3949                 appendDouble(m_aPages[rNote.m_nPage].getHeight()
3950                                  - convertMm100ToPoint(rPolygon.getB2DPoint(i).getY()),
3951                              aLine, nLog10Divisor);
3952                 aLine.append(" ");
3953             }
3954             aLine.append("]");
3955             aLine.append("/C [");
3956             appendColor(rNote.m_aContents.annotColor, aLine, false);
3957             aLine.append("] ");
3958         }
3959         aLine.append("] ");
3960     }
3961     else if (rNote.m_aContents.isFreeText)
3962         aLine.append("/FreeText ");
3963     else
3964         aLine.append("/Text ");
3965 
3966     aLine.append("/BS<</W 0>>");
3967 
3968     // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
3969     // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
3970     if (m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3)
3971         aLine.append("/F 4 ");
3972 
3973     appendAnnotationRect(rNote.m_aRect, aLine);
3974 
3975     aLine.append("/Popup ");
3976     appendObjectReference(rNote.m_aPopUpAnnotation.m_nObject, aLine);
3977 
3978     auto & rDateTime = rNote.m_aContents.maModificationDate;
3979 
3980     aLine.append("/M (");
3981     appendPdfTimeDate(aLine, rDateTime.Year, rDateTime.Month, rDateTime.Day, rDateTime.Hours, rDateTime.Minutes, rDateTime.Seconds, 0);
3982     aLine.append(") ");
3983 
3984     // contents of the note (type text string)
3985     aLine.append("/Contents ");
3986     appendUnicodeTextStringEncrypt(rNote.m_aContents.Contents, rNote.m_nObject, aLine);
3987     aLine.append("\n");
3988 
3989     // optional title
3990     if (!rNote.m_aContents.Title.isEmpty())
3991     {
3992         aLine.append("/T ");
3993         appendUnicodeTextStringEncrypt(rNote.m_aContents.Title, rNote.m_nObject, aLine);
3994         aLine.append("\n");
3995     }
3996     aLine.append(">>\n");
3997     aLine.append("endobj\n\n");
3998 }
3999 
4000 void PDFWriterImpl::emitPopupAnnotationLine(OStringBuffer & aLine, PDFPopupAnnotation const & rPopUp)
4001 {
4002     appendObjectID(rPopUp.m_nObject, aLine);
4003     aLine.append("<</Type /Annot /Subtype /Popup ");
4004     aLine.append("/Parent ");
4005     appendObjectReference(rPopUp.m_nParentObject, aLine);
4006     aLine.append(">>\n");
4007     aLine.append("endobj\n\n");
4008 }
4009 
4010 bool PDFWriterImpl::emitNoteAnnotations()
4011 {
4012     // emit note annotations
4013     int nAnnots = m_aNotes.size();
4014     for( int i = 0; i < nAnnots; i++ )
4015     {
4016         const PDFNoteEntry& rNote = m_aNotes[i];
4017         const PDFPopupAnnotation& rPopUp = rNote.m_aPopUpAnnotation;
4018 
4019         {
4020             if (!updateObject(rNote.m_nObject))
4021                 return false;
4022 
4023             OStringBuffer aLine(1024);
4024 
4025             emitTextAnnotationLine(aLine, rNote);
4026 
4027             if (!writeBuffer(aLine))
4028                 return false;
4029         }
4030 
4031         {
4032 
4033             if (!updateObject(rPopUp.m_nObject))
4034                 return false;
4035 
4036             OStringBuffer aLine(1024);
4037 
4038             emitPopupAnnotationLine(aLine, rPopUp);
4039 
4040             if (!writeBuffer(aLine))
4041                 return false;
4042         }
4043     }
4044     return true;
4045 }
4046 
4047 Font PDFWriterImpl::replaceFont( const vcl::Font& rControlFont, const vcl::Font&  rAppSetFont )
4048 {
4049     bool bAdjustSize = false;
4050 
4051     Font aFont( rControlFont );
4052     if( aFont.GetFamilyName().isEmpty() )
4053     {
4054         aFont = rAppSetFont;
4055         if( rControlFont.GetFontHeight() )
4056             aFont.SetFontSize( Size( 0, rControlFont.GetFontHeight() ) );
4057         else
4058             bAdjustSize = true;
4059         if( rControlFont.GetItalic() != ITALIC_DONTKNOW )
4060             aFont.SetItalic( rControlFont.GetItalic() );
4061         if( rControlFont.GetWeight() != WEIGHT_DONTKNOW )
4062             aFont.SetWeight( rControlFont.GetWeight() );
4063     }
4064     else if( ! aFont.GetFontHeight() )
4065     {
4066         aFont.SetFontSize( rAppSetFont.GetFontSize() );
4067         bAdjustSize = true;
4068     }
4069     if( bAdjustSize )
4070     {
4071         Size aFontSize = aFont.GetFontSize();
4072         OutputDevice* pDefDev = Application::GetDefaultDevice();
4073         aFontSize = OutputDevice::LogicToLogic( aFontSize, pDefDev->GetMapMode(), getMapMode() );
4074         aFont.SetFontSize( aFontSize );
4075     }
4076     return aFont;
4077 }
4078 
4079 sal_Int32 PDFWriterImpl::getBestBuildinFont( const vcl::Font& rFont )
4080 {
4081     sal_Int32 nBest = 4; // default to Helvetica
4082 
4083     if (rFont.GetFamilyType() == FAMILY_ROMAN)
4084     {
4085         // Serif: default to Times-Roman.
4086         nBest = 8;
4087     }
4088 
4089     OUString aFontName( rFont.GetFamilyName() );
4090     aFontName = aFontName.toAsciiLowerCase();
4091 
4092     if( aFontName.indexOf( "times" ) != -1 )
4093         nBest = 8;
4094     else if( aFontName.indexOf( "courier" ) != -1 )
4095         nBest = 0;
4096     else if( aFontName.indexOf( "dingbats" ) != -1 )
4097         nBest = 13;
4098     else if( aFontName.indexOf( "symbol" ) != -1 )
4099         nBest = 12;
4100     if( nBest < 12 )
4101     {
4102         if( rFont.GetItalic() == ITALIC_OBLIQUE || rFont.GetItalic() == ITALIC_NORMAL )
4103             nBest += 1;
4104         if( rFont.GetWeight() > WEIGHT_MEDIUM )
4105             nBest += 2;
4106     }
4107 
4108     if( m_aBuildinFontToObjectMap.find( nBest ) == m_aBuildinFontToObjectMap.end() )
4109         m_aBuildinFontToObjectMap[ nBest ] = createObject();
4110 
4111     return nBest;
4112 }
4113 
4114 static const Color& replaceColor( const Color& rCol1, const Color& rCol2 )
4115 {
4116     return (rCol1 == COL_TRANSPARENT) ? rCol2 : rCol1;
4117 }
4118 
4119 void PDFWriterImpl::createDefaultPushButtonAppearance( PDFWidget& rButton, const PDFWriter::PushButtonWidget& rWidget )
4120 {
4121     const StyleSettings& rSettings = m_aWidgetStyleSettings;
4122 
4123     // save graphics state
4124     push( PushFlags::ALL );
4125 
4126     // transform relative to control's coordinates since an
4127     // appearance stream is a form XObject
4128     // this relies on the m_aRect member of rButton NOT already being transformed
4129     // to default user space
4130     if( rWidget.Background || rWidget.Border )
4131     {
4132         setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetLightColor() ) : COL_TRANSPARENT );
4133         setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetDialogColor() ) : COL_TRANSPARENT );
4134         drawRectangle( rWidget.Location );
4135     }
4136     // prepare font to use
4137     Font aFont = replaceFont( rWidget.TextFont, rSettings.GetPushButtonFont() );
4138     setFont( aFont );
4139     setTextColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ) );
4140 
4141     drawText( rButton.m_aRect, rButton.m_aText, rButton.m_nTextStyle );
4142 
4143     // create DA string while local mapmode is still in place
4144     // (that is before endRedirect())
4145     OStringBuffer aDA( 256 );
4146     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ), aDA );
4147     Font aDummyFont( "Helvetica", aFont.GetFontSize() );
4148     sal_Int32 nDummyBuildin = getBestBuildinFont( aDummyFont );
4149     aDA.append( ' ' );
4150     aDA.append(pdf::BuildinFontFace::Get(nDummyBuildin).getNameObject());
4151     aDA.append( ' ' );
4152     m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
4153     aDA.append( " Tf" );
4154     rButton.m_aDAString = aDA.makeStringAndClear();
4155 
4156     pop();
4157 
4158     rButton.m_aAppearances[ "N" ][ "Standard" ] = new SvMemoryStream();
4159 
4160     /* seems like a bad hack but at least works in both AR5 and 6:
4161        we draw the button ourselves and tell AR
4162        the button would be totally transparent with no text
4163 
4164        One would expect that simply setting a normal appearance
4165        should suffice, but no, as soon as the user actually presses
4166        the button and an action is tied to it (gasp! a button that
4167        does something) the appearance gets replaced by some crap that AR
4168        creates on the fly even if no DA or MK is given. On AR6 at least
4169        the DA and MK work as expected, but on AR5 this creates a region
4170        filled with the background color but nor text. Urgh.
4171     */
4172     rButton.m_aMKDict = "/BC [] /BG [] /CA";
4173     rButton.m_aMKDictCAString = "";
4174 }
4175 
4176 Font PDFWriterImpl::drawFieldBorder( PDFWidget& rIntern,
4177                                      const PDFWriter::AnyWidget& rWidget,
4178                                      const StyleSettings& rSettings )
4179 {
4180     Font aFont = replaceFont( rWidget.TextFont, rSettings.GetFieldFont() );
4181 
4182     if( rWidget.Background || rWidget.Border )
4183     {
4184         if( rWidget.Border && rWidget.BorderColor == COL_TRANSPARENT )
4185         {
4186             sal_Int32 nDelta = GetDPIX() / 500;
4187             if( nDelta < 1 )
4188                 nDelta = 1;
4189             setLineColor( COL_TRANSPARENT );
4190             tools::Rectangle aRect = rIntern.m_aRect;
4191             setFillColor( rSettings.GetLightBorderColor() );
4192             drawRectangle( aRect );
4193             aRect.AdjustLeft(nDelta ); aRect.AdjustTop(nDelta );
4194             aRect.AdjustRight( -nDelta ); aRect.AdjustBottom( -nDelta );
4195             setFillColor( rSettings.GetFieldColor() );
4196             drawRectangle( aRect );
4197             setFillColor( rSettings.GetLightColor() );
4198             drawRectangle( tools::Rectangle( Point( aRect.Left(), aRect.Bottom()-nDelta ), aRect.BottomRight() ) );
4199             drawRectangle( tools::Rectangle( Point( aRect.Right()-nDelta, aRect.Top() ), aRect.BottomRight() ) );
4200             setFillColor( rSettings.GetDarkShadowColor() );
4201             drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Left()+nDelta, aRect.Bottom() ) ) );
4202             drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Right(), aRect.Top()+nDelta ) ) );
4203         }
4204         else
4205         {
4206             setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetShadowColor() ) : COL_TRANSPARENT );
4207             setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
4208             drawRectangle( rIntern.m_aRect );
4209         }
4210 
4211         if( rWidget.Border )
4212         {
4213             // adjust edit area accounting for border
4214             sal_Int32 nDelta = aFont.GetFontHeight()/4;
4215             if( nDelta < 1 )
4216                 nDelta = 1;
4217             rIntern.m_aRect.AdjustLeft(nDelta );
4218             rIntern.m_aRect.AdjustTop(nDelta );
4219             rIntern.m_aRect.AdjustRight( -nDelta );
4220             rIntern.m_aRect.AdjustBottom( -nDelta );
4221         }
4222     }
4223     return aFont;
4224 }
4225 
4226 void PDFWriterImpl::createDefaultEditAppearance( PDFWidget& rEdit, const PDFWriter::EditWidget& rWidget )
4227 {
4228     const StyleSettings& rSettings = m_aWidgetStyleSettings;
4229     SvMemoryStream* pEditStream = new SvMemoryStream( 1024, 1024 );
4230 
4231     push( PushFlags::ALL );
4232 
4233     // prepare font to use, draw field border
4234     Font aFont = drawFieldBorder( rEdit, rWidget, rSettings );
4235     // Get the built-in font which is closest to aFont.
4236     sal_Int32 nBest = getBestBuildinFont(aFont);
4237 
4238     // prepare DA string
4239     OStringBuffer aDA( 32 );
4240     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
4241     aDA.append( ' ' );
4242     aDA.append(pdf::BuildinFontFace::Get(nBest).getNameObject());
4243 
4244     OStringBuffer aDR( 32 );
4245     aDR.append( "/Font " );
4246     aDR.append( getFontDictObject() );
4247     aDR.append( " 0 R" );
4248     rEdit.m_aDRDict = aDR.makeStringAndClear();
4249     aDA.append( ' ' );
4250     m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
4251     aDA.append( " Tf" );
4252 
4253     /*  create an empty appearance stream, let the viewer create
4254         the appearance at runtime. This is because AR5 seems to
4255         paint the widget appearance always, and a dynamically created
4256         appearance on top of it. AR6 is well behaved in that regard, so
4257         that behaviour seems to be a bug. Anyway this empty appearance
4258         relies on /NeedAppearances in the AcroForm dictionary set to "true"
4259      */
4260     beginRedirect( pEditStream, rEdit.m_aRect );
4261     writeBuffer( "/Tx BMC\nEMC\n" );
4262 
4263     endRedirect();
4264     pop();
4265 
4266     rEdit.m_aAppearances[ "N" ][ "Standard" ] = pEditStream;
4267 
4268     rEdit.m_aDAString = aDA.makeStringAndClear();
4269 }
4270 
4271 void PDFWriterImpl::createDefaultListBoxAppearance( PDFWidget& rBox, const PDFWriter::ListBoxWidget& rWidget )
4272 {
4273     const StyleSettings& rSettings = m_aWidgetStyleSettings;
4274     SvMemoryStream* pListBoxStream = new SvMemoryStream( 1024, 1024 );
4275 
4276     push( PushFlags::ALL );
4277 
4278     // prepare font to use, draw field border
4279     Font aFont = drawFieldBorder( rBox, rWidget, rSettings );
4280     sal_Int32 nBest = getSystemFont( aFont );
4281 
4282     beginRedirect( pListBoxStream, rBox.m_aRect );
4283 
4284     setLineColor( COL_TRANSPARENT );
4285     setFillColor( replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) );
4286     drawRectangle( rBox.m_aRect );
4287 
4288     // empty appearance, see createDefaultEditAppearance for reference
4289     writeBuffer( "/Tx BMC\nEMC\n" );
4290 
4291     endRedirect();
4292     pop();
4293 
4294     rBox.m_aAppearances[ "N" ][ "Standard" ] = pListBoxStream;
4295 
4296     // prepare DA string
4297     OStringBuffer aDA( 256 );
4298     // prepare DA string
4299     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
4300     aDA.append( ' ' );
4301     aDA.append( "/F" );
4302     aDA.append( nBest );
4303 
4304     OStringBuffer aDR( 32 );
4305     aDR.append( "/Font " );
4306     aDR.append( getFontDictObject() );
4307     aDR.append( " 0 R" );
4308     rBox.m_aDRDict = aDR.makeStringAndClear();
4309     aDA.append( ' ' );
4310     m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
4311     aDA.append( " Tf" );
4312     rBox.m_aDAString = aDA.makeStringAndClear();
4313 }
4314 
4315 void PDFWriterImpl::createDefaultCheckBoxAppearance( PDFWidget& rBox, const PDFWriter::CheckBoxWidget& rWidget )
4316 {
4317     const StyleSettings& rSettings = m_aWidgetStyleSettings;
4318 
4319     // save graphics state
4320     push( PushFlags::ALL );
4321 
4322     if( rWidget.Background || rWidget.Border )
4323     {
4324         setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
4325         setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
4326         drawRectangle( rBox.m_aRect );
4327     }
4328 
4329     Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
4330     setFont( aFont );
4331     Size aFontSize = aFont.GetFontSize();
4332     if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
4333         aFontSize.setHeight( rBox.m_aRect.GetHeight() );
4334     sal_Int32 nDelta = aFontSize.Height()/10;
4335     if( nDelta < 1 )
4336         nDelta = 1;
4337 
4338     tools::Rectangle aCheckRect, aTextRect;
4339     {
4340         aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
4341         aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
4342         aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
4343         aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
4344 
4345         // #i74206# handle small controls without text area
4346         while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
4347         {
4348             aCheckRect.AdjustRight( -nDelta );
4349             aCheckRect.AdjustTop(nDelta/2 );
4350             aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
4351         }
4352 
4353         aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
4354         aTextRect.SetTop( rBox.m_aRect.Top() );
4355         aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
4356         aTextRect.SetBottom( rBox.m_aRect.Bottom() );
4357     }
4358     setLineColor( COL_BLACK );
4359     setFillColor( COL_TRANSPARENT );
4360     OStringBuffer aLW( 32 );
4361     aLW.append( "q " );
4362     m_aPages[m_nCurrentPage].appendMappedLength( nDelta, aLW );
4363     aLW.append( " w " );
4364     writeBuffer( aLW );
4365     drawRectangle( aCheckRect );
4366     writeBuffer( " Q\n" );
4367     setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4368     drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
4369 
4370     pop();
4371 
4372     OStringBuffer aDA( 256 );
4373 
4374     // tdf#93853 don't rely on Zapf (or any other 'standard' font)
4375     // being present, but our own OpenSymbol - N.B. PDF/A for good
4376     // reasons require even the standard PS fonts to be embedded!
4377     Push();
4378     SetFont( Font( OUString( "OpenSymbol" ), aFont.GetFontSize() ) );
4379     const LogicalFontInstance* pFontInstance = GetFontInstance();
4380     const vcl::font::PhysicalFontFace* pFace = pFontInstance->GetFontFace();
4381     Pop();
4382 
4383     // make sure OpenSymbol is embedded, and includes our checkmark
4384     const sal_Unicode cMark=0x2713;
4385     const auto nGlyphId = pFontInstance->GetGlyphIndex(cMark);
4386     const auto nGlyphWidth = pFontInstance->GetGlyphWidth(nGlyphId, false, false);
4387 
4388     sal_uInt8 nMappedGlyph;
4389     sal_Int32 nMappedFontObject;
4390     registerGlyph(nGlyphId, pFace, pFontInstance, { cMark }, nGlyphWidth, nMappedGlyph, nMappedFontObject);
4391 
4392     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
4393     aDA.append( ' ' );
4394     aDA.append( "/F" );
4395     aDA.append( nMappedFontObject );
4396     aDA.append( " 0 Tf" );
4397 
4398     OStringBuffer aDR( 32 );
4399     aDR.append( "/Font " );
4400     aDR.append( getFontDictObject() );
4401     aDR.append( " 0 R" );
4402     rBox.m_aDRDict = aDR.makeStringAndClear();
4403     rBox.m_aDAString = aDA.makeStringAndClear();
4404     rBox.m_aMKDict = "/CA";
4405     rBox.m_aMKDictCAString = "8";
4406     rBox.m_aRect = aCheckRect;
4407 
4408     // create appearance streams
4409     sal_Int32 nCharXOffset = 1000 - 787; // metrics from OpenSymbol
4410     nCharXOffset *= aCheckRect.GetHeight();
4411     nCharXOffset /= 2000;
4412     sal_Int32 nCharYOffset = 1000 - (820-143); // metrics from Zapf
4413     nCharYOffset *= aCheckRect.GetHeight();
4414     nCharYOffset /= 2000;
4415 
4416     // write 'checked' appearance stream
4417     SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
4418     beginRedirect( pCheckStream, aCheckRect );
4419     aDA.append( "/Tx BMC\nq BT\n" );
4420     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
4421     aDA.append( ' ' );
4422     aDA.append( "/F" );
4423     aDA.append( nMappedFontObject );
4424     aDA.append( ' ' );
4425     m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
4426     aDA.append( " Tf\n" );
4427     m_aPages[ m_nCurrentPage ].appendMappedLength( nCharXOffset, aDA );
4428     aDA.append( " " );
4429     m_aPages[ m_nCurrentPage ].appendMappedLength( nCharYOffset, aDA );
4430     aDA.append( " Td <" );
4431     appendHex( nMappedGlyph, aDA );
4432     aDA.append( "> Tj\nET\nQ\nEMC\n" );
4433     writeBuffer( aDA );
4434     endRedirect();
4435     rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream;
4436 
4437     // write 'unchecked' appearance stream
4438     SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
4439     beginRedirect( pUncheckStream, aCheckRect );
4440     writeBuffer( "/Tx BMC\nEMC\n" );
4441     endRedirect();
4442     rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream;
4443 }
4444 
4445 void PDFWriterImpl::createDefaultRadioButtonAppearance( PDFWidget& rBox, const PDFWriter::RadioButtonWidget& rWidget )
4446 {
4447     const StyleSettings& rSettings = m_aWidgetStyleSettings;
4448 
4449     // save graphics state
4450     push( PushFlags::ALL );
4451 
4452     if( rWidget.Background || rWidget.Border )
4453     {
4454         setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
4455         setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
4456         drawRectangle( rBox.m_aRect );
4457     }
4458 
4459     Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
4460     setFont( aFont );
4461     Size aFontSize = aFont.GetFontSize();
4462     if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
4463         aFontSize.setHeight( rBox.m_aRect.GetHeight() );
4464     sal_Int32 nDelta = aFontSize.Height()/10;
4465     if( nDelta < 1 )
4466         nDelta = 1;
4467 
4468     tools::Rectangle aCheckRect, aTextRect;
4469     {
4470         aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
4471         aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
4472         aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
4473         aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
4474 
4475         // #i74206# handle small controls without text area
4476         while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
4477         {
4478             aCheckRect.AdjustRight( -nDelta );
4479             aCheckRect.AdjustTop(nDelta/2 );
4480             aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
4481         }
4482 
4483         aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
4484         aTextRect.SetTop( rBox.m_aRect.Top() );
4485         aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
4486         aTextRect.SetBottom( rBox.m_aRect.Bottom() );
4487     }
4488     setLineColor( COL_BLACK );
4489     setFillColor( COL_TRANSPARENT );
4490     OStringBuffer aLW( 32 );
4491     aLW.append( "q " );
4492     m_aPages[ m_nCurrentPage ].appendMappedLength( nDelta, aLW );
4493     aLW.append( " w " );
4494     writeBuffer( aLW );
4495     drawEllipse( aCheckRect );
4496     writeBuffer( " Q\n" );
4497     setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4498     drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
4499 
4500     pop();
4501 
4502     //to encrypt this (el)
4503     rBox.m_aMKDict = "/CA";
4504     //after this assignment, to m_aMKDic cannot be added anything
4505     rBox.m_aMKDictCAString = "l";
4506 
4507     rBox.m_aRect = aCheckRect;
4508 
4509     // create appearance streams
4510     push( PushFlags::ALL);
4511     SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
4512 
4513     beginRedirect( pCheckStream, aCheckRect );
4514     OStringBuffer aDA( 256 );
4515     aDA.append( "/Tx BMC\nq BT\n" );
4516     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
4517     aDA.append( ' ' );
4518     m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
4519     aDA.append( " 0 0 Td\nET\nQ\n" );
4520     writeBuffer( aDA );
4521     setFillColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4522     setLineColor( COL_TRANSPARENT );
4523     aCheckRect.AdjustLeft(3*nDelta );
4524     aCheckRect.AdjustTop(3*nDelta );
4525     aCheckRect.AdjustBottom( -(3*nDelta) );
4526     aCheckRect.AdjustRight( -(3*nDelta) );
4527     drawEllipse( aCheckRect );
4528     writeBuffer( "\nEMC\n" );
4529     endRedirect();
4530 
4531     pop();
4532     rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream;
4533 
4534     SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
4535     beginRedirect( pUncheckStream, aCheckRect );
4536     writeBuffer( "/Tx BMC\nEMC\n" );
4537     endRedirect();
4538     rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream;
4539 }
4540 
4541 bool PDFWriterImpl::emitAppearances( PDFWidget& rWidget, OStringBuffer& rAnnotDict )
4542 {
4543     // TODO: check and insert default streams
4544     OString aStandardAppearance;
4545     switch( rWidget.m_eType )
4546     {
4547         case PDFWriter::CheckBox:
4548             aStandardAppearance = OUStringToOString( rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US );
4549             break;
4550         default:
4551             break;
4552     }
4553 
4554     if( !rWidget.m_aAppearances.empty() )
4555     {
4556         rAnnotDict.append( "/AP<<\n" );
4557         for (auto & dict_item : rWidget.m_aAppearances)
4558         {
4559             rAnnotDict.append( "/" );
4560             rAnnotDict.append( dict_item.first );
4561             bool bUseSubDict = (dict_item.second.size() > 1);
4562 
4563             // PDF/A requires sub-dicts for /FT/Btn objects (clause
4564             // 6.3.3)
4565             if( m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3)
4566             {
4567                 if( rWidget.m_eType == PDFWriter::RadioButton ||
4568                     rWidget.m_eType == PDFWriter::CheckBox ||
4569                     rWidget.m_eType == PDFWriter::PushButton )
4570                 {
4571                     bUseSubDict = true;
4572                 }
4573             }
4574 
4575             rAnnotDict.append( bUseSubDict ? "<<" : " " );
4576 
4577             for (auto const& stream_item : dict_item.second)
4578             {
4579                 SvMemoryStream* pAppearanceStream = stream_item.second;
4580                 dict_item.second[ stream_item.first ] = nullptr;
4581 
4582                 bool bDeflate = compressStream( pAppearanceStream );
4583 
4584                 sal_Int64 nStreamLen = pAppearanceStream->TellEnd();
4585                 pAppearanceStream->Seek( STREAM_SEEK_TO_BEGIN );
4586                 sal_Int32 nObject = createObject();
4587                 CHECK_RETURN( updateObject( nObject ) );
4588                 if (g_bDebugDisableCompression)
4589                 {
4590                     emitComment( "PDFWriterImpl::emitAppearances" );
4591                 }
4592                 OStringBuffer aLine;
4593                 aLine.append( nObject );
4594 
4595                 aLine.append( " 0 obj\n"
4596                               "<</Type/XObject\n"
4597                               "/Subtype/Form\n"
4598                               "/BBox[0 0 " );
4599                 appendFixedInt( rWidget.m_aRect.GetWidth()-1, aLine );
4600                 aLine.append( " " );
4601                 appendFixedInt( rWidget.m_aRect.GetHeight()-1, aLine );
4602                 aLine.append( "]\n"
4603                               "/Resources " );
4604                 aLine.append( getResourceDictObj() );
4605                 aLine.append( " 0 R\n"
4606                               "/Length " );
4607                 aLine.append( nStreamLen );
4608                 aLine.append( "\n" );
4609                 if( bDeflate )
4610                     aLine.append( "/Filter/FlateDecode\n" );
4611                 aLine.append( ">>\nstream\n" );
4612                 CHECK_RETURN( writeBuffer( aLine ) );
4613                 checkAndEnableStreamEncryption( nObject );
4614                 CHECK_RETURN( writeBufferBytes( pAppearanceStream->GetData(), nStreamLen ) );
4615                 disableStreamEncryption();
4616                 CHECK_RETURN( writeBuffer( "\nendstream\nendobj\n\n" ) );
4617 
4618                 if( bUseSubDict )
4619                 {
4620                     rAnnotDict.append( " /" );
4621                     rAnnotDict.append( stream_item.first );
4622                     rAnnotDict.append( " " );
4623                 }
4624                 rAnnotDict.append( nObject );
4625                 rAnnotDict.append( " 0 R" );
4626 
4627                 delete pAppearanceStream;
4628             }
4629 
4630             rAnnotDict.append( bUseSubDict ? ">>\n" : "\n" );
4631         }
4632         rAnnotDict.append( ">>\n" );
4633         if( !aStandardAppearance.isEmpty() )
4634         {
4635             rAnnotDict.append( "/AS /" );
4636             rAnnotDict.append( aStandardAppearance );
4637             rAnnotDict.append( "\n" );
4638         }
4639     }
4640 
4641     return true;
4642 }
4643 
4644 bool PDFWriterImpl::emitWidgetAnnotations()
4645 {
4646     ensureUniqueRadioOnValues();
4647 
4648     int nAnnots = m_aWidgets.size();
4649     for( int a = 0; a < nAnnots; a++ )
4650     {
4651         PDFWidget& rWidget = m_aWidgets[a];
4652 
4653         if( rWidget.m_eType == PDFWriter::CheckBox )
4654         {
4655             if ( !rWidget.m_aOnValue.isEmpty() )
4656             {
4657                 auto app_it = rWidget.m_aAppearances.find( "N" );
4658                 if( app_it != rWidget.m_aAppearances.end() )
4659                 {
4660                     auto stream_it = app_it->second.find( "Yes" );
4661                     if( stream_it != app_it->second.end() )
4662                     {
4663                         SvMemoryStream* pStream = stream_it->second;
4664                         app_it->second.erase( stream_it );
4665                         OStringBuffer aBuf( rWidget.m_aOnValue.getLength()*2 );
4666                         appendName( rWidget.m_aOnValue, aBuf );
4667                         (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
4668                     }
4669                     else
4670                         SAL_INFO("vcl.pdfwriter", "error: CheckBox without \"Yes\" stream" );
4671                 }
4672             }
4673 
4674             if ( !rWidget.m_aOffValue.isEmpty() )
4675             {
4676                 auto app_it = rWidget.m_aAppearances.find( "N" );
4677                 if( app_it != rWidget.m_aAppearances.end() )
4678                 {
4679                     auto stream_it = app_it->second.find( "Off" );
4680                     if( stream_it != app_it->second.end() )
4681                     {
4682                         SvMemoryStream* pStream = stream_it->second;
4683                         app_it->second.erase( stream_it );
4684                         OStringBuffer aBuf( rWidget.m_aOffValue.getLength()*2 );
4685                         appendName( rWidget.m_aOffValue, aBuf );
4686                         (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
4687                     }
4688                     else
4689                         SAL_INFO("vcl.pdfwriter", "error: CheckBox without \"Off\" stream" );
4690                 }
4691             }
4692         }
4693 
4694         OStringBuffer aLine( 1024 );
4695         OStringBuffer aValue( 256 );
4696         aLine.append( rWidget.m_nObject );
4697         aLine.append( " 0 obj\n"
4698                       "<<" );
4699         if( rWidget.m_eType != PDFWriter::Hierarchy )
4700         {
4701             // emit widget annotation only for terminal fields
4702             if( rWidget.m_aKids.empty() )
4703             {
4704                 int iRectMargin;
4705 
4706                 aLine.append( "/Type/Annot/Subtype/Widget/F " );
4707 
4708                 if (rWidget.m_eType == PDFWriter::Signature)
4709                 {
4710                     aLine.append( "132\n" ); // Print & Locked
4711                     iRectMargin = 0;
4712                 }
4713                 else
4714                 {
4715                     aLine.append( "4\n" );
4716                     iRectMargin = 1;
4717                 }
4718 
4719                 if (-1 != rWidget.m_nStructParent)
4720                 {
4721                     aLine.append("/StructParent ");
4722                     aLine.append(rWidget.m_nStructParent);
4723                     aLine.append("\n");
4724                 }
4725 
4726                 aLine.append("/Rect[" );
4727                 appendFixedInt( rWidget.m_aRect.Left()-iRectMargin, aLine );
4728                 aLine.append( ' ' );
4729                 appendFixedInt( rWidget.m_aRect.Top()+iRectMargin, aLine );
4730                 aLine.append( ' ' );
4731                 appendFixedInt( rWidget.m_aRect.Right()+iRectMargin, aLine );
4732                 aLine.append( ' ' );
4733                 appendFixedInt( rWidget.m_aRect.Bottom()-iRectMargin, aLine );
4734                 aLine.append( "]\n" );
4735             }
4736             aLine.append( "/FT/" );
4737             switch( rWidget.m_eType )
4738             {
4739                 case PDFWriter::RadioButton:
4740                 case PDFWriter::CheckBox:
4741                     // for radio buttons only the RadioButton field, not the
4742                     // CheckBox children should have a value, else acrobat reader
4743                     // does not always check the right button
4744                     // of course real check boxes (not belonging to a radio group)
4745                     // need their values, too
4746                     if( rWidget.m_eType == PDFWriter::RadioButton || rWidget.m_nRadioGroup < 0 )
4747                     {
4748                         aValue.append( "/" );
4749                         // check for radio group with all buttons unpressed
4750                         if( rWidget.m_aValue.isEmpty() )
4751                             aValue.append( "Off" );
4752                         else
4753                             appendName( rWidget.m_aValue, aValue );
4754                     }
4755                     [[fallthrough]];
4756                 case PDFWriter::PushButton:
4757                     aLine.append( "Btn" );
4758                     break;
4759                 case PDFWriter::ListBox:
4760                     if( rWidget.m_nFlags & 0x200000 ) // multiselect
4761                     {
4762                         aValue.append( "[" );
4763                         for( size_t i = 0; i < rWidget.m_aSelectedEntries.size(); i++ )
4764                         {
4765                             sal_Int32 nEntry = rWidget.m_aSelectedEntries[i];
4766                             if( nEntry >= 0
4767                                 && o3tl::make_unsigned(nEntry) < rWidget.m_aListEntries.size() )
4768                                 appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ nEntry ], rWidget.m_nObject, aValue );
4769                         }
4770                         aValue.append( "]" );
4771                     }
4772                     else if( !rWidget.m_aSelectedEntries.empty() &&
4773                              rWidget.m_aSelectedEntries[0] >= 0 &&
4774                              o3tl::make_unsigned(rWidget.m_aSelectedEntries[0]) < rWidget.m_aListEntries.size() )
4775                     {
4776                         appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ rWidget.m_aSelectedEntries[0] ], rWidget.m_nObject, aValue );
4777                     }
4778                     else
4779                         appendUnicodeTextStringEncrypt( OUString(), rWidget.m_nObject, aValue );
4780                     aLine.append( "Ch" );
4781                     break;
4782                 case PDFWriter::ComboBox:
4783                     appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue );
4784                     aLine.append( "Ch" );
4785                     break;
4786                 case PDFWriter::Edit:
4787                     aLine.append( "Tx" );
4788                     appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue );
4789                     break;
4790                 case PDFWriter::Signature:
4791                     aLine.append( "Sig" );
4792                     aValue.append(OUStringToOString(rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US));
4793                     break;
4794                 case PDFWriter::Hierarchy: // make the compiler happy
4795                     break;
4796             }
4797             aLine.append( "\n" );
4798             aLine.append( "/P " );
4799             aLine.append( m_aPages[ rWidget.m_nPage ].m_nPageObject );
4800             aLine.append( " 0 R\n" );
4801         }
4802         if( rWidget.m_nParent )
4803         {
4804             aLine.append( "/Parent " );
4805             aLine.append( rWidget.m_nParent );
4806             aLine.append( " 0 R\n" );
4807         }
4808         if( !rWidget.m_aKids.empty() )
4809         {
4810             aLine.append( "/Kids[" );
4811             for( size_t i = 0; i < rWidget.m_aKids.size(); i++ )
4812             {
4813                 aLine.append( rWidget.m_aKids[i] );
4814                 aLine.append( " 0 R" );
4815                 aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
4816             }
4817             aLine.append( "]\n" );
4818         }
4819         if( !rWidget.m_aName.isEmpty() )
4820         {
4821             aLine.append( "/T" );
4822             appendLiteralStringEncrypt( rWidget.m_aName, rWidget.m_nObject, aLine );
4823             aLine.append( "\n" );
4824         }
4825         if (!rWidget.m_aDescription.isEmpty())
4826         {
4827             // the alternate field name should be unicode able since it is
4828             // supposed to be used in UI
4829             aLine.append( "/TU" );
4830             appendUnicodeTextStringEncrypt( rWidget.m_aDescription, rWidget.m_nObject, aLine );
4831             aLine.append( "\n" );
4832         }
4833 
4834         if( rWidget.m_nFlags )
4835         {
4836             aLine.append( "/Ff " );
4837             aLine.append( rWidget.m_nFlags );
4838             aLine.append( "\n" );
4839         }
4840         if( !aValue.isEmpty() )
4841         {
4842             OString aVal = aValue.makeStringAndClear();
4843             aLine.append( "/V " );
4844             aLine.append( aVal );
4845             aLine.append( "\n"
4846                           "/DV " );
4847             aLine.append( aVal );
4848             aLine.append( "\n" );
4849         }
4850         if( rWidget.m_eType == PDFWriter::ListBox || rWidget.m_eType == PDFWriter::ComboBox )
4851         {
4852             sal_Int32 nTI = -1;
4853             aLine.append( "/Opt[\n" );
4854             sal_Int32 i = 0;
4855             for (auto const& entry : rWidget.m_aListEntries)
4856             {
4857                 appendUnicodeTextStringEncrypt( entry, rWidget.m_nObject, aLine );
4858                 aLine.append( "\n" );
4859                 if( entry == rWidget.m_aValue )
4860                     nTI = i;
4861                 ++i;
4862             }
4863             aLine.append( "]\n" );
4864             if( nTI > 0 )
4865             {
4866                 aLine.append( "/TI " );
4867                 aLine.append( nTI );
4868                 aLine.append( "\n" );
4869                 if( rWidget.m_nFlags & 0x200000 ) // Multiselect
4870                 {
4871                     aLine.append( "/I [" );
4872                     aLine.append( nTI );
4873                     aLine.append( "]\n" );
4874                 }
4875             }
4876         }
4877         if( rWidget.m_eType == PDFWriter::Edit )
4878         {
4879             if ( rWidget.m_nMaxLen > 0 )
4880             {
4881                 aLine.append( "/MaxLen " );
4882                 aLine.append( rWidget.m_nMaxLen );
4883                 aLine.append( "\n" );
4884             }
4885 
4886             if ( rWidget.m_nFormat == PDFWriter::Number )
4887             {
4888                 OString aHexText;
4889 
4890                 if ( !rWidget.m_aCurrencySymbol.isEmpty() )
4891                 {
4892                     // Get the hexadecimal code
4893                     sal_UCS4 cChar = rWidget.m_aCurrencySymbol.iterateCodePoints(&o3tl::temporary(sal_Int32(1)), -1);
4894                     aHexText = "\\\\u" + OString::number(cChar, 16);
4895                 }
4896 
4897                 aLine.append("/AA<<\n");
4898                 aLine.append("/F<</JS(AFNumber_Format\\(");
4899                 aLine.append(OString::number(rWidget.m_nDecimalAccuracy));
4900                 aLine.append(", 0, 0, 0, \"");
4901                 aLine.append( aHexText );
4902                 aLine.append("\",");
4903                 aLine.append(OString::boolean(rWidget.m_bPrependCurrencySymbol));
4904                 aLine.append("\\);)");
4905                 aLine.append("/S/JavaScript>>\n");
4906                 aLine.append("/K<</JS(AFNumber_Keystroke\\(");
4907                 aLine.append(OString::number(rWidget.m_nDecimalAccuracy));
4908                 aLine.append(", 0, 0, 0, \"");
4909                 aLine.append( aHexText );
4910                 aLine.append("\",");
4911                 aLine.append(OString::boolean(rWidget.m_bPrependCurrencySymbol));
4912                 aLine.append("\\);)");
4913                 aLine.append("/S/JavaScript>>\n");
4914                 aLine.append(">>\n");
4915             }
4916             else if ( rWidget.m_nFormat == PDFWriter::Time )
4917             {
4918                 aLine.append("/AA<<\n");
4919                 aLine.append("/F<</JS(AFTime_FormatEx\\(\"");
4920                 aLine.append(OUStringToOString(rWidget.m_aTimeFormat, RTL_TEXTENCODING_ASCII_US));
4921                 aLine.append("\"\\);)");
4922                 aLine.append("/S/JavaScript>>\n");
4923                 aLine.append("/K<</JS(AFTime_KeystrokeEx\\(\"");
4924                 aLine.append(OUStringToOString(rWidget.m_aTimeFormat, RTL_TEXTENCODING_ASCII_US));
4925                 aLine.append("\"\\);)");
4926                 aLine.append("/S/JavaScript>>\n");
4927                 aLine.append(">>\n");
4928             }
4929             else if ( rWidget.m_nFormat == PDFWriter::Date )
4930             {
4931                 aLine.append("/AA<<\n");
4932                 aLine.append("/F<</JS(AFDate_FormatEx\\(\"");
4933                 aLine.append(OUStringToOString(rWidget.m_aDateFormat, RTL_TEXTENCODING_ASCII_US));
4934                 aLine.append("\"\\);)");
4935                 aLine.append("/S/JavaScript>>\n");
4936                 aLine.append("/K<</JS(AFDate_KeystrokeEx\\(\"");
4937                 aLine.append(OUStringToOString(rWidget.m_aDateFormat, RTL_TEXTENCODING_ASCII_US));
4938                 aLine.append("\"\\);)");
4939                 aLine.append("/S/JavaScript>>\n");
4940                 aLine.append(">>\n");
4941             }
4942         }
4943         if( rWidget.m_eType == PDFWriter::PushButton )
4944         {
4945             if(!m_bIsPDF_A1)
4946             {
4947                 OStringBuffer aDest;
4948                 if( rWidget.m_nDest != -1 && appendDest( m_aDestinationIdTranslation[ rWidget.m_nDest ], aDest ) )
4949                 {
4950                     aLine.append( "/AA<</D<</Type/Action/S/GoTo/D " );
4951                     aLine.append( aDest );
4952                     aLine.append( ">>>>\n" );
4953                 }
4954                 else if( rWidget.m_aListEntries.empty() )
4955                 {
4956                     if( !m_bIsPDF_A2 && !m_bIsPDF_A3 )
4957                     {
4958                         // create a reset form action
4959                         aLine.append( "/AA<</D<</Type/Action/S/ResetForm>>>>\n" );
4960                     }
4961                 }
4962                 else if( rWidget.m_bSubmit )
4963                 {
4964                     // create a submit form action
4965                     aLine.append( "/AA<</D<</Type/Action/S/SubmitForm/F" );
4966                     appendLiteralStringEncrypt( rWidget.m_aListEntries.front(), rWidget.m_nObject, aLine, osl_getThreadTextEncoding() );
4967                     aLine.append( "/Flags " );
4968 
4969                     sal_Int32 nFlags = 0;
4970                     switch( m_aContext.SubmitFormat )
4971                     {
4972                     case PDFWriter::HTML:
4973                         nFlags |= 4;
4974                         break;
4975                     case PDFWriter::XML:
4976                         nFlags |= 32;
4977                         break;
4978                     case PDFWriter::PDF:
4979                         nFlags |= 256;
4980                         break;
4981                     case PDFWriter::FDF:
4982                     default:
4983                         break;
4984                     }
4985                     if( rWidget.m_bSubmitGet )
4986                         nFlags |= 8;
4987                     aLine.append( nFlags );
4988                     aLine.append( ">>>>\n" );
4989                 }
4990                 else
4991                 {
4992                     // create a URI action
4993                     aLine.append( "/AA<</D<</Type/Action/S/URI/URI(" );
4994                     aLine.append( OUStringToOString( rWidget.m_aListEntries.front(), RTL_TEXTENCODING_ASCII_US ) );
4995                     aLine.append( ")>>>>\n" );
4996                 }
4997             }
4998             else
4999                 m_aErrors.insert( PDFWriter::Warning_FormAction_Omitted_PDFA );
5000         }
5001         if( !rWidget.m_aDAString.isEmpty() )
5002         {
5003             if( !rWidget.m_aDRDict.isEmpty() )
5004             {
5005                 aLine.append( "/DR<<" );
5006                 aLine.append( rWidget.m_aDRDict );
5007                 aLine.append( ">>\n" );
5008             }
5009             else
5010             {
5011                 aLine.append( "/DR<</Font<<" );
5012                 appendBuildinFontsToDict( aLine );
5013                 aLine.append( ">>>>\n" );
5014             }
5015             aLine.append( "/DA" );
5016             appendLiteralStringEncrypt( rWidget.m_aDAString, rWidget.m_nObject, aLine );
5017             aLine.append( "\n" );
5018             if( rWidget.m_nTextStyle & DrawTextFlags::Center )
5019                 aLine.append( "/Q 1\n" );
5020             else if( rWidget.m_nTextStyle & DrawTextFlags::Right )
5021                 aLine.append( "/Q 2\n" );
5022         }
5023         // appearance characteristics for terminal fields
5024         // which are supposed to have an appearance constructed
5025         // by the viewer application
5026         if( !rWidget.m_aMKDict.isEmpty() )
5027         {
5028             aLine.append( "/MK<<" );
5029             aLine.append( rWidget.m_aMKDict );
5030             //add the CA string, encrypting it
5031             appendLiteralStringEncrypt(rWidget.m_aMKDictCAString, rWidget.m_nObject, aLine);
5032             aLine.append( ">>\n" );
5033         }
5034 
5035         CHECK_RETURN( emitAppearances( rWidget, aLine ) );
5036 
5037         aLine.append( ">>\n"
5038                       "endobj\n\n" );
5039         CHECK_RETURN( updateObject( rWidget.m_nObject ) );
5040         CHECK_RETURN( writeBuffer( aLine ) );
5041     }
5042     return true;
5043 }
5044 
5045 bool PDFWriterImpl::emitAnnotations()
5046 {
5047     if( m_aPages.empty() )
5048         return false;
5049 
5050     CHECK_RETURN( emitLinkAnnotations() );
5051     CHECK_RETURN(emitScreenAnnotations());
5052     CHECK_RETURN( emitNoteAnnotations() );
5053     CHECK_RETURN( emitWidgetAnnotations() );
5054 
5055     return true;
5056 }
5057 
5058 class PDFStreamIf : public cppu::WeakImplHelper< css::io::XOutputStream >
5059 {
5060     VclPtr<PDFWriterImpl>  m_pWriter;
5061     bool            m_bWrite;
5062     public:
5063     explicit PDFStreamIf( PDFWriterImpl* pWriter ) : m_pWriter( pWriter ), m_bWrite( true ) {}
5064 
5065     virtual void SAL_CALL writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override
5066     {
5067         if( m_bWrite && aData.hasElements() )
5068         {
5069             sal_Int32 nBytes = aData.getLength();
5070             m_pWriter->writeBufferBytes( aData.getConstArray(), nBytes );
5071         }
5072     }
5073     virtual void SAL_CALL flush() override {}
5074     virtual void SAL_CALL closeOutput() override
5075     {
5076         m_bWrite = false;
5077     }
5078 };
5079 
5080 bool PDFWriterImpl::emitEmbeddedFiles()
5081 {
5082     for (auto& rEmbeddedFile : m_aEmbeddedFiles)
5083     {
5084         if (!updateObject(rEmbeddedFile.m_nObject))
5085             continue;
5086 
5087         sal_Int32 nSizeObject = createObject();
5088         sal_Int32 nParamsObject = createObject();
5089 
5090         OStringBuffer aLine;
5091         aLine.append(rEmbeddedFile.m_nObject);
5092         aLine.append(" 0 obj\n");
5093         aLine.append("<< /Type /EmbeddedFile");
5094         if (!rEmbeddedFile.m_aSubType.isEmpty())
5095         {
5096             aLine.append("/Subtype /");
5097             appendName(rEmbeddedFile.m_aSubType, aLine);
5098         }
5099         aLine.append(" /Length ");
5100         appendObjectReference(nSizeObject, aLine);
5101         aLine.append(" /Params ");
5102         appendObjectReference(nParamsObject, aLine);
5103         aLine.append(">>\nstream\n");
5104         checkAndEnableStreamEncryption(rEmbeddedFile.m_nObject);
5105         CHECK_RETURN(writeBuffer(aLine));
5106         disableStreamEncryption();
5107         aLine.setLength(0);
5108 
5109         sal_Int64 nSize{};
5110         if (!rEmbeddedFile.m_aDataContainer.isEmpty())
5111         {
5112             nSize = rEmbeddedFile.m_aDataContainer.getSize();
5113             CHECK_RETURN(writeBufferBytes(rEmbeddedFile.m_aDataContainer.getData(), rEmbeddedFile.m_aDataContainer.getSize()));
5114         }
5115         else if (rEmbeddedFile.m_pStream)
5116         {
5117             sal_uInt64 nBegin = getCurrentFilePosition();
5118             css::uno::Reference<css::io::XOutputStream> xStream(new PDFStreamIf(this));
5119             rEmbeddedFile.m_pStream->write(xStream);
5120             rEmbeddedFile.m_pStream.reset();
5121             xStream.clear();
5122             nSize = sal_Int64(getCurrentFilePosition() - nBegin);
5123         }
5124         aLine.append("\nendstream\nendobj\n\n");
5125         CHECK_RETURN(writeBuffer(aLine));
5126         aLine.setLength(0);
5127 
5128         if (!updateObject(nSizeObject))
5129             return false;
5130         aLine.append(nSizeObject);
5131         aLine.append(" 0 obj\n");
5132         aLine.append(nSize);
5133         aLine.append("\nendobj\n\n");
5134         if (!writeBuffer(aLine))
5135             return false;
5136         aLine.setLength(0);
5137 
5138         if (!updateObject(nParamsObject))
5139             return false;
5140         aLine.append(nParamsObject);
5141         aLine.append(" 0 obj\n");
5142         aLine.append("<<");
5143         aLine.append("/Size ");
5144         aLine.append(nSize);
5145         aLine.append(">>");
5146         aLine.append("\nendobj\n\n");
5147         if (!writeBuffer(aLine))
5148             return false;
5149     }
5150     return true;
5151 }
5152 
5153 #undef CHECK_RETURN
5154 #define CHECK_RETURN( x ) if( !x ) return false
5155 
5156 bool PDFWriterImpl::emitCatalog()
5157 {
5158     // build page tree
5159     // currently there is only one node that contains all leaves
5160 
5161     // first create a page tree node id
5162     sal_Int32 nTreeNode = createObject();
5163 
5164     // emit global resource dictionary (page emit needs it)
5165     CHECK_RETURN( emitResources() );
5166 
5167     // emit all pages
5168     for (auto & page : m_aPages)
5169         if( ! page.emit( nTreeNode ) )
5170             return false;
5171 
5172     sal_Int32 nNamedDestinationsDictionary = emitNamedDestinations();
5173 
5174     sal_Int32 nOutlineDict = emitOutline();
5175 
5176     // emit Output intent
5177     sal_Int32 nOutputIntentObject = emitOutputIntent();
5178 
5179     // emit metadata
5180     sal_Int32 nMetadataObject = emitDocumentMetadata();
5181 
5182     sal_Int32 nStructureDict = 0;
5183     if(m_aStructure.size() > 1)
5184     {
5185         removePlaceholderSE(m_aStructure, m_aStructure[0]);
5186         // check if dummy structure containers are needed
5187         addInternalStructureContainer(m_aStructure[0]);
5188         nStructureDict = m_aStructure[0].m_nObject = createObject();
5189         emitStructure( m_aStructure[ 0 ] );
5190     }
5191 
5192     // adjust tree node file offset
5193     if( ! updateObject( nTreeNode ) )
5194         return false;
5195 
5196     // emit tree node
5197     OStringBuffer aLine( 2048 );
5198     aLine.append( nTreeNode );
5199     aLine.append( " 0 obj\n" );
5200     aLine.append( "<</Type/Pages\n" );
5201     aLine.append( "/Resources " );
5202     aLine.append( getResourceDictObj() );
5203     aLine.append( " 0 R\n" );
5204 
5205     double nMediaBoxWidth = 0;
5206     double nMediaBoxHeight = 0;
5207     sal_Int32 nUserUnit = 1;
5208     if( m_aPages.empty() ) // sanity check, this should not happen
5209     {
5210         nMediaBoxWidth = g_nInheritedPageWidth;
5211         nMediaBoxHeight = g_nInheritedPageHeight;
5212     }
5213     else
5214     {
5215         for (auto const& page : m_aPages)
5216         {
5217             if( page.m_nPageWidth > nMediaBoxWidth )
5218             {
5219                 nMediaBoxWidth = page.m_nPageWidth;
5220                 nUserUnit = page.m_nUserUnit;
5221             }
5222             if( page.m_nPageHeight > nMediaBoxHeight )
5223             {
5224                 nMediaBoxHeight = page.m_nPageHeight;
5225                 nUserUnit = page.m_nUserUnit;
5226             }
5227         }
5228     }
5229     aLine.append( "/MediaBox[ 0 0 " );
5230     aLine.append(nMediaBoxWidth / nUserUnit);
5231     aLine.append( ' ' );
5232     aLine.append(nMediaBoxHeight / nUserUnit);
5233     aLine.append(" ]\n");
5234     if (nUserUnit > 1)
5235     {
5236         aLine.append("/UserUnit ");
5237         aLine.append(nUserUnit);
5238         aLine.append("\n");
5239     }
5240     aLine.append("/Kids[ ");
5241     unsigned int i = 0;
5242     for (const auto & page : m_aPages)
5243     {
5244         aLine.append( page.m_nPageObject );
5245         aLine.append( " 0 R" );
5246         aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
5247         ++i;
5248     }
5249     aLine.append( "]\n"
5250                   "/Count " );
5251     aLine.append( static_cast<sal_Int32>(m_aPages.size()) );
5252     aLine.append( ">>\n"
5253                   "endobj\n\n" );
5254     CHECK_RETURN( writeBuffer( aLine ) );
5255 
5256     // emit annotation objects
5257     CHECK_RETURN( emitAnnotations() );
5258     CHECK_RETURN( emitEmbeddedFiles() );
5259 
5260     // emit attached files
5261     for (auto & rAttachedFile : m_aDocumentAttachedFiles)
5262     {
5263         if (!updateObject(rAttachedFile.mnObjectId))
5264             return false;
5265         aLine.setLength( 0 );
5266 
5267         appendObjectID(rAttachedFile.mnObjectId, aLine);
5268         aLine.append("<</Type /Filespec");
5269         aLine.append("/F<");
5270         PDFWriter::AppendUnicodeTextString(rAttachedFile.maFilename, aLine);
5271         aLine.append("> ");
5272         if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
5273         {
5274             aLine.append("/UF<");
5275             PDFWriter::AppendUnicodeTextString(rAttachedFile.maFilename, aLine);
5276             aLine.append("> ");
5277         }
5278         if (!rAttachedFile.maDescription.isEmpty())
5279         {
5280             aLine.append("/Desc <");
5281             PDFWriter::AppendUnicodeTextString(rAttachedFile.maDescription, aLine);
5282             aLine.append("> ");
5283         }
5284         aLine.append("/EF <</F ");
5285         appendObjectReference(rAttachedFile.mnEmbeddedFileObjectId, aLine);
5286         aLine.append(">>");
5287         aLine.append(">>\nendobj\n\n");
5288         CHECK_RETURN( writeBuffer( aLine ) );
5289     }
5290 
5291     // emit Catalog
5292     m_nCatalogObject = createObject();
5293     if( ! updateObject( m_nCatalogObject ) )
5294         return false;
5295     aLine.setLength( 0 );
5296     aLine.append( m_nCatalogObject );
5297     aLine.append( " 0 obj\n"
5298                   "<</Type/Catalog/Pages " );
5299     aLine.append( nTreeNode );
5300     aLine.append( " 0 R\n" );
5301 
5302     // check if there are named destinations to emit (root must be inside the catalog)
5303     if( nNamedDestinationsDictionary )
5304     {
5305         aLine.append("/Dests ");
5306         aLine.append( nNamedDestinationsDictionary );
5307         aLine.append( " 0 R\n" );
5308     }
5309 
5310     if (!m_aDocumentAttachedFiles.empty())
5311     {
5312         aLine.append("/Names ");
5313         aLine.append("<</EmbeddedFiles <</Names [");
5314         for (auto & rAttachedFile : m_aDocumentAttachedFiles)
5315         {
5316             aLine.append('<');
5317             PDFWriter::AppendUnicodeTextString(rAttachedFile.maFilename, aLine);
5318             aLine.append('>');
5319             aLine.append(' ');
5320             appendObjectReference(rAttachedFile.mnObjectId, aLine);
5321         }
5322         aLine.append("]>>>>");
5323         aLine.append("\n" );
5324     }
5325 
5326     if( m_aContext.PageLayout != PDFWriter::DefaultLayout )
5327         switch(  m_aContext.PageLayout )
5328         {
5329         default :
5330         case  PDFWriter::SinglePage :
5331             aLine.append( "/PageLayout/SinglePage\n" );
5332             break;
5333         case  PDFWriter::Continuous :
5334             aLine.append( "/PageLayout/OneColumn\n" );
5335             break;
5336         case  PDFWriter::ContinuousFacing :
5337             // the flag m_aContext.FirstPageLeft below is used to set the page on the left side
5338             aLine.append( "/PageLayout/TwoColumnRight\n" );//odd page on the right side
5339             break;
5340         }
5341     if( m_aContext.PDFDocumentMode != PDFWriter::ModeDefault && !m_aContext.OpenInFullScreenMode )
5342         switch(  m_aContext.PDFDocumentMode )
5343         {
5344         default :
5345             aLine.append( "/PageMode/UseNone\n" );
5346             break;
5347         case PDFWriter::UseOutlines :
5348             aLine.append( "/PageMode/UseOutlines\n" ); //document is opened with outline pane open
5349             break;
5350         case PDFWriter::UseThumbs :
5351             aLine.append( "/PageMode/UseThumbs\n" ); //document is opened with thumbnails pane open
5352             break;
5353         }
5354     else if( m_aContext.OpenInFullScreenMode )
5355         aLine.append( "/PageMode/FullScreen\n" ); //document is opened full screen
5356 
5357     OStringBuffer aInitPageRef;
5358     if( m_aContext.InitialPage >= 0 && o3tl::make_unsigned(m_aContext.InitialPage) < m_aPages.size() )
5359     {
5360         aInitPageRef.append( m_aPages[m_aContext.InitialPage].m_nPageObject );
5361         aInitPageRef.append( " 0 R" );
5362     }
5363     else
5364         aInitPageRef.append( "0" );
5365 
5366     switch( m_aContext.PDFDocumentAction )
5367     {
5368     case PDFWriter::ActionDefault :     //do nothing, this is the Acrobat default
5369     default:
5370         if( aInitPageRef.getLength() > 1 )
5371         {
5372             aLine.append( "/OpenAction[" );
5373             aLine.append( aInitPageRef );
5374             aLine.append( " /XYZ null null 0]\n" );
5375         }
5376         break;
5377     case PDFWriter::FitInWindow :
5378         aLine.append( "/OpenAction[" );
5379         aLine.append( aInitPageRef );
5380         aLine.append( " /Fit]\n" ); //Open fit page
5381         break;
5382     case PDFWriter::FitWidth :
5383         aLine.append( "/OpenAction[" );
5384         aLine.append( aInitPageRef );
5385         aLine.append( " /FitH " );
5386         aLine.append( g_nInheritedPageHeight );//Open fit width
5387         aLine.append( "]\n" );
5388         break;
5389     case PDFWriter::FitVisible :
5390         aLine.append( "/OpenAction[" );
5391         aLine.append( aInitPageRef );
5392         aLine.append( " /FitBH " );
5393         aLine.append( g_nInheritedPageHeight );//Open fit visible
5394         aLine.append( "]\n" );
5395         break;
5396     case PDFWriter::ActionZoom :
5397         aLine.append( "/OpenAction[" );
5398         aLine.append( aInitPageRef );
5399         aLine.append( " /XYZ null null " );
5400         if( m_aContext.Zoom >= 50 && m_aContext.Zoom <= 1600 )
5401             aLine.append( static_cast<double>(m_aContext.Zoom)/100.0 );
5402         else
5403             aLine.append( "0" );
5404         aLine.append( "]\n" );
5405         break;
5406     }
5407 
5408     // viewer preferences, if we had some, then emit
5409     if( m_aContext.HideViewerToolbar ||
5410         (!m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle) ||
5411         m_aContext.HideViewerMenubar ||
5412         m_aContext.HideViewerWindowControls || m_aContext.FitWindow ||
5413         m_aContext.CenterWindow || (m_aContext.FirstPageLeft  &&  m_aContext.PageLayout == PDFWriter::ContinuousFacing ) ||
5414         m_aContext.OpenInFullScreenMode )
5415     {
5416         aLine.append( "/ViewerPreferences<<" );
5417         if( m_aContext.HideViewerToolbar )
5418             aLine.append( "/HideToolbar true\n" );
5419         if( m_aContext.HideViewerMenubar )
5420             aLine.append( "/HideMenubar true\n" );
5421         if( m_aContext.HideViewerWindowControls )
5422             aLine.append( "/HideWindowUI true\n" );
5423         if( m_aContext.FitWindow )
5424             aLine.append( "/FitWindow true\n" );
5425         if( m_aContext.CenterWindow )
5426             aLine.append( "/CenterWindow true\n" );
5427         if (!m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle)
5428             aLine.append( "/DisplayDocTitle true\n" );
5429         if( m_aContext.FirstPageLeft &&  m_aContext.PageLayout == PDFWriter::ContinuousFacing )
5430             aLine.append( "/Direction/R2L\n" );
5431         if( m_aContext.OpenInFullScreenMode )
5432             switch( m_aContext.PDFDocumentMode )
5433             {
5434             default :
5435             case PDFWriter::ModeDefault :
5436                 aLine.append( "/NonFullScreenPageMode/UseNone\n" );
5437                 break;
5438             case PDFWriter::UseOutlines :
5439                 aLine.append( "/NonFullScreenPageMode/UseOutlines\n" );
5440                 break;
5441             case PDFWriter::UseThumbs :
5442                 aLine.append( "/NonFullScreenPageMode/UseThumbs\n" );
5443                 break;
5444             }
5445         aLine.append( ">>\n" );
5446     }
5447 
5448     if( nOutlineDict )
5449     {
5450         aLine.append( "/Outlines " );
5451         aLine.append( nOutlineDict );
5452         aLine.append( " 0 R\n" );
5453     }
5454     if( nStructureDict )
5455     {
5456         aLine.append( "/StructTreeRoot " );
5457         aLine.append( nStructureDict );
5458         aLine.append( " 0 R\n" );
5459     }
5460     if( !m_aContext.DocumentLocale.Language.isEmpty() )
5461     {
5462         /* PDF allows only RFC 3066, see above in emitStructure(). */
5463         LanguageTag aLanguageTag( m_aContext.DocumentLocale);
5464         OUString aLanguage, aScript, aCountry;
5465         aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
5466         if (!aLanguage.isEmpty())
5467         {
5468             OUStringBuffer aLocBuf( 16 );
5469             aLocBuf.append( aLanguage );
5470             if( !aCountry.isEmpty() )
5471             {
5472                 aLocBuf.append( '-' );
5473                 aLocBuf.append( aCountry );
5474             }
5475             aLine.append( "/Lang" );
5476             appendLiteralStringEncrypt( aLocBuf, m_nCatalogObject, aLine );
5477             aLine.append( "\n" );
5478         }
5479     }
5480     if (m_aContext.Tagged)
5481     {
5482         aLine.append( "/MarkInfo<</Marked true>>\n" );
5483     }
5484     if( !m_aWidgets.empty() )
5485     {
5486         aLine.append( "/AcroForm<</Fields[\n" );
5487         int nWidgets = m_aWidgets.size();
5488         int nOut = 0;
5489         for( int j = 0; j < nWidgets; j++ )
5490         {
5491             // output only root fields
5492             if( m_aWidgets[j].m_nParent < 1 )
5493             {
5494                 aLine.append( m_aWidgets[j].m_nObject );
5495                 aLine.append( (nOut++ % 5)==4 ? " 0 R\n" : " 0 R " );
5496             }
5497         }
5498         aLine.append( "\n]" );
5499 
5500 #if HAVE_FEATURE_NSS
5501         if (m_nSignatureObject != -1)
5502             aLine.append( "/SigFlags 3");
5503 #endif
5504 
5505         aLine.append( "/DR " );
5506         aLine.append( getResourceDictObj() );
5507         aLine.append( " 0 R" );
5508         // NeedAppearances must not be used if PDF is signed
5509         if( m_bIsPDF_A1 || m_bIsPDF_A2 || m_bIsPDF_A3
5510 #if HAVE_FEATURE_NSS
5511             || ( m_nSignatureObject != -1 )
5512 #endif
5513             )
5514             aLine.append( ">>\n" );
5515         else
5516             aLine.append( "/NeedAppearances true>>\n" );
5517     }
5518 
5519     //check if there is a Metadata object
5520     if( nOutputIntentObject )
5521     {
5522         aLine.append("/OutputIntents[");
5523         aLine.append( nOutputIntentObject );
5524         aLine.append( " 0 R]" );
5525     }
5526 
5527     if( nMetadataObject )
5528     {
5529         aLine.append("/Metadata ");
5530         aLine.append( nMetadataObject );
5531         aLine.append( " 0 R" );
5532     }
5533 
5534     aLine.append( ">>\n"
5535                   "endobj\n\n" );
5536     return writeBuffer( aLine );
5537 }
5538 
5539 #if HAVE_FEATURE_NSS
5540 
5541 bool PDFWriterImpl::emitSignature()
5542 {
5543     if( !updateObject( m_nSignatureObject ) )
5544         return false;
5545 
5546     OStringBuffer aLine( 0x5000 );
5547     aLine.append( m_nSignatureObject );
5548     aLine.append( " 0 obj\n" );
5549     aLine.append("<</Contents <" );
5550 
5551     sal_uInt64 nOffset = ~0U;
5552     CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nOffset) ) );
5553 
5554     m_nSignatureContentOffset = nOffset + aLine.getLength();
5555 
5556     // reserve some space for the PKCS#7 object
5557     OStringBuffer aContentFiller( MAX_SIGNATURE_CONTENT_LENGTH );
5558     comphelper::string::padToLength(aContentFiller, MAX_SIGNATURE_CONTENT_LENGTH, '0');
5559     aLine.append( aContentFiller );
5560     aLine.append( ">\n/Type/Sig/SubFilter/adbe.pkcs7.detached");
5561 
5562     if( !m_aContext.DocumentInfo.Author.isEmpty() )
5563     {
5564         aLine.append( "/Name" );
5565         appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Author, m_nSignatureObject, aLine );
5566     }
5567 
5568     aLine.append( " /M ");
5569     appendLiteralStringEncrypt( m_aCreationDateString, m_nSignatureObject, aLine );
5570 
5571     aLine.append( " /ByteRange [ 0 ");
5572     aLine.append( m_nSignatureContentOffset - 1 );
5573     aLine.append( " " );
5574     aLine.append( m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1 );
5575     aLine.append( " " );
5576 
5577     m_nSignatureLastByteRangeNoOffset = nOffset + aLine.getLength();
5578 
5579     // mark the last ByteRange no and add some space. Now, we don't know
5580     // how many bytes we need for this ByteRange value
5581     // The real value will be overwritten in the finalizeSignature method
5582     OStringBuffer aByteRangeFiller( 100  );
5583     comphelper::string::padToLength(aByteRangeFiller, 100, ' ');
5584     aLine.append( aByteRangeFiller );
5585     aLine.append("  /Filter/Adobe.PPKMS");
5586 
5587     //emit reason, location and contactinfo
5588     if ( !m_aContext.SignReason.isEmpty() )
5589     {
5590         aLine.append("/Reason");
5591         appendUnicodeTextStringEncrypt( m_aContext.SignReason, m_nSignatureObject, aLine );
5592     }
5593 
5594     if ( !m_aContext.SignLocation.isEmpty() )
5595     {
5596         aLine.append("/Location");
5597         appendUnicodeTextStringEncrypt( m_aContext.SignLocation, m_nSignatureObject, aLine );
5598     }
5599 
5600     if ( !m_aContext.SignContact.isEmpty() )
5601     {
5602         aLine.append("/ContactInfo");
5603         appendUnicodeTextStringEncrypt( m_aContext.SignContact, m_nSignatureObject, aLine );
5604     }
5605 
5606     aLine.append(" >>\nendobj\n\n" );
5607 
5608     return writeBuffer( aLine );
5609 }
5610 
5611 bool PDFWriterImpl::finalizeSignature()
5612 {
5613     if (!m_aContext.SignCertificate.is())
5614         return false;
5615 
5616     // 1- calculate last ByteRange value
5617     sal_uInt64 nOffset = ~0U;
5618     CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nOffset) ) );
5619 
5620     sal_Int64 nLastByteRangeNo = nOffset - (m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1);
5621 
5622     // 2- overwrite the value to the m_nSignatureLastByteRangeNoOffset position
5623     sal_uInt64 nWritten = 0;
5624     CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureLastByteRangeNoOffset) ) );
5625     OString aByteRangeNo = OString::number( nLastByteRangeNo ) + " ]";
5626 
5627     if (m_aFile.write(aByteRangeNo.getStr(), aByteRangeNo.getLength(), nWritten) != osl::File::E_None)
5628     {
5629         CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset)) );
5630         return false;
5631     }
5632 
5633     // 3- create the PKCS#7 object using NSS
5634 
5635     // Prepare buffer and calculate PDF file digest
5636     CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, 0)) );
5637 
5638     std::unique_ptr<char[]> buffer1(new char[m_nSignatureContentOffset + 1]);
5639     sal_uInt64 bytesRead1;
5640 
5641     //FIXME: Check if hash is calculated from the correct byterange
5642     if (osl::File::E_None != m_aFile.read(buffer1.get(), m_nSignatureContentOffset - 1 , bytesRead1) ||
5643         bytesRead1 != static_cast<sal_uInt64>(m_nSignatureContentOffset) - 1)
5644     {
5645         SAL_WARN("vcl.pdfwriter", "First buffer read failed");
5646         return false;
5647     }
5648 
5649     std::unique_ptr<char[]> buffer2(new char[nLastByteRangeNo + 1]);
5650     sal_uInt64 bytesRead2;
5651 
5652     if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1) ||
5653         osl::File::E_None != m_aFile.read(buffer2.get(), nLastByteRangeNo, bytesRead2) ||
5654         bytesRead2 != static_cast<sal_uInt64>(nLastByteRangeNo))
5655     {
5656         SAL_WARN("vcl.pdfwriter", "Second buffer read failed");
5657         return false;
5658     }
5659 
5660     OStringBuffer aCMSHexBuffer;
5661     svl::crypto::Signing aSigning(m_aContext.SignCertificate);
5662     aSigning.AddDataRange(buffer1.get(), bytesRead1);
5663     aSigning.AddDataRange(buffer2.get(), bytesRead2);
5664     aSigning.SetSignTSA(m_aContext.SignTSA);
5665     aSigning.SetSignPassword(m_aContext.SignPassword);
5666     if (!aSigning.Sign(aCMSHexBuffer))
5667     {
5668         SAL_WARN("vcl.pdfwriter", "PDFWriter::Sign() failed");
5669         return false;
5670     }
5671 
5672     assert(aCMSHexBuffer.getLength() <= MAX_SIGNATURE_CONTENT_LENGTH);
5673 
5674     // Set file pointer to the m_nSignatureContentOffset, we're ready to overwrite PKCS7 object
5675     nWritten = 0;
5676     CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset)) );
5677     m_aFile.write(aCMSHexBuffer.getStr(), aCMSHexBuffer.getLength(), nWritten);
5678 
5679     return osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset);
5680 }
5681 
5682 #endif //HAVE_FEATURE_NSS
5683 
5684 sal_Int32 PDFWriterImpl::emitInfoDict( )
5685 {
5686     sal_Int32 nObject = createObject();
5687 
5688     if( updateObject( nObject ) )
5689     {
5690         OStringBuffer aLine( 1024 );
5691         aLine.append( nObject );
5692         aLine.append( " 0 obj\n"
5693                       "<<" );
5694         if( !m_aContext.DocumentInfo.Title.isEmpty() )
5695         {
5696             aLine.append( "/Title" );
5697             appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Title, nObject, aLine );
5698             aLine.append( "\n" );
5699         }
5700         if( !m_aContext.DocumentInfo.Author.isEmpty() )
5701         {
5702             aLine.append( "/Author" );
5703             appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Author, nObject, aLine );
5704             aLine.append( "\n" );
5705         }
5706         if( !m_aContext.DocumentInfo.Subject.isEmpty() )
5707         {
5708             aLine.append( "/Subject" );
5709             appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Subject, nObject, aLine );
5710             aLine.append( "\n" );
5711         }
5712         if( !m_aContext.DocumentInfo.Keywords.isEmpty() )
5713         {
5714             aLine.append( "/Keywords" );
5715             appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Keywords, nObject, aLine );
5716             aLine.append( "\n" );
5717         }
5718         if( !m_aContext.DocumentInfo.Creator.isEmpty() )
5719         {
5720             aLine.append( "/Creator" );
5721             appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Creator, nObject, aLine );
5722             aLine.append( "\n" );
5723         }
5724         if( !m_aContext.DocumentInfo.Producer.isEmpty() )
5725         {
5726             aLine.append( "/Producer" );
5727             appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Producer, nObject, aLine );
5728             aLine.append( "\n" );
5729         }
5730 
5731         aLine.append( "/CreationDate" );
5732         appendLiteralStringEncrypt( m_aCreationDateString, nObject, aLine );
5733         aLine.append( ">>\nendobj\n\n" );
5734         if( ! writeBuffer( aLine ) )
5735             nObject = 0;
5736     }
5737     else
5738         nObject = 0;
5739 
5740     return nObject;
5741 }
5742 
5743 // Part of this function may be shared with method appendDest.
5744 sal_Int32 PDFWriterImpl::emitNamedDestinations()
5745 {
5746     sal_Int32  nCount = m_aNamedDests.size();
5747     if( nCount <= 0 )
5748         return 0;//define internal error
5749 
5750     //get the object number for all the destinations
5751     sal_Int32 nObject = createObject();
5752 
5753     if( updateObject( nObject ) )
5754     {
5755         //emit the dictionary
5756         OStringBuffer aLine( 1024 );
5757         aLine.append( nObject );
5758         aLine.append( " 0 obj\n"
5759                       "<<" );
5760 
5761         sal_Int32  nDestID;
5762         for( nDestID = 0; nDestID < nCount; nDestID++ )
5763         {
5764             const PDFNamedDest& rDest   = m_aNamedDests[ nDestID ];
5765             // In order to correctly function both under an Internet browser and
5766             // directly with a reader (provided the reader has the feature) we
5767             // need to set the name of the destination the same way it will be encoded
5768             // in an Internet link
5769             INetURLObject aLocalURL( u"http://ahost.ax" ); //dummy location, won't be used
5770             aLocalURL.SetMark( rDest.m_aDestName );
5771 
5772             const OUString aName   = aLocalURL.GetMark( INetURLObject::DecodeMechanism::NONE ); //same coding as
5773             // in link creation ( see PDFWriterImpl::emitLinkAnnotations )
5774             const PDFPage& rDestPage    = m_aPages[ rDest.m_nPage ];
5775 
5776             aLine.append( '/' );
5777             appendDestinationName( aName, aLine ); // this conversion must be done when forming the link to target ( see in emitCatalog )
5778             aLine.append( '[' ); // the '[' can be emitted immediately, because the appendDestinationName function
5779                                  //maps the preceding character properly
5780             aLine.append( rDestPage.m_nPageObject );
5781             aLine.append( " 0 R" );
5782 
5783             switch( rDest.m_eType )
5784             {
5785             case PDFWriter::DestAreaType::XYZ:
5786             default:
5787                 aLine.append( "/XYZ " );
5788                 appendFixedInt( rDest.m_aRect.Left(), aLine );
5789                 aLine.append( ' ' );
5790                 appendFixedInt( rDest.m_aRect.Bottom(), aLine );
5791                 aLine.append( " 0" );
5792                 break;
5793             case PDFWriter::DestAreaType::FitRectangle:
5794                 aLine.append( "/FitR " );
5795                 appendFixedInt( rDest.m_aRect.Left(), aLine );
5796                 aLine.append( ' ' );
5797                 appendFixedInt( rDest.m_aRect.Top(), aLine );
5798                 aLine.append( ' ' );
5799                 appendFixedInt( rDest.m_aRect.Right(), aLine );
5800                 aLine.append( ' ' );
5801                 appendFixedInt( rDest.m_aRect.Bottom(), aLine );
5802                 break;
5803             }
5804             aLine.append( "]\n" );
5805         }
5806 
5807         //close
5808         aLine.append( ">>\nendobj\n\n" );
5809         if( ! writeBuffer( aLine ) )
5810             nObject = 0;
5811     }
5812     else
5813         nObject = 0;
5814 
5815     return nObject;
5816 }
5817 
5818 // emits the output intent dictionary
5819 sal_Int32 PDFWriterImpl::emitOutputIntent()
5820 {
5821     if( !m_bIsPDF_A1 && !m_bIsPDF_A2 && !m_bIsPDF_A3 )
5822         return 0;
5823 
5824     //emit the sRGB standard profile, in ICC format, in a stream, per IEC61966-2.1
5825 
5826     OStringBuffer aLine( 1024 );
5827     sal_Int32 nICCObject = createObject();
5828     sal_Int32 nStreamLengthObject = createObject();
5829 
5830     aLine.append( nICCObject );
5831 // sRGB has 3 colors, hence /N 3 below (PDF 1.4 table 4.16)
5832     aLine.append( " 0 obj\n<</N 3/Length " );
5833     aLine.append( nStreamLengthObject );
5834     aLine.append( " 0 R" );
5835     if (!g_bDebugDisableCompression)
5836         aLine.append( "/Filter/FlateDecode" );
5837     aLine.append( ">>\nstream\n" );
5838     if ( !updateObject( nICCObject ) ) return 0;
5839     if ( !writeBuffer( aLine ) ) return 0;
5840     //get file position
5841     sal_uInt64 nBeginStreamPos = 0;
5842     if (osl::File::E_None != m_aFile.getPos(nBeginStreamPos))
5843         return 0;
5844     beginCompression();
5845     checkAndEnableStreamEncryption( nICCObject );
5846     cmsHPROFILE hProfile = cmsCreate_sRGBProfile();
5847     //force ICC profile version 2.1
5848     cmsSetProfileVersion(hProfile, 2.1);
5849     cmsUInt32Number nBytesNeeded = 0;
5850     cmsSaveProfileToMem(hProfile, nullptr, &nBytesNeeded);
5851     if (!nBytesNeeded)
5852       return 0;
5853     std::vector<unsigned char> aBuffer(nBytesNeeded);
5854     cmsSaveProfileToMem(hProfile, aBuffer.data(), &nBytesNeeded);
5855     cmsCloseProfile(hProfile);
5856     bool written = writeBufferBytes( aBuffer.data(), static_cast<sal_Int32>(aBuffer.size()) );
5857     disableStreamEncryption();
5858     endCompression();
5859 
5860     sal_uInt64 nEndStreamPos = 0;
5861     if (m_aFile.getPos(nEndStreamPos) != osl::File::E_None)
5862         return 0;
5863 
5864     if( !written )
5865         return 0;
5866     if( ! writeBuffer( "\nendstream\nendobj\n\n" ) )
5867         return 0 ;
5868     aLine.setLength( 0 );
5869 
5870     //emit the stream length   object
5871     if ( !updateObject( nStreamLengthObject ) ) return 0;
5872     aLine.setLength( 0 );
5873     aLine.append( nStreamLengthObject );
5874     aLine.append( " 0 obj\n" );
5875     aLine.append( static_cast<sal_Int64>(nEndStreamPos-nBeginStreamPos) );
5876     aLine.append( "\nendobj\n\n" );
5877     if ( !writeBuffer( aLine ) ) return 0;
5878     aLine.setLength( 0 );
5879 
5880     //emit the OutputIntent dictionary
5881     sal_Int32 nOIObject = createObject();
5882     if ( !updateObject( nOIObject ) ) return 0;
5883     aLine.append( nOIObject );
5884     aLine.append( " 0 obj\n"
5885                   "<</Type/OutputIntent/S/GTS_PDFA1/OutputConditionIdentifier");
5886 
5887     appendLiteralStringEncrypt( std::string_view("sRGB IEC61966-2.1") ,nOIObject, aLine );
5888     aLine.append("/DestOutputProfile ");
5889     aLine.append( nICCObject );
5890     aLine.append( " 0 R>>\nendobj\n\n" );
5891     if ( !writeBuffer( aLine ) ) return 0;
5892 
5893     return nOIObject;
5894 }
5895 
5896 static void lcl_assignMeta(std::u16string_view aValue, OString& aMeta)
5897 {
5898     if (!aValue.empty())
5899     {
5900         OUString aTempString = comphelper::string::encodeForXml(aValue);
5901         aMeta = OUStringToOString(aTempString, RTL_TEXTENCODING_UTF8);
5902     }
5903 }
5904 
5905 // emits the document metadata
5906 sal_Int32 PDFWriterImpl::emitDocumentMetadata()
5907 {
5908     if( !m_bIsPDF_A1 && !m_bIsPDF_A2 && !m_bIsPDF_A3 && !m_bIsPDF_UA)
5909         return 0;
5910 
5911     //get the object number for all the destinations
5912     sal_Int32 nObject = createObject();
5913 
5914     if( updateObject( nObject ) )
5915     {
5916         pdf::XmpMetadata aMetadata;
5917 
5918         if (m_bIsPDF_A1)
5919             aMetadata.mnPDF_A = 1;
5920         else if (m_bIsPDF_A2)
5921             aMetadata.mnPDF_A = 2;
5922         else if (m_bIsPDF_A3)
5923             aMetadata.mnPDF_A = 3;
5924 
5925         aMetadata.mbPDF_UA = m_bIsPDF_UA;
5926 
5927         lcl_assignMeta(m_aContext.DocumentInfo.Title, aMetadata.msTitle);
5928         lcl_assignMeta(m_aContext.DocumentInfo.Author, aMetadata.msAuthor);
5929         lcl_assignMeta(m_aContext.DocumentInfo.Subject, aMetadata.msSubject);
5930         lcl_assignMeta(m_aContext.DocumentInfo.Producer, aMetadata.msProducer);
5931         lcl_assignMeta(m_aContext.DocumentInfo.Keywords, aMetadata.msKeywords);
5932         lcl_assignMeta(m_aContext.DocumentInfo.Creator, aMetadata.m_sCreatorTool);
5933         aMetadata.m_sCreateDate = m_aCreationMetaDateString;
5934 
5935         OStringBuffer aMetadataObj( 1024 );
5936 
5937         aMetadataObj.append( nObject );
5938         aMetadataObj.append( " 0 obj\n" );
5939 
5940         aMetadataObj.append( "<</Type/Metadata/Subtype/XML/Length " );
5941 
5942         aMetadataObj.append( sal_Int32(aMetadata.getSize()) );
5943         aMetadataObj.append( ">>\nstream\n" );
5944         if ( !writeBuffer( aMetadataObj ) )
5945             return 0;
5946         //emit the stream
5947         if ( !writeBufferBytes( aMetadata.getData(), aMetadata.getSize() ) )
5948             return 0;
5949 
5950         if( ! writeBuffer( "\nendstream\nendobj\n\n" ) )
5951             nObject = 0;
5952     }
5953     else
5954         nObject = 0;
5955 
5956     return nObject;
5957 }
5958 
5959 bool PDFWriterImpl::emitTrailer()
5960 {
5961     // emit doc info
5962     sal_Int32 nDocInfoObject = emitInfoDict( );
5963 
5964     sal_Int32 nSecObject = 0;
5965 
5966     if( m_aContext.Encryption.Encrypt() )
5967     {
5968         //emit the security information
5969         //must be emitted as indirect dictionary object, since
5970         //Acrobat Reader 5 works only with this kind of implementation
5971         nSecObject = createObject();
5972 
5973         if( updateObject( nSecObject ) )
5974         {
5975             OStringBuffer aLineS( 1024 );
5976             aLineS.append( nSecObject );
5977             aLineS.append( " 0 obj\n"
5978                            "<</Filter/Standard/V " );
5979             // check the version
5980             aLineS.append( "2/Length 128/R 3" );
5981 
5982             // emit the owner password, must not be encrypted
5983             aLineS.append( "/O(" );
5984             appendLiteralString( reinterpret_cast<char*>(m_aContext.Encryption.OValue.data()), sal_Int32(m_aContext.Encryption.OValue.size()), aLineS );
5985             aLineS.append( ")/U(" );
5986             appendLiteralString( reinterpret_cast<char*>(m_aContext.Encryption.UValue.data()), sal_Int32(m_aContext.Encryption.UValue.size()), aLineS );
5987             aLineS.append( ")/P " );// the permission set
5988             aLineS.append( m_nAccessPermissions );
5989             aLineS.append( ">>\nendobj\n\n" );
5990             if( !writeBuffer( aLineS ) )
5991                 nSecObject = 0;
5992         }
5993         else
5994             nSecObject = 0;
5995     }
5996     // emit xref table
5997     // remember start
5998     sal_uInt64 nXRefOffset = 0;
5999     CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nXRefOffset )) );
6000     CHECK_RETURN( writeBuffer( "xref\n" ) );
6001 
6002     sal_Int32 nObjects = m_aObjects.size();
6003     OStringBuffer aLine;
6004     aLine.append( "0 " );
6005     aLine.append( static_cast<sal_Int32>(nObjects+1) );
6006     aLine.append( "\n" );
6007     aLine.append( "0000000000 65535 f \n" );
6008     CHECK_RETURN( writeBuffer( aLine ) );
6009 
6010     for( sal_Int32 i = 0; i < nObjects; i++ )
6011     {
6012         aLine.setLength( 0 );
6013         OString aOffset = OString::number( m_aObjects[i] );
6014         for( sal_Int32 j = 0; j < (10-aOffset.getLength()); j++ )
6015             aLine.append( '0' );
6016         aLine.append( aOffset );
6017         aLine.append( " 00000 n \n" );
6018         SAL_WARN_IF( aLine.getLength() != 20, "vcl.pdfwriter", "invalid xref entry" );
6019         CHECK_RETURN( writeBuffer( aLine ) );
6020     }
6021 
6022     // prepare document checksum
6023     OStringBuffer aDocChecksum( 2*RTL_DIGEST_LENGTH_MD5+1 );
6024     ::std::vector<unsigned char> const nMD5Sum(m_DocDigest.finalize());
6025     for (sal_uInt8 i : nMD5Sum)
6026         appendHex( i, aDocChecksum );
6027     // document id set in setDocInfo method
6028     // emit trailer
6029     aLine.setLength( 0 );
6030     aLine.append( "trailer\n"
6031                   "<</Size " );
6032     aLine.append( static_cast<sal_Int32>(nObjects+1) );
6033     aLine.append( "/Root " );
6034     aLine.append( m_nCatalogObject );
6035     aLine.append( " 0 R\n" );
6036     if( nSecObject )
6037     {
6038         aLine.append( "/Encrypt ");
6039         aLine.append( nSecObject );
6040         aLine.append( " 0 R\n" );
6041     }
6042     if( nDocInfoObject )
6043     {
6044         aLine.append( "/Info " );
6045         aLine.append( nDocInfoObject );
6046         aLine.append( " 0 R\n" );
6047     }
6048     if( ! m_aContext.Encryption.DocumentIdentifier.empty() )
6049     {
6050         aLine.append( "/ID [ <" );
6051         for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
6052         {
6053             appendHex( sal_Int8(item), aLine );
6054         }
6055         aLine.append( ">\n"
6056                       "<" );
6057         for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
6058         {
6059             appendHex( sal_Int8(item), aLine );
6060         }
6061         aLine.append( "> ]\n" );
6062     }
6063     if( !aDocChecksum.isEmpty() )
6064     {
6065         aLine.append( "/DocChecksum /" );
6066         aLine.append( aDocChecksum );
6067         aLine.append( "\n" );
6068     }
6069 
6070     if (!m_aDocumentAttachedFiles.empty())
6071     {
6072         aLine.append( "/AdditionalStreams [" );
6073         for (auto const& rAttachedFile : m_aDocumentAttachedFiles)
6074         {
6075             aLine.append( "/" );
6076             appendName(rAttachedFile.maMimeType, aLine);
6077             aLine.append(" ");
6078             appendObjectReference(rAttachedFile.mnEmbeddedFileObjectId, aLine);
6079             aLine.append("\n");
6080         }
6081         aLine.append( "]\n" );
6082     }
6083 
6084     aLine.append( ">>\n"
6085                   "startxref\n" );
6086     aLine.append( static_cast<sal_Int64>(nXRefOffset) );
6087     aLine.append( "\n"
6088                   "%%EOF\n" );
6089     return writeBuffer( aLine );
6090 }
6091 
6092 namespace {
6093 
6094 struct AnnotationSortEntry
6095 {
6096     sal_Int32 nTabOrder;
6097     sal_Int32 nObject;
6098     sal_Int32 nWidgetIndex;
6099 
6100     AnnotationSortEntry( sal_Int32 nTab, sal_Int32 nObj, sal_Int32 nI ) :
6101         nTabOrder( nTab ),
6102         nObject( nObj ),
6103         nWidgetIndex( nI )
6104     {}
6105 };
6106 
6107 struct AnnotSortContainer
6108 {
6109     o3tl::sorted_vector< sal_Int32 >      aObjects;
6110     std::vector< AnnotationSortEntry >    aSortedAnnots;
6111 };
6112 
6113 struct AnnotSorterLess
6114 {
6115     std::vector<PDFWidget>& m_rWidgets;
6116 
6117     explicit AnnotSorterLess( std::vector<PDFWidget>& rWidgets ) : m_rWidgets( rWidgets ) {}
6118 
6119     bool operator()( const AnnotationSortEntry& rLeft, const AnnotationSortEntry& rRight )
6120     {
6121         if( rLeft.nTabOrder < rRight.nTabOrder )
6122             return true;
6123         if( rRight.nTabOrder < rLeft.nTabOrder )
6124             return false;
6125         if( rLeft.nWidgetIndex < 0 && rRight.nWidgetIndex < 0 )
6126             return false;
6127         if( rRight.nWidgetIndex < 0 )
6128             return true;
6129         if( rLeft.nWidgetIndex < 0 )
6130             return false;
6131         // remember: widget rects are in PDF coordinates, so they are ordered down up
6132         if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() >
6133             m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() )
6134             return true;
6135         if( m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() >
6136             m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() )
6137             return false;
6138         if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Left() <
6139             m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Left() )
6140             return true;
6141         return false;
6142     }
6143 };
6144 
6145 }
6146 
6147 void PDFWriterImpl::sortWidgets()
6148 {
6149     // sort widget annotations on each page as per their
6150     // TabOrder attribute
6151     std::unordered_map< sal_Int32, AnnotSortContainer > sorted;
6152     int nWidgets = m_aWidgets.size();
6153     for( int nW = 0; nW < nWidgets; nW++ )
6154     {
6155         const PDFWidget& rWidget = m_aWidgets[nW];
6156         if( rWidget.m_nPage >= 0 )
6157         {
6158             AnnotSortContainer& rCont = sorted[ rWidget.m_nPage ];
6159             // optimize vector allocation
6160             if( rCont.aSortedAnnots.empty() )
6161                 rCont.aSortedAnnots.reserve( m_aPages[ rWidget.m_nPage ].m_aAnnotations.size() );
6162             // insert widget to tab sorter
6163             // RadioButtons are not page annotations, only their individual check boxes are
6164             if( rWidget.m_eType != PDFWriter::RadioButton )
6165             {
6166                 rCont.aObjects.insert( rWidget.m_nObject );
6167                 rCont.aSortedAnnots.emplace_back( rWidget.m_nTabOrder, rWidget.m_nObject, nW );
6168             }
6169         }
6170     }
6171     for (auto & item : sorted)
6172     {
6173         // append entries for non widget annotations
6174         PDFPage& rPage = m_aPages[ item.first ];
6175         unsigned int nAnnots = rPage.m_aAnnotations.size();
6176         for( unsigned int nA = 0; nA < nAnnots; nA++ )
6177             if( item.second.aObjects.find( rPage.m_aAnnotations[nA] ) == item.second.aObjects.end())
6178                 item.second.aSortedAnnots.emplace_back( 10000, rPage.m_aAnnotations[nA], -1 );
6179 
6180         AnnotSorterLess aLess( m_aWidgets );
6181         std::stable_sort( item.second.aSortedAnnots.begin(), item.second.aSortedAnnots.end(), aLess );
6182         // sanity check
6183         if( item.second.aSortedAnnots.size() == nAnnots)
6184         {
6185             for( unsigned int nA = 0; nA < nAnnots; nA++ )
6186                 rPage.m_aAnnotations[nA] = item.second.aSortedAnnots[nA].nObject;
6187         }
6188         else
6189         {
6190             SAL_WARN( "vcl.pdfwriter", "wrong number of sorted annotations" );
6191             SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::sortWidgets(): wrong number of sorted assertions "
6192                      "on page nr " << item.first << ", " <<
6193                      static_cast<tools::Long>(item.second.aSortedAnnots.size()) << " sorted and " <<
6194                      static_cast<tools::Long>(nAnnots) << " unsorted");
6195         }
6196     }
6197 
6198     // FIXME: implement tab order in structure tree for PDF 1.5
6199 }
6200 
6201 bool PDFWriterImpl::emit()
6202 {
6203     endPage();
6204 
6205     // resort structure tree and annotations if necessary
6206     // needed for widget tab order
6207     sortWidgets();
6208 
6209 #if HAVE_FEATURE_NSS
6210     if( m_aContext.SignPDF )
6211     {
6212         // sign the document
6213         PDFWriter::SignatureWidget aSignature;
6214         aSignature.Name = "Signature1";
6215         createControl( aSignature, 0 );
6216     }
6217 #endif
6218 
6219     // emit catalog
6220     CHECK_RETURN( emitCatalog() );
6221 
6222 #if HAVE_FEATURE_NSS
6223     if (m_nSignatureObject != -1) // if document is signed, emit sigdict
6224     {
6225         if( !emitSignature() )
6226         {
6227             m_aErrors.insert( PDFWriter::Error_Signature_Failed );
6228             return false;
6229         }
6230     }
6231 #endif
6232 
6233     // emit trailer
6234     CHECK_RETURN( emitTrailer() );
6235 
6236 #if HAVE_FEATURE_NSS
6237     if (m_nSignatureObject != -1) // finalize the signature
6238     {
6239         if( !finalizeSignature() )
6240         {
6241             m_aErrors.insert( PDFWriter::Error_Signature_Failed );
6242             return false;
6243         }
6244     }
6245 #endif
6246 
6247     m_aFile.close();
6248     m_bOpen = false;
6249 
6250     return true;
6251 }
6252 
6253 
6254 sal_Int32 PDFWriterImpl::getSystemFont( const vcl::Font& i_rFont )
6255 {
6256     Push();
6257 
6258     SetFont( i_rFont );
6259 
6260     const LogicalFontInstance* pFontInstance = GetFontInstance();
6261     const vcl::font::PhysicalFontFace* pFace = pFontInstance->GetFontFace();
6262     sal_Int32 nFontID = 0;
6263     auto it = m_aSystemFonts.find( pFace );
6264     if( it != m_aSystemFonts.end() )
6265         nFontID = it->second.m_nNormalFontID;
6266     else
6267     {
6268         nFontID = m_nNextFID++;
6269         m_aSystemFonts[ pFace ] = EmbedFont();
6270         m_aSystemFonts[ pFace ].m_pFontInstance = const_cast<LogicalFontInstance*>(pFontInstance);
6271         m_aSystemFonts[ pFace ].m_nNormalFontID = nFontID;
6272     }
6273 
6274     Pop();
6275     return nFontID;
6276 }
6277 
6278 void PDFWriterImpl::registerSimpleGlyph(const sal_GlyphId nFontGlyphId,
6279                                   const vcl::font::PhysicalFontFace* pFace,
6280                                   const std::vector<sal_Ucs>& rCodeUnits,
6281                                   sal_Int32 nGlyphWidth,
6282                                   sal_uInt8& nMappedGlyph,
6283                                   sal_Int32& nMappedFontObject)
6284 {
6285     FontSubset& rSubset = m_aSubsets[ pFace ];
6286     // search for font specific glyphID
6287     auto it = rSubset.m_aMapping.find( nFontGlyphId );
6288     if( it != rSubset.m_aMapping.end() )
6289     {
6290         nMappedFontObject = it->second.m_nFontID;
6291         nMappedGlyph = it->second.m_nSubsetGlyphID;
6292     }
6293     else
6294     {
6295         // create new subset if necessary
6296         if( rSubset.m_aSubsets.empty()
6297         || (rSubset.m_aSubsets.back().m_aMapping.size() > 254) )
6298         {
6299             rSubset.m_aSubsets.emplace_back( m_nNextFID++ );
6300         }
6301 
6302         // copy font id
6303         nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID;
6304         // create new glyph in subset
6305         sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(rSubset.m_aSubsets.back().m_aMapping.size()+1);
6306         nMappedGlyph = nNewId;
6307 
6308         // add new glyph to emitted font subset
6309         GlyphEmit& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[ nFontGlyphId ];
6310         rNewGlyphEmit.setGlyphId( nNewId );
6311         rNewGlyphEmit.setGlyphWidth(XUnits(pFace->UnitsPerEm(), nGlyphWidth));
6312         for (const auto nCode : rCodeUnits)
6313             rNewGlyphEmit.addCode(nCode);
6314 
6315         // add new glyph to font mapping
6316         Glyph& rNewGlyph = rSubset.m_aMapping[ nFontGlyphId ];
6317         rNewGlyph.m_nFontID = nMappedFontObject;
6318         rNewGlyph.m_nSubsetGlyphID = nNewId;
6319     }
6320 }
6321 
6322 void PDFWriterImpl::registerGlyph(const sal_GlyphId nFontGlyphId,
6323                                   const vcl::font::PhysicalFontFace* pFace,
6324                                   const LogicalFontInstance* pFont,
6325                                   const std::vector<sal_Ucs>& rCodeUnits, sal_Int32 nGlyphWidth,
6326                                   sal_uInt8& nMappedGlyph, sal_Int32& nMappedFontObject)
6327 {
6328     auto bVariations = !pFace->GetVariations(*pFont).empty();
6329     // tdf#155161
6330     // PDF doesn’t support CFF2 table and we currently don’t convert them to
6331     // Type 1 (like we do with CFF table), so treat it like fonts with
6332     // variations and embed as Type 3 fonts.
6333     if (!pFace->GetRawFontData(HB_TAG('C', 'F', 'F', '2')).empty())
6334         bVariations = true;
6335 
6336     if (pFace->IsColorFont() || bVariations)
6337     {
6338         // Font has colors, check if this glyph has color layers or bitmap.
6339         tools::Rectangle aRect;
6340         auto aLayers = pFace->GetGlyphColorLayers(nFontGlyphId);
6341         auto aBitmap = pFace->GetGlyphColorBitmap(nFontGlyphId, aRect);
6342         if (!aLayers.empty() || !aBitmap.empty() || bVariations)
6343         {
6344             auto& rSubset = m_aType3Fonts[pFace];
6345             auto it = rSubset.m_aMapping.find(nFontGlyphId);
6346             if (it != rSubset.m_aMapping.end())
6347             {
6348                 nMappedFontObject = it->second.m_nFontID;
6349                 nMappedGlyph = it->second.m_nSubsetGlyphID;
6350             }
6351             else
6352             {
6353                 // create new subset if necessary
6354                 if (rSubset.m_aSubsets.empty()
6355                     || (rSubset.m_aSubsets.back().m_aMapping.size() > 254))
6356                 {
6357                     rSubset.m_aSubsets.emplace_back(m_nNextFID++);
6358                 }
6359 
6360                 // copy font id
6361                 nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID;
6362                 // create new glyph in subset
6363                 sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(
6364                     rSubset.m_aSubsets.back().m_aMapping.size() + 1);
6365                 nMappedGlyph = nNewId;
6366 
6367                 // add new glyph to emitted font subset
6368                 auto& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[nFontGlyphId];
6369                 rNewGlyphEmit.setGlyphId(nNewId);
6370                 rNewGlyphEmit.setGlyphWidth(nGlyphWidth);
6371                 for (const auto nCode : rCodeUnits)
6372                     rNewGlyphEmit.addCode(nCode);
6373 
6374                 // add color layers to the glyphs
6375                 if (!aLayers.empty())
6376                 {
6377                     for (const auto& aLayer : aLayers)
6378                     {
6379                         sal_uInt8 nLayerGlyph;
6380                         sal_Int32 nLayerFontID;
6381                         registerSimpleGlyph(aLayer.nGlyphIndex, pFace, rCodeUnits, nGlyphWidth,
6382                                             nLayerGlyph, nLayerFontID);
6383 
6384                         rNewGlyphEmit.addColorLayer(
6385                             { nLayerFontID, nLayerGlyph, aLayer.nColorIndex });
6386                     }
6387                 }
6388                 else if (!aBitmap.empty())
6389                     rNewGlyphEmit.setColorBitmap(aBitmap, aRect);
6390                 else if (bVariations)
6391                     rNewGlyphEmit.setOutline(pFont->GetGlyphOutlineUntransformed(nFontGlyphId));
6392 
6393                 // add new glyph to font mapping
6394                 Glyph& rNewGlyph = rSubset.m_aMapping[nFontGlyphId];
6395                 rNewGlyph.m_nFontID = nMappedFontObject;
6396                 rNewGlyph.m_nSubsetGlyphID = nNewId;
6397             }
6398             return;
6399         }
6400     }
6401 
6402     // If we reach here then the glyph has no color layers.
6403     registerSimpleGlyph(nFontGlyphId, pFace, rCodeUnits, nGlyphWidth, nMappedGlyph,
6404                         nMappedFontObject);
6405 }
6406 
6407 void PDFWriterImpl::drawRelief( SalLayout& rLayout, const OUString& rText, bool bTextLines )
6408 {
6409     push( PushFlags::ALL );
6410 
6411     FontRelief eRelief = m_aCurrentPDFState.m_aFont.GetRelief();
6412 
6413     Color aTextColor = m_aCurrentPDFState.m_aFont.GetColor();
6414     Color aTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
6415     Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
6416     Color aReliefColor( COL_LIGHTGRAY );
6417     if( aTextColor == COL_BLACK )
6418         aTextColor = COL_WHITE;
6419     if( aTextLineColor == COL_BLACK )
6420         aTextLineColor = COL_WHITE;
6421     if( aOverlineColor == COL_BLACK )
6422         aOverlineColor = COL_WHITE;
6423     // coverity[copy_paste_error : FALSE] - aReliefColor depending on aTextColor is correct
6424     if( aTextColor == COL_WHITE )
6425         aReliefColor = COL_BLACK;
6426 
6427     Font aSetFont = m_aCurrentPDFState.m_aFont;
6428     aSetFont.SetRelief( FontRelief::NONE );
6429     aSetFont.SetShadow( false );
6430 
6431     aSetFont.SetColor( aReliefColor );
6432     setTextLineColor( aReliefColor );
6433     setOverlineColor( aReliefColor );
6434     setFont( aSetFont );
6435     tools::Long nOff = 1 + GetDPIX()/300;
6436     if( eRelief == FontRelief::Engraved )
6437         nOff = -nOff;
6438 
6439     rLayout.DrawOffset() += Point( nOff, nOff );
6440     updateGraphicsState();
6441     drawLayout( rLayout, rText, bTextLines );
6442 
6443     rLayout.DrawOffset() -= Point( nOff, nOff );
6444     setTextLineColor( aTextLineColor );
6445     setOverlineColor( aOverlineColor );
6446     aSetFont.SetColor( aTextColor );
6447     setFont( aSetFont );
6448     updateGraphicsState();
6449     drawLayout( rLayout, rText, bTextLines );
6450 
6451     // clean up the mess
6452     pop();
6453 }
6454 
6455 void PDFWriterImpl::drawShadow( SalLayout& rLayout, const OUString& rText, bool bTextLines )
6456 {
6457     Font aSaveFont = m_aCurrentPDFState.m_aFont;
6458     Color aSaveTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
6459     Color aSaveOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
6460 
6461     Font& rFont = m_aCurrentPDFState.m_aFont;
6462     if( rFont.GetColor() == COL_BLACK || rFont.GetColor().GetLuminance() < 8 )
6463         rFont.SetColor( COL_LIGHTGRAY );
6464     else
6465         rFont.SetColor( COL_BLACK );
6466     rFont.SetShadow( false );
6467     rFont.SetOutline( false );
6468     setFont( rFont );
6469     setTextLineColor( rFont.GetColor() );
6470     setOverlineColor( rFont.GetColor() );
6471     updateGraphicsState();
6472 
6473     tools::Long nOff = 1 + ((GetFontInstance()->mnLineHeight-24)/24);
6474     if( rFont.IsOutline() )
6475         nOff++;
6476     rLayout.DrawBase() += basegfx::B2DPoint(nOff, nOff);
6477     drawLayout( rLayout, rText, bTextLines );
6478     rLayout.DrawBase() -= basegfx::B2DPoint(nOff, nOff);
6479 
6480     setFont( aSaveFont );
6481     setTextLineColor( aSaveTextLineColor );
6482     setOverlineColor( aSaveOverlineColor );
6483     updateGraphicsState();
6484 }
6485 
6486 void PDFWriterImpl::drawVerticalGlyphs(
6487         const std::vector<PDFGlyph>& rGlyphs,
6488         OStringBuffer& rLine,
6489         const Point& rAlignOffset,
6490         const Matrix3& rRotScale,
6491         double fAngle,
6492         double fXScale,
6493         sal_Int32 nFontHeight)
6494 {
6495     double nXOffset = 0;
6496     Point aCurPos(SubPixelToLogic(rGlyphs[0].m_aPos));
6497     aCurPos += rAlignOffset;
6498     for( size_t i = 0; i < rGlyphs.size(); i++ )
6499     {
6500         // have to emit each glyph on its own
6501         double fDeltaAngle = 0.0;
6502         double fYScale = 1.0;
6503         double fTempXScale = fXScale;
6504 
6505         // perform artificial italics if necessary
6506         double fSkew = 0.0;
6507         if (rGlyphs[i].m_pFont->NeedsArtificialItalic())
6508             fSkew = ARTIFICIAL_ITALIC_SKEW;
6509 
6510         double fSkewB = fSkew;
6511         double fSkewA = 0.0;
6512 
6513         Point aDeltaPos;
6514         if (rGlyphs[i].m_pGlyph->IsVertical())
6515         {
6516             fDeltaAngle = M_PI/2.0;
6517             fYScale = fXScale;
6518             fTempXScale = 1.0;
6519             fSkewA = -fSkewB;
6520             fSkewB = 0.0;
6521         }
6522         aDeltaPos += SubPixelToLogic(basegfx::B2DPoint(nXOffset / fXScale, 0)) - SubPixelToLogic(basegfx::B2DPoint());
6523         if( i < rGlyphs.size()-1 )
6524         // #i120627# the text on the Y axis is reversed when export ppt file to PDF format
6525         {
6526             double nOffsetX = rGlyphs[i+1].m_aPos.getX() - rGlyphs[i].m_aPos.getX();
6527             double nOffsetY = rGlyphs[i+1].m_aPos.getY() - rGlyphs[i].m_aPos.getY();
6528             nXOffset += std::hypot(nOffsetX, nOffsetY);
6529         }
6530         if (!rGlyphs[i].m_pGlyph->glyphId())
6531             continue;
6532 
6533         aDeltaPos = rRotScale.transform( aDeltaPos );
6534 
6535         Matrix3 aMat;
6536         if( fSkewB != 0.0 || fSkewA != 0.0 )
6537             aMat.skew( fSkewA, fSkewB );
6538         aMat.scale( fTempXScale, fYScale );
6539         aMat.rotate( fAngle+fDeltaAngle );
6540         aMat.translate( aCurPos.X()+aDeltaPos.X(), aCurPos.Y()+aDeltaPos.Y() );
6541         m_aPages.back().appendMatrix3(aMat, rLine);
6542         rLine.append( " Tm" );
6543         if( i == 0 || rGlyphs[i-1].m_nMappedFontId != rGlyphs[i].m_nMappedFontId )
6544         {
6545             rLine.append( " /F" );
6546             rLine.append( rGlyphs[i].m_nMappedFontId );
6547             rLine.append( ' ' );
6548             m_aPages.back().appendMappedLength( nFontHeight, rLine );
6549             rLine.append( " Tf" );
6550         }
6551         rLine.append( "<" );
6552         appendHex( rGlyphs[i].m_nMappedGlyphId, rLine );
6553         rLine.append( ">Tj\n" );
6554     }
6555 }
6556 
6557 void PDFWriterImpl::drawHorizontalGlyphs(
6558         const std::vector<PDFGlyph>& rGlyphs,
6559         OStringBuffer& rLine,
6560         const Point& rAlignOffset,
6561         bool bFirst,
6562         double fAngle,
6563         double fXScale,
6564         sal_Int32 nFontHeight,
6565         sal_Int32 nPixelFontHeight)
6566 {
6567     // horizontal (= normal) case
6568 
6569     // fill in  run end indices
6570     // end is marked by index of the first glyph of the next run
6571     // a run is marked by same mapped font id and same Y position
6572     std::vector< sal_uInt32 > aRunEnds;
6573     aRunEnds.reserve( rGlyphs.size() );
6574     for( size_t i = 1; i < rGlyphs.size(); i++ )
6575     {
6576         if( rGlyphs[i].m_nMappedFontId != rGlyphs[i-1].m_nMappedFontId ||
6577             rGlyphs[i].m_pFont != rGlyphs[i-1].m_pFont ||
6578             rGlyphs[i].m_aPos.getY() != rGlyphs[i-1].m_aPos.getY() )
6579         {
6580             aRunEnds.push_back(i);
6581         }
6582     }
6583     // last run ends at last glyph
6584     aRunEnds.push_back( rGlyphs.size() );
6585 
6586     // loop over runs of the same font
6587     sal_uInt32 nBeginRun = 0;
6588     for( size_t nRun = 0; nRun < aRunEnds.size(); nRun++ )
6589     {
6590         // setup text matrix back transformed to current coordinate system
6591         Point aCurPos(SubPixelToLogic(rGlyphs[nBeginRun].m_aPos));
6592         aCurPos += rAlignOffset;
6593 
6594         // perform artificial italics if necessary
6595         double fSkew = 0.0;
6596         if (rGlyphs[nBeginRun].m_pFont->NeedsArtificialItalic())
6597             fSkew = ARTIFICIAL_ITALIC_SKEW;
6598 
6599         // the first run can be set with "Td" operator
6600         // subsequent use of that operator would move
6601         // the textline matrix relative to what was set before
6602         // making use of that would drive us into rounding issues
6603         Matrix3 aMat;
6604         if( bFirst && nRun == 0 && fAngle == 0.0 && fXScale == 1.0 && fSkew == 0.0 )
6605         {
6606             m_aPages.back().appendPoint( aCurPos, rLine );
6607             rLine.append( " Td " );
6608         }
6609         else
6610         {
6611             if( fSkew != 0.0 )
6612                 aMat.skew( 0.0, fSkew );
6613             aMat.scale( fXScale, 1.0 );
6614             aMat.rotate( fAngle );
6615             aMat.translate( aCurPos.X(), aCurPos.Y() );
6616             m_aPages.back().appendMatrix3(aMat, rLine);
6617             rLine.append( " Tm\n" );
6618         }
6619         // set up correct font
6620         rLine.append( "/F" );
6621         rLine.append( rGlyphs[nBeginRun].m_nMappedFontId );
6622         rLine.append( ' ' );
6623         m_aPages.back().appendMappedLength( nFontHeight, rLine );
6624         rLine.append( " Tf" );
6625 
6626         // output glyphs using Tj or TJ
6627         OStringBuffer aKernedLine( 256 ), aUnkernedLine( 256 );
6628         aKernedLine.append( "[<" );
6629         aUnkernedLine.append( '<' );
6630         appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aKernedLine );
6631         appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aUnkernedLine );
6632 
6633         aMat.invert();
6634         bool bNeedKern = false;
6635         for( sal_uInt32 nPos = nBeginRun+1; nPos < aRunEnds[nRun]; nPos++ )
6636         {
6637             appendHex( rGlyphs[nPos].m_nMappedGlyphId, aUnkernedLine );
6638             // check if default glyph positioning is sufficient
6639             const basegfx::B2DPoint aThisPos = aMat.transform( rGlyphs[nPos].m_aPos );
6640             const basegfx::B2DPoint aPrevPos = aMat.transform( rGlyphs[nPos-1].m_aPos );
6641             double fAdvance = aThisPos.getX() - aPrevPos.getX();
6642             fAdvance *= 1000.0 / nPixelFontHeight;
6643             const double fAdjustment = rGlyphs[nPos-1].m_nNativeWidth - fAdvance + 0.5;
6644             SAL_WARN_IF(
6645                 fAdjustment < SAL_MIN_INT32 || fAdjustment > SAL_MAX_INT32, "vcl.pdfwriter",
6646                 "adjustment " << fAdjustment << " outside 32-bit int");
6647             const sal_Int32 nAdjustment = static_cast<sal_Int32>(
6648                 std::clamp(fAdjustment, double(SAL_MIN_INT32), double(SAL_MAX_INT32)));
6649             if( nAdjustment != 0 )
6650             {
6651                 // apply individual glyph positioning
6652                 bNeedKern = true;
6653                 aKernedLine.append( ">" );
6654                 aKernedLine.append( nAdjustment );
6655                 aKernedLine.append( "<" );
6656             }
6657             appendHex( rGlyphs[nPos].m_nMappedGlyphId, aKernedLine );
6658         }
6659         aKernedLine.append( ">]TJ\n" );
6660         aUnkernedLine.append( ">Tj\n" );
6661         rLine.append( bNeedKern ? aKernedLine : aUnkernedLine );
6662 
6663         // set beginning of next run
6664         nBeginRun = aRunEnds[nRun];
6665     }
6666 }
6667 
6668 void PDFWriterImpl::drawLayout( SalLayout& rLayout, const OUString& rText, bool bTextLines )
6669 {
6670     // relief takes precedence over shadow (see outdev3.cxx)
6671     if(  m_aCurrentPDFState.m_aFont.GetRelief() != FontRelief::NONE )
6672     {
6673         drawRelief( rLayout, rText, bTextLines );
6674         return;
6675     }
6676     else if( m_aCurrentPDFState.m_aFont.IsShadow() )
6677         drawShadow( rLayout, rText, bTextLines );
6678 
6679     OStringBuffer aLine( 512 );
6680 
6681     const int nMaxGlyphs = 256;
6682 
6683     std::vector<sal_Ucs> aCodeUnits;
6684     bool bVertical = m_aCurrentPDFState.m_aFont.IsVertical();
6685     int nIndex = 0;
6686     double fXScale = 1.0;
6687     sal_Int32 nPixelFontHeight = GetFontInstance()->GetFontSelectPattern().mnHeight;
6688     TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
6689 
6690     // transform font height back to current units
6691     // note: the layout calculates in outdevs device pixel !!
6692     sal_Int32 nFontHeight = ImplDevicePixelToLogicHeight( nPixelFontHeight );
6693     if( m_aCurrentPDFState.m_aFont.GetAverageFontWidth() )
6694     {
6695         Font aFont( m_aCurrentPDFState.m_aFont );
6696         aFont.SetAverageFontWidth( 0 );
6697         FontMetric aMetric = GetFontMetric( aFont );
6698         if( aMetric.GetAverageFontWidth() != m_aCurrentPDFState.m_aFont.GetAverageFontWidth() )
6699         {
6700             fXScale =
6701                 static_cast<double>(m_aCurrentPDFState.m_aFont.GetAverageFontWidth()) /
6702                 static_cast<double>(aMetric.GetAverageFontWidth());
6703         }
6704     }
6705 
6706     // if the mapmode is distorted we need to adjust for that also
6707     if( m_aCurrentPDFState.m_aMapMode.GetScaleX() != m_aCurrentPDFState.m_aMapMode.GetScaleY() )
6708     {
6709         fXScale *= double(m_aCurrentPDFState.m_aMapMode.GetScaleX()) / double(m_aCurrentPDFState.m_aMapMode.GetScaleY());
6710     }
6711 
6712     Degree10 nAngle = m_aCurrentPDFState.m_aFont.GetOrientation();
6713     // normalize angles
6714     while( nAngle < 0_deg10 )
6715         nAngle += 3600_deg10;
6716     nAngle = nAngle % 3600_deg10;
6717     double fAngle = toRadians(nAngle);
6718 
6719     Matrix3 aRotScale;
6720     aRotScale.scale( fXScale, 1.0 );
6721     if( fAngle != 0.0 )
6722         aRotScale.rotate( -fAngle );
6723 
6724     bool bPop = false;
6725     bool bABold = false;
6726     // artificial bold necessary ?
6727     if (GetFontInstance()->NeedsArtificialBold())
6728     {
6729         aLine.append("q ");
6730         bPop = true;
6731         bABold = true;
6732     }
6733     // setup text colors (if necessary)
6734     Color aStrokeColor( COL_TRANSPARENT );
6735     Color aNonStrokeColor( COL_TRANSPARENT );
6736 
6737     if( m_aCurrentPDFState.m_aFont.IsOutline() )
6738     {
6739         aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6740         aNonStrokeColor = COL_WHITE;
6741     }
6742     else
6743         aNonStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6744     if( bABold )
6745         aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6746 
6747     if( aStrokeColor != COL_TRANSPARENT && aStrokeColor != m_aCurrentPDFState.m_aLineColor )
6748     {
6749         if( ! bPop )
6750             aLine.append( "q " );
6751         bPop = true;
6752         appendStrokingColor( aStrokeColor, aLine );
6753         aLine.append( "\n" );
6754     }
6755     if( aNonStrokeColor != COL_TRANSPARENT && aNonStrokeColor != m_aCurrentPDFState.m_aFillColor )
6756     {
6757         if( ! bPop )
6758             aLine.append( "q " );
6759         bPop = true;
6760         appendNonStrokingColor( aNonStrokeColor, aLine );
6761         aLine.append( "\n" );
6762     }
6763 
6764     // begin text object
6765     aLine.append( "BT\n" );
6766     // outline attribute ?
6767     if( m_aCurrentPDFState.m_aFont.IsOutline() || bABold )
6768     {
6769         // set correct text mode, set stroke width
6770         aLine.append( "2 Tr " ); // fill, then stroke
6771 
6772         if( m_aCurrentPDFState.m_aFont.IsOutline() )
6773         {
6774             // unclear what to do in case of outline and artificial bold
6775             // for the time being outline wins
6776             aLine.append( "0.25 w \n" );
6777         }
6778         else
6779         {
6780             double fW = static_cast<double>(m_aCurrentPDFState.m_aFont.GetFontHeight()) / 30.0;
6781             m_aPages.back().appendMappedLength( fW, aLine );
6782             aLine.append ( " w\n" );
6783         }
6784     }
6785 
6786     FontMetric aRefDevFontMetric = GetFontMetric();
6787     const GlyphItem* pGlyph = nullptr;
6788     const LogicalFontInstance* pGlyphFont = nullptr;
6789 
6790     // collect the glyphs into a single array
6791     std::vector< PDFGlyph > aGlyphs;
6792     aGlyphs.reserve( nMaxGlyphs );
6793     // first get all the glyphs and register them; coordinates still in Pixel
6794     basegfx::B2DPoint aPos;
6795     while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pGlyphFont))
6796     {
6797         const auto* pFace = pGlyphFont->GetFontFace();
6798 
6799         aCodeUnits.clear();
6800 
6801         // tdf#66597, tdf#115117
6802         //
6803         // Here is how we embed textual content in PDF files, to allow for
6804         // better text extraction for complex and typography-rich text.
6805         //
6806         // * If there is many to one or many to many mapping, use an
6807         //   ActualText span embedding the original string, since ToUnicode
6808         //   can't handle these.
6809         // * If the one glyph is used for several Unicode code points, also
6810         //   use ActualText since ToUnicode can map each glyph in the font
6811         //   only once.
6812         // * Limit ActualText to single cluster at a time, since using it
6813         //   for whole words or sentences breaks text selection and
6814         //   highlighting in PDF viewers (there will be no way to tell
6815         //   which glyphs belong to which characters).
6816         // * Keep generating (now) redundant ToUnicode entries for
6817         //   compatibility with old tools not supporting ActualText.
6818 
6819         assert(pGlyph->charCount() >= 0);
6820         for (int n = 0; n < pGlyph->charCount(); n++)
6821             aCodeUnits.push_back(rText[pGlyph->charPos() + n]);
6822 
6823         bool bUseActualText = false;
6824 
6825         // If this is a start of complex cluster, use ActualText.
6826         if (pGlyph->IsClusterStart())
6827             bUseActualText = true;
6828 
6829         const auto nGlyphId = pGlyph->glyphId();
6830 
6831         // A glyph can't have more than one ToUnicode entry, use ActualText
6832         // instead.
6833         if (!aCodeUnits.empty() && !bUseActualText)
6834         {
6835             for (const auto& rSubset : m_aSubsets[pFace].m_aSubsets)
6836             {
6837                 const auto& it = rSubset.m_aMapping.find(nGlyphId);
6838                 if (it != rSubset.m_aMapping.cend() && it->second.codes() != aCodeUnits)
6839                 {
6840                     bUseActualText = true;
6841                     aCodeUnits.clear();
6842                 }
6843             }
6844         }
6845 
6846         auto nGlyphWidth = pGlyphFont->GetGlyphWidth(nGlyphId, pGlyph->IsVertical(), false);
6847 
6848         sal_uInt8 nMappedGlyph;
6849         sal_Int32 nMappedFontObject;
6850         registerGlyph(nGlyphId, pFace, pGlyphFont, aCodeUnits, nGlyphWidth, nMappedGlyph, nMappedFontObject);
6851 
6852         int nCharPos = -1;
6853         if (bUseActualText || pGlyph->IsInCluster())
6854             nCharPos = pGlyph->charPos();
6855 
6856         aGlyphs.emplace_back(aPos,
6857                              pGlyph,
6858                              pGlyphFont,
6859                              XUnits(pFace->UnitsPerEm(), nGlyphWidth),
6860                              nMappedFontObject,
6861                              nMappedGlyph,
6862                              nCharPos);
6863     }
6864 
6865     // Avoid fill color when map mode is in pixels, the below code assumes
6866     // logic map mode.
6867     bool bPixel = m_aCurrentPDFState.m_aMapMode.GetMapUnit() == MapUnit::MapPixel;
6868     if (m_aCurrentPDFState.m_aFont.GetFillColor() != COL_TRANSPARENT && !bPixel)
6869     {
6870         // PDF doesn't have a text fill color, so draw a rectangle before
6871         // drawing the actual text.
6872         push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR);
6873         setFillColor(m_aCurrentPDFState.m_aFont.GetFillColor());
6874         // Avoid border around the rectangle for Writer shape text.
6875         setLineColor(COL_TRANSPARENT);
6876 
6877         // The rectangle is the bounding box of the text, but also includes
6878         // ascent / descent to match the on-screen rendering.
6879         // This is the top left of the text without ascent / descent.
6880         basegfx::B2DPoint aDrawPosition(rLayout.GetDrawPosition());
6881         tools::Rectangle aRectangle(SubPixelToLogic(aDrawPosition),
6882                                     Size(ImplDevicePixelToLogicWidth(rLayout.GetTextWidth()), 0));
6883         aRectangle.AdjustTop(-aRefDevFontMetric.GetAscent());
6884         // This includes ascent / descent.
6885         aRectangle.setHeight(aRefDevFontMetric.GetLineHeight());
6886 
6887         const LogicalFontInstance* pFontInstance = GetFontInstance();
6888         if (pFontInstance->mnOrientation)
6889         {
6890             // Adapt rectangle for rotated text.
6891             tools::Polygon aPolygon(aRectangle);
6892             aPolygon.Rotate(SubPixelToLogic(aDrawPosition), pFontInstance->mnOrientation);
6893             drawPolygon(aPolygon);
6894         }
6895         else
6896             drawRectangle(aRectangle);
6897 
6898         pop();
6899     }
6900 
6901     Point aAlignOffset;
6902     if ( eAlign == ALIGN_BOTTOM )
6903         aAlignOffset.AdjustY( -(aRefDevFontMetric.GetDescent()) );
6904     else if ( eAlign == ALIGN_TOP )
6905         aAlignOffset.AdjustY(aRefDevFontMetric.GetAscent() );
6906     if( aAlignOffset.X() || aAlignOffset.Y() )
6907         aAlignOffset = aRotScale.transform( aAlignOffset );
6908 
6909     /* #159153# do not emit an empty glyph vector; this can happen if e.g. the original
6910        string contained only one of the UTF16 BOMs
6911     */
6912     if( ! aGlyphs.empty() )
6913     {
6914         size_t nStart = 0;
6915         size_t nEnd = 0;
6916         while (nStart < aGlyphs.size())
6917         {
6918             while (nEnd < aGlyphs.size() && aGlyphs[nEnd].m_nCharPos == aGlyphs[nStart].m_nCharPos)
6919                 nEnd++;
6920 
6921             std::vector<PDFGlyph> aRun(aGlyphs.begin() + nStart, aGlyphs.begin() + nEnd);
6922 
6923             int nCharPos, nCharCount;
6924             if (!aRun.front().m_pGlyph->IsRTLGlyph())
6925             {
6926                 nCharPos = aRun.front().m_nCharPos;
6927                 nCharCount = aRun.front().m_pGlyph->charCount();
6928             }
6929             else
6930             {
6931                 nCharPos = aRun.back().m_nCharPos;
6932                 nCharCount = aRun.back().m_pGlyph->charCount();
6933             }
6934 
6935             if (nCharPos >= 0 && nCharCount)
6936             {
6937                 aLine.append("/Span<</ActualText<FEFF");
6938                 for (int i = 0; i < nCharCount; i++)
6939                 {
6940                     sal_Unicode aChar = rText[nCharPos + i];
6941                     appendHex(static_cast<sal_Int8>(aChar >> 8), aLine);
6942                     appendHex(static_cast<sal_Int8>(aChar & 255), aLine);
6943                 }
6944                 aLine.append( ">>>\nBDC\n" );
6945             }
6946 
6947             if (bVertical)
6948                 drawVerticalGlyphs(aRun, aLine, aAlignOffset, aRotScale, fAngle, fXScale, nFontHeight);
6949             else
6950                 drawHorizontalGlyphs(aRun, aLine, aAlignOffset, nStart == 0, fAngle, fXScale, nFontHeight, nPixelFontHeight);
6951 
6952             if (nCharPos >= 0 && nCharCount)
6953                 aLine.append( "EMC\n" );
6954 
6955             nStart = nEnd;
6956         }
6957     }
6958 
6959     // end textobject
6960     aLine.append( "ET\n" );
6961     if( bPop )
6962         aLine.append( "Q\n" );
6963 
6964     writeBuffer( aLine );
6965 
6966     // draw eventual textlines
6967     FontStrikeout eStrikeout = m_aCurrentPDFState.m_aFont.GetStrikeout();
6968     FontLineStyle eUnderline = m_aCurrentPDFState.m_aFont.GetUnderline();
6969     FontLineStyle eOverline  = m_aCurrentPDFState.m_aFont.GetOverline();
6970     if( bTextLines &&
6971         (
6972          ( eUnderline != LINESTYLE_NONE && eUnderline != LINESTYLE_DONTKNOW ) ||
6973          ( eOverline  != LINESTYLE_NONE && eOverline  != LINESTYLE_DONTKNOW ) ||
6974          ( eStrikeout != STRIKEOUT_NONE && eStrikeout != STRIKEOUT_DONTKNOW )
6975          )
6976         )
6977     {
6978         bool bUnderlineAbove = m_aCurrentPDFState.m_aFont.IsUnderlineAbove();
6979         if( m_aCurrentPDFState.m_aFont.IsWordLineMode() )
6980         {
6981             basegfx::B2DPoint aStartPt;
6982             double nWidth = 0;
6983             nIndex = 0;
6984             while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex))
6985             {
6986                 if (!pGlyph->IsSpacing())
6987                 {
6988                     if( !nWidth )
6989                         aStartPt = aPos;
6990 
6991                     nWidth += pGlyph->newWidth();
6992                 }
6993                 else if( nWidth > 0 )
6994                 {
6995                     drawTextLine( SubPixelToLogic(aStartPt),
6996                                   ImplDevicePixelToLogicWidth( nWidth ),
6997                                   eStrikeout, eUnderline, eOverline, bUnderlineAbove );
6998                     nWidth = 0;
6999                 }
7000             }
7001 
7002             if( nWidth > 0 )
7003             {
7004                 drawTextLine( SubPixelToLogic(aStartPt),
7005                               ImplDevicePixelToLogicWidth( nWidth ),
7006                               eStrikeout, eUnderline, eOverline, bUnderlineAbove );
7007             }
7008         }
7009         else
7010         {
7011             basegfx::B2DPoint aStartPt = rLayout.GetDrawPosition();
7012             int nWidth = rLayout.GetTextWidth();
7013             drawTextLine( SubPixelToLogic(aStartPt),
7014                           ImplDevicePixelToLogicWidth( nWidth ),
7015                           eStrikeout, eUnderline, eOverline, bUnderlineAbove );
7016         }
7017     }
7018 
7019     // write eventual emphasis marks
7020     if( !(m_aCurrentPDFState.m_aFont.GetEmphasisMark() & FontEmphasisMark::Style) )
7021         return;
7022 
7023     push( PushFlags::ALL );
7024 
7025     aLine.setLength( 0 );
7026     aLine.append( "q\n" );
7027 
7028     FontEmphasisMark nEmphMark = m_aCurrentPDFState.m_aFont.GetEmphasisMarkStyle();
7029 
7030     tools::Long nEmphHeight;
7031     if ( nEmphMark & FontEmphasisMark::PosBelow )
7032         nEmphHeight = GetEmphasisDescent();
7033     else
7034         nEmphHeight = GetEmphasisAscent();
7035 
7036     vcl::font::EmphasisMark aEmphasisMark(nEmphMark, ImplDevicePixelToLogicWidth(nEmphHeight), GetDPIY());
7037     if ( aEmphasisMark.IsShapePolyLine() )
7038     {
7039         setLineColor( m_aCurrentPDFState.m_aFont.GetColor() );
7040         setFillColor( COL_TRANSPARENT );
7041     }
7042     else
7043     {
7044         setFillColor( m_aCurrentPDFState.m_aFont.GetColor() );
7045         setLineColor( COL_TRANSPARENT );
7046     }
7047 
7048     writeBuffer( aLine );
7049 
7050     Point aOffset(0,0);
7051     Point aOffsetVert(0,0);
7052 
7053     if ( nEmphMark & FontEmphasisMark::PosBelow )
7054     {
7055         aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset() );
7056         aOffsetVert = aOffset;
7057     }
7058     else
7059     {
7060         aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetAscent() + aEmphasisMark.GetYOffset()) );
7061         // Todo: use ideographic em-box or ideographic character face information.
7062         aOffsetVert.AdjustY(-(GetFontInstance()->mxFontMetric->GetAscent() +
7063                     GetFontInstance()->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset()));
7064     }
7065 
7066     tools::Long nEmphWidth2 = aEmphasisMark.GetWidth() / 2;
7067     tools::Long nEmphHeight2 = nEmphHeight / 2;
7068     aOffset += Point( nEmphWidth2, nEmphHeight2 );
7069 
7070     if ( eAlign == ALIGN_BOTTOM )
7071         aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetDescent()) );
7072     else if ( eAlign == ALIGN_TOP )
7073         aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetAscent() );
7074 
7075     tools::Rectangle aRectangle;
7076     nIndex = 0;
7077     while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pGlyphFont))
7078     {
7079         if (!pGlyph->GetGlyphBoundRect(pGlyphFont, aRectangle))
7080             continue;
7081 
7082         if (!pGlyph->IsSpacing())
7083         {
7084             basegfx::B2DPoint aAdjOffset;
7085             if (pGlyph->IsVertical())
7086             {
7087                 aAdjOffset = basegfx::B2DPoint(aOffsetVert.X(), aOffsetVert.Y());
7088                 aAdjOffset.adjustX((-pGlyph->origWidth() + aEmphasisMark.GetWidth()) / 2);
7089             }
7090             else
7091             {
7092                 aAdjOffset = basegfx::B2DPoint(aOffset.X(), aOffset.Y());
7093                 aAdjOffset.adjustX(aRectangle.Left() + (aRectangle.GetWidth() - aEmphasisMark.GetWidth()) / 2 );
7094             }
7095 
7096             aAdjOffset = aRotScale.transform( aAdjOffset );
7097 
7098             aAdjOffset -= basegfx::B2DPoint(nEmphWidth2, nEmphHeight2);
7099 
7100             basegfx::B2DPoint aMarkDevPos(aPos);
7101             aMarkDevPos += aAdjOffset;
7102             Point aMarkPos = SubPixelToLogic(aMarkDevPos);
7103             drawEmphasisMark( aMarkPos.X(), aMarkPos.Y(),
7104                               aEmphasisMark.GetShape(), aEmphasisMark.IsShapePolyLine(),
7105                               aEmphasisMark.GetRect1(), aEmphasisMark.GetRect2() );
7106         }
7107     }
7108 
7109     writeBuffer( "Q\n" );
7110     pop();
7111 
7112 }
7113 
7114 void PDFWriterImpl::drawEmphasisMark( tools::Long nX, tools::Long nY,
7115                                       const tools::PolyPolygon& rPolyPoly, bool bPolyLine,
7116                                       const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 )
7117 {
7118     // TODO: pass nWidth as width of this mark
7119     // long nWidth = 0;
7120 
7121     if ( rPolyPoly.Count() )
7122     {
7123         if ( bPolyLine )
7124         {
7125             tools::Polygon aPoly = rPolyPoly.GetObject( 0 );
7126             aPoly.Move( nX, nY );
7127             drawPolyLine( aPoly );
7128         }
7129         else
7130         {
7131             tools::PolyPolygon aPolyPoly = rPolyPoly;
7132             aPolyPoly.Move( nX, nY );
7133             drawPolyPolygon( aPolyPoly );
7134         }
7135     }
7136 
7137     if ( !rRect1.IsEmpty() )
7138     {
7139         tools::Rectangle aRect( Point( nX+rRect1.Left(),
7140                                 nY+rRect1.Top() ), rRect1.GetSize() );
7141         drawRectangle( aRect );
7142     }
7143 
7144     if ( !rRect2.IsEmpty() )
7145     {
7146         tools::Rectangle aRect( Point( nX+rRect2.Left(),
7147                                 nY+rRect2.Top() ), rRect2.GetSize() );
7148 
7149         drawRectangle( aRect );
7150     }
7151 }
7152 
7153 void PDFWriterImpl::drawText( const Point& rPos, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen, bool bTextLines )
7154 {
7155     MARK( "drawText" );
7156 
7157     updateGraphicsState();
7158 
7159     // get a layout from the OutputDevice's SalGraphics
7160     // this also enforces font substitution and sets the font on SalGraphics
7161     const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->
7162         GetLayoutGlyphs( this, rText, nIndex, nLen );
7163     std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos,
7164         0, {}, {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs );
7165     if( pLayout )
7166     {
7167         drawLayout( *pLayout, rText, bTextLines );
7168     }
7169 }
7170 
7171 void PDFWriterImpl::drawTextArray( const Point& rPos, const OUString& rText, KernArraySpan pDXArray, o3tl::span<const sal_Bool> pKashidaArray, sal_Int32 nIndex, sal_Int32 nLen )
7172 {
7173     MARK( "drawText with array" );
7174 
7175     updateGraphicsState();
7176 
7177     // get a layout from the OutputDevice's SalGraphics
7178     // this also enforces font substitution and sets the font on SalGraphics
7179     const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->
7180         GetLayoutGlyphs( this, rText, nIndex, nLen );
7181     std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, 0, pDXArray, pKashidaArray,
7182         SalLayoutFlags::NONE, nullptr, layoutGlyphs );
7183     if( pLayout )
7184     {
7185         drawLayout( *pLayout, rText, true );
7186     }
7187 }
7188 
7189 void PDFWriterImpl::drawStretchText( const Point& rPos, sal_Int32 nWidth, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen )
7190 {
7191     MARK( "drawStretchText" );
7192 
7193     updateGraphicsState();
7194 
7195     // get a layout from the OutputDevice's SalGraphics
7196     // this also enforces font substitution and sets the font on SalGraphics
7197     const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->
7198         GetLayoutGlyphs( this, rText, nIndex, nLen, nWidth );
7199     std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, nWidth,
7200         {}, {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs );
7201     if( pLayout )
7202     {
7203         drawLayout( *pLayout, rText, true );
7204     }
7205 }
7206 
7207 void PDFWriterImpl::drawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle )
7208 {
7209     tools::Long        nWidth          = rRect.GetWidth();
7210     tools::Long        nHeight         = rRect.GetHeight();
7211 
7212     if ( nWidth <= 0 || nHeight <= 0 )
7213         return;
7214 
7215     MARK( "drawText with rectangle" );
7216 
7217     updateGraphicsState();
7218 
7219     // clip with rectangle
7220     OStringBuffer aLine;
7221     aLine.append( "q " );
7222     m_aPages.back().appendRect( rRect, aLine );
7223     aLine.append( " W* n\n" );
7224     writeBuffer( aLine );
7225 
7226     // if disabled text is needed, put in here
7227 
7228     Point       aPos            = rRect.TopLeft();
7229 
7230     tools::Long        nTextHeight     = GetTextHeight();
7231     sal_Int32   nMnemonicPos    = -1;
7232 
7233     OUString aStr = rOrigStr;
7234     if ( nStyle & DrawTextFlags::Mnemonic )
7235         aStr = removeMnemonicFromString( aStr, nMnemonicPos );
7236 
7237     // multiline text
7238     if ( nStyle & DrawTextFlags::MultiLine )
7239     {
7240         ImplMultiTextLineInfo   aMultiLineInfo;
7241         sal_Int32               i;
7242         sal_Int32               nFormatLines;
7243 
7244         if ( nTextHeight )
7245         {
7246             vcl::DefaultTextLayout aLayout( *this );
7247             OUString               aLastLine;
7248             OutputDevice::ImplGetTextLines( rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle, aLayout );
7249             sal_Int32              nLines = nHeight/nTextHeight;
7250             nFormatLines = aMultiLineInfo.Count();
7251             if ( !nLines )
7252                 nLines = 1;
7253             if ( nFormatLines > nLines )
7254             {
7255                 if ( nStyle & DrawTextFlags::EndEllipsis )
7256                 {
7257                     // handle last line
7258                     nFormatLines = nLines-1;
7259 
7260                     ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( nFormatLines );
7261                     aLastLine = convertLineEnd(aStr.copy(rLineInfo.GetIndex()), LINEEND_LF);
7262                     // replace line feed by space
7263                     aLastLine = aLastLine.replace('\n', ' ');
7264                     aLastLine = GetEllipsisString( aLastLine, nWidth, nStyle );
7265                     nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom);
7266                     nStyle |= DrawTextFlags::Top;
7267                 }
7268             }
7269 
7270             // vertical alignment
7271             if ( nStyle & DrawTextFlags::Bottom )
7272                 aPos.AdjustY(nHeight-(nFormatLines*nTextHeight) );
7273             else if ( nStyle & DrawTextFlags::VCenter )
7274                 aPos.AdjustY((nHeight-(nFormatLines*nTextHeight))/2 );
7275 
7276             // draw all lines excluding the last
7277             for ( i = 0; i < nFormatLines; i++ )
7278             {
7279                 ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i );
7280                 if ( nStyle & DrawTextFlags::Right )
7281                     aPos.AdjustX(nWidth-rLineInfo.GetWidth() );
7282                 else if ( nStyle & DrawTextFlags::Center )
7283                     aPos.AdjustX((nWidth-rLineInfo.GetWidth())/2 );
7284                 sal_Int32 nIndex = rLineInfo.GetIndex();
7285                 sal_Int32 nLineLen = rLineInfo.GetLen();
7286                 drawText( aPos, aStr, nIndex, nLineLen );
7287                 // mnemonics should not appear in documents,
7288                 // if the need arises, put them in here
7289                 aPos.AdjustY(nTextHeight );
7290                 aPos.setX( rRect.Left() );
7291             }
7292 
7293             // output last line left adjusted since it was shortened
7294             if (!aLastLine.isEmpty())
7295                 drawText( aPos, aLastLine, 0, aLastLine.getLength() );
7296         }
7297     }
7298     else
7299     {
7300         tools::Long nTextWidth = GetTextWidth( aStr );
7301 
7302         // Evt. Text kuerzen
7303         if ( nTextWidth > nWidth )
7304         {
7305             if ( nStyle & (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis) )
7306             {
7307                 aStr = GetEllipsisString( aStr, nWidth, nStyle );
7308                 nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right);
7309                 nStyle |= DrawTextFlags::Left;
7310                 nTextWidth = GetTextWidth( aStr );
7311             }
7312         }
7313 
7314         // vertical alignment
7315         if ( nStyle & DrawTextFlags::Right )
7316             aPos.AdjustX(nWidth-nTextWidth );
7317         else if ( nStyle & DrawTextFlags::Center )
7318             aPos.AdjustX((nWidth-nTextWidth)/2 );
7319 
7320         if ( nStyle & DrawTextFlags::Bottom )
7321             aPos.AdjustY(nHeight-nTextHeight );
7322         else if ( nStyle & DrawTextFlags::VCenter )
7323             aPos.AdjustY((nHeight-nTextHeight)/2 );
7324 
7325         // mnemonics should be inserted here if the need arises
7326 
7327         // draw the actual text
7328         drawText( aPos, aStr, 0, aStr.getLength() );
7329     }
7330 
7331     // reset clip region to original value
7332     aLine.setLength( 0 );
7333     aLine.append( "Q\n" );
7334     writeBuffer( aLine );
7335 }
7336 
7337 void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop )
7338 {
7339     MARK( "drawLine" );
7340 
7341     updateGraphicsState();
7342 
7343     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
7344         return;
7345 
7346     OStringBuffer aLine;
7347     m_aPages.back().appendPoint( rStart, aLine );
7348     aLine.append( " m " );
7349     m_aPages.back().appendPoint( rStop, aLine );
7350     aLine.append( " l S\n" );
7351 
7352     writeBuffer( aLine );
7353 }
7354 
7355 void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo )
7356 {
7357     MARK( "drawLine with LineInfo" );
7358     updateGraphicsState();
7359 
7360     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
7361         return;
7362 
7363     if( rInfo.GetStyle() == LineStyle::Solid && rInfo.GetWidth() < 2 )
7364     {
7365         drawLine( rStart, rStop );
7366         return;
7367     }
7368 
7369     OStringBuffer aLine;
7370 
7371     aLine.append( "q " );
7372     if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
7373     {
7374         m_aPages.back().appendPoint( rStart, aLine );
7375         aLine.append( " m " );
7376         m_aPages.back().appendPoint( rStop, aLine );
7377         aLine.append( " l S Q\n" );
7378 
7379         writeBuffer( aLine );
7380     }
7381     else
7382     {
7383         PDFWriter::ExtLineInfo aInfo;
7384         convertLineInfoToExtLineInfo( rInfo, aInfo );
7385         Point aPolyPoints[2] = { rStart, rStop };
7386         tools::Polygon aPoly( 2, aPolyPoints );
7387         drawPolyLine( aPoly, aInfo );
7388     }
7389 }
7390 
7391 #define HCONV( x ) ImplDevicePixelToLogicHeight( x )
7392 
7393 void PDFWriterImpl::drawWaveTextLine( OStringBuffer& aLine, tools::Long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
7394 {
7395     // note: units in pFontInstance are ref device pixel
7396     const LogicalFontInstance*  pFontInstance = GetFontInstance();
7397     tools::Long            nLineHeight = 0;
7398     tools::Long            nLinePos = 0;
7399 
7400     appendStrokingColor( aColor, aLine );
7401     aLine.append( "\n" );
7402 
7403     if ( bIsAbove )
7404     {
7405         if ( !pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() )
7406             ImplInitAboveTextLineSize();
7407         nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() );
7408         nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineOffset() );
7409     }
7410     else
7411     {
7412         if ( !pFontInstance->mxFontMetric->GetWavelineUnderlineSize() )
7413             ImplInitTextLineSize();
7414         nLineHeight = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineSize() );
7415         nLinePos = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineOffset() );
7416     }
7417     if ( (eTextLine == LINESTYLE_SMALLWAVE) && (nLineHeight > 3) )
7418         nLineHeight = 3;
7419 
7420     tools::Long nLineWidth = GetDPIX()/450;
7421     if ( ! nLineWidth )
7422         nLineWidth = 1;
7423 
7424     if ( eTextLine == LINESTYLE_BOLDWAVE )
7425         nLineWidth = 3*nLineWidth;
7426 
7427     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineWidth), aLine );
7428     aLine.append( " w " );
7429 
7430     if ( eTextLine == LINESTYLE_DOUBLEWAVE )
7431     {
7432         tools::Long nOrgLineHeight = nLineHeight;
7433         nLineHeight /= 3;
7434         if ( nLineHeight < 2 )
7435         {
7436             if ( nOrgLineHeight > 1 )
7437                 nLineHeight = 2;
7438             else
7439                 nLineHeight = 1;
7440         }
7441         tools::Long nLineDY = nOrgLineHeight-(nLineHeight*2);
7442         if ( nLineDY < nLineWidth )
7443             nLineDY = nLineWidth;
7444         tools::Long nLineDY2 = nLineDY/2;
7445         if ( !nLineDY2 )
7446             nLineDY2 = 1;
7447 
7448         nLinePos -= nLineWidth-nLineDY2;
7449 
7450         m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
7451 
7452         nLinePos += nLineWidth+nLineDY;
7453         m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
7454     }
7455     else
7456     {
7457         if ( eTextLine != LINESTYLE_BOLDWAVE )
7458             nLinePos -= nLineWidth/2;
7459         m_aPages.back().appendWaveLine( nWidth, -nLinePos, nLineHeight, aLine );
7460     }
7461 }
7462 
7463 void PDFWriterImpl::drawStraightTextLine( OStringBuffer& aLine, tools::Long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
7464 {
7465     // note: units in pFontInstance are ref device pixel
7466     const LogicalFontInstance*  pFontInstance = GetFontInstance();
7467     tools::Long            nLineHeight = 0;
7468     tools::Long            nLinePos  = 0;
7469     tools::Long            nLinePos2 = 0;
7470 
7471     if ( eTextLine > LINESTYLE_BOLDWAVE )
7472         eTextLine = LINESTYLE_SINGLE;
7473 
7474     switch ( eTextLine )
7475     {
7476         case LINESTYLE_SINGLE:
7477         case LINESTYLE_DOTTED:
7478         case LINESTYLE_DASH:
7479         case LINESTYLE_LONGDASH:
7480         case LINESTYLE_DASHDOT:
7481         case LINESTYLE_DASHDOTDOT:
7482             if ( bIsAbove )
7483             {
7484                 if ( !pFontInstance->mxFontMetric->GetAboveUnderlineSize() )
7485                     ImplInitAboveTextLineSize();
7486                 nLineHeight = pFontInstance->mxFontMetric->GetAboveUnderlineSize();
7487                 nLinePos    = pFontInstance->mxFontMetric->GetAboveUnderlineOffset();
7488             }
7489             else
7490             {
7491                 if ( !pFontInstance->mxFontMetric->GetUnderlineSize() )
7492                     ImplInitTextLineSize();
7493                 nLineHeight = pFontInstance->mxFontMetric->GetUnderlineSize();
7494                 nLinePos    = pFontInstance->mxFontMetric->GetUnderlineOffset();
7495             }
7496             break;
7497         case LINESTYLE_BOLD:
7498         case LINESTYLE_BOLDDOTTED:
7499         case LINESTYLE_BOLDDASH:
7500         case LINESTYLE_BOLDLONGDASH:
7501         case LINESTYLE_BOLDDASHDOT:
7502         case LINESTYLE_BOLDDASHDOTDOT:
7503             if ( bIsAbove )
7504             {
7505                 if ( !pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() )
7506                     ImplInitAboveTextLineSize();
7507                 nLineHeight = pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize();
7508                 nLinePos    = pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset();
7509             }
7510             else
7511             {
7512                 if ( !pFontInstance->mxFontMetric->GetBoldUnderlineSize() )
7513                     ImplInitTextLineSize();
7514                 nLineHeight = pFontInstance->mxFontMetric->GetBoldUnderlineSize();
7515                 nLinePos    = pFontInstance->mxFontMetric->GetBoldUnderlineOffset();
7516             }
7517             break;
7518         case LINESTYLE_DOUBLE:
7519             if ( bIsAbove )
7520             {
7521                 if ( !pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() )
7522                     ImplInitAboveTextLineSize();
7523                 nLineHeight = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize();
7524                 nLinePos    = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1();
7525                 nLinePos2   = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2();
7526             }
7527             else
7528             {
7529                 if ( !pFontInstance->mxFontMetric->GetDoubleUnderlineSize() )
7530                     ImplInitTextLineSize();
7531                 nLineHeight = pFontInstance->mxFontMetric->GetDoubleUnderlineSize();
7532                 nLinePos    = pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1();
7533                 nLinePos2   = pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2();
7534             }
7535             break;
7536         default:
7537             break;
7538     }
7539 
7540     if ( !nLineHeight )
7541         return;
7542 
7543     // tdf#154235
7544     // nLinePos/nLinePos2 is the distance from baseline to the top of the line,
7545     // while in PDF we stroke the line so the position is to the middle of the
7546     // line, we add half of nLineHeight to account for that.
7547     auto nOffset = nLineHeight / 2;
7548     if (m_aCurrentPDFState.m_aFont.IsOutline() && eTextLine == LINESTYLE_SINGLE)
7549     {
7550         // Except when outlining, as now we are drawing a rectangle, so
7551         // nLinePos is the bottom of the rectangle, so need to add nLineHeight
7552         // to it.
7553         nOffset = nLineHeight;
7554     }
7555 
7556     nLineHeight = HCONV(nLineHeight);
7557     nLinePos = HCONV(nLinePos + nOffset);
7558     nLinePos2 = HCONV(nLinePos2 + nOffset);
7559 
7560     // outline attribute ?
7561     if (m_aCurrentPDFState.m_aFont.IsOutline() && eTextLine == LINESTYLE_SINGLE)
7562     {
7563         appendStrokingColor(aColor, aLine); // stroke with text color
7564         aLine.append( " " );
7565         appendNonStrokingColor(COL_WHITE, aLine); // fill with white
7566         aLine.append( "\n" );
7567         aLine.append( "0.25 w \n" ); // same line thickness as in drawLayout
7568 
7569         // draw rectangle instead
7570         aLine.append( "0 " );
7571         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7572         aLine.append( " " );
7573         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
7574         aLine.append( ' ' );
7575         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7576         aLine.append( " re h B\n" );
7577         return;
7578     }
7579 
7580 
7581     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7582     aLine.append( " w " );
7583     appendStrokingColor( aColor, aLine );
7584     aLine.append( "\n" );
7585 
7586     switch ( eTextLine )
7587     {
7588         case LINESTYLE_DOTTED:
7589         case LINESTYLE_BOLDDOTTED:
7590             aLine.append( "[ " );
7591             m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7592             aLine.append( " ] 0 d\n" );
7593             break;
7594         case LINESTYLE_DASH:
7595         case LINESTYLE_LONGDASH:
7596         case LINESTYLE_BOLDDASH:
7597         case LINESTYLE_BOLDLONGDASH:
7598             {
7599                 sal_Int32 nDashLength = 4*nLineHeight;
7600                 sal_Int32 nVoidLength = 2*nLineHeight;
7601                 if ( ( eTextLine == LINESTYLE_LONGDASH ) || ( eTextLine == LINESTYLE_BOLDLONGDASH ) )
7602                     nDashLength = 8*nLineHeight;
7603 
7604                 aLine.append( "[ " );
7605                 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7606                 aLine.append( ' ' );
7607                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7608                 aLine.append( " ] 0 d\n" );
7609             }
7610             break;
7611         case LINESTYLE_DASHDOT:
7612         case LINESTYLE_BOLDDASHDOT:
7613             {
7614                 sal_Int32 nDashLength = 4*nLineHeight;
7615                 sal_Int32 nVoidLength = 2*nLineHeight;
7616                 aLine.append( "[ " );
7617                 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7618                 aLine.append( ' ' );
7619                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7620                 aLine.append( ' ' );
7621                 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7622                 aLine.append( ' ' );
7623                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7624                 aLine.append( " ] 0 d\n" );
7625             }
7626             break;
7627         case LINESTYLE_DASHDOTDOT:
7628         case LINESTYLE_BOLDDASHDOTDOT:
7629             {
7630                 sal_Int32 nDashLength = 4*nLineHeight;
7631                 sal_Int32 nVoidLength = 2*nLineHeight;
7632                 aLine.append( "[ " );
7633                 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7634                 aLine.append( ' ' );
7635                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7636                 aLine.append( ' ' );
7637                 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7638                 aLine.append( ' ' );
7639                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7640                 aLine.append( ' ' );
7641                 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7642                 aLine.append( ' ' );
7643                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7644                 aLine.append( " ] 0 d\n" );
7645             }
7646             break;
7647         default:
7648             break;
7649     }
7650 
7651     aLine.append( "0 " );
7652     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7653     aLine.append( " m " );
7654     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
7655     aLine.append( ' ' );
7656     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7657     aLine.append( " l S\n" );
7658     if ( eTextLine == LINESTYLE_DOUBLE )
7659     {
7660         aLine.append( "0 " );
7661         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7662         aLine.append( " m " );
7663         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
7664         aLine.append( ' ' );
7665         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7666         aLine.append( " l S\n" );
7667     }
7668 
7669 }
7670 
7671 void PDFWriterImpl::drawStrikeoutLine( OStringBuffer& aLine, tools::Long nWidth, FontStrikeout eStrikeout, Color aColor )
7672 {
7673     // note: units in pFontInstance are ref device pixel
7674     const LogicalFontInstance*  pFontInstance = GetFontInstance();
7675     tools::Long            nLineHeight = 0;
7676     tools::Long            nLinePos  = 0;
7677     tools::Long            nLinePos2 = 0;
7678 
7679     if ( eStrikeout > STRIKEOUT_X )
7680         eStrikeout = STRIKEOUT_SINGLE;
7681 
7682     switch ( eStrikeout )
7683     {
7684         case STRIKEOUT_SINGLE:
7685             if ( !pFontInstance->mxFontMetric->GetStrikeoutSize() )
7686                 ImplInitTextLineSize();
7687             nLineHeight = pFontInstance->mxFontMetric->GetStrikeoutSize();
7688             nLinePos    = pFontInstance->mxFontMetric->GetStrikeoutOffset();
7689             break;
7690         case STRIKEOUT_BOLD:
7691             if ( !pFontInstance->mxFontMetric->GetBoldStrikeoutSize() )
7692                 ImplInitTextLineSize();
7693             nLineHeight = pFontInstance->mxFontMetric->GetBoldStrikeoutSize();
7694             nLinePos    = pFontInstance->mxFontMetric->GetBoldStrikeoutOffset();
7695             break;
7696         case STRIKEOUT_DOUBLE:
7697             if ( !pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() )
7698                 ImplInitTextLineSize();
7699             nLineHeight = pFontInstance->mxFontMetric->GetDoubleStrikeoutSize();
7700             nLinePos    = pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1();
7701             nLinePos2   = pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2();
7702             break;
7703         default:
7704             break;
7705     }
7706 
7707     if ( !nLineHeight )
7708         return;
7709 
7710     // tdf#154235
7711     // nLinePos/nLinePos2 is the distance from baseline to the bottom of the line,
7712     // while in PDF we stroke the line so the position is to the middle of the
7713     // line, we add half of nLineHeight to account for that.
7714     nLinePos = HCONV(nLinePos + nLineHeight / 2);
7715     nLinePos2 = HCONV(nLinePos2 + nLineHeight / 2);
7716     nLineHeight = HCONV(nLineHeight);
7717 
7718     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7719     aLine.append( " w " );
7720     appendStrokingColor( aColor, aLine );
7721     aLine.append( "\n" );
7722 
7723     aLine.append( "0 " );
7724     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7725     aLine.append( " m " );
7726     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
7727     aLine.append( ' ' );
7728     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7729     aLine.append( " l S\n" );
7730 
7731     if ( eStrikeout == STRIKEOUT_DOUBLE )
7732     {
7733         aLine.append( "0 " );
7734         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7735         aLine.append( " m " );
7736         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
7737         aLine.append( ' ' );
7738         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7739         aLine.append( " l S\n" );
7740     }
7741 
7742 }
7743 
7744 void PDFWriterImpl::drawStrikeoutChar( const Point& rPos, tools::Long nWidth, FontStrikeout eStrikeout )
7745 {
7746     //See qadevOOo/testdocs/StrikeThrough.odt for examples if you need
7747     //to tweak this
7748 
7749     OUString aStrikeoutChar = eStrikeout == STRIKEOUT_SLASH ? OUString( "/" ) : OUString( "X" );
7750     OUString aStrikeout = aStrikeoutChar;
7751     while( GetTextWidth( aStrikeout ) < nWidth )
7752         aStrikeout += aStrikeout;
7753 
7754     // do not get broader than nWidth modulo 1 character
7755     while( GetTextWidth( aStrikeout ) >= nWidth )
7756         aStrikeout = aStrikeout.copy(1);
7757     aStrikeout += aStrikeoutChar;
7758     bool bShadow = m_aCurrentPDFState.m_aFont.IsShadow();
7759     if ( bShadow )
7760     {
7761         Font aFont = m_aCurrentPDFState.m_aFont;
7762         aFont.SetShadow( false );
7763         setFont( aFont );
7764         updateGraphicsState();
7765     }
7766 
7767     // strikeout string is left aligned non-CTL text
7768     vcl::text::ComplexTextLayoutFlags nOrigTLM = GetLayoutMode();
7769     SetLayoutMode(vcl::text::ComplexTextLayoutFlags::BiDiStrong);
7770 
7771     push( PushFlags::CLIPREGION );
7772     FontMetric aRefDevFontMetric = GetFontMetric();
7773     tools::Rectangle aRect;
7774     aRect.SetLeft( rPos.X() );
7775     aRect.SetRight( aRect.Left()+nWidth );
7776     aRect.SetBottom( rPos.Y()+aRefDevFontMetric.GetDescent() );
7777     aRect.SetTop( rPos.Y()-aRefDevFontMetric.GetAscent() );
7778 
7779     const LogicalFontInstance* pFontInstance = GetFontInstance();
7780     if (pFontInstance->mnOrientation)
7781     {
7782         tools::Polygon aPoly( aRect );
7783         aPoly.Rotate( rPos, pFontInstance->mnOrientation);
7784         aRect = aPoly.GetBoundRect();
7785     }
7786 
7787     intersectClipRegion( aRect );
7788     drawText( rPos, aStrikeout, 0, aStrikeout.getLength(), false );
7789     pop();
7790 
7791     SetLayoutMode( nOrigTLM );
7792 
7793     if ( bShadow )
7794     {
7795         Font aFont = m_aCurrentPDFState.m_aFont;
7796         aFont.SetShadow( true );
7797         setFont( aFont );
7798         updateGraphicsState();
7799     }
7800 }
7801 
7802 void PDFWriterImpl::drawTextLine( const Point& rPos, tools::Long nWidth, FontStrikeout eStrikeout, FontLineStyle eUnderline, FontLineStyle eOverline, bool bUnderlineAbove )
7803 {
7804     if ( !nWidth ||
7805          ( ((eStrikeout == STRIKEOUT_NONE)||(eStrikeout == STRIKEOUT_DONTKNOW)) &&
7806            ((eUnderline == LINESTYLE_NONE)||(eUnderline == LINESTYLE_DONTKNOW)) &&
7807            ((eOverline  == LINESTYLE_NONE)||(eOverline  == LINESTYLE_DONTKNOW)) ) )
7808         return;
7809 
7810     MARK( "drawTextLine" );
7811     updateGraphicsState();
7812 
7813     // note: units in pFontInstance are ref device pixel
7814     const LogicalFontInstance* pFontInstance = GetFontInstance();
7815     Color           aUnderlineColor = m_aCurrentPDFState.m_aTextLineColor;
7816     Color           aOverlineColor  = m_aCurrentPDFState.m_aOverlineColor;
7817     Color           aStrikeoutColor = m_aCurrentPDFState.m_aFont.GetColor();
7818     bool            bStrikeoutDone = false;
7819     bool            bUnderlineDone = false;
7820     bool            bOverlineDone  = false;
7821 
7822     if ( (eStrikeout == STRIKEOUT_SLASH) || (eStrikeout == STRIKEOUT_X) )
7823     {
7824         drawStrikeoutChar( rPos, nWidth, eStrikeout );
7825         bStrikeoutDone = true;
7826     }
7827 
7828     Point aPos( rPos );
7829     TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
7830     if( eAlign == ALIGN_TOP )
7831         aPos.AdjustY(HCONV( pFontInstance->mxFontMetric->GetAscent() ));
7832     else if( eAlign == ALIGN_BOTTOM )
7833         aPos.AdjustY( -HCONV( pFontInstance->mxFontMetric->GetDescent() ) );
7834 
7835     OStringBuffer aLine( 512 );
7836     // save GS
7837     aLine.append( "q " );
7838 
7839     // rotate and translate matrix
7840     double fAngle = toRadians(m_aCurrentPDFState.m_aFont.GetOrientation());
7841     Matrix3 aMat;
7842     aMat.rotate( fAngle );
7843     aMat.translate( aPos.X(), aPos.Y() );
7844     m_aPages.back().appendMatrix3(aMat, aLine);
7845     aLine.append( " cm\n" );
7846 
7847     if ( aUnderlineColor.IsTransparent() )
7848         aUnderlineColor = aStrikeoutColor;
7849 
7850     if ( aOverlineColor.IsTransparent() )
7851         aOverlineColor = aStrikeoutColor;
7852 
7853     if ( (eUnderline == LINESTYLE_SMALLWAVE) ||
7854          (eUnderline == LINESTYLE_WAVE) ||
7855          (eUnderline == LINESTYLE_DOUBLEWAVE) ||
7856          (eUnderline == LINESTYLE_BOLDWAVE) )
7857     {
7858         drawWaveTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
7859         bUnderlineDone = true;
7860     }
7861 
7862     if ( (eOverline == LINESTYLE_SMALLWAVE) ||
7863          (eOverline == LINESTYLE_WAVE) ||
7864          (eOverline == LINESTYLE_DOUBLEWAVE) ||
7865          (eOverline == LINESTYLE_BOLDWAVE) )
7866     {
7867         drawWaveTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
7868         bOverlineDone = true;
7869     }
7870 
7871     if ( !bUnderlineDone )
7872     {
7873         drawStraightTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
7874     }
7875 
7876     if ( !bOverlineDone )
7877     {
7878         drawStraightTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
7879     }
7880 
7881     if ( !bStrikeoutDone )
7882     {
7883         drawStrikeoutLine( aLine, nWidth, eStrikeout, aStrikeoutColor );
7884     }
7885 
7886     aLine.append( "Q\n" );
7887     writeBuffer( aLine );
7888 }
7889 
7890 void PDFWriterImpl::drawPolygon( const tools::Polygon& rPoly )
7891 {
7892     MARK( "drawPolygon" );
7893 
7894     updateGraphicsState();
7895 
7896     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7897         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7898         return;
7899 
7900     int nPoints = rPoly.GetSize();
7901     OStringBuffer aLine( 20 * nPoints );
7902     m_aPages.back().appendPolygon( rPoly, aLine );
7903     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7904         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7905         aLine.append( "B*\n" );
7906     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7907         aLine.append( "S\n" );
7908     else
7909         aLine.append( "f*\n" );
7910 
7911     writeBuffer( aLine );
7912 }
7913 
7914 void PDFWriterImpl::drawPolyPolygon( const tools::PolyPolygon& rPolyPoly )
7915 {
7916     MARK( "drawPolyPolygon" );
7917 
7918     updateGraphicsState();
7919 
7920     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7921         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7922         return;
7923 
7924     int nPolygons = rPolyPoly.Count();
7925 
7926     OStringBuffer aLine( 40 * nPolygons );
7927     m_aPages.back().appendPolyPolygon( rPolyPoly, aLine );
7928     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7929         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7930         aLine.append( "B*\n" );
7931     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7932         aLine.append( "S\n" );
7933     else
7934         aLine.append( "f*\n" );
7935 
7936     writeBuffer( aLine );
7937 }
7938 
7939 void PDFWriterImpl::drawTransparent( const tools::PolyPolygon& rPolyPoly, sal_uInt32 nTransparentPercent )
7940 {
7941     SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
7942     nTransparentPercent = nTransparentPercent % 100;
7943 
7944     MARK( "drawTransparent" );
7945 
7946     updateGraphicsState();
7947 
7948     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7949         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7950         return;
7951 
7952     if( m_bIsPDF_A1 || m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
7953     {
7954         m_aErrors.insert( m_bIsPDF_A1 ?
7955                           PDFWriter::Warning_Transparency_Omitted_PDFA :
7956                           PDFWriter::Warning_Transparency_Omitted_PDF13 );
7957 
7958         drawPolyPolygon( rPolyPoly );
7959         return;
7960     }
7961 
7962     // create XObject
7963     m_aTransparentObjects.emplace_back( );
7964     // FIXME: polygons with beziers may yield incorrect bound rect
7965     m_aTransparentObjects.back().m_aBoundRect     = rPolyPoly.GetBoundRect();
7966     // convert rectangle to default user space
7967     m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
7968     m_aTransparentObjects.back().m_nObject          = createObject();
7969     m_aTransparentObjects.back().m_nExtGStateObject = createObject();
7970     m_aTransparentObjects.back().m_fAlpha           = static_cast<double>(100-nTransparentPercent) / 100.0;
7971     m_aTransparentObjects.back().m_pContentStream.reset(new SvMemoryStream( 256, 256 ));
7972     // create XObject's content stream
7973     OStringBuffer aContent( 256 );
7974     m_aPages.back().appendPolyPolygon( rPolyPoly, aContent );
7975     if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT &&
7976         m_aCurrentPDFState.m_aFillColor != COL_TRANSPARENT )
7977         aContent.append( " B*\n" );
7978     else if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT )
7979         aContent.append( " S\n" );
7980     else
7981         aContent.append( " f*\n" );
7982     m_aTransparentObjects.back().m_pContentStream->WriteBytes(
7983         aContent.getStr(), aContent.getLength() );
7984 
7985     OStringBuffer aObjName( 16 );
7986     aObjName.append( "Tr" );
7987     aObjName.append( m_aTransparentObjects.back().m_nObject );
7988     OString aTrName( aObjName.makeStringAndClear() );
7989     aObjName.append( "EGS" );
7990     aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
7991     OString aExtName( aObjName.makeStringAndClear() );
7992 
7993     OString aLine =
7994     // insert XObject
7995         "q /" +
7996         aExtName +
7997         " gs /" +
7998         aTrName +
7999         " Do Q\n";
8000     writeBuffer( aLine );
8001 
8002     pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject );
8003     pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
8004 }
8005 
8006 void PDFWriterImpl::pushResource(ResourceKind eKind, const OString& rResource, sal_Int32 nObject, ResourceDict& rResourceDict, std::list<StreamRedirect>& rOutputStreams)
8007 {
8008     if( nObject < 0 )
8009         return;
8010 
8011     switch( eKind )
8012     {
8013         case ResourceKind::XObject:
8014             rResourceDict.m_aXObjects[rResource] = nObject;
8015             if (!rOutputStreams.empty())
8016                 rOutputStreams.front().m_aResourceDict.m_aXObjects[rResource] = nObject;
8017             break;
8018         case ResourceKind::ExtGState:
8019             rResourceDict.m_aExtGStates[rResource] = nObject;
8020             if (!rOutputStreams.empty())
8021                 rOutputStreams.front().m_aResourceDict.m_aExtGStates[rResource] = nObject;
8022             break;
8023         case ResourceKind::Shading:
8024             rResourceDict.m_aShadings[rResource] = nObject;
8025             if (!rOutputStreams.empty())
8026                 rOutputStreams.front().m_aResourceDict.m_aShadings[rResource] = nObject;
8027             break;
8028         case ResourceKind::Pattern:
8029             rResourceDict.m_aPatterns[rResource] = nObject;
8030             if (!rOutputStreams.empty())
8031                 rOutputStreams.front().m_aResourceDict.m_aPatterns[rResource] = nObject;
8032             break;
8033     }
8034 }
8035 
8036 void PDFWriterImpl::pushResource( ResourceKind eKind, const OString& rResource, sal_Int32 nObject )
8037 {
8038     pushResource(eKind, rResource, nObject, m_aGlobalResourceDict, m_aOutputStreams);
8039 }
8040 
8041 void PDFWriterImpl::beginRedirect( SvStream* pStream, const tools::Rectangle& rTargetRect )
8042 {
8043     push( PushFlags::ALL );
8044 
8045     // force reemitting clip region inside the new stream, and
8046     // prevent emitting an unbalanced "Q" at the start
8047     clearClipRegion();
8048     // this is needed to point m_aCurrentPDFState at the pushed state
8049     // ... but it's pointless to actually write into the "outer" stream here!
8050     updateGraphicsState(Mode::NOWRITE);
8051 
8052     m_aOutputStreams.push_front( StreamRedirect() );
8053     m_aOutputStreams.front().m_pStream = pStream;
8054     m_aOutputStreams.front().m_aMapMode = m_aMapMode;
8055 
8056     if( !rTargetRect.IsEmpty() )
8057     {
8058         m_aOutputStreams.front().m_aTargetRect =
8059             lcl_convert( m_aGraphicsStack.front().m_aMapMode,
8060                          m_aMapMode,
8061                          this,
8062                          rTargetRect );
8063         Point aDelta = m_aOutputStreams.front().m_aTargetRect.BottomLeft();
8064         tools::Long nPageHeight = pointToPixel(m_aPages[m_nCurrentPage].getHeight());
8065         aDelta.setY( -(nPageHeight - m_aOutputStreams.front().m_aTargetRect.Bottom()) );
8066         m_aMapMode.SetOrigin( m_aMapMode.GetOrigin() + aDelta );
8067     }
8068 
8069     // setup graphics state for independent object stream
8070 
8071     // force reemitting colors
8072     m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
8073     m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
8074 }
8075 
8076 SvStream* PDFWriterImpl::endRedirect()
8077 {
8078     SvStream* pStream = nullptr;
8079     if( ! m_aOutputStreams.empty() )
8080     {
8081         pStream     = m_aOutputStreams.front().m_pStream;
8082         m_aMapMode  = m_aOutputStreams.front().m_aMapMode;
8083         m_aOutputStreams.pop_front();
8084     }
8085 
8086     pop();
8087 
8088     m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
8089     m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
8090 
8091     // needed after pop() to set m_aCurrentPDFState
8092     updateGraphicsState(Mode::NOWRITE);
8093 
8094     return pStream;
8095 }
8096 
8097 void PDFWriterImpl::beginTransparencyGroup()
8098 {
8099     updateGraphicsState();
8100     if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
8101         beginRedirect( new SvMemoryStream( 1024, 1024 ), tools::Rectangle() );
8102 }
8103 
8104 void PDFWriterImpl::endTransparencyGroup( const tools::Rectangle& rBoundingBox, sal_uInt32 nTransparentPercent )
8105 {
8106     SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
8107     nTransparentPercent = nTransparentPercent % 100;
8108 
8109     if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
8110         return;
8111 
8112     // create XObject
8113     m_aTransparentObjects.emplace_back( );
8114     m_aTransparentObjects.back().m_aBoundRect   = rBoundingBox;
8115     // convert rectangle to default user space
8116     m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
8117     m_aTransparentObjects.back().m_nObject      = createObject();
8118     m_aTransparentObjects.back().m_fAlpha       = static_cast<double>(100-nTransparentPercent) / 100.0;
8119     // get XObject's content stream
8120     m_aTransparentObjects.back().m_pContentStream.reset( static_cast<SvMemoryStream*>(endRedirect()) );
8121     m_aTransparentObjects.back().m_nExtGStateObject = createObject();
8122 
8123     OStringBuffer aObjName( 16 );
8124     aObjName.append( "Tr" );
8125     aObjName.append( m_aTransparentObjects.back().m_nObject );
8126     OString aTrName( aObjName.makeStringAndClear() );
8127     aObjName.append( "EGS" );
8128     aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
8129     OString aExtName( aObjName.makeStringAndClear() );
8130 
8131     OString aLine =
8132     // insert XObject
8133         "q /" +
8134         aExtName +
8135         " gs /" +
8136         aTrName +
8137         " Do Q\n";
8138     writeBuffer( aLine );
8139 
8140     pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject );
8141     pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
8142 
8143 }
8144 
8145 void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect )
8146 {
8147     MARK( "drawRectangle" );
8148 
8149     updateGraphicsState();
8150 
8151     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
8152         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
8153         return;
8154 
8155     OStringBuffer aLine( 40 );
8156     m_aPages.back().appendRect( rRect, aLine );
8157 
8158     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
8159         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
8160         aLine.append( " B*\n" );
8161     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
8162         aLine.append( " S\n" );
8163     else
8164         aLine.append( " f*\n" );
8165 
8166     writeBuffer( aLine );
8167 }
8168 
8169 void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound )
8170 {
8171     MARK( "drawRectangle with rounded edges" );
8172 
8173     if( !nHorzRound && !nVertRound )
8174         drawRectangle( rRect );
8175 
8176     updateGraphicsState();
8177 
8178     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
8179         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
8180         return;
8181 
8182     if( nHorzRound > static_cast<sal_uInt32>(rRect.GetWidth())/2 )
8183         nHorzRound = rRect.GetWidth()/2;
8184     if( nVertRound > static_cast<sal_uInt32>(rRect.GetHeight())/2 )
8185         nVertRound = rRect.GetHeight()/2;
8186 
8187     Point aPoints[16];
8188     const double kappa = 0.5522847498;
8189     const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(nHorzRound))+0.5);
8190     const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(nVertRound))+0.5);
8191 
8192     aPoints[1]  = Point( rRect.Left() + nHorzRound, rRect.Top() );
8193     aPoints[0]  = Point( aPoints[1].X() - kx, aPoints[1].Y() );
8194     aPoints[2]  = Point( rRect.Right()+1 - nHorzRound, aPoints[1].Y() );
8195     aPoints[3]  = Point( aPoints[2].X()+kx, aPoints[2].Y() );
8196 
8197     aPoints[5]  = Point( rRect.Right()+1, rRect.Top()+nVertRound );
8198     aPoints[4]  = Point( aPoints[5].X(), aPoints[5].Y()-ky );
8199     aPoints[6]  = Point( aPoints[5].X(), rRect.Bottom()+1 - nVertRound );
8200     aPoints[7]  = Point( aPoints[6].X(), aPoints[6].Y()+ky );
8201 
8202     aPoints[9]  = Point( rRect.Right()+1-nHorzRound, rRect.Bottom()+1 );
8203     aPoints[8]  = Point( aPoints[9].X()+kx, aPoints[9].Y() );
8204     aPoints[10] = Point( rRect.Left() + nHorzRound, aPoints[9].Y() );
8205     aPoints[11] = Point( aPoints[10].X()-kx, aPoints[10].Y() );
8206 
8207     aPoints[13] = Point( rRect.Left(), rRect.Bottom()+1-nVertRound );
8208     aPoints[12] = Point( aPoints[13].X(), aPoints[13].Y()+ky );
8209     aPoints[14] = Point( rRect.Left(), rRect.Top()+nVertRound );
8210     aPoints[15] = Point( aPoints[14].X(), aPoints[14].Y()-ky );
8211 
8212     OStringBuffer aLine( 80 );
8213     m_aPages.back().appendPoint( aPoints[1], aLine );
8214     aLine.append( " m " );
8215     m_aPages.back().appendPoint( aPoints[2], aLine );
8216     aLine.append( " l " );
8217     m_aPages.back().appendPoint( aPoints[3], aLine );
8218     aLine.append( ' ' );
8219     m_aPages.back().appendPoint( aPoints[4], aLine );
8220     aLine.append( ' ' );
8221     m_aPages.back().appendPoint( aPoints[5], aLine );
8222     aLine.append( " c\n" );
8223     m_aPages.back().appendPoint( aPoints[6], aLine );
8224     aLine.append( " l " );
8225     m_aPages.back().appendPoint( aPoints[7], aLine );
8226     aLine.append( ' ' );
8227     m_aPages.back().appendPoint( aPoints[8], aLine );
8228     aLine.append( ' ' );
8229     m_aPages.back().appendPoint( aPoints[9], aLine );
8230     aLine.append( " c\n" );
8231     m_aPages.back().appendPoint( aPoints[10], aLine );
8232     aLine.append( " l " );
8233     m_aPages.back().appendPoint( aPoints[11], aLine );
8234     aLine.append( ' ' );
8235     m_aPages.back().appendPoint( aPoints[12], aLine );
8236     aLine.append( ' ' );
8237     m_aPages.back().appendPoint( aPoints[13], aLine );
8238     aLine.append( " c\n" );
8239     m_aPages.back().appendPoint( aPoints[14], aLine );
8240     aLine.append( " l " );
8241     m_aPages.back().appendPoint( aPoints[15], aLine );
8242     aLine.append( ' ' );
8243     m_aPages.back().appendPoint( aPoints[0], aLine );
8244     aLine.append( ' ' );
8245     m_aPages.back().appendPoint( aPoints[1], aLine );
8246     aLine.append( " c " );
8247 
8248     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
8249         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
8250         aLine.append( "b*\n" );
8251     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
8252         aLine.append( "s\n" );
8253     else
8254         aLine.append( "f*\n" );
8255 
8256     writeBuffer( aLine );
8257 }
8258 
8259 void PDFWriterImpl::drawEllipse( const tools::Rectangle& rRect )
8260 {
8261     MARK( "drawEllipse" );
8262 
8263     updateGraphicsState();
8264 
8265     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
8266         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
8267         return;
8268 
8269     Point aPoints[12];
8270     const double kappa = 0.5522847498;
8271     const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetWidth())/2.0)+0.5);
8272     const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetHeight())/2.0)+0.5);
8273 
8274     aPoints[1]  = Point( rRect.Left() + rRect.GetWidth()/2, rRect.Top() );
8275     aPoints[0]  = Point( aPoints[1].X() - kx, aPoints[1].Y() );
8276     aPoints[2]  = Point( aPoints[1].X() + kx, aPoints[1].Y() );
8277 
8278     aPoints[4]  = Point( rRect.Right()+1, rRect.Top() + rRect.GetHeight()/2 );
8279     aPoints[3]  = Point( aPoints[4].X(), aPoints[4].Y() - ky );
8280     aPoints[5]  = Point( aPoints[4].X(), aPoints[4].Y() + ky );
8281 
8282     aPoints[7]  = Point( rRect.Left() + rRect.GetWidth()/2, rRect.Bottom()+1 );
8283     aPoints[6]  = Point( aPoints[7].X() + kx, aPoints[7].Y() );
8284     aPoints[8]  = Point( aPoints[7].X() - kx, aPoints[7].Y() );
8285 
8286     aPoints[10] = Point( rRect.Left(), rRect.Top() + rRect.GetHeight()/2 );
8287     aPoints[9]  = Point( aPoints[10].X(), aPoints[10].Y() + ky );
8288     aPoints[11] = Point( aPoints[10].X(), aPoints[10].Y() - ky );
8289 
8290     OStringBuffer aLine( 80 );
8291     m_aPages.back().appendPoint( aPoints[1], aLine );
8292     aLine.append( " m " );
8293     m_aPages.back().appendPoint( aPoints[2], aLine );
8294     aLine.append( ' ' );
8295     m_aPages.back().appendPoint( aPoints[3], aLine );
8296     aLine.append( ' ' );
8297     m_aPages.back().appendPoint( aPoints[4], aLine );
8298     aLine.append( " c\n" );
8299     m_aPages.back().appendPoint( aPoints[5], aLine );
8300     aLine.append( ' ' );
8301     m_aPages.back().appendPoint( aPoints[6], aLine );
8302     aLine.append( ' ' );
8303     m_aPages.back().appendPoint( aPoints[7], aLine );
8304     aLine.append( " c\n" );
8305     m_aPages.back().appendPoint( aPoints[8], aLine );
8306     aLine.append( ' ' );
8307     m_aPages.back().appendPoint( aPoints[9], aLine );
8308     aLine.append( ' ' );
8309     m_aPages.back().appendPoint( aPoints[10], aLine );
8310     aLine.append( " c\n" );
8311     m_aPages.back().appendPoint( aPoints[11], aLine );
8312     aLine.append( ' ' );
8313     m_aPages.back().appendPoint( aPoints[0], aLine );
8314     aLine.append( ' ' );
8315     m_aPages.back().appendPoint( aPoints[1], aLine );
8316     aLine.append( " c " );
8317 
8318     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
8319         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
8320         aLine.append( "b*\n" );
8321     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
8322         aLine.append( "s\n" );
8323     else
8324         aLine.append( "f*\n" );
8325 
8326     writeBuffer( aLine );
8327 }
8328 
8329 static double calcAngle( const tools::Rectangle& rRect, const Point& rPoint )
8330 {
8331     Point aOrigin((rRect.Left()+rRect.Right()+1)/2,
8332                   (rRect.Top()+rRect.Bottom()+1)/2);
8333     Point aPoint = rPoint - aOrigin;
8334 
8335     double fX = static_cast<double>(aPoint.X());
8336     double fY = static_cast<double>(-aPoint.Y());
8337 
8338     if ((rRect.GetHeight() == 0) || (rRect.GetWidth() == 0))
8339         throw o3tl::divide_by_zero();
8340 
8341     if( rRect.GetWidth() > rRect.GetHeight() )
8342         fY = fY*(static_cast<double>(rRect.GetWidth())/static_cast<double>(rRect.GetHeight()));
8343     else if( rRect.GetHeight() > rRect.GetWidth() )
8344         fX = fX*(static_cast<double>(rRect.GetHeight())/static_cast<double>(rRect.GetWidth()));
8345     return atan2( fY, fX );
8346 }
8347 
8348 void PDFWriterImpl::drawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop, bool bWithPie, bool bWithChord )
8349 {
8350     MARK( "drawArc" );
8351 
8352     updateGraphicsState();
8353 
8354     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
8355         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
8356         return;
8357 
8358     // calculate start and stop angles
8359     const double fStartAngle = calcAngle( rRect, rStart );
8360     double fStopAngle  = calcAngle( rRect, rStop );
8361     while( fStopAngle < fStartAngle )
8362         fStopAngle += 2.0*M_PI;
8363     const int nFragments = static_cast<int>((fStopAngle-fStartAngle)/(M_PI/2.0))+1;
8364     const double fFragmentDelta = (fStopAngle-fStartAngle)/static_cast<double>(nFragments);
8365     const double kappa = fabs( 4.0 * (1.0-cos(fFragmentDelta/2.0))/sin(fFragmentDelta/2.0) / 3.0);
8366     const double halfWidth = static_cast<double>(rRect.GetWidth())/2.0;
8367     const double halfHeight = static_cast<double>(rRect.GetHeight())/2.0;
8368 
8369     const Point aCenter( (rRect.Left()+rRect.Right()+1)/2,
8370                          (rRect.Top()+rRect.Bottom()+1)/2 );
8371 
8372     OStringBuffer aLine( 30*nFragments );
8373     Point aPoint( static_cast<int>(halfWidth * cos(fStartAngle) ),
8374                   -static_cast<int>(halfHeight * sin(fStartAngle) ) );
8375     aPoint += aCenter;
8376     m_aPages.back().appendPoint( aPoint, aLine );
8377     aLine.append( " m " );
8378     if( !basegfx::fTools::equal(fStartAngle, fStopAngle) )
8379     {
8380         for( int i = 0; i < nFragments; i++ )
8381         {
8382             const double fStartFragment = fStartAngle + static_cast<double>(i)*fFragmentDelta;
8383             const double fStopFragment = fStartFragment + fFragmentDelta;
8384             aPoint = Point( static_cast<int>(halfWidth * (cos(fStartFragment) - kappa*sin(fStartFragment) ) ),
8385                             -static_cast<int>(halfHeight * (sin(fStartFragment) + kappa*cos(fStartFragment) ) ) );
8386             aPoint += aCenter;
8387             m_aPages.back().appendPoint( aPoint, aLine );
8388             aLine.append( ' ' );
8389 
8390             aPoint = Point( static_cast<int>(halfWidth * (cos(fStopFragment) + kappa*sin(fStopFragment) ) ),
8391                             -static_cast<int>(halfHeight * (sin(fStopFragment) - kappa*cos(fStopFragment) ) ) );
8392             aPoint += aCenter;
8393             m_aPages.back().appendPoint( aPoint, aLine );
8394             aLine.append( ' ' );
8395 
8396             aPoint = Point( static_cast<int>(halfWidth * cos(fStopFragment) ),
8397                             -static_cast<int>(halfHeight * sin(fStopFragment) ) );
8398             aPoint += aCenter;
8399             m_aPages.back().appendPoint( aPoint, aLine );
8400             aLine.append( " c\n" );
8401         }
8402     }
8403     if( bWithChord || bWithPie )
8404     {
8405         if( bWithPie )
8406         {
8407             m_aPages.back().appendPoint( aCenter, aLine );
8408             aLine.append( " l " );
8409         }
8410         aLine.append( "h " );
8411     }
8412     if( ! bWithChord && ! bWithPie )
8413         aLine.append( "S\n" );
8414     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
8415         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
8416         aLine.append( "B*\n" );
8417     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
8418         aLine.append( "S\n" );
8419     else
8420         aLine.append( "f*\n" );
8421 
8422     writeBuffer( aLine );
8423 }
8424 
8425 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly )
8426 {
8427     MARK( "drawPolyLine" );
8428 
8429     sal_uInt16 nPoints = rPoly.GetSize();
8430     if( nPoints < 2 )
8431         return;
8432 
8433     updateGraphicsState();
8434 
8435     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
8436         return;
8437 
8438     OStringBuffer aLine( 20 * nPoints );
8439     m_aPages.back().appendPolygon( rPoly, aLine, rPoly[0] == rPoly[nPoints-1] );
8440     aLine.append( "S\n" );
8441 
8442     writeBuffer( aLine );
8443 }
8444 
8445 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const LineInfo& rInfo )
8446 {
8447     MARK( "drawPolyLine with LineInfo" );
8448 
8449     updateGraphicsState();
8450 
8451     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
8452         return;
8453 
8454     OStringBuffer aLine;
8455     aLine.append( "q " );
8456     if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
8457     {
8458         writeBuffer( aLine );
8459         drawPolyLine( rPoly );
8460         writeBuffer( "Q\n" );
8461     }
8462     else
8463     {
8464         PDFWriter::ExtLineInfo aInfo;
8465         convertLineInfoToExtLineInfo( rInfo, aInfo );
8466         drawPolyLine( rPoly, aInfo );
8467     }
8468 }
8469 
8470 void PDFWriterImpl::convertLineInfoToExtLineInfo( const LineInfo& rIn, PDFWriter::ExtLineInfo& rOut )
8471 {
8472     SAL_WARN_IF( rIn.GetStyle() != LineStyle::Dash, "vcl.pdfwriter", "invalid conversion" );
8473     rOut.m_fLineWidth           = rIn.GetWidth();
8474     rOut.m_fTransparency        = 0.0;
8475     rOut.m_eCap                 = PDFWriter::capButt;
8476     rOut.m_eJoin                = PDFWriter::joinMiter;
8477     rOut.m_fMiterLimit          = 10;
8478     rOut.m_aDashArray           = rIn.GetDotDashArray();
8479 
8480     // add LineJoin
8481     switch(rIn.GetLineJoin())
8482     {
8483         case basegfx::B2DLineJoin::Bevel :
8484         {
8485             rOut.m_eJoin = PDFWriter::joinBevel;
8486             break;
8487         }
8488         // Pdf has no 'none' lineJoin, default is miter
8489         case basegfx::B2DLineJoin::NONE :
8490         case basegfx::B2DLineJoin::Miter :
8491         {
8492             rOut.m_eJoin = PDFWriter::joinMiter;
8493             break;
8494         }
8495         case basegfx::B2DLineJoin::Round :
8496         {
8497             rOut.m_eJoin = PDFWriter::joinRound;
8498             break;
8499         }
8500     }
8501 
8502     // add LineCap
8503     switch(rIn.GetLineCap())
8504     {
8505         default: /* css::drawing::LineCap_BUTT */
8506         {
8507             rOut.m_eCap = PDFWriter::capButt;
8508             break;
8509         }
8510         case css::drawing::LineCap_ROUND:
8511         {
8512             rOut.m_eCap = PDFWriter::capRound;
8513             break;
8514         }
8515         case css::drawing::LineCap_SQUARE:
8516         {
8517             rOut.m_eCap = PDFWriter::capSquare;
8518             break;
8519         }
8520     }
8521 }
8522 
8523 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const PDFWriter::ExtLineInfo& rInfo )
8524 {
8525     MARK( "drawPolyLine with ExtLineInfo" );
8526 
8527     updateGraphicsState();
8528 
8529     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
8530         return;
8531 
8532     if( rInfo.m_fTransparency >= 1.0 )
8533         return;
8534 
8535     if( rInfo.m_fTransparency != 0.0 )
8536         beginTransparencyGroup();
8537 
8538     OStringBuffer aLine;
8539     aLine.append( "q " );
8540     m_aPages.back().appendMappedLength( rInfo.m_fLineWidth, aLine );
8541     aLine.append( " w" );
8542     if( rInfo.m_aDashArray.size() < 10 ) // implementation limit of acrobat reader
8543     {
8544         switch( rInfo.m_eCap )
8545         {
8546             default:
8547             case PDFWriter::capButt:   aLine.append( " 0 J" );break;
8548             case PDFWriter::capRound:  aLine.append( " 1 J" );break;
8549             case PDFWriter::capSquare: aLine.append( " 2 J" );break;
8550         }
8551         switch( rInfo.m_eJoin )
8552         {
8553             default:
8554             case PDFWriter::joinMiter:
8555             {
8556                 double fLimit = rInfo.m_fMiterLimit;
8557                 if( rInfo.m_fLineWidth < rInfo.m_fMiterLimit )
8558                     fLimit = fLimit / rInfo.m_fLineWidth;
8559                 if( fLimit < 1.0 )
8560                     fLimit = 1.0;
8561                 aLine.append( " 0 j " );
8562                 appendDouble( fLimit, aLine );
8563                 aLine.append( " M" );
8564             }
8565             break;
8566             case PDFWriter::joinRound:  aLine.append( " 1 j" );break;
8567             case PDFWriter::joinBevel:  aLine.append( " 2 j" );break;
8568         }
8569         if( !rInfo.m_aDashArray.empty() )
8570         {
8571             aLine.append( " [ " );
8572             for (auto const& dash : rInfo.m_aDashArray)
8573             {
8574                 m_aPages.back().appendMappedLength( dash, aLine );
8575                 aLine.append( ' ' );
8576             }
8577             aLine.append( "] 0 d" );
8578         }
8579         aLine.append( "\n" );
8580         writeBuffer( aLine );
8581         drawPolyLine( rPoly );
8582     }
8583     else
8584     {
8585         basegfx::B2DPolygon aPoly(rPoly.getB2DPolygon());
8586         basegfx::B2DPolyPolygon aPolyPoly;
8587 
8588         basegfx::utils::applyLineDashing(aPoly, rInfo.m_aDashArray, &aPolyPoly);
8589 
8590         // Old applyLineDashing subdivided the polygon. New one will create bezier curve segments.
8591         // To mimic old behaviour, apply subdivide here. If beziers shall be written (better quality)
8592         // this line needs to be removed and the loop below adapted accordingly
8593         aPolyPoly = basegfx::utils::adaptiveSubdivideByAngle(aPolyPoly);
8594 
8595         const sal_uInt32 nPolygonCount(aPolyPoly.count());
8596 
8597         for( sal_uInt32 nPoly = 0; nPoly < nPolygonCount; nPoly++ )
8598         {
8599             aLine.append( (nPoly != 0 && (nPoly & 7) == 0) ? "\n" : " " );
8600             aPoly = aPolyPoly.getB2DPolygon( nPoly );
8601             const sal_uInt32 nPointCount(aPoly.count());
8602 
8603             if(nPointCount)
8604             {
8605                 const sal_uInt32 nEdgeCount(aPoly.isClosed() ? nPointCount : nPointCount - 1);
8606                 basegfx::B2DPoint aCurrent(aPoly.getB2DPoint(0));
8607 
8608                 for(sal_uInt32 a(0); a < nEdgeCount; a++)
8609                 {
8610                     if( a > 0 )
8611                         aLine.append( " " );
8612                     const sal_uInt32 nNextIndex((a + 1) % nPointCount);
8613                     const basegfx::B2DPoint aNext(aPoly.getB2DPoint(nNextIndex));
8614 
8615                     m_aPages.back().appendPoint( Point( FRound(aCurrent.getX()),
8616                                                         FRound(aCurrent.getY()) ),
8617                                                  aLine );
8618                     aLine.append( " m " );
8619                     m_aPages.back().appendPoint( Point( FRound(aNext.getX()),
8620                                                         FRound(aNext.getY()) ),
8621                                                  aLine );
8622                     aLine.append( " l" );
8623 
8624                     // prepare next edge
8625                     aCurrent = aNext;
8626                 }
8627             }
8628         }
8629         aLine.append( " S " );
8630         writeBuffer( aLine );
8631     }
8632     writeBuffer( "Q\n" );
8633 
8634     if( rInfo.m_fTransparency == 0.0 )
8635         return;
8636 
8637     // FIXME: actually this may be incorrect with bezier polygons
8638     tools::Rectangle aBoundRect( rPoly.GetBoundRect() );
8639     // avoid clipping with thick lines
8640     if( rInfo.m_fLineWidth > 0.0 )
8641     {
8642         sal_Int32 nLW = sal_Int32(rInfo.m_fLineWidth);
8643         aBoundRect.AdjustTop( -nLW );
8644         aBoundRect.AdjustLeft( -nLW );
8645         aBoundRect.AdjustRight(nLW );
8646         aBoundRect.AdjustBottom(nLW );
8647     }
8648     endTransparencyGroup( aBoundRect, static_cast<sal_uInt16>(100.0*rInfo.m_fTransparency) );
8649 }
8650 
8651 void PDFWriterImpl::drawPixel( const Point& rPoint, const Color& rColor )
8652 {
8653     MARK( "drawPixel" );
8654 
8655     Color aColor = ( rColor == COL_TRANSPARENT ? m_aGraphicsStack.front().m_aLineColor : rColor );
8656 
8657     if( aColor == COL_TRANSPARENT )
8658         return;
8659 
8660     // pixels are drawn in line color, so have to set
8661     // the nonstroking color to line color
8662     Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
8663     setFillColor( aColor );
8664 
8665     updateGraphicsState();
8666 
8667     OStringBuffer aLine( 20 );
8668     m_aPages.back().appendPoint( rPoint, aLine );
8669     aLine.append( ' ' );
8670     appendDouble( 1.0/double(GetDPIX()), aLine );
8671     aLine.append( ' ' );
8672     appendDouble( 1.0/double(GetDPIY()), aLine );
8673     aLine.append( " re f\n" );
8674     writeBuffer( aLine );
8675 
8676     setFillColor( aOldFillColor );
8677 }
8678 
8679 void PDFWriterImpl::writeTransparentObject( TransparencyEmit& rObject )
8680 {
8681     CHECK_RETURN2( updateObject( rObject.m_nObject ) );
8682 
8683     bool bFlateFilter = compressStream( rObject.m_pContentStream.get() );
8684     sal_uInt64 nSize = rObject.m_pContentStream->TellEnd();
8685     rObject.m_pContentStream->Seek( STREAM_SEEK_TO_BEGIN );
8686     if (g_bDebugDisableCompression)
8687     {
8688         emitComment( "PDFWriterImpl::writeTransparentObject" );
8689     }
8690     OStringBuffer aLine( 512 );
8691     CHECK_RETURN2( updateObject( rObject.m_nObject ) );
8692     aLine.append( rObject.m_nObject );
8693     aLine.append( " 0 obj\n"
8694                   "<</Type/XObject\n"
8695                   "/Subtype/Form\n"
8696                   "/BBox[ " );
8697     appendFixedInt( rObject.m_aBoundRect.Left(), aLine );
8698     aLine.append( ' ' );
8699     appendFixedInt( rObject.m_aBoundRect.Top(), aLine );
8700     aLine.append( ' ' );
8701     appendFixedInt( rObject.m_aBoundRect.Right(), aLine );
8702     aLine.append( ' ' );
8703     appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aLine );
8704     aLine.append( " ]\n" );
8705     if( ! m_bIsPDF_A1 )
8706     {
8707         // 7.8.3 Resource dicts are required for content streams
8708         aLine.append( "/Resources " );
8709         aLine.append( getResourceDictObj() );
8710         aLine.append( " 0 R\n" );
8711 
8712         aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/K true>>\n" );
8713     }
8714 
8715     aLine.append( "/Length " );
8716     aLine.append( static_cast<sal_Int32>(nSize) );
8717     aLine.append( "\n" );
8718     if( bFlateFilter )
8719         aLine.append( "/Filter/FlateDecode\n" );
8720     aLine.append( ">>\n"
8721                   "stream\n" );
8722     CHECK_RETURN2( writeBuffer( aLine ) );
8723     checkAndEnableStreamEncryption( rObject.m_nObject );
8724     CHECK_RETURN2( writeBufferBytes( rObject.m_pContentStream->GetData(), nSize ) );
8725     disableStreamEncryption();
8726     aLine.setLength( 0 );
8727     aLine.append( "\n"
8728                   "endstream\n"
8729                   "endobj\n\n" );
8730     CHECK_RETURN2( writeBuffer( aLine ) );
8731 
8732     // write ExtGState dict for this XObject
8733     aLine.setLength( 0 );
8734     aLine.append( rObject.m_nExtGStateObject );
8735     aLine.append( " 0 obj\n"
8736                   "<<" );
8737 
8738     if( m_bIsPDF_A1 )
8739     {
8740         aLine.append( "/CA 1.0/ca 1.0" );
8741         m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
8742     }
8743     else
8744     {
8745         aLine.append(  "/CA " );
8746         appendDouble( rObject.m_fAlpha, aLine );
8747         aLine.append( "\n"
8748                           "   /ca " );
8749         appendDouble( rObject.m_fAlpha, aLine );
8750     }
8751     aLine.append( "\n" );
8752 
8753     aLine.append( ">>\n"
8754                   "endobj\n\n" );
8755     CHECK_RETURN2( updateObject( rObject.m_nExtGStateObject ) );
8756     CHECK_RETURN2( writeBuffer( aLine ) );
8757 }
8758 
8759 bool PDFWriterImpl::writeGradientFunction( GradientEmit const & rObject )
8760 {
8761     // LO internal gradient -> PDF shading type:
8762     //  * css::awt::GradientStyle_LINEAR: axial shading, using sampled-function with 2 samples
8763     //                          [t=0:colorStart, t=1:colorEnd]
8764     //  * css::awt::GradientStyle_AXIAL: axial shading, using sampled-function with 3 samples
8765     //                          [t=0:colorEnd, t=0.5:colorStart, t=1:colorEnd]
8766     //  * other styles: function shading with aSize.Width() * aSize.Height() samples
8767     sal_Int32 nFunctionObject = createObject();
8768     CHECK_RETURN( updateObject( nFunctionObject ) );
8769 
8770     ScopedVclPtrInstance< VirtualDevice > aDev;
8771     aDev->SetOutputSizePixel( rObject.m_aSize );
8772     aDev->SetMapMode( MapMode( MapUnit::MapPixel ) );
8773     if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
8774         aDev->SetDrawMode( aDev->GetDrawMode() |
8775                           ( DrawModeFlags::GrayLine | DrawModeFlags::GrayFill | DrawModeFlags::GrayText |
8776                             DrawModeFlags::GrayBitmap | DrawModeFlags::GrayGradient ) );
8777     aDev->DrawGradient( tools::Rectangle( Point( 0, 0 ), rObject.m_aSize ), rObject.m_aGradient );
8778 
8779     Bitmap aSample = aDev->GetBitmap( Point( 0, 0 ), rObject.m_aSize );
8780     Bitmap::ScopedReadAccess pAccess(aSample);
8781 
8782     Size aSize = aSample.GetSizePixel();
8783 
8784     sal_Int32 nStreamLengthObject = createObject();
8785     if (g_bDebugDisableCompression)
8786     {
8787         emitComment( "PDFWriterImpl::writeGradientFunction" );
8788     }
8789     OStringBuffer aLine( 120 );
8790     aLine.append( nFunctionObject );
8791     aLine.append( " 0 obj\n"
8792                   "<</FunctionType 0\n");
8793     switch (rObject.m_aGradient.GetStyle())
8794     {
8795         case css::awt::GradientStyle_LINEAR:
8796         case css::awt::GradientStyle_AXIAL:
8797             aLine.append("/Domain[ 0 1]\n");
8798             break;
8799         default:
8800             aLine.append("/Domain[ 0 1 0 1]\n");
8801     }
8802     aLine.append("/Size[ " );
8803     switch (rObject.m_aGradient.GetStyle())
8804     {
8805         case css::awt::GradientStyle_LINEAR:
8806             aLine.append('2');
8807             break;
8808         case css::awt::GradientStyle_AXIAL:
8809             aLine.append('3');
8810             break;
8811         default:
8812             aLine.append( static_cast<sal_Int32>(aSize.Width()) );
8813             aLine.append( ' ' );
8814             aLine.append( static_cast<sal_Int32>(aSize.Height()) );
8815     }
8816     aLine.append( " ]\n"
8817                   "/BitsPerSample 8\n"
8818                   "/Range[ 0 1 0 1 0 1 ]\n"
8819                   "/Order 3\n"
8820                   "/Length " );
8821     aLine.append( nStreamLengthObject );
8822     if (!g_bDebugDisableCompression)
8823         aLine.append( " 0 R\n"
8824                       "/Filter/FlateDecode"
8825                       ">>\n"
8826                       "stream\n" );
8827     else
8828         aLine.append( " 0 R\n"
8829                       ">>\n"
8830                       "stream\n" );
8831     CHECK_RETURN( writeBuffer( aLine ) );
8832 
8833     sal_uInt64 nStartStreamPos = 0;
8834     CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nStartStreamPos)) );
8835 
8836     checkAndEnableStreamEncryption( nFunctionObject );
8837     beginCompression();
8838     sal_uInt8 aCol[3];
8839     switch (rObject.m_aGradient.GetStyle())
8840     {
8841         case css::awt::GradientStyle_AXIAL:
8842             aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
8843             aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
8844             aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
8845             CHECK_RETURN( writeBufferBytes( aCol, 3 ) );
8846             [[fallthrough]];
8847         case css::awt::GradientStyle_LINEAR:
8848         {
8849             aCol[0] = rObject.m_aGradient.GetStartColor().GetRed();
8850             aCol[1] = rObject.m_aGradient.GetStartColor().GetGreen();
8851             aCol[2] = rObject.m_aGradient.GetStartColor().GetBlue();
8852             CHECK_RETURN( writeBufferBytes( aCol, 3 ) );
8853 
8854             aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
8855             aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
8856             aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
8857             CHECK_RETURN( writeBufferBytes( aCol, 3 ) );
8858             break;
8859         }
8860         default:
8861             for( int y = aSize.Height()-1; y >= 0; y-- )
8862             {
8863                 for( tools::Long x = 0; x < aSize.Width(); x++ )
8864                 {
8865                     BitmapColor aColor = pAccess->GetColor( y, x );
8866                     aCol[0] = aColor.GetRed();
8867                     aCol[1] = aColor.GetGreen();
8868                     aCol[2] = aColor.GetBlue();
8869                     CHECK_RETURN( writeBufferBytes( aCol, 3 ) );
8870                 }
8871             }
8872     }
8873     endCompression();
8874     disableStreamEncryption();
8875 
8876     sal_uInt64 nEndStreamPos = 0;
8877     CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nEndStreamPos)) );
8878 
8879     aLine.setLength( 0 );
8880     aLine.append( "\nendstream\nendobj\n\n" );
8881     CHECK_RETURN( writeBuffer( aLine ) );
8882 
8883     // write stream length
8884     CHECK_RETURN( updateObject( nStreamLengthObject ) );
8885     aLine.setLength( 0 );
8886     aLine.append( nStreamLengthObject );
8887     aLine.append( " 0 obj\n" );
8888     aLine.append( static_cast<sal_Int64>(nEndStreamPos-nStartStreamPos) );
8889     aLine.append( "\nendobj\n\n" );
8890     CHECK_RETURN( writeBuffer( aLine ) );
8891 
8892     CHECK_RETURN( updateObject( rObject.m_nObject ) );
8893     aLine.setLength( 0 );
8894     aLine.append( rObject.m_nObject );
8895     aLine.append( " 0 obj\n");
8896     switch (rObject.m_aGradient.GetStyle())
8897     {
8898         case css::awt::GradientStyle_LINEAR:
8899         case css::awt::GradientStyle_AXIAL:
8900             aLine.append("<</ShadingType 2\n");
8901             break;
8902         default:
8903             aLine.append("<</ShadingType 1\n");
8904     }
8905     aLine.append("/ColorSpace/DeviceRGB\n"
8906                   "/AntiAlias true\n");
8907 
8908     // Determination of shading axis
8909     // See: OutputDevice::ImplDrawLinearGradient for reference
8910     tools::Rectangle aRect;
8911     aRect.SetLeft(0);
8912     aRect.SetTop(0);
8913     aRect.SetRight( aSize.Width() );
8914     aRect.SetBottom( aSize.Height() );
8915 
8916     tools::Rectangle aBoundRect;
8917     Point     aCenter;
8918     Degree10 nAngle = rObject.m_aGradient.GetAngle() % 3600_deg10;
8919     rObject.m_aGradient.GetBoundRect( aRect, aBoundRect, aCenter );
8920 
8921     const bool bLinear = (rObject.m_aGradient.GetStyle() == css::awt::GradientStyle_LINEAR);
8922     double fBorder = aBoundRect.GetHeight() * rObject.m_aGradient.GetBorder() / 100.0;
8923     if ( !bLinear )
8924     {
8925         fBorder /= 2.0;
8926     }
8927 
8928     aBoundRect.AdjustBottom( -fBorder );
8929     if (!bLinear)
8930     {
8931         aBoundRect.AdjustTop(fBorder );
8932     }
8933 
8934     switch (rObject.m_aGradient.GetStyle())
8935     {
8936         case css::awt::GradientStyle_LINEAR:
8937         case css::awt::GradientStyle_AXIAL:
8938         {
8939             aLine.append("/Domain[ 0 1 ]\n"
8940                     "/Coords[ " );
8941             tools::Polygon aPoly( 2 );
8942             aPoly[0] = aBoundRect.BottomCenter();
8943             aPoly[1] = aBoundRect.TopCenter();
8944             aPoly.Rotate( aCenter, 3600_deg10 - nAngle );
8945 
8946             aLine.append( static_cast<sal_Int32>(aPoly[0].X()) );
8947             aLine.append( " " );
8948             aLine.append( static_cast<sal_Int32>(aPoly[0].Y()) );
8949             aLine.append( " " );
8950             aLine.append( static_cast<sal_Int32>(aPoly[1].X()));
8951             aLine.append( " ");
8952             aLine.append( static_cast<sal_Int32>(aPoly[1].Y()));
8953             aLine.append( " ]\n");
8954             aLine.append("/Extend [true true]\n");
8955             break;
8956         }
8957         default:
8958             aLine.append("/Domain[ 0 1 0 1 ]\n"
8959                     "/Matrix[ " );
8960             aLine.append( static_cast<sal_Int32>(aSize.Width()) );
8961             aLine.append( " 0 0 " );
8962             aLine.append( static_cast<sal_Int32>(aSize.Height()) );
8963             aLine.append( " 0 0 ]\n");
8964     }
8965     aLine.append("/Function " );
8966     aLine.append( nFunctionObject );
8967     aLine.append( " 0 R\n"
8968                   ">>\n"
8969                   "endobj\n\n" );
8970     return writeBuffer( aLine );
8971 }
8972 
8973 void PDFWriterImpl::writeJPG( const JPGEmit& rObject )
8974 {
8975     if (rObject.m_aReferenceXObject.hasExternalPDFData() && !m_aContext.UseReferenceXObject)
8976     {
8977         writeReferenceXObject(rObject.m_aReferenceXObject);
8978         return;
8979     }
8980 
8981     CHECK_RETURN2( rObject.m_pStream );
8982     CHECK_RETURN2( updateObject( rObject.m_nObject ) );
8983 
8984     sal_uInt64 nLength = rObject.m_pStream->TellEnd();
8985     rObject.m_pStream->Seek( STREAM_SEEK_TO_BEGIN );
8986 
8987     sal_Int32 nMaskObject = 0;
8988     if( !rObject.m_aAlphaMask.IsEmpty() )
8989     {
8990         if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4
8991                 && !m_bIsPDF_A1)
8992         {
8993             nMaskObject = createObject();
8994         }
8995         else if( m_bIsPDF_A1 )
8996             m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
8997         else if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
8998             m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDF13 );
8999 
9000     }
9001     if (g_bDebugDisableCompression)
9002     {
9003         emitComment( "PDFWriterImpl::writeJPG" );
9004     }
9005 
9006     OStringBuffer aLine(200);
9007     aLine.append( rObject.m_nObject );
9008     aLine.append( " 0 obj\n"
9009                   "<</Type/XObject/Subtype/Image/Width " );
9010     aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Width()) );
9011     aLine.append( " /Height " );
9012     aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Height()) );
9013     aLine.append( " /BitsPerComponent 8 " );
9014     if( rObject.m_bTrueColor )
9015         aLine.append( "/ColorSpace/DeviceRGB" );
9016     else
9017         aLine.append( "/ColorSpace/DeviceGray" );
9018     aLine.append( "/Filter/DCTDecode/Length " );
9019     aLine.append( static_cast<sal_Int64>(nLength) );
9020     if( nMaskObject )
9021     {
9022         aLine.append(" /SMask ");
9023         aLine.append( nMaskObject );
9024         aLine.append( " 0 R " );
9025     }
9026     aLine.append( ">>\nstream\n" );
9027     CHECK_RETURN2( writeBuffer( aLine ) );
9028 
9029     checkAndEnableStreamEncryption( rObject.m_nObject );
9030     CHECK_RETURN2( writeBufferBytes( rObject.m_pStream->GetData(), nLength ) );
9031     disableStreamEncryption();
9032 
9033     aLine.setLength( 0 );
9034     CHECK_RETURN2( writeBuffer( "\nendstream\nendobj\n\n" ) );
9035 
9036     if( nMaskObject )
9037     {
9038         BitmapEmit aEmit;
9039         aEmit.m_nObject = nMaskObject;
9040         aEmit.m_aBitmap = BitmapEx( rObject.m_aAlphaMask, rObject.m_aAlphaMask );
9041         writeBitmapObject( aEmit, true );
9042     }
9043 
9044     writeReferenceXObject(rObject.m_aReferenceXObject);
9045 }
9046 
9047 void PDFWriterImpl::writeReferenceXObject(const ReferenceXObjectEmit& rEmit)
9048 {
9049     if (rEmit.m_nFormObject <= 0)
9050         return;
9051 
9052     // Count /Matrix and /BBox.
9053     // vcl::ImportPDF() uses getDefaultPdfResolutionDpi to set the desired
9054     // rendering DPI so we have to take into account that here too.
9055     static const double fResolutionDPI = vcl::pdf::getDefaultPdfResolutionDpi();
9056 
9057     sal_Int32 nOldDPIX = GetDPIX();
9058     sal_Int32 nOldDPIY = GetDPIY();
9059     SetDPIX(fResolutionDPI);
9060     SetDPIY(fResolutionDPI);
9061     Size aSize = PixelToLogic(rEmit.m_aPixelSize, MapMode(m_aMapMode.GetMapUnit()));
9062     SetDPIX(nOldDPIX);
9063     SetDPIY(nOldDPIY);
9064     double fScaleX = 1.0 / aSize.Width();
9065     double fScaleY = 1.0 / aSize.Height();
9066 
9067     sal_Int32 nWrappedFormObject = 0;
9068     if (!m_aContext.UseReferenceXObject)
9069     {
9070         // Parse the PDF data, we need that to write the PDF dictionary of our
9071         // object.
9072         if (rEmit.m_nExternalPDFDataIndex < 0)
9073             return;
9074         auto& rExternalPDFStream = m_aExternalPDFStreams.get(rEmit.m_nExternalPDFDataIndex);
9075         auto& pPDFDocument = rExternalPDFStream.getPDFDocument();
9076         if (!pPDFDocument)
9077         {
9078             // Couldn't parse the document and can't continue
9079             SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: failed to parse the document");
9080             return;
9081         }
9082 
9083         std::vector<filter::PDFObjectElement*> aPages = pPDFDocument->GetPages();
9084         if (aPages.empty())
9085         {
9086             SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no pages");
9087             return;
9088         }
9089 
9090         size_t nPageIndex = rEmit.m_nExternalPDFPageIndex >= 0 ? rEmit.m_nExternalPDFPageIndex : 0;
9091 
9092         filter::PDFObjectElement* pPage = aPages[nPageIndex];
9093         if (!pPage)
9094         {
9095             SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no page");
9096             return;
9097         }
9098 
9099         double aOrigin[2] = { 0.0, 0.0 };
9100         if (auto* pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("MediaBox")))
9101         {
9102             const auto& rElements = pArray->GetElements();
9103             if (rElements.size() >= 4)
9104             {
9105                 // get x1, y1 of the rectangle.
9106                 for (sal_Int32 nIdx = 0; nIdx < 2; ++nIdx)
9107                 {
9108                     if (const auto* pNumElement = dynamic_cast<filter::PDFNumberElement*>(rElements[nIdx]))
9109                         aOrigin[nIdx] = pNumElement->GetValue();
9110                 }
9111             }
9112         }
9113 
9114         std::vector<filter::PDFObjectElement*> aContentStreams;
9115         if (filter::PDFObjectElement* pContentStream = pPage->LookupObject("Contents"))
9116             aContentStreams.push_back(pContentStream);
9117         else if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Contents")))
9118         {
9119             for (const auto pElement : pArray->GetElements())
9120             {
9121                 auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
9122                 if (!pReference)
9123                     continue;
9124 
9125                 filter::PDFObjectElement* pObject = pReference->LookupObject();
9126                 if (!pObject)
9127                     continue;
9128 
9129                 aContentStreams.push_back(pObject);
9130             }
9131         }
9132 
9133         if (aContentStreams.empty())
9134         {
9135             SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no content stream");
9136             return;
9137         }
9138 
9139         // Merge link annotations from pPage to our page.
9140         std::vector<filter::PDFObjectElement*> aAnnots;
9141         if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Annots")))
9142         {
9143             for (const auto pElement : pArray->GetElements())
9144             {
9145                 auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
9146                 if (!pReference)
9147                 {
9148                     continue;
9149                 }
9150 
9151                 filter::PDFObjectElement* pObject = pReference->LookupObject();
9152                 if (!pObject)
9153                 {
9154                     continue;
9155                 }
9156 
9157                 auto pType = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Type"));
9158                 if (!pType || pType->GetValue() != "Annot")
9159                 {
9160                     continue;
9161                 }
9162 
9163                 auto pSubtype = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Subtype"));
9164                 if (!pSubtype || pSubtype->GetValue() != "Link")
9165                 {
9166                     continue;
9167                 }
9168 
9169                 // Reference to a link annotation object, remember it.
9170                 aAnnots.push_back(pObject);
9171             }
9172         }
9173         if (!aAnnots.empty())
9174         {
9175             PDFObjectCopier aCopier(*this);
9176             SvMemoryStream& rDocBuffer = pPage->GetDocument().GetEditBuffer();
9177             std::map<sal_Int32, sal_Int32> aMap;
9178             for (const auto& pAnnot : aAnnots)
9179             {
9180                 // Copy over the annotation and refer to its new id.
9181                 sal_Int32 nNewId = aCopier.copyExternalResource(rDocBuffer, *pAnnot, aMap);
9182                 m_aPages.back().m_aAnnotations.push_back(nNewId);
9183             }
9184         }
9185 
9186         nWrappedFormObject = createObject();
9187         // Write the form XObject wrapped below. This is a separate object from
9188         // the wrapper, this way there is no need to alter the stream contents.
9189 
9190         OStringBuffer aLine;
9191         aLine.append(nWrappedFormObject);
9192         aLine.append(" 0 obj\n");
9193         aLine.append("<< /Type /XObject");
9194         aLine.append(" /Subtype /Form");
9195 
9196         tools::Long nWidth = aSize.Width();
9197         tools::Long nHeight = aSize.Height();
9198         basegfx::B2DRange aBBox(0, 0, aSize.Width(),  aSize.Height());
9199         if (auto pRotate = dynamic_cast<filter::PDFNumberElement*>(pPage->Lookup("Rotate")))
9200         {
9201             // The original page was rotated, then construct a transformation matrix which does the
9202             // same with our form object.
9203             sal_Int32 nRotAngle = static_cast<sal_Int32>(pRotate->GetValue()) % 360;
9204             // /Rotate is clockwise, matrix rotate is counter-clockwise.
9205             sal_Int32 nAngle = -1 * nRotAngle;
9206 
9207             // The bounding box just rotates.
9208             basegfx::B2DHomMatrix aBBoxMat;
9209             aBBoxMat.rotate(basegfx::deg2rad(pRotate->GetValue()));
9210             aBBox.transform(aBBoxMat);
9211 
9212             // Now transform the object: rotate around the center and make sure that the rotation
9213             // doesn't affect the aspect ratio.
9214             basegfx::B2DHomMatrix aMat;
9215             aMat.translate(-0.5 * aBBox.getWidth() - aOrigin[0], -0.5 * aBBox.getHeight() - aOrigin[1]);
9216             aMat.rotate(basegfx::deg2rad(nAngle));
9217             aMat.translate(0.5 * nWidth, 0.5 * nHeight);
9218 
9219             aLine.append(" /Matrix [ ");
9220             aLine.append(aMat.a());
9221             aLine.append(" ");
9222             aLine.append(aMat.b());
9223             aLine.append(" ");
9224             aLine.append(aMat.c());
9225             aLine.append(" ");
9226             aLine.append(aMat.d());
9227             aLine.append(" ");
9228             aLine.append(aMat.e());
9229             aLine.append(" ");
9230             aLine.append(aMat.f());
9231             aLine.append(" ] ");
9232         }
9233 
9234         PDFObjectCopier aCopier(*this);
9235         auto & rResources = rExternalPDFStream.getCopiedResources();
9236         aCopier.copyPageResources(pPage, aLine, rResources);
9237 
9238         aLine.append(" /BBox [ ");
9239         aLine.append(aOrigin[0]);
9240         aLine.append(' ');
9241         aLine.append(aOrigin[1]);
9242         aLine.append(' ');
9243         aLine.append(aBBox.getWidth() + aOrigin[0]);
9244         aLine.append(' ');
9245         aLine.append(aBBox.getHeight() + aOrigin[1]);
9246         aLine.append(" ]");
9247 
9248         if (!g_bDebugDisableCompression)
9249             aLine.append(" /Filter/FlateDecode");
9250         aLine.append(" /Length ");
9251 
9252         SvMemoryStream aStream;
9253         bool bCompressed = false;
9254         sal_Int32 nLength = PDFObjectCopier::copyPageStreams(aContentStreams, aStream, bCompressed);
9255         aLine.append(nLength);
9256 
9257         aLine.append(">>\nstream\n");
9258         if (g_bDebugDisableCompression)
9259         {
9260             emitComment("PDFWriterImpl::writeReferenceXObject, WrappedFormObject");
9261         }
9262         if (!updateObject(nWrappedFormObject))
9263             return;
9264         if (!writeBuffer(aLine))
9265             return;
9266         aLine.setLength(0);
9267 
9268         checkAndEnableStreamEncryption(nWrappedFormObject);
9269         // Copy the original page streams to the form XObject stream.
9270         aLine.append(static_cast<const char*>(aStream.GetData()), aStream.GetSize());
9271         if (!writeBuffer(aLine))
9272             return;
9273         aLine.setLength(0);
9274         disableStreamEncryption();
9275 
9276         aLine.append("\nendstream\nendobj\n\n");
9277         if (!writeBuffer(aLine))
9278             return;
9279     }
9280 
9281     OStringBuffer aLine;
9282     if (g_bDebugDisableCompression)
9283     {
9284         emitComment("PDFWriterImpl::writeReferenceXObject, FormObject");
9285     }
9286     if (!updateObject(rEmit.m_nFormObject))
9287         return;
9288 
9289     // Now have all the info to write the form XObject.
9290     aLine.append(rEmit.m_nFormObject);
9291     aLine.append(" 0 obj\n");
9292     aLine.append("<< /Type /XObject");
9293     aLine.append(" /Subtype /Form");
9294     aLine.append(" /Resources << /XObject<<");
9295 
9296     sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject;
9297     aLine.append(" /Im");
9298     aLine.append(nObject);
9299     aLine.append(" ");
9300     aLine.append(nObject);
9301     aLine.append(" 0 R");
9302 
9303     aLine.append(">> >>");
9304     aLine.append(" /Matrix [ ");
9305     appendDouble(fScaleX, aLine);
9306     aLine.append(" 0 0 ");
9307     appendDouble(fScaleY, aLine);
9308     aLine.append(" 0 0 ]");
9309     aLine.append(" /BBox [ 0 0 ");
9310     aLine.append(aSize.Width());
9311     aLine.append(" ");
9312     aLine.append(aSize.Height());
9313     aLine.append(" ]\n");
9314 
9315     if (m_aContext.UseReferenceXObject && rEmit.m_nEmbeddedObject > 0)
9316     {
9317         // Write the reference dictionary.
9318         aLine.append("/Ref<< /F << /Type /Filespec /F (<embedded file>) ");
9319         if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
9320         {   // ISO 14289-1:2014, Clause: 7.11
9321             aLine.append("/UF (<embedded file>) ");
9322         }
9323         aLine.append("/EF << /F ");
9324         aLine.append(rEmit.m_nEmbeddedObject);
9325         aLine.append(" 0 R >> >> /Page 0 >>\n");
9326     }
9327 
9328     aLine.append("/Length ");
9329 
9330     OStringBuffer aStream;
9331     aStream.append("q ");
9332     if (m_aContext.UseReferenceXObject)
9333     {
9334         // Reference XObject markup is used, just refer to the fallback bitmap
9335         // here.
9336         aStream.append(aSize.Width());
9337         aStream.append(" 0 0 ");
9338         aStream.append(aSize.Height());
9339         aStream.append(" 0 0 cm\n");
9340         aStream.append("/Im");
9341         aStream.append(rEmit.m_nBitmapObject);
9342         aStream.append(" Do\n");
9343     }
9344     else
9345     {
9346         // Reset line width to the default.
9347         aStream.append(" 1 w\n");
9348 
9349         // vcl::RenderPDFBitmaps() effectively renders a white background for transparent input, be
9350         // consistent with that.
9351         aStream.append("1 1 1 rg\n");
9352         aStream.append("0 0 ");
9353         aStream.append(aSize.Width());
9354         aStream.append(" ");
9355         aStream.append(aSize.Height());
9356         aStream.append(" re\n");
9357         aStream.append("f*\n");
9358 
9359         // Reset non-stroking color in case the XObject uses the default
9360         aStream.append("0 0 0 rg\n");
9361         // No reference XObject, draw the form XObject containing the original
9362         // page streams.
9363         aStream.append("/Im");
9364         aStream.append(nWrappedFormObject);
9365         aStream.append(" Do\n");
9366     }
9367     aStream.append("Q");
9368     aLine.append(aStream.getLength());
9369 
9370     aLine.append(">>\nstream\n");
9371     if (!writeBuffer(aLine))
9372         return;
9373     aLine.setLength(0);
9374 
9375     checkAndEnableStreamEncryption(rEmit.m_nFormObject);
9376     aLine.append(aStream.getStr());
9377     if (!writeBuffer(aLine))
9378         return;
9379     aLine.setLength(0);
9380     disableStreamEncryption();
9381 
9382     aLine.append("\nendstream\nendobj\n\n");
9383     CHECK_RETURN2(writeBuffer(aLine));
9384 }
9385 
9386 bool PDFWriterImpl::writeBitmapObject( const BitmapEmit& rObject, bool bMask )
9387 {
9388     if (rObject.m_aReferenceXObject.hasExternalPDFData() && !m_aContext.UseReferenceXObject)
9389     {
9390         writeReferenceXObject(rObject.m_aReferenceXObject);
9391         return true;
9392     }
9393 
9394     CHECK_RETURN( updateObject( rObject.m_nObject ) );
9395 
9396     Bitmap  aBitmap;
9397     bool    bWriteMask = false;
9398     if( ! bMask )
9399     {
9400         aBitmap = rObject.m_aBitmap.GetBitmap();
9401         if( rObject.m_aBitmap.IsAlpha() )
9402         {
9403             if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
9404                 bWriteMask = true;
9405             // else draw without alpha channel
9406         }
9407     }
9408     else
9409     {
9410         if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 || ! rObject.m_aBitmap.IsAlpha() )
9411         {
9412             if( rObject.m_aBitmap.IsAlpha() )
9413             {
9414                 aBitmap = rObject.m_aBitmap.GetAlphaMask();
9415                 aBitmap.Convert( BmpConversion::N1BitThreshold );
9416                 SAL_WARN_IF(aBitmap.getPixelFormat() != vcl::PixelFormat::N8_BPP, "vcl.pdfwriter", "mask conversion failed" );
9417             }
9418         }
9419         else if (aBitmap.getPixelFormat() != vcl::PixelFormat::N8_BPP)
9420         {
9421             aBitmap = rObject.m_aBitmap.GetAlphaMask().GetBitmap();
9422             aBitmap.Convert( BmpConversion::N8BitGreys );
9423             SAL_WARN_IF(aBitmap.getPixelFormat() != vcl::PixelFormat::N8_BPP, "vcl.pdfwriter", "alpha mask conversion failed" );
9424         }
9425     }
9426 
9427     Bitmap::ScopedReadAccess pAccess(aBitmap);
9428 
9429     bool bTrueColor = true;
9430     sal_Int32 nBitsPerComponent = 0;
9431     auto const ePixelFormat = aBitmap.getPixelFormat();
9432     switch (ePixelFormat)
9433     {
9434         case vcl::PixelFormat::N8_BPP:
9435             bTrueColor = false;
9436             nBitsPerComponent = vcl::pixelFormatBitCount(ePixelFormat);
9437             break;
9438         case vcl::PixelFormat::N24_BPP:
9439         case vcl::PixelFormat::N32_BPP:
9440             bTrueColor = true;
9441             nBitsPerComponent = 8;
9442             break;
9443         case vcl::PixelFormat::INVALID:
9444             return false;
9445     }
9446 
9447     sal_Int32 nStreamLengthObject   = createObject();
9448     sal_Int32 nMaskObject           = 0;
9449 
9450     if (g_bDebugDisableCompression)
9451     {
9452         emitComment( "PDFWriterImpl::writeBitmapObject" );
9453     }
9454     OStringBuffer aLine(1024);
9455     aLine.append( rObject.m_nObject );
9456     aLine.append( " 0 obj\n"
9457                   "<</Type/XObject/Subtype/Image/Width " );
9458     aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Width()) );
9459     aLine.append( "/Height " );
9460     aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Height()) );
9461     aLine.append( "/BitsPerComponent " );
9462     aLine.append( nBitsPerComponent );
9463     aLine.append( "/Length " );
9464     aLine.append( nStreamLengthObject );
9465     aLine.append( " 0 R\n" );
9466     if (!g_bDebugDisableCompression)
9467     {
9468         if( nBitsPerComponent != 1 )
9469         {
9470             aLine.append( "/Filter/FlateDecode" );
9471         }
9472         else
9473         {
9474             aLine.append( "/Filter/CCITTFaxDecode/DecodeParms<</K -1/BlackIs1 true/Columns " );
9475             aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Width()) );
9476             aLine.append( ">>\n" );
9477         }
9478     }
9479     if( ! bMask )
9480     {
9481         aLine.append( "/ColorSpace" );
9482         if( bTrueColor )
9483             aLine.append( "/DeviceRGB\n" );
9484         else if( aBitmap.HasGreyPaletteAny() )
9485         {
9486             aLine.append( "/DeviceGray\n" );
9487             if (aBitmap.getPixelFormat() == vcl::PixelFormat::N8_BPP)
9488             {
9489                 // #i47395# 1 bit bitmaps occasionally have an inverted grey palette
9490                 sal_uInt16 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_BLACK ) );
9491                 assert( nBlackIndex == 0 || nBlackIndex == 1);
9492                 sal_uInt16 nWhiteIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_WHITE ) );
9493                 if( pAccess->GetPalette()[nBlackIndex] == BitmapColor( COL_BLACK ) &&
9494                     pAccess->GetPalette()[nWhiteIndex] == BitmapColor( COL_WHITE ) )
9495                 {
9496                     // It is black and white
9497                     if( nBlackIndex == 1 )
9498                         aLine.append( "/Decode[1 0]\n" );
9499                 }
9500                 else
9501                 {
9502                     // It is two levels of grey
9503                     aLine.append( "/Decode[" );
9504                     assert( pAccess->GetPalette()[0].GetRed() == pAccess->GetPalette()[0].GetGreen() &&
9505                             pAccess->GetPalette()[0].GetRed() == pAccess->GetPalette()[0].GetBlue() &&
9506                             pAccess->GetPalette()[1].GetRed() == pAccess->GetPalette()[1].GetGreen() &&
9507                             pAccess->GetPalette()[1].GetRed() == pAccess->GetPalette()[1].GetBlue() );
9508                     aLine.append( pAccess->GetPalette()[0].GetRed() / 255.0 );
9509                     aLine.append( " " );
9510                     aLine.append( pAccess->GetPalette()[1].GetRed() / 255.0 );
9511                     aLine.append( "]\n" );
9512                 }
9513             }
9514         }
9515         else
9516         {
9517             aLine.append( "[ /Indexed/DeviceRGB " );
9518             aLine.append( static_cast<sal_Int32>(pAccess->GetPaletteEntryCount()-1) );
9519             aLine.append( "\n<" );
9520             if( m_aContext.Encryption.Encrypt() )
9521             {
9522                 enableStringEncryption( rObject.m_nObject );
9523                 //check encryption buffer size
9524                 m_vEncryptionBuffer.resize(pAccess->GetPaletteEntryCount()*3);
9525                 int nChar = 0;
9526                 //fill the encryption buffer
9527                 for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
9528                 {
9529                     const BitmapColor& rColor = pAccess->GetPaletteColor( i );
9530                     m_vEncryptionBuffer[nChar++] = rColor.GetRed();
9531                     m_vEncryptionBuffer[nChar++] = rColor.GetGreen();
9532                     m_vEncryptionBuffer[nChar++] = rColor.GetBlue();
9533                 }
9534                 //encrypt the colorspace lookup table
9535                 rtl_cipher_encodeARCFOUR( m_aCipher, m_vEncryptionBuffer.data(), nChar, m_vEncryptionBuffer.data(), nChar );
9536                 //now queue the data for output
9537                 nChar = 0;
9538                 for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
9539                 {
9540                     appendHex(m_vEncryptionBuffer[nChar++], aLine );
9541                     appendHex(m_vEncryptionBuffer[nChar++], aLine );
9542                     appendHex(m_vEncryptionBuffer[nChar++], aLine );
9543                 }
9544             }
9545             else //no encryption requested (PDF/A-1a program flow drops here)
9546             {
9547                 for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
9548                 {
9549                     const BitmapColor& rColor = pAccess->GetPaletteColor( i );
9550                     appendHex( rColor.GetRed(), aLine );
9551                     appendHex( rColor.GetGreen(), aLine );
9552                     appendHex( rColor.GetBlue(), aLine );
9553                 }
9554             }
9555             aLine.append( ">\n]\n" );
9556         }
9557     }
9558     else
9559     {
9560         aLine.append( "/ColorSpace/DeviceGray\n"
9561                       "/Decode [ 1 0 ]\n" );
9562     }
9563 
9564     if (!bMask && !m_bIsPDF_A1)
9565     {
9566         if( bWriteMask )
9567         {
9568             nMaskObject = createObject();
9569             if (rObject.m_aBitmap.IsAlpha())
9570                 aLine.append( "/SMask " );
9571             else
9572                 aLine.append( "/Mask " );
9573             aLine.append( nMaskObject );
9574             aLine.append( " 0 R\n" );
9575         }
9576     }
9577     else if( m_bIsPDF_A1 && bWriteMask )
9578         m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
9579 
9580     aLine.append( ">>\n"
9581                   "stream\n" );
9582     CHECK_RETURN( writeBuffer( aLine ) );
9583     sal_uInt64 nStartPos = 0;
9584     CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nStartPos)) );
9585 
9586     checkAndEnableStreamEncryption( rObject.m_nObject );
9587     if (!g_bDebugDisableCompression && nBitsPerComponent == 1)
9588     {
9589         writeG4Stream(pAccess.get());
9590     }
9591     else
9592     {
9593         beginCompression();
9594         if( ! bTrueColor || pAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb )
9595         {
9596             //With PDF bitmaps, each row is padded to a BYTE boundary (multiple of 8 bits).
9597             const int nScanLineBytes = ((pAccess->GetBitCount() * pAccess->Width()) + 7U) / 8U;
9598 
9599             for( tools::Long i = 0; i < pAccess->Height(); i++ )
9600             {
9601                 CHECK_RETURN( writeBufferBytes( pAccess->GetScanline( i ), nScanLineBytes ) );
9602             }
9603         }
9604         else
9605         {
9606             const int nScanLineBytes = pAccess->Width()*3;
9607             std::unique_ptr<sal_uInt8[]> xCol(new sal_uInt8[nScanLineBytes]);
9608             for( tools::Long y = 0; y < pAccess->Height(); y++ )
9609             {
9610                 for( tools::Long x = 0; x < pAccess->Width(); x++ )
9611                 {
9612                     BitmapColor aColor = pAccess->GetColor( y, x );
9613                     xCol[3*x+0] = aColor.GetRed();
9614                     xCol[3*x+1] = aColor.GetGreen();
9615                     xCol[3*x+2] = aColor.GetBlue();
9616                 }
9617                 CHECK_RETURN(writeBufferBytes(xCol.get(), nScanLineBytes));
9618             }
9619         }
9620         endCompression();
9621     }
9622     disableStreamEncryption();
9623 
9624     sal_uInt64 nEndPos = 0;
9625     CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nEndPos)) );
9626     aLine.setLength( 0 );
9627     aLine.append( "\nendstream\nendobj\n\n" );
9628     CHECK_RETURN( writeBuffer( aLine ) );
9629     CHECK_RETURN( updateObject( nStreamLengthObject ) );
9630     aLine.setLength( 0 );
9631     aLine.append( nStreamLengthObject );
9632     aLine.append( " 0 obj\n" );
9633     aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
9634     aLine.append( "\nendobj\n\n" );
9635     CHECK_RETURN( writeBuffer( aLine ) );
9636 
9637     if( nMaskObject )
9638     {
9639         BitmapEmit aEmit;
9640         aEmit.m_nObject             = nMaskObject;
9641         aEmit.m_aBitmap             = rObject.m_aBitmap;
9642         return writeBitmapObject( aEmit, true );
9643     }
9644 
9645     writeReferenceXObject(rObject.m_aReferenceXObject);
9646 
9647     return true;
9648 }
9649 
9650 void PDFWriterImpl::createEmbeddedFile(const Graphic& rGraphic, ReferenceXObjectEmit& rEmit, sal_Int32 nBitmapObject)
9651 {
9652     // The bitmap object is always a valid identifier, even if the graphic has
9653     // no pdf data.
9654     rEmit.m_nBitmapObject = nBitmapObject;
9655 
9656     if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf)
9657         return;
9658 
9659     BinaryDataContainer const & rDataContainer = rGraphic.getVectorGraphicData()->getBinaryDataContainer();
9660 
9661     if (m_aContext.UseReferenceXObject)
9662     {
9663         // Store the original PDF data as an embedded file.
9664         auto nObjectID = addEmbeddedFile(rDataContainer);
9665         rEmit.m_nEmbeddedObject = nObjectID;
9666     }
9667     else
9668     {
9669         sal_Int32 aIndex = m_aExternalPDFStreams.store(rDataContainer);
9670         rEmit.m_nExternalPDFPageIndex = rGraphic.getVectorGraphicData()->getPageIndex();
9671         rEmit.m_nExternalPDFDataIndex = aIndex;
9672     }
9673 
9674     rEmit.m_nFormObject = createObject();
9675     rEmit.m_aPixelSize = rGraphic.GetPrefSize();
9676 }
9677 
9678 void PDFWriterImpl::drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const Size& rSizePixel, const tools::Rectangle& rTargetArea, const AlphaMask& rAlphaMask, const Graphic& rGraphic )
9679 {
9680     MARK( "drawJPGBitmap" );
9681 
9682     OStringBuffer aLine( 80 );
9683     updateGraphicsState();
9684 
9685     // #i40055# sanity check
9686     if( ! (rTargetArea.GetWidth() && rTargetArea.GetHeight() ) )
9687         return;
9688     if( ! (rSizePixel.Width() && rSizePixel.Height()) )
9689         return;
9690 
9691     rDCTData.Seek( 0 );
9692     if( bIsTrueColor && m_aContext.ColorMode == PDFWriter::DrawGreyscale )
9693     {
9694         // need to convert to grayscale;
9695         // load stream to bitmap and draw the bitmap instead
9696         Graphic aGraphic;
9697         GraphicConverter::Import( rDCTData, aGraphic, ConvertDataFormat::JPG );
9698         if( !rAlphaMask.IsEmpty() && rAlphaMask.GetSizePixel() == aGraphic.GetSizePixel() )
9699         {
9700             Bitmap aBmp( aGraphic.GetBitmapEx().GetBitmap() );
9701             BitmapEx aBmpEx( aBmp, rAlphaMask );
9702             drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aBmpEx );
9703         }
9704         else
9705             drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aGraphic.GetBitmapEx() );
9706         return;
9707     }
9708 
9709     std::unique_ptr<SvMemoryStream> pStream(new SvMemoryStream);
9710     pStream->WriteStream( rDCTData );
9711     pStream->Seek( STREAM_SEEK_TO_END );
9712 
9713     BitmapID aID;
9714     aID.m_aPixelSize    = rSizePixel;
9715     aID.m_nSize         = pStream->Tell();
9716     pStream->Seek( STREAM_SEEK_TO_BEGIN );
9717     aID.m_nChecksum     = vcl_get_checksum( 0, pStream->GetData(), aID.m_nSize );
9718     if( ! rAlphaMask.IsEmpty() )
9719         aID.m_nMaskChecksum = rAlphaMask.GetChecksum();
9720 
9721     std::vector< JPGEmit >::const_iterator it = std::find_if(m_aJPGs.begin(), m_aJPGs.end(),
9722                                              [&](const JPGEmit& arg) { return aID == arg.m_aID; });
9723     if( it == m_aJPGs.end() )
9724     {
9725         m_aJPGs.emplace( m_aJPGs.begin() );
9726         JPGEmit& rEmit = m_aJPGs.front();
9727         if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject)
9728             rEmit.m_nObject = createObject();
9729         rEmit.m_aID         = aID;
9730         rEmit.m_pStream = std::move( pStream );
9731         rEmit.m_bTrueColor  = bIsTrueColor;
9732         if( !rAlphaMask.IsEmpty() && rAlphaMask.GetSizePixel() == rSizePixel )
9733             rEmit.m_aAlphaMask = rAlphaMask;
9734         createEmbeddedFile(rGraphic, rEmit.m_aReferenceXObject, rEmit.m_nObject);
9735 
9736         it = m_aJPGs.begin();
9737     }
9738 
9739     aLine.append( "q " );
9740     sal_Int32 nCheckWidth = 0;
9741     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetWidth()), aLine, false, &nCheckWidth );
9742     aLine.append( " 0 0 " );
9743     sal_Int32 nCheckHeight = 0;
9744     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetHeight()), aLine, true, &nCheckHeight );
9745     aLine.append( ' ' );
9746     m_aPages.back().appendPoint( rTargetArea.BottomLeft(), aLine );
9747     aLine.append( " cm\n/Im" );
9748     sal_Int32 nObject = it->m_aReferenceXObject.getObject();
9749     aLine.append(nObject);
9750     aLine.append( " Do Q\n" );
9751     if( nCheckWidth == 0 || nCheckHeight == 0 )
9752     {
9753         // #i97512# avoid invalid current matrix
9754         aLine.setLength( 0 );
9755         aLine.append( "\n%jpeg image /Im" );
9756         aLine.append( it->m_nObject );
9757         aLine.append( " scaled to zero size, omitted\n" );
9758     }
9759     writeBuffer( aLine );
9760 
9761     OString aObjName = "Im" + OString::number(nObject);
9762     pushResource( ResourceKind::XObject, aObjName, nObject );
9763 
9764 }
9765 
9766 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEmit& rBitmap, const Color& rFillColor )
9767 {
9768     OStringBuffer aLine( 80 );
9769     updateGraphicsState();
9770 
9771     aLine.append( "q " );
9772     if( rFillColor != COL_TRANSPARENT )
9773     {
9774         appendNonStrokingColor( rFillColor, aLine );
9775         aLine.append( ' ' );
9776     }
9777     sal_Int32 nCheckWidth = 0;
9778     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Width()), aLine, false, &nCheckWidth );
9779     aLine.append( " 0 0 " );
9780     sal_Int32 nCheckHeight = 0;
9781     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Height()), aLine, true, &nCheckHeight );
9782     aLine.append( ' ' );
9783     m_aPages.back().appendPoint( rDestPoint + Point( 0, rDestSize.Height()-1 ), aLine );
9784     aLine.append( " cm\n/Im" );
9785     sal_Int32 nObject = rBitmap.m_aReferenceXObject.getObject();
9786     aLine.append(nObject);
9787     aLine.append( " Do Q\n" );
9788     if( nCheckWidth == 0 || nCheckHeight == 0 )
9789     {
9790         // #i97512# avoid invalid current matrix
9791         aLine.setLength( 0 );
9792         aLine.append( "\n%bitmap image /Im" );
9793         aLine.append( rBitmap.m_nObject );
9794         aLine.append( " scaled to zero size, omitted\n" );
9795     }
9796     writeBuffer( aLine );
9797 }
9798 
9799 const BitmapEmit& PDFWriterImpl::createBitmapEmit(const BitmapEx& i_rBitmap, const Graphic& rGraphic, std::list<BitmapEmit>& rBitmaps, ResourceDict& rResourceDict, std::list<StreamRedirect>& rOutputStreams)
9800 {
9801     BitmapEx aBitmap( i_rBitmap );
9802     auto ePixelFormat = aBitmap.GetBitmap().getPixelFormat();
9803     if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
9804         aBitmap.Convert(BmpConversion::N8BitGreys);
9805     BitmapID aID;
9806     aID.m_aPixelSize        = aBitmap.GetSizePixel();
9807     aID.m_nSize             = vcl::pixelFormatBitCount(ePixelFormat);
9808     aID.m_nChecksum         = aBitmap.GetBitmap().GetChecksum();
9809     aID.m_nMaskChecksum     = 0;
9810     if( aBitmap.IsAlpha() )
9811         aID.m_nMaskChecksum = aBitmap.GetAlphaMask().GetChecksum();
9812     std::list<BitmapEmit>::const_iterator it = std::find_if(rBitmaps.begin(), rBitmaps.end(),
9813                                              [&](const BitmapEmit& arg) { return aID == arg.m_aID; });
9814     if (it == rBitmaps.end())
9815     {
9816         rBitmaps.push_front(BitmapEmit());
9817         rBitmaps.front().m_aID = aID;
9818         rBitmaps.front().m_aBitmap = aBitmap;
9819         if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject)
9820             rBitmaps.front().m_nObject = createObject();
9821         createEmbeddedFile(rGraphic, rBitmaps.front().m_aReferenceXObject, rBitmaps.front().m_nObject);
9822         it = rBitmaps.begin();
9823     }
9824 
9825     sal_Int32 nObject = it->m_aReferenceXObject.getObject();
9826     OString aObjName = "Im" + OString::number(nObject);
9827     pushResource(ResourceKind::XObject, aObjName, nObject, rResourceDict, rOutputStreams);
9828 
9829     return *it;
9830 }
9831 
9832 const BitmapEmit& PDFWriterImpl::createBitmapEmit( const BitmapEx& i_rBitmap, const Graphic& rGraphic )
9833 {
9834     return createBitmapEmit(i_rBitmap, rGraphic, m_aBitmaps, m_aGlobalResourceDict, m_aOutputStreams);
9835 }
9836 
9837 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap, const Graphic& rGraphic )
9838 {
9839     MARK( "drawBitmap (Bitmap)" );
9840 
9841     // #i40055# sanity check
9842     if( ! (rDestSize.Width() && rDestSize.Height()) )
9843         return;
9844 
9845     const BitmapEmit& rEmit = createBitmapEmit( BitmapEx( rBitmap ), rGraphic );
9846     drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
9847 }
9848 
9849 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEx& rBitmap )
9850 {
9851     MARK( "drawBitmap (BitmapEx)" );
9852 
9853     // #i40055# sanity check
9854     if( ! (rDestSize.Width() && rDestSize.Height()) )
9855         return;
9856 
9857     const BitmapEmit& rEmit = createBitmapEmit( rBitmap, Graphic() );
9858     drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
9859 }
9860 
9861 sal_Int32 PDFWriterImpl::createGradient( const Gradient& rGradient, const Size& rSize )
9862 {
9863     Size aPtSize( lcl_convert( m_aGraphicsStack.front().m_aMapMode,
9864                                MapMode( MapUnit::MapPoint ),
9865                                this,
9866                                rSize ) );
9867     // check if we already have this gradient
9868     // rounding to point will generally lose some pixels
9869     // round up to point boundary
9870     aPtSize.AdjustWidth( 1 );
9871     aPtSize.AdjustHeight( 1 );
9872     std::list< GradientEmit >::const_iterator it = std::find_if(m_aGradients.begin(), m_aGradients.end(),
9873                                              [&](const GradientEmit& arg) { return ((rGradient == arg.m_aGradient) && (aPtSize == arg.m_aSize) ); });
9874 
9875     if( it == m_aGradients.end() )
9876     {
9877         m_aGradients.push_front( GradientEmit() );
9878         m_aGradients.front().m_aGradient    = rGradient;
9879         m_aGradients.front().m_nObject      = createObject();
9880         m_aGradients.front().m_aSize        = aPtSize;
9881         it = m_aGradients.begin();
9882     }
9883 
9884     OStringBuffer aObjName( 16 );
9885     aObjName.append( 'P' );
9886     aObjName.append( it->m_nObject );
9887     pushResource( ResourceKind::Shading, aObjName.makeStringAndClear(), it->m_nObject );
9888 
9889     return it->m_nObject;
9890 }
9891 
9892 void PDFWriterImpl::drawGradient( const tools::Rectangle& rRect, const Gradient& rGradient )
9893 {
9894     MARK( "drawGradient (Rectangle)" );
9895 
9896     sal_Int32 nGradient = createGradient( rGradient, rRect.GetSize() );
9897 
9898     Point aTranslate( rRect.BottomLeft() );
9899     aTranslate += Point( 0, 1 );
9900 
9901     updateGraphicsState();
9902 
9903     OStringBuffer aLine( 80 );
9904     aLine.append( "q 1 0 0 1 " );
9905     m_aPages.back().appendPoint( aTranslate, aLine );
9906     aLine.append( " cm " );
9907     // if a stroke is appended reset the clip region before stroke
9908     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
9909         aLine.append( "q " );
9910     aLine.append( "0 0 " );
9911     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
9912     aLine.append( ' ' );
9913     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
9914     aLine.append( " re W n\n" );
9915 
9916     aLine.append( "/P" );
9917     aLine.append( nGradient );
9918     aLine.append( " sh " );
9919     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
9920     {
9921         aLine.append( "Q 0 0 " );
9922         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
9923         aLine.append( ' ' );
9924         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
9925         aLine.append( " re S " );
9926     }
9927     aLine.append( "Q\n" );
9928     writeBuffer( aLine );
9929 }
9930 
9931 void PDFWriterImpl::drawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch )
9932 {
9933     MARK( "drawHatch" );
9934 
9935     updateGraphicsState();
9936 
9937     if( rPolyPoly.Count() )
9938     {
9939         tools::PolyPolygon     aPolyPoly( rPolyPoly );
9940 
9941         aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME );
9942         push( PushFlags::LINECOLOR );
9943         setLineColor( rHatch.GetColor() );
9944         DrawHatch( aPolyPoly, rHatch, false );
9945         pop();
9946     }
9947 }
9948 
9949 void PDFWriterImpl::drawWallpaper( const tools::Rectangle& rRect, const Wallpaper& rWall )
9950 {
9951     MARK( "drawWallpaper" );
9952 
9953     bool bDrawColor         = false;
9954     bool bDrawGradient      = false;
9955     bool bDrawBitmap        = false;
9956 
9957     BitmapEx aBitmap;
9958     Point aBmpPos = rRect.TopLeft();
9959     Size aBmpSize;
9960     if( rWall.IsBitmap() )
9961     {
9962         aBitmap = rWall.GetBitmap();
9963         aBmpSize = lcl_convert( aBitmap.GetPrefMapMode(),
9964                                 getMapMode(),
9965                                 this,
9966                                 aBitmap.GetPrefSize() );
9967         tools::Rectangle aRect( rRect );
9968         if( rWall.IsRect() )
9969         {
9970             aRect = rWall.GetRect();
9971             aBmpPos = aRect.TopLeft();
9972             aBmpSize = aRect.GetSize();
9973         }
9974         if( rWall.GetStyle() != WallpaperStyle::Scale )
9975         {
9976             if( rWall.GetStyle() != WallpaperStyle::Tile )
9977             {
9978                 bDrawBitmap     = true;
9979                 if( rWall.IsGradient() )
9980                     bDrawGradient = true;
9981                 else
9982                     bDrawColor = true;
9983                 switch( rWall.GetStyle() )
9984                 {
9985                     case WallpaperStyle::TopLeft:
9986                         break;
9987                     case WallpaperStyle::Top:
9988                         aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
9989                         break;
9990                     case WallpaperStyle::Left:
9991                         aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
9992                         break;
9993                     case WallpaperStyle::TopRight:
9994                         aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
9995                         break;
9996                     case WallpaperStyle::Center:
9997                         aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
9998                         aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
9999                         break;
10000                     case WallpaperStyle::Right:
10001                         aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
10002                         aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
10003                         break;
10004                     case WallpaperStyle::BottomLeft:
10005                         aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
10006                         break;
10007                     case WallpaperStyle::Bottom:
10008                         aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
10009                         aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
10010                         break;
10011                     case WallpaperStyle::BottomRight:
10012                         aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
10013                         aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
10014                         break;
10015                     default: ;
10016                 }
10017             }
10018             else
10019             {
10020                 // push the bitmap
10021                 const BitmapEmit& rEmit = createBitmapEmit( aBitmap, Graphic() );
10022 
10023                 // convert to page coordinates; this needs to be done here
10024                 // since the emit does not know the page anymore
10025                 tools::Rectangle aConvertRect( aBmpPos, aBmpSize );
10026                 m_aPages.back().convertRect( aConvertRect );
10027 
10028                 OString aImageName = "Im" + OString::number( rEmit.m_nObject );
10029 
10030                 // push the pattern
10031                 OStringBuffer aTilingStream( 32 );
10032                 appendFixedInt( aConvertRect.GetWidth(), aTilingStream );
10033                 aTilingStream.append( " 0 0 " );
10034                 appendFixedInt( aConvertRect.GetHeight(), aTilingStream );
10035                 aTilingStream.append( " 0 0 cm\n/" );
10036                 aTilingStream.append( aImageName );
10037                 aTilingStream.append( " Do\n" );
10038 
10039                 m_aTilings.emplace_back( );
10040                 m_aTilings.back().m_nObject         = createObject();
10041                 m_aTilings.back().m_aRectangle      = tools::Rectangle( Point( 0, 0 ), aConvertRect.GetSize() );
10042                 m_aTilings.back().m_pTilingStream.reset(new SvMemoryStream());
10043                 m_aTilings.back().m_pTilingStream->WriteBytes(
10044                     aTilingStream.getStr(), aTilingStream.getLength() );
10045                 // phase the tiling so wallpaper begins on upper left
10046                 if ((aConvertRect.GetWidth() == 0) || (aConvertRect.GetHeight() == 0))
10047                     throw o3tl::divide_by_zero();
10048                 m_aTilings.back().m_aTransform.matrix[2] = double(aConvertRect.Left() % aConvertRect.GetWidth()) / fDivisor;
10049                 m_aTilings.back().m_aTransform.matrix[5] = double(aConvertRect.Top() % aConvertRect.GetHeight()) / fDivisor;
10050                 m_aTilings.back().m_aResources.m_aXObjects[aImageName] = rEmit.m_nObject;
10051 
10052                 updateGraphicsState();
10053 
10054                 OStringBuffer aObjName( 16 );
10055                 aObjName.append( 'P' );
10056                 aObjName.append( m_aTilings.back().m_nObject );
10057                 OString aPatternName( aObjName.makeStringAndClear() );
10058                 pushResource( ResourceKind::Pattern, aPatternName, m_aTilings.back().m_nObject );
10059 
10060                 // fill a rRect with the pattern
10061                 OStringBuffer aLine( 100 );
10062                 aLine.append( "q /Pattern cs /" );
10063                 aLine.append( aPatternName );
10064                 aLine.append( " scn " );
10065                 m_aPages.back().appendRect( rRect, aLine );
10066                 aLine.append( " f Q\n" );
10067                 writeBuffer( aLine );
10068             }
10069         }
10070         else
10071         {
10072             aBmpPos     = aRect.TopLeft();
10073             aBmpSize    = aRect.GetSize();
10074             bDrawBitmap = true;
10075         }
10076 
10077         if( aBitmap.IsAlpha() )
10078         {
10079             if( rWall.IsGradient() )
10080                 bDrawGradient = true;
10081             else
10082                 bDrawColor = true;
10083         }
10084     }
10085     else if( rWall.IsGradient() )
10086         bDrawGradient = true;
10087     else
10088         bDrawColor = true;
10089 
10090     if( bDrawGradient )
10091     {
10092         drawGradient( rRect, rWall.GetGradient() );
10093     }
10094     if( bDrawColor )
10095     {
10096         Color aOldLineColor = m_aGraphicsStack.front().m_aLineColor;
10097         Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
10098         setLineColor( COL_TRANSPARENT );
10099         setFillColor( rWall.GetColor() );
10100         drawRectangle( rRect );
10101         setLineColor( aOldLineColor );
10102         setFillColor( aOldFillColor );
10103     }
10104     if( bDrawBitmap )
10105     {
10106         // set temporary clip region since aBmpPos and aBmpSize
10107         // may be outside rRect
10108         OStringBuffer aLine( 20 );
10109         aLine.append( "q " );
10110         m_aPages.back().appendRect( rRect, aLine );
10111         aLine.append( " W n\n" );
10112         writeBuffer( aLine );
10113         drawBitmap( aBmpPos, aBmpSize, aBitmap );
10114         writeBuffer( "Q\n" );
10115     }
10116 }
10117 
10118 void PDFWriterImpl::updateGraphicsState(Mode const mode)
10119 {
10120     OStringBuffer aLine( 256 );
10121     GraphicsState& rNewState = m_aGraphicsStack.front();
10122     // first set clip region since it might invalidate everything else
10123 
10124     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::ClipRegion )
10125     {
10126         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::ClipRegion;
10127 
10128         if( m_aCurrentPDFState.m_bClipRegion != rNewState.m_bClipRegion ||
10129             ( rNewState.m_bClipRegion && m_aCurrentPDFState.m_aClipRegion != rNewState.m_aClipRegion ) )
10130         {
10131             if( m_aCurrentPDFState.m_bClipRegion )
10132             {
10133                 aLine.append( "Q " );
10134                 // invalidate everything but the clip region
10135                 m_aCurrentPDFState = GraphicsState();
10136                 rNewState.m_nUpdateFlags = ~GraphicsStateUpdateFlags::ClipRegion;
10137             }
10138             if( rNewState.m_bClipRegion )
10139             {
10140                 // clip region is always stored in private PDF mapmode
10141                 MapMode aNewMapMode = rNewState.m_aMapMode;
10142                 rNewState.m_aMapMode = m_aMapMode;
10143                 SetMapMode( rNewState.m_aMapMode );
10144                 m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
10145 
10146                 aLine.append("q ");
10147                 if ( rNewState.m_aClipRegion.count() )
10148                 {
10149                     m_aPages.back().appendPolyPolygon( rNewState.m_aClipRegion, aLine );
10150                 }
10151                 else
10152                 {
10153                     // tdf#130150 Need to revert tdf#99680, that breaks the
10154                     // rule that an set but empty clip-region clips everything
10155                     // aka draws nothing -> nothing is in an empty clip-region
10156                     aLine.append( "0 0 m h " ); // NULL clip, i.e. nothing visible
10157                 }
10158                 aLine.append( "W* n\n" );
10159 
10160                 rNewState.m_aMapMode = aNewMapMode;
10161                 SetMapMode( rNewState.m_aMapMode );
10162                 m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
10163             }
10164         }
10165     }
10166 
10167     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::MapMode )
10168     {
10169         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::MapMode;
10170         SetMapMode( rNewState.m_aMapMode );
10171     }
10172 
10173     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::Font )
10174     {
10175         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::Font;
10176         SetFont( rNewState.m_aFont );
10177     }
10178 
10179     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LayoutMode )
10180     {
10181         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LayoutMode;
10182         SetLayoutMode( rNewState.m_nLayoutMode );
10183     }
10184 
10185     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::DigitLanguage )
10186     {
10187         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::DigitLanguage;
10188         SetDigitLanguage( rNewState.m_aDigitLanguage );
10189     }
10190 
10191     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LineColor )
10192     {
10193         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LineColor;
10194         if( m_aCurrentPDFState.m_aLineColor != rNewState.m_aLineColor &&
10195             rNewState.m_aLineColor != COL_TRANSPARENT )
10196         {
10197             appendStrokingColor( rNewState.m_aLineColor, aLine );
10198             aLine.append( "\n" );
10199         }
10200     }
10201 
10202     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::FillColor )
10203     {
10204         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::FillColor;
10205         if( m_aCurrentPDFState.m_aFillColor != rNewState.m_aFillColor &&
10206             rNewState.m_aFillColor != COL_TRANSPARENT )
10207         {
10208             appendNonStrokingColor( rNewState.m_aFillColor, aLine );
10209             aLine.append( "\n" );
10210         }
10211     }
10212 
10213     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::TransparentPercent )
10214     {
10215         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::TransparentPercent;
10216         if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
10217         {
10218             // TODO: switch extended graphicsstate
10219         }
10220     }
10221 
10222     // everything is up to date now
10223     m_aCurrentPDFState = m_aGraphicsStack.front();
10224     if ((mode != Mode::NOWRITE) &&  !aLine.isEmpty())
10225         writeBuffer( aLine );
10226 }
10227 
10228 /* #i47544# imitate OutputDevice behaviour:
10229 *  if a font with a nontransparent color is set, it overwrites the current
10230 *  text color. OTOH setting the text color will overwrite the color of the font.
10231 */
10232 void PDFWriterImpl::setFont( const vcl::Font& rFont )
10233 {
10234     Color aColor = rFont.GetColor();
10235     if( aColor == COL_TRANSPARENT )
10236         aColor = m_aGraphicsStack.front().m_aFont.GetColor();
10237     m_aGraphicsStack.front().m_aFont = rFont;
10238     m_aGraphicsStack.front().m_aFont.SetColor( aColor );
10239     m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font;
10240 }
10241 
10242 void PDFWriterImpl::push( PushFlags nFlags )
10243 {
10244     OSL_ENSURE( !m_aGraphicsStack.empty(), "invalid graphics stack" );
10245     m_aGraphicsStack.push_front( m_aGraphicsStack.front() );
10246     m_aGraphicsStack.front().m_nFlags = nFlags;
10247 }
10248 
10249 void PDFWriterImpl::pop()
10250 {
10251     OSL_ENSURE( m_aGraphicsStack.size() > 1, "pop without push" );
10252     if( m_aGraphicsStack.size() < 2 )
10253         return;
10254 
10255     GraphicsState aState = m_aGraphicsStack.front();
10256     m_aGraphicsStack.pop_front();
10257     GraphicsState& rOld = m_aGraphicsStack.front();
10258 
10259     // move those parameters back that were not pushed
10260     // in the first place
10261     if( ! (aState.m_nFlags & PushFlags::LINECOLOR) )
10262         setLineColor( aState.m_aLineColor );
10263     if( ! (aState.m_nFlags & PushFlags::FILLCOLOR) )
10264         setFillColor( aState.m_aFillColor );
10265     if( ! (aState.m_nFlags & PushFlags::FONT) )
10266         setFont( aState.m_aFont );
10267     if( ! (aState.m_nFlags & PushFlags::TEXTCOLOR) )
10268         setTextColor( aState.m_aFont.GetColor() );
10269     if( ! (aState.m_nFlags & PushFlags::MAPMODE) )
10270         setMapMode( aState.m_aMapMode );
10271     if( ! (aState.m_nFlags & PushFlags::CLIPREGION) )
10272     {
10273         // do not use setClipRegion here
10274         // it would convert again assuming the current mapmode
10275         rOld.m_aClipRegion = aState.m_aClipRegion;
10276         rOld.m_bClipRegion = aState.m_bClipRegion;
10277     }
10278     if( ! (aState.m_nFlags & PushFlags::TEXTLINECOLOR ) )
10279         setTextLineColor( aState.m_aTextLineColor );
10280     if( ! (aState.m_nFlags & PushFlags::OVERLINECOLOR ) )
10281         setOverlineColor( aState.m_aOverlineColor );
10282     if( ! (aState.m_nFlags & PushFlags::TEXTALIGN ) )
10283         setTextAlign( aState.m_aFont.GetAlignment() );
10284     if( ! (aState.m_nFlags & PushFlags::TEXTFILLCOLOR) )
10285         setTextFillColor( aState.m_aFont.GetFillColor() );
10286     if( ! (aState.m_nFlags & PushFlags::REFPOINT) )
10287     {
10288         // what ?
10289     }
10290     // invalidate graphics state
10291     m_aGraphicsStack.front().m_nUpdateFlags = GraphicsStateUpdateFlags::All;
10292 }
10293 
10294 void PDFWriterImpl::setMapMode( const MapMode& rMapMode )
10295 {
10296     m_aGraphicsStack.front().m_aMapMode = rMapMode;
10297     SetMapMode( rMapMode );
10298     m_aCurrentPDFState.m_aMapMode = rMapMode;
10299 }
10300 
10301 void PDFWriterImpl::setClipRegion( const basegfx::B2DPolyPolygon& rRegion )
10302 {
10303     // tdf#130150 improve coordinate manipulations to double precision transformations
10304     const basegfx::B2DHomMatrix aCurrentTransform(
10305         GetInverseViewTransformation(m_aMapMode) * GetViewTransformation(m_aGraphicsStack.front().m_aMapMode));
10306     basegfx::B2DPolyPolygon aRegion(rRegion);
10307 
10308     aRegion.transform(aCurrentTransform);
10309     m_aGraphicsStack.front().m_aClipRegion = aRegion;
10310     m_aGraphicsStack.front().m_bClipRegion = true;
10311     m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10312 }
10313 
10314 void PDFWriterImpl::moveClipRegion( sal_Int32 nX, sal_Int32 nY )
10315 {
10316     if( !(m_aGraphicsStack.front().m_bClipRegion && m_aGraphicsStack.front().m_aClipRegion.count()) )
10317         return;
10318 
10319     // tdf#130150 improve coordinate manipulations to double precision transformations
10320     basegfx::B2DHomMatrix aConvertA;
10321 
10322     if(MapUnit::MapPixel == m_aGraphicsStack.front().m_aMapMode.GetMapUnit())
10323     {
10324         aConvertA = GetInverseViewTransformation(m_aMapMode);
10325     }
10326     else
10327     {
10328         aConvertA = LogicToLogic(m_aGraphicsStack.front().m_aMapMode, m_aMapMode);
10329     }
10330 
10331     basegfx::B2DPoint aB2DPointA(nX, nY);
10332     basegfx::B2DPoint aB2DPointB(0.0, 0.0);
10333     aB2DPointA *= aConvertA;
10334     aB2DPointB *= aConvertA;
10335     aB2DPointA -= aB2DPointB;
10336     basegfx::B2DHomMatrix aMat;
10337 
10338     aMat.translate(aB2DPointA.getX(), aB2DPointA.getY());
10339     m_aGraphicsStack.front().m_aClipRegion.transform( aMat );
10340     m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10341 }
10342 
10343 void PDFWriterImpl::intersectClipRegion( const tools::Rectangle& rRect )
10344 {
10345     basegfx::B2DPolyPolygon aRect( basegfx::utils::createPolygonFromRect(
10346                                     vcl::unotools::b2DRectangleFromRectangle(rRect) ) );
10347     intersectClipRegion( aRect );
10348 }
10349 
10350 void PDFWriterImpl::intersectClipRegion( const basegfx::B2DPolyPolygon& rRegion )
10351 {
10352     // tdf#130150 improve coordinate manipulations to double precision transformations
10353     const basegfx::B2DHomMatrix aCurrentTransform(
10354         GetInverseViewTransformation(m_aMapMode) * GetViewTransformation(m_aGraphicsStack.front().m_aMapMode));
10355     basegfx::B2DPolyPolygon aRegion(rRegion);
10356 
10357     aRegion.transform(aCurrentTransform);
10358     m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10359 
10360     if( m_aGraphicsStack.front().m_bClipRegion )
10361     {
10362         basegfx::B2DPolyPolygon aOld( basegfx::utils::prepareForPolygonOperation( m_aGraphicsStack.front().m_aClipRegion ) );
10363         aRegion = basegfx::utils::prepareForPolygonOperation( aRegion );
10364         m_aGraphicsStack.front().m_aClipRegion = basegfx::utils::solvePolygonOperationAnd( aOld, aRegion );
10365     }
10366     else
10367     {
10368         m_aGraphicsStack.front().m_aClipRegion = aRegion;
10369         m_aGraphicsStack.front().m_bClipRegion = true;
10370     }
10371 }
10372 
10373 void PDFWriterImpl::createNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr )
10374 {
10375     if (nPageNr < 0)
10376         nPageNr = m_nCurrentPage;
10377 
10378     if (nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size())
10379         return;
10380 
10381     m_aNotes.emplace_back();
10382     auto & rNoteEntry = m_aNotes.back();
10383     rNoteEntry.m_nObject = createObject();
10384     rNoteEntry.m_aPopUpAnnotation.m_nObject = createObject();
10385     rNoteEntry.m_aPopUpAnnotation.m_nParentObject = rNoteEntry.m_nObject;
10386     rNoteEntry.m_aContents = rNote;
10387     rNoteEntry.m_aRect = rRect;
10388     rNoteEntry.m_nPage = nPageNr;
10389     // convert to default user space now, since the mapmode may change
10390     m_aPages[nPageNr].convertRect(rNoteEntry.m_aRect);
10391 
10392     // insert note to page's annotation list
10393     m_aPages[nPageNr].m_aAnnotations.push_back(rNoteEntry.m_nObject);
10394     m_aPages[nPageNr].m_aAnnotations.push_back(rNoteEntry.m_aPopUpAnnotation.m_nObject);
10395 }
10396 
10397 sal_Int32 PDFWriterImpl::createLink(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText)
10398 {
10399     if( nPageNr < 0 )
10400         nPageNr = m_nCurrentPage;
10401 
10402     if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
10403         return -1;
10404 
10405     sal_Int32 nRet = m_aLinks.size();
10406 
10407     m_aLinks.emplace_back(rAltText);
10408     m_aLinks.back().m_nObject   = createObject();
10409     m_aLinks.back().m_nPage     = nPageNr;
10410     m_aLinks.back().m_aRect     = rRect;
10411     // convert to default user space now, since the mapmode may change
10412     m_aPages[nPageNr].convertRect( m_aLinks.back().m_aRect );
10413 
10414     // insert link to page's annotation list
10415     m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aLinks.back().m_nObject );
10416 
10417     return nRet;
10418 }
10419 
10420 sal_Int32 PDFWriterImpl::createScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText, OUString const& rMimeType)
10421 {
10422     if (nPageNr < 0)
10423         nPageNr = m_nCurrentPage;
10424 
10425     if (nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size())
10426         return -1;
10427 
10428     sal_Int32 nRet = m_aScreens.size();
10429 
10430     m_aScreens.emplace_back(rAltText, rMimeType);
10431     m_aScreens.back().m_nObject = createObject();
10432     m_aScreens.back().m_nPage = nPageNr;
10433     m_aScreens.back().m_aRect = rRect;
10434     // Convert to default user space now, since the mapmode may change.
10435     m_aPages[nPageNr].convertRect(m_aScreens.back().m_aRect);
10436 
10437     // Insert link to page's annotation list.
10438     m_aPages[nPageNr].m_aAnnotations.push_back(m_aScreens.back().m_nObject);
10439 
10440     return nRet;
10441 }
10442 
10443 sal_Int32 PDFWriterImpl::createNamedDest( const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10444 {
10445     if( nPageNr < 0 )
10446         nPageNr = m_nCurrentPage;
10447 
10448     if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
10449         return -1;
10450 
10451     sal_Int32 nRet = m_aNamedDests.size();
10452 
10453     m_aNamedDests.emplace_back( );
10454     m_aNamedDests.back().m_aDestName = sDestName;
10455     m_aNamedDests.back().m_nPage = nPageNr;
10456     m_aNamedDests.back().m_eType = eType;
10457     m_aNamedDests.back().m_aRect = rRect;
10458     // convert to default user space now, since the mapmode may change
10459     m_aPages[nPageNr].convertRect( m_aNamedDests.back().m_aRect );
10460 
10461     return nRet;
10462 }
10463 
10464 sal_Int32 PDFWriterImpl::createDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10465 {
10466     if( nPageNr < 0 )
10467         nPageNr = m_nCurrentPage;
10468 
10469     if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
10470         return -1;
10471 
10472     sal_Int32 nRet = m_aDests.size();
10473 
10474     m_aDests.emplace_back( );
10475     m_aDests.back().m_nPage = nPageNr;
10476     m_aDests.back().m_eType = eType;
10477     m_aDests.back().m_aRect = rRect;
10478     // convert to default user space now, since the mapmode may change
10479     m_aPages[nPageNr].convertRect( m_aDests.back().m_aRect );
10480 
10481     return nRet;
10482 }
10483 
10484 sal_Int32 PDFWriterImpl::registerDestReference( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10485 {
10486     m_aDestinationIdTranslation[ nDestId ] = createDest( rRect, nPageNr, eType );
10487     return m_aDestinationIdTranslation[ nDestId ];
10488 }
10489 
10490 void PDFWriterImpl::setLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
10491 {
10492     if( nLinkId < 0 || o3tl::make_unsigned(nLinkId) >= m_aLinks.size() )
10493         return;
10494     if( nDestId < 0 || o3tl::make_unsigned(nDestId) >= m_aDests.size() )
10495         return;
10496 
10497     m_aLinks[ nLinkId ].m_nDest = nDestId;
10498 }
10499 
10500 void PDFWriterImpl::setLinkURL( sal_Int32 nLinkId, const OUString& rURL )
10501 {
10502     if( nLinkId < 0 || o3tl::make_unsigned(nLinkId) >= m_aLinks.size() )
10503         return;
10504 
10505     m_aLinks[ nLinkId ].m_nDest = -1;
10506 
10507     using namespace ::com::sun::star;
10508 
10509     if (!m_xTrans.is())
10510     {
10511         uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
10512         m_xTrans = util::URLTransformer::create(xContext);
10513     }
10514 
10515     util::URL aURL;
10516     aURL.Complete = rURL;
10517 
10518     m_xTrans->parseStrict( aURL );
10519 
10520     m_aLinks[ nLinkId ].m_aURL  = aURL.Complete;
10521 }
10522 
10523 void PDFWriterImpl::setScreenURL(sal_Int32 nScreenId, const OUString& rURL)
10524 {
10525     if (nScreenId < 0 || o3tl::make_unsigned(nScreenId) >= m_aScreens.size())
10526         return;
10527 
10528     m_aScreens[nScreenId].m_aURL = rURL;
10529 }
10530 
10531 void PDFWriterImpl::setScreenStream(sal_Int32 nScreenId, const OUString& rURL)
10532 {
10533     if (nScreenId < 0 || o3tl::make_unsigned(nScreenId) >= m_aScreens.size())
10534         return;
10535 
10536     m_aScreens[nScreenId].m_aTempFileURL = rURL;
10537     m_aScreens[nScreenId].m_nTempFileObject = createObject();
10538 }
10539 
10540 void PDFWriterImpl::setLinkPropertyId( sal_Int32 nLinkId, sal_Int32 nPropertyId )
10541 {
10542     m_aLinkPropertyMap[ nPropertyId ] = nLinkId;
10543 }
10544 
10545 sal_Int32 PDFWriterImpl::createOutlineItem( sal_Int32 nParent, std::u16string_view rText, sal_Int32 nDestID )
10546 {
10547     // create new item
10548     sal_Int32 nNewItem = m_aOutline.size();
10549     m_aOutline.emplace_back( );
10550 
10551     // set item attributes
10552     setOutlineItemParent( nNewItem, nParent );
10553     setOutlineItemText( nNewItem, rText );
10554     setOutlineItemDest( nNewItem, nDestID );
10555 
10556     return nNewItem;
10557 }
10558 
10559 void PDFWriterImpl::setOutlineItemParent( sal_Int32 nItem, sal_Int32 nNewParent )
10560 {
10561     if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() )
10562         return;
10563 
10564     if( nNewParent < 0 || o3tl::make_unsigned(nNewParent) >= m_aOutline.size() || nNewParent == nItem )
10565     {
10566         nNewParent = 0;
10567     }
10568     // insert item to new parent's list of children
10569     m_aOutline[ nNewParent ].m_aChildren.push_back( nItem );
10570 }
10571 
10572 void PDFWriterImpl::setOutlineItemText( sal_Int32 nItem, std::u16string_view rText )
10573 {
10574     if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() )
10575         return;
10576 
10577     m_aOutline[ nItem ].m_aTitle = psp::WhitespaceToSpace( rText );
10578 }
10579 
10580 void PDFWriterImpl::setOutlineItemDest( sal_Int32 nItem, sal_Int32 nDestID )
10581 {
10582     if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() ) // item does not exist
10583         return;
10584     if( nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size() ) // dest does not exist
10585         return;
10586     m_aOutline[nItem].m_nDestID = nDestID;
10587 }
10588 
10589 const char* PDFWriterImpl::getStructureTag( PDFWriter::StructElement eType )
10590 {
10591     static constexpr auto aTagStrings = frozen::make_map<PDFWriter::StructElement, const char*>({
10592         { PDFWriter::NonStructElement, "NonStruct" },
10593         { PDFWriter::Document, "Document" },
10594         { PDFWriter::Part,         "Part" },
10595         { PDFWriter::Article,      "Art" },
10596         { PDFWriter::Section,     "Sect" },
10597         { PDFWriter::Division,    "Div" },
10598         { PDFWriter::BlockQuote,  "BlockQuote" },
10599         { PDFWriter::Caption,     "Caption" },
10600         { PDFWriter::TOC,         "TOC" },
10601         { PDFWriter::TOCI,        "TOCI" },
10602         { PDFWriter::Index,       "Index" },
10603         { PDFWriter::Paragraph,   "P" },
10604         { PDFWriter::Heading,     "H" },
10605         { PDFWriter::H1,          "H1" },
10606         { PDFWriter::H2,          "H2" },
10607         { PDFWriter::H3,          "H3" },
10608         { PDFWriter::H4,          "H4" },
10609         { PDFWriter::H5,          "H5" },
10610         { PDFWriter::H6,          "H6" },
10611         { PDFWriter::List,        "L" },
10612         { PDFWriter::ListItem,    "LI" },
10613         { PDFWriter::LILabel,     "Lbl" },
10614         { PDFWriter::LIBody,      "LBody" },
10615         { PDFWriter::Table,       "Table" },
10616         { PDFWriter::TableRow,    "TR" },
10617         { PDFWriter::TableHeader, "TH" },
10618         { PDFWriter::TableData,   "TD" },
10619         { PDFWriter::Span,        "Span" },
10620         { PDFWriter::Quote,       "Quote" },
10621         { PDFWriter::Note,        "Note" },
10622         { PDFWriter::Reference,   "Reference" },
10623         { PDFWriter::BibEntry,    "BibEntry" },
10624         { PDFWriter::Code,        "Code" },
10625         { PDFWriter::Link,        "Link" },
10626         { PDFWriter::Annot,       "Annot" },
10627         { PDFWriter::Figure,      "Figure" },
10628         { PDFWriter::Formula,     "Formula"},
10629         { PDFWriter::Form,        "Form" }
10630     });
10631 
10632     if (eType == PDFWriter::Annot
10633         && m_aContext.Version < PDFWriter::PDFVersion::PDF_1_5)
10634     {
10635         return "Figure"; // fallback
10636     }
10637 
10638     auto it = aTagStrings.find( eType );
10639 
10640     return it != aTagStrings.end() ? it->second : "Div";
10641 }
10642 
10643 void PDFWriterImpl::addRoleMap(OString aAlias, PDFWriter::StructElement eType)
10644 {
10645     OString aTag = getStructureTag(eType);
10646     // For PDF/UA it's not allowed to map an alias with the same name.
10647     // Not aware of a reason for doing it in any case, so just don't do it.
10648     if (aAlias != aTag)
10649         m_aRoleMap[aAlias] = aTag;
10650 }
10651 
10652 void PDFWriterImpl::beginStructureElementMCSeq()
10653 {
10654     assert(m_nCurrentStructElement == 0 || m_aStructure[m_nCurrentStructElement].m_oType);
10655     if( m_bEmitStructure &&
10656         m_nCurrentStructElement > 0 && // StructTreeRoot
10657         // Document = SwPageFrame => this is not *inside* the page content
10658         // stream so do not emit MCID!
10659         m_aStructure[m_nCurrentStructElement].m_oType &&
10660         *m_aStructure[m_nCurrentStructElement].m_oType != PDFWriter::Document &&
10661         ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
10662         )
10663     {
10664         PDFStructureElement& rEle = m_aStructure[ m_nCurrentStructElement ];
10665         OStringBuffer aLine( 128 );
10666         sal_Int32 nMCID = m_aPages[ m_nCurrentPage ].m_aMCIDParents.size();
10667         aLine.append( "/" );
10668         if( !rEle.m_aAlias.isEmpty() )
10669             aLine.append( rEle.m_aAlias );
10670         else
10671             aLine.append( getStructureTag(*rEle.m_oType) );
10672         aLine.append( "<</MCID " );
10673         aLine.append( nMCID );
10674         aLine.append( ">>BDC\n" );
10675         writeBuffer( aLine );
10676 
10677         // update the element's content list
10678         SAL_INFO("vcl.pdfwriter", "beginning marked content id " << nMCID << " on page object "
10679                  << m_aPages[ m_nCurrentPage ].m_nPageObject << ", structure first page = "
10680                  << rEle.m_nFirstPageObject);
10681         rEle.m_aKids.emplace_back( nMCID, m_aPages[m_nCurrentPage].m_nPageObject );
10682         // update the page's mcid parent list
10683         m_aPages[ m_nCurrentPage ].m_aMCIDParents.push_back( rEle.m_nObject );
10684         // mark element MC sequence as open
10685         rEle.m_bOpenMCSeq = true;
10686     }
10687     // handle artifacts
10688     else if( ! m_bEmitStructure && m_aContext.Tagged &&
10689                m_nCurrentStructElement > 0 &&
10690                m_aStructure[m_nCurrentStructElement].m_oType &&
10691                *m_aStructure[m_nCurrentStructElement].m_oType == PDFWriter::NonStructElement &&
10692              ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
10693              )
10694     {
10695         OString aLine = "/Artifact ";
10696         writeBuffer( aLine );
10697         // emit property list if requested
10698         OStringBuffer buf;
10699         for (auto const& rAttr : m_aStructure[m_nCurrentStructElement].m_aAttributes)
10700         {
10701             appendStructureAttributeLine(rAttr.first, rAttr.second, buf, false);
10702         }
10703         if (buf.isEmpty())
10704         {
10705             writeBuffer("BMC\n");
10706         }
10707         else
10708         {
10709             writeBuffer("<<");
10710             writeBuffer(buf);
10711             writeBuffer(">> BDC\n");
10712         }
10713         // mark element MC sequence as open
10714         m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = true;
10715     }
10716 }
10717 
10718 void PDFWriterImpl::endStructureElementMCSeq(EndMode const endMode)
10719 {
10720     if (m_nCurrentStructElement > 0 // not StructTreeRoot
10721         && m_aStructure[m_nCurrentStructElement].m_oType
10722         && (m_bEmitStructure
10723             || (endMode != EndMode::OnlyStruct
10724                 && m_aStructure[m_nCurrentStructElement].m_oType == PDFWriter::NonStructElement))
10725         && m_aStructure[m_nCurrentStructElement].m_bOpenMCSeq)
10726     {
10727         writeBuffer( "EMC\n" );
10728         m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = false;
10729     }
10730 }
10731 
10732 bool PDFWriterImpl::checkEmitStructure()
10733 {
10734     bool bEmit = false;
10735     if( m_aContext.Tagged )
10736     {
10737         bEmit = true;
10738         sal_Int32 nEle = m_nCurrentStructElement;
10739         while( nEle > 0 && o3tl::make_unsigned(nEle) < m_aStructure.size() )
10740         {
10741             if (m_aStructure[nEle].m_oType
10742                 && *m_aStructure[nEle].m_oType == PDFWriter::NonStructElement)
10743             {
10744                 bEmit = false;
10745                 break;
10746             }
10747             nEle = m_aStructure[ nEle ].m_nParentElement;
10748         }
10749     }
10750     return bEmit;
10751 }
10752 
10753 sal_Int32 PDFWriterImpl::ensureStructureElement()
10754 {
10755     if( ! m_aContext.Tagged )
10756         return -1;
10757 
10758     sal_Int32 nNewId = sal_Int32(m_aStructure.size());
10759     m_aStructure.emplace_back();
10760     PDFStructureElement& rEle = m_aStructure.back();
10761     // leave rEle.m_oType uninitialised
10762     rEle.m_nOwnElement      = nNewId;
10763     // temporary parent
10764     rEle.m_nParentElement   = m_nCurrentStructElement;
10765     rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject;
10766     m_aStructure[ m_nCurrentStructElement ].m_aChildren.push_back( nNewId );
10767     return nNewId;
10768 }
10769 
10770 void PDFWriterImpl::initStructureElement(sal_Int32 const id,
10771         PDFWriter::StructElement const eType, std::u16string_view const rAlias)
10772 {
10773     if( ! m_aContext.Tagged )
10774         return;
10775 
10776     if( m_nCurrentStructElement == 0 &&
10777         eType != PDFWriter::Document && eType != PDFWriter::NonStructElement )
10778     {
10779         // struct tree root hit, but not beginning document
10780         // this might happen with setCurrentStructureElement
10781         // silently insert structure into document again if one properly exists
10782         if( ! m_aStructure[ 0 ].m_aChildren.empty() )
10783         {
10784             const std::vector< sal_Int32 >& rRootChildren = m_aStructure[0].m_aChildren;
10785             auto it = std::find_if(rRootChildren.begin(), rRootChildren.end(),
10786                 [&](sal_Int32 nElement) {
10787                     return m_aStructure[nElement].m_oType
10788                         && *m_aStructure[nElement].m_oType == PDFWriter::Document; });
10789             if( it != rRootChildren.end() )
10790             {
10791                 m_nCurrentStructElement = *it;
10792                 SAL_WARN( "vcl.pdfwriter", "Structure element inserted to StructTreeRoot that is not a document" );
10793             }
10794             else {
10795                 OSL_FAIL( "document structure in disorder !" );
10796             }
10797         }
10798         else {
10799             OSL_FAIL( "PDF document structure MUST be contained in a Document element" );
10800         }
10801     }
10802 
10803     PDFStructureElement& rEle = m_aStructure[id];
10804     assert(!rEle.m_oType);
10805     rEle.m_oType.emplace(eType);
10806     // remove it from its possibly placeholder parent; append to real parent
10807     auto const it(std::find(m_aStructure[rEle.m_nParentElement].m_aChildren.begin(),
10808         m_aStructure[rEle.m_nParentElement].m_aChildren.end(), id));
10809     assert(it != m_aStructure[rEle.m_nParentElement].m_aChildren.end());
10810     m_aStructure[rEle.m_nParentElement].m_aChildren.erase(it);
10811     rEle.m_nParentElement   = m_nCurrentStructElement;
10812     rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject;
10813     m_aStructure[m_nCurrentStructElement].m_aChildren.push_back(id);
10814 
10815     // handle alias names
10816     if( !rAlias.empty() && eType != PDFWriter::NonStructElement )
10817     {
10818         OStringBuffer aNameBuf( rAlias.size() );
10819         appendName( rAlias, aNameBuf );
10820         OString aAliasName( aNameBuf.makeStringAndClear() );
10821         rEle.m_aAlias = aAliasName;
10822         addRoleMap(aAliasName, eType);
10823     }
10824 
10825     if (m_bEmitStructure && eType != PDFWriter::NonStructElement) // don't create nonexistent objects
10826     {
10827         rEle.m_nObject      = createObject();
10828         // update parent's kids list
10829         m_aStructure[ rEle.m_nParentElement ].m_aKids.emplace_back(rEle.m_nObject);
10830         // ISO 14289-1:2014, Clause: 7.9
10831         if (*rEle.m_oType == PDFWriter::Note)
10832         {
10833             m_StructElemObjsWithID.insert(rEle.m_nObject);
10834         }
10835     }
10836 }
10837 
10838 void PDFWriterImpl::beginStructureElement(sal_Int32 const id)
10839 {
10840     if( m_nCurrentPage < 0 )
10841         return;
10842 
10843     if( ! m_aContext.Tagged )
10844         return;
10845 
10846     assert(id != -1 && "cid#1538888 doesn't consider above m_aContext.Tagged");
10847 
10848     // close eventual current MC sequence
10849     endStructureElementMCSeq(EndMode::OnlyStruct);
10850 
10851     PDFStructureElement& rEle = m_aStructure[id];
10852     m_StructElementStack.push(m_nCurrentStructElement);
10853     m_nCurrentStructElement = id;
10854 
10855     if (g_bDebugDisableCompression)
10856     {
10857         OStringBuffer aLine( "beginStructureElement " );
10858         aLine.append( m_nCurrentStructElement );
10859         aLine.append( ": " );
10860         aLine.append( rEle.m_oType
10861             ? getStructureTag(*rEle.m_oType)
10862             : "<placeholder>" );
10863         if( !rEle.m_aAlias.isEmpty() )
10864         {
10865             aLine.append( " aliased as \"" );
10866             aLine.append( rEle.m_aAlias );
10867             aLine.append( '\"' );
10868         }
10869         emitComment( aLine.getStr() );
10870     }
10871 
10872     // check whether to emit structure henceforth
10873     m_bEmitStructure = checkEmitStructure();
10874 }
10875 
10876 void PDFWriterImpl::endStructureElement()
10877 {
10878     if( m_nCurrentPage < 0 )
10879         return;
10880 
10881     if( ! m_aContext.Tagged )
10882         return;
10883 
10884     if( m_nCurrentStructElement == 0 )
10885     {
10886         // hit the struct tree root, that means there is an endStructureElement
10887         // without corresponding beginStructureElement
10888         return;
10889     }
10890 
10891     // end the marked content sequence
10892     endStructureElementMCSeq();
10893 
10894     OStringBuffer aLine;
10895     if (g_bDebugDisableCompression)
10896     {
10897         aLine.append( "endStructureElement " );
10898         aLine.append( m_nCurrentStructElement );
10899         aLine.append( ": " );
10900         aLine.append( m_aStructure[m_nCurrentStructElement].m_oType
10901             ? getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
10902             : "<placeholder>" );
10903         if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
10904         {
10905             aLine.append( " aliased as \"" );
10906             aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
10907             aLine.append( '\"' );
10908         }
10909     }
10910 
10911     // "end" the structure element, the parent becomes current element
10912     m_nCurrentStructElement = m_StructElementStack.top();
10913     m_StructElementStack.pop();
10914 
10915     // check whether to emit structure henceforth
10916     m_bEmitStructure = checkEmitStructure();
10917 
10918     if (g_bDebugDisableCompression && m_bEmitStructure)
10919     {
10920         emitComment( aLine.getStr() );
10921     }
10922 }
10923 
10924 namespace {
10925 
10926 void removePlaceholderSEImpl(std::vector<PDFStructureElement> & rStructure,
10927         std::vector<sal_Int32>::iterator & rParentIt)
10928 {
10929     PDFStructureElement& rEle(rStructure[*rParentIt]);
10930     removePlaceholderSE(rStructure, rEle);
10931 
10932     if (!rEle.m_oType)
10933     {
10934         // Placeholder was not initialised - should not happen when printing
10935         // a full page, but might if a selection is printed, which can be only
10936         // a shape without its anchor.
10937         // Handle this by moving the children to the parent SE.
10938         PDFStructureElement & rParent(rStructure[rEle.m_nParentElement]);
10939         rParentIt = rParent.m_aChildren.erase(rParentIt);
10940         std::vector<sal_Int32> children;
10941         for (auto const child : rEle.m_aChildren)
10942         {
10943             PDFStructureElement& rChild = rStructure[child];
10944             rChild.m_nParentElement = rEle.m_nParentElement;
10945             children.push_back(rChild.m_nOwnElement);
10946         }
10947         rParentIt = rParent.m_aChildren.insert(rParentIt, children.begin(), children.end())
10948             + children.size();
10949     }
10950     else
10951     {
10952         ++rParentIt;
10953     }
10954 
10955 }
10956 
10957 void removePlaceholderSE(std::vector<PDFStructureElement> & rStructure, PDFStructureElement& rEle)
10958 {
10959     for (auto it = rEle.m_aChildren.begin(); it != rEle.m_aChildren.end(); )
10960     {
10961         removePlaceholderSEImpl(rStructure, it);
10962     }
10963 }
10964 
10965 } // end anonymous namespace
10966 
10967 /*
10968  * This function adds an internal structure list container to overcome the 8191 elements array limitation
10969  * in kids element emission.
10970  * Recursive function
10971  *
10972  */
10973 void PDFWriterImpl::addInternalStructureContainer( PDFStructureElement& rEle )
10974 {
10975     if (rEle.m_nOwnElement != rEle.m_nParentElement
10976         && *rEle.m_oType == PDFWriter::NonStructElement)
10977     {
10978         return;
10979     }
10980 
10981     for (auto const& child : rEle.m_aChildren)
10982     {
10983         assert(child > 0 && o3tl::make_unsigned(child) < m_aStructure.size());
10984         if( child > 0 && o3tl::make_unsigned(child) < m_aStructure.size() )
10985         {
10986             PDFStructureElement& rChild = m_aStructure[ child ];
10987             if (*rChild.m_oType != PDFWriter::NonStructElement)
10988             {
10989                 //triggered when a child of the rEle element is found
10990                 assert(rChild.m_nParentElement == rEle.m_nOwnElement);
10991                 if( rChild.m_nParentElement == rEle.m_nOwnElement )
10992                     addInternalStructureContainer( rChild );//examine the child
10993                 else
10994                 {
10995                     OSL_FAIL( "PDFWriterImpl::addInternalStructureContainer: invalid child structure element" );
10996                     SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure element with id " << child );
10997                 }
10998             }
10999         }
11000         else
11001         {
11002             OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
11003             SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure id " << child );
11004         }
11005     }
11006 
11007     if( rEle.m_nOwnElement == rEle.m_nParentElement )
11008         return;
11009 
11010     if( rEle.m_aKids.empty() )
11011         return;
11012 
11013     if( rEle.m_aKids.size() <= ncMaxPDFArraySize )        return;
11014 
11015     //then we need to add the containers for the kids elements
11016     // a list to be used for the new kid element
11017     std::list< PDFStructureElementKid > aNewKids;
11018     std::vector< sal_Int32 > aNewChildren;
11019 
11020     // add Div in RoleMap, in case no one else did (TODO: is it needed? Is it dangerous?)
11021     OString aAliasName("Div");
11022     addRoleMap(aAliasName, PDFWriter::Division);
11023 
11024     while( rEle.m_aKids.size() > ncMaxPDFArraySize )
11025     {
11026         sal_Int32 nCurrentStructElement = rEle.m_nOwnElement;
11027         sal_Int32 nNewId = sal_Int32(m_aStructure.size());
11028         m_aStructure.emplace_back( );
11029         PDFStructureElement& rEleNew = m_aStructure.back();
11030         rEleNew.m_aAlias            = aAliasName;
11031         rEleNew.m_oType.emplace(PDFWriter::Division); // a new Div type container
11032         rEleNew.m_nOwnElement       = nNewId;
11033         rEleNew.m_nParentElement    = nCurrentStructElement;
11034         //inherit the same page as the first child to be reparented
11035         rEleNew.m_nFirstPageObject  = m_aStructure[ rEle.m_aChildren.front() ].m_nFirstPageObject;
11036         rEleNew.m_nObject           = createObject();//assign a PDF object number
11037         //add the object to the kid list of the parent
11038         aNewKids.emplace_back( rEleNew.m_nObject );
11039         aNewChildren.push_back( nNewId );
11040 
11041         std::vector< sal_Int32 >::iterator aChildEndIt( rEle.m_aChildren.begin() );
11042         std::list< PDFStructureElementKid >::iterator aKidEndIt( rEle.m_aKids.begin() );
11043         advance( aChildEndIt, ncMaxPDFArraySize );
11044         advance( aKidEndIt, ncMaxPDFArraySize );
11045 
11046         rEleNew.m_aKids.splice( rEleNew.m_aKids.begin(),
11047                                 rEle.m_aKids,
11048                                 rEle.m_aKids.begin(),
11049                                 aKidEndIt );
11050         rEleNew.m_aChildren.insert( rEleNew.m_aChildren.begin(),
11051                                     rEle.m_aChildren.begin(),
11052                                     aChildEndIt );
11053         rEle.m_aChildren.erase( rEle.m_aChildren.begin(), aChildEndIt );
11054 
11055         // set the kid's new parent
11056         for (auto const& child : rEleNew.m_aChildren)
11057         {
11058             m_aStructure[ child ].m_nParentElement = nNewId;
11059         }
11060     }
11061     //finally add the new kids resulting from the container added
11062     rEle.m_aKids.insert( rEle.m_aKids.begin(), aNewKids.begin(), aNewKids.end() );
11063     rEle.m_aChildren.insert( rEle.m_aChildren.begin(), aNewChildren.begin(), aNewChildren.end() );
11064 }
11065 
11066 bool PDFWriterImpl::setCurrentStructureElement( sal_Int32 nEle )
11067 {
11068     bool bSuccess = false;
11069 
11070     if( m_aContext.Tagged && nEle >= 0 && o3tl::make_unsigned(nEle) < m_aStructure.size() )
11071     {
11072         // end eventual previous marked content sequence
11073         endStructureElementMCSeq();
11074 
11075         m_nCurrentStructElement = nEle;
11076         m_bEmitStructure = checkEmitStructure();
11077         if (g_bDebugDisableCompression)
11078         {
11079             OStringBuffer aLine( "setCurrentStructureElement " );
11080             aLine.append( m_nCurrentStructElement );
11081             aLine.append( ": " );
11082             aLine.append( m_aStructure[m_nCurrentStructElement].m_oType
11083                 ? getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
11084                 : "<placeholder>" );
11085             if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
11086             {
11087                 aLine.append( " aliased as \"" );
11088                 aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
11089                 aLine.append( '\"' );
11090             }
11091             if( ! m_bEmitStructure )
11092                 aLine.append( " (inside NonStruct)" );
11093             emitComment( aLine.getStr() );
11094         }
11095         bSuccess = true;
11096     }
11097 
11098     return bSuccess;
11099 }
11100 
11101 bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr, enum PDFWriter::StructAttributeValue eVal )
11102 {
11103     if( !m_aContext.Tagged )
11104         return false;
11105 
11106     assert(m_aStructure[m_nCurrentStructElement].m_oType);
11107     bool bInsert = false;
11108     if (m_nCurrentStructElement > 0
11109         && (m_bEmitStructure
11110             // allow it for topmost non-structured element
11111             || (m_aContext.Tagged
11112                 && (0 == m_aStructure[m_nCurrentStructElement].m_nParentElement
11113                     || !m_aStructure[m_aStructure[m_nCurrentStructElement].m_nParentElement].m_oType
11114                     || *m_aStructure[m_aStructure[m_nCurrentStructElement].m_nParentElement].m_oType != PDFWriter::NonStructElement))))
11115     {
11116         PDFWriter::StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType;
11117         switch( eAttr )
11118         {
11119             case PDFWriter::Placement:
11120                 if( eVal == PDFWriter::Block        ||
11121                     eVal == PDFWriter::Inline       ||
11122                     eVal == PDFWriter::Before       ||
11123                     eVal == PDFWriter::Start        ||
11124                     eVal == PDFWriter::End )
11125                     bInsert = true;
11126                 break;
11127             case PDFWriter::WritingMode:
11128                 if( eVal == PDFWriter::LrTb         ||
11129                     eVal == PDFWriter::RlTb         ||
11130                     eVal == PDFWriter::TbRl )
11131                 {
11132                     bInsert = true;
11133                 }
11134                 break;
11135             case PDFWriter::TextAlign:
11136                 if( eVal == PDFWriter::Start        ||
11137                     eVal == PDFWriter::Center       ||
11138                     eVal == PDFWriter::End          ||
11139                     eVal == PDFWriter::Justify )
11140                 {
11141                     if( eType == PDFWriter::Paragraph   ||
11142                         eType == PDFWriter::Heading     ||
11143                         eType == PDFWriter::H1          ||
11144                         eType == PDFWriter::H2          ||
11145                         eType == PDFWriter::H3          ||
11146                         eType == PDFWriter::H4          ||
11147                         eType == PDFWriter::H5          ||
11148                         eType == PDFWriter::H6          ||
11149                         eType == PDFWriter::List        ||
11150                         eType == PDFWriter::ListItem    ||
11151                         eType == PDFWriter::LILabel     ||
11152                         eType == PDFWriter::LIBody      ||
11153                         eType == PDFWriter::Table       ||
11154                         eType == PDFWriter::TableRow    ||
11155                         eType == PDFWriter::TableHeader ||
11156                         eType == PDFWriter::TableData )
11157                     {
11158                         bInsert = true;
11159                     }
11160                 }
11161                 break;
11162             case PDFWriter::Width:
11163             case PDFWriter::Height:
11164                 if( eVal == PDFWriter::Auto )
11165                 {
11166                     if( eType == PDFWriter::Figure      ||
11167                         eType == PDFWriter::Formula     ||
11168                         eType == PDFWriter::Form        ||
11169                         eType == PDFWriter::Table       ||
11170                         eType == PDFWriter::TableHeader ||
11171                         eType == PDFWriter::TableData )
11172                     {
11173                         bInsert = true;
11174                     }
11175                 }
11176                 break;
11177             case PDFWriter::BlockAlign:
11178                 if( eVal == PDFWriter::Before       ||
11179                     eVal == PDFWriter::Middle       ||
11180                     eVal == PDFWriter::After        ||
11181                     eVal == PDFWriter::Justify )
11182                 {
11183                     if( eType == PDFWriter::TableHeader ||
11184                         eType == PDFWriter::TableData )
11185                     {
11186                         bInsert = true;
11187                     }
11188                 }
11189                 break;
11190             case PDFWriter::InlineAlign:
11191                 if( eVal == PDFWriter::Start        ||
11192                     eVal == PDFWriter::Center       ||
11193                     eVal == PDFWriter::End )
11194                 {
11195                     if( eType == PDFWriter::TableHeader ||
11196                         eType == PDFWriter::TableData )
11197                     {
11198                         bInsert = true;
11199                     }
11200                 }
11201                 break;
11202             case PDFWriter::LineHeight:
11203                 if( eVal == PDFWriter::Normal       ||
11204                     eVal == PDFWriter::Auto )
11205                 {
11206                     // only for ILSE and BLSE
11207                     if( eType == PDFWriter::Paragraph   ||
11208                         eType == PDFWriter::Heading     ||
11209                         eType == PDFWriter::H1          ||
11210                         eType == PDFWriter::H2          ||
11211                         eType == PDFWriter::H3          ||
11212                         eType == PDFWriter::H4          ||
11213                         eType == PDFWriter::H5          ||
11214                         eType == PDFWriter::H6          ||
11215                         eType == PDFWriter::List        ||
11216                         eType == PDFWriter::ListItem    ||
11217                         eType == PDFWriter::LILabel     ||
11218                         eType == PDFWriter::LIBody      ||
11219                         eType == PDFWriter::Table       ||
11220                         eType == PDFWriter::TableRow    ||
11221                         eType == PDFWriter::TableHeader ||
11222                         eType == PDFWriter::TableData   ||
11223                         eType == PDFWriter::Span        ||
11224                         eType == PDFWriter::Quote       ||
11225                         eType == PDFWriter::Note        ||
11226                         eType == PDFWriter::Reference   ||
11227                         eType == PDFWriter::BibEntry    ||
11228                         eType == PDFWriter::Code        ||
11229                         eType == PDFWriter::Link )
11230                     {
11231                         bInsert = true;
11232                     }
11233                 }
11234                 break;
11235             case PDFWriter::TextDecorationType:
11236                 if( eVal == PDFWriter::NONE         ||
11237                     eVal == PDFWriter::Underline    ||
11238                     eVal == PDFWriter::Overline     ||
11239                     eVal == PDFWriter::LineThrough )
11240                 {
11241                     // only for ILSE and BLSE
11242                     if( eType == PDFWriter::Paragraph   ||
11243                         eType == PDFWriter::Heading     ||
11244                         eType == PDFWriter::H1          ||
11245                         eType == PDFWriter::H2          ||
11246                         eType == PDFWriter::H3          ||
11247                         eType == PDFWriter::H4          ||
11248                         eType == PDFWriter::H5          ||
11249                         eType == PDFWriter::H6          ||
11250                         eType == PDFWriter::List        ||
11251                         eType == PDFWriter::ListItem    ||
11252                         eType == PDFWriter::LILabel     ||
11253                         eType == PDFWriter::LIBody      ||
11254                         eType == PDFWriter::Table       ||
11255                         eType == PDFWriter::TableRow    ||
11256                         eType == PDFWriter::TableHeader ||
11257                         eType == PDFWriter::TableData   ||
11258                         eType == PDFWriter::Span        ||
11259                         eType == PDFWriter::Quote       ||
11260                         eType == PDFWriter::Note        ||
11261                         eType == PDFWriter::Reference   ||
11262                         eType == PDFWriter::BibEntry    ||
11263                         eType == PDFWriter::Code        ||
11264                         eType == PDFWriter::Link )
11265                     {
11266                         bInsert = true;
11267                     }
11268                 }
11269                 break;
11270             case PDFWriter::Scope:
11271                 if (eVal == PDFWriter::Row || eVal == PDFWriter::Column || eVal == PDFWriter::Both)
11272                 {
11273                     if (eType == PDFWriter::TableHeader
11274                         && PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version)
11275                     {
11276                         bInsert = true;
11277                     }
11278                 }
11279                 break;
11280             case PDFWriter::Type:
11281                 if (eVal == PDFWriter::Pagination || eVal == PDFWriter::Layout || eVal == PDFWriter::Page)
11282                     // + Background for PDF >= 1.7
11283                 {
11284                     if (eType == PDFWriter::NonStructElement)
11285                     {
11286                         bInsert = true;
11287                     }
11288                 }
11289                 break;
11290             case PDFWriter::Subtype:
11291                 if (eVal == PDFWriter::Header || eVal == PDFWriter::Footer || eVal == PDFWriter::Watermark)
11292                 {
11293                     if (eType == PDFWriter::NonStructElement
11294                         && PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
11295                     {
11296                         bInsert = true;
11297                     }
11298                 }
11299                 break;
11300             case PDFWriter::Role:
11301                 if (eVal == PDFWriter::Rb || eVal == PDFWriter::Cb || eVal == PDFWriter::Pb || eVal == PDFWriter::Tv)
11302                 {
11303                     if (eType == PDFWriter::Form
11304                         && PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
11305                     {
11306                         bInsert = true;
11307                     }
11308                 }
11309                 break;
11310             case PDFWriter::ListNumbering:
11311                 if( eVal == PDFWriter::NONE         ||
11312                     eVal == PDFWriter::Disc         ||
11313                     eVal == PDFWriter::Circle       ||
11314                     eVal == PDFWriter::Square       ||
11315                     eVal == PDFWriter::Decimal      ||
11316                     eVal == PDFWriter::UpperRoman   ||
11317                     eVal == PDFWriter::LowerRoman   ||
11318                     eVal == PDFWriter::UpperAlpha   ||
11319                     eVal == PDFWriter::LowerAlpha )
11320                 {
11321                     if( eType == PDFWriter::List )
11322                         bInsert = true;
11323                 }
11324                 break;
11325             default: break;
11326         }
11327     }
11328 
11329     if( bInsert )
11330         m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( eVal );
11331     else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
11332         SAL_INFO("vcl.pdfwriter",
11333                  "rejecting setStructureAttribute( " << getAttributeTag( eAttr )
11334                  << ", " << getAttributeValueTag( eVal )
11335                  << " ) on " << getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
11336                  << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias
11337                  << ") element");
11338 
11339     return bInsert;
11340 }
11341 
11342 bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttribute eAttr, sal_Int32 nValue )
11343 {
11344     if( ! m_aContext.Tagged )
11345         return false;
11346 
11347     assert(m_aStructure[m_nCurrentStructElement].m_oType);
11348     bool bInsert = false;
11349     if( m_nCurrentStructElement > 0 && m_bEmitStructure )
11350     {
11351         if( eAttr == PDFWriter::Language )
11352         {
11353             m_aStructure[ m_nCurrentStructElement ].m_aLocale = LanguageTag( LanguageType(nValue) ).getLocale();
11354             return true;
11355         }
11356 
11357         PDFWriter::StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType;
11358         switch( eAttr )
11359         {
11360             case PDFWriter::SpaceBefore:
11361             case PDFWriter::SpaceAfter:
11362             case PDFWriter::StartIndent:
11363             case PDFWriter::EndIndent:
11364                 // just for BLSE
11365                 if( eType == PDFWriter::Paragraph   ||
11366                     eType == PDFWriter::Heading     ||
11367                     eType == PDFWriter::H1          ||
11368                     eType == PDFWriter::H2          ||
11369                     eType == PDFWriter::H3          ||
11370                     eType == PDFWriter::H4          ||
11371                     eType == PDFWriter::H5          ||
11372                     eType == PDFWriter::H6          ||
11373                     eType == PDFWriter::List        ||
11374                     eType == PDFWriter::ListItem    ||
11375                     eType == PDFWriter::LILabel     ||
11376                     eType == PDFWriter::LIBody      ||
11377                     eType == PDFWriter::Table       ||
11378                     eType == PDFWriter::TableRow    ||
11379                     eType == PDFWriter::TableHeader ||
11380                     eType == PDFWriter::TableData )
11381                 {
11382                     bInsert = true;
11383                 }
11384                 break;
11385             case PDFWriter::TextIndent:
11386                 // paragraph like BLSE and additional elements
11387                 if( eType == PDFWriter::Paragraph   ||
11388                     eType == PDFWriter::Heading     ||
11389                     eType == PDFWriter::H1          ||
11390                     eType == PDFWriter::H2          ||
11391                     eType == PDFWriter::H3          ||
11392                     eType == PDFWriter::H4          ||
11393                     eType == PDFWriter::H5          ||
11394                     eType == PDFWriter::H6          ||
11395                     eType == PDFWriter::LILabel     ||
11396                     eType == PDFWriter::LIBody      ||
11397                     eType == PDFWriter::TableHeader ||
11398                     eType == PDFWriter::TableData )
11399                 {
11400                     bInsert = true;
11401                 }
11402                 break;
11403             case PDFWriter::Width:
11404             case PDFWriter::Height:
11405                 if( eType == PDFWriter::Figure      ||
11406                     eType == PDFWriter::Formula     ||
11407                     eType == PDFWriter::Form        ||
11408                     eType == PDFWriter::Table       ||
11409                     eType == PDFWriter::TableHeader ||
11410                     eType == PDFWriter::TableData )
11411                 {
11412                     bInsert = true;
11413                 }
11414                 break;
11415             case PDFWriter::LineHeight:
11416             case PDFWriter::BaselineShift:
11417                 // only for ILSE and BLSE
11418                 if( eType == PDFWriter::Paragraph   ||
11419                     eType == PDFWriter::Heading     ||
11420                     eType == PDFWriter::H1          ||
11421                     eType == PDFWriter::H2          ||
11422                     eType == PDFWriter::H3          ||
11423                     eType == PDFWriter::H4          ||
11424                     eType == PDFWriter::H5          ||
11425                     eType == PDFWriter::H6          ||
11426                     eType == PDFWriter::List        ||
11427                     eType == PDFWriter::ListItem    ||
11428                     eType == PDFWriter::LILabel     ||
11429                     eType == PDFWriter::LIBody      ||
11430                     eType == PDFWriter::Table       ||
11431                     eType == PDFWriter::TableRow    ||
11432                     eType == PDFWriter::TableHeader ||
11433                     eType == PDFWriter::TableData   ||
11434                     eType == PDFWriter::Span        ||
11435                     eType == PDFWriter::Quote       ||
11436                     eType == PDFWriter::Note        ||
11437                     eType == PDFWriter::Reference   ||
11438                     eType == PDFWriter::BibEntry    ||
11439                     eType == PDFWriter::Code        ||
11440                     eType == PDFWriter::Link )
11441                 {
11442                         bInsert = true;
11443                 }
11444                 break;
11445             case PDFWriter::RowSpan:
11446             case PDFWriter::ColSpan:
11447                 // only for table cells
11448                 if( eType == PDFWriter::TableHeader ||
11449                     eType == PDFWriter::TableData )
11450                 {
11451                     bInsert = true;
11452                 }
11453                 break;
11454             case PDFWriter::LinkAnnotation:
11455                 if( eType == PDFWriter::Link )
11456                     bInsert = true;
11457                 break;
11458             default: break;
11459         }
11460     }
11461 
11462     if( bInsert )
11463         m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( nValue );
11464     else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
11465         SAL_INFO("vcl.pdfwriter",
11466                  "rejecting setStructureAttributeNumerical( " << getAttributeTag( eAttr )
11467                  << ", " << static_cast<int>(nValue)
11468                  << " ) on " << getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
11469                  << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias
11470                  << ") element");
11471 
11472     return bInsert;
11473 }
11474 
11475 void PDFWriterImpl::setStructureBoundingBox( const tools::Rectangle& rRect )
11476 {
11477     sal_Int32 nPageNr = m_nCurrentPage;
11478     if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() || !m_aContext.Tagged )
11479         return;
11480 
11481     if( !(m_nCurrentStructElement > 0 && m_bEmitStructure) )
11482         return;
11483 
11484     assert(m_aStructure[m_nCurrentStructElement].m_oType);
11485     PDFWriter::StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType;
11486     if( eType == PDFWriter::Figure      ||
11487         eType == PDFWriter::Formula     ||
11488         eType == PDFWriter::Form        ||
11489         eType == PDFWriter::Division    ||
11490         eType == PDFWriter::Table )
11491     {
11492         m_aStructure[ m_nCurrentStructElement ].m_aBBox = rRect;
11493         // convert to default user space now, since the mapmode may change
11494         m_aPages[nPageNr].convertRect( m_aStructure[ m_nCurrentStructElement ].m_aBBox );
11495     }
11496 }
11497 
11498 void PDFWriterImpl::setStructureAnnotIds(::std::vector<sal_Int32> const& rAnnotIds)
11499 {
11500     assert(!(m_nCurrentPage < 0 || m_aPages.size() <= o3tl::make_unsigned(m_nCurrentPage)));
11501 
11502     if (!m_aContext.Tagged || m_nCurrentStructElement <= 0 || !m_bEmitStructure)
11503     {
11504         return;
11505     }
11506 
11507     m_aStructure[m_nCurrentStructElement].m_AnnotIds = rAnnotIds;
11508 }
11509 
11510 void PDFWriterImpl::setActualText( const OUString& rText )
11511 {
11512     if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
11513     {
11514         m_aStructure[ m_nCurrentStructElement ].m_aActualText = rText;
11515     }
11516 }
11517 
11518 void PDFWriterImpl::setAlternateText( const OUString& rText )
11519 {
11520     if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
11521     {
11522         m_aStructure[ m_nCurrentStructElement ].m_aAltText = rText;
11523     }
11524 }
11525 
11526 void PDFWriterImpl::setPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr )
11527 {
11528     if( nPageNr < 0 )
11529         nPageNr = m_nCurrentPage;
11530 
11531     if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
11532         return;
11533 
11534     m_aPages[ nPageNr ].m_eTransition   = eType;
11535     m_aPages[ nPageNr ].m_nTransTime    = nMilliSec;
11536 }
11537 
11538 void PDFWriterImpl::ensureUniqueRadioOnValues()
11539 {
11540     // loop over radio groups
11541     for (auto const& group : m_aRadioGroupWidgets)
11542     {
11543         PDFWidget& rGroupWidget = m_aWidgets[ group.second ];
11544         // check whether all kids have a unique OnValue
11545         std::unordered_map< OUString, sal_Int32 > aOnValues;
11546         bool bIsUnique = true;
11547         for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11548         {
11549             const OUString& rVal = m_aWidgets[nKidIndex].m_aOnValue;
11550             SAL_INFO("vcl.pdfwriter", "OnValue: " << rVal);
11551             if( aOnValues.find( rVal ) == aOnValues.end() )
11552             {
11553                 aOnValues[ rVal ] = 1;
11554             }
11555             else
11556             {
11557                 bIsUnique = false;
11558                 break;
11559             }
11560         }
11561         if( ! bIsUnique )
11562         {
11563             SAL_INFO("vcl.pdfwriter", "enforcing unique OnValues" );
11564             // make unique by using ascending OnValues
11565             int nKid = 0;
11566             for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11567             {
11568                 PDFWidget& rKid = m_aWidgets[nKidIndex];
11569                 rKid.m_aOnValue = OUString::number( nKid+1 );
11570                 if( rKid.m_aValue != "Off" )
11571                     rKid.m_aValue = rKid.m_aOnValue;
11572                 ++nKid;
11573             }
11574         }
11575         // finally move the "Yes" appearance to the OnValue appearance
11576         for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11577         {
11578             PDFWidget& rKid = m_aWidgets[nKidIndex];
11579             if ( !rKid.m_aOnValue.isEmpty() )
11580             {
11581                 auto app_it = rKid.m_aAppearances.find( "N" );
11582                 if( app_it != rKid.m_aAppearances.end() )
11583                 {
11584                     auto stream_it = app_it->second.find( "Yes" );
11585                     if( stream_it != app_it->second.end() )
11586                     {
11587                         SvMemoryStream* pStream = stream_it->second;
11588                         app_it->second.erase( stream_it );
11589                         OStringBuffer aBuf( rKid.m_aOnValue.getLength()*2 );
11590                         appendName( rKid.m_aOnValue, aBuf );
11591                         (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
11592                     }
11593                     else
11594                         SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Yes\" stream" );
11595                 }
11596             }
11597 
11598             if ( !rKid.m_aOffValue.isEmpty() )
11599             {
11600                 auto app_it = rKid.m_aAppearances.find( "N" );
11601                 if( app_it != rKid.m_aAppearances.end() )
11602                 {
11603                     auto stream_it = app_it->second.find( "Off" );
11604                     if( stream_it != app_it->second.end() )
11605                     {
11606                         SvMemoryStream* pStream = stream_it->second;
11607                         app_it->second.erase( stream_it );
11608                         OStringBuffer aBuf( rKid.m_aOffValue.getLength()*2 );
11609                         appendName( rKid.m_aOffValue, aBuf );
11610                         (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
11611                     }
11612                     else
11613                         SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Off\" stream" );
11614                 }
11615             }
11616 
11617             // update selected radio button
11618             if( rKid.m_aValue != "Off" )
11619             {
11620                 rGroupWidget.m_aValue = rKid.m_aValue;
11621             }
11622         }
11623     }
11624 }
11625 
11626 sal_Int32 PDFWriterImpl::findRadioGroupWidget( const PDFWriter::RadioButtonWidget& rBtn )
11627 {
11628     sal_Int32 nRadioGroupWidget = -1;
11629 
11630     std::map< sal_Int32, sal_Int32 >::const_iterator it = m_aRadioGroupWidgets.find( rBtn.RadioGroup );
11631 
11632     if( it == m_aRadioGroupWidgets.end() )
11633     {
11634         m_aRadioGroupWidgets[ rBtn.RadioGroup ] = nRadioGroupWidget =
11635             sal_Int32(m_aWidgets.size());
11636 
11637         // new group, insert the radiobutton
11638         m_aWidgets.emplace_back( );
11639         m_aWidgets.back().m_nObject     = createObject();
11640         m_aWidgets.back().m_nPage       = m_nCurrentPage;
11641         m_aWidgets.back().m_eType       = PDFWriter::RadioButton;
11642         m_aWidgets.back().m_nRadioGroup = rBtn.RadioGroup;
11643         m_aWidgets.back().m_nFlags |= 0x0000C000;   // NoToggleToOff and Radio bits
11644 
11645         createWidgetFieldName( sal_Int32(m_aWidgets.size()-1), rBtn );
11646     }
11647     else
11648         nRadioGroupWidget = it->second;
11649 
11650     return nRadioGroupWidget;
11651 }
11652 
11653 sal_Int32 PDFWriterImpl::createControl( const PDFWriter::AnyWidget& rControl, sal_Int32 nPageNr )
11654 {
11655     if( nPageNr < 0 )
11656         nPageNr = m_nCurrentPage;
11657 
11658     if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
11659         return -1;
11660 
11661     bool sigHidden(true);
11662     sal_Int32 nNewWidget = m_aWidgets.size();
11663     m_aWidgets.emplace_back( );
11664 
11665     m_aWidgets.back().m_nObject         = createObject();
11666     m_aWidgets.back().m_aRect           = rControl.Location;
11667     m_aWidgets.back().m_nPage           = nPageNr;
11668     m_aWidgets.back().m_eType           = rControl.getType();
11669 
11670     sal_Int32 nRadioGroupWidget = -1;
11671     // for unknown reasons the radio buttons of a radio group must not have a
11672     // field name, else the buttons are in fact check boxes -
11673     // that is multiple buttons of the radio group can be selected
11674     if( rControl.getType() == PDFWriter::RadioButton )
11675         nRadioGroupWidget = findRadioGroupWidget( static_cast<const PDFWriter::RadioButtonWidget&>(rControl) );
11676     else
11677     {
11678         createWidgetFieldName( nNewWidget, rControl );
11679     }
11680 
11681     // caution: m_aWidgets must not be changed after here or rNewWidget may be invalid
11682     PDFWidget& rNewWidget           = m_aWidgets[nNewWidget];
11683     rNewWidget.m_aDescription       = rControl.Description;
11684     rNewWidget.m_aText              = rControl.Text;
11685     rNewWidget.m_nTextStyle         = rControl.TextStyle &
11686         (  DrawTextFlags::Left | DrawTextFlags::Center | DrawTextFlags::Right | DrawTextFlags::Top |
11687            DrawTextFlags::VCenter | DrawTextFlags::Bottom |
11688            DrawTextFlags::MultiLine | DrawTextFlags::WordBreak  );
11689     rNewWidget.m_nTabOrder          = rControl.TabOrder;
11690 
11691     // various properties are set via the flags (/Ff) property of the field dict
11692     if( rControl.ReadOnly )
11693         rNewWidget.m_nFlags |= 1;
11694     if( rControl.getType() == PDFWriter::PushButton )
11695     {
11696         const PDFWriter::PushButtonWidget& rBtn = static_cast<const PDFWriter::PushButtonWidget&>(rControl);
11697         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11698             rNewWidget.m_nTextStyle =
11699                 DrawTextFlags::Center | DrawTextFlags::VCenter |
11700                 DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11701 
11702         rNewWidget.m_nFlags |= 0x00010000;
11703         if( !rBtn.URL.isEmpty() )
11704             rNewWidget.m_aListEntries.push_back( rBtn.URL );
11705         rNewWidget.m_bSubmit    = rBtn.Submit;
11706         rNewWidget.m_bSubmitGet = rBtn.SubmitGet;
11707         rNewWidget.m_nDest      = rBtn.Dest;
11708         createDefaultPushButtonAppearance( rNewWidget, rBtn );
11709     }
11710     else if( rControl.getType() == PDFWriter::RadioButton )
11711     {
11712         const PDFWriter::RadioButtonWidget& rBtn = static_cast<const PDFWriter::RadioButtonWidget&>(rControl);
11713         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11714             rNewWidget.m_nTextStyle =
11715                 DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11716         /*  PDF sees a RadioButton group as one radio button with
11717          *  children which are in turn check boxes
11718          *
11719          *  so we need to create a radio button on demand for a new group
11720          *  and insert a checkbox for each RadioButtonWidget as its child
11721          */
11722         rNewWidget.m_eType          = PDFWriter::CheckBox;
11723         rNewWidget.m_nRadioGroup    = rBtn.RadioGroup;
11724 
11725         SAL_WARN_IF( nRadioGroupWidget < 0 || o3tl::make_unsigned(nRadioGroupWidget) >= m_aWidgets.size(), "vcl.pdfwriter", "no radio group parent" );
11726 
11727         PDFWidget& rRadioButton = m_aWidgets[nRadioGroupWidget];
11728         rRadioButton.m_aKids.push_back( rNewWidget.m_nObject );
11729         rRadioButton.m_aKidsIndex.push_back( nNewWidget );
11730         rNewWidget.m_nParent = rRadioButton.m_nObject;
11731 
11732         rNewWidget.m_aValue     = "Off";
11733         rNewWidget.m_aOnValue   = rBtn.OnValue;
11734         rNewWidget.m_aOffValue   = rBtn.OffValue;
11735         if( rRadioButton.m_aValue.isEmpty() && rBtn.Selected )
11736         {
11737             rNewWidget.m_aValue     = rNewWidget.m_aOnValue;
11738             rRadioButton.m_aValue   = rNewWidget.m_aOnValue;
11739         }
11740         createDefaultRadioButtonAppearance( rNewWidget, rBtn );
11741 
11742         // union rect of radio group
11743         tools::Rectangle aRect = rNewWidget.m_aRect;
11744         m_aPages[ nPageNr ].convertRect( aRect );
11745         rRadioButton.m_aRect.Union( aRect );
11746     }
11747     else if( rControl.getType() == PDFWriter::CheckBox )
11748     {
11749         const PDFWriter::CheckBoxWidget& rBox = static_cast<const PDFWriter::CheckBoxWidget&>(rControl);
11750         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11751             rNewWidget.m_nTextStyle =
11752                 DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11753 
11754         rNewWidget.m_aValue
11755             = rBox.Checked ? std::u16string_view(u"Yes") : std::u16string_view(u"Off" );
11756         rNewWidget.m_aOnValue   = rBox.OnValue;
11757         rNewWidget.m_aOffValue   = rBox.OffValue;
11758         // create default appearance before m_aRect gets transformed
11759         createDefaultCheckBoxAppearance( rNewWidget, rBox );
11760     }
11761     else if( rControl.getType() == PDFWriter::ListBox )
11762     {
11763         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11764             rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
11765 
11766         const PDFWriter::ListBoxWidget& rLstBox = static_cast<const PDFWriter::ListBoxWidget&>(rControl);
11767         rNewWidget.m_aListEntries     = rLstBox.Entries;
11768         rNewWidget.m_aSelectedEntries = rLstBox.SelectedEntries;
11769         rNewWidget.m_aValue           = rLstBox.Text;
11770         if( rLstBox.DropDown )
11771             rNewWidget.m_nFlags |= 0x00020000;
11772         if (rLstBox.MultiSelect && !rLstBox.DropDown)
11773             rNewWidget.m_nFlags |= 0x00200000;
11774 
11775         createDefaultListBoxAppearance( rNewWidget, rLstBox );
11776     }
11777     else if( rControl.getType() == PDFWriter::ComboBox )
11778     {
11779         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11780             rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
11781 
11782         const PDFWriter::ComboBoxWidget& rBox = static_cast<const PDFWriter::ComboBoxWidget&>(rControl);
11783         rNewWidget.m_aValue         = rBox.Text;
11784         rNewWidget.m_aListEntries   = rBox.Entries;
11785         rNewWidget.m_nFlags |= 0x00060000; // combo and edit flag
11786 
11787         PDFWriter::ListBoxWidget aLBox;
11788         aLBox.Name              = rBox.Name;
11789         aLBox.Description       = rBox.Description;
11790         aLBox.Text              = rBox.Text;
11791         aLBox.TextStyle         = rBox.TextStyle;
11792         aLBox.ReadOnly          = rBox.ReadOnly;
11793         aLBox.Border            = rBox.Border;
11794         aLBox.BorderColor       = rBox.BorderColor;
11795         aLBox.Background        = rBox.Background;
11796         aLBox.BackgroundColor   = rBox.BackgroundColor;
11797         aLBox.TextFont          = rBox.TextFont;
11798         aLBox.TextColor         = rBox.TextColor;
11799         aLBox.DropDown          = true;
11800         aLBox.MultiSelect       = false;
11801         aLBox.Entries           = rBox.Entries;
11802 
11803         createDefaultListBoxAppearance( rNewWidget, aLBox );
11804     }
11805     else if( rControl.getType() == PDFWriter::Edit )
11806     {
11807         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11808             rNewWidget.m_nTextStyle = DrawTextFlags::Left | DrawTextFlags::VCenter;
11809 
11810         const PDFWriter::EditWidget& rEdit = static_cast<const  PDFWriter::EditWidget&>(rControl);
11811         if( rEdit.MultiLine )
11812         {
11813             rNewWidget.m_nFlags |= 0x00001000;
11814             rNewWidget.m_nTextStyle |= DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11815         }
11816         if( rEdit.Password )
11817             rNewWidget.m_nFlags |= 0x00002000;
11818         if (rEdit.FileSelect)
11819             rNewWidget.m_nFlags |= 0x00100000;
11820         rNewWidget.m_nMaxLen = rEdit.MaxLen;
11821         rNewWidget.m_nFormat = rEdit.Format;
11822         rNewWidget.m_aCurrencySymbol = rEdit.CurrencySymbol;
11823         rNewWidget.m_nDecimalAccuracy = rEdit.DecimalAccuracy;
11824         rNewWidget.m_bPrependCurrencySymbol = rEdit.PrependCurrencySymbol;
11825         rNewWidget.m_aTimeFormat = rEdit.TimeFormat;
11826         rNewWidget.m_aDateFormat = rEdit.DateFormat;
11827         rNewWidget.m_aValue = rEdit.Text;
11828 
11829         createDefaultEditAppearance( rNewWidget, rEdit );
11830     }
11831 #if HAVE_FEATURE_NSS
11832     else if( rControl.getType() == PDFWriter::Signature)
11833     {
11834         sigHidden = true;
11835 
11836         rNewWidget.m_aRect = tools::Rectangle(0, 0, 0, 0);
11837 
11838         m_nSignatureObject = createObject();
11839         rNewWidget.m_aValue = OUString::number( m_nSignatureObject );
11840         rNewWidget.m_aValue += " 0 R";
11841         // let's add a fake appearance
11842         rNewWidget.m_aAppearances[ "N" ][ "Standard" ] = new SvMemoryStream();
11843     }
11844 #endif
11845 
11846     // if control is a hidden signature, do not convert coordinates since we
11847     // need /Rect [ 0 0 0 0 ]
11848     if ( ! ( ( rControl.getType() == PDFWriter::Signature ) && sigHidden ) )
11849     {
11850         // convert to default user space now, since the mapmode may change
11851         // note: create default appearances before m_aRect gets transformed
11852         m_aPages[ nPageNr ].convertRect( rNewWidget.m_aRect );
11853     }
11854 
11855     // insert widget to page's annotation list
11856     m_aPages[ nPageNr ].m_aAnnotations.push_back( rNewWidget.m_nObject );
11857 
11858     return nNewWidget;
11859 }
11860 
11861 void PDFWriterImpl::MARK( const char* pString )
11862 {
11863     beginStructureElementMCSeq();
11864     if (g_bDebugDisableCompression)
11865         emitComment( pString );
11866 }
11867 
11868 sal_Int32 ReferenceXObjectEmit::getObject() const
11869 {
11870     if (m_nFormObject > 0)
11871         return m_nFormObject;
11872     else
11873         return m_nBitmapObject;
11874 }
11875 }
11876 
11877 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
11878