xref: /core/vcl/source/gdi/pdfwriter_impl.cxx (revision 4b750f6ed5c106f8adb86a5579ad96dc60c5e72f)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <sal/config.h>
21 #include <config_crypto.h>
22 
23 #include <sal/types.h>
24 
25 #include <math.h>
26 #include <algorithm>
27 #include <string_view>
28 
29 #include <lcms2.h>
30 
31 #include <basegfx/matrix/b2dhommatrix.hxx>
32 #include <basegfx/polygon/b2dpolygon.hxx>
33 #include <basegfx/polygon/b2dpolygontools.hxx>
34 #include <basegfx/polygon/b2dpolypolygon.hxx>
35 #include <basegfx/polygon/b2dpolypolygoncutter.hxx>
36 #include <basegfx/polygon/b2dpolypolygontools.hxx>
37 #include <memory>
38 #include <com/sun/star/io/XOutputStream.hpp>
39 #include <com/sun/star/util/URL.hpp>
40 #include <com/sun/star/util/URLTransformer.hpp>
41 #include <comphelper/processfactory.hxx>
42 #include <comphelper/string.hxx>
43 #include <comphelper/xmlencode.hxx>
44 #include <cppuhelper/implbase.hxx>
45 #include <i18nlangtag/languagetag.hxx>
46 #include <o3tl/numeric.hxx>
47 #include <o3tl/safeint.hxx>
48 #include <o3tl/temporary.hxx>
49 #include <officecfg/Office/Common.hxx>
50 #include <osl/diagnose.h>
51 #include <osl/file.hxx>
52 #include <osl/thread.h>
53 #include <rtl/crc.h>
54 #include <rtl/character.hxx>
55 #include <rtl/digest.h>
56 #include <rtl/uri.hxx>
57 #include <rtl/ustrbuf.hxx>
58 #include <svl/cryptosign.hxx>
59 #include <sal/log.hxx>
60 #include <svl/urihelper.hxx>
61 #include <tools/fract.hxx>
62 #include <tools/stream.hxx>
63 #include <tools/helpers.hxx>
64 #include <tools/urlobj.hxx>
65 #include <tools/UnitConversion.hxx>
66 #include <tools/zcodec.hxx>
67 #include <unotools/configmgr.hxx>
68 #include <vcl/bitmapex.hxx>
69 #include <vcl/canvastools.hxx>
70 #include <vcl/cvtgrf.hxx>
71 #include <vcl/fontcharmap.hxx>
72 #include <vcl/glyphitemcache.hxx>
73 #include <vcl/kernarray.hxx>
74 #include <vcl/lineinfo.hxx>
75 #include <vcl/metric.hxx>
76 #include <vcl/mnemonic.hxx>
77 #include <vcl/pdfread.hxx>
78 #include <vcl/settings.hxx>
79 #include <strhelper.hxx>
80 #include <vcl/svapp.hxx>
81 #include <vcl/virdev.hxx>
82 #include <vcl/filter/pdfdocument.hxx>
83 #include <vcl/filter/PngImageReader.hxx>
84 #include <comphelper/hash.hxx>
85 #include <vcl/pdf/PDFNote.hxx>
86 
87 #include <svdata.hxx>
88 #include <vcl/BitmapWriteAccess.hxx>
89 #include <pdf/COSWriter.hxx>
90 #include <fontsubset.hxx>
91 #include <font/EmphasisMark.hxx>
92 #include <font/PhysicalFontFace.hxx>
93 #include <salgdi.hxx>
94 #include <textlayout.hxx>
95 #include <textlineinfo.hxx>
96 #include <impglyphitem.hxx>
97 #include <pdf/XmpMetadata.hxx>
98 #include <pdf/objectcopier.hxx>
99 #include <pdf/pdfwriter_impl.hxx>
100 #include <pdf/PdfConfig.hxx>
101 #include <pdf/PDFEncryptorR6.hxx>
102 #include <pdf/PDFEncryptor.hxx>
103 #include <o3tl/sorted_vector.hxx>
104 #include <frozen/bits/defines.h>
105 #include <frozen/bits/elsa_std.h>
106 #include <frozen/unordered_map.h>
107 
108 using namespace::com::sun::star;
109 
110 static bool g_bDebugDisableCompression = getenv("VCL_DEBUG_DISABLE_PDFCOMPRESSION");
111 
112 namespace
113 {
114 
115 constexpr std::string_view constNamespacePDF2("http://iso.org/pdf2/ssn");
116 
117 constexpr sal_Int32 nLog10Divisor = 3;
118 constexpr double fDivisor = 1000.0;
119 
pixelToPoint(double px)120 constexpr double pixelToPoint(double px)
121 {
122     return px / fDivisor;
123 }
124 
pointToPixel(double pt)125 constexpr sal_Int32 pointToPixel(double pt)
126 {
127     return sal_Int32(pt * fDivisor);
128 }
129 
appendObjectID(sal_Int32 nObjectID,OStringBuffer & aLine)130 void appendObjectID(sal_Int32 nObjectID, OStringBuffer & aLine)
131 {
132     aLine.append(nObjectID);
133     aLine.append(" 0 obj\n");
134 }
135 
appendObjectReference(sal_Int32 nObjectID,OStringBuffer & aLine)136 void appendObjectReference(sal_Int32 nObjectID, OStringBuffer & aLine)
137 {
138     aLine.append(nObjectID);
139     aLine.append(" 0 R ");
140 }
141 
142 /*
143  * Convert a string before using it.
144  *
145  * This string conversion function is needed because the destination name
146  * in a PDF file seen through an Internet browser should be
147  * specially crafted, in order to be used directly by the browser.
148  * In this way the fragment part of a hyperlink to a PDF file (e.g. something
149  * as 'test1/test2/a-file.pdf\#thefragment) will be (hopefully) interpreted by the
150  * PDF reader (currently only Adobe Reader plug-in seems to be working that way) called
151  * from inside the Internet browser as: 'open the file test1/test2/a-file.pdf
152  * and go to named destination thefragment using default zoom'.
153  * The conversion is needed because in case of a fragment in the form: Slide%201
154  * (meaning Slide 1) as it is converted obeying the Inet rules, it will become Slide25201
155  * using this conversion, in both the generated named destinations, fragment and GoToR
156  * destination.
157  *
158  * The names for destinations are name objects and so they don't need to be encrypted
159  * even though they expose the content of PDF file (e.g. guessing the PDF content from the
160  * destination name).
161  *
162  * Further limitation: it is advisable to use standard ASCII characters for
163  * OOo bookmarks.
164 */
appendDestinationName(std::u16string_view rString,OStringBuffer & rBuffer)165 void appendDestinationName( std::u16string_view rString, OStringBuffer& rBuffer )
166 {
167     for( auto aChar: rString)
168     {
169         if( rtl::isAsciiAlphanumeric(aChar) || aChar == '-' )
170         {
171             rBuffer.append(static_cast<char>(aChar));
172         }
173         else
174         {
175             sal_Int8 aValueHigh = sal_Int8(aChar >> 8);
176             if(aValueHigh > 0)
177                 vcl::COSWriter::appendHex(aValueHigh, rBuffer);
178             vcl::COSWriter::appendHex(static_cast<sal_Int8>(aChar & 255 ), rBuffer);
179         }
180     }
181 }
182 } // end anonymous namespace
183 
184 namespace vcl
185 {
186 
187 namespace
188 {
189 
190 template < class GEOMETRY >
lcl_convert(const MapMode & _rSource,const MapMode & _rDest,OutputDevice * _pPixelConversion,const GEOMETRY & _rObject)191 GEOMETRY lcl_convert( const MapMode& _rSource, const MapMode& _rDest, OutputDevice* _pPixelConversion, const GEOMETRY& _rObject )
192 {
193     GEOMETRY aPoint;
194     if ( MapUnit::MapPixel == _rSource.GetMapUnit() )
195     {
196         aPoint = _pPixelConversion->PixelToLogic( _rObject, _rDest );
197     }
198     else
199     {
200         aPoint = OutputDevice::LogicToLogic( _rObject, _rSource, _rDest );
201     }
202     return aPoint;
203 }
204 
205 void removePlaceholderSE(std::vector<PDFStructureElement> & rStructure, PDFStructureElement& rEle);
206 
207 } // end anonymous namespace
208 
createWidgetFieldName(sal_Int32 i_nWidgetIndex,const PDFWriter::AnyWidget & i_rControl)209 void PDFWriterImpl::createWidgetFieldName( sal_Int32 i_nWidgetIndex, const PDFWriter::AnyWidget& i_rControl )
210 {
211     /* #i80258# previously we use appendName here
212        however we need a slightly different coding scheme than the normal
213        name encoding for field names
214     */
215     const OUString& rName = i_rControl.Name;
216     OString aStr( OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ) );
217     int nLen = aStr.getLength();
218 
219     OStringBuffer aBuffer( rName.getLength()+64 );
220     for( int i = 0; i < nLen; i++ )
221     {
222         /*  #i16920# PDF recommendation: output UTF8, any byte
223          *  outside the interval [32(=ASCII' ');126(=ASCII'~')]
224          *  should be escaped hexadecimal
225          */
226         if( aStr[i] >= 32 && aStr[i] <= 126 )
227             aBuffer.append( aStr[i] );
228         else
229         {
230             aBuffer.append( '#' );
231             COSWriter::appendHex(static_cast<sal_Int8>(aStr[i]), aBuffer);
232         }
233     }
234 
235     OString aFullName( aBuffer.makeStringAndClear() );
236 
237     /* #i82785# create hierarchical fields down to the for each dot in i_rName */
238     sal_Int32 nTokenIndex = 0, nLastTokenIndex = 0;
239     OString aPartialName;
240     OString aDomain;
241     do
242     {
243         nLastTokenIndex = nTokenIndex;
244         aPartialName = aFullName.getToken( 0, '.', nTokenIndex );
245         if( nTokenIndex != -1 )
246         {
247             // find or create a hierarchical field
248             // first find the fully qualified name up to this field
249             aDomain = aFullName.copy( 0, nTokenIndex-1 );
250             std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
251             if( it == m_aFieldNameMap.end() )
252             {
253                  // create new hierarchy field
254                 sal_Int32 nNewWidget = m_aWidgets.size();
255                 m_aWidgets.emplace_back( );
256                 m_aWidgets[nNewWidget].m_nObject = createObject();
257                 m_aWidgets[nNewWidget].m_eType = PDFWriter::Hierarchy;
258                 m_aWidgets[nNewWidget].m_aName = aPartialName;
259                 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
260                 m_aFieldNameMap[aDomain] = nNewWidget;
261                 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
262                 if( nLastTokenIndex > 0 )
263                 {
264                     // this field is not a root field and
265                     // needs to be inserted to its parent
266                     OString aParentDomain( aDomain.copy( 0, nLastTokenIndex-1 ) );
267                     it = m_aFieldNameMap.find( aParentDomain );
268                     OSL_ENSURE( it != m_aFieldNameMap.end(), "field name not found" );
269                     if( it != m_aFieldNameMap.end()  )
270                     {
271                         OSL_ENSURE( it->second < sal_Int32(m_aWidgets.size()), "invalid field number entry" );
272                         if( it->second < sal_Int32(m_aWidgets.size()) )
273                         {
274                             PDFWidget& rParentField( m_aWidgets[it->second] );
275                             rParentField.m_aKids.push_back( m_aWidgets[nNewWidget].m_nObject );
276                             rParentField.m_aKidsIndex.push_back( nNewWidget );
277                             m_aWidgets[nNewWidget].m_nParent = rParentField.m_nObject;
278                         }
279                     }
280                 }
281             }
282             else if( m_aWidgets[it->second].m_eType != PDFWriter::Hierarchy )
283             {
284                 // this is invalid, someone tries to have a terminal field as parent
285                 // example: a button with the name foo.bar exists and
286                 // another button is named foo.bar.no
287                 // workaround: put the second terminal field as much up in the hierarchy as
288                 // necessary to have a non-terminal field as parent (or none at all)
289                 // since it->second already is terminal, we just need to use its parent
290                 aDomain.clear();
291                 aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
292                 if( nLastTokenIndex > 0 )
293                 {
294                     aDomain = aFullName.copy( 0, nLastTokenIndex-1 );
295                     aFullName = aDomain + "." + aPartialName;
296                 }
297                 else
298                     aFullName = aPartialName;
299                 break;
300             }
301         }
302     } while( nTokenIndex != -1 );
303 
304     // insert widget into its hierarchy field
305     if( !aDomain.isEmpty() )
306     {
307         std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
308         if( it != m_aFieldNameMap.end() )
309         {
310             OSL_ENSURE( it->second >= 0 && o3tl::make_unsigned(it->second) < m_aWidgets.size(), "invalid field index" );
311             if( it->second >= 0 && o3tl::make_unsigned(it->second) < m_aWidgets.size() )
312             {
313                 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[it->second].m_nObject;
314                 m_aWidgets[it->second].m_aKids.push_back( m_aWidgets[i_nWidgetIndex].m_nObject);
315                 m_aWidgets[it->second].m_aKidsIndex.push_back( i_nWidgetIndex );
316             }
317         }
318     }
319 
320     if( aPartialName.isEmpty() )
321     {
322         // how funny, an empty field name
323         if( i_rControl.getType() == PDFWriter::RadioButton )
324         {
325             aPartialName  = "RadioGroup" +
326                 OString::number( static_cast<const PDFWriter::RadioButtonWidget&>(i_rControl).RadioGroup );
327         }
328         else
329             aPartialName = "Widget"_ostr;
330     }
331 
332     if( ! m_aContext.AllowDuplicateFieldNames )
333     {
334         std::unordered_map<OString, sal_Int32>::iterator it = m_aFieldNameMap.find( aFullName );
335 
336         if( it != m_aFieldNameMap.end() ) // not unique
337         {
338             std::unordered_map< OString, sal_Int32 >::const_iterator check_it;
339             OString aTry;
340             sal_Int32 nTry = 2;
341             do
342             {
343                 aTry = aFullName + "_" + OString::number(nTry++);
344                 check_it = m_aFieldNameMap.find( aTry );
345             } while( check_it != m_aFieldNameMap.end() );
346             aFullName = aTry;
347             m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
348             aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
349         }
350         else
351             m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
352     }
353 
354     // finally
355     m_aWidgets[i_nWidgetIndex].m_aName = aPartialName;
356 }
357 
358 namespace
359 {
360 
appendFixedInt(sal_Int32 nValue,OStringBuffer & rBuffer)361 void appendFixedInt( sal_Int32 nValue, OStringBuffer& rBuffer )
362 {
363     if( nValue < 0 )
364     {
365         rBuffer.append( '-' );
366         nValue = -nValue;
367     }
368     sal_Int32 nFactor = 1, nDiv = nLog10Divisor;
369     while( nDiv-- )
370         nFactor *= 10;
371 
372     sal_Int32 nInt = nValue / nFactor;
373     rBuffer.append( nInt );
374     if (nFactor > 1 && nValue % nFactor)
375     {
376         rBuffer.append( '.' );
377         do
378         {
379             nFactor /= 10;
380             rBuffer.append((nValue / nFactor) % 10);
381         }
382         while (nFactor > 1 && nValue % nFactor); // omit trailing zeros
383     }
384 }
385 
386 // appends a double. PDF does not accept exponential format, only fixed point
appendDouble(double fValue,OStringBuffer & rBuffer,sal_Int32 nPrecision=10)387 void appendDouble( double fValue, OStringBuffer& rBuffer, sal_Int32 nPrecision = 10 )
388 {
389     bool bNeg = false;
390     if( fValue < 0.0 )
391     {
392         bNeg = true;
393         fValue=-fValue;
394     }
395 
396     sal_Int64 nInt = static_cast<sal_Int64>(fValue);
397     fValue -= static_cast<double>(nInt);
398     // optimizing hardware may lead to a value of 1.0 after the subtraction
399     if( rtl::math::approxEqual(fValue, 1.0) || log10( 1.0-fValue ) <= -nPrecision )
400     {
401         nInt++;
402         fValue = 0.0;
403     }
404     sal_Int64 nFrac = 0;
405     if( fValue )
406     {
407         fValue *= pow( 10.0, static_cast<double>(nPrecision) );
408         nFrac = static_cast<sal_Int64>(fValue);
409     }
410     if( bNeg && ( nInt || nFrac ) )
411         rBuffer.append( '-' );
412     rBuffer.append( nInt );
413     if( !nFrac )
414         return;
415 
416     int i;
417     rBuffer.append( '.' );
418     sal_Int64 nBound = static_cast<sal_Int64>(pow( 10.0, nPrecision - 1.0 )+0.5);
419     for ( i = 0; ( i < nPrecision ) && nFrac; i++ )
420     {
421         sal_Int64 nNumb = nFrac / nBound;
422         nFrac -= nNumb * nBound;
423         rBuffer.append( nNumb );
424         nBound /= 10;
425     }
426 }
427 
appendColor(const Color & rColor,OStringBuffer & rBuffer,bool bConvertToGrey)428 void appendColor( const Color& rColor, OStringBuffer& rBuffer, bool bConvertToGrey )
429 {
430     if( rColor == COL_TRANSPARENT )
431         return;
432 
433     if( bConvertToGrey )
434     {
435         sal_uInt8 cByte = rColor.GetLuminance();
436         appendDouble( static_cast<double>(cByte) / 255.0, rBuffer );
437     }
438     else
439     {
440         appendDouble( static_cast<double>(rColor.GetRed()) / 255.0, rBuffer );
441         rBuffer.append( ' ' );
442         appendDouble( static_cast<double>(rColor.GetGreen()) / 255.0, rBuffer );
443         rBuffer.append( ' ' );
444         appendDouble( static_cast<double>(rColor.GetBlue()) / 255.0, rBuffer );
445     }
446 }
447 
448 } // end anonymous namespace
449 
appendStrokingColor(const Color & rColor,OStringBuffer & rBuffer)450 void PDFWriterImpl::appendStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
451 {
452     if( rColor != COL_TRANSPARENT )
453     {
454         bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
455         appendColor( rColor, rBuffer, bGrey );
456         rBuffer.append( bGrey ? " G" : " RG" );
457     }
458 }
459 
appendNonStrokingColor(const Color & rColor,OStringBuffer & rBuffer)460 void PDFWriterImpl::appendNonStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
461 {
462     if( rColor != COL_TRANSPARENT )
463     {
464         bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
465         appendColor( rColor, rBuffer, bGrey );
466         rBuffer.append( bGrey ? " g" : " rg" );
467     }
468 }
469 
470 namespace
471 {
472 
appendPdfTimeDate(OStringBuffer & rBuffer,sal_Int16 year,sal_uInt16 month,sal_uInt16 day,sal_uInt16 hours,sal_uInt16 minutes,sal_uInt16 seconds,sal_Int32 tzDelta)473 void appendPdfTimeDate(OStringBuffer & rBuffer,
474     sal_Int16 year, sal_uInt16 month, sal_uInt16 day, sal_uInt16 hours, sal_uInt16 minutes, sal_uInt16 seconds, sal_Int32 tzDelta)
475 {
476     rBuffer.append("D:");
477     rBuffer.append(char('0' + ((year / 1000) % 10)));
478     rBuffer.append(char('0' + ((year / 100) % 10)));
479     rBuffer.append(char('0' + ((year / 10) % 10)));
480     rBuffer.append(char('0' + (year % 10)));
481     rBuffer.append(char('0' + ((month / 10) % 10)));
482     rBuffer.append(char('0' + (month % 10)));
483     rBuffer.append(char('0' + ((day / 10) % 10)));
484     rBuffer.append(char('0' + (day % 10)));
485     rBuffer.append(char('0' + ((hours / 10) % 10)));
486     rBuffer.append(char('0' + (hours % 10)));
487     rBuffer.append(char('0' + ((minutes / 10) % 10)));
488     rBuffer.append(char('0' + (minutes % 10)));
489     rBuffer.append(char('0' + ((seconds / 10) % 10)));
490     rBuffer.append(char('0' + (seconds % 10)));
491 
492     if (tzDelta == 0)
493     {
494         rBuffer.append("Z");
495     }
496     else
497     {
498         if (tzDelta > 0 )
499             rBuffer.append("+");
500         else
501         {
502             rBuffer.append("-");
503             tzDelta = -tzDelta;
504         }
505 
506         rBuffer.append(char('0' + ((tzDelta / 36000) % 10)));
507         rBuffer.append(char('0' + ((tzDelta / 3600) % 10)));
508         rBuffer.append("'");
509         rBuffer.append(char('0' + ((tzDelta / 600) % 6)));
510         rBuffer.append(char('0' + ((tzDelta / 60) % 10)));
511     }
512 }
513 
getPDFVersionStr(PDFWriter::PDFVersion ePDFVersion)514 const char* getPDFVersionStr(PDFWriter::PDFVersion ePDFVersion)
515 {
516     switch (ePDFVersion)
517     {
518         case PDFWriter::PDFVersion::PDF_A_1:
519         case PDFWriter::PDFVersion::PDF_1_4:
520             return "1.4";
521         case PDFWriter::PDFVersion::PDF_1_5:
522             return "1.5";
523         case PDFWriter::PDFVersion::PDF_1_6:
524             return "1.6";
525         default:
526         case PDFWriter::PDFVersion::PDF_A_2:
527         case PDFWriter::PDFVersion::PDF_A_3:
528         case PDFWriter::PDFVersion::PDF_1_7:
529             return "1.7";
530         // PDF 2.0
531         case PDFWriter::PDFVersion::PDF_A_4:
532         case PDFWriter::PDFVersion::PDF_2_0:
533             return "2.0";
534     }
535 }
536 
computeDocumentIdentifier(std::vector<sal_uInt8> & o_rIdentifier,const vcl::PDFWriter::PDFDocInfo & i_rDocInfo,const OString & i_rCString1,const css::util::DateTime & rCreationMetaDate,OString & o_rCString2)537 void computeDocumentIdentifier(std::vector<sal_uInt8>& o_rIdentifier,
538                                const vcl::PDFWriter::PDFDocInfo& i_rDocInfo,
539                                const OString& i_rCString1,
540                                const css::util::DateTime& rCreationMetaDate, OString& o_rCString2)
541 {
542     o_rIdentifier.clear();
543 
544     //build the document id
545     OString aInfoValuesOut;
546     OStringBuffer aID(1024);
547     if (!i_rDocInfo.Title.isEmpty())
548         COSWriter::appendUnicodeTextString(i_rDocInfo.Title, aID);
549     if (!i_rDocInfo.Author.isEmpty())
550         COSWriter::appendUnicodeTextString(i_rDocInfo.Author, aID);
551     if (!i_rDocInfo.Subject.isEmpty())
552         COSWriter::appendUnicodeTextString(i_rDocInfo.Subject, aID);
553     if (!i_rDocInfo.Keywords.isEmpty())
554         COSWriter::appendUnicodeTextString(i_rDocInfo.Keywords, aID);
555     if (!i_rDocInfo.Creator.isEmpty())
556         COSWriter::appendUnicodeTextString(i_rDocInfo.Creator, aID);
557     if (!i_rDocInfo.Producer.isEmpty())
558         COSWriter::appendUnicodeTextString(i_rDocInfo.Producer, aID);
559 
560     TimeValue aTVal, aGMT;
561     oslDateTime aDT;
562     aDT.NanoSeconds = rCreationMetaDate.NanoSeconds;
563     aDT.Seconds = rCreationMetaDate.Seconds;
564     aDT.Minutes = rCreationMetaDate.Minutes;
565     aDT.Hours = rCreationMetaDate.Hours;
566     aDT.Day = rCreationMetaDate.Day;
567     aDT.Month = rCreationMetaDate.Month;
568     aDT.Year = rCreationMetaDate.Year;
569 
570     osl_getSystemTime(&aGMT);
571     osl_getLocalTimeFromSystemTime(&aGMT, &aTVal);
572     OStringBuffer aCreationMetaDateString(64);
573 
574     // i59651: we fill the Metadata date string as well, if PDF/A is requested
575     // according to ISO 19005-1:2005 6.7.3 the date is corrected for
576     // local time zone offset UTC only, whereas Acrobat 8 seems
577     // to use the localtime notation only
578     // according to a recommendation in XMP Specification (Jan 2004, page 75)
579     // the Acrobat way seems the right approach
580     aCreationMetaDateString.append(char('0' + ((aDT.Year / 1000) % 10)));
581     aCreationMetaDateString.append(char('0' + ((aDT.Year / 100) % 10)));
582     aCreationMetaDateString.append(char('0' + ((aDT.Year / 10) % 10)));
583     aCreationMetaDateString.append(char('0' + ((aDT.Year) % 10)));
584     aCreationMetaDateString.append("-");
585     aCreationMetaDateString.append(char('0' + ((aDT.Month / 10) % 10)));
586     aCreationMetaDateString.append(char('0' + ((aDT.Month) % 10)));
587     aCreationMetaDateString.append("-");
588     aCreationMetaDateString.append(char('0' + ((aDT.Day / 10) % 10)));
589     aCreationMetaDateString.append(char('0' + ((aDT.Day) % 10)));
590     aCreationMetaDateString.append("T");
591     aCreationMetaDateString.append(char('0' + ((aDT.Hours / 10) % 10)));
592     aCreationMetaDateString.append(char('0' + ((aDT.Hours) % 10)));
593     aCreationMetaDateString.append(":");
594     aCreationMetaDateString.append(char('0' + ((aDT.Minutes / 10) % 10)));
595     aCreationMetaDateString.append(char('0' + ((aDT.Minutes) % 10)));
596     aCreationMetaDateString.append(":");
597     aCreationMetaDateString.append(char('0' + ((aDT.Seconds / 10) % 10)));
598     aCreationMetaDateString.append(char('0' + ((aDT.Seconds) % 10)));
599 
600     sal_uInt32 nDelta = 0;
601     if (aGMT.Seconds > aTVal.Seconds)
602     {
603         nDelta = aGMT.Seconds - aTVal.Seconds;
604         aCreationMetaDateString.append("-");
605     }
606     else if (aGMT.Seconds < aTVal.Seconds)
607     {
608         nDelta = aTVal.Seconds - aGMT.Seconds;
609         aCreationMetaDateString.append("+");
610     }
611     else
612     {
613         aCreationMetaDateString.append("Z");
614     }
615     if (nDelta)
616     {
617         aCreationMetaDateString.append(char('0' + ((nDelta / 36000) % 10)));
618         aCreationMetaDateString.append(char('0' + ((nDelta / 3600) % 10)));
619         aCreationMetaDateString.append(":");
620         aCreationMetaDateString.append(char('0' + ((nDelta / 600) % 6)));
621         aCreationMetaDateString.append(char('0' + ((nDelta / 60) % 10)));
622     }
623     aID.append(i_rCString1.getStr(), i_rCString1.getLength());
624 
625     aInfoValuesOut = aID.makeStringAndClear();
626     o_rCString2 = aCreationMetaDateString.makeStringAndClear();
627 
628     ::comphelper::Hash aDigest(::comphelper::HashType::MD5);
629     aDigest.update(&aGMT, sizeof(aGMT));
630     aDigest.update(aInfoValuesOut.getStr(), aInfoValuesOut.getLength());
631     //the binary form of the doc id is needed for encryption stuff
632     o_rIdentifier = aDigest.finalize();
633 }
634 
635 } // end anonymous namespace
636 
PDFPage(PDFWriterImpl * pWriter,double nPageWidth,double nPageHeight,PDFWriter::Orientation eOrientation)637 PDFPage::PDFPage( PDFWriterImpl* pWriter, double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
638         :
639         m_pWriter( pWriter ),
640         m_nPageWidth( nPageWidth ),
641         m_nPageHeight( nPageHeight ),
642         m_nUserUnit( 1 ),
643         m_eOrientation( eOrientation ),
644         m_nPageObject( 0 ),  // invalid object number
645         m_nStreamLengthObject( 0 ),
646         m_nBeginStreamPos( 0 ),
647         m_eTransition( PDFWriter::PageTransition::Regular ),
648         m_nTransTime( 0 )
649 {
650     // object ref must be only ever updated in emit()
651     m_nPageObject = m_pWriter->createObject();
652 
653     switch (m_pWriter->m_aContext.Version)
654     {
655         // 1.6 or later
656         default:
657             m_nUserUnit = std::ceil(std::max(nPageWidth, nPageHeight) / 14400.0);
658             break;
659         case PDFWriter::PDFVersion::PDF_1_4:
660         case PDFWriter::PDFVersion::PDF_1_5:
661         case PDFWriter::PDFVersion::PDF_A_1:
662             break;
663     }
664 }
665 
beginStream()666 void PDFPage::beginStream()
667 {
668     if (g_bDebugDisableCompression)
669     {
670         m_pWriter->emitComment("PDFWriterImpl::PDFPage::beginStream, +");
671     }
672     m_aStreamObjects.push_back(m_pWriter->createObject());
673     if( ! m_pWriter->updateObject( m_aStreamObjects.back() ) )
674         return;
675 
676     m_nStreamLengthObject = m_pWriter->createObject();
677     // write content stream header
678     OStringBuffer aLine(
679         OString::number(m_aStreamObjects.back())
680         + " 0 obj\n<</Length "
681         + OString::number( m_nStreamLengthObject )
682         + " 0 R" );
683     if (!g_bDebugDisableCompression)
684         aLine.append( "/Filter/FlateDecode" );
685     aLine.append( ">>\nstream\n" );
686     if( ! m_pWriter->writeBuffer( aLine ) )
687         return;
688     if (osl::File::E_None != m_pWriter->m_aFile.getPos(m_nBeginStreamPos))
689     {
690         m_pWriter->m_aFile.close();
691         m_pWriter->m_bOpen = false;
692     }
693     if (!g_bDebugDisableCompression)
694         m_pWriter->beginCompression();
695     m_pWriter->checkAndEnableStreamEncryption( m_aStreamObjects.back() );
696 }
697 
endStream()698 void PDFPage::endStream()
699 {
700     if (!g_bDebugDisableCompression)
701         m_pWriter->endCompression();
702     sal_uInt64 nEndStreamPos;
703     if (osl::File::E_None != m_pWriter->m_aFile.getPos(nEndStreamPos))
704     {
705         m_pWriter->m_aFile.close();
706         m_pWriter->m_bOpen = false;
707         return;
708     }
709     m_pWriter->disableStreamEncryption();
710     if( ! m_pWriter->writeBuffer( "\nendstream\nendobj\n\n" ) )
711         return;
712     // emit stream length object
713     if( ! m_pWriter->updateObject( m_nStreamLengthObject ) )
714         return;
715     OString aLine =
716         OString::number( m_nStreamLengthObject ) +
717         " 0 obj\n"  +
718         OString::number( static_cast<sal_Int64>(nEndStreamPos-m_nBeginStreamPos) ) +
719         "\nendobj\n\n";
720     m_pWriter->writeBuffer( aLine );
721 }
722 
emit(sal_Int32 nParentObject)723 bool PDFPage::emit(sal_Int32 nParentObject )
724 {
725     m_pWriter->MARK("PDFPage::emit");
726     // emit page object
727     if( ! m_pWriter->updateObject( m_nPageObject ) )
728         return false;
729     OStringBuffer aLine(
730         OString::number(m_nPageObject)
731         + " 0 obj\n"
732           "<</Type/Page/Parent "
733         + OString::number(nParentObject)
734         + " 0 R"
735         "/Resources "
736         + OString::number(m_pWriter->getResourceDictObj())
737         + " 0 R" );
738     if( m_nPageWidth && m_nPageHeight )
739     {
740         aLine.append( "/MediaBox[0 0 "
741             + OString::number(m_nPageWidth / m_nUserUnit)
742             + " "
743             + OString::number(m_nPageHeight / m_nUserUnit)
744             + "]" );
745         if (m_nUserUnit > 1)
746         {
747             aLine.append("\n/UserUnit " + OString::number(m_nUserUnit));
748         }
749     }
750     switch( m_eOrientation )
751     {
752         case PDFWriter::Orientation::Portrait: aLine.append( "/Rotate 0\n" );break;
753         case PDFWriter::Orientation::Inherit:  break;
754     }
755     int nAnnots = m_aAnnotations.size();
756     if( nAnnots > 0 )
757     {
758         aLine.append( "/Annots[\n" );
759         for( int i = 0; i < nAnnots; i++ )
760         {
761             aLine.append( OString::number(m_aAnnotations[i])
762                 + " 0 R" );
763             aLine.append( ((i+1)%15) ? " " : "\n" );
764         }
765         aLine.append( "]\n" );
766     }
767     if (PDFWriter::PDFVersion::PDF_1_5 <= m_pWriter->m_aContext.Version)
768     {
769         // ISO 14289-1:2014, Clause: 7.18.3 requires it if there are annotations
770         // but Adobe Acrobat Pro complains if it is ever missing so just
771         // write it always.
772         aLine.append( "/Tabs/S\n" );
773     }
774     if( !m_aMCIDParents.empty() )
775     {
776         OStringBuffer aStructParents( 1024 );
777         aStructParents.append( "[ " );
778         int nParents = m_aMCIDParents.size();
779         for( int i = 0; i < nParents; i++ )
780         {
781             aStructParents.append( OString::number(m_aMCIDParents[i])
782                 + " 0 R" );
783             aStructParents.append( ((i%10) == 9) ? "\n" : " " );
784         }
785         aStructParents.append( "]" );
786         m_pWriter->m_aStructParentTree.push_back( aStructParents.makeStringAndClear() );
787 
788         aLine.append( "/StructParents "
789             + OString::number( sal_Int32(m_pWriter->m_aStructParentTree.size()-1) )
790             + "\n" );
791     }
792     if( m_eTransition != PDFWriter::PageTransition::Regular && m_nTransTime > 0 )
793     {
794         // transition duration
795         aLine.append( "/Trans<</D " );
796         appendDouble( static_cast<double>(m_nTransTime)/1000.0, aLine, 3 );
797         aLine.append( "\n" );
798         const char *pStyle = nullptr, *pDm = nullptr, *pM = nullptr, *pDi = nullptr;
799         switch( m_eTransition )
800         {
801             case PDFWriter::PageTransition::SplitHorizontalInward:
802                 pStyle = "Split"; pDm = "H"; pM = "I"; break;
803             case PDFWriter::PageTransition::SplitHorizontalOutward:
804                 pStyle = "Split"; pDm = "H"; pM = "O"; break;
805             case PDFWriter::PageTransition::SplitVerticalInward:
806                 pStyle = "Split"; pDm = "V"; pM = "I"; break;
807             case PDFWriter::PageTransition::SplitVerticalOutward:
808                 pStyle = "Split"; pDm = "V"; pM = "O"; break;
809             case PDFWriter::PageTransition::BlindsHorizontal:
810                 pStyle = "Blinds"; pDm = "H"; break;
811             case PDFWriter::PageTransition::BlindsVertical:
812                 pStyle = "Blinds"; pDm = "V"; break;
813             case PDFWriter::PageTransition::BoxInward:
814                 pStyle = "Box"; pM = "I"; break;
815             case PDFWriter::PageTransition::BoxOutward:
816                 pStyle = "Box"; pM = "O"; break;
817             case PDFWriter::PageTransition::WipeLeftToRight:
818                 pStyle = "Wipe"; pDi = "0"; break;
819             case PDFWriter::PageTransition::WipeBottomToTop:
820                 pStyle = "Wipe"; pDi = "90"; break;
821             case PDFWriter::PageTransition::WipeRightToLeft:
822                 pStyle = "Wipe"; pDi = "180"; break;
823             case PDFWriter::PageTransition::WipeTopToBottom:
824                 pStyle = "Wipe"; pDi = "270"; break;
825             case PDFWriter::PageTransition::Dissolve:
826                 pStyle = "Dissolve"; break;
827             case PDFWriter::PageTransition::Regular:
828                 break;
829         }
830         // transition style
831         if( pStyle )
832         {
833             aLine.append( OString::Concat("/S/") + pStyle + "\n" );
834         }
835         if( pDm )
836         {
837             aLine.append( OString::Concat("/Dm/") + pDm + "\n" );
838         }
839         if( pM )
840         {
841             aLine.append( OString::Concat("/M/") + pM + "\n" );
842         }
843         if( pDi  )
844         {
845             aLine.append( OString::Concat("/Di ") + pDi + "\n" );
846         }
847         aLine.append( ">>\n" );
848     }
849 
850     aLine.append( "/Contents" );
851     unsigned int nStreamObjects = m_aStreamObjects.size();
852     if( nStreamObjects > 1 )
853         aLine.append( '[' );
854     for(sal_Int32 i : m_aStreamObjects)
855     {
856         aLine.append( " " + OString::number( i ) + " 0 R" );
857     }
858     if( nStreamObjects > 1 )
859         aLine.append( ']' );
860     aLine.append( ">>\nendobj\n\n" );
861     return m_pWriter->writeBuffer( aLine );
862 }
863 
appendPoint(const Point & rPoint,OStringBuffer & rBuffer) const864 void PDFPage::appendPoint( const Point& rPoint, OStringBuffer& rBuffer ) const
865 {
866     Point aPoint( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
867                                m_pWriter->m_aMapMode,
868                                m_pWriter,
869                                rPoint ) );
870 
871     sal_Int32 nValue    = aPoint.X();
872 
873     appendFixedInt( nValue, rBuffer );
874 
875     rBuffer.append( ' ' );
876 
877     nValue      = pointToPixel(getHeight()) - aPoint.Y();
878 
879     appendFixedInt( nValue, rBuffer );
880 }
881 
appendPixelPoint(const basegfx::B2DPoint & rPoint,OStringBuffer & rBuffer) const882 void PDFPage::appendPixelPoint( const basegfx::B2DPoint& rPoint, OStringBuffer& rBuffer ) const
883 {
884     double fValue   = pixelToPoint(rPoint.getX());
885 
886     appendDouble( fValue, rBuffer, nLog10Divisor );
887     rBuffer.append( ' ' );
888     fValue      = getHeight() - pixelToPoint(rPoint.getY());
889     appendDouble( fValue, rBuffer, nLog10Divisor );
890 }
891 
appendRect(const tools::Rectangle & rRect,OStringBuffer & rBuffer) const892 void PDFPage::appendRect( const tools::Rectangle& rRect, OStringBuffer& rBuffer ) const
893 {
894     appendPoint( rRect.BottomLeft() + Point( 0, 1 ), rBuffer );
895     rBuffer.append( ' ' );
896     appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), rBuffer, false );
897     rBuffer.append( ' ' );
898     appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), rBuffer );
899     rBuffer.append( " re" );
900 }
901 
convertRect(tools::Rectangle & rRect) const902 void PDFPage::convertRect( tools::Rectangle& rRect ) const
903 {
904     Point aLL = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
905                              m_pWriter->m_aMapMode,
906                              m_pWriter,
907                              rRect.BottomLeft() + Point( 0, 1 )
908                              );
909     Size aSize = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
910                               m_pWriter->m_aMapMode,
911                               m_pWriter,
912                               rRect.GetSize() );
913     rRect.SetLeft( aLL.X() );
914     rRect.SetRight( aLL.X() + aSize.Width() );
915     rRect.SetTop( pointToPixel(getHeight()) - aLL.Y() );
916     rRect.SetBottom( rRect.Top() + aSize.Height() );
917 }
918 
appendPolygon(const tools::Polygon & rPoly,OStringBuffer & rBuffer,bool bClose) const919 void PDFPage::appendPolygon( const tools::Polygon& rPoly, OStringBuffer& rBuffer, bool bClose ) const
920 {
921     sal_uInt16 nPoints = rPoly.GetSize();
922     /*
923      *  #108582# applications do weird things
924      */
925     sal_uInt32 nBufLen = rBuffer.getLength();
926     if( nPoints <= 0 )
927         return;
928 
929     const PolyFlags* pFlagArray = rPoly.GetConstFlagAry();
930     appendPoint( rPoly[0], rBuffer );
931     rBuffer.append( " m\n" );
932     for( sal_uInt16 i = 1; i < nPoints; i++ )
933     {
934         if( pFlagArray && pFlagArray[i] == PolyFlags::Control && nPoints-i > 2 )
935         {
936             // bezier
937             SAL_WARN_IF( pFlagArray[i+1] != PolyFlags::Control || pFlagArray[i+2] == PolyFlags::Control, "vcl.pdfwriter", "unexpected sequence of control points" );
938             appendPoint( rPoly[i], rBuffer );
939             rBuffer.append( " " );
940             appendPoint( rPoly[i+1], rBuffer );
941             rBuffer.append( " " );
942             appendPoint( rPoly[i+2], rBuffer );
943             rBuffer.append( " c" );
944             i += 2; // add additionally consumed points
945         }
946         else
947         {
948             // line
949             appendPoint( rPoly[i], rBuffer );
950             rBuffer.append( " l" );
951         }
952         if( (rBuffer.getLength() - nBufLen) > 65 )
953         {
954             rBuffer.append( "\n" );
955             nBufLen = rBuffer.getLength();
956         }
957         else
958             rBuffer.append( " " );
959     }
960     if( bClose )
961         rBuffer.append( "h\n" );
962 }
963 
appendPolygon(const basegfx::B2DPolygon & rPoly,OStringBuffer & rBuffer) const964 void PDFPage::appendPolygon( const basegfx::B2DPolygon& rPoly, OStringBuffer& rBuffer ) const
965 {
966     basegfx::B2DPolygon aPoly( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
967                                             m_pWriter->m_aMapMode,
968                                             m_pWriter,
969                                             rPoly ) );
970 
971     if( basegfx::utils::isRectangle( aPoly ) )
972     {
973         basegfx::B2DRange aRange( aPoly.getB2DRange() );
974         basegfx::B2DPoint aBL( aRange.getMinX(), aRange.getMaxY() );
975         appendPixelPoint( aBL, rBuffer );
976         rBuffer.append( ' ' );
977         appendMappedLength( aRange.getWidth(), rBuffer, false, nLog10Divisor );
978         rBuffer.append( ' ' );
979         appendMappedLength( aRange.getHeight(), rBuffer, true, nLog10Divisor );
980         rBuffer.append( " re\n" );
981         return;
982     }
983     sal_uInt32 nPoints = aPoly.count();
984     if( nPoints <= 0 )
985         return;
986 
987     sal_uInt32 nBufLen = rBuffer.getLength();
988     basegfx::B2DPoint aLastPoint( aPoly.getB2DPoint( 0 ) );
989     appendPixelPoint( aLastPoint, rBuffer );
990     rBuffer.append( " m\n" );
991     for( sal_uInt32 i = 1; i <= nPoints; i++ )
992     {
993         if( i != nPoints || aPoly.isClosed() )
994         {
995             sal_uInt32 nCurPoint  = i % nPoints;
996             sal_uInt32 nLastPoint = i-1;
997             basegfx::B2DPoint aPoint( aPoly.getB2DPoint( nCurPoint ) );
998             if( aPoly.isNextControlPointUsed( nLastPoint ) &&
999                 aPoly.isPrevControlPointUsed( nCurPoint ) )
1000             {
1001                 appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer );
1002                 rBuffer.append( ' ' );
1003                 appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer );
1004                 rBuffer.append( ' ' );
1005                 appendPixelPoint( aPoint, rBuffer );
1006                 rBuffer.append( " c" );
1007             }
1008             else if( aPoly.isNextControlPointUsed( nLastPoint ) )
1009             {
1010                 appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer );
1011                 rBuffer.append( ' ' );
1012                 appendPixelPoint( aPoint, rBuffer );
1013                 rBuffer.append( " y" );
1014             }
1015             else if( aPoly.isPrevControlPointUsed( nCurPoint ) )
1016             {
1017                 appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer );
1018                 rBuffer.append( ' ' );
1019                 appendPixelPoint( aPoint, rBuffer );
1020                 rBuffer.append( " v" );
1021             }
1022             else
1023             {
1024                 appendPixelPoint( aPoint, rBuffer );
1025                 rBuffer.append( " l" );
1026             }
1027             if( (rBuffer.getLength() - nBufLen) > 65 )
1028             {
1029                 rBuffer.append( "\n" );
1030                 nBufLen = rBuffer.getLength();
1031             }
1032             else
1033                 rBuffer.append( " " );
1034         }
1035     }
1036     rBuffer.append( "h\n" );
1037 }
1038 
appendPolyPolygon(const tools::PolyPolygon & rPolyPoly,OStringBuffer & rBuffer) const1039 void PDFPage::appendPolyPolygon( const tools::PolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
1040 {
1041     sal_uInt16 nPolygons = rPolyPoly.Count();
1042     for( sal_uInt16 n = 0; n < nPolygons; n++ )
1043         appendPolygon( rPolyPoly[n], rBuffer );
1044 }
1045 
appendPolyPolygon(const basegfx::B2DPolyPolygon & rPolyPoly,OStringBuffer & rBuffer) const1046 void PDFPage::appendPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
1047 {
1048     for(auto const& rPolygon : rPolyPoly)
1049         appendPolygon( rPolygon, rBuffer );
1050 }
1051 
appendMappedLength(sal_Int32 nLength,OStringBuffer & rBuffer,bool bVertical,sal_Int32 * pOutLength) const1052 void PDFPage::appendMappedLength( sal_Int32 nLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32* pOutLength ) const
1053 {
1054     sal_Int32 nValue = nLength;
1055     if ( nLength < 0 )
1056     {
1057         rBuffer.append( '-' );
1058         nValue = -nLength;
1059     }
1060     Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1061                              m_pWriter->m_aMapMode,
1062                              m_pWriter,
1063                              Size( nValue, nValue ) ) );
1064     nValue = bVertical ? aSize.Height() : aSize.Width();
1065     if( pOutLength )
1066         *pOutLength = ((nLength < 0 ) ? -nValue : nValue);
1067 
1068     appendFixedInt( nValue, rBuffer );
1069 }
1070 
appendMappedLength(double fLength,OStringBuffer & rBuffer,bool bVertical,sal_Int32 nPrecision) const1071 void PDFPage::appendMappedLength( double fLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32 nPrecision ) const
1072 {
1073     Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1074                              m_pWriter->m_aMapMode,
1075                              m_pWriter,
1076                              Size( 1000, 1000 ) ) );
1077     fLength *= pixelToPoint(static_cast<double>(bVertical ? aSize.Height() : aSize.Width()) / 1000.0);
1078     appendDouble( fLength, rBuffer, nPrecision );
1079 }
1080 
appendLineInfo(const LineInfo & rInfo,OStringBuffer & rBuffer) const1081 bool PDFPage::appendLineInfo( const LineInfo& rInfo, OStringBuffer& rBuffer ) const
1082 {
1083     if(LineStyle::Dash == rInfo.GetStyle() && rInfo.GetDashLen() != rInfo.GetDotLen())
1084     {
1085         // dashed and non-degraded case, check for implementation limits of dash array
1086         // in PDF reader apps (e.g. acroread)
1087         if(2 * (rInfo.GetDashCount() + rInfo.GetDotCount()) > 10)
1088         {
1089             return false;
1090         }
1091     }
1092 
1093     if(basegfx::B2DLineJoin::NONE != rInfo.GetLineJoin())
1094     {
1095         // LineJoin used, ExtLineInfo required
1096         return false;
1097     }
1098 
1099     if(css::drawing::LineCap_BUTT != rInfo.GetLineCap())
1100     {
1101         // LineCap used, ExtLineInfo required
1102         return false;
1103     }
1104 
1105     if( rInfo.GetStyle() == LineStyle::Dash )
1106     {
1107         rBuffer.append( "[ " );
1108         if( rInfo.GetDashLen() == rInfo.GetDotLen() ) // degraded case
1109         {
1110             appendMappedLength( rInfo.GetDashLen(), rBuffer );
1111             rBuffer.append( ' ' );
1112             appendMappedLength( rInfo.GetDistance(), rBuffer );
1113             rBuffer.append( ' ' );
1114         }
1115         else
1116         {
1117             for( int n = 0; n < rInfo.GetDashCount(); n++ )
1118             {
1119                 appendMappedLength( rInfo.GetDashLen(), rBuffer );
1120                 rBuffer.append( ' ' );
1121                 appendMappedLength( rInfo.GetDistance(), rBuffer );
1122                 rBuffer.append( ' ' );
1123             }
1124             for( int m = 0; m < rInfo.GetDotCount(); m++ )
1125             {
1126                 appendMappedLength( rInfo.GetDotLen(), rBuffer );
1127                 rBuffer.append( ' ' );
1128                 appendMappedLength( rInfo.GetDistance(), rBuffer );
1129                 rBuffer.append( ' ' );
1130             }
1131         }
1132         rBuffer.append( "] 0 d\n" );
1133     }
1134 
1135     if( rInfo.GetWidth() > 1 )
1136     {
1137         appendMappedLength( rInfo.GetWidth(), rBuffer );
1138         rBuffer.append( " w\n" );
1139     }
1140     else if( rInfo.GetWidth() == 0 )
1141     {
1142         // "pixel" line
1143         appendDouble( 72.0/double(m_pWriter->GetDPIX()), rBuffer );
1144         rBuffer.append( " w\n" );
1145     }
1146 
1147     return true;
1148 }
1149 
appendWaveLine(sal_Int32 nWidth,sal_Int32 nY,sal_Int32 nDelta,OStringBuffer & rBuffer) const1150 void PDFPage::appendWaveLine( sal_Int32 nWidth, sal_Int32 nY, sal_Int32 nDelta, OStringBuffer& rBuffer ) const
1151 {
1152     if( nWidth <= 0 )
1153         return;
1154     if( nDelta < 1 )
1155         nDelta = 1;
1156 
1157     rBuffer.append( "0 " );
1158     appendMappedLength( nY, rBuffer );
1159     rBuffer.append( " m\n" );
1160     for( sal_Int32 n = 0; n < nWidth; )
1161     {
1162         n += nDelta;
1163         appendMappedLength( n, rBuffer, false );
1164         rBuffer.append( ' ' );
1165         appendMappedLength( nDelta+nY, rBuffer );
1166         rBuffer.append( ' ' );
1167         n += nDelta;
1168         appendMappedLength( n, rBuffer, false );
1169         rBuffer.append( ' ' );
1170         appendMappedLength( nY, rBuffer );
1171         rBuffer.append( " v " );
1172         if( n < nWidth )
1173         {
1174             n += nDelta;
1175             appendMappedLength( n, rBuffer, false );
1176             rBuffer.append( ' ' );
1177             appendMappedLength( nY-nDelta, rBuffer );
1178             rBuffer.append( ' ' );
1179             n += nDelta;
1180             appendMappedLength( n, rBuffer, false );
1181             rBuffer.append( ' ' );
1182             appendMappedLength( nY, rBuffer );
1183             rBuffer.append( " v\n" );
1184         }
1185     }
1186     rBuffer.append( "S\n" );
1187 }
1188 
appendMatrix3(Matrix3 const & rMatrix,OStringBuffer & rBuffer)1189 void PDFPage::appendMatrix3(Matrix3 const & rMatrix, OStringBuffer& rBuffer)
1190 {
1191     appendDouble(rMatrix.get(0), rBuffer);
1192     rBuffer.append(' ');
1193     appendDouble(rMatrix.get(1), rBuffer);
1194     rBuffer.append(' ');
1195     appendDouble(rMatrix.get(2), rBuffer);
1196     rBuffer.append(' ');
1197     appendDouble(rMatrix.get(3), rBuffer);
1198     rBuffer.append(' ');
1199     appendPoint(Point(tools::Long(rMatrix.get(4)), tools::Long(rMatrix.get(5))), rBuffer);
1200 }
1201 
getHeight() const1202 double PDFPage::getHeight() const
1203 {
1204     double fRet = m_nPageHeight ? m_nPageHeight : 842; // default A4 height in inch/72, OK to use hardcoded value here?
1205 
1206     if (m_nUserUnit > 1)
1207     {
1208         fRet /= m_nUserUnit;
1209     }
1210 
1211     return fRet;
1212 }
1213 
PDFWriterImpl(const PDFWriter::PDFWriterContext & rContext,const css::uno::Reference<css::beans::XMaterialHolder> & xEncryptionMaterialHolder,PDFWriter & i_rOuterFace)1214 PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext,
1215                                const css::uno::Reference< css::beans::XMaterialHolder >& xEncryptionMaterialHolder,
1216                                PDFWriter& i_rOuterFace)
1217         : VirtualDevice(Application::GetDefaultDevice(), DeviceFormat::WITHOUT_ALPHA, OUTDEV_PDF),
1218         m_aMapMode( MapUnit::MapPoint, Point(), Fraction( 1, pointToPixel(1) ), Fraction( 1, pointToPixel(1) ) ),
1219         m_aWidgetStyleSettings(Application::GetSettings().GetStyleSettings()),
1220         m_nCurrentStructElement( 0 ),
1221         m_bEmitStructure( true ),
1222         m_nNextFID( 1 ),
1223         m_aPDFBmpCache(comphelper::IsFuzzing() ? 15 :
1224             officecfg::Office::Common::VCL::PDFExportImageCacheSize::get()),
1225         m_nCurrentPage( -1 ),
1226         m_nCatalogObject(0),
1227         m_nSignatureObject( -1 ),
1228         m_nSignatureContentOffset( 0 ),
1229         m_nSignatureLastByteRangeNoOffset( 0 ),
1230         m_nResourceDict( -1 ),
1231         m_nFontDictObject( -1 ),
1232         m_aContext(rContext),
1233         m_aFile(m_aContext.URL),
1234         m_bOpen(false),
1235         m_DocDigest(::comphelper::HashType::MD5),
1236         m_rOuterFace( i_rOuterFace )
1237 {
1238     m_aStructure.emplace_back( );
1239     m_aStructure[0].m_nOwnElement       = 0;
1240     m_aStructure[0].m_nParentElement    = 0;
1241     //m_StructElementStack.push(0);
1242 
1243     // tdf#150786 use the same settings for widgets regardless of theme
1244     m_aWidgetStyleSettings.SetStandardStyles();
1245 
1246     GraphicsState aState;
1247     aState.m_aMapMode       = m_aMapMode;
1248     aState.m_aFont.SetFamilyName( u"Times"_ustr );
1249     aState.m_aFont.SetFontSize( Size( 0, 12 ) );
1250 
1251     m_aGraphicsStack.push_front( aState );
1252 
1253     osl::File::RC aError = m_aFile.open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create);
1254     if (aError != osl::File::E_None)
1255     {
1256         if (aError == osl::File::E_EXIST)
1257         {
1258             aError = m_aFile.open(osl_File_OpenFlag_Write);
1259             if (aError == osl::File::E_None)
1260                 aError = m_aFile.setSize(0);
1261         }
1262     }
1263     if (aError != osl::File::E_None)
1264         return;
1265 
1266     m_bOpen = true;
1267 
1268     // setup DocInfo
1269     setupDocInfo();
1270 
1271     if (xEncryptionMaterialHolder.is())
1272     {
1273         if (m_aContext.Version == PDFWriter::PDFVersion::PDF_2_0 || m_aContext.Version == PDFWriter::PDFVersion::PDF_A_4)
1274             m_pPDFEncryptor.reset(new PDFEncryptorR6);
1275         else
1276             m_pPDFEncryptor.reset(new PDFEncryptor);
1277         m_pPDFEncryptor->prepareEncryption(xEncryptionMaterialHolder, m_aContext.Encryption);
1278     }
1279 
1280     if (m_pPDFEncryptor && m_aContext.Encryption.canEncrypt())
1281     {
1282         m_pPDFEncryptor->setupKeysAndCheck(m_aContext.Encryption);
1283     }
1284 
1285     // write header
1286     OStringBuffer aBuffer( 20 );
1287     aBuffer.append( "%PDF-" );
1288     aBuffer.append(getPDFVersionStr(m_aContext.Version));
1289     // append something binary as comment (suggested in PDF Reference)
1290     aBuffer.append( "\n%\303\244\303\274\303\266\303\237\n" );
1291     if( !writeBuffer( aBuffer ) )
1292     {
1293         m_aFile.close();
1294         m_bOpen = false;
1295         return;
1296     }
1297 
1298     // insert outline root
1299     m_aOutline.emplace_back( );
1300 
1301     switch (m_aContext.Version)
1302     {
1303         case PDFWriter::PDFVersion::PDF_A_1:
1304             m_nPDFA_Version = 1;
1305             m_bIsPDF_A1 = true;
1306             m_aContext.Version = PDFWriter::PDFVersion::PDF_1_4; //meaning we need PDF 1.4, PDF/A flavour
1307             break;
1308         case PDFWriter::PDFVersion::PDF_A_2:
1309             m_nPDFA_Version = 2;
1310             m_bIsPDF_A2 = true;
1311             m_aContext.Version = PDFWriter::PDFVersion::PDF_1_7;
1312             break;
1313         case PDFWriter::PDFVersion::PDF_A_3:
1314             m_nPDFA_Version = 3;
1315             m_bIsPDF_A3 = true;
1316             m_aContext.Version = PDFWriter::PDFVersion::PDF_1_7;
1317             break;
1318         case PDFWriter::PDFVersion::PDF_A_4:
1319             m_nPDFA_Version = 4;
1320             m_bIsPDF_A4 = true;
1321             m_aContext.Version = PDFWriter::PDFVersion::PDF_2_0;
1322             break;
1323         default:
1324             break;
1325     }
1326 
1327     // PDF/UA can only be enabled if PDF version is 1.7 (PDF/UA-1) and 2.0 (PDF/UA-2)
1328     if (m_aContext.UniversalAccessibilityCompliance && m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_7)
1329     {
1330         m_bIsPDF_UA = true;
1331         m_aContext.Tagged = true;
1332     }
1333 
1334     // Add common PDF 2.0 namespace when we are using PDF 2.0
1335     if (m_aContext.Version == PDFWriter::PDFVersion::PDF_2_0)
1336     {
1337         m_aNamespacesMap.emplace(constNamespacePDF2, createObject());
1338     }
1339 
1340     if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 )
1341         SetReferenceDevice( VirtualDevice::RefDevMode::PDF1 );
1342     else
1343         SetReferenceDevice( m_aContext.DPIx, m_aContext.DPIy );
1344 
1345     SetOutputSizePixel( Size( 640, 480 ) );
1346     SetMapMode(MapMode(MapUnit::MapMM));
1347 }
1348 
~PDFWriterImpl()1349 PDFWriterImpl::~PDFWriterImpl()
1350 {
1351     disposeOnce();
1352 }
1353 
dispose()1354 void PDFWriterImpl::dispose()
1355 {
1356     m_aPages.clear();
1357     VirtualDevice::dispose();
1358 }
1359 
ImplNewFont() const1360 bool PDFWriterImpl::ImplNewFont() const
1361 {
1362     const ImplSVData* pSVData = ImplGetSVData();
1363 
1364     if( mxFontCollection == pSVData->maGDIData.mxScreenFontList
1365         ||  mxFontCache == pSVData->maGDIData.mxScreenFontCache )
1366     {
1367         const_cast<vcl::PDFWriterImpl&>(*this).ImplUpdateFontData();
1368     }
1369 
1370     return OutputDevice::ImplNewFont();
1371 }
1372 
setupDocInfo()1373 void PDFWriterImpl::setupDocInfo()
1374 {
1375     std::vector< sal_uInt8 > aId;
1376     m_aCreationDateString = PDFWriter::GetDateTime();
1377     computeDocumentIdentifier(aId, m_aContext.DocumentInfo, m_aCreationDateString, m_aContext.DocumentInfo.ModificationDate, m_aCreationMetaDateString);
1378     if( m_aContext.Encryption.DocumentIdentifier.empty() )
1379         m_aContext.Encryption.DocumentIdentifier = std::move(aId);
1380 }
1381 
GetDateTime(svl::crypto::SigningContext * pSigningContext)1382 OString PDFWriter::GetDateTime(svl::crypto::SigningContext* pSigningContext)
1383 {
1384     OStringBuffer aRet;
1385 
1386     TimeValue aTVal, aGMT;
1387     oslDateTime aDT;
1388     osl_getSystemTime(&aGMT);
1389 
1390     if (pSigningContext)
1391     {
1392         // The context unit is milliseconds, TimeValue is seconds + nanoseconds.
1393         if (pSigningContext->m_nSignatureTime)
1394         {
1395             aGMT = std::chrono::milliseconds(pSigningContext->m_nSignatureTime);
1396         }
1397         else
1398         {
1399             pSigningContext->m_nSignatureTime = static_cast<sal_Int64>(aGMT.Seconds) * 1000 + aGMT.Nanosec / 1000000;
1400         }
1401     }
1402 
1403     osl_getLocalTimeFromSystemTime(&aGMT, &aTVal);
1404     osl_getDateTimeFromTimeValue(&aTVal, &aDT);
1405 
1406     sal_Int32 nDelta = aTVal.Seconds-aGMT.Seconds;
1407 
1408     appendPdfTimeDate(aRet, aDT.Year, aDT.Month, aDT.Day, aDT.Hours, aDT.Minutes, aDT.Seconds, nDelta);
1409 
1410     aRet.append("'");
1411     return aRet.makeStringAndClear();
1412 }
1413 
emitComment(const char * pComment)1414 void PDFWriterImpl::emitComment( const char* pComment )
1415 {
1416     OString aLine = OString::Concat("% ") + pComment + "\n";
1417     writeBuffer( aLine );
1418 }
1419 
compressStream(SvMemoryStream * pStream)1420 bool PDFWriterImpl::compressStream( SvMemoryStream* pStream )
1421 {
1422     if (!g_bDebugDisableCompression)
1423     {
1424         sal_uInt64 nEndPos = pStream->TellEnd();
1425         pStream->Seek( STREAM_SEEK_TO_BEGIN );
1426         ZCodec aCodec( 0x4000, 0x4000 );
1427         SvMemoryStream aStream;
1428         aCodec.BeginCompression();
1429         aCodec.Write( aStream, static_cast<const sal_uInt8*>(pStream->GetData()), nEndPos );
1430         aCodec.EndCompression();
1431         nEndPos = aStream.Tell();
1432         pStream->Seek( STREAM_SEEK_TO_BEGIN );
1433         aStream.Seek( STREAM_SEEK_TO_BEGIN );
1434         pStream->SetStreamSize( nEndPos );
1435         pStream->WriteBytes( aStream.GetData(), nEndPos );
1436         return true;
1437     }
1438     else
1439         return false;
1440 }
1441 
beginCompression()1442 void PDFWriterImpl::beginCompression()
1443 {
1444     if (!g_bDebugDisableCompression)
1445     {
1446         m_pCodec = std::make_unique<ZCodec>( 0x4000, 0x4000 );
1447         m_pMemStream = std::make_unique<SvMemoryStream>();
1448         m_pCodec->BeginCompression();
1449     }
1450 }
1451 
endCompression()1452 void PDFWriterImpl::endCompression()
1453 {
1454     if (!g_bDebugDisableCompression && m_pCodec)
1455     {
1456         m_pCodec->EndCompression();
1457         m_pCodec.reset();
1458         sal_uInt64 nLen = m_pMemStream->Tell();
1459         m_pMemStream->Seek( 0 );
1460         (void)writeBufferBytes( m_pMemStream->GetData(), nLen );
1461         m_pMemStream.reset();
1462     }
1463 }
1464 
writeBufferBytes(const void * pBuffer,sal_uInt64 nBytes)1465 bool PDFWriterImpl::writeBufferBytes( const void* pBuffer, sal_uInt64 nBytes )
1466 {
1467     if( ! m_bOpen ) // we are already down the drain
1468         return false;
1469 
1470     if( ! nBytes ) // huh ?
1471         return true;
1472 
1473     if( !m_aOutputStreams.empty() )
1474     {
1475         m_aOutputStreams.front().m_pStream->Seek( STREAM_SEEK_TO_END );
1476         m_aOutputStreams.front().m_pStream->WriteBytes(
1477                 pBuffer, sal::static_int_cast<std::size_t>(nBytes));
1478         return true;
1479     }
1480 
1481     sal_uInt64 nWritten;
1482     sal_uInt64 nActualSize = nBytes;
1483 
1484     // we are compressing the stream
1485     if (m_pCodec)
1486     {
1487         m_pCodec->Write( *m_pMemStream, static_cast<const sal_uInt8*>(pBuffer), static_cast<sal_uLong>(nBytes) );
1488         nWritten = nBytes;
1489     }
1490     else
1491     {
1492         // is it encrypted?
1493         bool bStreamEncryption = m_pPDFEncryptor && m_pPDFEncryptor->isStreamEncryptionEnabled();
1494         if (bStreamEncryption)
1495         {
1496             nActualSize = m_pPDFEncryptor->calculateSizeIncludingHeader(nActualSize);
1497             m_vEncryptionBuffer.resize(nActualSize);
1498             m_pPDFEncryptor->encrypt(pBuffer, nBytes, m_vEncryptionBuffer, nActualSize);
1499         }
1500 
1501         const void* pWriteBuffer = bStreamEncryption ? m_vEncryptionBuffer.data() : pBuffer;
1502         m_DocDigest.update(pWriteBuffer, sal_uInt32(nActualSize));
1503 
1504 
1505         if (m_aFile.write(pWriteBuffer, nActualSize, nWritten) != osl::File::E_None)
1506             nWritten = 0;
1507 
1508         if (nWritten != nActualSize)
1509         {
1510             m_aFile.close();
1511             m_bOpen = false;
1512             return false;
1513         }
1514         return true;
1515     }
1516 
1517     return nWritten == nActualSize;
1518 }
1519 
newPage(double nPageWidth,double nPageHeight,PDFWriter::Orientation eOrientation)1520 void PDFWriterImpl::newPage( double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
1521 {
1522     endPage();
1523     m_nCurrentPage = m_aPages.size();
1524     m_aPages.emplace_back(this, nPageWidth, nPageHeight, eOrientation );
1525 
1526     const Fraction frac(m_aPages.back().m_nUserUnit, pointToPixel(1));
1527     m_aMapMode = MapMode(MapUnit::MapPoint, Point(), frac, frac);
1528 
1529     m_aPages.back().beginStream();
1530 
1531     // setup global graphics state
1532     // linewidth is "1 pixel" by default
1533     OStringBuffer aBuf( 16 );
1534     appendDouble( 72.0/double(GetDPIX()), aBuf );
1535     aBuf.append( " w\n" );
1536     writeBuffer( aBuf );
1537 }
1538 
endPage()1539 void PDFWriterImpl::endPage()
1540 {
1541     if( m_aPages.empty() )
1542         return;
1543 
1544     // close eventual MC sequence
1545     endStructureElementMCSeq();
1546 
1547     // sanity check
1548     if( !m_aOutputStreams.empty() )
1549     {
1550         OSL_FAIL( "redirection across pages !!!" );
1551         m_aOutputStreams.clear(); // leak !
1552         m_aMapMode.SetOrigin( Point() );
1553     }
1554 
1555     m_aGraphicsStack.clear();
1556     m_aGraphicsStack.emplace_back( );
1557 
1558     // this should pop the PDF graphics stack if necessary
1559     updateGraphicsState();
1560 
1561     m_aPages.back().endStream();
1562 
1563     // reset the default font
1564     Font aFont;
1565     aFont.SetFamilyName( u"Times"_ustr );
1566     aFont.SetFontSize( Size( 0, 12 ) );
1567 
1568     m_aCurrentPDFState = m_aGraphicsStack.front();
1569     m_aGraphicsStack.front().m_aFont = std::move(aFont);
1570 
1571     for (auto & bitmap : m_aBitmaps)
1572     {
1573         if( ! bitmap.m_aBitmap.IsEmpty() )
1574         {
1575             writeBitmapObject(bitmap);
1576             bitmap.m_aBitmap = Bitmap();
1577         }
1578     }
1579     for (auto & jpeg : m_aJPGs)
1580     {
1581         if( jpeg.m_pStream )
1582         {
1583             writeJPG( jpeg );
1584             jpeg.m_pStream.reset();
1585             jpeg.m_aAlphaMask = AlphaMask();
1586         }
1587     }
1588     for (auto & item : m_aTransparentObjects)
1589     {
1590         if( item.m_pContentStream )
1591         {
1592             writeTransparentObject(item);
1593             item.m_pContentStream.reset();
1594         }
1595     }
1596 
1597 }
1598 
createObject()1599 sal_Int32 PDFWriterImpl::createObject()
1600 {
1601     m_aObjects.push_back( ~0U );
1602     return m_aObjects.size();
1603 }
1604 
updateObject(sal_Int32 n)1605 bool PDFWriterImpl::updateObject( sal_Int32 n )
1606 {
1607     if( ! m_bOpen )
1608         return false;
1609 
1610     sal_uInt64 nOffset = ~0U;
1611     osl::File::RC aError = m_aFile.getPos(nOffset);
1612     SAL_WARN_IF( aError != osl::File::E_None, "vcl.pdfwriter", "could not register object" );
1613     if (aError != osl::File::E_None)
1614     {
1615         m_aFile.close();
1616         m_bOpen = false;
1617     }
1618     m_aObjects[ n-1 ] = nOffset;
1619     return aError == osl::File::E_None;
1620 }
1621 
emitStructParentTree(sal_Int32 nObject)1622 sal_Int32 PDFWriterImpl::emitStructParentTree( sal_Int32 nObject )
1623 {
1624     if( nObject > 0 )
1625     {
1626         OStringBuffer aLine( 1024 );
1627 
1628         aLine.append( OString::number(nObject)
1629             + " 0 obj\n"
1630               "<</Nums[\n" );
1631         sal_Int32 nTreeItems = m_aStructParentTree.size();
1632         for( sal_Int32 n = 0; n < nTreeItems; n++ )
1633         {
1634             aLine.append( OString::number(n) + " "
1635                 + m_aStructParentTree[n]
1636                 + "\n" );
1637         }
1638         aLine.append( "]>>\nendobj\n\n" );
1639         if (!updateObject(nObject))
1640             return 0;
1641         if (!writeBuffer(aLine))
1642             return 0;
1643     }
1644     return nObject;
1645 }
1646 
1647 // every structure element already has a unique object id - just use it for ID
GenerateID(sal_Int32 const nObjectId)1648 static OString GenerateID(sal_Int32 const nObjectId)
1649 {
1650     return "id" + OString::number(nObjectId);
1651 }
1652 
emitStructIDTree(sal_Int32 const nObject)1653 sal_Int32 PDFWriterImpl::emitStructIDTree(sal_Int32 const nObject)
1654 {
1655     // loosely following PDF 1.7, 10.6.5 Example of Logical Structure, Example 10.15
1656     if (nObject < 0)
1657     {
1658         return nObject;
1659     }
1660     // the name tree entries must be sorted lexicographically.
1661     std::map<OString, sal_Int32> ids;
1662     for (auto n : m_StructElemObjsWithID)
1663     {
1664         ids.emplace(GenerateID(n), n);
1665     }
1666     OStringBuffer buf;
1667     COSWriter aWriter(buf, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
1668     appendObjectID(nObject, buf);
1669     buf.append("<</Names [\n");
1670     for (auto const& it : ids)
1671     {
1672         aWriter.writeLiteralEncrypt(it.first, nObject);
1673         buf.append(" ");
1674         appendObjectReference(it.second, buf);
1675         buf.append("\n");
1676     }
1677     buf.append("] >>\nendobj\n\n");
1678 
1679     if (!updateObject(nObject)) return 0;
1680     if (!writeBuffer(buf)) return 0;
1681 
1682     return nObject;
1683 }
1684 
getAttributeTag(PDFWriter::StructAttribute eAttr)1685 const char* PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr )
1686 {
1687     static constexpr auto aAttributeStrings = frozen::make_unordered_map<PDFWriter::StructAttribute, const char*>({
1688         { PDFWriter::Placement,         "Placement" },
1689         { PDFWriter::WritingMode,       "WritingMode" },
1690         { PDFWriter::SpaceBefore,       "SpaceBefore" },
1691         { PDFWriter::SpaceAfter,        "SpaceAfter" },
1692         { PDFWriter::StartIndent,       "StartIndent" },
1693         { PDFWriter::EndIndent,         "EndIndent" },
1694         { PDFWriter::TextIndent,        "TextIndent" },
1695         { PDFWriter::TextAlign,         "TextAlign" },
1696         { PDFWriter::Width,             "Width" },
1697         { PDFWriter::Height,            "Height" },
1698         { PDFWriter::BlockAlign,        "BlockAlign" },
1699         { PDFWriter::InlineAlign,       "InlineAlign" },
1700         { PDFWriter::LineHeight,        "LineHeight" },
1701         { PDFWriter::BaselineShift,     "BaselineShift" },
1702         { PDFWriter::TextDecorationType,"TextDecorationType" },
1703         { PDFWriter::ListNumbering,     "ListNumbering" },
1704         { PDFWriter::RowSpan,           "RowSpan" },
1705         { PDFWriter::ColSpan,           "ColSpan" },
1706         { PDFWriter::Scope,             "Scope" },
1707         { PDFWriter::Role,              "Role" },
1708         { PDFWriter::RubyAlign,         "RubyAlign" },
1709         { PDFWriter::RubyPosition,      "RubyPosition" },
1710         { PDFWriter::Type,              "Type" },
1711         { PDFWriter::Subtype,           "Subtype" },
1712         { PDFWriter::LinkAnnotation,    "LinkAnnotation" },
1713         { PDFWriter::NoteAnnotation,    "NoteAnnotation" }
1714     });
1715 
1716     auto it = aAttributeStrings.find( eAttr );
1717 
1718     if( it == aAttributeStrings.end() )
1719         SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttribute " << eAttr);
1720 
1721     return it != aAttributeStrings.end() ? it->second : "";
1722 }
1723 
getAttributeValueTag(PDFWriter::StructAttributeValue eVal)1724 const char* PDFWriterImpl::getAttributeValueTag( PDFWriter::StructAttributeValue eVal )
1725 {
1726     static constexpr auto aValueStrings = frozen::make_unordered_map<PDFWriter::StructAttributeValue, const char*>({
1727         { PDFWriter::NONE,       "None" },
1728         { PDFWriter::Block,      "Block" },
1729         { PDFWriter::Inline,     "Inline" },
1730         { PDFWriter::Before,     "Before" },
1731         { PDFWriter::After,      "After" },
1732         { PDFWriter::Start,      "Start" },
1733         { PDFWriter::End,        "End" },
1734         { PDFWriter::LrTb,       "LrTb" },
1735         { PDFWriter::RlTb,       "RlTb" },
1736         { PDFWriter::TbRl,       "TbRl" },
1737         { PDFWriter::Center,     "Center" },
1738         { PDFWriter::Justify,    "Justify" },
1739         { PDFWriter::Auto,       "Auto" },
1740         { PDFWriter::Middle,     "Middle" },
1741         { PDFWriter::Normal,     "Normal" },
1742         { PDFWriter::Underline,  "Underline" },
1743         { PDFWriter::Overline,   "Overline" },
1744         { PDFWriter::LineThrough,"LineThrough" },
1745         { PDFWriter::Row,        "Row" },
1746         { PDFWriter::Column,     "Column" },
1747         { PDFWriter::Both,       "Both" },
1748         { PDFWriter::Pagination, "Pagination" },
1749         { PDFWriter::Layout,     "Layout" },
1750         { PDFWriter::Page,       "Page" },
1751         { PDFWriter::Background, "Background" },
1752         { PDFWriter::Header,     "Header" },
1753         { PDFWriter::Footer,     "Footer" },
1754         { PDFWriter::Watermark,  "Watermark" },
1755         { PDFWriter::Rb,         "rb" },
1756         { PDFWriter::Cb,         "cb" },
1757         { PDFWriter::Pb,         "pb" },
1758         { PDFWriter::Tv,         "tv" },
1759         { PDFWriter::RStart,     "Start" },
1760         { PDFWriter::RCenter,    "Center" },
1761         { PDFWriter::REnd,       "End" },
1762         { PDFWriter::RJustify,   "Justify" },
1763         { PDFWriter::RDistribute,"Distribute" },
1764         { PDFWriter::RBefore,    "Before" },
1765         { PDFWriter::RAfter,     "After" },
1766         { PDFWriter::RWarichu,   "Warichu" },
1767         { PDFWriter::RInline,    "Inline" },
1768         { PDFWriter::Disc,       "Disc" },
1769         { PDFWriter::Circle,     "Circle" },
1770         { PDFWriter::Square,     "Square" },
1771         { PDFWriter::Decimal,    "Decimal" },
1772         { PDFWriter::UpperRoman, "UpperRoman" },
1773         { PDFWriter::LowerRoman, "LowerRoman" },
1774         { PDFWriter::UpperAlpha, "UpperAlpha" },
1775         { PDFWriter::LowerAlpha, "LowerAlpha" }
1776     });
1777 
1778     auto it = aValueStrings.find( eVal );
1779 
1780     if( it == aValueStrings.end() )
1781         SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttributeValue " << eVal);
1782 
1783     return it != aValueStrings.end() ? it->second : "";
1784 }
1785 
appendStructureAttributeLine(PDFWriter::StructAttribute i_eAttr,const PDFStructureAttribute & i_rVal,OStringBuffer & o_rLine,bool i_bIsFixedInt)1786 static void appendStructureAttributeLine( PDFWriter::StructAttribute i_eAttr, const PDFStructureAttribute& i_rVal, OStringBuffer& o_rLine, bool i_bIsFixedInt )
1787 {
1788     o_rLine.append( "/" );
1789     o_rLine.append( PDFWriterImpl::getAttributeTag( i_eAttr ) );
1790 
1791     if( i_rVal.eValue != PDFWriter::Invalid )
1792     {
1793         o_rLine.append( "/" );
1794         o_rLine.append( PDFWriterImpl::getAttributeValueTag( i_rVal.eValue ) );
1795     }
1796     else
1797     {
1798         // numerical value
1799         o_rLine.append( " " );
1800         if( i_bIsFixedInt )
1801             appendFixedInt( i_rVal.nValue, o_rLine );
1802         else
1803             o_rLine.append( i_rVal.nValue );
1804     }
1805     o_rLine.append( "\n" );
1806 }
1807 
1808 template<typename T>
AppendAnnotKid(PDFStructureElement & i_rEle,T & rAnnot)1809 void PDFWriterImpl::AppendAnnotKid(PDFStructureElement& i_rEle, T & rAnnot)
1810 {
1811     // update struct parent of link
1812     OString const aStructParentEntry(OString::number(i_rEle.m_nObject) + " 0 R");
1813     m_aStructParentTree.push_back( aStructParentEntry );
1814     rAnnot.m_nStructParent = m_aStructParentTree.size()-1;
1815     sal_Int32 const nAnnotObj(rAnnot.m_nObject);
1816     i_rEle.m_aKids.emplace_back(ObjReferenceObj{nAnnotObj});
1817 }
1818 
emitStructureAttributes(PDFStructureElement & i_rEle)1819 OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle )
1820 {
1821     // create layout, list and table attribute sets
1822     OStringBuffer aLayout(256), aList(64), aTable(64);
1823     OStringBuffer aPrintField;
1824     for (auto const& attribute : i_rEle.m_aAttributes)
1825     {
1826         if( attribute.first == PDFWriter::ListNumbering )
1827             appendStructureAttributeLine( attribute.first, attribute.second, aList, true );
1828         else if (attribute.first == PDFWriter::Role)
1829         {
1830             appendStructureAttributeLine(attribute.first, attribute.second, aPrintField, true);
1831         }
1832         else if( attribute.first == PDFWriter::RowSpan ||
1833                  attribute.first == PDFWriter::ColSpan ||
1834                  attribute.first == PDFWriter::Scope)
1835         {
1836             appendStructureAttributeLine( attribute.first, attribute.second, aTable, false );
1837         }
1838         else if( attribute.first == PDFWriter::LinkAnnotation )
1839         {
1840             sal_Int32 nLink = attribute.second.nValue;
1841             std::map< sal_Int32, sal_Int32 >::const_iterator link_it =
1842                 m_aLinkPropertyMap.find( nLink );
1843             if( link_it != m_aLinkPropertyMap.end() )
1844                 nLink = link_it->second;
1845             if( nLink >= 0 && o3tl::make_unsigned(nLink) < m_aLinks.size() )
1846             {
1847                 AppendAnnotKid(i_rEle, m_aLinks[nLink]);
1848             }
1849             else
1850             {
1851                 OSL_FAIL( "unresolved link id for Link structure" );
1852                 SAL_INFO("vcl.pdfwriter", "unresolved link id " << nLink << " for Link structure");
1853                 if (g_bDebugDisableCompression)
1854                 {
1855                     OString aLine = "unresolved link id " +
1856                             OString::number( nLink ) +
1857                             " for Link structure";
1858                     emitComment( aLine.getStr() );
1859                 }
1860             }
1861         }
1862         else if (attribute.first == PDFWriter::NoteAnnotation)
1863         {
1864             sal_Int32 nNote = attribute.second.nValue;
1865             std::map<sal_Int32, sal_Int32>::const_iterator link_it = m_aLinkPropertyMap.find(nNote);
1866             if (link_it != m_aLinkPropertyMap.end())
1867                 nNote = link_it->second;
1868             if (nNote >= 0 && o3tl::make_unsigned(nNote) < m_aNotes.size())
1869             {
1870                 AppendAnnotKid(i_rEle, m_aNotes[nNote]);
1871             }
1872             else
1873             {
1874                 OSL_FAIL("unresolved note id for Note structure");
1875                 SAL_INFO("vcl.pdfwriter", "unresolved note id " << nNote << " for Note structure");
1876                 if (g_bDebugDisableCompression)
1877                 {
1878                     OString aLine
1879                         = "unresolved note id " + OString::number(nNote) + " for Note structure";
1880                     emitComment(aLine.getStr());
1881                 }
1882             }
1883         }
1884         else
1885             appendStructureAttributeLine( attribute.first, attribute.second, aLayout, true );
1886     }
1887     if( ! i_rEle.m_aBBox.IsEmpty() )
1888     {
1889         aLayout.append( "/BBox[" );
1890         appendFixedInt( i_rEle.m_aBBox.Left(), aLayout );
1891         aLayout.append( " " );
1892         appendFixedInt( i_rEle.m_aBBox.Top(), aLayout );
1893         aLayout.append( " " );
1894         appendFixedInt( i_rEle.m_aBBox.Right(), aLayout );
1895         aLayout.append( " " );
1896         appendFixedInt( i_rEle.m_aBBox.Bottom(), aLayout );
1897         aLayout.append( "]\n" );
1898     }
1899 
1900     OStringBuffer aRet(256);
1901     bool isArray(false);
1902     if (1 < (aLayout.isEmpty() ? 0 : 1) + (aList.isEmpty() ? 0 : 1)
1903             + (aPrintField.isEmpty() ? 0 : 1) + (aTable.isEmpty() ? 0 : 1))
1904     {
1905         isArray = true;
1906         aRet.append(" [");
1907     }
1908     auto const WriteAttrs = [&](char const*const pName, OStringBuffer & rBuf)
1909     {
1910         aRet.append(" <</O");
1911         aRet.append(pName);
1912         aRet.append(rBuf);
1913         aRet.append(">>");
1914     };
1915     if( !aLayout.isEmpty() )
1916     {
1917         WriteAttrs("/Layout", aLayout);
1918     }
1919     if( !aList.isEmpty() )
1920     {
1921         WriteAttrs("/List", aList);
1922     }
1923     if (!aPrintField.isEmpty())
1924     {
1925         WriteAttrs("/PrintField", aPrintField);
1926     }
1927     if( !aTable.isEmpty() )
1928     {
1929         WriteAttrs("/Table", aTable);
1930     }
1931 
1932     if (isArray)
1933     {
1934         aRet.append( " ]" );
1935     }
1936     return aRet.makeStringAndClear();
1937 }
1938 
1939 // Write the namespace objects to the stream
emitNamespaces()1940 void PDFWriterImpl::emitNamespaces()
1941 {
1942     if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0)
1943         return;
1944 
1945     for (auto&[sNamespace, nObject] : m_aNamespacesMap)
1946     {
1947         if (!updateObject(nObject))
1948             return;
1949 
1950         COSWriter aWriter(m_aContext.Encryption.getParams(), m_pPDFEncryptor);
1951         aWriter.startObject(nObject);
1952         aWriter.startDict();
1953         aWriter.write("/Type", "/Namespace");
1954         aWriter.writeKeyAndLiteral("/NS", sNamespace);
1955         aWriter.endDict();
1956         aWriter.endObject();
1957 
1958         if (!writeBuffer(aWriter.getLine()))
1959             m_aNamespacesMap[sNamespace] = 0;
1960     }
1961 }
1962 
emitStructure(PDFStructureElement & rEle)1963 sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle )
1964 {
1965     assert(rEle.m_nOwnElement == 0 || rEle.m_oType);
1966     if (rEle.m_nOwnElement != rEle.m_nParentElement // emit the struct tree root
1967        // do not emit NonStruct and its children
1968         && *rEle.m_oType == vcl::pdf::StructElement::NonStructElement)
1969     {
1970         return 0;
1971     }
1972 
1973     for (auto const& child : rEle.m_aChildren)
1974     {
1975         if( child > 0 && o3tl::make_unsigned(child) < m_aStructure.size() )
1976         {
1977             PDFStructureElement& rChild = m_aStructure[ child ];
1978             if (*rChild.m_oType != vcl::pdf::StructElement::NonStructElement)
1979             {
1980                 if( rChild.m_nParentElement == rEle.m_nOwnElement )
1981                     emitStructure( rChild );
1982                 else
1983                 {
1984                     OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure element" );
1985                     SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure element with id " << child);
1986                 }
1987             }
1988         }
1989         else
1990         {
1991             OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
1992             SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure id " << child);
1993         }
1994     }
1995 
1996     OStringBuffer aLine( 512 );
1997     COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
1998     aLine.append(
1999         OString::number(rEle.m_nObject)
2000         + " 0 obj\n"
2001           "<</Type" );
2002     sal_Int32 nParentTree = -1;
2003     sal_Int32 nIDTree = -1;
2004     if( rEle.m_nOwnElement == rEle.m_nParentElement )
2005     {
2006         nParentTree = createObject();
2007         if (!nParentTree)
2008             return 0;
2009         aLine.append("/StructTreeRoot\n");
2010         aWriter.writeKeyAndReference("/ParentTree", nParentTree);
2011 
2012         // Write the reference to the PDF 2.0 namespace
2013         if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0)
2014         {
2015             auto iterator = m_aNamespacesMap.find(constNamespacePDF2);
2016             if (iterator != m_aNamespacesMap.end())
2017             {
2018                 aLine.append("/Namespaces [");
2019                 aWriter.writeReference(iterator->second);
2020                 aLine.append("]");
2021             }
2022         }
2023 
2024         if( ! m_aRoleMap.empty() )
2025         {
2026             aLine.append( "/RoleMap<<" );
2027             for (auto const& role : m_aRoleMap)
2028             {
2029                 aLine.append( "/" + role.first + "/" + role.second + "\n" );
2030             }
2031             aLine.append( ">>\n" );
2032         }
2033         if (!m_StructElemObjsWithID.empty())
2034         {
2035             nIDTree = createObject();
2036             aLine.append("/IDTree ");
2037             appendObjectReference(nIDTree, aLine);
2038             aLine.append("\n");
2039         }
2040     }
2041     else
2042     {
2043         aLine.append("/StructElem");
2044 
2045         // Write the reference to the PDF 2.0 namespace
2046         if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0)
2047         {
2048             auto iterator = m_aNamespacesMap.find(constNamespacePDF2);
2049             if (iterator != m_aNamespacesMap.end())
2050                 aWriter.writeKeyAndReference("/NS", iterator->second);
2051         }
2052         aLine.append("/S/");
2053         if( !rEle.m_aAlias.isEmpty() )
2054             aLine.append( rEle.m_aAlias );
2055         else
2056             aLine.append( getStructureTag(*rEle.m_oType) );
2057         if (m_StructElemObjsWithID.find(rEle.m_nObject) != m_StructElemObjsWithID.end())
2058         {
2059             aLine.append("\n/ID ");
2060             aWriter.writeLiteralEncrypt(GenerateID(rEle.m_nObject), rEle.m_nObject);
2061         }
2062         aLine.append(
2063             "\n"
2064             "/P "
2065             + OString::number(m_aStructure[ rEle.m_nParentElement ].m_nObject)
2066             + " 0 R\n"
2067               "/Pg "
2068             + OString::number(rEle.m_nFirstPageObject)
2069             + " 0 R\n" );
2070         if( !rEle.m_aActualText.isEmpty() )
2071         {
2072             aLine.append( "/ActualText" );
2073             aWriter.writeUnicodeEncrypt(rEle.m_aActualText, rEle.m_nObject);
2074             aLine.append( "\n" );
2075         }
2076         if( !rEle.m_aAltText.isEmpty() )
2077         {
2078             aLine.append( "/Alt" );
2079             aWriter.writeUnicodeEncrypt(rEle.m_aAltText, rEle.m_nObject);
2080             aLine.append( "\n" );
2081         }
2082     }
2083     if( (! rEle.m_aBBox.IsEmpty()) || (! rEle.m_aAttributes.empty()) )
2084     {
2085         OString aAttribs =  emitStructureAttributes( rEle );
2086         if( !aAttribs.isEmpty() )
2087         {
2088             aLine.append( "/A" + aAttribs + "\n" );
2089         }
2090     }
2091     if( !rEle.m_aLocale.Language.isEmpty() )
2092     {
2093         /* PDF allows only RFC 3066, which is only partly BCP 47 and does not
2094          * include script tags and others.
2095          * http://pdf.editme.com/pdfua-naturalLanguageSpecification
2096          * http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf#page=886
2097          * https://www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf#M13.9.19332.1Heading.97.Natural.Language.Specification
2098          * */
2099         LanguageTag aLanguageTag( rEle.m_aLocale);
2100         OUString aLanguage, aScript, aCountry;
2101         aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
2102         if (!aLanguage.isEmpty())
2103         {
2104             OUStringBuffer aLocBuf( 16 );
2105             aLocBuf.append( aLanguage );
2106             if( !aCountry.isEmpty() )
2107             {
2108                 aLocBuf.append( "-" + aCountry );
2109             }
2110             aLine.append( "/Lang" );
2111             aWriter.writeLiteralEncrypt(aLocBuf.makeStringAndClear(), rEle.m_nObject);
2112             aLine.append( "\n" );
2113         }
2114     }
2115     if (!rEle.m_AnnotIds.empty())
2116     {
2117         for (auto const id : rEle.m_AnnotIds)
2118         {
2119             auto const it(m_aLinkPropertyMap.find(id));
2120             assert(it != m_aLinkPropertyMap.end());
2121 
2122             if (*rEle.m_oType == vcl::pdf::StructElement::Form)
2123             {
2124                 assert(0 <= it->second && o3tl::make_unsigned(it->second) < m_aWidgets.size());
2125                 AppendAnnotKid(rEle, m_aWidgets[it->second]);
2126             }
2127             else
2128             {
2129                 assert(0 <= it->second && o3tl::make_unsigned(it->second) < m_aScreens.size());
2130                 AppendAnnotKid(rEle, m_aScreens[it->second]);
2131             }
2132         }
2133     }
2134     if( ! rEle.m_aKids.empty() )
2135     {
2136         unsigned int i = 0;
2137         aLine.append( "/K[" );
2138         for (auto const& rKid : rEle.m_aKids)
2139         {
2140             if (std::holds_alternative<ObjReference>(rKid))
2141             {
2142                 ObjReference const& rObj(std::get<ObjReference>(rKid));
2143                 appendObjectReference(rObj.nObject, aLine);
2144                 aLine.append( ( (i & 15) == 15 ) ? "\n" : " " );
2145             }
2146             else if (std::holds_alternative<ObjReferenceObj>(rKid))
2147             {
2148                 ObjReferenceObj const& rObj(std::get<ObjReferenceObj>(rKid));
2149                 aLine.append("<</Type/OBJR/Obj ");
2150                 appendObjectReference(rObj.nObject, aLine);
2151                 aLine.append(">>\n");
2152             }
2153             else
2154             {
2155                 assert(std::holds_alternative<MCIDReference>(rKid));
2156                 MCIDReference const& rMCID(std::get<MCIDReference>(rKid));
2157                 if (rMCID.nPageObj == rEle.m_nFirstPageObject)
2158                 {
2159                     aLine.append(OString::number(rMCID.nMCID) + " ");
2160                 }
2161                 else
2162                 {
2163                     aLine.append("<</Type/MCR/Pg ");
2164                     appendObjectReference(rMCID.nPageObj, aLine);
2165                     aLine.append(" /MCID " + OString::number(rMCID.nMCID) + ">>\n");
2166                 }
2167             }
2168             ++i;
2169         }
2170         aLine.append( "]\n" );
2171     }
2172     aLine.append( ">>\nendobj\n\n" );
2173 
2174     if (!updateObject(rEle.m_nObject)) return 0;
2175     if (!writeBuffer(aLine)) return 0;
2176 
2177     if (!emitStructParentTree(nParentTree)) return 0;
2178     if (!emitStructIDTree(nIDTree)) return 0;
2179 
2180     return rEle.m_nObject;
2181 }
2182 
emitGradients()2183 bool PDFWriterImpl::emitGradients()
2184 {
2185     for (auto const& gradient : m_aGradients)
2186     {
2187         if ( !writeGradientFunction( gradient ) ) return false;
2188     }
2189     return true;
2190 }
2191 
emitTilings()2192 bool PDFWriterImpl::emitTilings()
2193 {
2194     OStringBuffer aTilingObj( 1024 );
2195 
2196     for (auto & tiling : m_aTilings)
2197     {
2198         SAL_WARN_IF( !tiling.m_pTilingStream, "vcl.pdfwriter", "tiling without stream" );
2199         if( ! tiling.m_pTilingStream )
2200             continue;
2201 
2202         aTilingObj.setLength( 0 );
2203 
2204         if (g_bDebugDisableCompression)
2205         {
2206             emitComment( "PDFWriterImpl::emitTilings" );
2207         }
2208 
2209         sal_Int32 nX = static_cast<sal_Int32>(tiling.m_aRectangle.Left());
2210         sal_Int32 nY = static_cast<sal_Int32>(tiling.m_aRectangle.Top());
2211         sal_Int32 nW = static_cast<sal_Int32>(tiling.m_aRectangle.GetWidth());
2212         sal_Int32 nH = static_cast<sal_Int32>(tiling.m_aRectangle.GetHeight());
2213         if( tiling.m_aCellSize.Width() == 0 )
2214             tiling.m_aCellSize.setWidth( nW );
2215         if( tiling.m_aCellSize.Height() == 0 )
2216             tiling.m_aCellSize.setHeight( nH );
2217 
2218         bool bDeflate = compressStream( tiling.m_pTilingStream.get() );
2219         sal_uInt64 const nTilingStreamSize = tiling.m_pTilingStream->TellEnd();
2220         tiling.m_pTilingStream->Seek( STREAM_SEEK_TO_BEGIN );
2221 
2222         // write pattern object
2223         aTilingObj.append(
2224             OString::number(tiling.m_nObject)
2225             + " 0 obj\n"
2226               "<</Type/Pattern/PatternType 1\n"
2227               "/PaintType 1\n"
2228               "/TilingType 2\n"
2229               "/BBox[" );
2230         appendFixedInt( nX, aTilingObj );
2231         aTilingObj.append( ' ' );
2232         appendFixedInt( nY, aTilingObj );
2233         aTilingObj.append( ' ' );
2234         appendFixedInt( nX+nW, aTilingObj );
2235         aTilingObj.append( ' ' );
2236         appendFixedInt( nY+nH, aTilingObj );
2237         aTilingObj.append( "]\n"
2238                            "/XStep " );
2239         appendFixedInt( tiling.m_aCellSize.Width(), aTilingObj );
2240         aTilingObj.append( "\n"
2241                            "/YStep " );
2242         appendFixedInt( tiling.m_aCellSize.Height(), aTilingObj );
2243         aTilingObj.append( "\n" );
2244         if( tiling.m_aTransform.matrix[0] != 1.0 ||
2245             tiling.m_aTransform.matrix[1] != 0.0 ||
2246             tiling.m_aTransform.matrix[3] != 0.0 ||
2247             tiling.m_aTransform.matrix[4] != 1.0 ||
2248             tiling.m_aTransform.matrix[2] != 0.0 ||
2249             tiling.m_aTransform.matrix[5] != 0.0 )
2250         {
2251             aTilingObj.append( "/Matrix [" );
2252             // TODO: scaling, mirroring on y, etc
2253             appendDouble( tiling.m_aTransform.matrix[0], aTilingObj );
2254             aTilingObj.append( ' ' );
2255             appendDouble( tiling.m_aTransform.matrix[1], aTilingObj );
2256             aTilingObj.append( ' ' );
2257             appendDouble( tiling.m_aTransform.matrix[3], aTilingObj );
2258             aTilingObj.append( ' ' );
2259             appendDouble( tiling.m_aTransform.matrix[4], aTilingObj );
2260             aTilingObj.append( ' ' );
2261             appendDouble( tiling.m_aTransform.matrix[2], aTilingObj );
2262             aTilingObj.append( ' ' );
2263             appendDouble( tiling.m_aTransform.matrix[5], aTilingObj );
2264             aTilingObj.append( "]\n" );
2265         }
2266         aTilingObj.append( "/Resources" );
2267         tiling.m_aResources.append(aTilingObj, getFontDictObject(), m_aContext.Version);
2268         if( bDeflate )
2269             aTilingObj.append( "/Filter/FlateDecode" );
2270         aTilingObj.append( "/Length "
2271             + OString::number(static_cast<sal_Int32>(nTilingStreamSize))
2272             + ">>\nstream\n" );
2273         if ( !updateObject( tiling.m_nObject ) ) return false;
2274         if ( !writeBuffer( aTilingObj ) ) return false;
2275         checkAndEnableStreamEncryption( tiling.m_nObject );
2276         bool written = writeBufferBytes( tiling.m_pTilingStream->GetData(), nTilingStreamSize );
2277         tiling.m_pTilingStream.reset();
2278         if( !written )
2279             return false;
2280         disableStreamEncryption();
2281         aTilingObj.setLength( 0 );
2282         aTilingObj.append( "\nendstream\nendobj\n\n" );
2283         if ( !writeBuffer( aTilingObj ) ) return false;
2284     }
2285     return true;
2286 }
2287 
emitBuildinFont(const pdf::BuildinFontFace * pFD,sal_Int32 nFontObject)2288 sal_Int32 PDFWriterImpl::emitBuildinFont(const pdf::BuildinFontFace* pFD, sal_Int32 nFontObject)
2289 {
2290     if( !pFD )
2291         return 0;
2292     const pdf::BuildinFont& rBuildinFont = pFD->GetBuildinFont();
2293 
2294     OStringBuffer aLine( 1024 );
2295 
2296     if( nFontObject <= 0 )
2297         nFontObject = createObject();
2298     if (!updateObject(nFontObject))
2299         return 0;
2300     aLine.append(
2301         OString::number(nFontObject)
2302         + " 0 obj\n"
2303           "<</Type/Font/Subtype/Type1/BaseFont/" );
2304     COSWriter::appendName( rBuildinFont.m_pPSName, aLine );
2305     aLine.append( "\n" );
2306     if( rBuildinFont.m_eCharSet == RTL_TEXTENCODING_MS_1252 )
2307          aLine.append( "/Encoding/WinAnsiEncoding\n" );
2308     aLine.append( ">>\nendobj\n\n" );
2309     if (!writeBuffer(aLine))
2310         return 0;
2311     return nFontObject;
2312 }
2313 
2314 namespace
2315 {
2316 // Translate units from TT to PS (standard 1/1000)
XUnits(int nUPEM,int n)2317 int XUnits(int nUPEM, int n) { return (n * 1000) / nUPEM; }
2318 }
2319 
emitSystemFont(const vcl::font::PhysicalFontFace * pFace,EmbedFont const & rEmbed)2320 std::map< sal_Int32, sal_Int32 > PDFWriterImpl::emitSystemFont( const vcl::font::PhysicalFontFace* pFace, EmbedFont const & rEmbed )
2321 {
2322     std::map< sal_Int32, sal_Int32 > aRet;
2323 
2324     if (g_bDebugDisableCompression)
2325         emitComment("PDFWriterImpl::emitSystemFont");
2326 
2327     FontSubsetInfo aInfo;
2328     // fill in dummy values
2329     aInfo.m_nAscent = 1000;
2330     aInfo.m_nDescent = 200;
2331     aInfo.m_nCapHeight = 1000;
2332     aInfo.m_aFontBBox = tools::Rectangle( Point( -200, -200 ), Size( 1700, 1700 ) );
2333     aInfo.m_aPSName = pFace->GetFamilyName();
2334 
2335     sal_Int32 pWidths[256] = { 0 };
2336     const LogicalFontInstance* pFontInstance = rEmbed.m_pFontInstance;
2337     auto nUPEM = pFace->UnitsPerEm();
2338     for( sal_Ucs c = 32; c < 256; c++ )
2339     {
2340         sal_GlyphId nGlyph = pFontInstance->GetGlyphIndex(c);
2341         pWidths[c] = XUnits(nUPEM, pFontInstance->GetGlyphWidth(nGlyph, false, false));
2342     }
2343 
2344     // We are interested only in filling aInfo
2345     sal_GlyphId aGlyphIds[] = { 0 };
2346     sal_uInt8 pEncoding[] = { 0 };
2347     std::vector<sal_uInt8> aBuffer;
2348     pFace->CreateFontSubset(aBuffer, aGlyphIds, pEncoding, 1, aInfo);
2349 
2350     // write font descriptor
2351     sal_Int32 nFontDescriptor = emitFontDescriptor( pFace, aInfo, 0, 0 );
2352     if( nFontDescriptor )
2353     {
2354         // write font object
2355         sal_Int32 nObject = createObject();
2356         if( updateObject( nObject ) )
2357         {
2358             OStringBuffer aLine( 1024 );
2359             aLine.append(
2360                 OString::number(nObject)
2361                 + " 0 obj\n"
2362                   "<</Type/Font/Subtype/TrueType"
2363                   "/BaseFont/" );
2364             COSWriter::appendName( aInfo.m_aPSName, aLine );
2365             aLine.append( "\n" );
2366             if (!pFace->IsMicrosoftSymbolEncoded())
2367                 aLine.append( "/Encoding/WinAnsiEncoding\n" );
2368             aLine.append( "/FirstChar 32 /LastChar 255\n"
2369                           "/Widths[" );
2370             for( int i = 32; i < 256; i++ )
2371             {
2372                 aLine.append( pWidths[i] );
2373                 aLine.append( ((i&15) == 15) ? "\n" : " " );
2374             }
2375             aLine.append( "]\n"
2376                           "/FontDescriptor "
2377                 + OString::number( nFontDescriptor )
2378                 + " 0 R>>\n"
2379                   "endobj\n\n" );
2380             writeBuffer( aLine );
2381 
2382             aRet[ rEmbed.m_nNormalFontID ] = nObject;
2383         }
2384     }
2385 
2386     return aRet;
2387 }
2388 
2389 namespace
2390 {
fillSubsetArrays(const FontEmit & rSubset,sal_GlyphId * pGlyphIds,sal_Int32 * pWidths,sal_uInt8 * pEncoding,sal_Int32 * pEncToUnicodeIndex,sal_Int32 * pCodeUnitsPerGlyph,std::vector<sal_Ucs> & rCodeUnits,sal_Int32 & nToUnicodeStream)2391 uint32_t fillSubsetArrays(const FontEmit& rSubset, sal_GlyphId* pGlyphIds, sal_Int32* pWidths,
2392                           sal_uInt8* pEncoding, sal_Int32* pEncToUnicodeIndex,
2393                           sal_Int32* pCodeUnitsPerGlyph, std::vector<sal_Ucs>& rCodeUnits,
2394                           sal_Int32& nToUnicodeStream)
2395 {
2396     rCodeUnits.reserve(256);
2397 
2398     // if it gets used then it will appear in s_subset.m_aMapping, otherwise 0 is fine
2399     pWidths[0] = 0;
2400 
2401     uint32_t nGlyphs = 1;
2402     for (auto const& item : rSubset.m_aMapping)
2403     {
2404         sal_uInt8 nEnc = item.second.getGlyphId();
2405 
2406         SAL_WARN_IF(pGlyphIds[nEnc] != 0 || pEncoding[nEnc] != 0, "vcl.pdfwriter",
2407                     "duplicate glyph");
2408         SAL_WARN_IF(nEnc > rSubset.m_aMapping.size(), "vcl.pdfwriter", "invalid glyph encoding");
2409 
2410         pGlyphIds[nEnc] = item.first;
2411         pEncoding[nEnc] = nEnc;
2412         pEncToUnicodeIndex[nEnc] = static_cast<sal_Int32>(rCodeUnits.size());
2413         pCodeUnitsPerGlyph[nEnc] = item.second.countCodes();
2414         pWidths[nEnc] = item.second.getGlyphWidth();
2415         for (sal_Int32 n = 0; n < pCodeUnitsPerGlyph[nEnc]; n++)
2416             rCodeUnits.push_back(item.second.getCode(n));
2417         if (item.second.getCode(0))
2418             nToUnicodeStream = 1;
2419         if (nGlyphs < 256)
2420             nGlyphs++;
2421         else
2422             OSL_FAIL("too many glyphs for subset");
2423     }
2424 
2425     return nGlyphs;
2426 }
2427 }
2428 
emitType3Font(const vcl::font::PhysicalFontFace * pFace,const FontSubset & rType3Font,std::map<sal_Int32,sal_Int32> & rFontIDToObject)2429 bool PDFWriterImpl::emitType3Font(const vcl::font::PhysicalFontFace* pFace,
2430                                   const FontSubset& rType3Font,
2431                                   std::map<sal_Int32, sal_Int32>& rFontIDToObject)
2432 {
2433     if (g_bDebugDisableCompression)
2434         emitComment("PDFWriterImpl::emitType3Font");
2435 
2436     const auto& rColorPalettes = pFace->GetColorPalettes();
2437 
2438     FontSubsetInfo aSubsetInfo;
2439     sal_GlyphId pTempGlyphIds[] = { 0 };
2440     sal_uInt8 pTempEncoding[] = { 0 };
2441     std::vector<sal_uInt8> aBuffer;
2442     pFace->CreateFontSubset(aBuffer, pTempGlyphIds, pTempEncoding, 1, aSubsetInfo);
2443 
2444     for (auto& rSubset : rType3Font.m_aSubsets)
2445     {
2446         sal_GlyphId pGlyphIds[256] = {};
2447         sal_Int32 pWidths[256];
2448         sal_uInt8 pEncoding[256] = {};
2449         sal_Int32 pEncToUnicodeIndex[256] = {};
2450         sal_Int32 pCodeUnitsPerGlyph[256] = {};
2451         std::vector<sal_Ucs> aCodeUnits;
2452         sal_Int32 nToUnicodeStream = 0;
2453 
2454         // fill arrays and prepare encoding index map
2455         auto nGlyphs = fillSubsetArrays(rSubset, pGlyphIds, pWidths, pEncoding, pEncToUnicodeIndex,
2456                                         pCodeUnitsPerGlyph, aCodeUnits, nToUnicodeStream);
2457 
2458         // write font descriptor
2459         sal_Int32 nFontDescriptor = 0;
2460         if (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_4)
2461             nFontDescriptor = emitFontDescriptor(pFace, aSubsetInfo, rSubset.m_nFontID, 0);
2462 
2463         if (nToUnicodeStream)
2464             nToUnicodeStream = createToUnicodeCMap(pEncoding, aCodeUnits, pCodeUnitsPerGlyph,
2465                                                    pEncToUnicodeIndex, nGlyphs);
2466 
2467         // write font object
2468         sal_Int32 nFontObject = createObject();
2469         if (!updateObject(nFontObject))
2470             return false;
2471 
2472         OStringBuffer aLine(1024);
2473         aLine.append(
2474             OString::number(nFontObject)
2475             + " 0 obj\n"
2476               "<</Type/Font/Subtype/Type3/Name/");
2477         COSWriter::appendName(aSubsetInfo.m_aPSName, aLine);
2478 
2479         aLine.append(
2480             "\n/FontBBox["
2481         // note: Top and Bottom are reversed in VCL and PDF rectangles
2482             + OString::number(aSubsetInfo.m_aFontBBox.Left())
2483             + " "
2484             + OString::number(aSubsetInfo.m_aFontBBox.Top())
2485             + " "
2486             + OString::number(aSubsetInfo.m_aFontBBox.Right())
2487             + " "
2488             + OString::number(aSubsetInfo.m_aFontBBox.Bottom() + 1)
2489             + "]\n");
2490 
2491         // tdf#155610
2492         // Adobe Acrobat does not seem to like certain UPEMs, so instead of
2493         // setting the FontMatrix scale relative to the UPEM, we always set to
2494         // 0.001 (1000 UPEM) and scale everything if the font’s UPEM is
2495         // different.
2496         double fScale = 1000. / pFace->UnitsPerEm();
2497 
2498         aLine.append("/FontMatrix[0.001 0 0 0.001 0 0]\n");
2499 
2500         sal_Int32 pGlyphStreams[256] = {};
2501         aLine.append("/CharProcs<<\n");
2502         for (auto i = 1u; i < nGlyphs; i++)
2503         {
2504             auto nStream = createObject();
2505             aLine.append("/"
2506                 + pFace->GetGlyphName(pGlyphIds[i], true)
2507                 + " "
2508                 + OString::number(nStream)
2509                 + " 0 R\n");
2510             pGlyphStreams[i] = nStream;
2511         }
2512         aLine.append(">>\n"
2513 
2514             "/Encoding<</Type/Encoding/Differences[1");
2515         for (auto i = 1u; i < nGlyphs; i++)
2516             aLine.append(" /" + pFace->GetGlyphName(pGlyphIds[i], true));
2517         aLine.append("]>>\n"
2518 
2519             "/FirstChar 0\n"
2520             "/LastChar "
2521             + OString::number(nGlyphs - 1)
2522             + "\n"
2523 
2524             "/Widths[");
2525         for (auto i = 0u; i < nGlyphs; i++)
2526         {
2527             appendDouble(pWidths[i] * fScale, aLine);
2528             aLine.append(" ");
2529         }
2530         aLine.append("]\n");
2531 
2532         if (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_4)
2533         {
2534             aLine.append("/FontDescriptor " + OString::number(nFontDescriptor) + " 0 R\n");
2535         }
2536 
2537         auto nResources = createObject();
2538         aLine.append("/Resources " + OString::number(nResources) + " 0 R\n");
2539 
2540         if (nToUnicodeStream)
2541         {
2542             aLine.append("/ToUnicode " + OString::number(nToUnicodeStream) + " 0 R\n");
2543         }
2544 
2545         aLine.append(">>\n"
2546                      "endobj\n\n");
2547 
2548         if (!writeBuffer(aLine))
2549             return false;
2550 
2551         std::set<sal_Int32> aUsedFonts;
2552         std::list<BitmapEmit> aUsedBitmaps;
2553         std::map<sal_uInt8, sal_Int32> aUsedAlpha;
2554         ResourceDict aResourceDict;
2555         std::list<StreamRedirect> aOutputStreams;
2556 
2557         // Scale for glyph outlines.
2558         double fScaleX = (GetDPIX() / 72.) * fScale;
2559         double fScaleY = (GetDPIY() / 72.) * fScale;
2560 
2561         for (auto i = 1u; i < nGlyphs; i++)
2562         {
2563             auto nStream = pGlyphStreams[i];
2564             if (!updateObject(nStream))
2565                 return false;
2566             OStringBuffer aContents(1024);
2567             appendDouble(pWidths[i] * fScale, aContents);
2568             aContents.append(" 0 d0\n");
2569 
2570             const auto& rGlyph = rSubset.m_aMapping.find(pGlyphIds[i])->second;
2571             const auto& rLayers = rGlyph.getColorLayers();
2572             for (const auto& rLayer : rLayers)
2573             {
2574                 aUsedFonts.insert(rLayer.m_nFontID);
2575 
2576                 aContents.append("q ");
2577                 // 0xFFFF is a special value means foreground color.
2578                 if (rLayer.m_nColorIndex != 0xFFFF)
2579                 {
2580                     auto& rPalette = rColorPalettes[0];
2581                     auto aColor(rPalette[rLayer.m_nColorIndex]);
2582                     appendNonStrokingColor(aColor, aContents);
2583                     aContents.append(" ");
2584                     if (aColor.GetAlpha() != 0xFF
2585                         && m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4)
2586                     {
2587                         auto nAlpha = aColor.GetAlpha();
2588                         OStringBuffer aName(16);
2589                         aName.append("GS");
2590                         COSWriter::appendHex(nAlpha, aName);
2591 
2592                         aContents.append("/" + aName + " gs ");
2593 
2594                         if (aUsedAlpha.find(nAlpha) == aUsedAlpha.end())
2595                         {
2596                             auto nObject = createObject();
2597                             aUsedAlpha[nAlpha] = nObject;
2598                             pushResource(ResourceKind::ExtGState, aName.makeStringAndClear(),
2599                                          nObject, aResourceDict, aOutputStreams);
2600                         }
2601                     }
2602                 }
2603                 aContents.append(
2604                     "BT "
2605                     "/F" + OString::number(rLayer.m_nFontID) + " ");
2606                 appendDouble(pFace->UnitsPerEm() * fScale, aContents);
2607                 aContents.append(
2608                     " Tf "
2609                     "<");
2610                 COSWriter::appendHex(rLayer.m_nSubsetGlyphID, aContents);
2611                 aContents.append(
2612                     ">Tj "
2613                     "ET "
2614                     "Q\n");
2615             }
2616 
2617             tools::Rectangle aRect;
2618             const auto& rBitmapData = rGlyph.getColorBitmap(aRect);
2619             if (!rBitmapData.empty())
2620             {
2621                 SvMemoryStream aStream(const_cast<uint8_t*>(rBitmapData.data()), rBitmapData.size(),
2622                                        StreamMode::READ);
2623                 vcl::PngImageReader aReader(aStream);
2624 
2625                 Bitmap aBitmap = aReader.read();
2626                 const BitmapEmit& rBitmapEmit = createBitmapEmit(aBitmap, Graphic(),
2627                                                                  aUsedBitmaps, aResourceDict,
2628                                                                  aOutputStreams);
2629 
2630                 auto nObject = rBitmapEmit.m_aReferenceXObject.getObject();
2631                 aContents.append("q ");
2632                 appendDouble(aRect.GetWidth() * fScale, aContents);
2633                 aContents.append(" 0 0 ");
2634                 appendDouble(aRect.GetHeight() * fScale, aContents);
2635                 aContents.append(" ");
2636                 appendDouble(aRect.getX() * fScale, aContents);
2637                 aContents.append(" ");
2638                 appendDouble(aRect.getY() * fScale, aContents);
2639                 aContents.append(" cm /Im" + OString::number(nObject) + " Do Q\n");
2640             }
2641 
2642             const auto& rOutline = rGlyph.getOutline();
2643             if (rOutline.count())
2644             {
2645                 aContents.append("q ");
2646                 appendDouble(fScaleX, aContents);
2647                 aContents.append(" 0 0 ");
2648                 appendDouble(fScaleY, aContents);
2649                 aContents.append(" 0 ");
2650                 appendDouble(m_aPages.back().getHeight() * -fScaleY, aContents, 3);
2651                 aContents.append(" cm\n");
2652                 m_aPages.back().appendPolyPolygon(rOutline, aContents);
2653                 aContents.append("f\n"
2654                                  "Q\n");
2655             }
2656 
2657             aLine.setLength(0);
2658             aLine.append(OString::number(nStream)
2659                 + " 0 obj\n<</Length "
2660                 + OString::number(aContents.getLength() - 1) // Trailing newline doesn't count
2661                 + ">>\nstream\n");
2662             if (!writeBuffer(aLine))
2663                 return false;
2664             if (!writeBuffer(aContents))
2665                 return false;
2666             aLine.setLength(0);
2667             aLine.append("endstream\nendobj\n\n");
2668             if (!writeBuffer(aLine))
2669                 return false;
2670         }
2671 
2672         // write font dict
2673         sal_Int32 nFontDict = 0;
2674         if (!aUsedFonts.empty())
2675         {
2676             nFontDict = createObject();
2677             aLine.setLength(0);
2678             aLine.append(OString::number(nFontDict) + " 0 obj\n<<");
2679             for (auto nFontID : aUsedFonts)
2680             {
2681                 aLine.append("/F"
2682                     + OString::number(nFontID)
2683                     + " "
2684                     + OString::number(rFontIDToObject[nFontID])
2685                     + " 0 R");
2686             }
2687             aLine.append(">>\nendobj\n\n");
2688             if (!updateObject(nFontDict))
2689                 return false;
2690             if (!writeBuffer(aLine))
2691                 return false;
2692         }
2693 
2694         // write ExtGState objects
2695         if (!aUsedAlpha.empty())
2696         {
2697             for (const auto & [ nAlpha, nObject ] : aUsedAlpha)
2698             {
2699                 aLine.setLength(0);
2700                 aLine.append(OString::number(nObject) + " 0 obj\n<<");
2701                 if (m_bIsPDF_A1)
2702                 {
2703                     aLine.append("/CA 1.0/ca 1.0");
2704                     m_aErrors.insert(PDFWriter::Warning_Transparency_Omitted_PDFA);
2705                 }
2706                 else
2707                 {
2708                     aLine.append("/CA ");
2709                     appendDouble(nAlpha / 255., aLine);
2710                     aLine.append("/ca ");
2711                     appendDouble(nAlpha / 255., aLine);
2712                 }
2713                 aLine.append(">>\nendobj\n\n");
2714                 if (!updateObject(nObject))
2715                     return false;
2716                 if (!writeBuffer(aLine))
2717                     return false;
2718             }
2719         }
2720 
2721         // write bitmap objects
2722         for (auto& aBitmap : aUsedBitmaps)
2723             writeBitmapObject(aBitmap);
2724 
2725         // write resources dict
2726         aLine.setLength(0);
2727         aLine.append(OString::number(nResources) + " 0 obj\n");
2728         aResourceDict.append(aLine, nFontDict, m_aContext.Version);
2729         aLine.append("endobj\n\n");
2730         if (!updateObject(nResources))
2731             return false;
2732         if (!writeBuffer(aLine))
2733             return false;
2734 
2735         rFontIDToObject[rSubset.m_nFontID] = nFontObject;
2736     }
2737 
2738     return true;
2739 }
2740 
2741 typedef int ThreeInts[3];
getPfbSegmentLengths(const unsigned char * pFontBytes,int nByteLen,ThreeInts & rSegmentLengths)2742 static bool getPfbSegmentLengths( const unsigned char* pFontBytes, int nByteLen,
2743     ThreeInts& rSegmentLengths )
2744 {
2745     if( !pFontBytes || (nByteLen < 0) )
2746         return false;
2747     const unsigned char* pPtr = pFontBytes;
2748     const unsigned char* pEnd = pFontBytes + nByteLen;
2749 
2750     for(int & rSegmentLength : rSegmentLengths) {
2751         // read segment1 header
2752         if( pPtr+6 >= pEnd )
2753             return false;
2754         if( (pPtr[0] != 0x80) || (pPtr[1] >= 0x03) )
2755             return false;
2756         const int nLen = (pPtr[5]<<24) + (pPtr[4]<<16) + (pPtr[3]<<8) + pPtr[2];
2757         if( nLen <= 0)
2758             return false;
2759         rSegmentLength = nLen;
2760         pPtr += nLen + 6;
2761     }
2762 
2763     // read segment-end header
2764     if( pPtr+2 >= pEnd )
2765         return false;
2766     if( (pPtr[0] != 0x80) || (pPtr[1] != 0x03) )
2767         return false;
2768 
2769     return true;
2770 }
2771 
appendSubsetName(int nSubsetID,std::u16string_view rPSName,OStringBuffer & rBuffer)2772 static void appendSubsetName( int nSubsetID, std::u16string_view rPSName, OStringBuffer& rBuffer )
2773 {
2774     if( nSubsetID )
2775     {
2776         for( int i = 0; i < 6; i++ )
2777         {
2778             int nOffset = nSubsetID % 26;
2779             nSubsetID /= 26;
2780             rBuffer.append( static_cast<char>('A'+nOffset) );
2781         }
2782         rBuffer.append( '+' );
2783     }
2784     COSWriter::appendName( rPSName, rBuffer );
2785 }
2786 
createToUnicodeCMap(sal_uInt8 const * pEncoding,const std::vector<sal_Ucs> & rCodeUnits,const sal_Int32 * pCodeUnitsPerGlyph,const sal_Int32 * pEncToUnicodeIndex,uint32_t nGlyphs)2787 sal_Int32 PDFWriterImpl::createToUnicodeCMap( sal_uInt8 const * pEncoding,
2788                                               const std::vector<sal_Ucs>& rCodeUnits,
2789                                               const sal_Int32* pCodeUnitsPerGlyph,
2790                                               const sal_Int32* pEncToUnicodeIndex,
2791                                               uint32_t nGlyphs )
2792 {
2793     int nMapped = 0;
2794     for (auto n = 0u; n < nGlyphs; ++n)
2795         if (pCodeUnitsPerGlyph[n] && rCodeUnits[pEncToUnicodeIndex[n]])
2796             nMapped++;
2797 
2798     if( nMapped == 0 )
2799         return 0;
2800 
2801     sal_Int32 nStream = createObject();
2802     if (!updateObject(nStream))
2803         return 0;
2804     OStringBuffer aContents( 1024 );
2805     aContents.append(
2806                      "/CIDInit/ProcSet findresource begin\n"
2807                      "12 dict begin\n"
2808                      "begincmap\n"
2809                      "/CIDSystemInfo<<\n"
2810                      "/Registry (Adobe)\n"
2811                      "/Ordering (UCS)\n"
2812                      "/Supplement 0\n"
2813                      ">> def\n"
2814                      "/CMapName/Adobe-Identity-UCS def\n"
2815                      "/CMapType 2 def\n"
2816                      "1 begincodespacerange\n"
2817                      "<00> <FF>\n"
2818                      "endcodespacerange\n"
2819                      );
2820     int nCount = 0;
2821     for (auto n = 0u; n < nGlyphs; ++n)
2822     {
2823         if (pCodeUnitsPerGlyph[n] && rCodeUnits[pEncToUnicodeIndex[n]])
2824         {
2825             if( (nCount % 100) == 0 )
2826             {
2827                 if( nCount )
2828                     aContents.append( "endbfchar\n" );
2829                 aContents.append( OString::number(static_cast<sal_Int32>(std::min(nMapped-nCount, 100)) )
2830                     + " beginbfchar\n" );
2831             }
2832             aContents.append( '<' );
2833             COSWriter::appendHex(static_cast<sal_Int8>(pEncoding[n]), aContents);
2834             aContents.append( "> <" );
2835             // TODO: handle code points>U+FFFF
2836             sal_Int32 nIndex = pEncToUnicodeIndex[n];
2837             for( sal_Int32 j = 0; j < pCodeUnitsPerGlyph[n]; j++ )
2838             {
2839                 COSWriter::appendHex(static_cast<sal_Int8>(rCodeUnits[nIndex + j] / 256), aContents);
2840                 COSWriter::appendHex(static_cast<sal_Int8>(rCodeUnits[nIndex + j] & 255), aContents);
2841             }
2842             aContents.append( ">\n" );
2843             nCount++;
2844         }
2845     }
2846     aContents.append( "endbfchar\n"
2847                       "endcmap\n"
2848                       "CMapName currentdict /CMap defineresource pop\n"
2849                       "end\n"
2850                       "end\n" );
2851     SvMemoryStream aStream;
2852     if (!g_bDebugDisableCompression)
2853     {
2854         ZCodec aCodec( 0x4000, 0x4000 );
2855         aCodec.BeginCompression();
2856         aCodec.Write( aStream, reinterpret_cast<const sal_uInt8*>(aContents.getStr()), aContents.getLength() );
2857         aCodec.EndCompression();
2858     }
2859 
2860     if (g_bDebugDisableCompression)
2861     {
2862         emitComment( "PDFWriterImpl::createToUnicodeCMap" );
2863     }
2864     OStringBuffer aLine( 40 );
2865 
2866     aLine.append( OString::number(nStream ) + " 0 obj\n<</Length " );
2867     sal_uInt64 nLen = 0;
2868     if (!g_bDebugDisableCompression)
2869     {
2870         nLen = aStream.Tell();
2871         aStream.Seek( 0 );
2872         aLine.append( OString::number(nLen) + "/Filter/FlateDecode" );
2873     }
2874     else
2875         aLine.append( aContents.getLength() );
2876     aLine.append( ">>\nstream\n" );
2877     if (!writeBuffer(aLine)) return 0;
2878     checkAndEnableStreamEncryption( nStream );
2879     if (!g_bDebugDisableCompression)
2880     {
2881         if(!writeBufferBytes(aStream.GetData(), nLen)) return 0;
2882     }
2883     else
2884     {
2885         if (!writeBuffer(aContents)) return 0;
2886     }
2887     disableStreamEncryption();
2888     aLine.setLength( 0 );
2889     aLine.append( "\nendstream\n"
2890                   "endobj\n\n" );
2891     if (!writeBuffer(aLine)) return 0;
2892     return nStream;
2893 }
2894 
emitFontDescriptor(const vcl::font::PhysicalFontFace * pFace,FontSubsetInfo const & rInfo,sal_Int32 nSubsetID,sal_Int32 nFontStream)2895 sal_Int32 PDFWriterImpl::emitFontDescriptor( const vcl::font::PhysicalFontFace* pFace, FontSubsetInfo const & rInfo, sal_Int32 nSubsetID, sal_Int32 nFontStream )
2896 {
2897     OStringBuffer aLine( 1024 );
2898     // get font flags, see PDF reference 1.4 p. 358
2899     // possibly characters outside Adobe standard encoding
2900     // so set Symbolic flag
2901     sal_Int32 nFontFlags = (1<<2);
2902     if( pFace->GetItalic() == ITALIC_NORMAL || pFace->GetItalic() == ITALIC_OBLIQUE )
2903         nFontFlags |= (1 << 6);
2904     if( pFace->GetPitch() == PITCH_FIXED )
2905         nFontFlags |= 1;
2906     if( pFace->GetFamilyType() == FAMILY_SCRIPT )
2907         nFontFlags |= (1 << 3);
2908     else if( pFace->GetFamilyType() == FAMILY_ROMAN )
2909         nFontFlags |= (1 << 1);
2910 
2911     sal_Int32 nFontDescriptor = createObject();
2912     if (!updateObject(nFontDescriptor)) return 0;
2913     aLine.setLength( 0 );
2914     aLine.append(
2915         OString::number(nFontDescriptor)
2916         + " 0 obj\n"
2917           "<</Type/FontDescriptor/FontName/" );
2918     appendSubsetName( nSubsetID, rInfo.m_aPSName, aLine );
2919     aLine.append( "\n"
2920                   "/Flags "
2921         + OString::number( nFontFlags )
2922         + "\n"
2923           "/FontBBox["
2924     // note: Top and Bottom are reversed in VCL and PDF rectangles
2925         + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Left()) )
2926         + " "
2927         + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Top()) )
2928         + " "
2929         + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Right()) )
2930         + " "
2931         + OString::number( static_cast<sal_Int32>(rInfo.m_aFontBBox.Bottom()+1) )
2932         +  "]/ItalicAngle " );
2933     if( pFace->GetItalic() == ITALIC_OBLIQUE || pFace->GetItalic() == ITALIC_NORMAL )
2934         aLine.append( "-30" );
2935     else
2936         aLine.append( "0" );
2937     aLine.append( "\n"
2938                   "/Ascent "
2939         + OString::number( static_cast<sal_Int32>(rInfo.m_nAscent) )
2940         + "\n"
2941           "/Descent "
2942         + OString::number( static_cast<sal_Int32>(-rInfo.m_nDescent) )
2943         + "\n"
2944           "/CapHeight "
2945         + OString::number( static_cast<sal_Int32>(rInfo.m_nCapHeight) )
2946     // According to PDF reference 1.4 StemV is required
2947     // seems a tad strange to me, but well ...
2948         + "\n"
2949           "/StemV 80\n" );
2950     if( nFontStream )
2951     {
2952         aLine.append( "/FontFile" );
2953         switch( rInfo.m_nFontType )
2954         {
2955             case FontType::SFNT_TTF:
2956                 aLine.append( '2' );
2957                 break;
2958             case FontType::TYPE1_PFA:
2959             case FontType::TYPE1_PFB:
2960             case FontType::ANY_TYPE1:
2961                 break;
2962             default:
2963                 OSL_FAIL( "unknown fonttype in PDF font descriptor" );
2964                 return 0;
2965         }
2966         aLine.append( " " + OString::number(nFontStream) + " 0 R\n" );
2967     }
2968     aLine.append( ">>\n"
2969                   "endobj\n\n" );
2970     if (!writeBuffer(aLine)) return 0;
2971 
2972     return nFontDescriptor;
2973 }
2974 
appendBuildinFontsToDict(OStringBuffer & rDict) const2975 void PDFWriterImpl::appendBuildinFontsToDict( OStringBuffer& rDict ) const
2976 {
2977     for (auto const& item : m_aBuildinFontToObjectMap)
2978     {
2979         rDict.append( pdf::BuildinFontFace::Get(item.first).getNameObject() );
2980         rDict.append( ' ' );
2981         rDict.append( item.second );
2982         rDict.append( " 0 R" );
2983     }
2984 }
2985 
emitFonts()2986 bool PDFWriterImpl::emitFonts()
2987 {
2988     OStringBuffer aLine( 1024 );
2989 
2990     std::map< sal_Int32, sal_Int32 > aFontIDToObject;
2991 
2992     for (const auto & subset : m_aSubsets)
2993     {
2994         for (auto & s_subset :subset.second.m_aSubsets)
2995         {
2996             sal_GlyphId pGlyphIds[ 256 ] = {};
2997             sal_Int32 pWidths[ 256 ];
2998             sal_uInt8 pEncoding[ 256 ] = {};
2999             sal_Int32 pEncToUnicodeIndex[ 256 ] = {};
3000             sal_Int32 pCodeUnitsPerGlyph[ 256 ] = {};
3001             std::vector<sal_Ucs> aCodeUnits;
3002             sal_Int32 nToUnicodeStream = 0;
3003 
3004             // fill arrays and prepare encoding index map
3005             auto nGlyphs = fillSubsetArrays(s_subset, pGlyphIds, pWidths, pEncoding, pEncToUnicodeIndex,
3006                                             pCodeUnitsPerGlyph, aCodeUnits, nToUnicodeStream);
3007 
3008             std::vector<sal_uInt8> aBuffer;
3009             FontSubsetInfo aSubsetInfo;
3010             const auto* pFace = subset.first;
3011             if (pFace->CreateFontSubset(aBuffer, pGlyphIds, pEncoding, nGlyphs, aSubsetInfo))
3012             {
3013                 // create font stream
3014                 if (g_bDebugDisableCompression)
3015                 {
3016                     emitComment( "PDFWriterImpl::emitFonts" );
3017                 }
3018                 sal_Int32 nFontStream = createObject();
3019                 sal_Int32 nStreamLengthObject = createObject();
3020                 if ( !updateObject( nFontStream ) ) return false;
3021                 aLine.setLength( 0 );
3022                 aLine.append( OString::number(nFontStream)
3023                     + " 0 obj\n"
3024                       "<</Length "
3025                     + OString::number( nStreamLengthObject ) );
3026                 if (!g_bDebugDisableCompression)
3027                     aLine.append( " 0 R"
3028                                  "/Filter/FlateDecode"
3029                                  "/Length1 " );
3030                 else
3031                     aLine.append( " 0 R"
3032                                  "/Length1 " );
3033 
3034                 sal_uInt64 nStartPos = 0;
3035                 if( aSubsetInfo.m_nFontType == FontType::SFNT_TTF )
3036                 {
3037                     aLine.append( OString::number(static_cast<sal_Int32>(aBuffer.size()))
3038                                + ">>\n"
3039                                  "stream\n" );
3040                     if ( !writeBuffer( aLine ) ) return false;
3041                     if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
3042 
3043                     // copy font file
3044                     beginCompression();
3045                     checkAndEnableStreamEncryption( nFontStream );
3046                     if (!writeBufferBytes(aBuffer.data(), aBuffer.size()))
3047                         return false;
3048                 }
3049                 else if( aSubsetInfo.m_nFontType & FontType::CFF_FONT)
3050                 {
3051                     // TODO: implement
3052                     OSL_FAIL( "PDFWriterImpl does not support CFF-font subsets yet!" );
3053                 }
3054                 else if( aSubsetInfo.m_nFontType & FontType::TYPE1_PFB) // TODO: also support PFA?
3055                 {
3056                     // get the PFB-segment lengths
3057                     ThreeInts aSegmentLengths = {0,0,0};
3058                     getPfbSegmentLengths(aBuffer.data(), static_cast<int>(aBuffer.size()), aSegmentLengths);
3059                     // the lengths below are mandatory for PDF-exported Type1 fonts
3060                     // because the PFB segment headers get stripped! WhyOhWhy.
3061                     aLine.append( OString::number(static_cast<sal_Int32>(aSegmentLengths[0]) )
3062                         + "/Length2 "
3063                         + OString::number( static_cast<sal_Int32>(aSegmentLengths[1]) )
3064                         + "/Length3 "
3065                         + OString::number( static_cast<sal_Int32>(aSegmentLengths[2]) )
3066                         + ">>\n"
3067                           "stream\n" );
3068                     if ( !writeBuffer( aLine ) ) return false;
3069                     if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
3070 
3071                     // emit PFB-sections without section headers
3072                     beginCompression();
3073                     checkAndEnableStreamEncryption( nFontStream );
3074                     if ( !writeBufferBytes( &aBuffer[6], aSegmentLengths[0] ) ) return false;
3075                     if ( !writeBufferBytes( &aBuffer[12] + aSegmentLengths[0], aSegmentLengths[1] ) ) return false;
3076                     if ( !writeBufferBytes( &aBuffer[18] + aSegmentLengths[0] + aSegmentLengths[1], aSegmentLengths[2] ) ) return false;
3077                 }
3078                 else
3079                 {
3080                     SAL_INFO("vcl.pdfwriter", "PDF: CreateFontSubset result in not yet supported format=" << static_cast<int>(aSubsetInfo.m_nFontType));
3081                     aLine.append( "0 >>\nstream\n" );
3082                 }
3083 
3084                 endCompression();
3085                 disableStreamEncryption();
3086 
3087                 sal_uInt64 nEndPos = 0;
3088                 if ( osl::File::E_None != m_aFile.getPos(nEndPos) ) return false;
3089                 // end the stream
3090                 aLine.setLength( 0 );
3091                 aLine.append( "\nendstream\nendobj\n\n" );
3092                 if ( !writeBuffer( aLine ) ) return false;
3093 
3094                 // emit stream length object
3095                 if ( !updateObject( nStreamLengthObject ) ) return false;
3096                 aLine.setLength( 0 );
3097                 aLine.append( OString::number(nStreamLengthObject)
3098                     + " 0 obj\n"
3099                     + OString::number( static_cast<sal_Int64>(nEndPos-nStartPos) )
3100                     + "\nendobj\n\n" );
3101                 if ( !writeBuffer( aLine ) ) return false;
3102 
3103                 // write font descriptor
3104                 sal_Int32 nFontDescriptor = emitFontDescriptor( subset.first, aSubsetInfo, s_subset.m_nFontID, nFontStream );
3105 
3106                 if( nToUnicodeStream )
3107                     nToUnicodeStream = createToUnicodeCMap( pEncoding, aCodeUnits, pCodeUnitsPerGlyph, pEncToUnicodeIndex, nGlyphs );
3108 
3109                 sal_Int32 nFontObject = createObject();
3110                 if ( !updateObject( nFontObject ) ) return false;
3111                 aLine.setLength( 0 );
3112                 aLine.append( OString::number(nFontObject) + " 0 obj\n" );
3113                 aLine.append( (aSubsetInfo.m_nFontType & FontType::ANY_TYPE1) ?
3114                              "<</Type/Font/Subtype/Type1/BaseFont/" :
3115                              "<</Type/Font/Subtype/TrueType/BaseFont/" );
3116                 appendSubsetName( s_subset.m_nFontID, aSubsetInfo.m_aPSName, aLine );
3117                 aLine.append( "\n"
3118                              "/FirstChar 0\n"
3119                              "/LastChar "
3120                     + OString::number( static_cast<sal_Int32>(nGlyphs-1) )
3121                     + "\n"
3122                       "/Widths[" );
3123                 for (auto i = 0u; i < nGlyphs; i++)
3124                 {
3125                     aLine.append( pWidths[ i ] );
3126                     aLine.append( ((i & 15) == 15) ? "\n" : " " );
3127                 }
3128                 aLine.append( "]\n"
3129                              "/FontDescriptor "
3130                     + OString::number( nFontDescriptor )
3131                     + " 0 R\n" );
3132                 if( nToUnicodeStream )
3133                 {
3134                     aLine.append( "/ToUnicode "
3135                         + OString::number( nToUnicodeStream )
3136                         + " 0 R\n" );
3137                 }
3138                 aLine.append( ">>\n"
3139                              "endobj\n\n" );
3140                 if ( !writeBuffer( aLine ) ) return false;
3141 
3142                 aFontIDToObject[ s_subset.m_nFontID ] = nFontObject;
3143             }
3144             else
3145             {
3146                 OStringBuffer aErrorComment( 256 );
3147                 aErrorComment.append( "CreateFontSubset failed for font \""
3148                     + OUStringToOString( pFace->GetFamilyName(), RTL_TEXTENCODING_UTF8 )
3149                     + "\"" );
3150                 if( pFace->GetItalic() == ITALIC_NORMAL )
3151                     aErrorComment.append( " italic" );
3152                 else if( pFace->GetItalic() == ITALIC_OBLIQUE )
3153                     aErrorComment.append( " oblique" );
3154                 aErrorComment.append( " weight=" + OString::number( sal_Int32(pFace->GetWeight()) ) );
3155                 emitComment( aErrorComment.getStr() );
3156             }
3157         }
3158     }
3159 
3160     // emit system fonts
3161     for (auto const& systemFont : m_aSystemFonts)
3162     {
3163         std::map< sal_Int32, sal_Int32 > aObjects = emitSystemFont( systemFont.first, systemFont.second );
3164         for (auto const& item : aObjects)
3165         {
3166             if ( !item.second ) return false;
3167             aFontIDToObject[ item.first ] = item.second;
3168         }
3169     }
3170 
3171     // emit Type3 fonts
3172     for (auto const& it : m_aType3Fonts)
3173     {
3174         if (!emitType3Font(it.first, it.second, aFontIDToObject))
3175             return false;
3176     }
3177 
3178     OStringBuffer aFontDict( 1024 );
3179     aFontDict.append( OString::number(getFontDictObject())
3180         + " 0 obj\n"
3181           "<<" );
3182     int ni = 0;
3183     for (auto const& itemMap : aFontIDToObject)
3184     {
3185         aFontDict.append( "/F"
3186             + OString::number( itemMap.first )
3187             + " "
3188             + OString::number( itemMap.second )
3189             + " 0 R" );
3190         if( ((++ni) & 7) == 0 )
3191             aFontDict.append( '\n' );
3192     }
3193     // emit builtin font for widget appearances / variable text
3194     for (auto & item : m_aBuildinFontToObjectMap)
3195     {
3196         rtl::Reference<pdf::BuildinFontFace> aData(new pdf::BuildinFontFace(item.first));
3197         item.second = emitBuildinFont( aData.get(), item.second );
3198     }
3199 
3200     appendBuildinFontsToDict(aFontDict);
3201     aFontDict.append( "\n>>\nendobj\n\n" );
3202 
3203     if ( !updateObject( getFontDictObject() ) ) return false;
3204     if ( !writeBuffer( aFontDict ) ) return false;
3205     return true;
3206 }
3207 
emitResources()3208 sal_Int32 PDFWriterImpl::emitResources()
3209 {
3210     // emit shadings
3211     if (!m_aGradients.empty())
3212     {
3213         if (!emitGradients()) return 0;
3214     }
3215     // emit tilings
3216     if (!m_aTilings.empty())
3217     {
3218         if(!emitTilings()) return 0;
3219     }
3220 
3221     // emit font dict
3222     if (!emitFonts()) return 0;
3223 
3224     // emit Resource dict
3225     OStringBuffer aLine( 512 );
3226     sal_Int32 nResourceDict = getResourceDictObj();
3227     if (!updateObject(nResourceDict))
3228         return 0;
3229     aLine.setLength( 0 );
3230     aLine.append( OString::number(nResourceDict)
3231         + " 0 obj\n" );
3232     m_aGlobalResourceDict.append(aLine, getFontDictObject(), m_aContext.Version);
3233     aLine.append( "endobj\n\n" );
3234     if (!writeBuffer(aLine)) return 0;
3235     return nResourceDict;
3236 }
3237 
updateOutlineItemCount(std::vector<sal_Int32> & rCounts,sal_Int32 nItemLevel,sal_Int32 nCurrentItemId)3238 sal_Int32 PDFWriterImpl::updateOutlineItemCount( std::vector< sal_Int32 >& rCounts,
3239                                                  sal_Int32 nItemLevel,
3240                                                  sal_Int32 nCurrentItemId )
3241 {
3242     /* The /Count number of an item is
3243        positive: the number of visible subitems
3244        negative: the negative number of subitems that will become visible if
3245                  the item gets opened
3246        see PDF ref 1.4 p 478
3247     */
3248 
3249     sal_Int32 nCount = 0;
3250 
3251     if( m_aContext.OpenBookmarkLevels < 0           || // all levels are visible
3252         m_aContext.OpenBookmarkLevels >= nItemLevel    // this level is visible
3253       )
3254     {
3255         PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
3256         sal_Int32 nChildren = rItem.m_aChildren.size();
3257         for( sal_Int32 i = 0; i < nChildren; i++ )
3258             nCount += updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
3259         rCounts[nCurrentItemId] = nCount;
3260         // return 1 (this item) + visible sub items
3261         if( nCount < 0 )
3262             nCount = 0;
3263         nCount++;
3264     }
3265     else
3266     {
3267         // this bookmark level is invisible
3268         PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
3269         sal_Int32 nChildren = rItem.m_aChildren.size();
3270         rCounts[ nCurrentItemId ] = -sal_Int32(rItem.m_aChildren.size());
3271         for( sal_Int32 i = 0; i < nChildren; i++ )
3272             updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
3273         nCount = -1;
3274     }
3275 
3276     return nCount;
3277 }
3278 
emitOutline()3279 sal_Int32 PDFWriterImpl::emitOutline()
3280 {
3281     int i, nItems = m_aOutline.size();
3282 
3283     // do we have an outline at all ?
3284     if( nItems < 2 )
3285         return 0;
3286 
3287     // reserve object numbers for all outline items
3288     for( i = 0; i < nItems; ++i )
3289         m_aOutline[i].m_nObject = createObject();
3290 
3291     // update all parent, next and prev object ids
3292     for( i = 0; i < nItems; ++i )
3293     {
3294         PDFOutlineEntry& rItem = m_aOutline[i];
3295         int nChildren = rItem.m_aChildren.size();
3296 
3297         if( nChildren )
3298         {
3299             for( int n = 0; n < nChildren; ++n )
3300             {
3301                 PDFOutlineEntry& rChild = m_aOutline[ rItem.m_aChildren[n] ];
3302 
3303                 rChild.m_nParentObject = rItem.m_nObject;
3304                 rChild.m_nPrevObject = (n > 0) ? m_aOutline[ rItem.m_aChildren[n-1] ].m_nObject : 0;
3305                 rChild.m_nNextObject = (n < nChildren-1) ? m_aOutline[ rItem.m_aChildren[n+1] ].m_nObject : 0;
3306             }
3307 
3308         }
3309     }
3310 
3311     // calculate Count entries for all items
3312     std::vector< sal_Int32 > aCounts( nItems );
3313     updateOutlineItemCount( aCounts, 0, 0 );
3314 
3315     // emit hierarchy
3316     for( i = 0; i < nItems; ++i )
3317     {
3318         PDFOutlineEntry& rItem = m_aOutline[i];
3319         OStringBuffer aLine( 1024 );
3320         COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
3321 
3322         if (!updateObject(rItem.m_nObject))
3323             return 0;
3324         aLine.append( OString::number(rItem.m_nObject)
3325             + " 0 obj\n"
3326               "<<" );
3327         // number of visible children (all levels)
3328         if( i > 0 || aCounts[0] > 0 )
3329         {
3330             aLine.append( "/Count " + OString::number( aCounts[i] ) );
3331         }
3332         if( ! rItem.m_aChildren.empty() )
3333         {
3334             // children list: First, Last
3335             aLine.append( "/First "
3336                 + OString::number( m_aOutline[rItem.m_aChildren.front()].m_nObject )
3337                 + " 0 R/Last "
3338                 + OString::number( m_aOutline[rItem.m_aChildren.back()].m_nObject )
3339                 + " 0 R\n" );
3340         }
3341         if( i > 0 )
3342         {
3343             // Title, Dest, Parent, Prev, Next
3344             aLine.append( "/Title" );
3345             aWriter.writeUnicodeEncrypt(rItem.m_aTitle, rItem.m_nObject);
3346             aLine.append( "\n" );
3347             // Dest is not required
3348             if( rItem.m_nDestID >= 0 && o3tl::make_unsigned(rItem.m_nDestID) < m_aDests.size() )
3349             {
3350                 aLine.append( "/Dest" );
3351                 appendDest( rItem.m_nDestID, aLine );
3352             }
3353             aLine.append( "/Parent "
3354                 + OString::number( rItem.m_nParentObject )
3355                 + " 0 R" );
3356             if( rItem.m_nPrevObject )
3357             {
3358                 aLine.append( "/Prev "
3359                     + OString::number( rItem.m_nPrevObject )
3360                     + " 0 R" );
3361             }
3362             if( rItem.m_nNextObject )
3363             {
3364                 aLine.append( "/Next "
3365                     + OString::number( rItem.m_nNextObject )
3366                     + " 0 R" );
3367             }
3368         }
3369         aLine.append( ">>\nendobj\n\n" );
3370         if (!writeBuffer(aLine))
3371             return 0;
3372     }
3373 
3374     return m_aOutline[0].m_nObject;
3375 }
3376 
appendDest(sal_Int32 nDestID,OStringBuffer & rBuffer)3377 bool PDFWriterImpl::appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer )
3378 {
3379     if( nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size() )
3380     {
3381         SAL_INFO("vcl.pdfwriter", "ERROR: invalid dest " << static_cast<int>(nDestID) << " requested");
3382         return false;
3383     }
3384 
3385     const PDFDest& rDest        = m_aDests[ nDestID ];
3386     const PDFPage& rDestPage    = m_aPages[ rDest.m_nPage ];
3387 
3388     rBuffer.append( '[' );
3389     rBuffer.append( rDestPage.m_nPageObject );
3390     rBuffer.append( " 0 R" );
3391 
3392     switch( rDest.m_eType )
3393     {
3394         case PDFWriter::DestAreaType::XYZ:
3395         default:
3396             rBuffer.append( "/XYZ " );
3397             appendFixedInt( rDest.m_aRect.Left(), rBuffer );
3398             rBuffer.append( ' ' );
3399             appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
3400             rBuffer.append( " 0" );
3401             break;
3402         case PDFWriter::DestAreaType::FitRectangle:
3403             rBuffer.append( "/FitR " );
3404             appendFixedInt( rDest.m_aRect.Left(), rBuffer );
3405             rBuffer.append( ' ' );
3406             appendFixedInt( rDest.m_aRect.Top(), rBuffer );
3407             rBuffer.append( ' ' );
3408             appendFixedInt( rDest.m_aRect.Right(), rBuffer );
3409             rBuffer.append( ' ' );
3410             appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
3411             break;
3412     }
3413     rBuffer.append( ']' );
3414 
3415     return true;
3416 }
3417 
addDocumentAttachedFile(OUString const & rFileName,OUString const & rMimeType,OUString const & rDescription,std::unique_ptr<PDFOutputStream> rStream)3418 void PDFWriterImpl::addDocumentAttachedFile(OUString const& rFileName, OUString const& rMimeType, OUString const& rDescription, std::unique_ptr<PDFOutputStream> rStream)
3419 {
3420     if (m_nPDFA_Version == 1 || m_nPDFA_Version == 2)
3421         return;
3422 
3423     sal_Int32 nObjectID = addEmbeddedFile(std::move(rStream), rMimeType);
3424     auto& rAttachedFile = m_aDocumentAttachedFiles.emplace_back();
3425     rAttachedFile.maFilename = rFileName;
3426     rAttachedFile.maMimeType = rMimeType;
3427     rAttachedFile.maDescription = rDescription;
3428     rAttachedFile.mnEmbeddedFileObjectId = nObjectID;
3429     rAttachedFile.mnObjectId = createObject();
3430 }
3431 
addEmbeddedFile(std::unique_ptr<PDFOutputStream> rStream,OUString const & rMimeType)3432 sal_Int32 PDFWriterImpl::addEmbeddedFile(std::unique_ptr<PDFOutputStream> rStream, OUString const& rMimeType)
3433 {
3434     sal_Int32 aObjectID = createObject();
3435     auto& rEmbedded = m_aEmbeddedFiles.emplace_back();
3436     rEmbedded.m_nObject = aObjectID;
3437     rEmbedded.m_aSubType = rMimeType;
3438     rEmbedded.m_pStream = std::move(rStream);
3439     return aObjectID;
3440 }
3441 
addEmbeddedFile(BinaryDataContainer const & rDataContainer)3442 sal_Int32 PDFWriterImpl::addEmbeddedFile(BinaryDataContainer const & rDataContainer)
3443 {
3444     sal_Int32 aObjectID = createObject();
3445     m_aEmbeddedFiles.emplace_back();
3446     m_aEmbeddedFiles.back().m_nObject = aObjectID;
3447     m_aEmbeddedFiles.back().m_aDataContainer = rDataContainer;
3448     return aObjectID;
3449 }
3450 
emitScreenAnnotations()3451 bool PDFWriterImpl::emitScreenAnnotations()
3452 {
3453     int nAnnots = m_aScreens.size();
3454     for (int i = 0; i < nAnnots; i++)
3455     {
3456         const PDFScreen& rScreen = m_aScreens[i];
3457 
3458         OStringBuffer aLine;
3459         COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
3460         bool bEmbed = false;
3461         if (!rScreen.m_aTempFileURL.isEmpty())
3462         {
3463             bEmbed = true;
3464             if (!updateObject(rScreen.m_nTempFileObject))
3465                 continue;
3466 
3467             SvFileStream aFileStream(rScreen.m_aTempFileURL, StreamMode::READ);
3468             SvMemoryStream aMemoryStream;
3469             aMemoryStream.WriteStream(aFileStream);
3470 
3471             aLine.append(rScreen.m_nTempFileObject);
3472             aLine.append(" 0 obj\n");
3473             aLine.append("<< /Type /EmbeddedFile /Length ");
3474             aLine.append(static_cast<sal_Int64>(aMemoryStream.GetSize()));
3475             aLine.append(" >>\nstream\n");
3476             if (!writeBuffer(aLine))
3477                 return false;
3478             aLine.setLength(0);
3479 
3480             if (!writeBufferBytes(aMemoryStream.GetData(), aMemoryStream.GetSize()))
3481                 return false;
3482 
3483             aLine.append("\nendstream\nendobj\n\n");
3484             if (!writeBuffer(aLine))
3485                 return false;
3486             aLine.setLength(0);
3487         }
3488 
3489         if (!updateObject(rScreen.m_nObject))
3490             continue;
3491 
3492         // Annot dictionary.
3493         aLine.append(OString::number(rScreen.m_nObject)
3494             + " 0 obj\n"
3495               "<</Type/Annot"
3496               "/Subtype/Screen/Rect[");
3497         appendFixedInt(rScreen.m_aRect.Left(), aLine);
3498         aLine.append(' ');
3499         appendFixedInt(rScreen.m_aRect.Top(), aLine);
3500         aLine.append(' ');
3501         appendFixedInt(rScreen.m_aRect.Right(), aLine);
3502         aLine.append(' ');
3503         appendFixedInt(rScreen.m_aRect.Bottom(), aLine);
3504         aLine.append("]");
3505 
3506         // Action dictionary.
3507         aLine.append("/A<</Type/Action /S/Rendition /AN "
3508             + OString::number(rScreen.m_nObject)
3509             + " 0 R ");
3510 
3511         // Rendition dictionary.
3512         aLine.append("/R<</Type/Rendition /S/MR ");
3513 
3514         // MediaClip dictionary.
3515         aLine.append("/C<</Type/MediaClip /S/MCD ");
3516         if (bEmbed)
3517         {
3518             aLine.append("\n/D << /Type /Filespec /F (<embedded file>) ");
3519             if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
3520             {   // ISO 14289-1:2014, Clause: 7.11
3521                 aLine.append("/UF (<embedded file>) ");
3522             }
3523             aLine.append("/EF << /F ");
3524             aLine.append(rScreen.m_nTempFileObject);
3525             aLine.append(" 0 R >>");
3526         }
3527         else
3528         {
3529             // Linked.
3530             aLine.append("\n/D << /Type /Filespec /FS /URL /F ");
3531             aWriter.writeLiteralEncrypt(rScreen.m_aURL, rScreen.m_nObject, osl_getThreadTextEncoding());
3532             if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
3533             {   // ISO 14289-1:2014, Clause: 7.11
3534                 aLine.append("/UF ");
3535                 aWriter.writeUnicodeEncrypt(rScreen.m_aURL, rScreen.m_nObject);
3536             }
3537         }
3538         if (PDFWriter::PDFVersion::PDF_1_6 <= m_aContext.Version
3539             && !rScreen.m_AltText.isEmpty())
3540         {   // ISO 14289-1:2014, Clause: 7.11
3541             aLine.append("/Desc ");
3542             aWriter.writeUnicodeEncrypt(rScreen.m_AltText, rScreen.m_nObject);
3543         }
3544         aLine.append(" >>\n"); // end of /D
3545         // Allow playing the video via a tempfile.
3546         aLine.append("/P <</TF (TEMPACCESS)>>");
3547         // ISO 14289-1:2014, Clause: 7.18.6.2
3548         aLine.append("/CT ");
3549         aWriter.writeLiteralEncrypt(rScreen.m_MimeType, rScreen.m_nObject);
3550         // ISO 14289-1:2014, Clause: 7.18.6.2
3551         // Alt text is a "Multi-language Text Array"
3552         aLine.append(" /Alt [ () ");
3553         aWriter.writeUnicodeEncrypt(rScreen.m_AltText, rScreen.m_nObject);
3554         aLine.append(" ] "
3555                      ">>");
3556 
3557         // End Rendition dictionary by requesting play/pause/stop controls.
3558         aLine.append("/P<</BE<</C true >>>>"
3559                      ">>");
3560 
3561         // End Action dictionary.
3562         aLine.append("/OP 0 >>");
3563 
3564         if (-1 != rScreen.m_nStructParent)
3565         {
3566             aLine.append("\n/StructParent "
3567                 + OString::number(rScreen.m_nStructParent)
3568                 + "\n");
3569         }
3570 
3571         // End Annot dictionary.
3572         aLine.append("/P "
3573             + OString::number(m_aPages[rScreen.m_nPage].m_nPageObject)
3574             + " 0 R\n>>\nendobj\n\n");
3575         if (!writeBuffer(aLine))
3576             return false;
3577     }
3578 
3579     return true;
3580 }
3581 
emitLinkAnnotations()3582 bool PDFWriterImpl::emitLinkAnnotations()
3583 {
3584     MARK("PDFWriterImpl::emitLinkAnnotations");
3585     int nAnnots = m_aLinks.size();
3586     for( int i = 0; i < nAnnots; i++ )
3587     {
3588         const PDFLink& rLink            = m_aLinks[i];
3589         if( ! updateObject( rLink.m_nObject ) )
3590             continue;
3591         if( m_aContext.DefaultLinkAction == PDFWriter::RemoveExternalLinks && rLink.m_nDest < 0 )
3592             continue;
3593 
3594         OStringBuffer aLine( 1024 );
3595         COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
3596         aLine.append( rLink.m_nObject );
3597         aLine.append( " 0 obj\n" );
3598 // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
3599 // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
3600         aLine.append( "<</Type/Annot" );
3601         if (m_nPDFA_Version > 0)
3602             aLine.append( "/F 4" );
3603         aLine.append( "/Subtype/Link/Border[0 0 0]/Rect[" );
3604 
3605         appendFixedInt( rLink.m_aRect.Left()-7, aLine );//the +7 to have a better shape of the border rectangle
3606         aLine.append( ' ' );
3607         appendFixedInt( rLink.m_aRect.Top(), aLine );
3608         aLine.append( ' ' );
3609         appendFixedInt( rLink.m_aRect.Right()+7, aLine );//the +7 to have a better shape of the border rectangle
3610         aLine.append( ' ' );
3611         appendFixedInt( rLink.m_aRect.Bottom(), aLine );
3612         aLine.append( "]" );
3613         // ISO 14289-1:2014, Clause: 7.18.5
3614         if (!rLink.m_AltText.isEmpty())
3615         {
3616             aLine.append("/Contents");
3617             aWriter.writeUnicodeEncrypt(rLink.m_AltText, rLink.m_nObject);
3618         }
3619         if( rLink.m_nDest >= 0 )
3620         {
3621             aLine.append( "/Dest" );
3622             appendDest( rLink.m_nDest, aLine );
3623         }
3624         else
3625         {
3626 /*
3627 destination is external to the document, so
3628 we check in the following sequence:
3629 
3630  if target type is neither .pdf, nor .od[tpgs], then
3631           check if relative or absolute and act accordingly (use URI or 'launch application' as requested)
3632                              end processing
3633  else if target is .od[tpgs]: then
3634       if conversion of type from od[tpgs]  to pdf is requested, convert it and this becomes the new target file
3635       processing continue
3636 
3637  if (new)target is .pdf : then
3638      if GotToR is requested, then
3639            convert the target in GoToR where the fragment of the URI is
3640            considered the named destination in the target file, set relative or absolute as requested
3641      else strip the fragment from URL and then set URI or 'launch application' as requested
3642 */
3643 
3644 // FIXME: check if the decode mechanisms for URL processing throughout this implementation
3645 // are the correct one!!
3646 
3647 // extract target file type
3648             auto url(URIHelper::resolveIdnaHost(rLink.m_aURL));
3649 
3650             INetURLObject aDocumentURL( m_aContext.BaseURL );
3651             INetURLObject aTargetURL( url );
3652             bool bSetGoToRMode = false;
3653             bool    bTargetHasPDFExtension = false;
3654             INetProtocol eTargetProtocol = aTargetURL.GetProtocol();
3655             bool    bIsUNCPath = false;
3656             bool    bUnparsedURI = false;
3657 
3658             // check if the protocol is a known one, or if there is no protocol at all (on target only)
3659             // if there is no protocol, make the target relative to the current document directory
3660             // getting the needed URL information from the current document path
3661             if( eTargetProtocol == INetProtocol::NotValid )
3662             {
3663                 if( url.getLength() > 4 && url.startsWith("\\\\\\\\"))
3664                 {
3665                     bIsUNCPath = true;
3666                 }
3667                 else
3668                 {
3669                     //reassign the new target URL
3670                     aTargetURL = INetURLObject(rtl::Uri::convertRelToAbs(
3671                                                 (m_aContext.BaseURL.getLength() > 0 ?
3672                                                     m_aContext.BaseURL :
3673                                                     //use dummy location if empty
3674                                                     u"http://ahost.ax"_ustr),
3675                                                 url));
3676 
3677                     //recompute the target protocol, with the new URL
3678                     //normal URL processing resumes
3679                     eTargetProtocol = aTargetURL.GetProtocol();
3680 
3681                     bUnparsedURI = eTargetProtocol == INetProtocol::NotValid;
3682                 }
3683             }
3684 
3685             OUString aFileExtension = aTargetURL.GetFileExtension();
3686 
3687             // Check if the URL ends in '/': if yes it's a directory,
3688             // it will be forced to a URI link.
3689             // possibly a malformed URI, leave it as it is, force as URI
3690             if( aTargetURL.hasFinalSlash() )
3691                 m_aContext.DefaultLinkAction = PDFWriter::URIAction;
3692 
3693             if( !aFileExtension.isEmpty() )
3694             {
3695                 if( m_aContext.ConvertOOoTargetToPDFTarget )
3696                 {
3697                     bool bChangeFileExtensionToPDF = false;
3698                     //examine the file type (.odm .odt. .odp, odg, ods)
3699                     if( aFileExtension.equalsIgnoreAsciiCase( "odm" ) )
3700                         bChangeFileExtensionToPDF = true;
3701                     if( aFileExtension.equalsIgnoreAsciiCase( "odt" ) )
3702                         bChangeFileExtensionToPDF = true;
3703                     else if( aFileExtension.equalsIgnoreAsciiCase( "odp" ) )
3704                         bChangeFileExtensionToPDF = true;
3705                     else if( aFileExtension.equalsIgnoreAsciiCase( "odg" ) )
3706                         bChangeFileExtensionToPDF = true;
3707                     else if( aFileExtension.equalsIgnoreAsciiCase( "ods" ) )
3708                         bChangeFileExtensionToPDF = true;
3709                     if( bChangeFileExtensionToPDF )
3710                         aTargetURL.setExtension(u"pdf" );
3711                 }
3712                 //check if extension is pdf, see if GoToR should be forced
3713                 bTargetHasPDFExtension = aTargetURL.GetFileExtension().equalsIgnoreAsciiCase( "pdf" );
3714                 if( m_aContext.ForcePDFAction && bTargetHasPDFExtension )
3715                     bSetGoToRMode = true;
3716             }
3717             //prepare the URL, if relative or not
3718             INetProtocol eBaseProtocol = aDocumentURL.GetProtocol();
3719             //queue the string common to all types of actions
3720             aLine.append( "/A<</Type/Action/S");
3721             if( bIsUNCPath ) // handle Win UNC paths
3722             {
3723                 aLine.append("/Launch");
3724                 // Entry /Win is deprecated in PDF 2.0
3725                 if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0)
3726                 {
3727                     // So write /F directly. AFAICS it's up to PDF viewer to resolve this correctly
3728                     aWriter.writeKeyAndLiteralEncrypt("/F", url, rLink.m_nObject, osl_getThreadTextEncoding());
3729                 }
3730                 else
3731                 {
3732                     aLine.append("/Win");
3733                     aWriter.startDict();
3734                     // INetURLObject is not good with UNC paths, use original path
3735                     aWriter.writeKeyAndLiteralEncrypt("/F", url, rLink.m_nObject, osl_getThreadTextEncoding());
3736                     aWriter.endDict();
3737                 }
3738             }
3739             else
3740             {
3741                 bool bSetRelative = false;
3742                 bool bFileSpec = false;
3743                 //check if relative file link is requested and if the protocol is 'file://'
3744                 if( m_aContext.RelFsys && eBaseProtocol == eTargetProtocol && eTargetProtocol == INetProtocol::File )
3745                     bSetRelative = true;
3746 
3747                 OUString aFragment = aTargetURL.GetMark( INetURLObject::DecodeMechanism::NONE /*DecodeMechanism::WithCharset*/ ); //fragment as is,
3748                 if( !bSetGoToRMode )
3749                 {
3750                     switch( m_aContext.DefaultLinkAction )
3751                     {
3752                     default:
3753                     case PDFWriter::URIAction :
3754                     case PDFWriter::URIActionDestination :
3755                         aLine.append( "/URI/URI" );
3756                         break;
3757                     case PDFWriter::LaunchAction:
3758                         // now:
3759                         // if a launch action is requested and the hyperlink target has a fragment
3760                         // and the target file does not have a pdf extension, or it's not a 'file:://'
3761                         // protocol then force the uri action on it
3762                         // This code will permit the correct opening of application on web pages,
3763                         // the one that normally have fragments (but I may be wrong...)
3764                         // and will force the use of URI when the protocol is not file:
3765                         if( (!aFragment.isEmpty() && !bTargetHasPDFExtension) ||
3766                                         eTargetProtocol != INetProtocol::File )
3767                         {
3768                             aLine.append( "/URI/URI" );
3769                         }
3770                         else
3771                         {
3772                             aLine.append( "/Launch/F" );
3773                             bFileSpec = true;
3774                         }
3775                         break;
3776                     }
3777                 }
3778 
3779                 //fragment are encoded in the same way as in the named destination processing
3780                 if( bSetGoToRMode )
3781                 {
3782                     //add the fragment
3783                     OUString aURLNoMark = aTargetURL.GetURLNoMark( INetURLObject::DecodeMechanism::WithCharset );
3784                     aLine.append("/GoToR");
3785                     aLine.append("/F");
3786                     aWriter.writeLiteralEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURLNoMark,
3787                                                                                          INetURLObject::EncodeMechanism::WasEncoded,
3788                                                                                          INetURLObject::DecodeMechanism::WithCharset ) :
3789                                                                    aURLNoMark, rLink.m_nObject, osl_getThreadTextEncoding());
3790                     if( !aFragment.isEmpty() )
3791                     {
3792                         aLine.append("/D/");
3793                         appendDestinationName( aFragment , aLine );
3794                     }
3795                 }
3796                 else
3797                 {
3798                     // change the fragment to accommodate the bookmark (only if the file extension
3799                     // is PDF and the requested action is of the correct type)
3800                     if(m_aContext.DefaultLinkAction == PDFWriter::URIActionDestination &&
3801                                bTargetHasPDFExtension && !aFragment.isEmpty() )
3802                     {
3803                         OStringBuffer aLineLoc( 1024 );
3804                         appendDestinationName( aFragment , aLineLoc );
3805                         //substitute the fragment
3806                         aTargetURL.SetMark( OStringToOUString(aLineLoc, RTL_TEXTENCODING_ASCII_US) );
3807                     }
3808                     OUString aURL = bUnparsedURI ? url :
3809                                                    aTargetURL.GetMainURL( bFileSpec ? INetURLObject::DecodeMechanism::WithCharset :
3810                                                                                       INetURLObject::DecodeMechanism::NONE );
3811                     aWriter.writeLiteralEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURL,
3812                                                                                         INetURLObject::EncodeMechanism::WasEncoded,
3813                                                                                             bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE
3814                                                                                             ) :
3815                                                                                aURL , rLink.m_nObject, osl_getThreadTextEncoding() );
3816                 }
3817             }
3818             aLine.append( ">>\n" );
3819         }
3820         if (rLink.m_nStructParent != -1)
3821         {
3822             aLine.append( "/StructParent " );
3823             aLine.append( rLink.m_nStructParent );
3824         }
3825         aLine.append( ">>\nendobj\n\n" );
3826         if (!writeBuffer(aLine))
3827             return false;
3828     }
3829 
3830     return true;
3831 }
3832 
3833 namespace
3834 {
3835 
appendAnnotationRect(tools::Rectangle const & rRectangle,OStringBuffer & aLine)3836 void appendAnnotationRect(tools::Rectangle const & rRectangle, OStringBuffer & aLine)
3837 {
3838     aLine.append("/Rect [");
3839     appendFixedInt(rRectangle.Left(), aLine);
3840     aLine.append(' ');
3841     appendFixedInt(rRectangle.Top(), aLine);
3842     aLine.append(' ');
3843     appendFixedInt(rRectangle.Right(), aLine);
3844     aLine.append(' ');
3845     appendFixedInt(rRectangle.Bottom(), aLine);
3846     aLine.append("] ");
3847 }
3848 
appendAnnotationColor(Color const & rColor,OStringBuffer & aLine)3849 void appendAnnotationColor(Color const& rColor, OStringBuffer & aLine)
3850 {
3851     aLine.append("/C [");
3852     appendColor(rColor, aLine, false);
3853     aLine.append("] ");
3854 }
3855 
appendAnnotationInteriorColor(Color const & rColor,OStringBuffer & aLine)3856 void appendAnnotationInteriorColor(Color const& rColor, OStringBuffer & aLine)
3857 {
3858     aLine.append("/IC [");
3859     appendColor(rColor, aLine, false);
3860     aLine.append("] ");
3861 }
3862 
appendPolygon(basegfx::B2DPolygon const & rPolygon,double fPageHeight,OStringBuffer & aLine)3863 void appendPolygon(basegfx::B2DPolygon const& rPolygon, double fPageHeight, OStringBuffer & aLine)
3864 {
3865     for (sal_uInt32 i = 0; i < rPolygon.count(); ++i)
3866     {
3867         appendDouble(convertMm100ToPoint(rPolygon.getB2DPoint(i).getX()), aLine, nLog10Divisor);
3868         aLine.append(" ");
3869         appendDouble(fPageHeight - convertMm100ToPoint(rPolygon.getB2DPoint(i).getY()), aLine, nLog10Divisor);
3870         aLine.append(" ");
3871     }
3872 }
3873 
appendVertices(basegfx::B2DPolygon const & rPolygon,double fPageHeight,OStringBuffer & aLine)3874 void appendVertices(basegfx::B2DPolygon const& rPolygon, double fPageHeight, OStringBuffer & aLine)
3875 {
3876     aLine.append("/Vertices [");
3877     appendPolygon(rPolygon, fPageHeight, aLine);
3878     aLine.append("] ");
3879 
3880 }
3881 
appendAnnotationBorder(float fBorderWidth,OStringBuffer & aLine)3882 void appendAnnotationBorder(float fBorderWidth, OStringBuffer & aLine)
3883 {
3884     aLine.append("/Border [0 0 ");
3885     appendDouble(convertMm100ToPoint(fBorderWidth), aLine, nLog10Divisor);
3886     aLine.append("] ");
3887 }
3888 
3889 } // end anonymous namespace
3890 
emitTextAnnotationLine(OStringBuffer & aLine,PDFNoteEntry const & rNote)3891 void PDFWriterImpl::emitTextAnnotationLine(OStringBuffer & aLine, PDFNoteEntry const & rNote)
3892 {
3893     COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
3894 
3895     appendObjectID(rNote.m_nObject, aLine);
3896 
3897     double fPageHeight = m_aPages[rNote.m_nPage].getHeight();
3898 
3899     aLine.append("<</Type /Annot ");
3900 
3901     appendAnnotationRect(rNote.m_aRect, aLine);
3902 
3903     aLine.append("/Subtype ");
3904 
3905     if (rNote.m_aContents.meType == vcl::pdf::PDFAnnotationSubType::Polygon ||
3906         rNote.m_aContents.meType == vcl::pdf::PDFAnnotationSubType::Polyline)
3907     {
3908         auto const& rPolygon = rNote.m_aContents.maPolygons[0];
3909         if (rNote.m_aContents.meType == vcl::pdf::PDFAnnotationSubType::Polygon)
3910             aLine.append("/Polygon ");
3911         else
3912             aLine.append("/Polyline ");
3913 
3914         appendVertices(rPolygon, fPageHeight, aLine);
3915         appendAnnotationColor(rNote.m_aContents.maAnnotationColor, aLine);
3916 
3917         if (rNote.m_aContents.meType == vcl::pdf::PDFAnnotationSubType::Polygon)
3918             appendAnnotationInteriorColor(rNote.m_aContents.maInteriorColor, aLine);
3919         appendAnnotationBorder(rNote.m_aContents.mfWidth, aLine);
3920     }
3921     else if (rNote.m_aContents.meType == vcl::pdf::PDFAnnotationSubType::Square)
3922     {
3923         aLine.append("/Square ");
3924         appendAnnotationColor(rNote.m_aContents.maAnnotationColor, aLine);
3925         appendAnnotationInteriorColor(rNote.m_aContents.maInteriorColor, aLine);
3926         appendAnnotationBorder(rNote.m_aContents.mfWidth, aLine);
3927     }
3928     else if (rNote.m_aContents.meType == vcl::pdf::PDFAnnotationSubType::Circle)
3929     {
3930         aLine.append("/Circle ");
3931         appendAnnotationColor(rNote.m_aContents.maAnnotationColor, aLine);
3932         appendAnnotationInteriorColor(rNote.m_aContents.maInteriorColor, aLine);
3933         appendAnnotationBorder(rNote.m_aContents.mfWidth, aLine);
3934     }
3935     else if (rNote.m_aContents.meType == vcl::pdf::PDFAnnotationSubType::Ink)
3936     {
3937         aLine.append("/Ink ");
3938 
3939         aLine.append("/InkList [");
3940         for (auto const& rPolygon : rNote.m_aContents.maPolygons)
3941         {
3942             aLine.append("[");
3943             appendPolygon(rPolygon, fPageHeight, aLine);
3944             aLine.append("]");
3945         }
3946         aLine.append("] ");
3947 
3948         appendAnnotationColor(rNote.m_aContents.maAnnotationColor, aLine);
3949         appendAnnotationBorder(rNote.m_aContents.mfWidth, aLine);
3950 
3951     }
3952     else if (rNote.m_aContents.meType == vcl::pdf::PDFAnnotationSubType::FreeText)
3953     {
3954         aLine.append("/FreeText ");
3955     }
3956     else
3957     {
3958         aLine.append("/Text ");
3959     }
3960 
3961     // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
3962     // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
3963     if (m_nPDFA_Version > 0)
3964         aLine.append("/F 4 ");
3965 
3966     aLine.append("/Popup ");
3967     appendObjectReference(rNote.m_aPopUpAnnotation.m_nObject, aLine);
3968 
3969     auto & rDateTime = rNote.m_aContents.maModificationDate;
3970 
3971     aLine.append("/M (");
3972     appendPdfTimeDate(aLine, rDateTime.Year, rDateTime.Month, rDateTime.Day, rDateTime.Hours, rDateTime.Minutes, rDateTime.Seconds, 0);
3973     aLine.append(") ");
3974 
3975     // contents of the note (type text string)
3976     aLine.append("/Contents ");
3977     aWriter.writeUnicodeEncrypt(rNote.m_aContents.maContents, rNote.m_nObject);
3978     aLine.append("\n");
3979 
3980     // optional title
3981     if (!rNote.m_aContents.maTitle.isEmpty())
3982     {
3983         aLine.append("/T ");
3984         aWriter.writeUnicodeEncrypt(rNote.m_aContents.maTitle, rNote.m_nObject);
3985         aLine.append("\n");
3986     }
3987 
3988     if (-1 != rNote.m_nStructParent)
3989     {
3990         aLine.append("/StructParent ");
3991         aLine.append(rNote.m_nStructParent);
3992         aLine.append("\n");
3993     }
3994 
3995     aLine.append(">>\n");
3996     aLine.append("endobj\n\n");
3997 }
3998 
emitPopupAnnotationLine(OStringBuffer & aLine,PDFPopupAnnotation const & rPopUp)3999 void PDFWriterImpl::emitPopupAnnotationLine(OStringBuffer & aLine, PDFPopupAnnotation const & rPopUp)
4000 {
4001     appendObjectID(rPopUp.m_nObject, aLine);
4002     aLine.append("<</Type /Annot /Subtype /Popup ");
4003     aLine.append("/Rect[");
4004     appendFixedInt(rPopUp.m_aRect.Left(), aLine);
4005     aLine.append(' ');
4006     appendFixedInt(rPopUp.m_aRect.Top(), aLine);
4007     aLine.append(' ');
4008     appendFixedInt(rPopUp.m_aRect.Right(), aLine);
4009     aLine.append(' ');
4010     appendFixedInt(rPopUp.m_aRect.Bottom(), aLine);
4011     aLine.append("]");
4012     aLine.append("/Parent ");
4013     appendObjectReference(rPopUp.m_nParentObject, aLine);
4014     aLine.append(">>\n");
4015     aLine.append("endobj\n\n");
4016 }
4017 
emitNoteAnnotations()4018 bool PDFWriterImpl::emitNoteAnnotations()
4019 {
4020     // emit note annotations
4021     int nAnnots = m_aNotes.size();
4022     for( int i = 0; i < nAnnots; i++ )
4023     {
4024         const PDFNoteEntry& rNote = m_aNotes[i];
4025         const PDFPopupAnnotation& rPopUp = rNote.m_aPopUpAnnotation;
4026 
4027         {
4028             if (!updateObject(rNote.m_nObject))
4029                 return false;
4030 
4031             OStringBuffer aLine(1024);
4032 
4033             emitTextAnnotationLine(aLine, rNote);
4034 
4035             if (!writeBuffer(aLine))
4036                 return false;
4037         }
4038 
4039         {
4040 
4041             if (!updateObject(rPopUp.m_nObject))
4042                 return false;
4043 
4044             OStringBuffer aLine(1024);
4045 
4046             emitPopupAnnotationLine(aLine, rPopUp);
4047 
4048             if (!writeBuffer(aLine))
4049                 return false;
4050         }
4051     }
4052     return true;
4053 }
4054 
replaceFont(const vcl::Font & rControlFont,const vcl::Font & rAppSetFont)4055 Font PDFWriterImpl::replaceFont( const vcl::Font& rControlFont, const vcl::Font&  rAppSetFont )
4056 {
4057     bool bAdjustSize = false;
4058 
4059     Font aFont( rControlFont );
4060     if( aFont.GetFamilyName().isEmpty() )
4061     {
4062         aFont = rAppSetFont;
4063         if( rControlFont.GetFontHeight() )
4064             aFont.SetFontSize( Size( 0, rControlFont.GetFontHeight() ) );
4065         else
4066             bAdjustSize = true;
4067         if( rControlFont.GetItalic() != ITALIC_DONTKNOW )
4068             aFont.SetItalic( rControlFont.GetItalic() );
4069         if( rControlFont.GetWeight() != WEIGHT_DONTKNOW )
4070             aFont.SetWeight( rControlFont.GetWeight() );
4071     }
4072     else if( ! aFont.GetFontHeight() )
4073     {
4074         aFont.SetFontSize( rAppSetFont.GetFontSize() );
4075         bAdjustSize = true;
4076     }
4077     if( bAdjustSize )
4078     {
4079         Size aFontSize = aFont.GetFontSize();
4080         OutputDevice* pDefDev = Application::GetDefaultDevice();
4081         aFontSize = OutputDevice::LogicToLogic( aFontSize, pDefDev->GetMapMode(), getMapMode() );
4082         aFont.SetFontSize( aFontSize );
4083     }
4084     return aFont;
4085 }
4086 
getBestBuildinFont(const vcl::Font & rFont)4087 sal_Int32 PDFWriterImpl::getBestBuildinFont( const vcl::Font& rFont )
4088 {
4089     sal_Int32 nBest = 4; // default to Helvetica
4090 
4091     if (rFont.GetFamilyType() == FAMILY_ROMAN)
4092     {
4093         // Serif: default to Times-Roman.
4094         nBest = 8;
4095     }
4096 
4097     OUString aFontName( rFont.GetFamilyName() );
4098     aFontName = aFontName.toAsciiLowerCase();
4099 
4100     if( aFontName.indexOf( "times" ) != -1 )
4101         nBest = 8;
4102     else if( aFontName.indexOf( "courier" ) != -1 )
4103         nBest = 0;
4104     else if( aFontName.indexOf( "dingbats" ) != -1 )
4105         nBest = 13;
4106     else if( aFontName.indexOf( "symbol" ) != -1 )
4107         nBest = 12;
4108     if( nBest < 12 )
4109     {
4110         if( rFont.GetItalic() == ITALIC_OBLIQUE || rFont.GetItalic() == ITALIC_NORMAL )
4111             nBest += 1;
4112         if( rFont.GetWeight() > WEIGHT_MEDIUM )
4113             nBest += 2;
4114     }
4115 
4116     if( m_aBuildinFontToObjectMap.find( nBest ) == m_aBuildinFontToObjectMap.end() )
4117         m_aBuildinFontToObjectMap[ nBest ] = createObject();
4118 
4119     return nBest;
4120 }
4121 
replaceColor(const Color & rCol1,const Color & rCol2)4122 static const Color& replaceColor( const Color& rCol1, const Color& rCol2 )
4123 {
4124     return (rCol1 == COL_TRANSPARENT) ? rCol2 : rCol1;
4125 }
4126 
createDefaultPushButtonAppearance(PDFWidget & rButton,const PDFWriter::PushButtonWidget & rWidget)4127 void PDFWriterImpl::createDefaultPushButtonAppearance( PDFWidget& rButton, const PDFWriter::PushButtonWidget& rWidget )
4128 {
4129     const StyleSettings& rSettings = m_aWidgetStyleSettings;
4130 
4131     // save graphics state
4132     push( PushFlags::ALL );
4133 
4134     // transform relative to control's coordinates since an
4135     // appearance stream is a form XObject
4136     // this relies on the m_aRect member of rButton NOT already being transformed
4137     // to default user space
4138     if( rWidget.Background || rWidget.Border )
4139     {
4140         setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetLightColor() ) : COL_TRANSPARENT );
4141         setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetDialogColor() ) : COL_TRANSPARENT );
4142         drawRectangle( rWidget.Location );
4143     }
4144     // prepare font to use
4145     Font aFont = replaceFont( rWidget.TextFont, rSettings.GetPushButtonFont() );
4146     setFont( aFont );
4147     setTextColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ) );
4148 
4149     drawText( rButton.m_aRect, rButton.m_aText, rButton.m_nTextStyle );
4150 
4151     // create DA string while local mapmode is still in place
4152     // (that is before endRedirect())
4153     OStringBuffer aDA( 256 );
4154     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ), aDA );
4155     Font aDummyFont( u"Helvetica"_ustr, aFont.GetFontSize() );
4156     sal_Int32 nDummyBuildin = getBestBuildinFont( aDummyFont );
4157     aDA.append( ' ' );
4158     aDA.append(pdf::BuildinFontFace::Get(nDummyBuildin).getNameObject());
4159     aDA.append( ' ' );
4160     m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
4161     aDA.append( " Tf" );
4162     rButton.m_aDAString = aDA.makeStringAndClear();
4163 
4164     pop();
4165 
4166     rButton.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = new SvMemoryStream();
4167 
4168     /* seems like a bad hack but at least works in both AR5 and 6:
4169        we draw the button ourselves and tell AR
4170        the button would be totally transparent with no text
4171 
4172        One would expect that simply setting a normal appearance
4173        should suffice, but no, as soon as the user actually presses
4174        the button and an action is tied to it (gasp! a button that
4175        does something) the appearance gets replaced by some crap that AR
4176        creates on the fly even if no DA or MK is given. On AR6 at least
4177        the DA and MK work as expected, but on AR5 this creates a region
4178        filled with the background color but nor text. Urgh.
4179     */
4180     rButton.m_aMKDict = "/BC [] /BG [] /CA"_ostr;
4181     rButton.m_aMKDictCAString = ""_ostr;
4182 }
4183 
drawFieldBorder(PDFWidget & rIntern,const PDFWriter::AnyWidget & rWidget,const StyleSettings & rSettings)4184 Font PDFWriterImpl::drawFieldBorder( PDFWidget& rIntern,
4185                                      const PDFWriter::AnyWidget& rWidget,
4186                                      const StyleSettings& rSettings )
4187 {
4188     Font aFont = replaceFont( rWidget.TextFont, rSettings.GetFieldFont() );
4189 
4190     if( rWidget.Background || rWidget.Border )
4191     {
4192         if( rWidget.Border && rWidget.BorderColor == COL_TRANSPARENT )
4193         {
4194             sal_Int32 nDelta = GetDPIX() / 500;
4195             if( nDelta < 1 )
4196                 nDelta = 1;
4197             setLineColor( COL_TRANSPARENT );
4198             tools::Rectangle aRect = rIntern.m_aRect;
4199             setFillColor( rSettings.GetLightBorderColor() );
4200             drawRectangle( aRect );
4201             aRect.AdjustLeft(nDelta ); aRect.AdjustTop(nDelta );
4202             aRect.AdjustRight( -nDelta ); aRect.AdjustBottom( -nDelta );
4203             setFillColor( rSettings.GetFieldColor() );
4204             drawRectangle( aRect );
4205             setFillColor( rSettings.GetLightColor() );
4206             drawRectangle( tools::Rectangle( Point( aRect.Left(), aRect.Bottom()-nDelta ), aRect.BottomRight() ) );
4207             drawRectangle( tools::Rectangle( Point( aRect.Right()-nDelta, aRect.Top() ), aRect.BottomRight() ) );
4208             setFillColor( rSettings.GetDarkShadowColor() );
4209             drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Left()+nDelta, aRect.Bottom() ) ) );
4210             drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Right(), aRect.Top()+nDelta ) ) );
4211         }
4212         else
4213         {
4214             setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetShadowColor() ) : COL_TRANSPARENT );
4215             setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
4216             drawRectangle( rIntern.m_aRect );
4217         }
4218 
4219         if( rWidget.Border )
4220         {
4221             // adjust edit area accounting for border
4222             sal_Int32 nDelta = aFont.GetFontHeight()/4;
4223             if( nDelta < 1 )
4224                 nDelta = 1;
4225             rIntern.m_aRect.AdjustLeft(nDelta );
4226             rIntern.m_aRect.AdjustTop(nDelta );
4227             rIntern.m_aRect.AdjustRight( -nDelta );
4228             rIntern.m_aRect.AdjustBottom( -nDelta );
4229         }
4230     }
4231     return aFont;
4232 }
4233 
createDefaultEditAppearance(PDFWidget & rEdit,const PDFWriter::EditWidget & rWidget)4234 void PDFWriterImpl::createDefaultEditAppearance( PDFWidget& rEdit, const PDFWriter::EditWidget& rWidget )
4235 {
4236     const StyleSettings& rSettings = m_aWidgetStyleSettings;
4237     SvMemoryStream* pEditStream = new SvMemoryStream( 1024, 1024 );
4238 
4239     push( PushFlags::ALL );
4240 
4241     // prepare font to use, draw field border
4242     Font aFont = drawFieldBorder( rEdit, rWidget, rSettings );
4243     // Get the built-in font which is closest to aFont.
4244     sal_Int32 nBest = getBestBuildinFont(aFont);
4245 
4246     // prepare DA string
4247     OStringBuffer aDA( 32 );
4248     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
4249     aDA.append( ' ' );
4250     aDA.append(pdf::BuildinFontFace::Get(nBest).getNameObject());
4251 
4252     OStringBuffer aDR( 32 );
4253     aDR.append( "/Font " );
4254     aDR.append( getFontDictObject() );
4255     aDR.append( " 0 R" );
4256     rEdit.m_aDRDict = aDR.makeStringAndClear();
4257     aDA.append( ' ' );
4258     m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
4259     aDA.append( " Tf" );
4260 
4261     /*  create an empty appearance stream, let the viewer create
4262         the appearance at runtime. This is because AR5 seems to
4263         paint the widget appearance always, and a dynamically created
4264         appearance on top of it. AR6 is well behaved in that regard, so
4265         that behaviour seems to be a bug. Anyway this empty appearance
4266         relies on /NeedAppearances in the AcroForm dictionary set to "true"
4267      */
4268     beginRedirect( pEditStream, rEdit.m_aRect );
4269     writeBuffer( "/Tx BMC\nEMC\n" );
4270 
4271     endRedirect();
4272     pop();
4273 
4274     rEdit.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = pEditStream;
4275 
4276     rEdit.m_aDAString = aDA.makeStringAndClear();
4277 }
4278 
createDefaultListBoxAppearance(PDFWidget & rBox,const PDFWriter::ListBoxWidget & rWidget)4279 void PDFWriterImpl::createDefaultListBoxAppearance( PDFWidget& rBox, const PDFWriter::ListBoxWidget& rWidget )
4280 {
4281     const StyleSettings& rSettings = m_aWidgetStyleSettings;
4282     SvMemoryStream* pListBoxStream = new SvMemoryStream( 1024, 1024 );
4283 
4284     push( PushFlags::ALL );
4285 
4286     // prepare font to use, draw field border
4287     Font aFont = drawFieldBorder( rBox, rWidget, rSettings );
4288     sal_Int32 nBest = getSystemFont( aFont );
4289 
4290     setLineColor( COL_TRANSPARENT );
4291     setFillColor( replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) );
4292     drawRectangle( rBox.m_aRect );
4293 
4294     pop();
4295 
4296     // prepare DA string
4297     OStringBuffer aDA( 256 );
4298     // prepare DA string
4299     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
4300     aDA.append( ' ' );
4301     aDA.append( "/F" );
4302     aDA.append( nBest );
4303 
4304     OStringBuffer aDR( 32 );
4305     aDR.append( "/Font " );
4306     aDR.append( getFontDictObject() );
4307     aDR.append( " 0 R" );
4308     rBox.m_aDRDict = aDR.makeStringAndClear();
4309     aDA.append( ' ' );
4310     m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
4311     aDA.append( " Tf" );
4312 
4313     beginRedirect(pListBoxStream, rBox.m_aRect);
4314     // empty appearance, see createDefaultEditAppearance for reference
4315     writeBuffer("/Tx BMC\nEMC\n");
4316     endRedirect();
4317 
4318     rBox.m_aAppearances["N"_ostr]["Standard"_ostr] = pListBoxStream;
4319     rBox.m_aDAString = aDA.makeStringAndClear();
4320 }
4321 
createDefaultCheckBoxAppearance(PDFWidget & rBox,const PDFWriter::CheckBoxWidget & rWidget)4322 void PDFWriterImpl::createDefaultCheckBoxAppearance( PDFWidget& rBox, const PDFWriter::CheckBoxWidget& rWidget )
4323 {
4324     const StyleSettings& rSettings = m_aWidgetStyleSettings;
4325 
4326     // save graphics state
4327     push( PushFlags::ALL );
4328 
4329     if( rWidget.Background || rWidget.Border )
4330     {
4331         setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
4332         setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
4333         drawRectangle( rBox.m_aRect );
4334     }
4335 
4336     Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
4337     setFont( aFont );
4338     Size aFontSize = aFont.GetFontSize();
4339     if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
4340         aFontSize.setHeight( rBox.m_aRect.GetHeight() );
4341     sal_Int32 nDelta = aFontSize.Height()/10;
4342     if( nDelta < 1 )
4343         nDelta = 1;
4344 
4345     tools::Rectangle aCheckRect, aTextRect;
4346     {
4347         aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
4348         aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
4349         aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
4350         aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
4351 
4352         // #i74206# handle small controls without text area
4353         while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
4354         {
4355             aCheckRect.AdjustRight( -nDelta );
4356             aCheckRect.AdjustTop(nDelta/2 );
4357             aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
4358         }
4359 
4360         aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
4361         aTextRect.SetTop( rBox.m_aRect.Top() );
4362         aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
4363         aTextRect.SetBottom( rBox.m_aRect.Bottom() );
4364     }
4365     setLineColor( COL_BLACK );
4366     setFillColor( COL_TRANSPARENT );
4367     OStringBuffer aLW( 32 );
4368     aLW.append( "q " );
4369     m_aPages[m_nCurrentPage].appendMappedLength( nDelta, aLW );
4370     aLW.append( " w " );
4371     writeBuffer( aLW );
4372     drawRectangle( aCheckRect );
4373     writeBuffer( " Q\n" );
4374     setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4375     drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
4376 
4377     pop();
4378 
4379     OStringBuffer aDA( 256 );
4380 
4381     // tdf#93853 don't rely on Zapf (or any other 'standard' font)
4382     // being present, but our own OpenSymbol - N.B. PDF/A for good
4383     // reasons require even the standard PS fonts to be embedded!
4384     Push();
4385     SetFont( Font( u"OpenSymbol"_ustr, aFont.GetFontSize() ) );
4386     const LogicalFontInstance* pFontInstance = GetFontInstance();
4387     const vcl::font::PhysicalFontFace* pFace = pFontInstance->GetFontFace();
4388     Pop();
4389 
4390     // make sure OpenSymbol is embedded, and includes our checkmark
4391     const sal_Unicode cMark=0x2713;
4392     const auto nGlyphId = pFontInstance->GetGlyphIndex(cMark);
4393     const auto nGlyphWidth = pFontInstance->GetGlyphWidth(nGlyphId, false, false);
4394 
4395     sal_uInt8 nMappedGlyph;
4396     sal_Int32 nMappedFontObject;
4397     registerGlyph(nGlyphId, pFace, pFontInstance, { cMark }, nGlyphWidth, nMappedGlyph, nMappedFontObject);
4398 
4399     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
4400     aDA.append( ' ' );
4401     aDA.append( "/F" );
4402     aDA.append( nMappedFontObject );
4403     aDA.append( " 0 Tf" );
4404 
4405     OStringBuffer aDR( 32 );
4406     aDR.append( "/Font " );
4407     aDR.append( getFontDictObject() );
4408     aDR.append( " 0 R" );
4409     rBox.m_aDRDict = aDR.makeStringAndClear();
4410     rBox.m_aDAString = aDA.makeStringAndClear();
4411     rBox.m_aMKDict = "/CA"_ostr;
4412     rBox.m_aMKDictCAString = "8"_ostr;
4413     rBox.m_aRect = aCheckRect;
4414 
4415     // create appearance streams
4416     sal_Int32 nCharXOffset = 1000 - 787; // metrics from OpenSymbol
4417     nCharXOffset *= aCheckRect.GetHeight();
4418     nCharXOffset /= 2000;
4419     sal_Int32 nCharYOffset = 1000 - (820-143); // metrics from Zapf
4420     nCharYOffset *= aCheckRect.GetHeight();
4421     nCharYOffset /= 2000;
4422 
4423     // write 'checked' appearance stream
4424     SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
4425     beginRedirect( pCheckStream, aCheckRect );
4426     aDA.append( "/Tx BMC\nq BT\n" );
4427     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
4428     aDA.append( ' ' );
4429     aDA.append( "/F" );
4430     aDA.append( nMappedFontObject );
4431     aDA.append( ' ' );
4432     m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
4433     aDA.append( " Tf\n" );
4434     m_aPages[ m_nCurrentPage ].appendMappedLength( nCharXOffset, aDA );
4435     aDA.append( " " );
4436     m_aPages[ m_nCurrentPage ].appendMappedLength( nCharYOffset, aDA );
4437     aDA.append( " Td <" );
4438     COSWriter::appendHex( nMappedGlyph, aDA );
4439     aDA.append( "> Tj\nET\nQ\nEMC\n" );
4440     writeBuffer( aDA );
4441     endRedirect();
4442     rBox.m_aAppearances[ "N"_ostr ][ "Yes"_ostr ] = pCheckStream;
4443 
4444     // write 'unchecked' appearance stream
4445     SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
4446     beginRedirect( pUncheckStream, aCheckRect );
4447     writeBuffer( "/Tx BMC\nEMC\n" );
4448     endRedirect();
4449     rBox.m_aAppearances[ "N"_ostr ][ "Off"_ostr ] = pUncheckStream;
4450 }
4451 
createDefaultRadioButtonAppearance(PDFWidget & rBox,const PDFWriter::RadioButtonWidget & rWidget)4452 void PDFWriterImpl::createDefaultRadioButtonAppearance( PDFWidget& rBox, const PDFWriter::RadioButtonWidget& rWidget )
4453 {
4454     const StyleSettings& rSettings = m_aWidgetStyleSettings;
4455 
4456     // save graphics state
4457     push( PushFlags::ALL );
4458 
4459     if( rWidget.Background || rWidget.Border )
4460     {
4461         setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
4462         setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
4463         drawRectangle( rBox.m_aRect );
4464     }
4465 
4466     Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
4467     setFont( aFont );
4468     Size aFontSize = aFont.GetFontSize();
4469     if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
4470         aFontSize.setHeight( rBox.m_aRect.GetHeight() );
4471     sal_Int32 nDelta = aFontSize.Height()/10;
4472     if( nDelta < 1 )
4473         nDelta = 1;
4474 
4475     tools::Rectangle aCheckRect, aTextRect;
4476     {
4477         aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
4478         aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
4479         aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
4480         aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
4481 
4482         // #i74206# handle small controls without text area
4483         while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
4484         {
4485             aCheckRect.AdjustRight( -nDelta );
4486             aCheckRect.AdjustTop(nDelta/2 );
4487             aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
4488         }
4489 
4490         aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
4491         aTextRect.SetTop( rBox.m_aRect.Top() );
4492         aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
4493         aTextRect.SetBottom( rBox.m_aRect.Bottom() );
4494     }
4495     setLineColor( COL_BLACK );
4496     setFillColor( COL_TRANSPARENT );
4497     OStringBuffer aLW( 32 );
4498     aLW.append( "q " );
4499     m_aPages[ m_nCurrentPage ].appendMappedLength( nDelta, aLW );
4500     aLW.append( " w " );
4501     writeBuffer( aLW );
4502     drawEllipse( aCheckRect );
4503     writeBuffer( " Q\n" );
4504     setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4505     drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
4506 
4507     pop();
4508 
4509     //to encrypt this (el)
4510     rBox.m_aMKDict = "/CA"_ostr;
4511     //after this assignment, to m_aMKDic cannot be added anything
4512     rBox.m_aMKDictCAString = "l"_ostr;
4513 
4514     rBox.m_aRect = aCheckRect;
4515 
4516     // create appearance streams
4517     push( PushFlags::ALL);
4518     SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
4519 
4520     beginRedirect( pCheckStream, aCheckRect );
4521     OStringBuffer aDA( 256 );
4522     aDA.append( "/Tx BMC\nq BT\n" );
4523     appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
4524     aDA.append( ' ' );
4525     m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
4526     aDA.append( " 0 0 Td\nET\nQ\n" );
4527     writeBuffer( aDA );
4528     setFillColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4529     setLineColor( COL_TRANSPARENT );
4530     aCheckRect.AdjustLeft(3*nDelta );
4531     aCheckRect.AdjustTop(3*nDelta );
4532     aCheckRect.AdjustBottom( -(3*nDelta) );
4533     aCheckRect.AdjustRight( -(3*nDelta) );
4534     drawEllipse( aCheckRect );
4535     writeBuffer( "\nEMC\n" );
4536     endRedirect();
4537 
4538     pop();
4539     rBox.m_aAppearances[ "N"_ostr ][ "Yes"_ostr ] = pCheckStream;
4540 
4541     SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
4542     beginRedirect( pUncheckStream, aCheckRect );
4543     writeBuffer( "/Tx BMC\nEMC\n" );
4544     endRedirect();
4545     rBox.m_aAppearances[ "N"_ostr ][ "Off"_ostr ] = pUncheckStream;
4546 }
4547 
emitAppearances(PDFWidget & rWidget,OStringBuffer & rAnnotDict)4548 bool PDFWriterImpl::emitAppearances( PDFWidget& rWidget, OStringBuffer& rAnnotDict )
4549 {
4550     // TODO: check and insert default streams
4551     OString aStandardAppearance;
4552     switch( rWidget.m_eType )
4553     {
4554         case PDFWriter::CheckBox:
4555             aStandardAppearance = OUStringToOString( rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US );
4556             break;
4557         default:
4558             break;
4559     }
4560 
4561     if( !rWidget.m_aAppearances.empty() )
4562     {
4563         rAnnotDict.append( "/AP<<\n" );
4564         for (auto & dict_item : rWidget.m_aAppearances)
4565         {
4566             rAnnotDict.append( "/" );
4567             rAnnotDict.append( dict_item.first );
4568             bool bUseSubDict = (dict_item.second.size() > 1);
4569 
4570             // PDF/A requires sub-dicts for /FT/Btn objects (clause
4571             // 6.3.3)
4572             if (m_nPDFA_Version > 0)
4573             {
4574                 if( rWidget.m_eType == PDFWriter::RadioButton ||
4575                     rWidget.m_eType == PDFWriter::CheckBox ||
4576                     rWidget.m_eType == PDFWriter::PushButton )
4577                 {
4578                     bUseSubDict = true;
4579                 }
4580             }
4581 
4582             rAnnotDict.append( bUseSubDict ? "<<" : " " );
4583 
4584             for (auto const& stream_item : dict_item.second)
4585             {
4586                 SvMemoryStream* pAppearanceStream = stream_item.second;
4587                 dict_item.second[ stream_item.first ] = nullptr;
4588 
4589                 bool bDeflate = compressStream( pAppearanceStream );
4590 
4591                 sal_Int64 nStreamLen = pAppearanceStream->TellEnd();
4592                 pAppearanceStream->Seek( STREAM_SEEK_TO_BEGIN );
4593                 sal_Int32 nObject = createObject();
4594                 if (!updateObject(nObject))
4595                     return false;
4596                 if (g_bDebugDisableCompression)
4597                 {
4598                     emitComment( "PDFWriterImpl::emitAppearances" );
4599                 }
4600                 OStringBuffer aLine;
4601                 aLine.append( nObject );
4602 
4603                 aLine.append( " 0 obj\n"
4604                               "<</Type/XObject\n"
4605                               "/Subtype/Form\n"
4606                               "/BBox[0 0 " );
4607                 appendFixedInt( rWidget.m_aRect.GetWidth()-1, aLine );
4608                 aLine.append( " " );
4609                 appendFixedInt( rWidget.m_aRect.GetHeight()-1, aLine );
4610                 aLine.append( "]\n"
4611                               "/Resources " );
4612                 aLine.append( getResourceDictObj() );
4613                 aLine.append( " 0 R\n"
4614                               "/Length " );
4615                 aLine.append( nStreamLen );
4616                 aLine.append( "\n" );
4617                 if( bDeflate )
4618                     aLine.append( "/Filter/FlateDecode\n" );
4619                 aLine.append( ">>\nstream\n" );
4620                 if (!writeBuffer(aLine)) return false;
4621                 checkAndEnableStreamEncryption( nObject );
4622                 if (!writeBufferBytes( pAppearanceStream->GetData(), nStreamLen)) return false;
4623                 disableStreamEncryption();
4624                 if (!writeBuffer("\nendstream\nendobj\n\n")) return false;
4625 
4626                 if( bUseSubDict )
4627                 {
4628                     rAnnotDict.append( " /" );
4629                     rAnnotDict.append( stream_item.first );
4630                     rAnnotDict.append( " " );
4631                 }
4632                 rAnnotDict.append( nObject );
4633                 rAnnotDict.append( " 0 R" );
4634 
4635                 delete pAppearanceStream;
4636             }
4637 
4638             rAnnotDict.append( bUseSubDict ? ">>\n" : "\n" );
4639         }
4640         rAnnotDict.append( ">>\n" );
4641         if( !aStandardAppearance.isEmpty() )
4642         {
4643             rAnnotDict.append( "/AS /" );
4644             rAnnotDict.append( aStandardAppearance );
4645             rAnnotDict.append( "\n" );
4646         }
4647     }
4648 
4649     return true;
4650 }
4651 
emitWidgetAnnotations()4652 bool PDFWriterImpl::emitWidgetAnnotations()
4653 {
4654     ensureUniqueRadioOnValues();
4655 
4656     int nAnnots = m_aWidgets.size();
4657     for( int a = 0; a < nAnnots; a++ )
4658     {
4659         PDFWidget& rWidget = m_aWidgets[a];
4660 
4661         if( rWidget.m_eType == PDFWriter::CheckBox )
4662         {
4663             if ( !rWidget.m_aOnValue.isEmpty() )
4664             {
4665                 auto app_it = rWidget.m_aAppearances.find( "N"_ostr );
4666                 if( app_it != rWidget.m_aAppearances.end() )
4667                 {
4668                     auto stream_it = app_it->second.find( "Yes"_ostr );
4669                     if( stream_it != app_it->second.end() )
4670                     {
4671                         SvMemoryStream* pStream = stream_it->second;
4672                         app_it->second.erase( stream_it );
4673                         OStringBuffer aBuf( rWidget.m_aOnValue.getLength()*2 );
4674                         COSWriter::appendName( rWidget.m_aOnValue, aBuf );
4675                         (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
4676                     }
4677                     else
4678                         SAL_INFO("vcl.pdfwriter", "error: CheckBox without \"Yes\" stream" );
4679                 }
4680             }
4681 
4682             if ( !rWidget.m_aOffValue.isEmpty() )
4683             {
4684                 auto app_it = rWidget.m_aAppearances.find( "N"_ostr );
4685                 if( app_it != rWidget.m_aAppearances.end() )
4686                 {
4687                     auto stream_it = app_it->second.find( "Off"_ostr );
4688                     if( stream_it != app_it->second.end() )
4689                     {
4690                         SvMemoryStream* pStream = stream_it->second;
4691                         app_it->second.erase( stream_it );
4692                         OStringBuffer aBuf( rWidget.m_aOffValue.getLength()*2 );
4693                         COSWriter::appendName( rWidget.m_aOffValue, aBuf );
4694                         (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
4695                     }
4696                     else
4697                         SAL_INFO("vcl.pdfwriter", "error: CheckBox without \"Off\" stream" );
4698                 }
4699             }
4700         }
4701 
4702         OStringBuffer aLine( 1024 );
4703         COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
4704         OStringBuffer aValue( 256 );
4705         COSWriter aValueWriter(aValue, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
4706         aLine.append( rWidget.m_nObject );
4707         aLine.append( " 0 obj\n"
4708                       "<<" );
4709         if( rWidget.m_eType != PDFWriter::Hierarchy )
4710         {
4711             // emit widget annotation only for terminal fields
4712             if( rWidget.m_aKids.empty() )
4713             {
4714                 int iRectMargin;
4715 
4716                 aLine.append( "/Type/Annot/Subtype/Widget/F " );
4717 
4718                 if (rWidget.m_eType == PDFWriter::Signature)
4719                 {
4720                     aLine.append( "132\n" ); // Print & Locked
4721                     iRectMargin = 0;
4722                 }
4723                 else
4724                 {
4725                     aLine.append( "4\n" );
4726                     iRectMargin = 1;
4727                 }
4728 
4729                 if (-1 != rWidget.m_nStructParent)
4730                 {
4731                     aLine.append("/StructParent ");
4732                     aLine.append(rWidget.m_nStructParent);
4733                     aLine.append("\n");
4734                 }
4735 
4736                 aLine.append("/Rect[" );
4737                 appendFixedInt( rWidget.m_aRect.Left()-iRectMargin, aLine );
4738                 aLine.append( ' ' );
4739                 appendFixedInt( rWidget.m_aRect.Top()+iRectMargin, aLine );
4740                 aLine.append( ' ' );
4741                 appendFixedInt( rWidget.m_aRect.Right()+iRectMargin, aLine );
4742                 aLine.append( ' ' );
4743                 appendFixedInt( rWidget.m_aRect.Bottom()-iRectMargin, aLine );
4744                 aLine.append( "]\n" );
4745             }
4746             aLine.append( "/FT/" );
4747             switch( rWidget.m_eType )
4748             {
4749                 case PDFWriter::RadioButton:
4750                 case PDFWriter::CheckBox:
4751                     // for radio buttons only the RadioButton field, not the
4752                     // CheckBox children should have a value, else acrobat reader
4753                     // does not always check the right button
4754                     // of course real check boxes (not belonging to a radio group)
4755                     // need their values, too
4756                     if( rWidget.m_eType == PDFWriter::RadioButton || rWidget.m_nRadioGroup < 0 )
4757                     {
4758                         aValue.append( "/" );
4759                         // check for radio group with all buttons unpressed
4760                         if( rWidget.m_aValue.isEmpty() )
4761                             aValue.append( "Off" );
4762                         else
4763                             COSWriter::appendName( rWidget.m_aValue, aValue );
4764                     }
4765                     [[fallthrough]];
4766                 case PDFWriter::PushButton:
4767                     aLine.append( "Btn" );
4768                     break;
4769                 case PDFWriter::ListBox:
4770                     if( rWidget.m_nFlags & 0x200000 ) // multiselect
4771                     {
4772                         aValue.append( "[" );
4773                         for( size_t i = 0; i < rWidget.m_aSelectedEntries.size(); i++ )
4774                         {
4775                             sal_Int32 nEntry = rWidget.m_aSelectedEntries[i];
4776                             if( nEntry >= 0
4777                                 && o3tl::make_unsigned(nEntry) < rWidget.m_aListEntries.size() )
4778                             {
4779                                 aValueWriter.writeUnicodeEncrypt(rWidget.m_aListEntries[ nEntry ], rWidget.m_nObject);
4780                             }
4781                         }
4782                         aValue.append( "]" );
4783                     }
4784                     else if( !rWidget.m_aSelectedEntries.empty() &&
4785                              rWidget.m_aSelectedEntries[0] >= 0 &&
4786                              o3tl::make_unsigned(rWidget.m_aSelectedEntries[0]) < rWidget.m_aListEntries.size() )
4787                     {
4788                         aValueWriter.writeUnicodeEncrypt(rWidget.m_aListEntries[ rWidget.m_aSelectedEntries[0] ], rWidget.m_nObject);
4789                     }
4790                     else
4791                         aValueWriter.writeUnicodeEncrypt( OUString(), rWidget.m_nObject);
4792                     aLine.append( "Ch" );
4793                     break;
4794                 case PDFWriter::ComboBox:
4795                     aValueWriter.writeUnicodeEncrypt( rWidget.m_aValue, rWidget.m_nObject);
4796                     aLine.append( "Ch" );
4797                     break;
4798                 case PDFWriter::Edit:
4799                     aLine.append( "Tx" );
4800                     aValueWriter.writeUnicodeEncrypt( rWidget.m_aValue, rWidget.m_nObject);
4801                     break;
4802                 case PDFWriter::Signature:
4803                     aLine.append( "Sig" );
4804                     aValue.append(OUStringToOString(rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US));
4805                     break;
4806                 case PDFWriter::Hierarchy: // make the compiler happy
4807                     break;
4808             }
4809             aLine.append( "\n" );
4810             aLine.append( "/P " );
4811             aLine.append( m_aPages[ rWidget.m_nPage ].m_nPageObject );
4812             aLine.append( " 0 R\n" );
4813         }
4814         if( rWidget.m_nParent )
4815         {
4816             aLine.append( "/Parent " );
4817             aLine.append( rWidget.m_nParent );
4818             aLine.append( " 0 R\n" );
4819         }
4820         if( !rWidget.m_aKids.empty() )
4821         {
4822             aLine.append( "/Kids[" );
4823             for( size_t i = 0; i < rWidget.m_aKids.size(); i++ )
4824             {
4825                 aLine.append( rWidget.m_aKids[i] );
4826                 aLine.append( " 0 R" );
4827                 aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
4828             }
4829             aLine.append( "]\n" );
4830         }
4831         if( !rWidget.m_aName.isEmpty() )
4832         {
4833             aLine.append( "/T" );
4834             aWriter.writeLiteralEncrypt(rWidget.m_aName, rWidget.m_nObject);
4835             aLine.append( "\n" );
4836         }
4837         if (!rWidget.m_aDescription.isEmpty())
4838         {
4839             // the alternate field name should be unicode able since it is
4840             // supposed to be used in UI
4841             aLine.append( "/TU" );
4842             aWriter.writeUnicodeEncrypt(rWidget.m_aDescription, rWidget.m_nObject);
4843             aLine.append( "\n" );
4844         }
4845 
4846         if( rWidget.m_nFlags )
4847         {
4848             aLine.append( "/Ff " );
4849             aLine.append( rWidget.m_nFlags );
4850             aLine.append( "\n" );
4851         }
4852         if( !aValue.isEmpty() )
4853         {
4854             OString aVal = aValue.makeStringAndClear();
4855             aLine.append( "/V " );
4856             aLine.append( aVal );
4857             aLine.append( "\n"
4858                           "/DV " );
4859             aLine.append( aVal );
4860             aLine.append( "\n" );
4861         }
4862         if( rWidget.m_eType == PDFWriter::ListBox || rWidget.m_eType == PDFWriter::ComboBox )
4863         {
4864             sal_Int32 nTI = -1;
4865             aLine.append( "/Opt[\n" );
4866             sal_Int32 i = 0;
4867             for (auto const& entry : rWidget.m_aListEntries)
4868             {
4869                 aWriter.writeUnicodeEncrypt(entry, rWidget.m_nObject);
4870                 aLine.append( "\n" );
4871                 if( entry == rWidget.m_aValue )
4872                     nTI = i;
4873                 ++i;
4874             }
4875             aLine.append( "]\n" );
4876             if( nTI > 0 )
4877             {
4878                 aLine.append( "/TI " );
4879                 aLine.append( nTI );
4880                 aLine.append( "\n" );
4881                 if( rWidget.m_nFlags & 0x200000 ) // Multiselect
4882                 {
4883                     aLine.append( "/I [" );
4884                     aLine.append( nTI );
4885                     aLine.append( "]\n" );
4886                 }
4887             }
4888         }
4889         if( rWidget.m_eType == PDFWriter::Edit )
4890         {
4891             if ( rWidget.m_nMaxLen > 0 )
4892             {
4893                 aLine.append( "/MaxLen " );
4894                 aLine.append( rWidget.m_nMaxLen );
4895                 aLine.append( "\n" );
4896             }
4897 
4898             if ( rWidget.m_nFormat == PDFWriter::Number )
4899             {
4900                 OString aHexText;
4901 
4902                 if ( !rWidget.m_aCurrencySymbol.isEmpty() )
4903                 {
4904                     // Get the hexadecimal code
4905                     sal_UCS4 cChar = rWidget.m_aCurrencySymbol.iterateCodePoints(&o3tl::temporary(sal_Int32(1)), -1);
4906                     aHexText = "\\\\u" + OString::number(cChar, 16);
4907                 }
4908 
4909                 aLine.append("/AA<<\n");
4910                 aLine.append("/F<</JS(AFNumber_Format\\(");
4911                 aLine.append(OString::number(rWidget.m_nDecimalAccuracy));
4912                 aLine.append(", 0, 0, 0, \"");
4913                 aLine.append( aHexText );
4914                 aLine.append("\",");
4915                 aLine.append(OString::boolean(rWidget.m_bPrependCurrencySymbol));
4916                 aLine.append("\\);)");
4917                 aLine.append("/S/JavaScript>>\n");
4918                 aLine.append("/K<</JS(AFNumber_Keystroke\\(");
4919                 aLine.append(OString::number(rWidget.m_nDecimalAccuracy));
4920                 aLine.append(", 0, 0, 0, \"");
4921                 aLine.append( aHexText );
4922                 aLine.append("\",");
4923                 aLine.append(OString::boolean(rWidget.m_bPrependCurrencySymbol));
4924                 aLine.append("\\);)");
4925                 aLine.append("/S/JavaScript>>\n");
4926                 aLine.append(">>\n");
4927             }
4928             else if ( rWidget.m_nFormat == PDFWriter::Time )
4929             {
4930                 aLine.append("/AA<<\n");
4931                 aLine.append("/F<</JS(AFTime_FormatEx\\(\"");
4932                 aLine.append(OUStringToOString(rWidget.m_aTimeFormat, RTL_TEXTENCODING_ASCII_US));
4933                 aLine.append("\"\\);)");
4934                 aLine.append("/S/JavaScript>>\n");
4935                 aLine.append("/K<</JS(AFTime_KeystrokeEx\\(\"");
4936                 aLine.append(OUStringToOString(rWidget.m_aTimeFormat, RTL_TEXTENCODING_ASCII_US));
4937                 aLine.append("\"\\);)");
4938                 aLine.append("/S/JavaScript>>\n");
4939                 aLine.append(">>\n");
4940             }
4941             else if ( rWidget.m_nFormat == PDFWriter::Date )
4942             {
4943                 aLine.append("/AA<<\n");
4944                 aLine.append("/F<</JS(AFDate_FormatEx\\(\"");
4945                 aLine.append(OUStringToOString(rWidget.m_aDateFormat, RTL_TEXTENCODING_ASCII_US));
4946                 aLine.append("\"\\);)");
4947                 aLine.append("/S/JavaScript>>\n");
4948                 aLine.append("/K<</JS(AFDate_KeystrokeEx\\(\"");
4949                 aLine.append(OUStringToOString(rWidget.m_aDateFormat, RTL_TEXTENCODING_ASCII_US));
4950                 aLine.append("\"\\);)");
4951                 aLine.append("/S/JavaScript>>\n");
4952                 aLine.append(">>\n");
4953             }
4954         }
4955         if( rWidget.m_eType == PDFWriter::PushButton )
4956         {
4957             if (!m_bIsPDF_A1)
4958             {
4959                 OStringBuffer aDest;
4960                 if( rWidget.m_nDest != -1 && appendDest( m_aDestinationIdTranslation[ rWidget.m_nDest ], aDest ) )
4961                 {
4962                     aLine.append( "/AA<</D<</Type/Action/S/GoTo/D " );
4963                     aLine.append( aDest );
4964                     aLine.append( ">>>>\n" );
4965                 }
4966                 else if( rWidget.m_aListEntries.empty() )
4967                 {
4968                     if( !m_bIsPDF_A2 && !m_bIsPDF_A3 )
4969                     {
4970                         // create a reset form action
4971                         aLine.append( "/AA<</D<</Type/Action/S/ResetForm>>>>\n" );
4972                     }
4973                 }
4974                 else if( rWidget.m_bSubmit )
4975                 {
4976                     // create a submit form action
4977                     aLine.append( "/AA<</D<</Type/Action/S/SubmitForm/F" );
4978                     aWriter.writeLiteralEncrypt(rWidget.m_aListEntries.front(), rWidget.m_nObject, osl_getThreadTextEncoding());
4979                     aLine.append( "/Flags " );
4980 
4981                     sal_Int32 nFlags = 0;
4982                     switch( m_aContext.SubmitFormat )
4983                     {
4984                     case PDFWriter::HTML:
4985                         nFlags |= 4;
4986                         break;
4987                     case PDFWriter::XML:
4988                         nFlags |= 32;
4989                         break;
4990                     case PDFWriter::PDF:
4991                         nFlags |= 256;
4992                         break;
4993                     case PDFWriter::FDF:
4994                     default:
4995                         break;
4996                     }
4997                     if( rWidget.m_bSubmitGet )
4998                         nFlags |= 8;
4999                     aLine.append( nFlags );
5000                     aLine.append( ">>>>\n" );
5001                 }
5002                 else
5003                 {
5004                     // create a URI action
5005                     aLine.append( "/AA<</D<</Type/Action/S/URI/URI(" );
5006                     aLine.append( OUStringToOString( rWidget.m_aListEntries.front(), RTL_TEXTENCODING_ASCII_US ) );
5007                     aLine.append( ")>>>>\n" );
5008                 }
5009             }
5010             else
5011                 m_aErrors.insert( PDFWriter::Warning_FormAction_Omitted_PDFA );
5012         }
5013         if( !rWidget.m_aDAString.isEmpty() )
5014         {
5015             if( !rWidget.m_aDRDict.isEmpty() )
5016             {
5017                 aLine.append( "/DR<<" );
5018                 aLine.append( rWidget.m_aDRDict );
5019                 aLine.append( ">>\n" );
5020             }
5021             else
5022             {
5023                 aLine.append( "/DR<</Font<<" );
5024                 appendBuildinFontsToDict( aLine );
5025                 aLine.append( ">>>>\n" );
5026             }
5027             aLine.append( "/DA" );
5028             aWriter.writeLiteralEncrypt(rWidget.m_aDAString, rWidget.m_nObject);
5029             aLine.append( "\n" );
5030             if( rWidget.m_nTextStyle & DrawTextFlags::Center )
5031                 aLine.append( "/Q 1\n" );
5032             else if( rWidget.m_nTextStyle & DrawTextFlags::Right )
5033                 aLine.append( "/Q 2\n" );
5034         }
5035         // appearance characteristics for terminal fields
5036         // which are supposed to have an appearance constructed
5037         // by the viewer application
5038         if( !rWidget.m_aMKDict.isEmpty() )
5039         {
5040             aLine.append( "/MK<<" );
5041             aLine.append( rWidget.m_aMKDict );
5042             //add the CA string, encrypting it
5043             aWriter.writeLiteralEncrypt(rWidget.m_aMKDictCAString, rWidget.m_nObject);
5044             aLine.append( ">>\n" );
5045         }
5046 
5047         if (!emitAppearances(rWidget, aLine)) return false;
5048 
5049         aLine.append( ">>\n"
5050                       "endobj\n\n" );
5051         if (!updateObject(rWidget.m_nObject)) return false;
5052         if (!writeBuffer(aLine)) return false;
5053     }
5054     return true;
5055 }
5056 
emitAnnotations()5057 bool PDFWriterImpl::emitAnnotations()
5058 {
5059     if( m_aPages.empty() )
5060         return false;
5061 
5062     if (!emitLinkAnnotations()) return false;
5063     if (!emitScreenAnnotations()) return false;
5064     if (!emitNoteAnnotations()) return false;
5065     if (!emitWidgetAnnotations()) return false;
5066 
5067     return true;
5068 }
5069 
5070 class PDFStreamIf : public cppu::WeakImplHelper< css::io::XOutputStream >
5071 {
5072     VclPtr<PDFWriterImpl>  m_pWriter;
5073     bool            m_bWrite;
5074     public:
PDFStreamIf(PDFWriterImpl * pWriter)5075     explicit PDFStreamIf( PDFWriterImpl* pWriter ) : m_pWriter( pWriter ), m_bWrite( true ) {}
5076 
writeBytes(const css::uno::Sequence<sal_Int8> & aData)5077     virtual void SAL_CALL writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override
5078     {
5079         if( m_bWrite && aData.hasElements() )
5080         {
5081             sal_Int32 nBytes = aData.getLength();
5082             (void)m_pWriter->writeBufferBytes( aData.getConstArray(), nBytes );
5083         }
5084     }
flush()5085     virtual void SAL_CALL flush() override {}
closeOutput()5086     virtual void SAL_CALL closeOutput() override
5087     {
5088         m_bWrite = false;
5089     }
5090 };
5091 
emitEmbeddedFiles()5092 bool PDFWriterImpl::emitEmbeddedFiles()
5093 {
5094     for (auto& rEmbeddedFile : m_aEmbeddedFiles)
5095     {
5096         if (!updateObject(rEmbeddedFile.m_nObject))
5097             continue;
5098 
5099         sal_Int32 nSizeObject = createObject();
5100         sal_Int32 nParamsObject = createObject();
5101 
5102         OStringBuffer aLine;
5103         aLine.append(rEmbeddedFile.m_nObject);
5104         aLine.append(" 0 obj\n");
5105         aLine.append("<< /Type /EmbeddedFile");
5106         if (!rEmbeddedFile.m_aSubType.isEmpty())
5107         {
5108             aLine.append("/Subtype /");
5109             COSWriter::appendName(rEmbeddedFile.m_aSubType, aLine);
5110         }
5111         aLine.append(" /Length ");
5112         appendObjectReference(nSizeObject, aLine);
5113         aLine.append(" /Params ");
5114         appendObjectReference(nParamsObject, aLine);
5115         aLine.append(">>\nstream\n");
5116         if (!writeBuffer(aLine)) return false;
5117         aLine.setLength(0);
5118 
5119         sal_Int64 nSize{};
5120         if (!rEmbeddedFile.m_aDataContainer.isEmpty())
5121         {
5122             nSize = rEmbeddedFile.m_aDataContainer.getSize();
5123             checkAndEnableStreamEncryption(rEmbeddedFile.m_nObject);
5124             if (!writeBufferBytes(rEmbeddedFile.m_aDataContainer.getData(), rEmbeddedFile.m_aDataContainer.getSize()))
5125                 return false;
5126             disableStreamEncryption();
5127         }
5128         else if (rEmbeddedFile.m_pStream)
5129         {
5130             checkAndEnableStreamEncryption(rEmbeddedFile.m_nObject);
5131             sal_uInt64 nBegin = getCurrentFilePosition();
5132             css::uno::Reference<css::io::XOutputStream> xStream(new PDFStreamIf(this));
5133             rEmbeddedFile.m_pStream->write(xStream);
5134             rEmbeddedFile.m_pStream.reset();
5135             xStream.clear();
5136             nSize = sal_Int64(getCurrentFilePosition() - nBegin);
5137             disableStreamEncryption();
5138         }
5139         aLine.append("\nendstream\nendobj\n\n");
5140         if (!writeBuffer(aLine)) return false;
5141         aLine.setLength(0);
5142 
5143         if (!updateObject(nSizeObject))
5144             return false;
5145         aLine.append(nSizeObject);
5146         aLine.append(" 0 obj\n");
5147         aLine.append(nSize);
5148         aLine.append("\nendobj\n\n");
5149         if (!writeBuffer(aLine))
5150             return false;
5151         aLine.setLength(0);
5152 
5153         if (!updateObject(nParamsObject))
5154             return false;
5155         aLine.append(nParamsObject);
5156         aLine.append(" 0 obj\n");
5157         aLine.append("<<");
5158         aLine.append("/Size ");
5159         aLine.append(nSize);
5160         aLine.append(">>");
5161         aLine.append("\nendobj\n\n");
5162         if (!writeBuffer(aLine))
5163             return false;
5164     }
5165     return true;
5166 }
5167 
emitCatalog()5168 bool PDFWriterImpl::emitCatalog()
5169 {
5170     // build page tree
5171     // currently there is only one node that contains all leaves
5172 
5173     // first create a page tree node id
5174     sal_Int32 nTreeNode = createObject();
5175 
5176     // emit global resource dictionary (page emit needs it)
5177     if (!emitResources()) return false;
5178 
5179     // emit all pages
5180     for (auto & page : m_aPages)
5181         if( ! page.emit( nTreeNode ) )
5182             return false;
5183 
5184     sal_Int32 nNamedDestinationsDictionary = emitNamedDestinations();
5185 
5186     sal_Int32 nOutlineDict = emitOutline();
5187 
5188     // emit Output intent
5189     sal_Int32 nOutputIntentObject = emitOutputIntent();
5190 
5191     // emit metadata
5192     sal_Int32 nMetadataObject = emitDocumentMetadata();
5193 
5194     emitNamespaces();
5195 
5196     sal_Int32 nStructureDict = 0;
5197     if(m_aStructure.size() > 1)
5198     {
5199         removePlaceholderSE(m_aStructure, m_aStructure[0]);
5200         // check if dummy structure containers are needed
5201         addInternalStructureContainer(m_aStructure[0]);
5202         nStructureDict = m_aStructure[0].m_nObject = createObject();
5203         emitStructure( m_aStructure[ 0 ] );
5204     }
5205 
5206     // adjust tree node file offset
5207     if( ! updateObject( nTreeNode ) )
5208         return false;
5209 
5210     // emit tree node
5211     OStringBuffer aLine( 2048 );
5212     COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
5213     aLine.append( nTreeNode );
5214     aLine.append( " 0 obj\n" );
5215     aLine.append( "<</Type/Pages\n" );
5216     aLine.append( "/Resources " );
5217     aLine.append( getResourceDictObj() );
5218     aLine.append( " 0 R\n" );
5219 
5220     if( m_aPages.empty() ) // sanity check, this should not happen
5221         aLine.append( "/MediaBox[0 0 595 842]\n" ); // default A4 size in pt
5222 
5223     aLine.append("/Kids[ ");
5224     unsigned int i = 0;
5225     for (const auto & page : m_aPages)
5226     {
5227         aLine.append( page.m_nPageObject );
5228         aLine.append( " 0 R" );
5229         aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
5230         ++i;
5231     }
5232     aLine.append( "]\n"
5233                   "/Count " );
5234     aLine.append( static_cast<sal_Int32>(m_aPages.size()) );
5235     aLine.append( ">>\n"
5236                   "endobj\n\n" );
5237     if (!writeBuffer(aLine)) return false;
5238 
5239     // emit annotation objects
5240     if (!emitAnnotations()) return false;
5241     if (!emitEmbeddedFiles()) return false;
5242 
5243     // emit attached files
5244     for (auto & rAttachedFile : m_aDocumentAttachedFiles)
5245     {
5246         if (!updateObject(rAttachedFile.mnObjectId))
5247             return false;
5248         aLine.setLength(0);
5249 
5250         aWriter.startObject(rAttachedFile.mnObjectId);
5251         aWriter.startDict();
5252         aWriter.write("/Type", "/Filespec");
5253 
5254         // Add associated files relationship (since PDF 2.0)
5255         if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0 || m_nPDFA_Version == 3)
5256         {
5257             aWriter.write("/AFRelationship", "/Source");
5258         }
5259 
5260         aWriter.writeKeyAndUnicodeEncrypt("/F", rAttachedFile.maFilename, rAttachedFile.mnObjectId);
5261         if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
5262         {
5263             aWriter.writeKeyAndUnicodeEncrypt("/UF", rAttachedFile.maFilename, rAttachedFile.mnObjectId);
5264         }
5265         if (!rAttachedFile.maDescription.isEmpty())
5266         {
5267             aWriter.writeKeyAndUnicodeEncrypt("/Desc", rAttachedFile.maDescription, rAttachedFile.mnObjectId);
5268         }
5269         aLine.append("/EF");
5270         aWriter.startDict();
5271         aWriter.writeKeyAndReference("/F", rAttachedFile.mnEmbeddedFileObjectId);
5272         aWriter.endDict();
5273         aWriter.endDict();
5274         aWriter.endObject();
5275         if (!writeBuffer(aLine)) return false;
5276     }
5277 
5278     // emit Catalog
5279     m_nCatalogObject = createObject();
5280     if( ! updateObject( m_nCatalogObject ) )
5281         return false;
5282     aLine.setLength( 0 );
5283     aLine.append( m_nCatalogObject );
5284     aLine.append( " 0 obj\n"
5285                   "<</Type/Catalog/Pages " );
5286     aLine.append( nTreeNode );
5287     aLine.append( " 0 R\n" );
5288 
5289     // check if there are named destinations to emit (root must be inside the catalog)
5290     if( nNamedDestinationsDictionary )
5291     {
5292         aLine.append("/Dests ");
5293         aLine.append( nNamedDestinationsDictionary );
5294         aLine.append( " 0 R\n" );
5295     }
5296 
5297     if (!m_aDocumentAttachedFiles.empty())
5298     {
5299         // Write the associated files catalog entry (since PDF 2.0)
5300         if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0 || m_nPDFA_Version == 3)
5301         {
5302             aLine.append("/AF");
5303             aLine.append("[");
5304             for (auto & rAttachedFile : m_aDocumentAttachedFiles)
5305                 aWriter.writeReference(rAttachedFile.mnObjectId);
5306             aLine.append("]");
5307         }
5308 
5309         aWriter.startDictWithKey("/Names");
5310         aWriter.startDictWithKey("/EmbeddedFiles");
5311         aLine.append("/Names [");
5312         for (auto & rAttachedFile : m_aDocumentAttachedFiles)
5313         {
5314             aWriter.writeUnicodeEncrypt(rAttachedFile.maFilename, m_nCatalogObject);
5315             aWriter.writeReference(rAttachedFile.mnObjectId);
5316         }
5317         aLine.append("]");
5318         aWriter.endDict();
5319         aWriter.endDict();
5320     }
5321 
5322     if( m_aContext.PageLayout != PDFWriter::DefaultLayout )
5323         switch(  m_aContext.PageLayout )
5324         {
5325         default :
5326         case  PDFWriter::SinglePage :
5327             aLine.append( "/PageLayout/SinglePage\n" );
5328             break;
5329         case  PDFWriter::Continuous :
5330             aLine.append( "/PageLayout/OneColumn\n" );
5331             break;
5332         case  PDFWriter::ContinuousFacing :
5333             // the flag m_aContext.FirstPageLeft below is used to set the page on the left side
5334             aLine.append( "/PageLayout/TwoColumnRight\n" );//odd page on the right side
5335             break;
5336         }
5337     if( m_aContext.PDFDocumentMode != PDFWriter::ModeDefault && !m_aContext.OpenInFullScreenMode )
5338         switch(  m_aContext.PDFDocumentMode )
5339         {
5340         default :
5341             aLine.append( "/PageMode/UseNone\n" );
5342             break;
5343         case PDFWriter::UseOutlines :
5344             aLine.append( "/PageMode/UseOutlines\n" ); //document is opened with outline pane open
5345             break;
5346         case PDFWriter::UseThumbs :
5347             aLine.append( "/PageMode/UseThumbs\n" ); //document is opened with thumbnails pane open
5348             break;
5349         }
5350     else if( m_aContext.OpenInFullScreenMode )
5351         aLine.append( "/PageMode/FullScreen\n" ); //document is opened full screen
5352 
5353     OStringBuffer aInitPageRef;
5354     if( m_aContext.InitialPage >= 0 && o3tl::make_unsigned(m_aContext.InitialPage) < m_aPages.size() )
5355     {
5356         aInitPageRef.append( m_aPages[m_aContext.InitialPage].m_nPageObject );
5357         aInitPageRef.append( " 0 R" );
5358     }
5359     else
5360         aInitPageRef.append( "0" );
5361 
5362     switch( m_aContext.PDFDocumentAction )
5363     {
5364     case PDFWriter::ActionDefault :     //do nothing, this is the Acrobat default
5365     default:
5366         if ( aInitPageRef.getLength() > 1 && m_aContext.InitialPage > 0 )
5367         {
5368             aLine.append( "/OpenAction[" );
5369             aLine.append( aInitPageRef );
5370             aLine.append( " /XYZ null null 0]\n" );
5371         }
5372         break;
5373     case PDFWriter::FitInWindow :
5374         aLine.append( "/OpenAction[" );
5375         aLine.append( aInitPageRef );
5376         aLine.append( " /Fit]\n" ); //Open fit page
5377         break;
5378     case PDFWriter::FitWidth :
5379         aLine.append( "/OpenAction[" );
5380         aLine.append( aInitPageRef );
5381         aLine.append( " /FitH null]\n" ); //Open fit width
5382         break;
5383     case PDFWriter::FitVisible :
5384         aLine.append( "/OpenAction[" );
5385         aLine.append( aInitPageRef );
5386         aLine.append( " /FitBH null]\n" ); //Open fit visible
5387         break;
5388     case PDFWriter::ActionZoom :
5389         aLine.append( "/OpenAction[" );
5390         aLine.append( aInitPageRef );
5391         aLine.append( " /XYZ null null " );
5392         if( m_aContext.Zoom >= 50 && m_aContext.Zoom <= 1600 )
5393             aLine.append( static_cast<double>(m_aContext.Zoom)/100.0 );
5394         else
5395             aLine.append( "0" );
5396         aLine.append( "]\n" );
5397         break;
5398     }
5399 
5400     // viewer preferences, if we had some, then emit
5401     if (m_aContext.HideViewerToolbar ||
5402         (!m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle) ||
5403         m_aContext.HideViewerMenubar ||
5404         m_aContext.HideViewerWindowControls || m_aContext.FitWindow ||
5405         m_aContext.CenterWindow || (m_aContext.FirstPageLeft  &&  m_aContext.PageLayout == PDFWriter::ContinuousFacing ) ||
5406         m_aContext.OpenInFullScreenMode ||
5407         m_bIsPDF_UA)
5408     {
5409         aLine.append( "/ViewerPreferences<<" );
5410         if( m_aContext.HideViewerToolbar )
5411             aLine.append( "/HideToolbar true\n" );
5412         if( m_aContext.HideViewerMenubar )
5413             aLine.append( "/HideMenubar true\n" );
5414         if( m_aContext.HideViewerWindowControls )
5415             aLine.append( "/HideWindowUI true\n" );
5416         if( m_aContext.FitWindow )
5417             aLine.append( "/FitWindow true\n" );
5418         if( m_aContext.CenterWindow )
5419             aLine.append( "/CenterWindow true\n" );
5420         // PDF/UA-2 requires /DisplayDocTitle set to true
5421         if ((!m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle) || m_bIsPDF_UA)
5422             aLine.append( "/DisplayDocTitle true\n" );
5423         if( m_aContext.FirstPageLeft &&  m_aContext.PageLayout == PDFWriter::ContinuousFacing )
5424             aLine.append( "/Direction/R2L\n" );
5425         if( m_aContext.OpenInFullScreenMode )
5426             switch( m_aContext.PDFDocumentMode )
5427             {
5428             default :
5429             case PDFWriter::ModeDefault :
5430                 aLine.append( "/NonFullScreenPageMode/UseNone\n" );
5431                 break;
5432             case PDFWriter::UseOutlines :
5433                 aLine.append( "/NonFullScreenPageMode/UseOutlines\n" );
5434                 break;
5435             case PDFWriter::UseThumbs :
5436                 aLine.append( "/NonFullScreenPageMode/UseThumbs\n" );
5437                 break;
5438             }
5439         aLine.append( ">>\n" );
5440     }
5441 
5442     if( nOutlineDict )
5443     {
5444         aLine.append( "/Outlines " );
5445         aLine.append( nOutlineDict );
5446         aLine.append( " 0 R\n" );
5447     }
5448     if( nStructureDict )
5449     {
5450         aLine.append( "/StructTreeRoot " );
5451         aLine.append( nStructureDict );
5452         aLine.append( " 0 R\n" );
5453     }
5454     if( !m_aContext.DocumentLocale.Language.isEmpty() )
5455     {
5456         /* PDF allows only RFC 3066, see above in emitStructure(). */
5457         LanguageTag aLanguageTag( m_aContext.DocumentLocale);
5458         OUString aLanguage, aScript, aCountry;
5459         aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
5460         if (!aLanguage.isEmpty())
5461         {
5462             OUStringBuffer aLocBuf( 16 );
5463             aLocBuf.append( aLanguage );
5464             if( !aCountry.isEmpty() )
5465             {
5466                 aLocBuf.append( '-' );
5467                 aLocBuf.append( aCountry );
5468             }
5469             aLine.append( "/Lang" );
5470             aWriter.writeLiteralEncrypt(aLocBuf, m_nCatalogObject);
5471             aLine.append( "\n" );
5472         }
5473     }
5474     if (m_aContext.Tagged)
5475     {
5476         aLine.append( "/MarkInfo<</Marked true>>\n" );
5477     }
5478 
5479     if (!m_aWidgets.empty() || !m_aCopiedWidgets.empty())
5480     {
5481         aLine.append("/AcroForm");
5482         aLine.append("<<");
5483         aLine.append("/Fields");
5484         aLine.append("[ ");
5485 
5486         for (auto const& rWidget : m_aWidgets)
5487         {
5488             // output only root fields
5489             if (rWidget.m_nParent < 1)
5490             {
5491                 appendObjectReference(rWidget.m_nObject, aLine);
5492             }
5493         }
5494         // Add widgets that were copied from an external PDF
5495         for (auto const& rCopiedWidget : m_aCopiedWidgets)
5496         {
5497             appendObjectReference(rCopiedWidget.m_nObject, aLine);
5498         }
5499         aLine.append(" ]\n");
5500 
5501         bool bSigned = false;
5502 #if HAVE_FEATURE_NSS
5503         if (m_nSignatureObject != -1)
5504         {
5505             aLine.append("/SigFlags 3 ");
5506             bSigned = true;
5507         }
5508 #endif
5509         aLine.append("/DR<</Font ");
5510         appendObjectReference(getFontDictObject(), aLine);
5511         aLine.append(">>");
5512 
5513         // NeedAppearances must not be used if PDF is signed, PDF/A is used or
5514         // we have copied widgets (can't guarantee we have appearance streams in this case)
5515         if (m_nPDFA_Version == 0 && !bSigned && m_aCopiedWidgets.empty())
5516             aLine.append("/NeedAppearances true ");
5517 
5518         aLine.append(">>\n");
5519     }
5520 
5521     //check if there is a Metadata object
5522     if( nOutputIntentObject )
5523     {
5524         aLine.append("/OutputIntents[");
5525         aLine.append( nOutputIntentObject );
5526         aLine.append( " 0 R]" );
5527     }
5528 
5529     if( nMetadataObject )
5530     {
5531         aWriter.writeKeyAndReference("/Metadata", nMetadataObject);
5532     }
5533 
5534     aLine.append( ">>\n"
5535                   "endobj\n\n" );
5536     return writeBuffer( aLine );
5537 }
5538 
5539 #if HAVE_FEATURE_NSS
5540 
emitSignature()5541 bool PDFWriterImpl::emitSignature()
5542 {
5543     if( !updateObject( m_nSignatureObject ) )
5544         return false;
5545 
5546     OStringBuffer aLine( 0x5000 );
5547     COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
5548     aLine.append( m_nSignatureObject );
5549     aLine.append( " 0 obj\n" );
5550     aLine.append("<</Contents <" );
5551 
5552     sal_uInt64 nOffset = ~0U;
5553     if (osl::File::E_None != m_aFile.getPos(nOffset))
5554         return false;
5555 
5556     m_nSignatureContentOffset = nOffset + aLine.getLength();
5557 
5558     // reserve some space for the PKCS#7 object
5559     aLine.append(RepeatedChar('0', MAX_SIGNATURE_CONTENT_LENGTH)
5560                  + ">\n/Type/Sig/SubFilter/adbe.pkcs7.detached");
5561 
5562     if( !m_aContext.DocumentInfo.Author.isEmpty() )
5563     {
5564         aLine.append( "/Name" );
5565         aWriter.writeUnicodeEncrypt(m_aContext.DocumentInfo.Author, m_nSignatureObject);
5566     }
5567 
5568     aLine.append( " /M ");
5569     aWriter.writeLiteralEncrypt(m_aCreationDateString, m_nSignatureObject);
5570 
5571     aLine.append( " /ByteRange [ 0 ");
5572     aLine.append( m_nSignatureContentOffset - 1 );
5573     aLine.append( " " );
5574     aLine.append( m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1 );
5575     aLine.append( " " );
5576 
5577     m_nSignatureLastByteRangeNoOffset = nOffset + aLine.getLength();
5578 
5579     // mark the last ByteRange no and add some space. Now, we don't know
5580     // how many bytes we need for this ByteRange value
5581     // The real value will be overwritten in the finalizeSignature method
5582     aLine.append(RepeatedChar(' ', 100) + "  /Filter/Adobe.PPKMS");
5583 
5584     //emit reason, location and contactinfo
5585     if ( !m_aContext.SignReason.isEmpty() )
5586     {
5587         aLine.append("/Reason");
5588         aWriter.writeUnicodeEncrypt(m_aContext.SignReason, m_nSignatureObject);
5589     }
5590 
5591     if ( !m_aContext.SignLocation.isEmpty() )
5592     {
5593         aLine.append("/Location");
5594         aWriter.writeUnicodeEncrypt(m_aContext.SignLocation, m_nSignatureObject);
5595     }
5596 
5597     if ( !m_aContext.SignContact.isEmpty() )
5598     {
5599         aLine.append("/ContactInfo");
5600         aWriter.writeUnicodeEncrypt(m_aContext.SignContact, m_nSignatureObject);
5601     }
5602 
5603     aLine.append(" >>\nendobj\n\n" );
5604 
5605     return writeBuffer( aLine );
5606 }
5607 
finalizeSignature()5608 bool PDFWriterImpl::finalizeSignature()
5609 {
5610     if (!m_aContext.SignCertificate.is())
5611         return false;
5612 
5613     // 1- calculate last ByteRange value
5614     sal_uInt64 nOffset = ~0U;
5615     if (osl::File::E_None != m_aFile.getPos(nOffset))
5616         return false;
5617 
5618     sal_Int64 nLastByteRangeNo = nOffset - (m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1);
5619 
5620     // 2- overwrite the value to the m_nSignatureLastByteRangeNoOffset position
5621     sal_uInt64 nWritten = 0;
5622     if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureLastByteRangeNoOffset))
5623         return false;
5624     OString aByteRangeNo = OString::number( nLastByteRangeNo ) + " ]";
5625 
5626     if (m_aFile.write(aByteRangeNo.getStr(), aByteRangeNo.getLength(), nWritten) != osl::File::E_None)
5627     {
5628         (void)m_aFile.setPos(osl_Pos_Absolut, nOffset);
5629         return false;
5630     }
5631 
5632     // 3- create the PKCS#7 object using NSS
5633 
5634     // Prepare buffer and calculate PDF file digest
5635     if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, 0))
5636         return false;
5637 
5638     std::unique_ptr<char[]> buffer1(new char[m_nSignatureContentOffset + 1]);
5639     sal_uInt64 bytesRead1;
5640 
5641     //FIXME: Check if hash is calculated from the correct byterange
5642     if (osl::File::E_None != m_aFile.read(buffer1.get(), m_nSignatureContentOffset - 1 , bytesRead1) ||
5643         bytesRead1 != static_cast<sal_uInt64>(m_nSignatureContentOffset) - 1)
5644     {
5645         SAL_WARN("vcl.pdfwriter", "First buffer read failed");
5646         return false;
5647     }
5648 
5649     std::unique_ptr<char[]> buffer2(new char[nLastByteRangeNo + 1]);
5650     sal_uInt64 bytesRead2;
5651 
5652     if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1) ||
5653         osl::File::E_None != m_aFile.read(buffer2.get(), nLastByteRangeNo, bytesRead2) ||
5654         bytesRead2 != static_cast<sal_uInt64>(nLastByteRangeNo))
5655     {
5656         SAL_WARN("vcl.pdfwriter", "Second buffer read failed");
5657         return false;
5658     }
5659 
5660     OStringBuffer aCMSHexBuffer;
5661     svl::crypto::SigningContext aSigningContext;
5662     aSigningContext.m_xCertificate = m_aContext.SignCertificate;
5663     svl::crypto::Signing aSigning(aSigningContext);
5664     aSigning.AddDataRange(buffer1.get(), bytesRead1);
5665     aSigning.AddDataRange(buffer2.get(), bytesRead2);
5666     aSigning.SetSignTSA(m_aContext.SignTSA);
5667     aSigning.SetSignPassword(m_aContext.SignPassword);
5668     if (!aSigning.Sign(aCMSHexBuffer))
5669     {
5670         SAL_WARN("vcl.pdfwriter", "PDFWriter::Sign() failed");
5671         return false;
5672     }
5673 
5674     assert(aCMSHexBuffer.getLength() <= MAX_SIGNATURE_CONTENT_LENGTH);
5675 
5676     // Set file pointer to the m_nSignatureContentOffset, we're ready to overwrite PKCS7 object
5677     nWritten = 0;
5678     if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset))
5679         return false;
5680     m_aFile.write(aCMSHexBuffer.getStr(), aCMSHexBuffer.getLength(), nWritten);
5681 
5682     return osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset);
5683 }
5684 
5685 #endif //HAVE_FEATURE_NSS
5686 
emitInfoDict()5687 sal_Int32 PDFWriterImpl::emitInfoDict( )
5688 {
5689     sal_Int32 nObject = createObject();
5690 
5691     if (!updateObject(nObject))
5692         return 0;
5693 
5694     COSWriter aWriter(m_aContext.Encryption.getParams(), m_pPDFEncryptor);
5695     aWriter.startObject(nObject);
5696     aWriter.startDict();
5697 
5698     // These entries are deprecated in PDF 2.0 (in favor of XMP metadata) and shouldn't be written.
5699     // Exception: CreationDate and ModDate (which we don't write)
5700     if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0)
5701     {
5702         if (!m_aContext.DocumentInfo.Title.isEmpty())
5703         {
5704             aWriter.writeKeyAndUnicodeEncrypt("/Title", m_aContext.DocumentInfo.Title, nObject);
5705         }
5706         if (!m_aContext.DocumentInfo.Author.isEmpty())
5707         {
5708             aWriter.writeKeyAndUnicodeEncrypt("/Author", m_aContext.DocumentInfo.Author, nObject);
5709         }
5710         if (!m_aContext.DocumentInfo.Subject.isEmpty())
5711         {
5712             aWriter.writeKeyAndUnicodeEncrypt("/Subject", m_aContext.DocumentInfo.Subject, nObject);
5713         }
5714         if (!m_aContext.DocumentInfo.Keywords.isEmpty())
5715         {
5716             aWriter.writeKeyAndUnicodeEncrypt("/Keywords", m_aContext.DocumentInfo.Keywords, nObject);
5717         }
5718         if (!m_aContext.DocumentInfo.Creator.isEmpty())
5719         {
5720             aWriter.writeKeyAndUnicodeEncrypt("/Creator", m_aContext.DocumentInfo.Creator, nObject);
5721         }
5722         if (!m_aContext.DocumentInfo.Producer.isEmpty())
5723         {
5724             aWriter.writeKeyAndUnicodeEncrypt("/Producer", m_aContext.DocumentInfo.Producer, nObject);
5725         }
5726     }
5727     // Allowed in PDF 2.0
5728     aWriter.writeKeyAndLiteralEncrypt("/CreationDate", m_aCreationDateString, nObject);
5729     aWriter.endDict();
5730     aWriter.endObject();
5731 
5732     if (!writeBuffer(aWriter.getLine()))
5733         nObject = 0;
5734 
5735     return nObject;
5736 }
5737 
5738 // Part of this function may be shared with method appendDest.
emitNamedDestinations()5739 sal_Int32 PDFWriterImpl::emitNamedDestinations()
5740 {
5741     sal_Int32  nCount = m_aNamedDests.size();
5742     if( nCount <= 0 )
5743         return 0;//define internal error
5744 
5745     //get the object number for all the destinations
5746     sal_Int32 nObject = createObject();
5747 
5748     if( updateObject( nObject ) )
5749     {
5750         //emit the dictionary
5751         OStringBuffer aLine( 1024 );
5752         aLine.append( nObject );
5753         aLine.append( " 0 obj\n"
5754                       "<<" );
5755 
5756         sal_Int32  nDestID;
5757         for( nDestID = 0; nDestID < nCount; nDestID++ )
5758         {
5759             const PDFNamedDest& rDest   = m_aNamedDests[ nDestID ];
5760             // In order to correctly function both under an Internet browser and
5761             // directly with a reader (provided the reader has the feature) we
5762             // need to set the name of the destination the same way it will be encoded
5763             // in an Internet link
5764             INetURLObject aLocalURL( u"http://ahost.ax" ); //dummy location, won't be used
5765             aLocalURL.SetMark( rDest.m_aDestName );
5766 
5767             const OUString aName   = aLocalURL.GetMark( INetURLObject::DecodeMechanism::NONE ); //same coding as
5768             // in link creation ( see PDFWriterImpl::emitLinkAnnotations )
5769             const PDFPage& rDestPage    = m_aPages[ rDest.m_nPage ];
5770 
5771             aLine.append( '/' );
5772             appendDestinationName( aName, aLine ); // this conversion must be done when forming the link to target ( see in emitCatalog )
5773             aLine.append( '[' ); // the '[' can be emitted immediately, because the appendDestinationName function
5774                                  //maps the preceding character properly
5775             aLine.append( rDestPage.m_nPageObject );
5776             aLine.append( " 0 R" );
5777 
5778             switch( rDest.m_eType )
5779             {
5780             case PDFWriter::DestAreaType::XYZ:
5781             default:
5782                 aLine.append( "/XYZ " );
5783                 appendFixedInt( rDest.m_aRect.Left(), aLine );
5784                 aLine.append( ' ' );
5785                 appendFixedInt( rDest.m_aRect.Bottom(), aLine );
5786                 aLine.append( " 0" );
5787                 break;
5788             case PDFWriter::DestAreaType::FitRectangle:
5789                 aLine.append( "/FitR " );
5790                 appendFixedInt( rDest.m_aRect.Left(), aLine );
5791                 aLine.append( ' ' );
5792                 appendFixedInt( rDest.m_aRect.Top(), aLine );
5793                 aLine.append( ' ' );
5794                 appendFixedInt( rDest.m_aRect.Right(), aLine );
5795                 aLine.append( ' ' );
5796                 appendFixedInt( rDest.m_aRect.Bottom(), aLine );
5797                 break;
5798             }
5799             aLine.append( "]\n" );
5800         }
5801 
5802         //close
5803         aLine.append( ">>\nendobj\n\n" );
5804         if( ! writeBuffer( aLine ) )
5805             nObject = 0;
5806     }
5807     else
5808         nObject = 0;
5809 
5810     return nObject;
5811 }
5812 
5813 // emits the output intent dictionary
emitOutputIntent()5814 sal_Int32 PDFWriterImpl::emitOutputIntent()
5815 {
5816     if (m_nPDFA_Version == 0) // not PDFA
5817         return 0;
5818 
5819     //emit the sRGB standard profile, in ICC format, in a stream, per IEC61966-2.1
5820 
5821     OStringBuffer aLine( 1024 );
5822     COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor);
5823     sal_Int32 nICCObject = createObject();
5824     sal_Int32 nStreamLengthObject = createObject();
5825 
5826     aLine.append( nICCObject );
5827 // sRGB has 3 colors, hence /N 3 below (PDF 1.4 table 4.16)
5828     aLine.append( " 0 obj\n<</N 3/Length " );
5829     aLine.append( nStreamLengthObject );
5830     aLine.append( " 0 R" );
5831     if (!g_bDebugDisableCompression)
5832         aLine.append( "/Filter/FlateDecode" );
5833     aLine.append( ">>\nstream\n" );
5834     if ( !updateObject( nICCObject ) ) return 0;
5835     if ( !writeBuffer( aLine ) ) return 0;
5836     //get file position
5837     sal_uInt64 nBeginStreamPos = 0;
5838     if (osl::File::E_None != m_aFile.getPos(nBeginStreamPos))
5839         return 0;
5840     beginCompression();
5841     checkAndEnableStreamEncryption( nICCObject );
5842     cmsHPROFILE hProfile = cmsCreate_sRGBProfile();
5843     //force ICC profile version 2.1
5844     cmsSetProfileVersion(hProfile, 2.1);
5845     cmsUInt32Number nBytesNeeded = 0;
5846     cmsSaveProfileToMem(hProfile, nullptr, &nBytesNeeded);
5847     if (!nBytesNeeded)
5848       return 0;
5849     std::vector<unsigned char> aBuffer(nBytesNeeded);
5850     cmsSaveProfileToMem(hProfile, aBuffer.data(), &nBytesNeeded);
5851     cmsCloseProfile(hProfile);
5852     bool written = writeBufferBytes( aBuffer.data(), static_cast<sal_Int32>(aBuffer.size()) );
5853     disableStreamEncryption();
5854     endCompression();
5855 
5856     sal_uInt64 nEndStreamPos = 0;
5857     if (m_aFile.getPos(nEndStreamPos) != osl::File::E_None)
5858         return 0;
5859 
5860     if( !written )
5861         return 0;
5862     if( ! writeBuffer( "\nendstream\nendobj\n\n" ) )
5863         return 0 ;
5864     aLine.setLength( 0 );
5865 
5866     //emit the stream length   object
5867     if ( !updateObject( nStreamLengthObject ) ) return 0;
5868     aLine.setLength( 0 );
5869     aLine.append( nStreamLengthObject );
5870     aLine.append( " 0 obj\n" );
5871     aLine.append( static_cast<sal_Int64>(nEndStreamPos-nBeginStreamPos) );
5872     aLine.append( "\nendobj\n\n" );
5873     if ( !writeBuffer( aLine ) ) return 0;
5874     aLine.setLength( 0 );
5875 
5876     //emit the OutputIntent dictionary
5877     sal_Int32 nOIObject = createObject();
5878     if ( !updateObject( nOIObject ) ) return 0;
5879     aLine.append( nOIObject );
5880     aLine.append( " 0 obj\n"
5881                   "<</Type/OutputIntent/S/GTS_PDFA1/OutputConditionIdentifier");
5882 
5883     aWriter.writeLiteralEncrypt(std::string_view("sRGB IEC61966-2.1"), nOIObject);
5884     aLine.append("/DestOutputProfile ");
5885     aLine.append( nICCObject );
5886     aLine.append( " 0 R>>\nendobj\n\n" );
5887     if ( !writeBuffer( aLine ) ) return 0;
5888 
5889     return nOIObject;
5890 }
5891 
lcl_assignMeta(std::u16string_view aValue,OString & aMeta)5892 static void lcl_assignMeta(std::u16string_view aValue, OString& aMeta)
5893 {
5894     if (!aValue.empty())
5895     {
5896         aMeta = OUStringToOString(comphelper::string::encodeForXml(aValue), RTL_TEXTENCODING_UTF8);
5897     }
5898 }
5899 
lcl_assignMeta(const css::uno::Sequence<OUString> & rValues,std::vector<OString> & rMeta)5900 static void lcl_assignMeta(const css::uno::Sequence<OUString>& rValues, std::vector<OString>& rMeta)
5901 {
5902     if (!rValues.hasElements())
5903         return;
5904 
5905     std::vector<OString> aNewMetaVector;
5906     aNewMetaVector.reserve(rValues.getLength());
5907 
5908     for (const OUString& rValue : rValues)
5909     {
5910         aNewMetaVector.emplace_back(
5911             OUStringToOString(comphelper::string::encodeForXml(rValue), RTL_TEXTENCODING_UTF8));
5912     }
5913 
5914     rMeta = std::move(aNewMetaVector);
5915 }
5916 
5917 // emits the document metadata
5918 // Since in PDF 1.4
emitDocumentMetadata()5919 sal_Int32 PDFWriterImpl::emitDocumentMetadata()
5920 {
5921     if (m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4)
5922         return 0;
5923 
5924     //get the object number for all the destinations
5925     sal_Int32 nObject = createObject();
5926 
5927     if (!updateObject(nObject))
5928         return 0;
5929 
5930     pdf::XmpMetadata aMetadata;
5931 
5932     if (m_nPDFA_Version > 0)
5933         aMetadata.mnPDF_A = m_nPDFA_Version;
5934 
5935     if (m_bIsPDF_UA)
5936     {
5937         // If PDF 2.0+ we need to use PDF/UA-2 otherwise PDF/UA-1
5938         aMetadata.mnPDF_UA = (m_aContext.Version >= PDFWriter::PDFVersion::PDF_2_0) ? 2 : 1;
5939     }
5940 
5941     lcl_assignMeta(m_aContext.DocumentInfo.Title, aMetadata.msTitle);
5942     lcl_assignMeta(m_aContext.DocumentInfo.Author, aMetadata.msAuthor);
5943     lcl_assignMeta(m_aContext.DocumentInfo.Subject, aMetadata.msSubject);
5944     lcl_assignMeta(m_aContext.DocumentInfo.Producer, aMetadata.msProducer);
5945     aMetadata.msPDFVersion = getPDFVersionStr(m_aContext.Version);
5946     if (m_nPDFA_Version == 4)
5947     {
5948         // if we have embedded files we need to use conformance level "F"
5949         aMetadata.msConformance = m_aEmbeddedFiles.empty() ? ""_ostr : "F"_ostr;
5950     }
5951     else
5952     {
5953         aMetadata.msConformance = "B"_ostr;
5954     }
5955     lcl_assignMeta(m_aContext.DocumentInfo.Keywords, aMetadata.msKeywords);
5956     lcl_assignMeta(m_aContext.DocumentInfo.Contributor, aMetadata.maContributor);
5957     lcl_assignMeta(m_aContext.DocumentInfo.Coverage, aMetadata.msCoverage);
5958     lcl_assignMeta(m_aContext.DocumentInfo.Identifier, aMetadata.msIdentifier);
5959     lcl_assignMeta(m_aContext.DocumentInfo.Publisher, aMetadata.maPublisher);
5960     lcl_assignMeta(m_aContext.DocumentInfo.Relation, aMetadata.maRelation);
5961     lcl_assignMeta(m_aContext.DocumentInfo.Rights, aMetadata.msRights);
5962     lcl_assignMeta(m_aContext.DocumentInfo.Source, aMetadata.msSource);
5963     lcl_assignMeta(m_aContext.DocumentInfo.Type, aMetadata.msType);
5964     lcl_assignMeta(m_aContext.DocumentInfo.Creator, aMetadata.m_sCreatorTool);
5965     aMetadata.m_sCreateDate = m_aCreationMetaDateString;
5966 
5967     {
5968         COSWriter aWriter;
5969         aWriter.startObject(nObject);
5970         aWriter.startDict();
5971         aWriter.write("/Type", "/Metadata");
5972         aWriter.write("/Subtype", "/XML");
5973         aWriter.write("/Length", sal_Int32(aMetadata.getSize()));
5974         aWriter.endDict();
5975         aWriter.startStream();
5976         if (!writeBuffer(aWriter.getLine()))
5977             return 0;
5978     }
5979 
5980     //emit the stream
5981     bool bEncryptMetadata = m_pPDFEncryptor && m_pPDFEncryptor->isMetadataEncrypted();
5982     if (bEncryptMetadata)
5983         checkAndEnableStreamEncryption(nObject);
5984 
5985     if (!writeBufferBytes(aMetadata.getData(), aMetadata.getSize()))
5986         return 0;
5987 
5988     if (bEncryptMetadata)
5989         disableStreamEncryption();
5990 
5991     {
5992         COSWriter aWriter;
5993         aWriter.endStream();
5994         aWriter.endObject();
5995         if (!writeBuffer(aWriter.getLine()))
5996             return 0;
5997     }
5998 
5999     return nObject;
6000 }
6001 
emitEncrypt()6002 sal_Int32 PDFWriterImpl::emitEncrypt()
6003 {
6004     //emit the security information
6005     //must be emitted as indirect dictionary object, since
6006     //Acrobat Reader 5 works only with this kind of implementation
6007 
6008     sal_Int32 nObject = createObject();
6009 
6010     if (updateObject(nObject))
6011     {
6012         PDFEncryptionProperties& rProperties = m_aContext.Encryption;
6013         COSWriter aWriter(m_aContext.Encryption.getParams(), m_pPDFEncryptor);
6014         aWriter.startObject(nObject);
6015         aWriter.startDict();
6016         aWriter.write("/Filter", "/Standard");
6017         aWriter.write("/V", m_pPDFEncryptor->getVersion());
6018         aWriter.write("/Length", m_pPDFEncryptor->getKeyLength() * 8);
6019         aWriter.write("/R", m_pPDFEncryptor->getRevision());
6020 
6021         if (m_pPDFEncryptor->getVersion() == 5 && m_pPDFEncryptor->getRevision() == 6)
6022         {
6023             // emit the owner password, must not be encrypted
6024             aWriter.writeHexArray("/U", rProperties.UValue.data(), rProperties.UValue.size());
6025             aWriter.writeHexArray("/UE", rProperties.UE.data(), rProperties.UE.size());
6026             aWriter.writeHexArray("/O", rProperties.OValue.data(), rProperties.OValue.size());
6027             aWriter.writeHexArray("/OE", rProperties.OE.data(), rProperties.OE.size());
6028 
6029             // Encrypted perms
6030             std::vector<sal_uInt8> aEncryptedPermissions = m_pPDFEncryptor->getEncryptedAccessPermissions(rProperties.EncryptionKey);
6031             aWriter.writeHexArray("/Perms", aEncryptedPermissions.data(), aEncryptedPermissions.size());
6032 
6033             // Write content filter stuff - to select we want AESv3 256bit
6034             aWriter.write("/CF", "<</StdCF <</CFM /AESV3 /Length 256>>>>");
6035             aWriter.write("/StmF", "/StdCF");
6036             aWriter.write("/StrF", "/StdCF");
6037             // Encrypt metadata. Default is true. Relevant for Revision 6+
6038             if (!m_pPDFEncryptor->isMetadataEncrypted())
6039                 aWriter.write("/EncryptMetadata", " false ");
6040         }
6041         else
6042         {
6043             // emit the owner password, must not be encrypted
6044             aWriter.writeHexArray("/U", rProperties.UValue.data(), rProperties.UValue.size());
6045             aWriter.writeHexArray("/O", rProperties.OValue.data(), rProperties.OValue.size());
6046         }
6047         aWriter.write("/P", m_pPDFEncryptor->getAccessPermissions());
6048         aWriter.endDict();
6049         aWriter.endObject();
6050 
6051         if (!writeBuffer(aWriter.getLine()))
6052             nObject = 0;
6053     }
6054     else
6055         nObject = 0;
6056 
6057     return nObject;
6058 }
6059 
emitTrailer()6060 bool PDFWriterImpl::emitTrailer()
6061 {
6062     // emit doc info
6063     sal_Int32 nDocInfoObject = 0;
6064     // Deprecated in PDF 2.0, but still allowed if /PieceInfo is written (which we don't support)
6065     if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0)
6066         nDocInfoObject = emitInfoDict();
6067 
6068     sal_Int32 nSecObject = 0;
6069 
6070     if (m_aContext.Encryption.canEncrypt())
6071     {
6072         nSecObject = emitEncrypt();
6073     }
6074     // emit xref table
6075     // remember start
6076     sal_uInt64 nXRefOffset = 0;
6077     if (osl::File::E_None != m_aFile.getPos(nXRefOffset))
6078         return false;
6079     if (!writeBuffer("xref\n"))
6080         return false;
6081 
6082     sal_Int32 nObjects = m_aObjects.size();
6083     OStringBuffer aLine;
6084     aLine.append( "0 " );
6085     aLine.append( static_cast<sal_Int32>(nObjects+1) );
6086     aLine.append( "\n" );
6087     aLine.append( "0000000000 65535 f \n" );
6088     if (!writeBuffer(aLine))
6089         return false;
6090 
6091     for( sal_Int32 i = 0; i < nObjects; i++ )
6092     {
6093         aLine.setLength( 0 );
6094         OString aOffset = OString::number( m_aObjects[i] );
6095         for( sal_Int32 j = 0; j < (10-aOffset.getLength()); j++ )
6096             aLine.append( '0' );
6097         aLine.append( aOffset );
6098         aLine.append( " 00000 n \n" );
6099         SAL_WARN_IF( aLine.getLength() != 20, "vcl.pdfwriter", "invalid xref entry" );
6100         if (!writeBuffer(aLine))
6101             return false;
6102     }
6103 
6104     // prepare document checksum
6105     OStringBuffer aDocChecksum( 2*RTL_DIGEST_LENGTH_MD5+1 );
6106     ::std::vector<unsigned char> const nMD5Sum(m_DocDigest.finalize());
6107     for (sal_uInt8 i : nMD5Sum)
6108         COSWriter::appendHex( i, aDocChecksum );
6109     // document id set in setDocInfo method
6110     // emit trailer
6111     aLine.setLength( 0 );
6112     aLine.append( "trailer\n"
6113                   "<</Size " );
6114     aLine.append( static_cast<sal_Int32>(nObjects+1) );
6115     aLine.append( "/Root " );
6116     aLine.append( m_nCatalogObject );
6117     aLine.append( " 0 R\n" );
6118     if( nSecObject )
6119     {
6120         aLine.append( "/Encrypt ");
6121         aLine.append( nSecObject );
6122         aLine.append( " 0 R\n" );
6123     }
6124     if( nDocInfoObject )
6125     {
6126         aLine.append( "/Info " );
6127         aLine.append( nDocInfoObject );
6128         aLine.append( " 0 R\n" );
6129     }
6130     if( ! m_aContext.Encryption.DocumentIdentifier.empty() )
6131     {
6132         aLine.append( "/ID [ <" );
6133         for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
6134         {
6135             COSWriter::appendHex( sal_Int8(item), aLine );
6136         }
6137         aLine.append( ">\n"
6138                       "<" );
6139         for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
6140         {
6141             COSWriter::appendHex( sal_Int8(item), aLine );
6142         }
6143         aLine.append( "> ]\n" );
6144     }
6145 
6146     // Writes the /DocChecksum - hash of the PDF stream
6147     // This entry is not defined in the standard, so don't write it if we
6148     // are using PDF/UA or PDF/A as the compliance checkers will complain.
6149     // Actually we shouldn't write it at all...
6150     if (!aDocChecksum.isEmpty() && !m_bIsPDF_UA && m_nPDFA_Version == 0)
6151     {
6152         aLine.append( "/DocChecksum /" );
6153         aLine.append( aDocChecksum );
6154         aLine.append( "\n" );
6155     }
6156 
6157     // Writes the /AdditionalStreams - writes the embedded / attached files into the PDF
6158     // This entry is not defined in the standard, so don't write it if we
6159     // are using PDF/UA or PDF/A as the compliance checkers will complain.
6160     if (!m_aDocumentAttachedFiles.empty() && !m_bIsPDF_UA && m_nPDFA_Version == 0)
6161     {
6162         aLine.append( "/AdditionalStreams [" );
6163         for (auto const& rAttachedFile : m_aDocumentAttachedFiles)
6164         {
6165             aLine.append( "/" );
6166             COSWriter::appendName(rAttachedFile.maMimeType, aLine);
6167             aLine.append(" ");
6168             appendObjectReference(rAttachedFile.mnEmbeddedFileObjectId, aLine);
6169             aLine.append("\n");
6170         }
6171         aLine.append( "]\n" );
6172     }
6173 
6174     // After calling m_DocDigest.finalize(), we need to initialize the hash again,
6175     // otherwise, m_DocDigest.update() inside writeBuffer will fail with
6176     // Assertion failure: rv == SECSuccess, at sechash.c:140
6177     m_DocDigest.initialize();
6178 
6179     aLine.append( ">>\n"
6180                   "startxref\n" );
6181     aLine.append( static_cast<sal_Int64>(nXRefOffset) );
6182     aLine.append( "\n"
6183                   "%%EOF\n" );
6184     return writeBuffer( aLine );
6185 }
6186 
6187 namespace {
6188 
6189 struct AnnotationSortEntry
6190 {
6191     sal_Int32 nTabOrder;
6192     sal_Int32 nObject;
6193     sal_Int32 nWidgetIndex;
6194 
AnnotationSortEntryvcl::__anon65e7e0150911::AnnotationSortEntry6195     AnnotationSortEntry( sal_Int32 nTab, sal_Int32 nObj, sal_Int32 nI ) :
6196         nTabOrder( nTab ),
6197         nObject( nObj ),
6198         nWidgetIndex( nI )
6199     {}
6200 };
6201 
6202 struct AnnotSortContainer
6203 {
6204     o3tl::sorted_vector< sal_Int32 >      aObjects;
6205     std::vector< AnnotationSortEntry >    aSortedAnnots;
6206 };
6207 
6208 struct AnnotSorterLess
6209 {
6210     std::vector<PDFWidget>& m_rWidgets;
6211 
AnnotSorterLessvcl::__anon65e7e0150911::AnnotSorterLess6212     explicit AnnotSorterLess( std::vector<PDFWidget>& rWidgets ) : m_rWidgets( rWidgets ) {}
6213 
operator ()vcl::__anon65e7e0150911::AnnotSorterLess6214     bool operator()( const AnnotationSortEntry& rLeft, const AnnotationSortEntry& rRight )
6215     {
6216         if( rLeft.nTabOrder < rRight.nTabOrder )
6217             return true;
6218         if( rRight.nTabOrder < rLeft.nTabOrder )
6219             return false;
6220         if( rLeft.nWidgetIndex < 0 && rRight.nWidgetIndex < 0 )
6221             return false;
6222         if( rRight.nWidgetIndex < 0 )
6223             return true;
6224         if( rLeft.nWidgetIndex < 0 )
6225             return false;
6226         // remember: widget rects are in PDF coordinates, so they are ordered down up
6227         if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() >
6228             m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() )
6229             return true;
6230         if( m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() >
6231             m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() )
6232             return false;
6233         if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Left() <
6234             m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Left() )
6235             return true;
6236         return false;
6237     }
6238 };
6239 
6240 }
6241 
sortWidgets()6242 void PDFWriterImpl::sortWidgets()
6243 {
6244     // sort widget annotations on each page as per their
6245     // TabOrder attribute
6246     std::unordered_map< sal_Int32, AnnotSortContainer > sorted;
6247     int nWidgets = m_aWidgets.size();
6248     for( int nW = 0; nW < nWidgets; nW++ )
6249     {
6250         const PDFWidget& rWidget = m_aWidgets[nW];
6251         if( rWidget.m_nPage >= 0 )
6252         {
6253             AnnotSortContainer& rCont = sorted[ rWidget.m_nPage ];
6254             // optimize vector allocation
6255             if( rCont.aSortedAnnots.empty() )
6256                 rCont.aSortedAnnots.reserve( m_aPages[ rWidget.m_nPage ].m_aAnnotations.size() );
6257             // insert widget to tab sorter
6258             // RadioButtons are not page annotations, only their individual check boxes are
6259             if( rWidget.m_eType != PDFWriter::RadioButton )
6260             {
6261                 rCont.aObjects.insert( rWidget.m_nObject );
6262                 rCont.aSortedAnnots.emplace_back( rWidget.m_nTabOrder, rWidget.m_nObject, nW );
6263             }
6264         }
6265     }
6266     for (auto & item : sorted)
6267     {
6268         // append entries for non widget annotations
6269         PDFPage& rPage = m_aPages[ item.first ];
6270         unsigned int nAnnots = rPage.m_aAnnotations.size();
6271         for( unsigned int nA = 0; nA < nAnnots; nA++ )
6272             if( item.second.aObjects.find( rPage.m_aAnnotations[nA] ) == item.second.aObjects.end())
6273                 item.second.aSortedAnnots.emplace_back( 10000, rPage.m_aAnnotations[nA], -1 );
6274 
6275         AnnotSorterLess aLess( m_aWidgets );
6276         std::stable_sort( item.second.aSortedAnnots.begin(), item.second.aSortedAnnots.end(), aLess );
6277         // sanity check
6278         if( item.second.aSortedAnnots.size() == nAnnots)
6279         {
6280             for( unsigned int nA = 0; nA < nAnnots; nA++ )
6281                 rPage.m_aAnnotations[nA] = item.second.aSortedAnnots[nA].nObject;
6282         }
6283         else
6284         {
6285             SAL_WARN( "vcl.pdfwriter", "wrong number of sorted annotations" );
6286             SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::sortWidgets(): wrong number of sorted assertions "
6287                      "on page nr " << item.first << ", " <<
6288                      static_cast<tools::Long>(item.second.aSortedAnnots.size()) << " sorted and " <<
6289                      static_cast<tools::Long>(nAnnots) << " unsorted");
6290         }
6291     }
6292 
6293     // FIXME: implement tab order in structure tree for PDF 1.5
6294 }
6295 
emit()6296 bool PDFWriterImpl::emit()
6297 {
6298     endPage();
6299 
6300     // resort structure tree and annotations if necessary
6301     // needed for widget tab order
6302     sortWidgets();
6303 
6304 #if HAVE_FEATURE_NSS
6305     if( m_aContext.SignPDF )
6306     {
6307         // sign the document
6308         PDFWriter::SignatureWidget aSignature;
6309         aSignature.Name = "Signature1";
6310         createControl( aSignature, 0 );
6311     }
6312 #endif
6313 
6314     // emit catalog
6315     if (!emitCatalog())
6316         return false;
6317 
6318 #if HAVE_FEATURE_NSS
6319     if (m_nSignatureObject != -1) // if document is signed, emit sigdict
6320     {
6321         if( !emitSignature() )
6322         {
6323             m_aErrors.insert( PDFWriter::Error_Signature_Failed );
6324             return false;
6325         }
6326     }
6327 #endif
6328 
6329     // emit trailer
6330     if (!emitTrailer())
6331         return false;
6332 
6333 #if HAVE_FEATURE_NSS
6334     if (m_nSignatureObject != -1) // finalize the signature
6335     {
6336         if( !finalizeSignature() )
6337         {
6338             m_aErrors.insert( PDFWriter::Error_Signature_Failed );
6339             return false;
6340         }
6341     }
6342 #endif
6343 
6344     m_aFile.close();
6345     m_bOpen = false;
6346 
6347     return true;
6348 }
6349 
6350 
getSystemFont(const vcl::Font & i_rFont)6351 sal_Int32 PDFWriterImpl::getSystemFont( const vcl::Font& i_rFont )
6352 {
6353     auto popIt = ScopedPush();
6354 
6355     SetFont( i_rFont );
6356 
6357     const LogicalFontInstance* pFontInstance = GetFontInstance();
6358     const vcl::font::PhysicalFontFace* pFace = pFontInstance->GetFontFace();
6359     sal_Int32 nFontID = 0;
6360     auto it = m_aSystemFonts.find( pFace );
6361     if( it != m_aSystemFonts.end() )
6362         nFontID = it->second.m_nNormalFontID;
6363     else
6364     {
6365         nFontID = m_nNextFID++;
6366         m_aSystemFonts[ pFace ] = EmbedFont();
6367         m_aSystemFonts[ pFace ].m_pFontInstance = const_cast<LogicalFontInstance*>(pFontInstance);
6368         m_aSystemFonts[ pFace ].m_nNormalFontID = nFontID;
6369     }
6370 
6371     return nFontID;
6372 }
6373 
registerSimpleGlyph(const sal_GlyphId nFontGlyphId,const vcl::font::PhysicalFontFace * pFace,const std::vector<sal_Ucs> & rCodeUnits,sal_Int32 nGlyphWidth,sal_uInt8 & nMappedGlyph,sal_Int32 & nMappedFontObject)6374 void PDFWriterImpl::registerSimpleGlyph(const sal_GlyphId nFontGlyphId,
6375                                   const vcl::font::PhysicalFontFace* pFace,
6376                                   const std::vector<sal_Ucs>& rCodeUnits,
6377                                   sal_Int32 nGlyphWidth,
6378                                   sal_uInt8& nMappedGlyph,
6379                                   sal_Int32& nMappedFontObject)
6380 {
6381     FontSubset& rSubset = m_aSubsets[ pFace ];
6382     // search for font specific glyphID
6383     auto it = rSubset.m_aMapping.find( nFontGlyphId );
6384     if( it != rSubset.m_aMapping.end() )
6385     {
6386         nMappedFontObject = it->second.m_nFontID;
6387         nMappedGlyph = it->second.m_nSubsetGlyphID;
6388     }
6389     else
6390     {
6391         // create new subset if necessary
6392         if( rSubset.m_aSubsets.empty()
6393         || (rSubset.m_aSubsets.back().m_aMapping.size() > 254) )
6394         {
6395             rSubset.m_aSubsets.emplace_back( m_nNextFID++ );
6396         }
6397 
6398         // copy font id
6399         nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID;
6400         // create new glyph in subset
6401         sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(rSubset.m_aSubsets.back().m_aMapping.size()+1);
6402         nMappedGlyph = nNewId;
6403 
6404         // add new glyph to emitted font subset
6405         GlyphEmit& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[ nFontGlyphId ];
6406         rNewGlyphEmit.setGlyphId( nNewId );
6407         rNewGlyphEmit.setGlyphWidth(XUnits(pFace->UnitsPerEm(), nGlyphWidth));
6408         for (const auto nCode : rCodeUnits)
6409             rNewGlyphEmit.addCode(nCode);
6410 
6411         // add new glyph to font mapping
6412         Glyph& rNewGlyph = rSubset.m_aMapping[ nFontGlyphId ];
6413         rNewGlyph.m_nFontID = nMappedFontObject;
6414         rNewGlyph.m_nSubsetGlyphID = nNewId;
6415     }
6416 }
6417 
registerGlyph(const sal_GlyphId nFontGlyphId,const vcl::font::PhysicalFontFace * pFace,const LogicalFontInstance * pFont,const std::vector<sal_Ucs> & rCodeUnits,sal_Int32 nGlyphWidth,sal_uInt8 & nMappedGlyph,sal_Int32 & nMappedFontObject)6418 void PDFWriterImpl::registerGlyph(const sal_GlyphId nFontGlyphId,
6419                                   const vcl::font::PhysicalFontFace* pFace,
6420                                   const LogicalFontInstance* pFont,
6421                                   const std::vector<sal_Ucs>& rCodeUnits, sal_Int32 nGlyphWidth,
6422                                   sal_uInt8& nMappedGlyph, sal_Int32& nMappedFontObject)
6423 {
6424     auto bVariations = !pFace->GetVariations(*pFont).empty();
6425     // tdf#155161
6426     // PDF doesn’t support CFF2 table and we currently don’t convert them to
6427     // Type 1 (like we do with CFF table), so treat it like fonts with
6428     // variations and embed as Type 3 fonts.
6429     if (!pFace->GetRawFontData(HB_TAG('C', 'F', 'F', '2')).empty())
6430         bVariations = true;
6431 
6432     if (pFace->IsColorFont() || bVariations)
6433     {
6434         // Font has colors, check if this glyph has color layers or bitmap.
6435         tools::Rectangle aRect;
6436         auto aLayers = pFace->GetGlyphColorLayers(nFontGlyphId);
6437         auto aBitmap = pFace->GetGlyphColorBitmap(nFontGlyphId, aRect);
6438         if (!aLayers.empty() || !aBitmap.empty() || bVariations)
6439         {
6440             auto& rSubset = m_aType3Fonts[pFace];
6441             auto it = rSubset.m_aMapping.find(nFontGlyphId);
6442             if (it != rSubset.m_aMapping.end())
6443             {
6444                 nMappedFontObject = it->second.m_nFontID;
6445                 nMappedGlyph = it->second.m_nSubsetGlyphID;
6446             }
6447             else
6448             {
6449                 // create new subset if necessary
6450                 if (rSubset.m_aSubsets.empty()
6451                     || (rSubset.m_aSubsets.back().m_aMapping.size() > 254))
6452                 {
6453                     rSubset.m_aSubsets.emplace_back(m_nNextFID++);
6454                 }
6455 
6456                 // copy font id
6457                 nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID;
6458                 // create new glyph in subset
6459                 sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(
6460                     rSubset.m_aSubsets.back().m_aMapping.size() + 1);
6461                 nMappedGlyph = nNewId;
6462 
6463                 // add new glyph to emitted font subset
6464                 auto& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[nFontGlyphId];
6465                 rNewGlyphEmit.setGlyphId(nNewId);
6466                 rNewGlyphEmit.setGlyphWidth(nGlyphWidth);
6467                 for (const auto nCode : rCodeUnits)
6468                     rNewGlyphEmit.addCode(nCode);
6469 
6470                 // add color layers to the glyphs
6471                 if (!aLayers.empty())
6472                 {
6473                     for (const auto& aLayer : aLayers)
6474                     {
6475                         sal_uInt8 nLayerGlyph;
6476                         sal_Int32 nLayerFontID;
6477                         registerSimpleGlyph(aLayer.nGlyphIndex, pFace, rCodeUnits, nGlyphWidth,
6478                                             nLayerGlyph, nLayerFontID);
6479 
6480                         rNewGlyphEmit.addColorLayer(
6481                             { nLayerFontID, nLayerGlyph, aLayer.nColorIndex });
6482                     }
6483                 }
6484                 else if (!aBitmap.empty())
6485                     rNewGlyphEmit.setColorBitmap(aBitmap, aRect);
6486                 else if (bVariations)
6487                     rNewGlyphEmit.setOutline(pFont->GetGlyphOutlineUntransformed(nFontGlyphId));
6488 
6489                 // add new glyph to font mapping
6490                 Glyph& rNewGlyph = rSubset.m_aMapping[nFontGlyphId];
6491                 rNewGlyph.m_nFontID = nMappedFontObject;
6492                 rNewGlyph.m_nSubsetGlyphID = nNewId;
6493             }
6494             return;
6495         }
6496     }
6497 
6498     // If we reach here then the glyph has no color layers.
6499     registerSimpleGlyph(nFontGlyphId, pFace, rCodeUnits, nGlyphWidth, nMappedGlyph,
6500                         nMappedFontObject);
6501 }
6502 
drawRelief(SalLayout & rLayout,const OUString & rText,bool bTextLines)6503 void PDFWriterImpl::drawRelief( SalLayout& rLayout, const OUString& rText, bool bTextLines )
6504 {
6505     push( PushFlags::ALL );
6506 
6507     FontRelief eRelief = m_aCurrentPDFState.m_aFont.GetRelief();
6508 
6509     Color aTextColor = m_aCurrentPDFState.m_aFont.GetColor();
6510     Color aTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
6511     Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
6512     Color aReliefColor( COL_LIGHTGRAY );
6513     if( aTextColor == COL_BLACK )
6514         aTextColor = COL_WHITE;
6515     if( aTextLineColor == COL_BLACK )
6516         aTextLineColor = COL_WHITE;
6517     if( aOverlineColor == COL_BLACK )
6518         aOverlineColor = COL_WHITE;
6519     // coverity[copy_paste_error : FALSE] - aReliefColor depending on aTextColor is correct
6520     if( aTextColor == COL_WHITE )
6521         aReliefColor = COL_BLACK;
6522 
6523     Font aSetFont = m_aCurrentPDFState.m_aFont;
6524     aSetFont.SetRelief( FontRelief::NONE );
6525     aSetFont.SetShadow( false );
6526 
6527     aSetFont.SetColor( aReliefColor );
6528     setTextLineColor( aReliefColor );
6529     setOverlineColor( aReliefColor );
6530     setFont( aSetFont );
6531     tools::Long nOff = 1 + GetDPIX()/300;
6532     if( eRelief == FontRelief::Engraved )
6533         nOff = -nOff;
6534 
6535     auto aPrevOffset = rLayout.DrawOffset();
6536     rLayout.DrawOffset()
6537         += basegfx::B2DPoint{ static_cast<double>(nOff), static_cast<double>(nOff) };
6538     updateGraphicsState();
6539     drawLayout( rLayout, rText, bTextLines );
6540 
6541     rLayout.DrawOffset() = aPrevOffset;
6542     setTextLineColor( aTextLineColor );
6543     setOverlineColor( aOverlineColor );
6544     aSetFont.SetColor( aTextColor );
6545     setFont( aSetFont );
6546     updateGraphicsState();
6547     drawLayout( rLayout, rText, bTextLines );
6548 
6549     // clean up the mess
6550     pop();
6551 }
6552 
drawShadow(SalLayout & rLayout,const OUString & rText,bool bTextLines)6553 void PDFWriterImpl::drawShadow( SalLayout& rLayout, const OUString& rText, bool bTextLines )
6554 {
6555     Font aSaveFont = m_aCurrentPDFState.m_aFont;
6556     Color aSaveTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
6557     Color aSaveOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
6558 
6559     Font& rFont = m_aCurrentPDFState.m_aFont;
6560     if( rFont.GetColor() == COL_BLACK || rFont.GetColor().GetLuminance() < 8 )
6561         rFont.SetColor( COL_LIGHTGRAY );
6562     else
6563         rFont.SetColor( COL_BLACK );
6564     rFont.SetShadow( false );
6565     rFont.SetOutline( false );
6566     setFont( rFont );
6567     setTextLineColor( rFont.GetColor() );
6568     setOverlineColor( rFont.GetColor() );
6569     updateGraphicsState();
6570 
6571     tools::Long nOff = 1 + ((GetFontInstance()->mnLineHeight-24)/24);
6572     if( rFont.IsOutline() )
6573         nOff++;
6574     rLayout.DrawBase() += basegfx::B2DPoint(nOff, nOff);
6575     drawLayout( rLayout, rText, bTextLines );
6576     rLayout.DrawBase() -= basegfx::B2DPoint(nOff, nOff);
6577 
6578     setFont( aSaveFont );
6579     setTextLineColor( aSaveTextLineColor );
6580     setOverlineColor( aSaveOverlineColor );
6581     updateGraphicsState();
6582 }
6583 
drawVerticalGlyphs(const std::vector<PDFGlyph> & rGlyphs,OStringBuffer & rLine,const Point & rAlignOffset,const Matrix3 & rRotScale,double fAngle,double fXScale,sal_Int32 nFontHeight)6584 void PDFWriterImpl::drawVerticalGlyphs(
6585         const std::vector<PDFGlyph>& rGlyphs,
6586         OStringBuffer& rLine,
6587         const Point& rAlignOffset,
6588         const Matrix3& rRotScale,
6589         double fAngle,
6590         double fXScale,
6591         sal_Int32 nFontHeight)
6592 {
6593     double nXOffset = 0;
6594     Point aCurPos(SubPixelToLogic(rGlyphs[0].m_aPos));
6595     aCurPos += rAlignOffset;
6596     for( size_t i = 0; i < rGlyphs.size(); i++ )
6597     {
6598         // have to emit each glyph on its own
6599         double fDeltaAngle = 0.0;
6600         double fYScale = 1.0;
6601         double fTempXScale = fXScale;
6602 
6603         // perform artificial italics if necessary
6604         double fSkew = 0.0;
6605         if (rGlyphs[i].m_pFont->NeedsArtificialItalic())
6606             fSkew = ARTIFICIAL_ITALIC_SKEW;
6607 
6608         double fSkewB = fSkew;
6609         double fSkewA = 0.0;
6610 
6611         Point aDeltaPos;
6612         if (rGlyphs[i].m_pGlyph->IsVertical())
6613         {
6614             fDeltaAngle = M_PI/2.0;
6615             fYScale = fXScale;
6616             fTempXScale = 1.0;
6617             fSkewA = -fSkewB;
6618             fSkewB = 0.0;
6619         }
6620         aDeltaPos += SubPixelToLogic(basegfx::B2DPoint(nXOffset / fXScale, 0)) - SubPixelToLogic(basegfx::B2DPoint());
6621         if( i < rGlyphs.size()-1 )
6622         // #i120627# the text on the Y axis is reversed when export ppt file to PDF format
6623         {
6624             double nOffsetX = rGlyphs[i+1].m_aPos.getX() - rGlyphs[i].m_aPos.getX();
6625             double nOffsetY = rGlyphs[i+1].m_aPos.getY() - rGlyphs[i].m_aPos.getY();
6626             nXOffset += std::hypot(nOffsetX, nOffsetY);
6627         }
6628         if (!rGlyphs[i].m_pGlyph->glyphId())
6629             continue;
6630 
6631         aDeltaPos = rRotScale.transform( aDeltaPos );
6632 
6633         Matrix3 aMat;
6634         if( fSkewB != 0.0 || fSkewA != 0.0 )
6635             aMat.skew( fSkewA, fSkewB );
6636         aMat.scale( fTempXScale, fYScale );
6637         aMat.rotate( fAngle+fDeltaAngle );
6638         aMat.translate( aCurPos.X()+aDeltaPos.X(), aCurPos.Y()+aDeltaPos.Y() );
6639         m_aPages.back().appendMatrix3(aMat, rLine);
6640         rLine.append( " Tm" );
6641         if( i == 0 || rGlyphs[i-1].m_nMappedFontId != rGlyphs[i].m_nMappedFontId )
6642         {
6643             rLine.append( " /F" );
6644             rLine.append( rGlyphs[i].m_nMappedFontId );
6645             rLine.append( ' ' );
6646             m_aPages.back().appendMappedLength( nFontHeight, rLine );
6647             rLine.append( " Tf" );
6648         }
6649         rLine.append( "<" );
6650         COSWriter::appendHex( rGlyphs[i].m_nMappedGlyphId, rLine );
6651         rLine.append( ">Tj\n" );
6652     }
6653 }
6654 
drawHorizontalGlyphs(const std::vector<PDFGlyph> & rGlyphs,OStringBuffer & rLine,const Point & rAlignOffset,bool bFirst,double fAngle,double fXScale,sal_Int32 nFontHeight,sal_Int32 nPixelFontHeight)6655 void PDFWriterImpl::drawHorizontalGlyphs(
6656         const std::vector<PDFGlyph>& rGlyphs,
6657         OStringBuffer& rLine,
6658         const Point& rAlignOffset,
6659         bool bFirst,
6660         double fAngle,
6661         double fXScale,
6662         sal_Int32 nFontHeight,
6663         sal_Int32 nPixelFontHeight)
6664 {
6665     // horizontal (= normal) case
6666 
6667     // fill in  run end indices
6668     // end is marked by index of the first glyph of the next run
6669     // a run is marked by same mapped font id and same Y position
6670     std::vector< sal_uInt32 > aRunEnds;
6671     aRunEnds.reserve( rGlyphs.size() );
6672     for( size_t i = 1; i < rGlyphs.size(); i++ )
6673     {
6674         if( rGlyphs[i].m_nMappedFontId != rGlyphs[i-1].m_nMappedFontId ||
6675             rGlyphs[i].m_pFont != rGlyphs[i-1].m_pFont ||
6676             rGlyphs[i].m_aPos.getY() != rGlyphs[i-1].m_aPos.getY() )
6677         {
6678             aRunEnds.push_back(i);
6679         }
6680     }
6681     // last run ends at last glyph
6682     aRunEnds.push_back( rGlyphs.size() );
6683 
6684     // loop over runs of the same font
6685     sal_uInt32 nBeginRun = 0;
6686     for( size_t nRun = 0; nRun < aRunEnds.size(); nRun++ )
6687     {
6688         // setup text matrix back transformed to current coordinate system
6689         Point aCurPos(SubPixelToLogic(rGlyphs[nBeginRun].m_aPos));
6690         aCurPos += rAlignOffset;
6691 
6692         // perform artificial italics if necessary
6693         double fSkew = 0.0;
6694         if (rGlyphs[nBeginRun].m_pFont->NeedsArtificialItalic())
6695             fSkew = ARTIFICIAL_ITALIC_SKEW;
6696 
6697         // the first run can be set with "Td" operator
6698         // subsequent use of that operator would move
6699         // the textline matrix relative to what was set before
6700         // making use of that would drive us into rounding issues
6701         Matrix3 aMat;
6702         if( bFirst && nRun == 0 && fAngle == 0.0 && fXScale == 1.0 && fSkew == 0.0 )
6703         {
6704             m_aPages.back().appendPoint( aCurPos, rLine );
6705             rLine.append( " Td " );
6706         }
6707         else
6708         {
6709             if( fSkew != 0.0 )
6710                 aMat.skew( 0.0, fSkew );
6711             aMat.scale( fXScale, 1.0 );
6712             aMat.rotate( fAngle );
6713             aMat.translate( aCurPos.X(), aCurPos.Y() );
6714             m_aPages.back().appendMatrix3(aMat, rLine);
6715             rLine.append( " Tm\n" );
6716         }
6717         // set up correct font
6718         rLine.append( "/F" );
6719         rLine.append( rGlyphs[nBeginRun].m_nMappedFontId );
6720         rLine.append( ' ' );
6721         m_aPages.back().appendMappedLength( nFontHeight, rLine );
6722         rLine.append( " Tf" );
6723 
6724         // output glyphs using Tj or TJ
6725         OStringBuffer aKernedLine( 256 ), aUnkernedLine( 256 );
6726         aKernedLine.append( "[<" );
6727         aUnkernedLine.append( '<' );
6728         COSWriter::appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aKernedLine );
6729         COSWriter::appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aUnkernedLine );
6730 
6731         aMat.invert();
6732         bool bNeedKern = false;
6733         for( sal_uInt32 nPos = nBeginRun+1; nPos < aRunEnds[nRun]; nPos++ )
6734         {
6735             COSWriter::appendHex( rGlyphs[nPos].m_nMappedGlyphId, aUnkernedLine );
6736             // check if default glyph positioning is sufficient
6737             const basegfx::B2DPoint aThisPos = aMat.transform( rGlyphs[nPos].m_aPos );
6738             const basegfx::B2DPoint aPrevPos = aMat.transform( rGlyphs[nPos-1].m_aPos );
6739             double fAdvance = aThisPos.getX() - aPrevPos.getX();
6740             fAdvance *= 1000.0 / nPixelFontHeight;
6741             const double fAdjustment = rGlyphs[nPos-1].m_nNativeWidth - fAdvance + 0.5;
6742             SAL_WARN_IF(
6743                 fAdjustment < SAL_MIN_INT32 || fAdjustment > SAL_MAX_INT32, "vcl.pdfwriter",
6744                 "adjustment " << fAdjustment << " outside 32-bit int");
6745             const sal_Int32 nAdjustment = static_cast<sal_Int32>(
6746                 std::clamp(fAdjustment, double(SAL_MIN_INT32), double(SAL_MAX_INT32)));
6747             if( nAdjustment != 0 )
6748             {
6749                 // apply individual glyph positioning
6750                 bNeedKern = true;
6751                 aKernedLine.append( ">" );
6752                 aKernedLine.append( nAdjustment );
6753                 aKernedLine.append( "<" );
6754             }
6755             COSWriter::appendHex( rGlyphs[nPos].m_nMappedGlyphId, aKernedLine );
6756         }
6757         aKernedLine.append( ">]TJ\n" );
6758         aUnkernedLine.append( ">Tj\n" );
6759         rLine.append( bNeedKern ? aKernedLine : aUnkernedLine );
6760 
6761         // set beginning of next run
6762         nBeginRun = aRunEnds[nRun];
6763     }
6764 }
6765 
drawLayout(SalLayout & rLayout,const OUString & rText,bool bTextLines)6766 void PDFWriterImpl::drawLayout( SalLayout& rLayout, const OUString& rText, bool bTextLines )
6767 {
6768     // relief takes precedence over shadow (see outdev3.cxx)
6769     if(  m_aCurrentPDFState.m_aFont.GetRelief() != FontRelief::NONE )
6770     {
6771         drawRelief( rLayout, rText, bTextLines );
6772         return;
6773     }
6774     else if( m_aCurrentPDFState.m_aFont.IsShadow() )
6775         drawShadow( rLayout, rText, bTextLines );
6776 
6777     OStringBuffer aLine( 512 );
6778 
6779     const int nMaxGlyphs = 256;
6780 
6781     std::vector<sal_Ucs> aCodeUnits;
6782     bool bVertical = m_aCurrentPDFState.m_aFont.IsVertical();
6783     int nIndex = 0;
6784     double fXScale = 1.0;
6785     sal_Int32 nPixelFontHeight = GetFontInstance()->GetFontSelectPattern().mnHeight;
6786     TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
6787 
6788     // transform font height back to current units
6789     // note: the layout calculates in outdevs device pixel !!
6790     sal_Int32 nFontHeight = ImplDevicePixelToLogicHeight( nPixelFontHeight );
6791     if( m_aCurrentPDFState.m_aFont.GetAverageFontWidth() )
6792     {
6793         Font aFont( m_aCurrentPDFState.m_aFont );
6794         aFont.SetAverageFontWidth( 0 );
6795         FontMetric aMetric = GetFontMetric( aFont );
6796         if( aMetric.GetAverageFontWidth() != m_aCurrentPDFState.m_aFont.GetAverageFontWidth() )
6797         {
6798             fXScale =
6799                 static_cast<double>(m_aCurrentPDFState.m_aFont.GetAverageFontWidth()) /
6800                 static_cast<double>(aMetric.GetAverageFontWidth());
6801         }
6802     }
6803 
6804     // if the mapmode is distorted we need to adjust for that also
6805     if( m_aCurrentPDFState.m_aMapMode.GetScaleX() != m_aCurrentPDFState.m_aMapMode.GetScaleY() )
6806     {
6807         fXScale *= double(m_aCurrentPDFState.m_aMapMode.GetScaleX()) / double(m_aCurrentPDFState.m_aMapMode.GetScaleY());
6808     }
6809 
6810     Degree10 nAngle = m_aCurrentPDFState.m_aFont.GetOrientation();
6811     // normalize angles
6812     while( nAngle < 0_deg10 )
6813         nAngle += 3600_deg10;
6814     nAngle = nAngle % 3600_deg10;
6815     double fAngle = toRadians(nAngle);
6816 
6817     Matrix3 aRotScale;
6818     aRotScale.scale( fXScale, 1.0 );
6819     if( fAngle != 0.0 )
6820         aRotScale.rotate( -fAngle );
6821 
6822     bool bPop = false;
6823     bool bABold = false;
6824     // artificial bold necessary ?
6825     if (GetFontInstance()->NeedsArtificialBold())
6826     {
6827         aLine.append("q ");
6828         bPop = true;
6829         bABold = true;
6830     }
6831     // setup text colors (if necessary)
6832     Color aStrokeColor( COL_TRANSPARENT );
6833     Color aNonStrokeColor( COL_TRANSPARENT );
6834 
6835     if( m_aCurrentPDFState.m_aFont.IsOutline() )
6836     {
6837         aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6838         aNonStrokeColor = COL_WHITE;
6839     }
6840     else
6841         aNonStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6842     if( bABold )
6843         aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6844 
6845     if( aStrokeColor != COL_TRANSPARENT && aStrokeColor != m_aCurrentPDFState.m_aLineColor )
6846     {
6847         if( ! bPop )
6848             aLine.append( "q " );
6849         bPop = true;
6850         appendStrokingColor( aStrokeColor, aLine );
6851         aLine.append( "\n" );
6852     }
6853     if( aNonStrokeColor != COL_TRANSPARENT && aNonStrokeColor != m_aCurrentPDFState.m_aFillColor )
6854     {
6855         if( ! bPop )
6856             aLine.append( "q " );
6857         bPop = true;
6858         appendNonStrokingColor( aNonStrokeColor, aLine );
6859         aLine.append( "\n" );
6860     }
6861 
6862     // begin text object
6863     aLine.append( "BT\n" );
6864     // outline attribute ?
6865     if( m_aCurrentPDFState.m_aFont.IsOutline() || bABold )
6866     {
6867         // set correct text mode, set stroke width
6868         aLine.append( "2 Tr " ); // fill, then stroke
6869 
6870         if( m_aCurrentPDFState.m_aFont.IsOutline() )
6871         {
6872             // unclear what to do in case of outline and artificial bold
6873             // for the time being outline wins
6874             aLine.append( "0.25 w \n" );
6875         }
6876         else
6877         {
6878             double fW = static_cast<double>(m_aCurrentPDFState.m_aFont.GetFontHeight()) / 30.0;
6879             m_aPages.back().appendMappedLength( fW, aLine );
6880             aLine.append ( " w\n" );
6881         }
6882     }
6883 
6884     FontMetric aRefDevFontMetric = GetFontMetric();
6885     const GlyphItem* pGlyph = nullptr;
6886     const LogicalFontInstance* pGlyphFont = nullptr;
6887 
6888     // collect the glyphs into a single array
6889     std::vector< PDFGlyph > aGlyphs;
6890     aGlyphs.reserve( nMaxGlyphs );
6891     // first get all the glyphs and register them; coordinates still in Pixel
6892     basegfx::B2DPoint aPos;
6893     while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pGlyphFont))
6894     {
6895         const auto* pFace = pGlyphFont->GetFontFace();
6896 
6897         aCodeUnits.clear();
6898 
6899         // tdf#66597, tdf#115117
6900         //
6901         // Here is how we embed textual content in PDF files, to allow for
6902         // better text extraction for complex and typography-rich text.
6903         //
6904         // * If there is many to one or many to many mapping, use an
6905         //   ActualText span embedding the original string, since ToUnicode
6906         //   can't handle these.
6907         // * If the one glyph is used for several Unicode code points, also
6908         //   use ActualText since ToUnicode can map each glyph in the font
6909         //   only once.
6910         // * Limit ActualText to single cluster at a time, since using it
6911         //   for whole words or sentences breaks text selection and
6912         //   highlighting in PDF viewers (there will be no way to tell
6913         //   which glyphs belong to which characters).
6914         // * Keep generating (now) redundant ToUnicode entries for
6915         //   compatibility with old tools not supporting ActualText.
6916 
6917         assert(pGlyph->charCount() >= 0);
6918         for (int n = 0; n < pGlyph->charCount(); n++)
6919             aCodeUnits.push_back(rText[pGlyph->charPos() + n]);
6920 
6921         bool bUseActualText = false;
6922 
6923         // If this is a start of complex cluster, use ActualText.
6924         if (pGlyph->IsClusterStart())
6925             bUseActualText = true;
6926 
6927         const auto nGlyphId = pGlyph->glyphId();
6928 
6929         // A glyph can't have more than one ToUnicode entry, use ActualText
6930         // instead.
6931         if (!aCodeUnits.empty() && !bUseActualText)
6932         {
6933             for (const auto& rSubset : m_aSubsets[pFace].m_aSubsets)
6934             {
6935                 const auto it = rSubset.m_aMapping.find(nGlyphId);
6936                 if (it != rSubset.m_aMapping.cend() && it->second.codes() != aCodeUnits)
6937                 {
6938                     bUseActualText = true;
6939                     aCodeUnits.clear();
6940                 }
6941             }
6942         }
6943 
6944         // tdf#157390: The width stored by registerGlyph() must be the actual glyph width.
6945         // This must be obtained by calling GetGlyphWidth(vertical=false), otherwise an incorrect
6946         // width value will be returned for CJK characters.
6947         auto nMappedGlyphWidth = pGlyphFont->GetGlyphWidth(nGlyphId, /*vertical*/ false, false);
6948         auto nGlyphWidth = nMappedGlyphWidth;
6949         if (pGlyph->IsVertical())
6950         {
6951             nGlyphWidth = pGlyphFont->GetGlyphWidth(nGlyphId, /*vertical*/ true, false);
6952         }
6953 
6954         sal_uInt8 nMappedGlyph;
6955         sal_Int32 nMappedFontObject;
6956         registerGlyph(nGlyphId, pFace, pGlyphFont, aCodeUnits, nMappedGlyphWidth, nMappedGlyph,
6957                       nMappedFontObject);
6958 
6959         int nCharPos = -1;
6960         if (bUseActualText || pGlyph->IsInCluster())
6961             nCharPos = pGlyph->charPos();
6962 
6963         aGlyphs.emplace_back(aPos,
6964                              pGlyph,
6965                              pGlyphFont,
6966                              XUnits(pFace->UnitsPerEm(), nGlyphWidth),
6967                              nMappedFontObject,
6968                              nMappedGlyph,
6969                              nCharPos);
6970     }
6971 
6972     // Avoid fill color when map mode is in pixels, the below code assumes
6973     // logic map mode.
6974     bool bPixel = m_aCurrentPDFState.m_aMapMode.GetMapUnit() == MapUnit::MapPixel;
6975     if (m_aCurrentPDFState.m_aFont.GetFillColor() != COL_TRANSPARENT && !bPixel)
6976     {
6977         // PDF doesn't have a text fill color, so draw a rectangle before
6978         // drawing the actual text.
6979         push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR);
6980         setFillColor(m_aCurrentPDFState.m_aFont.GetFillColor());
6981         // Avoid border around the rectangle for Writer shape text.
6982         setLineColor(COL_TRANSPARENT);
6983 
6984         // The rectangle is the bounding box of the text, but also includes
6985         // ascent / descent to match the on-screen rendering.
6986         // This is the top left of the text without ascent / descent.
6987         basegfx::B2DPoint aDrawPosition(rLayout.GetDrawPosition());
6988         tools::Rectangle aRectangle(SubPixelToLogic(aDrawPosition),
6989                                     Size(ImplDevicePixelToLogicWidth(rLayout.GetTextWidth()), 0));
6990         aRectangle.AdjustTop(-aRefDevFontMetric.GetAscent());
6991         // This includes ascent / descent.
6992         aRectangle.setHeight(aRefDevFontMetric.GetLineHeight());
6993 
6994         const LogicalFontInstance* pFontInstance = GetFontInstance();
6995         if (pFontInstance->mnOrientation)
6996         {
6997             // Adapt rectangle for rotated text.
6998             tools::Polygon aPolygon(aRectangle);
6999             aPolygon.Rotate(SubPixelToLogic(aDrawPosition), pFontInstance->mnOrientation);
7000             drawPolygon(aPolygon);
7001         }
7002         else
7003             drawRectangle(aRectangle);
7004 
7005         pop();
7006     }
7007 
7008     Point aAlignOffset;
7009     if ( eAlign == ALIGN_BOTTOM )
7010         aAlignOffset.AdjustY( -(aRefDevFontMetric.GetDescent()) );
7011     else if ( eAlign == ALIGN_TOP )
7012         aAlignOffset.AdjustY(aRefDevFontMetric.GetAscent() );
7013     if( aAlignOffset.X() || aAlignOffset.Y() )
7014         aAlignOffset = aRotScale.transform( aAlignOffset );
7015 
7016     /* #159153# do not emit an empty glyph vector; this can happen if e.g. the original
7017        string contained only one of the UTF16 BOMs
7018     */
7019     if( ! aGlyphs.empty() )
7020     {
7021         size_t nStart = 0;
7022         size_t nEnd = 0;
7023         while (nStart < aGlyphs.size())
7024         {
7025             while (nEnd < aGlyphs.size() && aGlyphs[nEnd].m_nCharPos == aGlyphs[nStart].m_nCharPos)
7026                 nEnd++;
7027 
7028             std::vector<PDFGlyph> aRun(aGlyphs.begin() + nStart, aGlyphs.begin() + nEnd);
7029 
7030             int nCharPos, nCharCount;
7031             if (!aRun.front().m_pGlyph->IsRTLGlyph())
7032             {
7033                 nCharPos = aRun.front().m_nCharPos;
7034                 nCharCount = aRun.front().m_pGlyph->charCount();
7035             }
7036             else
7037             {
7038                 nCharPos = aRun.back().m_nCharPos;
7039                 nCharCount = aRun.back().m_pGlyph->charCount();
7040             }
7041 
7042             if (nCharPos >= 0 && nCharCount)
7043             {
7044                 aLine.append("/Span<</ActualText<FEFF");
7045                 for (int i = 0; i < nCharCount; i++)
7046                 {
7047                     sal_Unicode aChar = rText[nCharPos + i];
7048                     COSWriter::appendHex(static_cast<sal_Int8>(aChar >> 8), aLine);
7049                     COSWriter::appendHex(static_cast<sal_Int8>(aChar & 255), aLine);
7050                 }
7051                 aLine.append( ">>>\nBDC\n" );
7052             }
7053 
7054             if (bVertical)
7055                 drawVerticalGlyphs(aRun, aLine, aAlignOffset, aRotScale, fAngle, fXScale, nFontHeight);
7056             else
7057                 drawHorizontalGlyphs(aRun, aLine, aAlignOffset, nStart == 0, fAngle, fXScale, nFontHeight, nPixelFontHeight);
7058 
7059             if (nCharPos >= 0 && nCharCount)
7060                 aLine.append( "EMC\n" );
7061 
7062             nStart = nEnd;
7063         }
7064     }
7065 
7066     // end textobject
7067     aLine.append( "ET\n" );
7068     if( bPop )
7069         aLine.append( "Q\n" );
7070 
7071     writeBuffer( aLine );
7072 
7073     // draw eventual textlines
7074     FontStrikeout eStrikeout = m_aCurrentPDFState.m_aFont.GetStrikeout();
7075     FontLineStyle eUnderline = m_aCurrentPDFState.m_aFont.GetUnderline();
7076     FontLineStyle eOverline  = m_aCurrentPDFState.m_aFont.GetOverline();
7077     if( bTextLines &&
7078         (
7079          ( eUnderline != LINESTYLE_NONE && eUnderline != LINESTYLE_DONTKNOW ) ||
7080          ( eOverline  != LINESTYLE_NONE && eOverline  != LINESTYLE_DONTKNOW ) ||
7081          ( eStrikeout != STRIKEOUT_NONE && eStrikeout != STRIKEOUT_DONTKNOW )
7082          )
7083         )
7084     {
7085         bool bUnderlineAbove = m_aCurrentPDFState.m_aFont.IsUnderlineAbove();
7086         if( m_aCurrentPDFState.m_aFont.IsWordLineMode() )
7087         {
7088             basegfx::B2DPoint aStartPt;
7089             double nWidth = 0;
7090             nIndex = 0;
7091             while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex))
7092             {
7093                 if (!pGlyph->IsSpacing())
7094                 {
7095                     if( !nWidth )
7096                         aStartPt = aPos;
7097 
7098                     nWidth += pGlyph->newWidth();
7099                 }
7100                 else if( nWidth > 0 )
7101                 {
7102                     drawTextLine( SubPixelToLogic(aStartPt),
7103                                   ImplDevicePixelToLogicWidth( nWidth ),
7104                                   eStrikeout, eUnderline, eOverline, bUnderlineAbove );
7105                     nWidth = 0;
7106                 }
7107             }
7108 
7109             if( nWidth > 0 )
7110             {
7111                 drawTextLine( SubPixelToLogic(aStartPt),
7112                               ImplDevicePixelToLogicWidth( nWidth ),
7113                               eStrikeout, eUnderline, eOverline, bUnderlineAbove );
7114             }
7115         }
7116         else
7117         {
7118             basegfx::B2DPoint aStartPt = rLayout.GetDrawPosition();
7119             int nWidth = rLayout.GetTextWidth();
7120             drawTextLine( SubPixelToLogic(aStartPt),
7121                           ImplDevicePixelToLogicWidth( nWidth ),
7122                           eStrikeout, eUnderline, eOverline, bUnderlineAbove );
7123         }
7124     }
7125 
7126     // write eventual emphasis marks
7127     if( !(m_aCurrentPDFState.m_aFont.GetEmphasisMark() & FontEmphasisMark::Style) )
7128         return;
7129 
7130     push( PushFlags::ALL );
7131 
7132     aLine.setLength( 0 );
7133     aLine.append( "q\n" );
7134 
7135     FontEmphasisMark nEmphMark = m_aCurrentPDFState.m_aFont.GetEmphasisMarkStyle();
7136 
7137     tools::Long nEmphHeight;
7138     if ( nEmphMark & FontEmphasisMark::PosBelow )
7139         nEmphHeight = GetEmphasisDescent();
7140     else
7141         nEmphHeight = GetEmphasisAscent();
7142 
7143     vcl::font::EmphasisMark aEmphasisMark(nEmphMark, ImplDevicePixelToLogicWidth(nEmphHeight), GetDPIY());
7144     if ( aEmphasisMark.IsShapePolyLine() )
7145     {
7146         setLineColor( m_aCurrentPDFState.m_aFont.GetColor() );
7147         setFillColor( COL_TRANSPARENT );
7148     }
7149     else
7150     {
7151         setFillColor( m_aCurrentPDFState.m_aFont.GetColor() );
7152         setLineColor( COL_TRANSPARENT );
7153     }
7154 
7155     writeBuffer( aLine );
7156 
7157     Point aOffset(0,0);
7158     Point aOffsetVert(0,0);
7159 
7160     if ( nEmphMark & FontEmphasisMark::PosBelow )
7161     {
7162         aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset() );
7163         aOffsetVert = aOffset;
7164     }
7165     else
7166     {
7167         aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetAscent() + aEmphasisMark.GetYOffset()) );
7168         // Todo: use ideographic em-box or ideographic character face information.
7169         aOffsetVert.AdjustY(-(GetFontInstance()->mxFontMetric->GetAscent() +
7170                     GetFontInstance()->mxFontMetric->GetDescent() + aEmphasisMark.GetYOffset()));
7171     }
7172 
7173     tools::Long nEmphWidth2 = aEmphasisMark.GetWidth() / 2;
7174     tools::Long nEmphHeight2 = nEmphHeight / 2;
7175     aOffset += Point( nEmphWidth2, nEmphHeight2 );
7176 
7177     if ( eAlign == ALIGN_BOTTOM )
7178         aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetDescent()) );
7179     else if ( eAlign == ALIGN_TOP )
7180         aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetAscent() );
7181 
7182     basegfx::B2DRectangle aRectangle;
7183     nIndex = 0;
7184     while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pGlyphFont))
7185     {
7186         if (!pGlyph->GetGlyphBoundRect(pGlyphFont, aRectangle))
7187             continue;
7188 
7189         if (!pGlyph->IsSpacing())
7190         {
7191             basegfx::B2DPoint aAdjOffset;
7192             if (pGlyph->IsVertical())
7193             {
7194                 aAdjOffset = basegfx::B2DPoint(aOffsetVert.X(), aOffsetVert.Y());
7195                 aAdjOffset.adjustX((-pGlyph->origWidth() + aEmphasisMark.GetWidth()) / 2);
7196             }
7197             else
7198             {
7199                 aAdjOffset = basegfx::B2DPoint(aOffset.X(), aOffset.Y());
7200                 aAdjOffset.adjustX(aRectangle.getMinX() + (aRectangle.getWidth() - aEmphasisMark.GetWidth()) / 2 );
7201             }
7202 
7203             aAdjOffset = aRotScale.transform( aAdjOffset );
7204 
7205             aAdjOffset -= basegfx::B2DPoint(nEmphWidth2, nEmphHeight2);
7206 
7207             basegfx::B2DPoint aMarkDevPos(aPos);
7208             aMarkDevPos += aAdjOffset;
7209             Point aMarkPos = SubPixelToLogic(aMarkDevPos);
7210             drawEmphasisMark( aMarkPos.X(), aMarkPos.Y(),
7211                               aEmphasisMark.GetShape(), aEmphasisMark.IsShapePolyLine(),
7212                               aEmphasisMark.GetRect1(), aEmphasisMark.GetRect2() );
7213         }
7214     }
7215 
7216     writeBuffer( "Q\n" );
7217     pop();
7218 
7219 }
7220 
drawEmphasisMark(tools::Long nX,tools::Long nY,const tools::PolyPolygon & rPolyPoly,bool bPolyLine,const tools::Rectangle & rRect1,const tools::Rectangle & rRect2)7221 void PDFWriterImpl::drawEmphasisMark( tools::Long nX, tools::Long nY,
7222                                       const tools::PolyPolygon& rPolyPoly, bool bPolyLine,
7223                                       const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 )
7224 {
7225     // TODO: pass nWidth as width of this mark
7226     // long nWidth = 0;
7227 
7228     if ( rPolyPoly.Count() )
7229     {
7230         if ( bPolyLine )
7231         {
7232             tools::Polygon aPoly = rPolyPoly.GetObject( 0 );
7233             aPoly.Move( nX, nY );
7234             drawPolyLine( aPoly );
7235         }
7236         else
7237         {
7238             tools::PolyPolygon aPolyPoly = rPolyPoly;
7239             aPolyPoly.Move( nX, nY );
7240             drawPolyPolygon( aPolyPoly );
7241         }
7242     }
7243 
7244     if ( !rRect1.IsEmpty() )
7245     {
7246         tools::Rectangle aRect( Point( nX+rRect1.Left(),
7247                                 nY+rRect1.Top() ), rRect1.GetSize() );
7248         drawRectangle( aRect );
7249     }
7250 
7251     if ( !rRect2.IsEmpty() )
7252     {
7253         tools::Rectangle aRect( Point( nX+rRect2.Left(),
7254                                 nY+rRect2.Top() ), rRect2.GetSize() );
7255 
7256         drawRectangle( aRect );
7257     }
7258 }
7259 
drawText(const Point & rPos,const OUString & rText,sal_Int32 nIndex,sal_Int32 nLen,bool bTextLines)7260 void PDFWriterImpl::drawText( const Point& rPos, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen, bool bTextLines )
7261 {
7262     MARK( "drawText" );
7263 
7264     updateGraphicsState();
7265 
7266     // get a layout from the OutputDevice's SalGraphics
7267     // this also enforces font substitution and sets the font on SalGraphics
7268     const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->
7269         GetLayoutGlyphs( this, rText, nIndex, nLen );
7270     std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos,
7271         0, {}, {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs );
7272     if( pLayout )
7273     {
7274         drawLayout( *pLayout, rText, bTextLines );
7275     }
7276 }
7277 
drawTextArray(const Point & rPos,const OUString & rText,KernArraySpan pDXArray,std::span<const sal_Bool> pKashidaArray,sal_Int32 nIndex,sal_Int32 nLen,sal_Int32 nLayoutContextIndex,sal_Int32 nLayoutContextLen)7278 void PDFWriterImpl::drawTextArray(const Point& rPos, const OUString& rText, KernArraySpan pDXArray,
7279                                   std::span<const sal_Bool> pKashidaArray, sal_Int32 nIndex,
7280                                   sal_Int32 nLen, sal_Int32 nLayoutContextIndex,
7281                                   sal_Int32 nLayoutContextLen)
7282 {
7283     MARK( "drawText with array" );
7284 
7285     updateGraphicsState();
7286 
7287     // get a layout from the OutputDevice's SalGraphics
7288     // this also enforces font substitution and sets the font on SalGraphics
7289     std::unique_ptr<SalLayout> pLayout;
7290     if (nLayoutContextIndex >= 0)
7291     {
7292         const auto* layoutGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(
7293             this, rText, nLayoutContextIndex, nLayoutContextLen, nIndex, nIndex + nLen);
7294         pLayout = ImplLayout(rText, nLayoutContextIndex, nLayoutContextLen, rPos, 0, pDXArray,
7295                              pKashidaArray, SalLayoutFlags::UnclusteredGlyphs, nullptr,
7296                              layoutGlyphs, /*nDrawOriginCluster=*/nIndex,
7297                              /*nDrawMinCharPos=*/nIndex, /*nDrawEndCharPos=*/nIndex + nLen);
7298     }
7299     else
7300     {
7301         const auto* layoutGlyphs
7302             = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(this, rText, nIndex, nLen);
7303         pLayout = ImplLayout(rText, nIndex, nLen, rPos, 0, pDXArray, pKashidaArray,
7304                              SalLayoutFlags::NONE, nullptr, layoutGlyphs);
7305     }
7306 
7307     if( pLayout )
7308     {
7309         drawLayout( *pLayout, rText, true );
7310     }
7311 }
7312 
drawStretchText(const Point & rPos,sal_Int32 nWidth,const OUString & rText,sal_Int32 nIndex,sal_Int32 nLen)7313 void PDFWriterImpl::drawStretchText( const Point& rPos, sal_Int32 nWidth, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen )
7314 {
7315     MARK( "drawStretchText" );
7316 
7317     updateGraphicsState();
7318 
7319     // get a layout from the OutputDevice's SalGraphics
7320     // this also enforces font substitution and sets the font on SalGraphics
7321     const SalLayoutGlyphs* layoutGlyphs = SalLayoutGlyphsCache::self()->
7322         GetLayoutGlyphs( this, rText, nIndex, nLen, nWidth );
7323     std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, nWidth,
7324         {}, {}, SalLayoutFlags::NONE, nullptr, layoutGlyphs );
7325     if( pLayout )
7326     {
7327         drawLayout( *pLayout, rText, true );
7328     }
7329 }
7330 
drawText(const tools::Rectangle & rRect,const OUString & rOrigStr,DrawTextFlags nStyle)7331 void PDFWriterImpl::drawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle )
7332 {
7333     tools::Long        nWidth          = rRect.GetWidth();
7334     tools::Long        nHeight         = rRect.GetHeight();
7335 
7336     if ( nWidth <= 0 || nHeight <= 0 )
7337         return;
7338 
7339     MARK( "drawText with rectangle" );
7340 
7341     updateGraphicsState();
7342 
7343     // clip with rectangle
7344     OStringBuffer aLine;
7345     aLine.append( "q " );
7346     m_aPages.back().appendRect( rRect, aLine );
7347     aLine.append( " W* n\n" );
7348     writeBuffer( aLine );
7349 
7350     // if disabled text is needed, put in here
7351 
7352     Point       aPos            = rRect.TopLeft();
7353 
7354     tools::Long        nTextHeight     = GetTextHeight();
7355 
7356     OUString aStr = rOrigStr;
7357     if ( nStyle & DrawTextFlags::Mnemonic )
7358         aStr = removeMnemonicFromString(aStr);
7359 
7360     // multiline text
7361     if ( nStyle & DrawTextFlags::MultiLine )
7362     {
7363         ImplMultiTextLineInfo   aMultiLineInfo;
7364         sal_Int32               i;
7365         sal_Int32               nFormatLines;
7366 
7367         if ( nTextHeight )
7368         {
7369             vcl::DefaultTextLayout aLayout( *this );
7370             OUString               aLastLine;
7371             aLayout.GetTextLines( rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle );
7372             sal_Int32              nLines = nHeight/nTextHeight;
7373             nFormatLines = aMultiLineInfo.Count();
7374             if ( !nLines )
7375                 nLines = 1;
7376             if ( nFormatLines > nLines )
7377             {
7378                 if ( nStyle & DrawTextFlags::EndEllipsis )
7379                 {
7380                     // handle last line
7381                     nFormatLines = nLines-1;
7382 
7383                     ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( nFormatLines );
7384                     aLastLine = convertLineEnd(aStr.copy(rLineInfo.GetIndex()), LINEEND_LF);
7385                     // replace line feed by space
7386                     aLastLine = aLastLine.replace('\n', ' ');
7387                     aLastLine = GetEllipsisString( aLastLine, nWidth, nStyle );
7388                     nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom);
7389                     nStyle |= DrawTextFlags::Top;
7390                 }
7391             }
7392 
7393             // vertical alignment
7394             if ( nStyle & DrawTextFlags::Bottom )
7395                 aPos.AdjustY(nHeight-(nFormatLines*nTextHeight) );
7396             else if ( nStyle & DrawTextFlags::VCenter )
7397                 aPos.AdjustY((nHeight-(nFormatLines*nTextHeight))/2 );
7398 
7399             // draw all lines excluding the last
7400             for ( i = 0; i < nFormatLines; i++ )
7401             {
7402                 ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i );
7403                 if ( nStyle & DrawTextFlags::Right )
7404                     aPos.AdjustX(nWidth-rLineInfo.GetWidth() );
7405                 else if ( nStyle & DrawTextFlags::Center )
7406                     aPos.AdjustX((nWidth-rLineInfo.GetWidth())/2 );
7407                 sal_Int32 nIndex = rLineInfo.GetIndex();
7408                 sal_Int32 nLineLen = rLineInfo.GetLen();
7409                 drawText( aPos, aStr, nIndex, nLineLen );
7410                 // mnemonics should not appear in documents,
7411                 // if the need arises, put them in here
7412                 aPos.AdjustY(nTextHeight );
7413                 aPos.setX( rRect.Left() );
7414             }
7415 
7416             // output last line left adjusted since it was shortened
7417             if (!aLastLine.isEmpty())
7418                 drawText( aPos, aLastLine, 0, aLastLine.getLength() );
7419         }
7420     }
7421     else
7422     {
7423         tools::Long nTextWidth = GetTextWidth( aStr );
7424 
7425         // Evt. Text kuerzen
7426         if ( nTextWidth > nWidth )
7427         {
7428             if ( nStyle & (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis) )
7429             {
7430                 aStr = GetEllipsisString( aStr, nWidth, nStyle );
7431                 nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right);
7432                 nStyle |= DrawTextFlags::Left;
7433                 nTextWidth = GetTextWidth( aStr );
7434             }
7435         }
7436 
7437         // vertical alignment
7438         if ( nStyle & DrawTextFlags::Right )
7439             aPos.AdjustX(nWidth-nTextWidth );
7440         else if ( nStyle & DrawTextFlags::Center )
7441             aPos.AdjustX((nWidth-nTextWidth)/2 );
7442 
7443         if ( nStyle & DrawTextFlags::Bottom )
7444             aPos.AdjustY(nHeight-nTextHeight );
7445         else if ( nStyle & DrawTextFlags::VCenter )
7446             aPos.AdjustY((nHeight-nTextHeight)/2 );
7447 
7448         // mnemonics should be inserted here if the need arises
7449 
7450         // draw the actual text
7451         drawText( aPos, aStr, 0, aStr.getLength() );
7452     }
7453 
7454     // reset clip region to original value
7455     aLine.setLength( 0 );
7456     aLine.append( "Q\n" );
7457     writeBuffer( aLine );
7458 }
7459 
drawLine(const Point & rStart,const Point & rStop)7460 void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop )
7461 {
7462     MARK( "drawLine" );
7463 
7464     updateGraphicsState();
7465 
7466     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
7467         return;
7468 
7469     OStringBuffer aLine;
7470     m_aPages.back().appendPoint( rStart, aLine );
7471     aLine.append( " m " );
7472     m_aPages.back().appendPoint( rStop, aLine );
7473     aLine.append( " l S\n" );
7474 
7475     writeBuffer( aLine );
7476 }
7477 
drawLine(const Point & rStart,const Point & rStop,const LineInfo & rInfo)7478 void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo )
7479 {
7480     MARK( "drawLine with LineInfo" );
7481     updateGraphicsState();
7482 
7483     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
7484         return;
7485 
7486     if( rInfo.GetStyle() == LineStyle::Solid && rInfo.GetWidth() < 2 )
7487     {
7488         drawLine( rStart, rStop );
7489         return;
7490     }
7491 
7492     OStringBuffer aLine;
7493 
7494     aLine.append( "q " );
7495     if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
7496     {
7497         m_aPages.back().appendPoint( rStart, aLine );
7498         aLine.append( " m " );
7499         m_aPages.back().appendPoint( rStop, aLine );
7500         aLine.append( " l S Q\n" );
7501 
7502         writeBuffer( aLine );
7503     }
7504     else
7505     {
7506         PDFWriter::ExtLineInfo aInfo;
7507         convertLineInfoToExtLineInfo( rInfo, aInfo );
7508         Point aPolyPoints[2] = { rStart, rStop };
7509         tools::Polygon aPoly( 2, aPolyPoints );
7510         drawPolyLine( aPoly, aInfo );
7511     }
7512 }
7513 
7514 #define HCONV( x ) ImplDevicePixelToLogicHeight( x )
7515 
drawWaveTextLine(OStringBuffer & aLine,tools::Long nWidth,FontLineStyle eTextLine,Color aColor,bool bIsAbove)7516 void PDFWriterImpl::drawWaveTextLine( OStringBuffer& aLine, tools::Long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
7517 {
7518     // note: units in pFontInstance are ref device pixel
7519     const LogicalFontInstance*  pFontInstance = GetFontInstance();
7520     tools::Long            nLineHeight = 0;
7521     tools::Long            nLinePos = 0;
7522 
7523     appendStrokingColor( aColor, aLine );
7524     aLine.append( "\n" );
7525 
7526     if ( bIsAbove )
7527     {
7528         if ( !pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() )
7529             ImplInitAboveTextLineSize();
7530         nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() );
7531         nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineOffset() );
7532     }
7533     else
7534     {
7535         if ( !pFontInstance->mxFontMetric->GetWavelineUnderlineSize() )
7536             ImplInitTextLineSize();
7537         nLineHeight = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineSize() );
7538         nLinePos = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineOffset() );
7539     }
7540     if ( (eTextLine == LINESTYLE_SMALLWAVE) && (nLineHeight > 3) )
7541         nLineHeight = 3;
7542 
7543     tools::Long nLineWidth = GetDPIX()/450;
7544     if ( ! nLineWidth )
7545         nLineWidth = 1;
7546 
7547     if ( eTextLine == LINESTYLE_BOLDWAVE )
7548         nLineWidth = 3*nLineWidth;
7549 
7550     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineWidth), aLine );
7551     aLine.append( " w " );
7552 
7553     if ( eTextLine == LINESTYLE_DOUBLEWAVE )
7554     {
7555         tools::Long nOrgLineHeight = nLineHeight;
7556         nLineHeight /= 3;
7557         if ( nLineHeight < 2 )
7558         {
7559             if ( nOrgLineHeight > 1 )
7560                 nLineHeight = 2;
7561             else
7562                 nLineHeight = 1;
7563         }
7564         tools::Long nLineDY = nOrgLineHeight-(nLineHeight*2);
7565         if ( nLineDY < nLineWidth )
7566             nLineDY = nLineWidth;
7567         tools::Long nLineDY2 = nLineDY/2;
7568         if ( !nLineDY2 )
7569             nLineDY2 = 1;
7570 
7571         nLinePos -= nLineWidth-nLineDY2;
7572 
7573         m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
7574 
7575         nLinePos += nLineWidth+nLineDY;
7576         m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
7577     }
7578     else
7579     {
7580         if ( eTextLine != LINESTYLE_BOLDWAVE )
7581             nLinePos -= nLineWidth/2;
7582         m_aPages.back().appendWaveLine( nWidth, -nLinePos, nLineHeight, aLine );
7583     }
7584 }
7585 
drawStraightTextLine(OStringBuffer & aLine,tools::Long nWidth,FontLineStyle eTextLine,Color aColor,bool bIsAbove)7586 void PDFWriterImpl::drawStraightTextLine( OStringBuffer& aLine, tools::Long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
7587 {
7588     // note: units in pFontInstance are ref device pixel
7589     const LogicalFontInstance*  pFontInstance = GetFontInstance();
7590     tools::Long            nLineHeight = 0;
7591     tools::Long            nLinePos  = 0;
7592     tools::Long            nLinePos2 = 0;
7593 
7594     if ( eTextLine > LINESTYLE_BOLDWAVE )
7595         eTextLine = LINESTYLE_SINGLE;
7596 
7597     switch ( eTextLine )
7598     {
7599         case LINESTYLE_SINGLE:
7600         case LINESTYLE_DOTTED:
7601         case LINESTYLE_DASH:
7602         case LINESTYLE_LONGDASH:
7603         case LINESTYLE_DASHDOT:
7604         case LINESTYLE_DASHDOTDOT:
7605             if ( bIsAbove )
7606             {
7607                 if ( !pFontInstance->mxFontMetric->GetAboveUnderlineSize() )
7608                     ImplInitAboveTextLineSize();
7609                 nLineHeight = pFontInstance->mxFontMetric->GetAboveUnderlineSize();
7610                 nLinePos    = pFontInstance->mxFontMetric->GetAboveUnderlineOffset();
7611             }
7612             else
7613             {
7614                 if ( !pFontInstance->mxFontMetric->GetUnderlineSize() )
7615                     ImplInitTextLineSize();
7616                 nLineHeight = pFontInstance->mxFontMetric->GetUnderlineSize();
7617                 nLinePos    = pFontInstance->mxFontMetric->GetUnderlineOffset();
7618             }
7619             break;
7620         case LINESTYLE_BOLD:
7621         case LINESTYLE_BOLDDOTTED:
7622         case LINESTYLE_BOLDDASH:
7623         case LINESTYLE_BOLDLONGDASH:
7624         case LINESTYLE_BOLDDASHDOT:
7625         case LINESTYLE_BOLDDASHDOTDOT:
7626             if ( bIsAbove )
7627             {
7628                 if ( !pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() )
7629                     ImplInitAboveTextLineSize();
7630                 nLineHeight = pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize();
7631                 nLinePos    = pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset();
7632             }
7633             else
7634             {
7635                 if ( !pFontInstance->mxFontMetric->GetBoldUnderlineSize() )
7636                     ImplInitTextLineSize();
7637                 nLineHeight = pFontInstance->mxFontMetric->GetBoldUnderlineSize();
7638                 nLinePos    = pFontInstance->mxFontMetric->GetBoldUnderlineOffset();
7639             }
7640             break;
7641         case LINESTYLE_DOUBLE:
7642             if ( bIsAbove )
7643             {
7644                 if ( !pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() )
7645                     ImplInitAboveTextLineSize();
7646                 nLineHeight = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize();
7647                 nLinePos    = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1();
7648                 nLinePos2   = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2();
7649             }
7650             else
7651             {
7652                 if ( !pFontInstance->mxFontMetric->GetDoubleUnderlineSize() )
7653                     ImplInitTextLineSize();
7654                 nLineHeight = pFontInstance->mxFontMetric->GetDoubleUnderlineSize();
7655                 nLinePos    = pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1();
7656                 nLinePos2   = pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2();
7657             }
7658             break;
7659         default:
7660             break;
7661     }
7662 
7663     if ( !nLineHeight )
7664         return;
7665 
7666     // tdf#154235
7667     // nLinePos/nLinePos2 is the distance from baseline to the top of the line,
7668     // while in PDF we stroke the line so the position is to the middle of the
7669     // line, we add half of nLineHeight to account for that.
7670     auto nOffset = nLineHeight / 2;
7671     if (m_aCurrentPDFState.m_aFont.IsOutline() && eTextLine == LINESTYLE_SINGLE)
7672     {
7673         // Except when outlining, as now we are drawing a rectangle, so
7674         // nLinePos is the bottom of the rectangle, so need to add nLineHeight
7675         // to it.
7676         nOffset = nLineHeight;
7677     }
7678 
7679     nLineHeight = HCONV(nLineHeight);
7680     nLinePos = HCONV(nLinePos + nOffset);
7681     nLinePos2 = HCONV(nLinePos2 + nOffset);
7682 
7683     // outline attribute ?
7684     if (m_aCurrentPDFState.m_aFont.IsOutline() && eTextLine == LINESTYLE_SINGLE)
7685     {
7686         appendStrokingColor(aColor, aLine); // stroke with text color
7687         aLine.append( " " );
7688         appendNonStrokingColor(COL_WHITE, aLine); // fill with white
7689         aLine.append( "\n" );
7690         aLine.append( "0.25 w \n" ); // same line thickness as in drawLayout
7691 
7692         // draw rectangle instead
7693         aLine.append( "0 " );
7694         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7695         aLine.append( " " );
7696         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
7697         aLine.append( ' ' );
7698         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7699         aLine.append( " re h B\n" );
7700         return;
7701     }
7702 
7703 
7704     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7705     aLine.append( " w " );
7706     appendStrokingColor( aColor, aLine );
7707     aLine.append( "\n" );
7708 
7709     switch ( eTextLine )
7710     {
7711         case LINESTYLE_DOTTED:
7712         case LINESTYLE_BOLDDOTTED:
7713             aLine.append( "[ " );
7714             m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7715             aLine.append( " ] 0 d\n" );
7716             break;
7717         case LINESTYLE_DASH:
7718         case LINESTYLE_LONGDASH:
7719         case LINESTYLE_BOLDDASH:
7720         case LINESTYLE_BOLDLONGDASH:
7721             {
7722                 sal_Int32 nDashLength = 4*nLineHeight;
7723                 sal_Int32 nVoidLength = 2*nLineHeight;
7724                 if ( ( eTextLine == LINESTYLE_LONGDASH ) || ( eTextLine == LINESTYLE_BOLDLONGDASH ) )
7725                     nDashLength = 8*nLineHeight;
7726 
7727                 aLine.append( "[ " );
7728                 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7729                 aLine.append( ' ' );
7730                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7731                 aLine.append( " ] 0 d\n" );
7732             }
7733             break;
7734         case LINESTYLE_DASHDOT:
7735         case LINESTYLE_BOLDDASHDOT:
7736             {
7737                 sal_Int32 nDashLength = 4*nLineHeight;
7738                 sal_Int32 nVoidLength = 2*nLineHeight;
7739                 aLine.append( "[ " );
7740                 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7741                 aLine.append( ' ' );
7742                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7743                 aLine.append( ' ' );
7744                 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7745                 aLine.append( ' ' );
7746                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7747                 aLine.append( " ] 0 d\n" );
7748             }
7749             break;
7750         case LINESTYLE_DASHDOTDOT:
7751         case LINESTYLE_BOLDDASHDOTDOT:
7752             {
7753                 sal_Int32 nDashLength = 4*nLineHeight;
7754                 sal_Int32 nVoidLength = 2*nLineHeight;
7755                 aLine.append( "[ " );
7756                 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7757                 aLine.append( ' ' );
7758                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7759                 aLine.append( ' ' );
7760                 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7761                 aLine.append( ' ' );
7762                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7763                 aLine.append( ' ' );
7764                 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7765                 aLine.append( ' ' );
7766                 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7767                 aLine.append( " ] 0 d\n" );
7768             }
7769             break;
7770         default:
7771             break;
7772     }
7773 
7774     aLine.append( "0 " );
7775     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7776     aLine.append( " m " );
7777     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
7778     aLine.append( ' ' );
7779     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7780     aLine.append( " l S\n" );
7781     if ( eTextLine == LINESTYLE_DOUBLE )
7782     {
7783         aLine.append( "0 " );
7784         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7785         aLine.append( " m " );
7786         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
7787         aLine.append( ' ' );
7788         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7789         aLine.append( " l S\n" );
7790     }
7791 
7792 }
7793 
drawStrikeoutLine(OStringBuffer & aLine,tools::Long nWidth,FontStrikeout eStrikeout,Color aColor)7794 void PDFWriterImpl::drawStrikeoutLine( OStringBuffer& aLine, tools::Long nWidth, FontStrikeout eStrikeout, Color aColor )
7795 {
7796     // note: units in pFontInstance are ref device pixel
7797     const LogicalFontInstance*  pFontInstance = GetFontInstance();
7798     tools::Long            nLineHeight = 0;
7799     tools::Long            nLinePos  = 0;
7800     tools::Long            nLinePos2 = 0;
7801 
7802     if ( eStrikeout > STRIKEOUT_X )
7803         eStrikeout = STRIKEOUT_SINGLE;
7804 
7805     switch ( eStrikeout )
7806     {
7807         case STRIKEOUT_SINGLE:
7808             if ( !pFontInstance->mxFontMetric->GetStrikeoutSize() )
7809                 ImplInitTextLineSize();
7810             nLineHeight = pFontInstance->mxFontMetric->GetStrikeoutSize();
7811             nLinePos    = pFontInstance->mxFontMetric->GetStrikeoutOffset();
7812             break;
7813         case STRIKEOUT_BOLD:
7814             if ( !pFontInstance->mxFontMetric->GetBoldStrikeoutSize() )
7815                 ImplInitTextLineSize();
7816             nLineHeight = pFontInstance->mxFontMetric->GetBoldStrikeoutSize();
7817             nLinePos    = pFontInstance->mxFontMetric->GetBoldStrikeoutOffset();
7818             break;
7819         case STRIKEOUT_DOUBLE:
7820             if ( !pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() )
7821                 ImplInitTextLineSize();
7822             nLineHeight = pFontInstance->mxFontMetric->GetDoubleStrikeoutSize();
7823             nLinePos    = pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1();
7824             nLinePos2   = pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2();
7825             break;
7826         default:
7827             break;
7828     }
7829 
7830     if ( !nLineHeight )
7831         return;
7832 
7833     // tdf#154235
7834     // nLinePos/nLinePos2 is the distance from baseline to the bottom of the line,
7835     // while in PDF we stroke the line so the position is to the middle of the
7836     // line, we add half of nLineHeight to account for that.
7837     nLinePos = HCONV(nLinePos + nLineHeight / 2);
7838     nLinePos2 = HCONV(nLinePos2 + nLineHeight / 2);
7839     nLineHeight = HCONV(nLineHeight);
7840 
7841     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7842     aLine.append( " w " );
7843     appendStrokingColor( aColor, aLine );
7844     aLine.append( "\n" );
7845 
7846     aLine.append( "0 " );
7847     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7848     aLine.append( " m " );
7849     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
7850     aLine.append( ' ' );
7851     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7852     aLine.append( " l S\n" );
7853 
7854     if ( eStrikeout == STRIKEOUT_DOUBLE )
7855     {
7856         aLine.append( "0 " );
7857         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7858         aLine.append( " m " );
7859         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
7860         aLine.append( ' ' );
7861         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7862         aLine.append( " l S\n" );
7863     }
7864 
7865 }
7866 
drawStrikeoutChar(const Point & rPos,tools::Long nWidth,FontStrikeout eStrikeout)7867 void PDFWriterImpl::drawStrikeoutChar( const Point& rPos, tools::Long nWidth, FontStrikeout eStrikeout )
7868 {
7869     //See qadevOOo/testdocs/StrikeThrough.odt for examples if you need
7870     //to tweak this
7871 
7872     OUString aStrikeoutChar = eStrikeout == STRIKEOUT_SLASH ? u"/"_ustr : u"X"_ustr;
7873     OUString aStrikeout = aStrikeoutChar;
7874     while( GetTextWidth( aStrikeout ) < nWidth )
7875         aStrikeout += aStrikeout;
7876 
7877     // do not get broader than nWidth modulo 1 character
7878     while( GetTextWidth( aStrikeout ) >= nWidth )
7879         aStrikeout = aStrikeout.copy(1);
7880     aStrikeout += aStrikeoutChar;
7881     bool bShadow = m_aCurrentPDFState.m_aFont.IsShadow();
7882     if ( bShadow )
7883     {
7884         Font aFont = m_aCurrentPDFState.m_aFont;
7885         aFont.SetShadow( false );
7886         setFont( aFont );
7887         updateGraphicsState();
7888     }
7889 
7890     // strikeout string is left aligned non-CTL text
7891     vcl::text::ComplexTextLayoutFlags nOrigTLM = GetLayoutMode();
7892     SetLayoutMode(vcl::text::ComplexTextLayoutFlags::BiDiStrong);
7893 
7894     push( PushFlags::CLIPREGION );
7895     FontMetric aRefDevFontMetric = GetFontMetric();
7896     tools::Rectangle aRect;
7897     aRect.SetLeft( rPos.X() );
7898     aRect.SetRight( aRect.Left()+nWidth );
7899     aRect.SetBottom( rPos.Y()+aRefDevFontMetric.GetDescent() );
7900     aRect.SetTop( rPos.Y()-aRefDevFontMetric.GetAscent() );
7901 
7902     const LogicalFontInstance* pFontInstance = GetFontInstance();
7903     if (pFontInstance->mnOrientation)
7904     {
7905         tools::Polygon aPoly( aRect );
7906         aPoly.Rotate( rPos, pFontInstance->mnOrientation);
7907         aRect = aPoly.GetBoundRect();
7908     }
7909 
7910     intersectClipRegion( aRect );
7911     drawText( rPos, aStrikeout, 0, aStrikeout.getLength(), false );
7912     pop();
7913 
7914     SetLayoutMode( nOrigTLM );
7915 
7916     if ( bShadow )
7917     {
7918         Font aFont = m_aCurrentPDFState.m_aFont;
7919         aFont.SetShadow( true );
7920         setFont( aFont );
7921         updateGraphicsState();
7922     }
7923 }
7924 
drawTextLine(const Point & rPos,tools::Long nWidth,FontStrikeout eStrikeout,FontLineStyle eUnderline,FontLineStyle eOverline,bool bUnderlineAbove)7925 void PDFWriterImpl::drawTextLine( const Point& rPos, tools::Long nWidth, FontStrikeout eStrikeout, FontLineStyle eUnderline, FontLineStyle eOverline, bool bUnderlineAbove )
7926 {
7927     if ( !nWidth ||
7928          ( ((eStrikeout == STRIKEOUT_NONE)||(eStrikeout == STRIKEOUT_DONTKNOW)) &&
7929            ((eUnderline == LINESTYLE_NONE)||(eUnderline == LINESTYLE_DONTKNOW)) &&
7930            ((eOverline  == LINESTYLE_NONE)||(eOverline  == LINESTYLE_DONTKNOW)) ) )
7931         return;
7932 
7933     MARK( "drawTextLine" );
7934     updateGraphicsState();
7935 
7936     // note: units in pFontInstance are ref device pixel
7937     const LogicalFontInstance* pFontInstance = GetFontInstance();
7938     Color           aUnderlineColor = m_aCurrentPDFState.m_aTextLineColor;
7939     Color           aOverlineColor  = m_aCurrentPDFState.m_aOverlineColor;
7940     Color           aStrikeoutColor = m_aCurrentPDFState.m_aFont.GetColor();
7941     bool            bStrikeoutDone = false;
7942     bool            bUnderlineDone = false;
7943     bool            bOverlineDone  = false;
7944 
7945     if ( (eStrikeout == STRIKEOUT_SLASH) || (eStrikeout == STRIKEOUT_X) )
7946     {
7947         drawStrikeoutChar( rPos, nWidth, eStrikeout );
7948         bStrikeoutDone = true;
7949     }
7950 
7951     Point aPos( rPos );
7952     TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
7953     if( eAlign == ALIGN_TOP )
7954         aPos.AdjustY(HCONV( pFontInstance->mxFontMetric->GetAscent() ));
7955     else if( eAlign == ALIGN_BOTTOM )
7956         aPos.AdjustY( -HCONV( pFontInstance->mxFontMetric->GetDescent() ) );
7957 
7958     OStringBuffer aLine( 512 );
7959     // save GS
7960     aLine.append( "q " );
7961 
7962     // rotate and translate matrix
7963     double fAngle = toRadians(m_aCurrentPDFState.m_aFont.GetOrientation());
7964     Matrix3 aMat;
7965     aMat.rotate( fAngle );
7966     aMat.translate( aPos.X(), aPos.Y() );
7967     m_aPages.back().appendMatrix3(aMat, aLine);
7968     aLine.append( " cm\n" );
7969 
7970     if ( aUnderlineColor.IsTransparent() )
7971         aUnderlineColor = aStrikeoutColor;
7972 
7973     if ( aOverlineColor.IsTransparent() )
7974         aOverlineColor = aStrikeoutColor;
7975 
7976     if ( (eUnderline == LINESTYLE_SMALLWAVE) ||
7977          (eUnderline == LINESTYLE_WAVE) ||
7978          (eUnderline == LINESTYLE_DOUBLEWAVE) ||
7979          (eUnderline == LINESTYLE_BOLDWAVE) )
7980     {
7981         drawWaveTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
7982         bUnderlineDone = true;
7983     }
7984 
7985     if ( (eOverline == LINESTYLE_SMALLWAVE) ||
7986          (eOverline == LINESTYLE_WAVE) ||
7987          (eOverline == LINESTYLE_DOUBLEWAVE) ||
7988          (eOverline == LINESTYLE_BOLDWAVE) )
7989     {
7990         drawWaveTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
7991         bOverlineDone = true;
7992     }
7993 
7994     if ( !bUnderlineDone )
7995     {
7996         drawStraightTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
7997     }
7998 
7999     if ( !bOverlineDone )
8000     {
8001         drawStraightTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
8002     }
8003 
8004     if ( !bStrikeoutDone )
8005     {
8006         drawStrikeoutLine( aLine, nWidth, eStrikeout, aStrikeoutColor );
8007     }
8008 
8009     aLine.append( "Q\n" );
8010     writeBuffer( aLine );
8011 }
8012 
drawPolygon(const tools::Polygon & rPoly)8013 void PDFWriterImpl::drawPolygon( const tools::Polygon& rPoly )
8014 {
8015     MARK( "drawPolygon" );
8016 
8017     updateGraphicsState();
8018 
8019     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
8020         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
8021         return;
8022 
8023     int nPoints = rPoly.GetSize();
8024     OStringBuffer aLine( 20 * nPoints );
8025     m_aPages.back().appendPolygon( rPoly, aLine );
8026     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
8027         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
8028         aLine.append( "B*\n" );
8029     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
8030         aLine.append( "S\n" );
8031     else
8032         aLine.append( "f*\n" );
8033 
8034     writeBuffer( aLine );
8035 }
8036 
drawPolyPolygon(const tools::PolyPolygon & rPolyPoly)8037 void PDFWriterImpl::drawPolyPolygon( const tools::PolyPolygon& rPolyPoly )
8038 {
8039     MARK( "drawPolyPolygon" );
8040 
8041     updateGraphicsState();
8042 
8043     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
8044         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
8045         return;
8046 
8047     int nPolygons = rPolyPoly.Count();
8048 
8049     OStringBuffer aLine( 40 * nPolygons );
8050     m_aPages.back().appendPolyPolygon( rPolyPoly, aLine );
8051     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
8052         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
8053         aLine.append( "B*\n" );
8054     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
8055         aLine.append( "S\n" );
8056     else
8057         aLine.append( "f*\n" );
8058 
8059     writeBuffer( aLine );
8060 }
8061 
drawTransparent(const tools::PolyPolygon & rPolyPoly,sal_uInt32 nTransparentPercent)8062 void PDFWriterImpl::drawTransparent( const tools::PolyPolygon& rPolyPoly, sal_uInt32 nTransparentPercent )
8063 {
8064     SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
8065     nTransparentPercent = nTransparentPercent % 100;
8066 
8067     MARK( "drawTransparent" );
8068 
8069     updateGraphicsState();
8070 
8071     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
8072         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
8073         return;
8074 
8075     if( m_bIsPDF_A1 || m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
8076     {
8077         m_aErrors.insert( m_bIsPDF_A1 ?
8078                           PDFWriter::Warning_Transparency_Omitted_PDFA :
8079                           PDFWriter::Warning_Transparency_Omitted_PDF13 );
8080 
8081         drawPolyPolygon( rPolyPoly );
8082         return;
8083     }
8084 
8085     // create XObject
8086     m_aTransparentObjects.emplace_back( );
8087     // FIXME: polygons with beziers may yield incorrect bound rect
8088     m_aTransparentObjects.back().m_aBoundRect     = rPolyPoly.GetBoundRect();
8089     // convert rectangle to default user space
8090     m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
8091     m_aTransparentObjects.back().m_nObject          = createObject();
8092     m_aTransparentObjects.back().m_nExtGStateObject = createObject();
8093     m_aTransparentObjects.back().m_fAlpha           = static_cast<double>(100-nTransparentPercent) / 100.0;
8094     m_aTransparentObjects.back().m_pContentStream.reset(new SvMemoryStream( 256, 256 ));
8095     // create XObject's content stream
8096     OStringBuffer aContent( 256 );
8097     m_aPages.back().appendPolyPolygon( rPolyPoly, aContent );
8098     if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT &&
8099         m_aCurrentPDFState.m_aFillColor != COL_TRANSPARENT )
8100         aContent.append( " B*\n" );
8101     else if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT )
8102         aContent.append( " S\n" );
8103     else
8104         aContent.append( " f*\n" );
8105     m_aTransparentObjects.back().m_pContentStream->WriteBytes(
8106         aContent.getStr(), aContent.getLength() );
8107 
8108     OStringBuffer aObjName( 16 );
8109     aObjName.append( "Tr" );
8110     aObjName.append( m_aTransparentObjects.back().m_nObject );
8111     OString aTrName( aObjName.makeStringAndClear() );
8112     aObjName.append( "EGS" );
8113     aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
8114     OString aExtName( aObjName.makeStringAndClear() );
8115 
8116     OString aLine =
8117     // insert XObject
8118         "q /" +
8119         aExtName +
8120         " gs /" +
8121         aTrName +
8122         " Do Q\n";
8123     writeBuffer( aLine );
8124 
8125     pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject );
8126     pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
8127 }
8128 
pushResource(ResourceKind eKind,const OString & rResource,sal_Int32 nObject,ResourceDict & rResourceDict,std::list<StreamRedirect> & rOutputStreams)8129 void PDFWriterImpl::pushResource(ResourceKind eKind, const OString& rResource, sal_Int32 nObject, ResourceDict& rResourceDict, std::list<StreamRedirect>& rOutputStreams)
8130 {
8131     if( nObject < 0 )
8132         return;
8133 
8134     switch( eKind )
8135     {
8136         case ResourceKind::XObject:
8137             rResourceDict.m_aXObjects[rResource] = nObject;
8138             if (!rOutputStreams.empty())
8139                 rOutputStreams.front().m_aResourceDict.m_aXObjects[rResource] = nObject;
8140             break;
8141         case ResourceKind::ExtGState:
8142             rResourceDict.m_aExtGStates[rResource] = nObject;
8143             if (!rOutputStreams.empty())
8144                 rOutputStreams.front().m_aResourceDict.m_aExtGStates[rResource] = nObject;
8145             break;
8146         case ResourceKind::Shading:
8147             rResourceDict.m_aShadings[rResource] = nObject;
8148             if (!rOutputStreams.empty())
8149                 rOutputStreams.front().m_aResourceDict.m_aShadings[rResource] = nObject;
8150             break;
8151         case ResourceKind::Pattern:
8152             rResourceDict.m_aPatterns[rResource] = nObject;
8153             if (!rOutputStreams.empty())
8154                 rOutputStreams.front().m_aResourceDict.m_aPatterns[rResource] = nObject;
8155             break;
8156     }
8157 }
8158 
pushResource(ResourceKind eKind,const OString & rResource,sal_Int32 nObject)8159 void PDFWriterImpl::pushResource( ResourceKind eKind, const OString& rResource, sal_Int32 nObject )
8160 {
8161     pushResource(eKind, rResource, nObject, m_aGlobalResourceDict, m_aOutputStreams);
8162 }
8163 
beginRedirect(SvStream * pStream,const tools::Rectangle & rTargetRect)8164 void PDFWriterImpl::beginRedirect( SvStream* pStream, const tools::Rectangle& rTargetRect )
8165 {
8166     push( PushFlags::ALL );
8167 
8168     // force reemitting clip region inside the new stream, and
8169     // prevent emitting an unbalanced "Q" at the start
8170     clearClipRegion();
8171     // this is needed to point m_aCurrentPDFState at the pushed state
8172     // ... but it's pointless to actually write into the "outer" stream here!
8173     updateGraphicsState(Mode::NOWRITE);
8174 
8175     m_aOutputStreams.push_front( StreamRedirect() );
8176     m_aOutputStreams.front().m_pStream = pStream;
8177     m_aOutputStreams.front().m_aMapMode = m_aMapMode;
8178 
8179     if( !rTargetRect.IsEmpty() )
8180     {
8181         m_aOutputStreams.front().m_aTargetRect =
8182             lcl_convert( m_aGraphicsStack.front().m_aMapMode,
8183                          m_aMapMode,
8184                          this,
8185                          rTargetRect );
8186         Point aDelta = m_aOutputStreams.front().m_aTargetRect.BottomLeft();
8187         tools::Long nPageHeight = pointToPixel(m_aPages[m_nCurrentPage].getHeight());
8188         aDelta.setY( -(nPageHeight - m_aOutputStreams.front().m_aTargetRect.Bottom()) );
8189         m_aMapMode.SetOrigin( m_aMapMode.GetOrigin() + aDelta );
8190     }
8191 
8192     // setup graphics state for independent object stream
8193 
8194     // force reemitting colors
8195     m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
8196     m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
8197 }
8198 
endRedirect()8199 SvStream* PDFWriterImpl::endRedirect()
8200 {
8201     SvStream* pStream = nullptr;
8202     if( ! m_aOutputStreams.empty() )
8203     {
8204         pStream     = m_aOutputStreams.front().m_pStream;
8205         m_aMapMode  = m_aOutputStreams.front().m_aMapMode;
8206         m_aOutputStreams.pop_front();
8207     }
8208 
8209     pop();
8210 
8211     m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
8212     m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
8213 
8214     // needed after pop() to set m_aCurrentPDFState
8215     updateGraphicsState(Mode::NOWRITE);
8216 
8217     return pStream;
8218 }
8219 
beginTransparencyGroup()8220 void PDFWriterImpl::beginTransparencyGroup()
8221 {
8222     updateGraphicsState();
8223     if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
8224         beginRedirect( new SvMemoryStream( 1024, 1024 ), tools::Rectangle() );
8225 }
8226 
endTransparencyGroup(const tools::Rectangle & rBoundingBox,sal_uInt32 nTransparentPercent)8227 void PDFWriterImpl::endTransparencyGroup( const tools::Rectangle& rBoundingBox, sal_uInt32 nTransparentPercent )
8228 {
8229     SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
8230     nTransparentPercent = nTransparentPercent % 100;
8231 
8232     if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
8233         return;
8234 
8235     // create XObject
8236     m_aTransparentObjects.emplace_back( );
8237     m_aTransparentObjects.back().m_aBoundRect   = rBoundingBox;
8238     // convert rectangle to default user space
8239     m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
8240     m_aTransparentObjects.back().m_nObject      = createObject();
8241     m_aTransparentObjects.back().m_fAlpha       = static_cast<double>(100-nTransparentPercent) / 100.0;
8242     // get XObject's content stream
8243     m_aTransparentObjects.back().m_pContentStream.reset( static_cast<SvMemoryStream*>(endRedirect()) );
8244     m_aTransparentObjects.back().m_nExtGStateObject = createObject();
8245 
8246     OStringBuffer aObjName( 16 );
8247     aObjName.append( "Tr" );
8248     aObjName.append( m_aTransparentObjects.back().m_nObject );
8249     OString aTrName( aObjName.makeStringAndClear() );
8250     aObjName.append( "EGS" );
8251     aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
8252     OString aExtName( aObjName.makeStringAndClear() );
8253 
8254     OString aLine =
8255     // insert XObject
8256         "q /" +
8257         aExtName +
8258         " gs /" +
8259         aTrName +
8260         " Do Q\n";
8261     writeBuffer( aLine );
8262 
8263     pushResource( ResourceKind::XObject, aTrName, m_aTransparentObjects.back().m_nObject );
8264     pushResource( ResourceKind::ExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
8265 
8266 }
8267 
drawRectangle(const tools::Rectangle & rRect)8268 void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect )
8269 {
8270     MARK( "drawRectangle" );
8271 
8272     updateGraphicsState();
8273 
8274     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
8275         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
8276         return;
8277 
8278     OStringBuffer aLine( 40 );
8279     m_aPages.back().appendRect( rRect, aLine );
8280 
8281     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
8282         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
8283         aLine.append( " B*\n" );
8284     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
8285         aLine.append( " S\n" );
8286     else
8287         aLine.append( " f*\n" );
8288 
8289     writeBuffer( aLine );
8290 }
8291 
drawRectangle(const tools::Rectangle & rRect,sal_uInt32 nHorzRound,sal_uInt32 nVertRound)8292 void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound )
8293 {
8294     MARK( "drawRectangle with rounded edges" );
8295 
8296     if( !nHorzRound && !nVertRound )
8297         drawRectangle( rRect );
8298 
8299     updateGraphicsState();
8300 
8301     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
8302         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
8303         return;
8304 
8305     if( nHorzRound > static_cast<sal_uInt32>(rRect.GetWidth())/2 )
8306         nHorzRound = rRect.GetWidth()/2;
8307     if( nVertRound > static_cast<sal_uInt32>(rRect.GetHeight())/2 )
8308         nVertRound = rRect.GetHeight()/2;
8309 
8310     Point aPoints[16];
8311     const double kappa = 0.5522847498;
8312     const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(nHorzRound))+0.5);
8313     const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(nVertRound))+0.5);
8314 
8315     aPoints[1]  = Point( rRect.Left() + nHorzRound, rRect.Top() );
8316     aPoints[0]  = Point( aPoints[1].X() - kx, aPoints[1].Y() );
8317     aPoints[2]  = Point( rRect.Right()+1 - nHorzRound, aPoints[1].Y() );
8318     aPoints[3]  = Point( aPoints[2].X()+kx, aPoints[2].Y() );
8319 
8320     aPoints[5]  = Point( rRect.Right()+1, rRect.Top()+nVertRound );
8321     aPoints[4]  = Point( aPoints[5].X(), aPoints[5].Y()-ky );
8322     aPoints[6]  = Point( aPoints[5].X(), rRect.Bottom()+1 - nVertRound );
8323     aPoints[7]  = Point( aPoints[6].X(), aPoints[6].Y()+ky );
8324 
8325     aPoints[9]  = Point( rRect.Right()+1-nHorzRound, rRect.Bottom()+1 );
8326     aPoints[8]  = Point( aPoints[9].X()+kx, aPoints[9].Y() );
8327     aPoints[10] = Point( rRect.Left() + nHorzRound, aPoints[9].Y() );
8328     aPoints[11] = Point( aPoints[10].X()-kx, aPoints[10].Y() );
8329 
8330     aPoints[13] = Point( rRect.Left(), rRect.Bottom()+1-nVertRound );
8331     aPoints[12] = Point( aPoints[13].X(), aPoints[13].Y()+ky );
8332     aPoints[14] = Point( rRect.Left(), rRect.Top()+nVertRound );
8333     aPoints[15] = Point( aPoints[14].X(), aPoints[14].Y()-ky );
8334 
8335     OStringBuffer aLine( 80 );
8336     m_aPages.back().appendPoint( aPoints[1], aLine );
8337     aLine.append( " m " );
8338     m_aPages.back().appendPoint( aPoints[2], aLine );
8339     aLine.append( " l " );
8340     m_aPages.back().appendPoint( aPoints[3], aLine );
8341     aLine.append( ' ' );
8342     m_aPages.back().appendPoint( aPoints[4], aLine );
8343     aLine.append( ' ' );
8344     m_aPages.back().appendPoint( aPoints[5], aLine );
8345     aLine.append( " c\n" );
8346     m_aPages.back().appendPoint( aPoints[6], aLine );
8347     aLine.append( " l " );
8348     m_aPages.back().appendPoint( aPoints[7], aLine );
8349     aLine.append( ' ' );
8350     m_aPages.back().appendPoint( aPoints[8], aLine );
8351     aLine.append( ' ' );
8352     m_aPages.back().appendPoint( aPoints[9], aLine );
8353     aLine.append( " c\n" );
8354     m_aPages.back().appendPoint( aPoints[10], aLine );
8355     aLine.append( " l " );
8356     m_aPages.back().appendPoint( aPoints[11], aLine );
8357     aLine.append( ' ' );
8358     m_aPages.back().appendPoint( aPoints[12], aLine );
8359     aLine.append( ' ' );
8360     m_aPages.back().appendPoint( aPoints[13], aLine );
8361     aLine.append( " c\n" );
8362     m_aPages.back().appendPoint( aPoints[14], aLine );
8363     aLine.append( " l " );
8364     m_aPages.back().appendPoint( aPoints[15], aLine );
8365     aLine.append( ' ' );
8366     m_aPages.back().appendPoint( aPoints[0], aLine );
8367     aLine.append( ' ' );
8368     m_aPages.back().appendPoint( aPoints[1], aLine );
8369     aLine.append( " c " );
8370 
8371     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
8372         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
8373         aLine.append( "b*\n" );
8374     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
8375         aLine.append( "s\n" );
8376     else
8377         aLine.append( "f*\n" );
8378 
8379     writeBuffer( aLine );
8380 }
8381 
drawEllipse(const tools::Rectangle & rRect)8382 void PDFWriterImpl::drawEllipse( const tools::Rectangle& rRect )
8383 {
8384     MARK( "drawEllipse" );
8385 
8386     updateGraphicsState();
8387 
8388     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
8389         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
8390         return;
8391 
8392     Point aPoints[12];
8393     const double kappa = 0.5522847498;
8394     const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetWidth())/2.0)+0.5);
8395     const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetHeight())/2.0)+0.5);
8396 
8397     aPoints[1]  = Point( rRect.Left() + rRect.GetWidth()/2, rRect.Top() );
8398     aPoints[0]  = Point( aPoints[1].X() - kx, aPoints[1].Y() );
8399     aPoints[2]  = Point( aPoints[1].X() + kx, aPoints[1].Y() );
8400 
8401     aPoints[4]  = Point( rRect.Right()+1, rRect.Top() + rRect.GetHeight()/2 );
8402     aPoints[3]  = Point( aPoints[4].X(), aPoints[4].Y() - ky );
8403     aPoints[5]  = Point( aPoints[4].X(), aPoints[4].Y() + ky );
8404 
8405     aPoints[7]  = Point( rRect.Left() + rRect.GetWidth()/2, rRect.Bottom()+1 );
8406     aPoints[6]  = Point( aPoints[7].X() + kx, aPoints[7].Y() );
8407     aPoints[8]  = Point( aPoints[7].X() - kx, aPoints[7].Y() );
8408 
8409     aPoints[10] = Point( rRect.Left(), rRect.Top() + rRect.GetHeight()/2 );
8410     aPoints[9]  = Point( aPoints[10].X(), aPoints[10].Y() + ky );
8411     aPoints[11] = Point( aPoints[10].X(), aPoints[10].Y() - ky );
8412 
8413     OStringBuffer aLine( 80 );
8414     m_aPages.back().appendPoint( aPoints[1], aLine );
8415     aLine.append( " m " );
8416     m_aPages.back().appendPoint( aPoints[2], aLine );
8417     aLine.append( ' ' );
8418     m_aPages.back().appendPoint( aPoints[3], aLine );
8419     aLine.append( ' ' );
8420     m_aPages.back().appendPoint( aPoints[4], aLine );
8421     aLine.append( " c\n" );
8422     m_aPages.back().appendPoint( aPoints[5], aLine );
8423     aLine.append( ' ' );
8424     m_aPages.back().appendPoint( aPoints[6], aLine );
8425     aLine.append( ' ' );
8426     m_aPages.back().appendPoint( aPoints[7], aLine );
8427     aLine.append( " c\n" );
8428     m_aPages.back().appendPoint( aPoints[8], aLine );
8429     aLine.append( ' ' );
8430     m_aPages.back().appendPoint( aPoints[9], aLine );
8431     aLine.append( ' ' );
8432     m_aPages.back().appendPoint( aPoints[10], aLine );
8433     aLine.append( " c\n" );
8434     m_aPages.back().appendPoint( aPoints[11], aLine );
8435     aLine.append( ' ' );
8436     m_aPages.back().appendPoint( aPoints[0], aLine );
8437     aLine.append( ' ' );
8438     m_aPages.back().appendPoint( aPoints[1], aLine );
8439     aLine.append( " c " );
8440 
8441     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
8442         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
8443         aLine.append( "b*\n" );
8444     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
8445         aLine.append( "s\n" );
8446     else
8447         aLine.append( "f*\n" );
8448 
8449     writeBuffer( aLine );
8450 }
8451 
calcAngle(const tools::Rectangle & rRect,const Point & rPoint)8452 static double calcAngle( const tools::Rectangle& rRect, const Point& rPoint )
8453 {
8454     Point aOrigin((rRect.Left()+rRect.Right()+1)/2,
8455                   (rRect.Top()+rRect.Bottom()+1)/2);
8456     Point aPoint = rPoint - aOrigin;
8457 
8458     double fX = static_cast<double>(aPoint.X());
8459     double fY = static_cast<double>(-aPoint.Y());
8460 
8461     if ((rRect.GetHeight() == 0) || (rRect.GetWidth() == 0))
8462         throw o3tl::divide_by_zero();
8463 
8464     if( rRect.GetWidth() > rRect.GetHeight() )
8465         fY = fY*(static_cast<double>(rRect.GetWidth())/static_cast<double>(rRect.GetHeight()));
8466     else if( rRect.GetHeight() > rRect.GetWidth() )
8467         fX = fX*(static_cast<double>(rRect.GetHeight())/static_cast<double>(rRect.GetWidth()));
8468     return atan2( fY, fX );
8469 }
8470 
drawArc(const tools::Rectangle & rRect,const Point & rStart,const Point & rStop,bool bWithPie,bool bWithChord)8471 void PDFWriterImpl::drawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop, bool bWithPie, bool bWithChord )
8472 {
8473     MARK( "drawArc" );
8474 
8475     updateGraphicsState();
8476 
8477     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
8478         m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
8479         return;
8480 
8481     // calculate start and stop angles
8482     const double fStartAngle = calcAngle( rRect, rStart );
8483     double fStopAngle  = calcAngle( rRect, rStop );
8484     while( fStopAngle < fStartAngle )
8485         fStopAngle += 2.0*M_PI;
8486     const int nFragments = static_cast<int>((fStopAngle-fStartAngle)/(M_PI/2.0))+1;
8487     const double fFragmentDelta = (fStopAngle-fStartAngle)/static_cast<double>(nFragments);
8488     const double kappa = fabs( 4.0 * (1.0-cos(fFragmentDelta/2.0))/sin(fFragmentDelta/2.0) / 3.0);
8489     const double halfWidth = static_cast<double>(rRect.GetWidth())/2.0;
8490     const double halfHeight = static_cast<double>(rRect.GetHeight())/2.0;
8491 
8492     const Point aCenter( (rRect.Left()+rRect.Right()+1)/2,
8493                          (rRect.Top()+rRect.Bottom()+1)/2 );
8494 
8495     OStringBuffer aLine( 30*nFragments );
8496     Point aPoint( static_cast<int>(halfWidth * cos(fStartAngle) ),
8497                   -static_cast<int>(halfHeight * sin(fStartAngle) ) );
8498     aPoint += aCenter;
8499     m_aPages.back().appendPoint( aPoint, aLine );
8500     aLine.append( " m " );
8501     if( !basegfx::fTools::equal(fStartAngle, fStopAngle) )
8502     {
8503         for( int i = 0; i < nFragments; i++ )
8504         {
8505             const double fStartFragment = fStartAngle + static_cast<double>(i)*fFragmentDelta;
8506             const double fStopFragment = fStartFragment + fFragmentDelta;
8507             aPoint = Point( static_cast<int>(halfWidth * (cos(fStartFragment) - kappa*sin(fStartFragment) ) ),
8508                             -static_cast<int>(halfHeight * (sin(fStartFragment) + kappa*cos(fStartFragment) ) ) );
8509             aPoint += aCenter;
8510             m_aPages.back().appendPoint( aPoint, aLine );
8511             aLine.append( ' ' );
8512 
8513             aPoint = Point( static_cast<int>(halfWidth * (cos(fStopFragment) + kappa*sin(fStopFragment) ) ),
8514                             -static_cast<int>(halfHeight * (sin(fStopFragment) - kappa*cos(fStopFragment) ) ) );
8515             aPoint += aCenter;
8516             m_aPages.back().appendPoint( aPoint, aLine );
8517             aLine.append( ' ' );
8518 
8519             aPoint = Point( static_cast<int>(halfWidth * cos(fStopFragment) ),
8520                             -static_cast<int>(halfHeight * sin(fStopFragment) ) );
8521             aPoint += aCenter;
8522             m_aPages.back().appendPoint( aPoint, aLine );
8523             aLine.append( " c\n" );
8524         }
8525     }
8526     if( bWithChord || bWithPie )
8527     {
8528         if( bWithPie )
8529         {
8530             m_aPages.back().appendPoint( aCenter, aLine );
8531             aLine.append( " l " );
8532         }
8533         aLine.append( "h " );
8534     }
8535     if( ! bWithChord && ! bWithPie )
8536         aLine.append( "S\n" );
8537     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
8538         m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
8539         aLine.append( "B*\n" );
8540     else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
8541         aLine.append( "S\n" );
8542     else
8543         aLine.append( "f*\n" );
8544 
8545     writeBuffer( aLine );
8546 }
8547 
drawPolyLine(const tools::Polygon & rPoly)8548 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly )
8549 {
8550     MARK( "drawPolyLine" );
8551 
8552     sal_uInt16 nPoints = rPoly.GetSize();
8553     if( nPoints < 2 )
8554         return;
8555 
8556     updateGraphicsState();
8557 
8558     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
8559         return;
8560 
8561     OStringBuffer aLine( 20 * nPoints );
8562     m_aPages.back().appendPolygon( rPoly, aLine, rPoly[0] == rPoly[nPoints-1] );
8563     aLine.append( "S\n" );
8564 
8565     writeBuffer( aLine );
8566 }
8567 
drawPolyLine(const tools::Polygon & rPoly,const LineInfo & rInfo)8568 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const LineInfo& rInfo )
8569 {
8570     MARK( "drawPolyLine with LineInfo" );
8571 
8572     updateGraphicsState();
8573 
8574     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
8575         return;
8576 
8577     OStringBuffer aLine;
8578     aLine.append( "q " );
8579     if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
8580     {
8581         writeBuffer( aLine );
8582         drawPolyLine( rPoly );
8583         writeBuffer( "Q\n" );
8584     }
8585     else
8586     {
8587         PDFWriter::ExtLineInfo aInfo;
8588         convertLineInfoToExtLineInfo( rInfo, aInfo );
8589         drawPolyLine( rPoly, aInfo );
8590     }
8591 }
8592 
convertLineInfoToExtLineInfo(const LineInfo & rIn,PDFWriter::ExtLineInfo & rOut)8593 void PDFWriterImpl::convertLineInfoToExtLineInfo( const LineInfo& rIn, PDFWriter::ExtLineInfo& rOut )
8594 {
8595     SAL_WARN_IF( rIn.GetStyle() != LineStyle::Dash, "vcl.pdfwriter", "invalid conversion" );
8596     rOut.m_fLineWidth           = rIn.GetWidth();
8597     rOut.m_fTransparency        = 0.0;
8598     rOut.m_eCap                 = PDFWriter::capButt;
8599     rOut.m_eJoin                = PDFWriter::joinMiter;
8600     rOut.m_fMiterLimit          = 10;
8601     rOut.m_aDashArray           = rIn.GetDotDashArray();
8602 
8603     // add LineJoin
8604     switch(rIn.GetLineJoin())
8605     {
8606         case basegfx::B2DLineJoin::Bevel :
8607         {
8608             rOut.m_eJoin = PDFWriter::joinBevel;
8609             break;
8610         }
8611         // Pdf has no 'none' lineJoin, default is miter
8612         case basegfx::B2DLineJoin::NONE :
8613         case basegfx::B2DLineJoin::Miter :
8614         {
8615             rOut.m_eJoin = PDFWriter::joinMiter;
8616             break;
8617         }
8618         case basegfx::B2DLineJoin::Round :
8619         {
8620             rOut.m_eJoin = PDFWriter::joinRound;
8621             break;
8622         }
8623     }
8624 
8625     // add LineCap
8626     switch(rIn.GetLineCap())
8627     {
8628         default: /* css::drawing::LineCap_BUTT */
8629         {
8630             rOut.m_eCap = PDFWriter::capButt;
8631             break;
8632         }
8633         case css::drawing::LineCap_ROUND:
8634         {
8635             rOut.m_eCap = PDFWriter::capRound;
8636             break;
8637         }
8638         case css::drawing::LineCap_SQUARE:
8639         {
8640             rOut.m_eCap = PDFWriter::capSquare;
8641             break;
8642         }
8643     }
8644 }
8645 
drawPolyLine(const tools::Polygon & rPoly,const PDFWriter::ExtLineInfo & rInfo)8646 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const PDFWriter::ExtLineInfo& rInfo )
8647 {
8648     MARK( "drawPolyLine with ExtLineInfo" );
8649 
8650     updateGraphicsState();
8651 
8652     if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
8653         return;
8654 
8655     if( rInfo.m_fTransparency >= 1.0 )
8656         return;
8657 
8658     if( rInfo.m_fTransparency != 0.0 )
8659         beginTransparencyGroup();
8660 
8661     OStringBuffer aLine;
8662     aLine.append( "q " );
8663     m_aPages.back().appendMappedLength( rInfo.m_fLineWidth, aLine );
8664     aLine.append( " w" );
8665     if( rInfo.m_aDashArray.size() < 10 ) // implementation limit of acrobat reader
8666     {
8667         switch( rInfo.m_eCap )
8668         {
8669             default:
8670             case PDFWriter::capButt:   aLine.append( " 0 J" );break;
8671             case PDFWriter::capRound:  aLine.append( " 1 J" );break;
8672             case PDFWriter::capSquare: aLine.append( " 2 J" );break;
8673         }
8674         switch( rInfo.m_eJoin )
8675         {
8676             default:
8677             case PDFWriter::joinMiter:
8678             {
8679                 double fLimit = rInfo.m_fMiterLimit;
8680                 if( rInfo.m_fLineWidth < rInfo.m_fMiterLimit )
8681                     fLimit = fLimit / rInfo.m_fLineWidth;
8682                 if( fLimit < 1.0 )
8683                     fLimit = 1.0;
8684                 aLine.append( " 0 j " );
8685                 appendDouble( fLimit, aLine );
8686                 aLine.append( " M" );
8687             }
8688             break;
8689             case PDFWriter::joinRound:  aLine.append( " 1 j" );break;
8690             case PDFWriter::joinBevel:  aLine.append( " 2 j" );break;
8691         }
8692         if( !rInfo.m_aDashArray.empty() )
8693         {
8694             aLine.append( " [ " );
8695             for (auto const& dash : rInfo.m_aDashArray)
8696             {
8697                 m_aPages.back().appendMappedLength( dash, aLine );
8698                 aLine.append( ' ' );
8699             }
8700             aLine.append( "] 0 d" );
8701         }
8702         aLine.append( "\n" );
8703         writeBuffer( aLine );
8704         drawPolyLine( rPoly );
8705     }
8706     else
8707     {
8708         basegfx::B2DPolygon aPoly(rPoly.getB2DPolygon());
8709         basegfx::B2DPolyPolygon aPolyPoly;
8710 
8711         basegfx::utils::applyLineDashing(aPoly, rInfo.m_aDashArray, &aPolyPoly);
8712 
8713         // Old applyLineDashing subdivided the polygon. New one will create bezier curve segments.
8714         // To mimic old behaviour, apply subdivide here. If beziers shall be written (better quality)
8715         // this line needs to be removed and the loop below adapted accordingly
8716         aPolyPoly = basegfx::utils::adaptiveSubdivideByAngle(aPolyPoly);
8717 
8718         const sal_uInt32 nPolygonCount(aPolyPoly.count());
8719 
8720         for( sal_uInt32 nPoly = 0; nPoly < nPolygonCount; nPoly++ )
8721         {
8722             aLine.append( (nPoly != 0 && (nPoly & 7) == 0) ? "\n" : " " );
8723             aPoly = aPolyPoly.getB2DPolygon( nPoly );
8724             const sal_uInt32 nPointCount(aPoly.count());
8725 
8726             if(nPointCount)
8727             {
8728                 const sal_uInt32 nEdgeCount(aPoly.isClosed() ? nPointCount : nPointCount - 1);
8729                 basegfx::B2DPoint aCurrent(aPoly.getB2DPoint(0));
8730 
8731                 for(sal_uInt32 a(0); a < nEdgeCount; a++)
8732                 {
8733                     if( a > 0 )
8734                         aLine.append( " " );
8735                     const sal_uInt32 nNextIndex((a + 1) % nPointCount);
8736                     const basegfx::B2DPoint aNext(aPoly.getB2DPoint(nNextIndex));
8737 
8738                     m_aPages.back().appendPoint( Point( basegfx::fround<tools::Long>(aCurrent.getX()),
8739                                                         basegfx::fround<tools::Long>(aCurrent.getY()) ),
8740                                                  aLine );
8741                     aLine.append( " m " );
8742                     m_aPages.back().appendPoint( Point( basegfx::fround<tools::Long>(aNext.getX()),
8743                                                         basegfx::fround<tools::Long>(aNext.getY()) ),
8744                                                  aLine );
8745                     aLine.append( " l" );
8746 
8747                     // prepare next edge
8748                     aCurrent = aNext;
8749                 }
8750             }
8751         }
8752         aLine.append( " S " );
8753         writeBuffer( aLine );
8754     }
8755     writeBuffer( "Q\n" );
8756 
8757     if( rInfo.m_fTransparency == 0.0 )
8758         return;
8759 
8760     // FIXME: actually this may be incorrect with bezier polygons
8761     tools::Rectangle aBoundRect( rPoly.GetBoundRect() );
8762     // avoid clipping with thick lines
8763     if( rInfo.m_fLineWidth > 0.0 )
8764     {
8765         sal_Int32 nLW = sal_Int32(rInfo.m_fLineWidth);
8766         aBoundRect.AdjustTop( -nLW );
8767         aBoundRect.AdjustLeft( -nLW );
8768         aBoundRect.AdjustRight(nLW );
8769         aBoundRect.AdjustBottom(nLW );
8770     }
8771     endTransparencyGroup( aBoundRect, static_cast<sal_uInt16>(100.0*rInfo.m_fTransparency) );
8772 }
8773 
drawPixel(const Point & rPoint,const Color & rColor)8774 void PDFWriterImpl::drawPixel( const Point& rPoint, const Color& rColor )
8775 {
8776     MARK( "drawPixel" );
8777 
8778     Color aColor = ( rColor == COL_TRANSPARENT ? m_aGraphicsStack.front().m_aLineColor : rColor );
8779 
8780     if( aColor == COL_TRANSPARENT )
8781         return;
8782 
8783     // pixels are drawn in line color, so have to set
8784     // the nonstroking color to line color
8785     Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
8786     setFillColor( aColor );
8787 
8788     updateGraphicsState();
8789 
8790     OStringBuffer aLine( 20 );
8791     m_aPages.back().appendPoint( rPoint, aLine );
8792     aLine.append( ' ' );
8793     appendDouble( 1.0/double(GetDPIX()), aLine );
8794     aLine.append( ' ' );
8795     appendDouble( 1.0/double(GetDPIY()), aLine );
8796     aLine.append( " re f\n" );
8797     writeBuffer( aLine );
8798 
8799     setFillColor( aOldFillColor );
8800 }
8801 
writeTransparentObject(TransparencyEmit & rObject)8802 void PDFWriterImpl::writeTransparentObject( TransparencyEmit& rObject )
8803 {
8804     if (!updateObject(rObject.m_nObject))
8805         return;
8806 
8807     bool bFlateFilter = compressStream( rObject.m_pContentStream.get() );
8808     sal_uInt64 nSize = rObject.m_pContentStream->TellEnd();
8809     rObject.m_pContentStream->Seek( STREAM_SEEK_TO_BEGIN );
8810     if (g_bDebugDisableCompression)
8811     {
8812         emitComment( "PDFWriterImpl::writeTransparentObject" );
8813     }
8814     OStringBuffer aLine( 512 );
8815     if (!updateObject(rObject.m_nObject))
8816         return;
8817     aLine.append( rObject.m_nObject );
8818     aLine.append( " 0 obj\n"
8819                   "<</Type/XObject\n"
8820                   "/Subtype/Form\n"
8821                   "/BBox[ " );
8822     appendFixedInt( rObject.m_aBoundRect.Left(), aLine );
8823     aLine.append( ' ' );
8824     appendFixedInt( rObject.m_aBoundRect.Top(), aLine );
8825     aLine.append( ' ' );
8826     appendFixedInt( rObject.m_aBoundRect.Right(), aLine );
8827     aLine.append( ' ' );
8828     appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aLine );
8829     aLine.append( " ]\n" );
8830     if( ! m_bIsPDF_A1 )
8831     {
8832         // 7.8.3 Resource dicts are required for content streams
8833         aLine.append( "/Resources " );
8834         aLine.append( getResourceDictObj() );
8835         aLine.append( " 0 R\n" );
8836 
8837         aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/K true>>\n" );
8838     }
8839 
8840     aLine.append( "/Length " );
8841     aLine.append( static_cast<sal_Int32>(nSize) );
8842     aLine.append( "\n" );
8843     if( bFlateFilter )
8844         aLine.append( "/Filter/FlateDecode\n" );
8845     aLine.append( ">>\n"
8846                   "stream\n" );
8847     if (!writeBuffer(aLine))
8848         return;
8849     checkAndEnableStreamEncryption( rObject.m_nObject );
8850     if (!writeBufferBytes(rObject.m_pContentStream->GetData(), nSize))
8851         return;
8852     disableStreamEncryption();
8853     aLine.setLength( 0 );
8854     aLine.append( "\n"
8855                   "endstream\n"
8856                   "endobj\n\n" );
8857     if (!writeBuffer(aLine))
8858         return;
8859 
8860     // write ExtGState dict for this XObject
8861     aLine.setLength( 0 );
8862     aLine.append( rObject.m_nExtGStateObject );
8863     aLine.append( " 0 obj\n"
8864                   "<<" );
8865 
8866     if( m_bIsPDF_A1 )
8867     {
8868         aLine.append( "/CA 1.0/ca 1.0" );
8869         m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
8870     }
8871     else
8872     {
8873         aLine.append(  "/CA " );
8874         appendDouble( rObject.m_fAlpha, aLine );
8875         aLine.append( "\n"
8876                           "   /ca " );
8877         appendDouble( rObject.m_fAlpha, aLine );
8878     }
8879     aLine.append( "\n" );
8880 
8881     aLine.append( ">>\n"
8882                   "endobj\n\n" );
8883     if (!updateObject(rObject.m_nExtGStateObject)) return;
8884     if (!writeBuffer(aLine)) return;
8885 }
8886 
writeGradientFunction(GradientEmit const & rObject)8887 bool PDFWriterImpl::writeGradientFunction( GradientEmit const & rObject )
8888 {
8889     // LO internal gradient -> PDF shading type:
8890     //  * css::awt::GradientStyle_LINEAR: axial shading, using sampled-function with 2 samples
8891     //                          [t=0:colorStart, t=1:colorEnd]
8892     //  * css::awt::GradientStyle_AXIAL: axial shading, using sampled-function with 3 samples
8893     //                          [t=0:colorEnd, t=0.5:colorStart, t=1:colorEnd]
8894     //  * other styles: function shading with aSize.Width() * aSize.Height() samples
8895     sal_Int32 nFunctionObject = createObject();
8896     if (!updateObject(nFunctionObject))
8897         return false;
8898 
8899     ScopedVclPtrInstance< VirtualDevice > aDev;
8900     aDev->SetOutputSizePixel( rObject.m_aSize );
8901     aDev->SetMapMode( MapMode( MapUnit::MapPixel ) );
8902     if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
8903         aDev->SetDrawMode( aDev->GetDrawMode() |
8904                           ( DrawModeFlags::GrayLine | DrawModeFlags::GrayFill | DrawModeFlags::GrayText |
8905                             DrawModeFlags::GrayBitmap | DrawModeFlags::GrayGradient ) );
8906     aDev->DrawGradient( tools::Rectangle( Point( 0, 0 ), rObject.m_aSize ), rObject.m_aGradient );
8907 
8908     Bitmap aSample = aDev->GetBitmap( Point( 0, 0 ), rObject.m_aSize );
8909     BitmapScopedReadAccess pAccess(aSample);
8910 
8911     Size aSize = aSample.GetSizePixel();
8912 
8913     sal_Int32 nStreamLengthObject = createObject();
8914     if (g_bDebugDisableCompression)
8915     {
8916         emitComment( "PDFWriterImpl::writeGradientFunction" );
8917     }
8918     OStringBuffer aLine( 120 );
8919     aLine.append( nFunctionObject );
8920     aLine.append( " 0 obj\n"
8921                   "<</FunctionType 0\n");
8922     switch (rObject.m_aGradient.GetStyle())
8923     {
8924         case css::awt::GradientStyle_LINEAR:
8925         case css::awt::GradientStyle_AXIAL:
8926             aLine.append("/Domain[ 0 1]\n");
8927             break;
8928         default:
8929             aLine.append("/Domain[ 0 1 0 1]\n");
8930     }
8931     aLine.append("/Size[ " );
8932     switch (rObject.m_aGradient.GetStyle())
8933     {
8934         case css::awt::GradientStyle_LINEAR:
8935             aLine.append('2');
8936             break;
8937         case css::awt::GradientStyle_AXIAL:
8938             aLine.append('3');
8939             break;
8940         default:
8941             aLine.append( static_cast<sal_Int32>(aSize.Width()) );
8942             aLine.append( ' ' );
8943             aLine.append( static_cast<sal_Int32>(aSize.Height()) );
8944     }
8945     aLine.append( " ]\n"
8946                   "/BitsPerSample 8\n"
8947                   "/Range[ 0 1 0 1 0 1 ]\n"
8948                   "/Order 3\n"
8949                   "/Length " );
8950     aLine.append( nStreamLengthObject );
8951     if (!g_bDebugDisableCompression)
8952         aLine.append( " 0 R\n"
8953                       "/Filter/FlateDecode"
8954                       ">>\n"
8955                       "stream\n" );
8956     else
8957         aLine.append( " 0 R\n"
8958                       ">>\n"
8959                       "stream\n" );
8960     if (!writeBuffer(aLine))
8961         return false;
8962 
8963     sal_uInt64 nStartStreamPos = 0;
8964     if (osl::File::E_None != m_aFile.getPos(nStartStreamPos))
8965         return false;
8966 
8967     checkAndEnableStreamEncryption( nFunctionObject );
8968     beginCompression();
8969     sal_uInt8 aCol[3];
8970     switch (rObject.m_aGradient.GetStyle())
8971     {
8972         case css::awt::GradientStyle_AXIAL:
8973             aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
8974             aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
8975             aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
8976             if (!writeBufferBytes(aCol, 3))
8977                 return false;
8978             [[fallthrough]];
8979         case css::awt::GradientStyle_LINEAR:
8980         {
8981             aCol[0] = rObject.m_aGradient.GetStartColor().GetRed();
8982             aCol[1] = rObject.m_aGradient.GetStartColor().GetGreen();
8983             aCol[2] = rObject.m_aGradient.GetStartColor().GetBlue();
8984             if (!writeBufferBytes(aCol, 3))
8985                 return false;
8986 
8987             aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
8988             aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
8989             aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
8990             if (!writeBufferBytes(aCol, 3))
8991                 return false;
8992             break;
8993         }
8994         default:
8995             for( int y = aSize.Height()-1; y >= 0; y-- )
8996             {
8997                 for( tools::Long x = 0; x < aSize.Width(); x++ )
8998                 {
8999                     BitmapColor aColor = pAccess->GetColor( y, x );
9000                     aCol[0] = aColor.GetRed();
9001                     aCol[1] = aColor.GetGreen();
9002                     aCol[2] = aColor.GetBlue();
9003                     if (!writeBufferBytes(aCol, 3))
9004                         return false;
9005                 }
9006             }
9007     }
9008     endCompression();
9009     disableStreamEncryption();
9010 
9011     sal_uInt64 nEndStreamPos = 0;
9012     if (osl::File::E_None != m_aFile.getPos(nEndStreamPos))
9013         return false;
9014 
9015     aLine.setLength( 0 );
9016     aLine.append( "\nendstream\nendobj\n\n" );
9017     if (!writeBuffer(aLine)) return false;
9018 
9019     // write stream length
9020     if (!updateObject(nStreamLengthObject)) return false;
9021     aLine.setLength( 0 );
9022     aLine.append( nStreamLengthObject );
9023     aLine.append( " 0 obj\n" );
9024     aLine.append( static_cast<sal_Int64>(nEndStreamPos-nStartStreamPos) );
9025     aLine.append( "\nendobj\n\n" );
9026     if (!writeBuffer(aLine)) return false;
9027 
9028     if (!updateObject(rObject.m_nObject)) return false;
9029     aLine.setLength( 0 );
9030     aLine.append( rObject.m_nObject );
9031     aLine.append( " 0 obj\n");
9032     switch (rObject.m_aGradient.GetStyle())
9033     {
9034         case css::awt::GradientStyle_LINEAR:
9035         case css::awt::GradientStyle_AXIAL:
9036             aLine.append("<</ShadingType 2\n");
9037             break;
9038         default:
9039             aLine.append("<</ShadingType 1\n");
9040     }
9041     aLine.append("/ColorSpace/DeviceRGB\n"
9042                   "/AntiAlias true\n");
9043 
9044     // Determination of shading axis
9045     // See: OutputDevice::ImplDrawLinearGradient for reference
9046     tools::Rectangle aRect;
9047     aRect.SetLeft(0);
9048     aRect.SetTop(0);
9049     aRect.SetRight( aSize.Width() );
9050     aRect.SetBottom( aSize.Height() );
9051 
9052     tools::Rectangle aBoundRect;
9053     Point     aCenter;
9054     Degree10 nAngle = rObject.m_aGradient.GetAngle() % 3600_deg10;
9055     rObject.m_aGradient.GetBoundRect( aRect, aBoundRect, aCenter );
9056 
9057     const bool bLinear = (rObject.m_aGradient.GetStyle() == css::awt::GradientStyle_LINEAR);
9058     double fBorder = aBoundRect.GetHeight() * rObject.m_aGradient.GetBorder() / 100.0;
9059     if ( !bLinear )
9060     {
9061         fBorder /= 2.0;
9062     }
9063 
9064     aBoundRect.AdjustBottom( -fBorder );
9065     if (!bLinear)
9066     {
9067         aBoundRect.AdjustTop(fBorder );
9068     }
9069 
9070     switch (rObject.m_aGradient.GetStyle())
9071     {
9072         case css::awt::GradientStyle_LINEAR:
9073         case css::awt::GradientStyle_AXIAL:
9074         {
9075             aLine.append("/Domain[ 0 1 ]\n"
9076                     "/Coords[ " );
9077             tools::Polygon aPoly( 2 );
9078             aPoly[0] = aBoundRect.BottomCenter();
9079             aPoly[1] = aBoundRect.TopCenter();
9080             aPoly.Rotate( aCenter, 3600_deg10 - nAngle );
9081 
9082             aLine.append( static_cast<sal_Int32>(aPoly[0].X()) );
9083             aLine.append( " " );
9084             aLine.append( static_cast<sal_Int32>(aPoly[0].Y()) );
9085             aLine.append( " " );
9086             aLine.append( static_cast<sal_Int32>(aPoly[1].X()));
9087             aLine.append( " ");
9088             aLine.append( static_cast<sal_Int32>(aPoly[1].Y()));
9089             aLine.append( " ]\n");
9090             aLine.append("/Extend [true true]\n");
9091             break;
9092         }
9093         default:
9094             aLine.append("/Domain[ 0 1 0 1 ]\n"
9095                     "/Matrix[ " );
9096             aLine.append( static_cast<sal_Int32>(aSize.Width()) );
9097             aLine.append( " 0 0 " );
9098             aLine.append( static_cast<sal_Int32>(aSize.Height()) );
9099             aLine.append( " 0 0 ]\n");
9100     }
9101     aLine.append("/Function " );
9102     aLine.append( nFunctionObject );
9103     aLine.append( " 0 R\n"
9104                   ">>\n"
9105                   "endobj\n\n" );
9106     return writeBuffer( aLine );
9107 }
9108 
writeJPG(const JPGEmit & rObject)9109 void PDFWriterImpl::writeJPG( const JPGEmit& rObject )
9110 {
9111     if (rObject.m_aReferenceXObject.hasExternalPDFData() && !m_aContext.UseReferenceXObject)
9112     {
9113         writeReferenceXObject(rObject.m_aReferenceXObject);
9114         return;
9115     }
9116 
9117     if (!rObject.m_pStream) return;
9118     if (!updateObject(rObject.m_nObject)) return;
9119 
9120     sal_uInt64 nLength = rObject.m_pStream->TellEnd();
9121     rObject.m_pStream->Seek( STREAM_SEEK_TO_BEGIN );
9122 
9123     sal_Int32 nMaskObject = 0;
9124     if( !rObject.m_aAlphaMask.IsEmpty() )
9125     {
9126         if (m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4
9127                 && !m_bIsPDF_A1)
9128         {
9129             nMaskObject = createObject();
9130         }
9131         else if( m_bIsPDF_A1 )
9132             m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
9133         else if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
9134             m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDF13 );
9135 
9136     }
9137     if (g_bDebugDisableCompression)
9138     {
9139         emitComment( "PDFWriterImpl::writeJPG" );
9140     }
9141 
9142     OStringBuffer aLine(200);
9143     aLine.append( rObject.m_nObject );
9144     aLine.append( " 0 obj\n"
9145                   "<</Type/XObject/Subtype/Image/Width " );
9146     aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Width()) );
9147     aLine.append( " /Height " );
9148     aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Height()) );
9149     aLine.append( " /BitsPerComponent 8 " );
9150     if( rObject.m_bTrueColor )
9151         aLine.append( "/ColorSpace/DeviceRGB" );
9152     else
9153         aLine.append( "/ColorSpace/DeviceGray" );
9154     aLine.append( "/Filter/DCTDecode/Length " );
9155     aLine.append( static_cast<sal_Int64>(nLength) );
9156     if( nMaskObject )
9157     {
9158         aLine.append(" /SMask ");
9159         aLine.append( nMaskObject );
9160         aLine.append( " 0 R " );
9161     }
9162     aLine.append( ">>\nstream\n" );
9163     if (!writeBuffer(aLine))
9164         return;
9165 
9166     checkAndEnableStreamEncryption( rObject.m_nObject );
9167     if (!writeBufferBytes(rObject.m_pStream->GetData(), nLength))
9168         return;
9169     disableStreamEncryption();
9170 
9171     aLine.setLength( 0 );
9172     if (!writeBuffer("\nendstream\nendobj\n\n"))
9173         return;
9174 
9175     if( nMaskObject )
9176         writeBitmapMaskObject( nMaskObject, rObject.m_aAlphaMask );
9177 
9178     writeReferenceXObject(rObject.m_aReferenceXObject);
9179 }
9180 
writeReferenceXObject(const ReferenceXObjectEmit & rEmit)9181 void PDFWriterImpl::writeReferenceXObject(const ReferenceXObjectEmit& rEmit)
9182 {
9183     if (rEmit.m_nFormObject <= 0)
9184         return;
9185 
9186     // Count /Matrix and /BBox.
9187     // vcl::ImportPDF() uses getDefaultPdfResolutionDpi to set the desired
9188     // rendering DPI so we have to take into account that here too.
9189     static const double fResolutionDPI = vcl::pdf::getDefaultPdfResolutionDpi();
9190     static const double fMagicScaleFactor = PDF_INSERT_MAGIC_SCALE_FACTOR;
9191 
9192     sal_Int32 nOldDPIX = GetDPIX();
9193     sal_Int32 nOldDPIY = GetDPIY();
9194     SetDPIX(fResolutionDPI);
9195     SetDPIY(fResolutionDPI);
9196     Size aSize = PixelToLogic(rEmit.m_aPixelSize, MapMode(m_aMapMode.GetMapUnit()));
9197     SetDPIX(nOldDPIX);
9198     SetDPIY(nOldDPIY);
9199     double fScaleX = 1.0 / aSize.Width();
9200     double fScaleY = 1.0 / aSize.Height();
9201 
9202     sal_Int32 nWrappedFormObject = 0;
9203     if (!m_aContext.UseReferenceXObject)
9204     {
9205         // tdf#156842 increase scale for external PDF data
9206         // Multiply PDF_INSERT_MAGIC_SCALE_FACTOR for platforms like macOS
9207         // that scale all images by this number.
9208         // This fix also allows the CppunitTest_vcl_pdfexport to run
9209         // successfully on macOS.
9210         fScaleX = fMagicScaleFactor / aSize.Width();
9211         fScaleY = fMagicScaleFactor / aSize.Height();
9212 
9213         // Parse the PDF data, we need that to write the PDF dictionary of our
9214         // object.
9215         if (rEmit.m_nExternalPDFDataIndex < 0)
9216             return;
9217         auto& rExternalPDFStream = m_aExternalPDFStreams.get(rEmit.m_nExternalPDFDataIndex);
9218         auto& pPDFDocument = rExternalPDFStream.getPDFDocument();
9219         if (!pPDFDocument)
9220         {
9221             // Couldn't parse the document and can't continue
9222             SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: failed to parse the document");
9223             return;
9224         }
9225 
9226         std::vector<filter::PDFObjectElement*> aPages = pPDFDocument->GetPages();
9227         if (aPages.empty())
9228         {
9229             SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no pages");
9230             return;
9231         }
9232 
9233         size_t nPageIndex = rEmit.m_nExternalPDFPageIndex >= 0 ? rEmit.m_nExternalPDFPageIndex : 0;
9234 
9235         filter::PDFObjectElement* pPage = aPages[nPageIndex];
9236         if (!pPage)
9237         {
9238             SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no page");
9239             return;
9240         }
9241 
9242         // Get the copied resource map, so we can use that to skip objects we already copied
9243         auto& rCopiedResourcesMap = rExternalPDFStream.getCopiedResources();
9244 
9245         // Add page mapping to the copied resources map.
9246         // Needed if we reference the current page and we want to prevent copying the page
9247         // if it is referenced.
9248         rCopiedResourcesMap.emplace(pPage->GetObjectValue(), m_aPages.back().m_nPageObject);
9249 
9250         double aOrigin[2] = { 0.0, 0.0 };
9251 
9252         // tdf#160714 use crop box for bounds of embedded PDF object
9253         // If there is no crop box, fallback to the media box just to be safe.
9254         auto* pBoundsArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("CropBox"_ostr));
9255         if (!pBoundsArray)
9256             pBoundsArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("MediaBox"_ostr));
9257         if (pBoundsArray)
9258         {
9259             const auto& rElements = pBoundsArray->GetElements();
9260             if (rElements.size() >= 4)
9261             {
9262                 // get x1, y1 of the rectangle.
9263                 for (sal_Int32 nIdx = 0; nIdx < 2; ++nIdx)
9264                 {
9265                     if (const auto* pNumElement = dynamic_cast<filter::PDFNumberElement*>(rElements[nIdx]))
9266                         aOrigin[nIdx] = pNumElement->GetValue();
9267                 }
9268             }
9269         }
9270 
9271         std::vector<filter::PDFObjectElement*> aContentStreams;
9272         if (filter::PDFObjectElement* pContentStream = pPage->LookupObject("Contents"_ostr))
9273             aContentStreams.push_back(pContentStream);
9274         else if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Contents"_ostr)))
9275         {
9276             for (const auto pElement : pArray->GetElements())
9277             {
9278                 auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
9279                 if (!pReference)
9280                     continue;
9281 
9282                 filter::PDFObjectElement* pObject = pReference->LookupObject();
9283                 if (!pObject)
9284                     continue;
9285 
9286                 aContentStreams.push_back(pObject);
9287             }
9288         }
9289 
9290         if (aContentStreams.empty())
9291         {
9292             SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no content stream");
9293             return;
9294         }
9295 
9296         // Merge link and widget annotations from pPage to our page.
9297         mergeAnnotationsFromExternalPage(pPage, rCopiedResourcesMap);
9298 
9299         nWrappedFormObject = createObject();
9300         // Write the form XObject wrapped below. This is a separate object from
9301         // the wrapper, this way there is no need to alter the stream contents.
9302 
9303         OStringBuffer aLine;
9304         aLine.append(nWrappedFormObject);
9305         aLine.append(" 0 obj\n");
9306         aLine.append("<< /Type /XObject");
9307         aLine.append(" /Subtype /Form");
9308 
9309         tools::Long nWidth = aSize.Width();
9310         tools::Long nHeight = aSize.Height();
9311         basegfx::B2DRange aBBox(0, 0, aSize.Width(),  aSize.Height());
9312         if (auto pRotate = dynamic_cast<filter::PDFNumberElement*>(pPage->Lookup("Rotate"_ostr)))
9313         {
9314             // The original page was rotated, then construct a transformation matrix which does the
9315             // same with our form object.
9316             sal_Int32 nRotAngle = static_cast<sal_Int32>(pRotate->GetValue()) % 360;
9317             // /Rotate is clockwise, matrix rotate is counter-clockwise.
9318             sal_Int32 nAngle = -1 * nRotAngle;
9319 
9320             // The bounding box just rotates.
9321             basegfx::B2DHomMatrix aBBoxMat;
9322             aBBoxMat.rotate(basegfx::deg2rad(pRotate->GetValue()));
9323             aBBox.transform(aBBoxMat);
9324 
9325             // Now transform the object: rotate around the center and make sure that the rotation
9326             // doesn't affect the aspect ratio.
9327             basegfx::B2DHomMatrix aMat;
9328             aMat.translate((-0.5 * aBBox.getWidth() / fMagicScaleFactor) - aOrigin[0], (-0.5 * aBBox.getHeight() / fMagicScaleFactor) - aOrigin[1]);
9329             aMat.rotate(basegfx::deg2rad(nAngle));
9330             aMat.translate(0.5 * nWidth / fMagicScaleFactor, 0.5 * nHeight / fMagicScaleFactor);
9331 
9332             aLine.append(" /Matrix [ ");
9333             aLine.append(aMat.a());
9334             aLine.append(" ");
9335             aLine.append(aMat.b());
9336             aLine.append(" ");
9337             aLine.append(aMat.c());
9338             aLine.append(" ");
9339             aLine.append(aMat.d());
9340             aLine.append(" ");
9341             aLine.append(aMat.e());
9342             aLine.append(" ");
9343             aLine.append(aMat.f());
9344             aLine.append(" ] ");
9345         }
9346 
9347         PDFObjectCopier aCopier(*this);
9348         aCopier.copyPageResources(pPage, aLine, rCopiedResourcesMap);
9349 
9350         aLine.append(" /BBox [ ");
9351         aLine.append(aOrigin[0]);
9352         aLine.append(' ');
9353         aLine.append(aOrigin[1]);
9354         aLine.append(' ');
9355         aLine.append(aBBox.getWidth() + aOrigin[0]);
9356         aLine.append(' ');
9357         aLine.append(aBBox.getHeight() + aOrigin[1]);
9358         aLine.append(" ]");
9359 
9360         if (!g_bDebugDisableCompression)
9361             aLine.append(" /Filter/FlateDecode");
9362         aLine.append(" /Length ");
9363 
9364         SvMemoryStream aStream;
9365         bool bCompressed = false;
9366         bool bIsTaggedNonReferenceXObject = m_aContext.Tagged && !m_aContext.UseReferenceXObject;
9367         sal_Int32 nLength = PDFObjectCopier::copyPageStreams(aContentStreams, aStream, bCompressed,
9368                                                              bIsTaggedNonReferenceXObject);
9369         aLine.append(nLength);
9370 
9371         aLine.append(">>\nstream\n");
9372         if (g_bDebugDisableCompression)
9373         {
9374             emitComment("PDFWriterImpl::writeReferenceXObject, WrappedFormObject");
9375         }
9376         if (!updateObject(nWrappedFormObject))
9377             return;
9378         if (!writeBuffer(aLine))
9379             return;
9380         aLine.setLength(0);
9381 
9382         checkAndEnableStreamEncryption(nWrappedFormObject);
9383         // Copy the original page streams to the form XObject stream.
9384         aLine.append(static_cast<const char*>(aStream.GetData()), aStream.GetSize());
9385         if (!writeBuffer(aLine))
9386             return;
9387         aLine.setLength(0);
9388         disableStreamEncryption();
9389 
9390         aLine.append("\nendstream\nendobj\n\n");
9391         if (!writeBuffer(aLine))
9392             return;
9393     }
9394 
9395     OStringBuffer aLine;
9396     if (g_bDebugDisableCompression)
9397     {
9398         emitComment("PDFWriterImpl::writeReferenceXObject, FormObject");
9399     }
9400     if (!updateObject(rEmit.m_nFormObject))
9401         return;
9402 
9403     // Now have all the info to write the form XObject.
9404     aLine.append(rEmit.m_nFormObject);
9405     aLine.append(" 0 obj\n");
9406     aLine.append("<< /Type /XObject");
9407     aLine.append(" /Subtype /Form");
9408     aLine.append(" /Resources << /XObject<<");
9409 
9410     sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject;
9411     aLine.append(" /Im");
9412     aLine.append(nObject);
9413     aLine.append(" ");
9414     aLine.append(nObject);
9415     aLine.append(" 0 R");
9416 
9417     aLine.append(">> >>");
9418     aLine.append(" /Matrix [ ");
9419     appendDouble(fScaleX, aLine);
9420     aLine.append(" 0 0 ");
9421     appendDouble(fScaleY, aLine);
9422     aLine.append(" 0 0 ]");
9423     aLine.append(" /BBox [ 0 0 ");
9424     // tdf#157680 reduce size by magic scale factor in /BBox
9425     aLine.append(aSize.Width() / fMagicScaleFactor);
9426     aLine.append(" ");
9427     // tdf#157680 reduce size by magic scale factor in /BBox
9428     aLine.append(aSize.Height() / fMagicScaleFactor);
9429     aLine.append(" ]\n");
9430 
9431     if (m_aContext.UseReferenceXObject && rEmit.m_nEmbeddedObject > 0)
9432     {
9433         // Write the reference dictionary.
9434         aLine.append("/Ref<< /F << /Type /Filespec /F (<embedded file>) ");
9435         if (PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
9436         {   // ISO 14289-1:2014, Clause: 7.11
9437             aLine.append("/UF (<embedded file>) ");
9438         }
9439         aLine.append("/EF << /F ");
9440         aLine.append(rEmit.m_nEmbeddedObject);
9441         aLine.append(" 0 R >> >> /Page 0 >>\n");
9442     }
9443 
9444     aLine.append("/Length ");
9445 
9446     OStringBuffer aStream;
9447     aStream.append("q ");
9448     if (m_aContext.UseReferenceXObject)
9449     {
9450         // Reference XObject markup is used, just refer to the fallback bitmap
9451         // here.
9452         aStream.append(aSize.Width());
9453         aStream.append(" 0 0 ");
9454         aStream.append(aSize.Height());
9455         aStream.append(" 0 0 cm\n");
9456         aStream.append("/Im");
9457         aStream.append(rEmit.m_nBitmapObject);
9458         aStream.append(" Do\n");
9459     }
9460     else
9461     {
9462         // Reset line width to the default.
9463         aStream.append(" 1 w\n");
9464 
9465         // vcl::RenderPDFBitmaps() effectively renders a white background for transparent input, be
9466         // consistent with that.
9467         aStream.append("1 1 1 rg\n");
9468         aStream.append("0 0 ");
9469         aStream.append(aSize.Width());
9470         aStream.append(" ");
9471         aStream.append(aSize.Height());
9472         aStream.append(" re\n");
9473         aStream.append("f*\n");
9474 
9475         // Reset non-stroking color in case the XObject uses the default
9476         aStream.append("0 0 0 rg\n");
9477         // No reference XObject, draw the form XObject containing the original
9478         // page streams.
9479         aStream.append("/Im");
9480         aStream.append(nWrappedFormObject);
9481         aStream.append(" Do\n");
9482     }
9483     aStream.append("Q");
9484     aLine.append(aStream.getLength());
9485 
9486     aLine.append(">>\nstream\n");
9487     if (!writeBuffer(aLine))
9488         return;
9489     aLine.setLength(0);
9490 
9491     checkAndEnableStreamEncryption(rEmit.m_nFormObject);
9492     aLine.append(aStream.getStr());
9493     if (!writeBuffer(aLine))
9494         return;
9495     aLine.setLength(0);
9496     disableStreamEncryption();
9497 
9498     aLine.append("\nendstream\nendobj\n\n");
9499     if (!writeBuffer(aLine))
9500         return;
9501 }
9502 
9503 namespace
9504 {
9505 
getRootParent(filter::PDFObjectElement * pObject)9506 sal_Int32 getRootParent(filter::PDFObjectElement* pObject)
9507 {
9508     auto* pReference = dynamic_cast<filter::PDFReferenceElement*>(pObject->Lookup("Parent"_ostr));
9509     if (!pReference)
9510         return pObject->GetObjectValue();
9511 
9512     auto* pParent = pReference->LookupObject();
9513     return getRootParent(pParent);
9514 }
9515 
9516 } // end anonymous
9517 
mergeAnnotationsFromExternalPage(filter::PDFObjectElement * pPage,std::map<sal_Int32,sal_Int32> & rCopiedResourcesMap)9518 void PDFWriterImpl::mergeAnnotationsFromExternalPage(filter::PDFObjectElement* pPage,  std::map<sal_Int32, sal_Int32>& rCopiedResourcesMap)
9519 {
9520     auto* pResult = pPage->Lookup("Annots"_ostr);
9521     filter::PDFArrayElement* pArray = nullptr;
9522     // If the Annots array is a reference - get the array from the referenced object
9523     auto pAnnotsReference = dynamic_cast<filter::PDFReferenceElement*>(pResult);
9524     if (pAnnotsReference)
9525     {
9526         filter::PDFObjectElement* pObject = pAnnotsReference->LookupObject();
9527         pArray = pObject->GetArray();
9528     }
9529     else
9530     {
9531         // Not a reference so is it an array
9532         pArray = dynamic_cast<filter::PDFArrayElement*>(pResult);
9533     }
9534 
9535     // Have we found our /Annots array?
9536     if (!pArray)
9537         return;
9538 
9539     std::unordered_set<sal_Int32> aAlreadyCopied;
9540     PDFObjectCopier aCopier(*this);
9541     SvMemoryStream& rDocBuffer = pPage->GetDocument().GetEditBuffer();
9542 
9543     for (const auto pElement : pArray->GetElements())
9544     {
9545         auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
9546         if (!pReference)
9547             continue;
9548 
9549         filter::PDFObjectElement* pObject = pReference->LookupObject();
9550         if (!pObject)
9551             continue;
9552 
9553         // Get the /Type and the /Subtype
9554         auto pType = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
9555         auto pSubtype = dynamic_cast<filter::PDFNameElement*>(pObject->Lookup("Subtype"_ostr));
9556 
9557         // Is it a /Annot we want to copy?
9558         if (pType && pType->GetValue() == "Annot" && pSubtype)
9559         {
9560             bool bIsLink = pSubtype->GetValue() == "Link";
9561             bool bIsWidget = pSubtype->GetValue() == "Widget";
9562 
9563             // is link or widget
9564             if (!bIsLink && !bIsWidget)
9565                 continue;
9566 
9567             // Copy over the annotation and refer to its new id.
9568             sal_Int32 nNewId = aCopier.copyExternalResource(rDocBuffer, *pObject, rCopiedResourcesMap);
9569             m_aPages.back().m_aAnnotations.push_back(nNewId);
9570 
9571             if (!bIsWidget)
9572                 continue;
9573 
9574             // Find the root
9575             sal_Int32 nRootID = getRootParent(pObject);
9576 
9577             auto aIterator = rCopiedResourcesMap.find(nRootID);
9578             if (aIterator == rCopiedResourcesMap.end()) // Can't find the mapped ID ?
9579                 continue;
9580 
9581             nNewId = aIterator->second;
9582 
9583             // Ignore if we added the ID already
9584             if (aAlreadyCopied.find(nNewId) == aAlreadyCopied.end())
9585             {
9586                 // Add new entry into copied widgets vector
9587                 auto& rCopiedWidget = m_aCopiedWidgets.emplace_back();
9588                 rCopiedWidget.m_nObject = nNewId;
9589                 aAlreadyCopied.emplace(nNewId);
9590             }
9591         }
9592     }
9593 
9594 }
9595 
writeBitmapObject(const BitmapEmit & rObject)9596 bool PDFWriterImpl::writeBitmapObject( const BitmapEmit& rObject )
9597 {
9598     if (rObject.m_aReferenceXObject.hasExternalPDFData() && !m_aContext.UseReferenceXObject)
9599     {
9600         writeReferenceXObject(rObject.m_aReferenceXObject);
9601         return true;
9602     }
9603 
9604     if (!updateObject(rObject.m_nObject))
9605         return false;
9606 
9607     bool    bWriteMask = false;
9608     if( rObject.m_aBitmap.HasAlpha() )
9609     {
9610         if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
9611             bWriteMask = true;
9612         // else draw without alpha channel
9613     }
9614 
9615     BitmapScopedReadAccess pAccess(rObject.m_aBitmap);
9616 
9617     bool bTrueColor = true;
9618     sal_Int32 nBitsPerComponent = 0;
9619     auto const ePixelFormat = rObject.m_aBitmap.getPixelFormat();
9620     switch (ePixelFormat)
9621     {
9622         case vcl::PixelFormat::N8_BPP:
9623             bTrueColor = false;
9624             nBitsPerComponent = vcl::pixelFormatBitCount(ePixelFormat);
9625             break;
9626         case vcl::PixelFormat::N24_BPP:
9627         case vcl::PixelFormat::N32_BPP:
9628             bTrueColor = true;
9629             nBitsPerComponent = 8;
9630             break;
9631         case vcl::PixelFormat::INVALID:
9632             return false;
9633     }
9634 
9635     sal_Int32 nStreamLengthObject   = createObject();
9636     sal_Int32 nMaskObject           = 0;
9637 
9638     if (g_bDebugDisableCompression)
9639     {
9640         emitComment( "PDFWriterImpl::writeBitmapObject" );
9641     }
9642     OStringBuffer aLine(1024);
9643     aLine.append( rObject.m_nObject );
9644     aLine.append( " 0 obj\n"
9645                   "<</Type/XObject/Subtype/Image/Width " );
9646     aLine.append( static_cast<sal_Int32>(rObject.m_aBitmap.GetSizePixel().Width()) );
9647     aLine.append( "/Height " );
9648     aLine.append( static_cast<sal_Int32>(rObject.m_aBitmap.GetSizePixel().Height()) );
9649     aLine.append( "/BitsPerComponent " );
9650     aLine.append( nBitsPerComponent );
9651     aLine.append( "/Length " );
9652     aLine.append( nStreamLengthObject );
9653     aLine.append( " 0 R\n" );
9654     if (!g_bDebugDisableCompression)
9655     {
9656         if( nBitsPerComponent != 1 )
9657         {
9658             aLine.append( "/Filter/FlateDecode" );
9659         }
9660         else
9661         {
9662             aLine.append( "/Filter/CCITTFaxDecode/DecodeParms<</K -1/BlackIs1 true/Columns " );
9663             aLine.append( static_cast<sal_Int32>(rObject.m_aBitmap.GetSizePixel().Width()) );
9664             aLine.append( ">>\n" );
9665         }
9666     }
9667     aLine.append( "/ColorSpace" );
9668     if( bTrueColor )
9669         aLine.append( "/DeviceRGB\n" );
9670     else
9671     {
9672         aLine.append( "[ /Indexed/DeviceRGB " );
9673         aLine.append( static_cast<sal_Int32>(pAccess->GetPaletteEntryCount()-1) );
9674         aLine.append( "\n<" );
9675         if (m_aContext.Encryption.canEncrypt())
9676         {
9677             enableStringEncryption(rObject.m_nObject);
9678             //check encryption buffer size
9679             m_vEncryptionBuffer.resize(pAccess->GetPaletteEntryCount()*3);
9680             int nChar = 0;
9681             //fill the encryption buffer
9682             for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
9683             {
9684                 const BitmapColor& rColor = pAccess->GetPaletteColor( i );
9685                 m_vEncryptionBuffer[nChar++] = rColor.GetRed();
9686                 m_vEncryptionBuffer[nChar++] = rColor.GetGreen();
9687                 m_vEncryptionBuffer[nChar++] = rColor.GetBlue();
9688             }
9689             //encrypt the colorspace lookup table
9690             std::vector<sal_uInt8> aOutputBuffer(nChar);
9691             m_pPDFEncryptor->encrypt(m_vEncryptionBuffer.data(), nChar, aOutputBuffer, nChar);
9692             //now queue the data for output
9693             COSWriter::appendHexArray(aOutputBuffer.data(), aOutputBuffer.size(), aLine);
9694         }
9695         else //no encryption requested (PDF/A-1a program flow drops here)
9696         {
9697             for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
9698             {
9699                 const BitmapColor& rColor = pAccess->GetPaletteColor( i );
9700                 COSWriter::appendHex( rColor.GetRed(), aLine );
9701                 COSWriter::appendHex( rColor.GetGreen(), aLine );
9702                 COSWriter::appendHex( rColor.GetBlue(), aLine );
9703             }
9704         }
9705         aLine.append( ">\n]\n" );
9706     }
9707 
9708     if (!m_bIsPDF_A1)
9709     {
9710         if( bWriteMask )
9711         {
9712             nMaskObject = createObject();
9713             aLine.append( "/SMask " );
9714             aLine.append( nMaskObject );
9715             aLine.append( " 0 R\n" );
9716         }
9717     }
9718     else if( m_bIsPDF_A1 && bWriteMask )
9719         m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
9720 
9721     aLine.append( ">>\n"
9722                   "stream\n" );
9723     if (!writeBuffer(aLine)) return false;
9724     sal_uInt64 nStartPos = 0;
9725     if (osl::File::E_None != m_aFile.getPos(nStartPos))
9726         return false;
9727 
9728     checkAndEnableStreamEncryption( rObject.m_nObject );
9729     if (!g_bDebugDisableCompression && nBitsPerComponent == 1)
9730     {
9731         writeG4Stream(pAccess.get());
9732     }
9733     else
9734     {
9735         beginCompression();
9736         if( ! bTrueColor || pAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb )
9737         {
9738             //With PDF bitmaps, each row is padded to a BYTE boundary (multiple of 8 bits).
9739             const int nScanLineBytes = ((pAccess->GetBitCount() * pAccess->Width()) + 7U) / 8U;
9740 
9741             for( tools::Long i = 0, nHeight = pAccess->Height(); i < nHeight; i++ )
9742             {
9743                 if (!writeBufferBytes(pAccess->GetScanline(i), nScanLineBytes))
9744                     return false;
9745             }
9746         }
9747         else
9748         {
9749             const int nScanLineBytes = pAccess->Width()*3;
9750             std::unique_ptr<sal_uInt8[]> xCol(new sal_uInt8[nScanLineBytes]);
9751             for( tools::Long y = 0, nHeight = pAccess->Height(); y < nHeight; y++ )
9752             {
9753                 for( tools::Long x = 0, nWidth = pAccess->Width(); x < nWidth; x++ )
9754                 {
9755                     BitmapColor aColor = pAccess->GetColor( y, x );
9756                     xCol[3*x+0] = aColor.GetRed();
9757                     xCol[3*x+1] = aColor.GetGreen();
9758                     xCol[3*x+2] = aColor.GetBlue();
9759                 }
9760                 if (!writeBufferBytes(xCol.get(), nScanLineBytes))
9761                     return false;
9762             }
9763         }
9764         endCompression();
9765     }
9766     disableStreamEncryption();
9767 
9768     sal_uInt64 nEndPos = 0;
9769     if (osl::File::E_None != m_aFile.getPos(nEndPos))
9770         return false;
9771     aLine.setLength( 0 );
9772     aLine.append( "\nendstream\nendobj\n\n" );
9773     if (!writeBuffer(aLine)) return false;
9774     if (!updateObject(nStreamLengthObject)) return false;
9775     aLine.setLength( 0 );
9776     aLine.append( nStreamLengthObject );
9777     aLine.append( " 0 obj\n" );
9778     aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
9779     aLine.append( "\nendobj\n\n" );
9780     if (!writeBuffer(aLine)) return false;
9781 
9782     if( nMaskObject )
9783         return writeBitmapMaskObject( nMaskObject, rObject.m_aBitmap.CreateAlphaMask() );
9784 
9785     writeReferenceXObject(rObject.m_aReferenceXObject);
9786 
9787     return true;
9788 }
9789 
writeBitmapMaskObject(sal_Int32 nMaskObject,const AlphaMask & rAlphaMask)9790 bool PDFWriterImpl::writeBitmapMaskObject( sal_Int32 nMaskObject, const AlphaMask& rAlphaMask )
9791 {
9792     assert( rAlphaMask.GetBitmap().getPixelFormat() == vcl::PixelFormat::N8_BPP );
9793 
9794     if (!updateObject(nMaskObject))
9795         return false;
9796 
9797     Bitmap  aBitmap;
9798     if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
9799     {
9800         aBitmap = rAlphaMask.GetBitmap();
9801         aBitmap.Convert( BmpConversion::N1BitThreshold );
9802     }
9803     else
9804     {
9805         aBitmap = rAlphaMask.GetBitmap();
9806     }
9807 
9808     const sal_Int32 nBitsPerComponent = 8;
9809 
9810     sal_Int32 nStreamLengthObject   = createObject();
9811 
9812     if (g_bDebugDisableCompression)
9813     {
9814         emitComment( "PDFWriterImpl::writeBitmapObject" );
9815     }
9816     OStringBuffer aLine(1024);
9817     aLine.append( nMaskObject );
9818     aLine.append( " 0 obj\n"
9819                   "<</Type/XObject/Subtype/Image/Width " );
9820     aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Width()) );
9821     aLine.append( "/Height " );
9822     aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Height()) );
9823     aLine.append( "/BitsPerComponent " );
9824     aLine.append( nBitsPerComponent );
9825     aLine.append( "/Length " );
9826     aLine.append( nStreamLengthObject );
9827     aLine.append( " 0 R\n" );
9828     if (!g_bDebugDisableCompression)
9829     {
9830         aLine.append( "/Filter/FlateDecode" );
9831     }
9832     aLine.append( "/ColorSpace/DeviceGray\n"
9833                   "/Decode [ 1 0 ]\n" );
9834 
9835     aLine.append( ">>\n"
9836                   "stream\n" );
9837     if (!writeBuffer(aLine)) return false;
9838     sal_uInt64 nStartPos = 0;
9839     if (osl::File::E_None != m_aFile.getPos(nStartPos))
9840         return false;
9841 
9842     checkAndEnableStreamEncryption( nMaskObject );
9843     beginCompression();
9844     BitmapScopedReadAccess pAccess(aBitmap);
9845     //With PDF bitmaps, each row is padded to a BYTE boundary (multiple of 8 bits).
9846     const int nScanLineBytes = ((pAccess->GetBitCount() * pAccess->Width()) + 7U) / 8U;
9847     // we have alpha, but we want to output transparency, so we need to invert the data
9848     std::unique_ptr<sal_uInt8[]> pInvertedBytes = std::make_unique<sal_uInt8[]>(nScanLineBytes);
9849     for( tools::Long i = 0, nHeight = pAccess->Height(); i < nHeight; i++ )
9850     {
9851         const Scanline pScanline = pAccess->GetScanline(i);
9852         std::copy(pScanline, pScanline + nScanLineBytes, pInvertedBytes.get());
9853         for (auto p = pInvertedBytes.get(); p < pInvertedBytes.get() + nScanLineBytes; ++p)
9854             *p = ~(*p);
9855         if (!writeBufferBytes(pInvertedBytes.get(), nScanLineBytes))
9856             return false;
9857     }
9858     endCompression();
9859     disableStreamEncryption();
9860 
9861     sal_uInt64 nEndPos = 0;
9862     if (osl::File::E_None != m_aFile.getPos(nEndPos))
9863         return false;
9864     aLine.setLength( 0 );
9865     aLine.append( "\nendstream\nendobj\n\n" );
9866     if (!writeBuffer(aLine)) return false;
9867     if (!updateObject(nStreamLengthObject)) return false;
9868     aLine.setLength( 0 );
9869     aLine.append( nStreamLengthObject );
9870     aLine.append( " 0 obj\n" );
9871     aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
9872     aLine.append( "\nendobj\n\n" );
9873     if (!writeBuffer(aLine)) return false;
9874 
9875     return true;
9876 }
9877 
createEmbeddedFile(const Graphic & rGraphic,ReferenceXObjectEmit & rEmit,sal_Int32 nBitmapObject)9878 void PDFWriterImpl::createEmbeddedFile(const Graphic& rGraphic, ReferenceXObjectEmit& rEmit, sal_Int32 nBitmapObject)
9879 {
9880     // The bitmap object is always a valid identifier, even if the graphic has
9881     // no pdf data.
9882     rEmit.m_nBitmapObject = nBitmapObject;
9883 
9884     if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf)
9885         return;
9886 
9887     BinaryDataContainer const & rDataContainer = rGraphic.getVectorGraphicData()->getBinaryDataContainer();
9888 
9889     if (m_aContext.UseReferenceXObject)
9890     {
9891         // Store the original PDF data as an embedded file.
9892         auto nObjectID = addEmbeddedFile(rDataContainer);
9893         rEmit.m_nEmbeddedObject = nObjectID;
9894     }
9895     else
9896     {
9897         sal_Int32 aIndex = m_aExternalPDFStreams.store(rDataContainer);
9898         rEmit.m_nExternalPDFPageIndex = rGraphic.getVectorGraphicData()->getPageIndex();
9899         rEmit.m_nExternalPDFDataIndex = aIndex;
9900     }
9901 
9902     rEmit.m_nFormObject = createObject();
9903     rEmit.m_aPixelSize = rGraphic.GetSizePixel();
9904 }
9905 
drawJPGBitmap(SvStream & rDCTData,bool bIsTrueColor,const Size & rSizePixel,const tools::Rectangle & rTargetArea,const AlphaMask & rAlphaMask,const Graphic & rGraphic)9906 void PDFWriterImpl::drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const Size& rSizePixel, const tools::Rectangle& rTargetArea, const AlphaMask& rAlphaMask, const Graphic& rGraphic )
9907 {
9908     MARK( "drawJPGBitmap" );
9909 
9910     OStringBuffer aLine( 80 );
9911     updateGraphicsState();
9912 
9913     // #i40055# sanity check
9914     if( ! (rTargetArea.GetWidth() && rTargetArea.GetHeight() ) )
9915         return;
9916     if( ! (rSizePixel.Width() && rSizePixel.Height()) )
9917         return;
9918 
9919     rDCTData.Seek( 0 );
9920     if( bIsTrueColor && m_aContext.ColorMode == PDFWriter::DrawGreyscale )
9921     {
9922         // need to convert to grayscale;
9923         // load stream to bitmap and draw the bitmap instead
9924         Graphic aGraphic;
9925         GraphicConverter::Import( rDCTData, aGraphic, ConvertDataFormat::JPG );
9926         if( !rAlphaMask.IsEmpty() && rAlphaMask.GetSizePixel() == aGraphic.GetSizePixel() )
9927         {
9928             Bitmap aBmp( aGraphic.GetBitmap().CreateColorBitmap() );
9929             BitmapEx aBmpEx( aBmp, rAlphaMask );
9930             drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), Bitmap(aBmpEx) );
9931         }
9932         else
9933             drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aGraphic.GetBitmap() );
9934         return;
9935     }
9936 
9937     std::unique_ptr<SvMemoryStream> pStream(new SvMemoryStream);
9938     pStream->WriteStream( rDCTData );
9939     pStream->Seek( STREAM_SEEK_TO_END );
9940 
9941     BitmapID aID;
9942     aID.m_aPixelSize    = rSizePixel;
9943     aID.m_nSize         = pStream->Tell();
9944     pStream->Seek( STREAM_SEEK_TO_BEGIN );
9945     aID.m_nChecksum     = rtl_crc32( 0, pStream->GetData(), aID.m_nSize );
9946     if( ! rAlphaMask.IsEmpty() )
9947         aID.m_nMaskChecksum = rAlphaMask.GetChecksum();
9948 
9949     std::vector< JPGEmit >::const_iterator it = std::find_if(m_aJPGs.begin(), m_aJPGs.end(),
9950                                              [&](const JPGEmit& arg) { return aID == arg.m_aID; });
9951     if( it == m_aJPGs.end() )
9952     {
9953         m_aJPGs.emplace( m_aJPGs.begin() );
9954         JPGEmit& rEmit = m_aJPGs.front();
9955         if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject)
9956             rEmit.m_nObject = createObject();
9957         rEmit.m_aID         = aID;
9958         rEmit.m_pStream = std::move( pStream );
9959         rEmit.m_bTrueColor  = bIsTrueColor;
9960         if( !rAlphaMask.IsEmpty() && rAlphaMask.GetSizePixel() == rSizePixel )
9961             rEmit.m_aAlphaMask = rAlphaMask;
9962         createEmbeddedFile(rGraphic, rEmit.m_aReferenceXObject, rEmit.m_nObject);
9963 
9964         it = m_aJPGs.begin();
9965     }
9966 
9967     aLine.append( "q " );
9968     sal_Int32 nCheckWidth = 0;
9969     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetWidth()), aLine, false, &nCheckWidth );
9970     aLine.append( " 0 0 " );
9971     sal_Int32 nCheckHeight = 0;
9972     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetHeight()), aLine, true, &nCheckHeight );
9973     aLine.append( ' ' );
9974     m_aPages.back().appendPoint( rTargetArea.BottomLeft(), aLine );
9975     aLine.append( " cm\n/Im" );
9976     sal_Int32 nObject = it->m_aReferenceXObject.getObject();
9977     aLine.append(nObject);
9978     aLine.append( " Do Q\n" );
9979     if( nCheckWidth == 0 || nCheckHeight == 0 )
9980     {
9981         // #i97512# avoid invalid current matrix
9982         aLine.setLength( 0 );
9983         aLine.append( "\n%jpeg image /Im" );
9984         aLine.append( it->m_nObject );
9985         aLine.append( " scaled to zero size, omitted\n" );
9986     }
9987     writeBuffer( aLine );
9988 
9989     OString aObjName = "Im" + OString::number(nObject);
9990     pushResource( ResourceKind::XObject, aObjName, nObject );
9991 
9992 }
9993 
drawBitmap(const Point & rDestPoint,const Size & rDestSize,const BitmapEmit & rBitmap,const Color & rFillColor)9994 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEmit& rBitmap, const Color& rFillColor )
9995 {
9996     OStringBuffer& rLine = drawBitmapLine;
9997     rLine.setLength(0);
9998     updateGraphicsState();
9999 
10000     rLine.append( "q " );
10001     if( rFillColor != COL_TRANSPARENT )
10002     {
10003         appendNonStrokingColor( rFillColor, rLine );
10004         rLine.append( ' ' );
10005     }
10006     sal_Int32 nCheckWidth = 0;
10007     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Width()), rLine, false, &nCheckWidth );
10008     rLine.append( " 0 0 " );
10009     sal_Int32 nCheckHeight = 0;
10010     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Height()), rLine, true, &nCheckHeight );
10011     rLine.append( ' ' );
10012     m_aPages.back().appendPoint( rDestPoint + Point( 0, rDestSize.Height()-1 ), rLine );
10013     rLine.append( " cm\n/Im" );
10014     sal_Int32 nObject = rBitmap.m_aReferenceXObject.getObject();
10015     rLine.append(nObject);
10016     rLine.append( " Do Q\n" );
10017     if( nCheckWidth == 0 || nCheckHeight == 0 )
10018     {
10019         // #i97512# avoid invalid current matrix
10020         rLine.setLength( 0 );
10021         rLine.append( "\n%bitmap image /Im" );
10022         rLine.append( rBitmap.m_nObject );
10023         rLine.append( " scaled to zero size, omitted\n" );
10024     }
10025     writeBuffer( rLine );
10026 }
10027 
createBitmapEmit(const Bitmap & i_rBitmap,const Graphic & rGraphic,std::list<BitmapEmit> & rBitmaps,ResourceDict & rResourceDict,std::list<StreamRedirect> & rOutputStreams)10028 const BitmapEmit& PDFWriterImpl::createBitmapEmit(const Bitmap& i_rBitmap, const Graphic& rGraphic, std::list<BitmapEmit>& rBitmaps, ResourceDict& rResourceDict, std::list<StreamRedirect>& rOutputStreams)
10029 {
10030     Bitmap aBitmap( i_rBitmap );
10031     auto ePixelFormat = aBitmap.getPixelFormat();
10032     if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
10033         aBitmap.Convert(BmpConversion::N8BitGreys);
10034     BitmapID aID;
10035     aID.m_aPixelSize        = aBitmap.GetSizePixel();
10036     aID.m_nSize             = vcl::pixelFormatBitCount(ePixelFormat);
10037     aID.m_nChecksum         = BitmapEx(aBitmap).GetBitmap().GetChecksum();
10038     aID.m_nMaskChecksum     = 0;
10039     if( aBitmap.HasAlpha() )
10040         aID.m_nMaskChecksum = aBitmap.CreateAlphaMask().GetChecksum();
10041     std::list<BitmapEmit>::const_iterator it = std::find_if(rBitmaps.begin(), rBitmaps.end(),
10042                                              [&](const BitmapEmit& arg) { return aID == arg.m_aID; });
10043     if (it == rBitmaps.end())
10044     {
10045         rBitmaps.push_front(BitmapEmit());
10046         rBitmaps.front().m_aID = aID;
10047         rBitmaps.front().m_aBitmap = std::move(aBitmap);
10048         if (!rGraphic.getVectorGraphicData() || rGraphic.getVectorGraphicData()->getType() != VectorGraphicDataType::Pdf || m_aContext.UseReferenceXObject)
10049             rBitmaps.front().m_nObject = createObject();
10050         createEmbeddedFile(rGraphic, rBitmaps.front().m_aReferenceXObject, rBitmaps.front().m_nObject);
10051         it = rBitmaps.begin();
10052     }
10053 
10054     sal_Int32 nObject = it->m_aReferenceXObject.getObject();
10055     OString aObjName = "Im" + OString::number(nObject);
10056     pushResource(ResourceKind::XObject, aObjName, nObject, rResourceDict, rOutputStreams);
10057 
10058     return *it;
10059 }
10060 
createBitmapEmit(const Bitmap & i_rBitmap,const Graphic & rGraphic)10061 const BitmapEmit& PDFWriterImpl::createBitmapEmit( const Bitmap& i_rBitmap, const Graphic& rGraphic )
10062 {
10063     return createBitmapEmit(i_rBitmap, rGraphic, m_aBitmaps, m_aGlobalResourceDict, m_aOutputStreams);
10064 }
10065 
drawBitmap(const Point & rDestPoint,const Size & rDestSize,const Bitmap & rBitmap,const Graphic & rGraphic)10066 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap, const Graphic& rGraphic )
10067 {
10068     MARK( "drawBitmap (Bitmap)" );
10069 
10070     // #i40055# sanity check
10071     if( ! (rDestSize.Width() && rDestSize.Height()) )
10072         return;
10073 
10074     const BitmapEmit& rEmit = createBitmapEmit( rBitmap, rGraphic );
10075     drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
10076 }
10077 
drawBitmap(const Point & rDestPoint,const Size & rDestSize,const Bitmap & rBitmap)10078 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap )
10079 {
10080     MARK( "drawBitmap (BitmapEx)" );
10081 
10082     // #i40055# sanity check
10083     if( ! (rDestSize.Width() && rDestSize.Height()) )
10084         return;
10085 
10086     const BitmapEmit& rEmit = createBitmapEmit( rBitmap, Graphic() );
10087     drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
10088 }
10089 
createGradient(const Gradient & rGradient,const Size & rSize)10090 sal_Int32 PDFWriterImpl::createGradient( const Gradient& rGradient, const Size& rSize )
10091 {
10092     Size aPtSize( lcl_convert( m_aGraphicsStack.front().m_aMapMode,
10093                                MapMode( MapUnit::MapPoint ),
10094                                this,
10095                                rSize ) );
10096     // check if we already have this gradient
10097     // rounding to point will generally lose some pixels
10098     // round up to point boundary
10099     aPtSize.AdjustWidth( 1 );
10100     aPtSize.AdjustHeight( 1 );
10101     std::list< GradientEmit >::const_iterator it = std::find_if(m_aGradients.begin(), m_aGradients.end(),
10102                                              [&](const GradientEmit& arg) { return ((rGradient == arg.m_aGradient) && (aPtSize == arg.m_aSize) ); });
10103 
10104     if( it == m_aGradients.end() )
10105     {
10106         m_aGradients.push_front( GradientEmit() );
10107         m_aGradients.front().m_aGradient    = rGradient;
10108         m_aGradients.front().m_nObject      = createObject();
10109         m_aGradients.front().m_aSize        = aPtSize;
10110         it = m_aGradients.begin();
10111     }
10112 
10113     OStringBuffer aObjName( 16 );
10114     aObjName.append( 'P' );
10115     aObjName.append( it->m_nObject );
10116     pushResource( ResourceKind::Shading, aObjName.makeStringAndClear(), it->m_nObject );
10117 
10118     return it->m_nObject;
10119 }
10120 
drawGradient(const tools::Rectangle & rRect,const Gradient & rGradient)10121 void PDFWriterImpl::drawGradient( const tools::Rectangle& rRect, const Gradient& rGradient )
10122 {
10123     MARK( "drawGradient (Rectangle)" );
10124 
10125     sal_Int32 nGradient = createGradient( rGradient, rRect.GetSize() );
10126 
10127     Point aTranslate( rRect.BottomLeft() );
10128     aTranslate += Point( 0, 1 );
10129 
10130     updateGraphicsState();
10131 
10132     OStringBuffer aLine( 80 );
10133     aLine.append( "q 1 0 0 1 " );
10134     m_aPages.back().appendPoint( aTranslate, aLine );
10135     aLine.append( " cm " );
10136     // if a stroke is appended reset the clip region before stroke
10137     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
10138         aLine.append( "q " );
10139     aLine.append( "0 0 " );
10140     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
10141     aLine.append( ' ' );
10142     m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
10143     aLine.append( " re W n\n" );
10144 
10145     aLine.append( "/P" );
10146     aLine.append( nGradient );
10147     aLine.append( " sh " );
10148     if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
10149     {
10150         aLine.append( "Q 0 0 " );
10151         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
10152         aLine.append( ' ' );
10153         m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
10154         aLine.append( " re S " );
10155     }
10156     aLine.append( "Q\n" );
10157     writeBuffer( aLine );
10158 }
10159 
drawHatch(const tools::PolyPolygon & rPolyPoly,const Hatch & rHatch)10160 void PDFWriterImpl::drawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch )
10161 {
10162     MARK( "drawHatch" );
10163 
10164     updateGraphicsState();
10165 
10166     if( rPolyPoly.Count() )
10167     {
10168         tools::PolyPolygon     aPolyPoly( rPolyPoly );
10169 
10170         aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME );
10171         push( PushFlags::LINECOLOR );
10172         setLineColor( rHatch.GetColor() );
10173         DrawHatch( aPolyPoly, rHatch, false );
10174         pop();
10175     }
10176 }
10177 
drawWallpaper(const tools::Rectangle & rRect,const Wallpaper & rWall)10178 void PDFWriterImpl::drawWallpaper( const tools::Rectangle& rRect, const Wallpaper& rWall )
10179 {
10180     MARK( "drawWallpaper" );
10181 
10182     bool bDrawColor         = false;
10183     bool bDrawGradient      = false;
10184     bool bDrawBitmap        = false;
10185 
10186     Bitmap aBitmap;
10187     Point aBmpPos = rRect.TopLeft();
10188     Size aBmpSize;
10189     if( rWall.IsBitmap() )
10190     {
10191         aBitmap = rWall.GetBitmap();
10192         aBmpSize = lcl_convert( aBitmap.GetPrefMapMode(),
10193                                 getMapMode(),
10194                                 this,
10195                                 aBitmap.GetPrefSize() );
10196         tools::Rectangle aRect( rRect );
10197         if( rWall.IsRect() )
10198         {
10199             aRect = rWall.GetRect();
10200             aBmpPos = aRect.TopLeft();
10201             aBmpSize = aRect.GetSize();
10202         }
10203         if( rWall.GetStyle() != WallpaperStyle::Scale )
10204         {
10205             if( rWall.GetStyle() != WallpaperStyle::Tile )
10206             {
10207                 bDrawBitmap     = true;
10208                 if( rWall.IsGradient() )
10209                     bDrawGradient = true;
10210                 else
10211                     bDrawColor = true;
10212                 switch( rWall.GetStyle() )
10213                 {
10214                     case WallpaperStyle::TopLeft:
10215                         break;
10216                     case WallpaperStyle::Top:
10217                         aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
10218                         break;
10219                     case WallpaperStyle::Left:
10220                         aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
10221                         break;
10222                     case WallpaperStyle::TopRight:
10223                         aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
10224                         break;
10225                     case WallpaperStyle::Center:
10226                         aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
10227                         aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
10228                         break;
10229                     case WallpaperStyle::Right:
10230                         aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
10231                         aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
10232                         break;
10233                     case WallpaperStyle::BottomLeft:
10234                         aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
10235                         break;
10236                     case WallpaperStyle::Bottom:
10237                         aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
10238                         aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
10239                         break;
10240                     case WallpaperStyle::BottomRight:
10241                         aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
10242                         aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
10243                         break;
10244                     default: ;
10245                 }
10246             }
10247             else
10248             {
10249                 // push the bitmap
10250                 const BitmapEmit& rEmit = createBitmapEmit( aBitmap, Graphic() );
10251 
10252                 // convert to page coordinates; this needs to be done here
10253                 // since the emit does not know the page anymore
10254                 tools::Rectangle aConvertRect( aBmpPos, aBmpSize );
10255                 m_aPages.back().convertRect( aConvertRect );
10256 
10257                 OString aImageName = "Im" + OString::number( rEmit.m_nObject );
10258 
10259                 // push the pattern
10260                 OStringBuffer aTilingStream( 32 );
10261                 appendFixedInt( aConvertRect.GetWidth(), aTilingStream );
10262                 aTilingStream.append( " 0 0 " );
10263                 appendFixedInt( aConvertRect.GetHeight(), aTilingStream );
10264                 aTilingStream.append( " 0 0 cm\n/" );
10265                 aTilingStream.append( aImageName );
10266                 aTilingStream.append( " Do\n" );
10267 
10268                 m_aTilings.emplace_back( );
10269                 m_aTilings.back().m_nObject         = createObject();
10270                 m_aTilings.back().m_aRectangle      = tools::Rectangle( Point( 0, 0 ), aConvertRect.GetSize() );
10271                 m_aTilings.back().m_pTilingStream.reset(new SvMemoryStream());
10272                 m_aTilings.back().m_pTilingStream->WriteBytes(
10273                     aTilingStream.getStr(), aTilingStream.getLength() );
10274                 // phase the tiling so wallpaper begins on upper left
10275                 if ((aConvertRect.GetWidth() == 0) || (aConvertRect.GetHeight() == 0))
10276                     throw o3tl::divide_by_zero();
10277                 m_aTilings.back().m_aTransform.matrix[2] = double(aConvertRect.Left() % aConvertRect.GetWidth()) / fDivisor;
10278                 m_aTilings.back().m_aTransform.matrix[5] = double(aConvertRect.Top() % aConvertRect.GetHeight()) / fDivisor;
10279                 m_aTilings.back().m_aResources.m_aXObjects[aImageName] = rEmit.m_nObject;
10280 
10281                 updateGraphicsState();
10282 
10283                 OStringBuffer aObjName( 16 );
10284                 aObjName.append( 'P' );
10285                 aObjName.append( m_aTilings.back().m_nObject );
10286                 OString aPatternName( aObjName.makeStringAndClear() );
10287                 pushResource( ResourceKind::Pattern, aPatternName, m_aTilings.back().m_nObject );
10288 
10289                 // fill a rRect with the pattern
10290                 OStringBuffer aLine( 100 );
10291                 aLine.append( "q /Pattern cs /" );
10292                 aLine.append( aPatternName );
10293                 aLine.append( " scn " );
10294                 m_aPages.back().appendRect( rRect, aLine );
10295                 aLine.append( " f Q\n" );
10296                 writeBuffer( aLine );
10297             }
10298         }
10299         else
10300         {
10301             aBmpPos     = aRect.TopLeft();
10302             aBmpSize    = aRect.GetSize();
10303             bDrawBitmap = true;
10304         }
10305 
10306         if( aBitmap.HasAlpha() )
10307         {
10308             if( rWall.IsGradient() )
10309                 bDrawGradient = true;
10310             else
10311                 bDrawColor = true;
10312         }
10313     }
10314     else if( rWall.IsGradient() )
10315         bDrawGradient = true;
10316     else
10317         bDrawColor = true;
10318 
10319     if( bDrawGradient )
10320     {
10321         drawGradient( rRect, rWall.GetGradient() );
10322     }
10323     if( bDrawColor )
10324     {
10325         Color aOldLineColor = m_aGraphicsStack.front().m_aLineColor;
10326         Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
10327         setLineColor( COL_TRANSPARENT );
10328         setFillColor( rWall.GetColor() );
10329         drawRectangle( rRect );
10330         setLineColor( aOldLineColor );
10331         setFillColor( aOldFillColor );
10332     }
10333     if( bDrawBitmap )
10334     {
10335         // set temporary clip region since aBmpPos and aBmpSize
10336         // may be outside rRect
10337         OStringBuffer aLine( 20 );
10338         aLine.append( "q " );
10339         m_aPages.back().appendRect( rRect, aLine );
10340         aLine.append( " W n\n" );
10341         writeBuffer( aLine );
10342         drawBitmap( aBmpPos, aBmpSize, aBitmap );
10343         writeBuffer( "Q\n" );
10344     }
10345 }
10346 
updateGraphicsState(Mode const mode)10347  void PDFWriterImpl::updateGraphicsState(Mode const mode)
10348 {
10349     OStringBuffer& rLine = updateGraphicsStateLine;
10350     rLine.setLength(0);
10351     GraphicsState& rNewState = m_aGraphicsStack.front();
10352     // first set clip region since it might invalidate everything else
10353 
10354     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::ClipRegion )
10355     {
10356         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::ClipRegion;
10357 
10358         if( m_aCurrentPDFState.m_bClipRegion != rNewState.m_bClipRegion ||
10359             ( rNewState.m_bClipRegion && m_aCurrentPDFState.m_aClipRegion != rNewState.m_aClipRegion ) )
10360         {
10361             if( m_aCurrentPDFState.m_bClipRegion )
10362             {
10363                 rLine.append( "Q " );
10364                 // invalidate everything but the clip region
10365                 m_aCurrentPDFState = GraphicsState();
10366                 rNewState.m_nUpdateFlags = ~GraphicsStateUpdateFlags::ClipRegion;
10367             }
10368             if( rNewState.m_bClipRegion )
10369             {
10370                 // clip region is always stored in private PDF mapmode
10371                 MapMode aNewMapMode = std::move(rNewState.m_aMapMode);
10372                 rNewState.m_aMapMode = m_aMapMode;
10373                 SetMapMode( rNewState.m_aMapMode );
10374                 m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
10375 
10376                 rLine.append("q ");
10377                 if ( rNewState.m_aClipRegion.count() )
10378                 {
10379                     m_aPages.back().appendPolyPolygon( rNewState.m_aClipRegion, rLine );
10380                 }
10381                 else
10382                 {
10383                     // tdf#130150 Need to revert tdf#99680, that breaks the
10384                     // rule that an set but empty clip-region clips everything
10385                     // aka draws nothing -> nothing is in an empty clip-region
10386                     rLine.append( "0 0 m h " ); // NULL clip, i.e. nothing visible
10387                 }
10388                 rLine.append( "W* n\n" );
10389 
10390                 rNewState.m_aMapMode = std::move(aNewMapMode);
10391                 SetMapMode( rNewState.m_aMapMode );
10392                 m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
10393             }
10394         }
10395     }
10396 
10397     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::MapMode )
10398     {
10399         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::MapMode;
10400         SetMapMode( rNewState.m_aMapMode );
10401     }
10402 
10403     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::Font )
10404     {
10405         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::Font;
10406         SetFont( rNewState.m_aFont );
10407     }
10408 
10409     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LayoutMode )
10410     {
10411         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LayoutMode;
10412         SetLayoutMode( rNewState.m_nLayoutMode );
10413     }
10414 
10415     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::DigitLanguage )
10416     {
10417         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::DigitLanguage;
10418         SetDigitLanguage( rNewState.m_aDigitLanguage );
10419     }
10420 
10421     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LineColor )
10422     {
10423         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LineColor;
10424         if( m_aCurrentPDFState.m_aLineColor != rNewState.m_aLineColor &&
10425             rNewState.m_aLineColor != COL_TRANSPARENT )
10426         {
10427             appendStrokingColor( rNewState.m_aLineColor, rLine );
10428             rLine.append( "\n" );
10429         }
10430     }
10431 
10432     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::FillColor )
10433     {
10434         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::FillColor;
10435         if( m_aCurrentPDFState.m_aFillColor != rNewState.m_aFillColor &&
10436             rNewState.m_aFillColor != COL_TRANSPARENT )
10437         {
10438             appendNonStrokingColor( rNewState.m_aFillColor, rLine );
10439             rLine.append( "\n" );
10440         }
10441     }
10442 
10443     if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::TransparentPercent )
10444     {
10445         rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::TransparentPercent;
10446         if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
10447         {
10448             // TODO: switch extended graphicsstate
10449         }
10450     }
10451 
10452     // everything is up to date now
10453     m_aCurrentPDFState = m_aGraphicsStack.front();
10454     if ((mode != Mode::NOWRITE) &&  !rLine.isEmpty())
10455         writeBuffer( rLine );
10456 }
10457 
10458 /* #i47544# imitate OutputDevice behaviour:
10459 *  if a font with a nontransparent color is set, it overwrites the current
10460 *  text color. OTOH setting the text color will overwrite the color of the font.
10461 */
setFont(const vcl::Font & rFont)10462 void PDFWriterImpl::setFont( const vcl::Font& rFont )
10463 {
10464     Color aColor = rFont.GetColor();
10465     if( aColor == COL_TRANSPARENT )
10466         aColor = m_aGraphicsStack.front().m_aFont.GetColor();
10467     m_aGraphicsStack.front().m_aFont = rFont;
10468     m_aGraphicsStack.front().m_aFont.SetColor( aColor );
10469     m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font;
10470 }
10471 
push(PushFlags nFlags)10472 void PDFWriterImpl::push( PushFlags nFlags )
10473 {
10474     OSL_ENSURE( !m_aGraphicsStack.empty(), "invalid graphics stack" );
10475     m_aGraphicsStack.push_front( m_aGraphicsStack.front() );
10476     m_aGraphicsStack.front().m_nFlags = nFlags;
10477 }
10478 
pop()10479 void PDFWriterImpl::pop()
10480 {
10481     OSL_ENSURE( m_aGraphicsStack.size() > 1, "pop without push" );
10482     if( m_aGraphicsStack.size() < 2 )
10483         return;
10484 
10485     GraphicsState aState = m_aGraphicsStack.front();
10486     m_aGraphicsStack.pop_front();
10487     GraphicsState& rOld = m_aGraphicsStack.front();
10488 
10489     // move those parameters back that were not pushed
10490     // in the first place
10491     if( ! (aState.m_nFlags & PushFlags::LINECOLOR) )
10492         setLineColor( aState.m_aLineColor );
10493     if( ! (aState.m_nFlags & PushFlags::FILLCOLOR) )
10494         setFillColor( aState.m_aFillColor );
10495     if( ! (aState.m_nFlags & PushFlags::FONT) )
10496         setFont( aState.m_aFont );
10497     if( ! (aState.m_nFlags & PushFlags::TEXTCOLOR) )
10498         setTextColor( aState.m_aFont.GetColor() );
10499     if( ! (aState.m_nFlags & PushFlags::MAPMODE) )
10500         setMapMode( aState.m_aMapMode );
10501     if( ! (aState.m_nFlags & PushFlags::CLIPREGION) )
10502     {
10503         // do not use setClipRegion here
10504         // it would convert again assuming the current mapmode
10505         rOld.m_aClipRegion = aState.m_aClipRegion;
10506         rOld.m_bClipRegion = aState.m_bClipRegion;
10507     }
10508     if( ! (aState.m_nFlags & PushFlags::TEXTLINECOLOR ) )
10509         setTextLineColor( aState.m_aTextLineColor );
10510     if( ! (aState.m_nFlags & PushFlags::OVERLINECOLOR ) )
10511         setOverlineColor( aState.m_aOverlineColor );
10512     if( ! (aState.m_nFlags & PushFlags::TEXTALIGN ) )
10513         setTextAlign( aState.m_aFont.GetAlignment() );
10514     if( ! (aState.m_nFlags & PushFlags::TEXTFILLCOLOR) )
10515         setTextFillColor( aState.m_aFont.GetFillColor() );
10516     if( ! (aState.m_nFlags & PushFlags::REFPOINT) )
10517     {
10518         // what ?
10519     }
10520     // invalidate graphics state
10521     m_aGraphicsStack.front().m_nUpdateFlags = GraphicsStateUpdateFlags::All;
10522 }
10523 
setMapMode(const MapMode & rMapMode)10524 void PDFWriterImpl::setMapMode( const MapMode& rMapMode )
10525 {
10526     m_aGraphicsStack.front().m_aMapMode = rMapMode;
10527     SetMapMode( rMapMode );
10528     m_aCurrentPDFState.m_aMapMode = rMapMode;
10529 }
10530 
setClipRegion(const basegfx::B2DPolyPolygon & rRegion)10531 void PDFWriterImpl::setClipRegion( const basegfx::B2DPolyPolygon& rRegion )
10532 {
10533     // tdf#130150 improve coordinate manipulations to double precision transformations
10534     const basegfx::B2DHomMatrix aCurrentTransform(
10535         GetInverseViewTransformation(m_aMapMode) * GetViewTransformation(m_aGraphicsStack.front().m_aMapMode));
10536     basegfx::B2DPolyPolygon aRegion(rRegion);
10537 
10538     aRegion.transform(aCurrentTransform);
10539     m_aGraphicsStack.front().m_aClipRegion = std::move(aRegion);
10540     m_aGraphicsStack.front().m_bClipRegion = true;
10541     m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10542 }
10543 
moveClipRegion(sal_Int32 nX,sal_Int32 nY)10544 void PDFWriterImpl::moveClipRegion( sal_Int32 nX, sal_Int32 nY )
10545 {
10546     if( !(m_aGraphicsStack.front().m_bClipRegion && m_aGraphicsStack.front().m_aClipRegion.count()) )
10547         return;
10548 
10549     // tdf#130150 improve coordinate manipulations to double precision transformations
10550     basegfx::B2DHomMatrix aConvertA;
10551 
10552     if(MapUnit::MapPixel == m_aGraphicsStack.front().m_aMapMode.GetMapUnit())
10553     {
10554         aConvertA = GetInverseViewTransformation(m_aMapMode);
10555     }
10556     else
10557     {
10558         aConvertA = LogicToLogic(m_aGraphicsStack.front().m_aMapMode, m_aMapMode);
10559     }
10560 
10561     basegfx::B2DPoint aB2DPointA(nX, nY);
10562     basegfx::B2DPoint aB2DPointB(0.0, 0.0);
10563     aB2DPointA *= aConvertA;
10564     aB2DPointB *= aConvertA;
10565     aB2DPointA -= aB2DPointB;
10566     basegfx::B2DHomMatrix aMat;
10567 
10568     aMat.translate(aB2DPointA.getX(), aB2DPointA.getY());
10569     m_aGraphicsStack.front().m_aClipRegion.transform( aMat );
10570     m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10571 }
10572 
intersectClipRegion(const tools::Rectangle & rRect)10573 void PDFWriterImpl::intersectClipRegion( const tools::Rectangle& rRect )
10574 {
10575     basegfx::B2DPolyPolygon aRect( basegfx::utils::createPolygonFromRect(
10576                                     vcl::unotools::b2DRectangleFromRectangle(rRect) ) );
10577     intersectClipRegion( aRect );
10578 }
10579 
intersectClipRegion(const basegfx::B2DPolyPolygon & rRegion)10580 void PDFWriterImpl::intersectClipRegion( const basegfx::B2DPolyPolygon& rRegion )
10581 {
10582     // tdf#130150 improve coordinate manipulations to double precision transformations
10583     const basegfx::B2DHomMatrix aCurrentTransform(
10584         GetInverseViewTransformation(m_aMapMode) * GetViewTransformation(m_aGraphicsStack.front().m_aMapMode));
10585     basegfx::B2DPolyPolygon aRegion(rRegion);
10586 
10587     aRegion.transform(aCurrentTransform);
10588     m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10589 
10590     if( m_aGraphicsStack.front().m_bClipRegion )
10591     {
10592         basegfx::B2DPolyPolygon aOld( basegfx::utils::prepareForPolygonOperation( m_aGraphicsStack.front().m_aClipRegion ) );
10593         aRegion = basegfx::utils::prepareForPolygonOperation( aRegion );
10594         m_aGraphicsStack.front().m_aClipRegion = basegfx::utils::solvePolygonOperationAnd( aOld, aRegion );
10595     }
10596     else
10597     {
10598         m_aGraphicsStack.front().m_aClipRegion = std::move(aRegion);
10599         m_aGraphicsStack.front().m_bClipRegion = true;
10600     }
10601 }
10602 
createNote(const tools::Rectangle & rRect,const tools::Rectangle & rPopupRect,const pdf::PDFNote & rNote,sal_Int32 nPageNr)10603 sal_Int32 PDFWriterImpl::createNote(const tools::Rectangle& rRect,
10604                                     const tools::Rectangle& rPopupRect, const pdf::PDFNote& rNote,
10605                                     sal_Int32 nPageNr)
10606 {
10607     if (nPageNr < 0)
10608         nPageNr = m_nCurrentPage;
10609 
10610     if (nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size())
10611         return -1;
10612 
10613     sal_Int32 nRet = m_aNotes.size();
10614 
10615     m_aNotes.emplace_back();
10616     auto & rNoteEntry = m_aNotes.back();
10617     rNoteEntry.m_nObject = createObject();
10618     rNoteEntry.m_aPopUpAnnotation.m_nObject = createObject();
10619     rNoteEntry.m_aPopUpAnnotation.m_nParentObject = rNoteEntry.m_nObject;
10620     rNoteEntry.m_aPopUpAnnotation.m_aRect = rPopupRect;
10621     rNoteEntry.m_aContents = rNote;
10622     rNoteEntry.m_aRect = rRect;
10623     rNoteEntry.m_nPage = nPageNr;
10624     // convert to default user space now, since the mapmode may change
10625     m_aPages[nPageNr].convertRect(rNoteEntry.m_aRect);
10626     m_aPages[nPageNr].convertRect(rNoteEntry.m_aPopUpAnnotation.m_aRect);
10627 
10628     // insert note to page's annotation list
10629     m_aPages[nPageNr].m_aAnnotations.push_back(rNoteEntry.m_nObject);
10630     m_aPages[nPageNr].m_aAnnotations.push_back(rNoteEntry.m_aPopUpAnnotation.m_nObject);
10631 
10632     return nRet;
10633 }
10634 
createLink(const tools::Rectangle & rRect,sal_Int32 nPageNr,OUString const & rAltText)10635 sal_Int32 PDFWriterImpl::createLink(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText)
10636 {
10637     if( nPageNr < 0 )
10638         nPageNr = m_nCurrentPage;
10639 
10640     if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
10641         return -1;
10642 
10643     sal_Int32 nRet = m_aLinks.size();
10644 
10645     m_aLinks.emplace_back(rAltText);
10646     m_aLinks.back().m_nObject   = createObject();
10647     m_aLinks.back().m_nPage     = nPageNr;
10648     m_aLinks.back().m_aRect     = rRect;
10649     // convert to default user space now, since the mapmode may change
10650     m_aPages[nPageNr].convertRect( m_aLinks.back().m_aRect );
10651 
10652     // insert link to page's annotation list
10653     m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aLinks.back().m_nObject );
10654 
10655     return nRet;
10656 }
10657 
createScreen(const tools::Rectangle & rRect,sal_Int32 nPageNr,OUString const & rAltText,OUString const & rMimeType)10658 sal_Int32 PDFWriterImpl::createScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr, OUString const& rAltText, OUString const& rMimeType)
10659 {
10660     if (nPageNr < 0)
10661         nPageNr = m_nCurrentPage;
10662 
10663     if (nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size())
10664         return -1;
10665 
10666     sal_Int32 nRet = m_aScreens.size();
10667 
10668     m_aScreens.emplace_back(rAltText, rMimeType);
10669     m_aScreens.back().m_nObject = createObject();
10670     m_aScreens.back().m_nPage = nPageNr;
10671     m_aScreens.back().m_aRect = rRect;
10672     // Convert to default user space now, since the mapmode may change.
10673     m_aPages[nPageNr].convertRect(m_aScreens.back().m_aRect);
10674 
10675     // Insert link to page's annotation list.
10676     m_aPages[nPageNr].m_aAnnotations.push_back(m_aScreens.back().m_nObject);
10677 
10678     return nRet;
10679 }
10680 
createNamedDest(const OUString & sDestName,const tools::Rectangle & rRect,sal_Int32 nPageNr,PDFWriter::DestAreaType eType)10681 sal_Int32 PDFWriterImpl::createNamedDest( const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10682 {
10683     if( nPageNr < 0 )
10684         nPageNr = m_nCurrentPage;
10685 
10686     if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
10687         return -1;
10688 
10689     sal_Int32 nRet = m_aNamedDests.size();
10690 
10691     m_aNamedDests.emplace_back( );
10692     m_aNamedDests.back().m_aDestName = sDestName;
10693     m_aNamedDests.back().m_nPage = nPageNr;
10694     m_aNamedDests.back().m_eType = eType;
10695     m_aNamedDests.back().m_aRect = rRect;
10696     // convert to default user space now, since the mapmode may change
10697     m_aPages[nPageNr].convertRect( m_aNamedDests.back().m_aRect );
10698 
10699     return nRet;
10700 }
10701 
createDest(const tools::Rectangle & rRect,sal_Int32 nPageNr,PDFWriter::DestAreaType eType)10702 sal_Int32 PDFWriterImpl::createDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10703 {
10704     if( nPageNr < 0 )
10705         nPageNr = m_nCurrentPage;
10706 
10707     if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
10708         return -1;
10709 
10710     sal_Int32 nRet = m_aDests.size();
10711 
10712     m_aDests.emplace_back( );
10713     m_aDests.back().m_nPage = nPageNr;
10714     m_aDests.back().m_eType = eType;
10715     m_aDests.back().m_aRect = rRect;
10716     // convert to default user space now, since the mapmode may change
10717     m_aPages[nPageNr].convertRect( m_aDests.back().m_aRect );
10718 
10719     return nRet;
10720 }
10721 
registerDestReference(sal_Int32 nDestId,const tools::Rectangle & rRect,sal_Int32 nPageNr,PDFWriter::DestAreaType eType)10722 sal_Int32 PDFWriterImpl::registerDestReference( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10723 {
10724     m_aDestinationIdTranslation[ nDestId ] = createDest( rRect, nPageNr, eType );
10725     return m_aDestinationIdTranslation[ nDestId ];
10726 }
10727 
setLinkDest(sal_Int32 nLinkId,sal_Int32 nDestId)10728 void PDFWriterImpl::setLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
10729 {
10730     if( nLinkId < 0 || o3tl::make_unsigned(nLinkId) >= m_aLinks.size() )
10731         return;
10732     if( nDestId < 0 || o3tl::make_unsigned(nDestId) >= m_aDests.size() )
10733         return;
10734 
10735     m_aLinks[ nLinkId ].m_nDest = nDestId;
10736 }
10737 
setLinkURL(sal_Int32 nLinkId,const OUString & rURL)10738 void PDFWriterImpl::setLinkURL( sal_Int32 nLinkId, const OUString& rURL )
10739 {
10740     if( nLinkId < 0 || o3tl::make_unsigned(nLinkId) >= m_aLinks.size() )
10741         return;
10742 
10743     m_aLinks[ nLinkId ].m_nDest = -1;
10744 
10745     using namespace ::com::sun::star;
10746 
10747     if (!m_xTrans.is())
10748     {
10749         const uno::Reference< uno::XComponentContext >& xContext( comphelper::getProcessComponentContext() );
10750         m_xTrans = util::URLTransformer::create(xContext);
10751     }
10752 
10753     util::URL aURL;
10754     aURL.Complete = rURL;
10755 
10756     m_xTrans->parseStrict( aURL );
10757 
10758     m_aLinks[ nLinkId ].m_aURL  = aURL.Complete;
10759 }
10760 
setScreenURL(sal_Int32 nScreenId,const OUString & rURL)10761 void PDFWriterImpl::setScreenURL(sal_Int32 nScreenId, const OUString& rURL)
10762 {
10763     if (nScreenId < 0 || o3tl::make_unsigned(nScreenId) >= m_aScreens.size())
10764         return;
10765 
10766     m_aScreens[nScreenId].m_aURL = rURL;
10767 }
10768 
setScreenStream(sal_Int32 nScreenId,const OUString & rURL)10769 void PDFWriterImpl::setScreenStream(sal_Int32 nScreenId, const OUString& rURL)
10770 {
10771     if (nScreenId < 0 || o3tl::make_unsigned(nScreenId) >= m_aScreens.size())
10772         return;
10773 
10774     m_aScreens[nScreenId].m_aTempFileURL = rURL;
10775     m_aScreens[nScreenId].m_nTempFileObject = createObject();
10776 }
10777 
setLinkPropertyId(sal_Int32 nLinkId,sal_Int32 nPropertyId)10778 void PDFWriterImpl::setLinkPropertyId( sal_Int32 nLinkId, sal_Int32 nPropertyId )
10779 {
10780     m_aLinkPropertyMap[ nPropertyId ] = nLinkId;
10781 }
10782 
createOutlineItem(sal_Int32 nParent,std::u16string_view rText,sal_Int32 nDestID)10783 sal_Int32 PDFWriterImpl::createOutlineItem( sal_Int32 nParent, std::u16string_view rText, sal_Int32 nDestID )
10784 {
10785     // create new item
10786     sal_Int32 nNewItem = m_aOutline.size();
10787     m_aOutline.emplace_back( );
10788 
10789     // set item attributes
10790     setOutlineItemParent( nNewItem, nParent );
10791     setOutlineItemText( nNewItem, rText );
10792     setOutlineItemDest( nNewItem, nDestID );
10793 
10794     return nNewItem;
10795 }
10796 
setOutlineItemParent(sal_Int32 nItem,sal_Int32 nNewParent)10797 void PDFWriterImpl::setOutlineItemParent( sal_Int32 nItem, sal_Int32 nNewParent )
10798 {
10799     if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() )
10800         return;
10801 
10802     if( nNewParent < 0 || o3tl::make_unsigned(nNewParent) >= m_aOutline.size() || nNewParent == nItem )
10803     {
10804         nNewParent = 0;
10805     }
10806     // insert item to new parent's list of children
10807     m_aOutline[ nNewParent ].m_aChildren.push_back( nItem );
10808 }
10809 
setOutlineItemText(sal_Int32 nItem,std::u16string_view rText)10810 void PDFWriterImpl::setOutlineItemText( sal_Int32 nItem, std::u16string_view rText )
10811 {
10812     if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() )
10813         return;
10814 
10815     m_aOutline[ nItem ].m_aTitle = psp::WhitespaceToSpace( rText );
10816 }
10817 
setOutlineItemDest(sal_Int32 nItem,sal_Int32 nDestID)10818 void PDFWriterImpl::setOutlineItemDest( sal_Int32 nItem, sal_Int32 nDestID )
10819 {
10820     if( nItem < 1 || o3tl::make_unsigned(nItem) >= m_aOutline.size() ) // item does not exist
10821         return;
10822     if( nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size() ) // dest does not exist
10823         return;
10824     m_aOutline[nItem].m_nDestID = nDestID;
10825 }
10826 
getStructureTag(vcl::pdf::StructElement eType)10827 const char* PDFWriterImpl::getStructureTag(vcl::pdf::StructElement eType)
10828 {
10829     using namespace vcl::pdf;
10830 
10831     static constexpr auto constTagStrings = frozen::make_unordered_map<StructElement, const char*>({
10832         { StructElement::NonStructElement, "NonStruct" },
10833         { StructElement::Document,    "Document" },
10834         { StructElement::Part,        "Part" },
10835         { StructElement::Article,     "Art" },
10836         { StructElement::Section,     "Sect" },
10837         { StructElement::Division,    "Div" },
10838         { StructElement::BlockQuote,  "BlockQuote" },
10839         { StructElement::Caption,     "Caption" },
10840         { StructElement::TOC,         "TOC" },
10841         { StructElement::TOCI,        "TOCI" },
10842         { StructElement::Index,       "Index" },
10843         { StructElement::Paragraph,   "P" },
10844         { StructElement::Heading,     "H" },
10845         { StructElement::H1,          "H1" },
10846         { StructElement::H2,          "H2" },
10847         { StructElement::H3,          "H3" },
10848         { StructElement::H4,          "H4" },
10849         { StructElement::H5,          "H5" },
10850         { StructElement::H6,          "H6" },
10851         { StructElement::List,        "L" },
10852         { StructElement::ListItem,    "LI" },
10853         { StructElement::LILabel,     "Lbl" },
10854         { StructElement::LIBody,      "LBody" },
10855         { StructElement::Table,       "Table" },
10856         { StructElement::TableRow,    "TR" },
10857         { StructElement::TableHeader, "TH" },
10858         { StructElement::TableData,   "TD" },
10859         { StructElement::Span,        "Span" },
10860         { StructElement::Quote,       "Quote" },
10861         { StructElement::Note,        "Note" },
10862         { StructElement::Reference,   "Reference" },
10863         { StructElement::BibEntry,    "BibEntry" },
10864         { StructElement::Code,        "Code" },
10865         { StructElement::Link,        "Link" },
10866         { StructElement::Annot,       "Annot" },
10867         { StructElement::Ruby,        "Ruby" },
10868         { StructElement::RB,          "RB" },
10869         { StructElement::RT,          "RT" },
10870         { StructElement::RP,          "RP" },
10871         { StructElement::Warichu,     "Warichu" },
10872         { StructElement::WT,          "WT" },
10873         { StructElement::WP,          "WP" },
10874         { StructElement::Figure,      "Figure" },
10875         { StructElement::Formula,     "Formula"},
10876         { StructElement::Form,        "Form" },
10877         { StructElement::Title, "Title" },
10878         { StructElement::Emphasis, "Em" },
10879         { StructElement::Strong, "Strong" },
10880     });
10881 
10882     // First handle fallbacks for elements that were added in a certain PDF version
10883 
10884     // PDF 1.5 fallbacks
10885     if (m_aContext.Version < PDFWriter::PDFVersion::PDF_1_5 && eType == StructElement::Annot)
10886         eType = StructElement::Figure;
10887 
10888     // PDF 2.0 fallbacks
10889     if (m_aContext.Version < PDFWriter::PDFVersion::PDF_2_0)
10890     {
10891         switch (eType)
10892         {
10893             case StructElement::Title:
10894                 eType = StructElement::Paragraph; break;
10895             case StructElement::Emphasis:
10896                 eType = StructElement::Span; break;
10897             case StructElement::Strong:
10898                 eType = StructElement::Span; break;
10899             default:
10900                 break;
10901         }
10902     }
10903 
10904     auto iterator = constTagStrings.find(eType);
10905 
10906     if (iterator == constTagStrings.end())
10907         return "Div";
10908 
10909     return iterator->second;
10910 }
10911 
addRoleMap(const OString & aAlias,vcl::pdf::StructElement eType)10912 void PDFWriterImpl::addRoleMap(const OString& aAlias, vcl::pdf::StructElement eType)
10913 {
10914     OString aTag = getStructureTag(eType);
10915     // For PDF/UA it's not allowed to map an alias with the same name.
10916     // Not aware of a reason for doing it in any case, so just don't do it.
10917     if (aAlias != aTag)
10918         m_aRoleMap[aAlias] = aTag;
10919 }
10920 
beginStructureElementMCSeq()10921 void PDFWriterImpl::beginStructureElementMCSeq()
10922 {
10923     assert(m_nCurrentStructElement == 0 || m_aStructure[m_nCurrentStructElement].m_oType);
10924     if( m_bEmitStructure &&
10925         m_nCurrentStructElement > 0 && // StructTreeRoot
10926         // Document = SwPageFrame => this is not *inside* the page content
10927         // stream so do not emit MCID!
10928         m_aStructure[m_nCurrentStructElement].m_oType &&
10929         *m_aStructure[m_nCurrentStructElement].m_oType != vcl::pdf::StructElement::Document &&
10930         ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
10931         )
10932     {
10933         PDFStructureElement& rEle = m_aStructure[ m_nCurrentStructElement ];
10934         OStringBuffer aLine( 128 );
10935         sal_Int32 nMCID = m_aPages[ m_nCurrentPage ].m_aMCIDParents.size();
10936         aLine.append( "/" );
10937         if( !rEle.m_aAlias.isEmpty() )
10938             aLine.append( rEle.m_aAlias );
10939         else
10940             aLine.append( getStructureTag(*rEle.m_oType) );
10941         aLine.append( "<</MCID " );
10942         aLine.append( nMCID );
10943         aLine.append( ">>BDC\n" );
10944         writeBuffer( aLine );
10945 
10946         // update the element's content list
10947         SAL_INFO("vcl.pdfwriter", "beginning marked content id " << nMCID << " on page object "
10948                  << m_aPages[ m_nCurrentPage ].m_nPageObject << ", structure first page = "
10949                  << rEle.m_nFirstPageObject);
10950         rEle.m_aKids.emplace_back(MCIDReference{m_aPages[m_nCurrentPage].m_nPageObject, nMCID});
10951         // update the page's mcid parent list
10952         m_aPages[ m_nCurrentPage ].m_aMCIDParents.push_back( rEle.m_nObject );
10953         // mark element MC sequence as open
10954         rEle.m_bOpenMCSeq = true;
10955     }
10956     // handle artifacts
10957     else if( ! m_bEmitStructure && m_aContext.Tagged &&
10958                m_nCurrentStructElement > 0 &&
10959                m_aStructure[m_nCurrentStructElement].m_oType &&
10960                *m_aStructure[m_nCurrentStructElement].m_oType == vcl::pdf::StructElement::NonStructElement &&
10961              ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
10962              )
10963     {
10964         OString aLine = "/Artifact "_ostr;
10965         writeBuffer( aLine );
10966         // emit property list if requested
10967         OStringBuffer buf;
10968         for (auto const& rAttr : m_aStructure[m_nCurrentStructElement].m_aAttributes)
10969         {
10970             appendStructureAttributeLine(rAttr.first, rAttr.second, buf, false);
10971         }
10972         if (buf.isEmpty())
10973         {
10974             writeBuffer("BMC\n");
10975         }
10976         else
10977         {
10978             writeBuffer("<<");
10979             writeBuffer(buf);
10980             writeBuffer(">> BDC\n");
10981         }
10982         // mark element MC sequence as open
10983         m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = true;
10984     }
10985 }
10986 
endStructureElementMCSeq(EndMode const endMode)10987 void PDFWriterImpl::endStructureElementMCSeq(EndMode const endMode)
10988 {
10989     if (m_nCurrentStructElement > 0 // not StructTreeRoot
10990         && m_aStructure[m_nCurrentStructElement].m_oType
10991         && (m_bEmitStructure
10992             || (endMode != EndMode::OnlyStruct
10993                 && m_aStructure[m_nCurrentStructElement].m_oType == vcl::pdf::StructElement::NonStructElement))
10994         && m_aStructure[m_nCurrentStructElement].m_bOpenMCSeq)
10995     {
10996         writeBuffer( "EMC\n" );
10997         m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = false;
10998     }
10999 }
11000 
checkEmitStructure()11001 bool PDFWriterImpl::checkEmitStructure()
11002 {
11003     bool bEmit = false;
11004     if( m_aContext.Tagged )
11005     {
11006         bEmit = true;
11007         sal_Int32 nEle = m_nCurrentStructElement;
11008         while( nEle > 0 && o3tl::make_unsigned(nEle) < m_aStructure.size() )
11009         {
11010             if (m_aStructure[nEle].m_oType
11011                 && *m_aStructure[nEle].m_oType == vcl::pdf::StructElement::NonStructElement)
11012             {
11013                 bEmit = false;
11014                 break;
11015             }
11016             nEle = m_aStructure[ nEle ].m_nParentElement;
11017         }
11018     }
11019     return bEmit;
11020 }
11021 
ensureStructureElement()11022 sal_Int32 PDFWriterImpl::ensureStructureElement()
11023 {
11024     if( ! m_aContext.Tagged )
11025         return -1;
11026 
11027     sal_Int32 nNewId = sal_Int32(m_aStructure.size());
11028 
11029     // use m_nCurrentStructElement as temporary parent
11030     m_aStructure.emplace_back(nNewId, m_nCurrentStructElement, m_aPages[m_nCurrentPage].m_nPageObject);
11031 
11032     m_aStructure[ m_nCurrentStructElement ].m_aChildren.push_back( nNewId );
11033     return nNewId;
11034 }
11035 
initStructureElement(sal_Int32 const id,vcl::pdf::StructElement const eType,std::u16string_view const rAlias)11036 void PDFWriterImpl::initStructureElement(sal_Int32 const id,
11037         vcl::pdf::StructElement const eType, std::u16string_view const rAlias)
11038 {
11039     if( ! m_aContext.Tagged )
11040         return;
11041 
11042     if( m_nCurrentStructElement == 0 &&
11043         eType != vcl::pdf::StructElement::Document && eType != vcl::pdf::StructElement::NonStructElement )
11044     {
11045         // struct tree root hit, but not beginning document
11046         // this might happen with setCurrentStructureElement
11047         // silently insert structure into document again if one properly exists
11048         if( ! m_aStructure[ 0 ].m_aChildren.empty() )
11049         {
11050             const std::vector< sal_Int32 >& rRootChildren = m_aStructure[0].m_aChildren;
11051             auto it = std::find_if(rRootChildren.begin(), rRootChildren.end(),
11052                 [&](sal_Int32 nElement) {
11053                     return m_aStructure[nElement].m_oType
11054                         && *m_aStructure[nElement].m_oType == vcl::pdf::StructElement::Document; });
11055             if( it != rRootChildren.end() )
11056             {
11057                 m_nCurrentStructElement = *it;
11058                 SAL_WARN( "vcl.pdfwriter", "Structure element inserted to StructTreeRoot that is not a document" );
11059             }
11060             else {
11061                 OSL_FAIL( "document structure in disorder !" );
11062             }
11063         }
11064         else {
11065             OSL_FAIL( "PDF document structure MUST be contained in a Document element" );
11066         }
11067     }
11068 
11069     PDFStructureElement& rEle = m_aStructure[id];
11070     assert(!rEle.m_oType);
11071     rEle.m_oType.emplace(eType);
11072     // remove it from its possibly placeholder parent; append to real parent
11073     auto const it(std::find(m_aStructure[rEle.m_nParentElement].m_aChildren.begin(),
11074         m_aStructure[rEle.m_nParentElement].m_aChildren.end(), id));
11075     assert(it != m_aStructure[rEle.m_nParentElement].m_aChildren.end());
11076     m_aStructure[rEle.m_nParentElement].m_aChildren.erase(it);
11077     rEle.m_nParentElement   = m_nCurrentStructElement;
11078     rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject;
11079     m_aStructure[m_nCurrentStructElement].m_aChildren.push_back(id);
11080 
11081     // handle alias names
11082     if( !rAlias.empty() && eType != vcl::pdf::StructElement::NonStructElement )
11083     {
11084         OStringBuffer aNameBuf( rAlias.size() );
11085         COSWriter::appendName( rAlias, aNameBuf );
11086         OString aAliasName( aNameBuf.makeStringAndClear() );
11087         rEle.m_aAlias = aAliasName;
11088         addRoleMap(aAliasName, eType);
11089     }
11090 
11091     if (m_bEmitStructure && eType != vcl::pdf::StructElement::NonStructElement) // don't create nonexistent objects
11092     {
11093         rEle.m_nObject      = createObject();
11094         // update parent's kids list
11095         m_aStructure[ rEle.m_nParentElement ].m_aKids.emplace_back(ObjReference{rEle.m_nObject});
11096         // ISO 14289-1:2014, Clause: 7.9
11097         if (*rEle.m_oType == vcl::pdf::StructElement::Note)
11098         {
11099             m_StructElemObjsWithID.insert(rEle.m_nObject);
11100         }
11101     }
11102 }
11103 
beginStructureElement(sal_Int32 const id)11104 void PDFWriterImpl::beginStructureElement(sal_Int32 const id)
11105 {
11106     if( m_nCurrentPage < 0 )
11107         return;
11108 
11109     if( ! m_aContext.Tagged )
11110         return;
11111 
11112     assert(id != -1 && "cid#1538888 doesn't consider above m_aContext.Tagged");
11113 
11114     // close eventual current MC sequence
11115     endStructureElementMCSeq(EndMode::OnlyStruct);
11116 
11117     PDFStructureElement& rEle = m_aStructure[id];
11118     m_StructElementStack.push(m_nCurrentStructElement);
11119     m_nCurrentStructElement = id;
11120 
11121     if (g_bDebugDisableCompression)
11122     {
11123         OStringBuffer aLine( "beginStructureElement " );
11124         aLine.append( m_nCurrentStructElement );
11125         aLine.append( ": " );
11126         aLine.append( rEle.m_oType
11127             ? getStructureTag(*rEle.m_oType)
11128             : "<placeholder>" );
11129         if( !rEle.m_aAlias.isEmpty() )
11130         {
11131             aLine.append( " aliased as \"" );
11132             aLine.append( rEle.m_aAlias );
11133             aLine.append( '\"' );
11134         }
11135         emitComment( aLine.getStr() );
11136     }
11137 
11138     // check whether to emit structure henceforth
11139     m_bEmitStructure = checkEmitStructure();
11140 }
11141 
endStructureElement()11142 void PDFWriterImpl::endStructureElement()
11143 {
11144     if( m_nCurrentPage < 0 )
11145         return;
11146 
11147     if( ! m_aContext.Tagged )
11148         return;
11149 
11150     if( m_nCurrentStructElement == 0 )
11151     {
11152         // hit the struct tree root, that means there is an endStructureElement
11153         // without corresponding beginStructureElement
11154         return;
11155     }
11156 
11157     // end the marked content sequence
11158     endStructureElementMCSeq();
11159 
11160     OStringBuffer aLine;
11161     if (g_bDebugDisableCompression)
11162     {
11163         aLine.append( "endStructureElement " );
11164         aLine.append( m_nCurrentStructElement );
11165         aLine.append( ": " );
11166         aLine.append( m_aStructure[m_nCurrentStructElement].m_oType
11167             ? getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
11168             : "<placeholder>" );
11169         if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
11170         {
11171             aLine.append( " aliased as \"" );
11172             aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
11173             aLine.append( '\"' );
11174         }
11175     }
11176 
11177     // "end" the structure element, the parent becomes current element
11178     m_nCurrentStructElement = m_StructElementStack.top();
11179     m_StructElementStack.pop();
11180 
11181     // check whether to emit structure henceforth
11182     m_bEmitStructure = checkEmitStructure();
11183 
11184     if (g_bDebugDisableCompression && m_bEmitStructure)
11185     {
11186         emitComment( aLine.getStr() );
11187     }
11188 }
11189 
11190 namespace {
11191 
removePlaceholderSEImpl(std::vector<PDFStructureElement> & rStructure,std::vector<sal_Int32>::iterator & rParentIt)11192 void removePlaceholderSEImpl(std::vector<PDFStructureElement> & rStructure,
11193         std::vector<sal_Int32>::iterator & rParentIt)
11194 {
11195     PDFStructureElement& rEle(rStructure[*rParentIt]);
11196     removePlaceholderSE(rStructure, rEle);
11197 
11198     if (!rEle.m_oType)
11199     {
11200         // Placeholder was not initialised - should not happen when printing
11201         // a full page, but might if a selection is printed, which can be only
11202         // a shape without its anchor.
11203         // Handle this by moving the children to the parent SE.
11204         PDFStructureElement & rParent(rStructure[rEle.m_nParentElement]);
11205         rParentIt = rParent.m_aChildren.erase(rParentIt);
11206         std::vector<sal_Int32> children;
11207         for (auto const child : rEle.m_aChildren)
11208         {
11209             PDFStructureElement& rChild = rStructure[child];
11210             rChild.m_nParentElement = rEle.m_nParentElement;
11211             children.push_back(rChild.m_nOwnElement);
11212         }
11213         rParentIt = rParent.m_aChildren.insert(rParentIt, children.begin(), children.end())
11214             + children.size();
11215     }
11216     else
11217     {
11218         ++rParentIt;
11219     }
11220 
11221 }
11222 
removePlaceholderSE(std::vector<PDFStructureElement> & rStructure,PDFStructureElement & rEle)11223 void removePlaceholderSE(std::vector<PDFStructureElement> & rStructure, PDFStructureElement& rEle)
11224 {
11225     for (auto it = rEle.m_aChildren.begin(); it != rEle.m_aChildren.end(); )
11226     {
11227         removePlaceholderSEImpl(rStructure, it);
11228     }
11229 }
11230 
11231 } // end anonymous namespace
11232 
11233 /*
11234  * This function adds an internal structure list container to overcome the 8191 elements array limitation
11235  * in kids element emission.
11236  * Recursive function
11237  *
11238  */
addInternalStructureContainer(PDFStructureElement & rEle)11239 void PDFWriterImpl::addInternalStructureContainer( PDFStructureElement& rEle )
11240 {
11241     if (rEle.m_nOwnElement != rEle.m_nParentElement
11242         && *rEle.m_oType == vcl::pdf::StructElement::NonStructElement)
11243     {
11244         return;
11245     }
11246 
11247     for (auto const& child : rEle.m_aChildren)
11248     {
11249         assert(child > 0 && o3tl::make_unsigned(child) < m_aStructure.size());
11250         if( child > 0 && o3tl::make_unsigned(child) < m_aStructure.size() )
11251         {
11252             PDFStructureElement& rChild = m_aStructure[ child ];
11253             if (*rChild.m_oType != vcl::pdf::StructElement::NonStructElement)
11254             {
11255                 //triggered when a child of the rEle element is found
11256                 assert(rChild.m_nParentElement == rEle.m_nOwnElement);
11257                 if( rChild.m_nParentElement == rEle.m_nOwnElement )
11258                     addInternalStructureContainer( rChild );//examine the child
11259                 else
11260                 {
11261                     OSL_FAIL( "PDFWriterImpl::addInternalStructureContainer: invalid child structure element" );
11262                     SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure element with id " << child );
11263                 }
11264             }
11265         }
11266         else
11267         {
11268             OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
11269             SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure id " << child );
11270         }
11271     }
11272 
11273     if( rEle.m_nOwnElement == rEle.m_nParentElement )
11274         return;
11275 
11276     if( rEle.m_aKids.empty() )
11277         return;
11278 
11279     if( rEle.m_aKids.size() <= ncMaxPDFArraySize )        return;
11280 
11281     //then we need to add the containers for the kids elements
11282     // a list to be used for the new kid element
11283     std::list< PDFStructureElementKid > aNewKids;
11284     std::vector< sal_Int32 > aNewChildren;
11285 
11286     // add Div in RoleMap, in case no one else did (TODO: is it needed? Is it dangerous?)
11287     OString aAliasName("Div"_ostr);
11288     addRoleMap(aAliasName, vcl::pdf::StructElement::Division);
11289 
11290     while( rEle.m_aKids.size() > ncMaxPDFArraySize )
11291     {
11292         sal_Int32 nCurrentStructElement = rEle.m_nOwnElement;
11293         sal_Int32 nNewId = sal_Int32(m_aStructure.size());
11294         m_aStructure.emplace_back( );
11295         PDFStructureElement& rEleNew = m_aStructure.back();
11296         rEleNew.m_aAlias            = aAliasName;
11297         rEleNew.m_oType.emplace(vcl::pdf::StructElement::Division); // a new Div type container
11298         rEleNew.m_nOwnElement       = nNewId;
11299         rEleNew.m_nParentElement    = nCurrentStructElement;
11300         //inherit the same page as the first child to be reparented
11301         rEleNew.m_nFirstPageObject  = m_aStructure[ rEle.m_aChildren.front() ].m_nFirstPageObject;
11302         rEleNew.m_nObject           = createObject();//assign a PDF object number
11303         //add the object to the kid list of the parent
11304         aNewKids.emplace_back(ObjReference{rEleNew.m_nObject});
11305         aNewChildren.push_back( nNewId );
11306 
11307         std::vector< sal_Int32 >::iterator aChildEndIt( rEle.m_aChildren.begin() );
11308         std::list< PDFStructureElementKid >::iterator aKidEndIt( rEle.m_aKids.begin() );
11309         advance( aChildEndIt, ncMaxPDFArraySize );
11310         advance( aKidEndIt, ncMaxPDFArraySize );
11311 
11312         rEleNew.m_aKids.splice( rEleNew.m_aKids.begin(),
11313                                 rEle.m_aKids,
11314                                 rEle.m_aKids.begin(),
11315                                 aKidEndIt );
11316         rEleNew.m_aChildren.insert( rEleNew.m_aChildren.begin(),
11317                                     rEle.m_aChildren.begin(),
11318                                     aChildEndIt );
11319         rEle.m_aChildren.erase( rEle.m_aChildren.begin(), aChildEndIt );
11320 
11321         // set the kid's new parent
11322         for (auto const& child : rEleNew.m_aChildren)
11323         {
11324             m_aStructure[ child ].m_nParentElement = nNewId;
11325         }
11326     }
11327     //finally add the new kids resulting from the container added
11328     rEle.m_aKids.insert( rEle.m_aKids.begin(), aNewKids.begin(), aNewKids.end() );
11329     rEle.m_aChildren.insert( rEle.m_aChildren.begin(), aNewChildren.begin(), aNewChildren.end() );
11330 }
11331 
setCurrentStructureElement(sal_Int32 nEle)11332 bool PDFWriterImpl::setCurrentStructureElement( sal_Int32 nEle )
11333 {
11334     bool bSuccess = false;
11335 
11336     if( m_aContext.Tagged && nEle >= 0 && o3tl::make_unsigned(nEle) < m_aStructure.size() )
11337     {
11338         // end eventual previous marked content sequence
11339         endStructureElementMCSeq();
11340 
11341         m_nCurrentStructElement = nEle;
11342         m_bEmitStructure = checkEmitStructure();
11343         if (g_bDebugDisableCompression)
11344         {
11345             OStringBuffer aLine( "setCurrentStructureElement " );
11346             aLine.append( m_nCurrentStructElement );
11347             aLine.append( ": " );
11348             aLine.append( m_aStructure[m_nCurrentStructElement].m_oType
11349                 ? getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
11350                 : "<placeholder>" );
11351             if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
11352             {
11353                 aLine.append( " aliased as \"" );
11354                 aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
11355                 aLine.append( '\"' );
11356             }
11357             if( ! m_bEmitStructure )
11358                 aLine.append( " (inside NonStruct)" );
11359             emitComment( aLine.getStr() );
11360         }
11361         bSuccess = true;
11362     }
11363 
11364     return bSuccess;
11365 }
11366 
setStructureAttribute(enum PDFWriter::StructAttribute eAttr,enum PDFWriter::StructAttributeValue eVal)11367 bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr, enum PDFWriter::StructAttributeValue eVal )
11368 {
11369     if( !m_aContext.Tagged )
11370         return false;
11371 
11372     assert(m_aStructure[m_nCurrentStructElement].m_oType);
11373     bool bInsert = false;
11374     if (m_nCurrentStructElement > 0
11375         && (m_bEmitStructure
11376             // allow it for topmost non-structured element
11377             || (m_aContext.Tagged
11378                 && (0 == m_aStructure[m_nCurrentStructElement].m_nParentElement
11379                     || !m_aStructure[m_aStructure[m_nCurrentStructElement].m_nParentElement].m_oType
11380                     || *m_aStructure[m_aStructure[m_nCurrentStructElement].m_nParentElement].m_oType != vcl::pdf::StructElement::NonStructElement))))
11381     {
11382         vcl::pdf::StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType;
11383         switch( eAttr )
11384         {
11385             case PDFWriter::Placement:
11386                 if( eVal == PDFWriter::Block        ||
11387                     eVal == PDFWriter::Inline       ||
11388                     eVal == PDFWriter::Before       ||
11389                     eVal == PDFWriter::Start        ||
11390                     eVal == PDFWriter::End )
11391                     bInsert = true;
11392                 break;
11393             case PDFWriter::WritingMode:
11394                 if( eVal == PDFWriter::LrTb         ||
11395                     eVal == PDFWriter::RlTb         ||
11396                     eVal == PDFWriter::TbRl )
11397                 {
11398                     bInsert = true;
11399                 }
11400                 break;
11401             case PDFWriter::TextAlign:
11402                 if( eVal == PDFWriter::Start        ||
11403                     eVal == PDFWriter::Center       ||
11404                     eVal == PDFWriter::End          ||
11405                     eVal == PDFWriter::Justify )
11406                 {
11407                     if (eType == vcl::pdf::StructElement::Paragraph   ||
11408                         eType == vcl::pdf::StructElement::Title ||
11409                         eType == vcl::pdf::StructElement::Heading     ||
11410                         eType == vcl::pdf::StructElement::H1          ||
11411                         eType == vcl::pdf::StructElement::H2          ||
11412                         eType == vcl::pdf::StructElement::H3          ||
11413                         eType == vcl::pdf::StructElement::H4          ||
11414                         eType == vcl::pdf::StructElement::H5          ||
11415                         eType == vcl::pdf::StructElement::H6          ||
11416                         eType == vcl::pdf::StructElement::List        ||
11417                         eType == vcl::pdf::StructElement::ListItem    ||
11418                         eType == vcl::pdf::StructElement::LILabel     ||
11419                         eType == vcl::pdf::StructElement::LIBody      ||
11420                         eType == vcl::pdf::StructElement::Table       ||
11421                         eType == vcl::pdf::StructElement::TableRow    ||
11422                         eType == vcl::pdf::StructElement::TableHeader ||
11423                         eType == vcl::pdf::StructElement::TableData)
11424                     {
11425                         bInsert = true;
11426                     }
11427                 }
11428                 break;
11429             case PDFWriter::Width:
11430             case PDFWriter::Height:
11431                 if( eVal == PDFWriter::Auto )
11432                 {
11433                     if (eType == vcl::pdf::StructElement::Figure      ||
11434                         eType == vcl::pdf::StructElement::Formula     ||
11435                         eType == vcl::pdf::StructElement::Form        ||
11436                         eType == vcl::pdf::StructElement::Table       ||
11437                         eType == vcl::pdf::StructElement::TableHeader ||
11438                         eType == vcl::pdf::StructElement::TableData)
11439                     {
11440                         bInsert = true;
11441                     }
11442                 }
11443                 break;
11444             case PDFWriter::BlockAlign:
11445                 if( eVal == PDFWriter::Before       ||
11446                     eVal == PDFWriter::Middle       ||
11447                     eVal == PDFWriter::After        ||
11448                     eVal == PDFWriter::Justify )
11449                 {
11450                     if (eType == vcl::pdf::StructElement::TableHeader ||
11451                         eType == vcl::pdf::StructElement::TableData)
11452                     {
11453                         bInsert = true;
11454                     }
11455                 }
11456                 break;
11457             case PDFWriter::InlineAlign:
11458                 if( eVal == PDFWriter::Start        ||
11459                     eVal == PDFWriter::Center       ||
11460                     eVal == PDFWriter::End )
11461                 {
11462                     if (eType == vcl::pdf::StructElement::TableHeader ||
11463                         eType == vcl::pdf::StructElement::TableData)
11464                     {
11465                         bInsert = true;
11466                     }
11467                 }
11468                 break;
11469             case PDFWriter::LineHeight:
11470                 if( eVal == PDFWriter::Normal       ||
11471                     eVal == PDFWriter::Auto )
11472                 {
11473                     // only for ILSE and BLSE
11474                     if (eType == vcl::pdf::StructElement::Paragraph   ||
11475                         eType == vcl::pdf::StructElement::Title ||
11476                         eType == vcl::pdf::StructElement::Heading     ||
11477                         eType == vcl::pdf::StructElement::H1          ||
11478                         eType == vcl::pdf::StructElement::H2          ||
11479                         eType == vcl::pdf::StructElement::H3          ||
11480                         eType == vcl::pdf::StructElement::H4          ||
11481                         eType == vcl::pdf::StructElement::H5          ||
11482                         eType == vcl::pdf::StructElement::H6          ||
11483                         eType == vcl::pdf::StructElement::List        ||
11484                         eType == vcl::pdf::StructElement::ListItem    ||
11485                         eType == vcl::pdf::StructElement::LILabel     ||
11486                         eType == vcl::pdf::StructElement::LIBody      ||
11487                         eType == vcl::pdf::StructElement::Table       ||
11488                         eType == vcl::pdf::StructElement::TableRow    ||
11489                         eType == vcl::pdf::StructElement::TableHeader ||
11490                         eType == vcl::pdf::StructElement::TableData   ||
11491                         eType == vcl::pdf::StructElement::Span        ||
11492                         eType == vcl::pdf::StructElement::Quote       ||
11493                         eType == vcl::pdf::StructElement::Emphasis ||
11494                         eType == vcl::pdf::StructElement::Strong ||
11495                         eType == vcl::pdf::StructElement::Note        ||
11496                         eType == vcl::pdf::StructElement::Reference   ||
11497                         eType == vcl::pdf::StructElement::BibEntry    ||
11498                         eType == vcl::pdf::StructElement::Code        ||
11499                         eType == vcl::pdf::StructElement::Link)
11500                     {
11501                         bInsert = true;
11502                     }
11503                 }
11504                 break;
11505             case PDFWriter::TextDecorationType:
11506                 if( eVal == PDFWriter::NONE         ||
11507                     eVal == PDFWriter::Underline    ||
11508                     eVal == PDFWriter::Overline     ||
11509                     eVal == PDFWriter::LineThrough )
11510                 {
11511                     // only for ILSE and BLSE
11512                     if (eType == vcl::pdf::StructElement::Paragraph   ||
11513                         eType == vcl::pdf::StructElement::Title ||
11514                         eType == vcl::pdf::StructElement::Heading     ||
11515                         eType == vcl::pdf::StructElement::H1          ||
11516                         eType == vcl::pdf::StructElement::H2          ||
11517                         eType == vcl::pdf::StructElement::H3          ||
11518                         eType == vcl::pdf::StructElement::H4          ||
11519                         eType == vcl::pdf::StructElement::H5          ||
11520                         eType == vcl::pdf::StructElement::H6          ||
11521                         eType == vcl::pdf::StructElement::List        ||
11522                         eType == vcl::pdf::StructElement::ListItem    ||
11523                         eType == vcl::pdf::StructElement::LILabel     ||
11524                         eType == vcl::pdf::StructElement::LIBody      ||
11525                         eType == vcl::pdf::StructElement::Table       ||
11526                         eType == vcl::pdf::StructElement::TableRow    ||
11527                         eType == vcl::pdf::StructElement::TableHeader ||
11528                         eType == vcl::pdf::StructElement::TableData   ||
11529                         eType == vcl::pdf::StructElement::Span        ||
11530                         eType == vcl::pdf::StructElement::Quote       ||
11531                         eType == vcl::pdf::StructElement::Emphasis ||
11532                         eType == vcl::pdf::StructElement::Strong ||
11533                         eType == vcl::pdf::StructElement::Note        ||
11534                         eType == vcl::pdf::StructElement::Reference   ||
11535                         eType == vcl::pdf::StructElement::BibEntry    ||
11536                         eType == vcl::pdf::StructElement::Code        ||
11537                         eType == vcl::pdf::StructElement::Link)
11538                     {
11539                         bInsert = true;
11540                     }
11541                 }
11542                 break;
11543             case PDFWriter::Scope:
11544                 if (eVal == PDFWriter::Row || eVal == PDFWriter::Column || eVal == PDFWriter::Both)
11545                 {
11546                     if (eType == vcl::pdf::StructElement::TableHeader
11547                         && PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version)
11548                     {
11549                         bInsert = true;
11550                     }
11551                 }
11552                 break;
11553             case PDFWriter::Type:
11554                 if (eVal == PDFWriter::Pagination || eVal == PDFWriter::Layout || eVal == PDFWriter::Page)
11555                     // + Background for PDF >= 1.7
11556                 {
11557                     if (eType == vcl::pdf::StructElement::NonStructElement)
11558                     {
11559                         bInsert = true;
11560                     }
11561                 }
11562                 break;
11563             case PDFWriter::Subtype:
11564                 if (eVal == PDFWriter::Header || eVal == PDFWriter::Footer || eVal == PDFWriter::Watermark)
11565                 {
11566                     if (eType == vcl::pdf::StructElement::NonStructElement
11567                         && PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
11568                     {
11569                         bInsert = true;
11570                     }
11571                 }
11572                 break;
11573             case PDFWriter::Role:
11574                 if (eVal == PDFWriter::Rb || eVal == PDFWriter::Cb || eVal == PDFWriter::Pb || eVal == PDFWriter::Tv)
11575                 {
11576                     if (eType == vcl::pdf::StructElement::Form
11577                         && PDFWriter::PDFVersion::PDF_1_7 <= m_aContext.Version)
11578                     {
11579                         bInsert = true;
11580                     }
11581                 }
11582                 break;
11583             case PDFWriter::RubyAlign:
11584                 if (eVal == PDFWriter::RStart || eVal == PDFWriter::RCenter || eVal == PDFWriter::REnd || eVal == PDFWriter::RJustify || eVal == PDFWriter::RDistribute)
11585                 {
11586                     if (eType == vcl::pdf::StructElement::RT
11587                         && PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version)
11588                     {
11589                         bInsert = true;
11590                     }
11591                 }
11592                 break;
11593             case PDFWriter::RubyPosition:
11594                 if (eVal == PDFWriter::RBefore || eVal == PDFWriter::RAfter || eVal == PDFWriter::RWarichu || eVal == PDFWriter::RInline)
11595                 {
11596                     if (eType == vcl::pdf::StructElement::RT
11597                         && PDFWriter::PDFVersion::PDF_1_5 <= m_aContext.Version)
11598                     {
11599                         bInsert = true;
11600                     }
11601                 }
11602                 break;
11603             case PDFWriter::ListNumbering:
11604                 if( eVal == PDFWriter::NONE         ||
11605                     eVal == PDFWriter::Disc         ||
11606                     eVal == PDFWriter::Circle       ||
11607                     eVal == PDFWriter::Square       ||
11608                     eVal == PDFWriter::Decimal      ||
11609                     eVal == PDFWriter::UpperRoman   ||
11610                     eVal == PDFWriter::LowerRoman   ||
11611                     eVal == PDFWriter::UpperAlpha   ||
11612                     eVal == PDFWriter::LowerAlpha )
11613                 {
11614                     if( eType == vcl::pdf::StructElement::List )
11615                         bInsert = true;
11616                 }
11617                 break;
11618             default: break;
11619         }
11620     }
11621 
11622     if( bInsert )
11623         m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( eVal );
11624     else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
11625         SAL_INFO("vcl.pdfwriter",
11626                  "rejecting setStructureAttribute( " << getAttributeTag( eAttr )
11627                  << ", " << getAttributeValueTag( eVal )
11628                  << " ) on " << getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
11629                  << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias
11630                  << ") element");
11631 
11632     return bInsert;
11633 }
11634 
setStructureAttributeNumerical(enum PDFWriter::StructAttribute eAttr,sal_Int32 nValue)11635 bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttribute eAttr, sal_Int32 nValue )
11636 {
11637     if( ! m_aContext.Tagged )
11638         return false;
11639 
11640     assert(m_aStructure[m_nCurrentStructElement].m_oType);
11641     bool bInsert = false;
11642     if( m_nCurrentStructElement > 0 && m_bEmitStructure )
11643     {
11644         if( eAttr == PDFWriter::Language )
11645         {
11646             m_aStructure[ m_nCurrentStructElement ].m_aLocale = LanguageTag( LanguageType(nValue) ).getLocale();
11647             return true;
11648         }
11649 
11650         vcl::pdf::StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType;
11651         switch( eAttr )
11652         {
11653             case PDFWriter::SpaceBefore:
11654             case PDFWriter::SpaceAfter:
11655             case PDFWriter::StartIndent:
11656             case PDFWriter::EndIndent:
11657                 // just for BLSE
11658                 if (eType == vcl::pdf::StructElement::Paragraph   ||
11659                     eType == vcl::pdf::StructElement::Title ||
11660                     eType == vcl::pdf::StructElement::Heading     ||
11661                     eType == vcl::pdf::StructElement::H1          ||
11662                     eType == vcl::pdf::StructElement::H2          ||
11663                     eType == vcl::pdf::StructElement::H3          ||
11664                     eType == vcl::pdf::StructElement::H4          ||
11665                     eType == vcl::pdf::StructElement::H5          ||
11666                     eType == vcl::pdf::StructElement::H6          ||
11667                     eType == vcl::pdf::StructElement::List        ||
11668                     eType == vcl::pdf::StructElement::ListItem    ||
11669                     eType == vcl::pdf::StructElement::LILabel     ||
11670                     eType == vcl::pdf::StructElement::LIBody      ||
11671                     eType == vcl::pdf::StructElement::Table       ||
11672                     eType == vcl::pdf::StructElement::TableRow    ||
11673                     eType == vcl::pdf::StructElement::TableHeader ||
11674                     eType == vcl::pdf::StructElement::TableData)
11675                 {
11676                     bInsert = true;
11677                 }
11678                 break;
11679             case PDFWriter::TextIndent:
11680                 // paragraph like BLSE and additional elements
11681                 if (eType == vcl::pdf::StructElement::Paragraph   ||
11682                     eType == vcl::pdf::StructElement::Title ||
11683                     eType == vcl::pdf::StructElement::Heading     ||
11684                     eType == vcl::pdf::StructElement::H1          ||
11685                     eType == vcl::pdf::StructElement::H2          ||
11686                     eType == vcl::pdf::StructElement::H3          ||
11687                     eType == vcl::pdf::StructElement::H4          ||
11688                     eType == vcl::pdf::StructElement::H5          ||
11689                     eType == vcl::pdf::StructElement::H6          ||
11690                     eType == vcl::pdf::StructElement::LILabel     ||
11691                     eType == vcl::pdf::StructElement::LIBody      ||
11692                     eType == vcl::pdf::StructElement::TableHeader ||
11693                     eType == vcl::pdf::StructElement::TableData)
11694                 {
11695                     bInsert = true;
11696                 }
11697                 break;
11698             case PDFWriter::Width:
11699             case PDFWriter::Height:
11700                 if (eType == vcl::pdf::StructElement::Figure      ||
11701                     eType == vcl::pdf::StructElement::Formula     ||
11702                     eType == vcl::pdf::StructElement::Form        ||
11703                     eType == vcl::pdf::StructElement::Table       ||
11704                     eType == vcl::pdf::StructElement::TableHeader ||
11705                     eType == vcl::pdf::StructElement::TableData)
11706                 {
11707                     bInsert = true;
11708                 }
11709                 break;
11710             case PDFWriter::LineHeight:
11711             case PDFWriter::BaselineShift:
11712                 // only for ILSE and BLSE
11713                 if (eType == vcl::pdf::StructElement::Paragraph   ||
11714                     eType == vcl::pdf::StructElement::Title ||
11715                     eType == vcl::pdf::StructElement::Heading     ||
11716                     eType == vcl::pdf::StructElement::H1          ||
11717                     eType == vcl::pdf::StructElement::H2          ||
11718                     eType == vcl::pdf::StructElement::H3          ||
11719                     eType == vcl::pdf::StructElement::H4          ||
11720                     eType == vcl::pdf::StructElement::H5          ||
11721                     eType == vcl::pdf::StructElement::H6          ||
11722                     eType == vcl::pdf::StructElement::List        ||
11723                     eType == vcl::pdf::StructElement::ListItem    ||
11724                     eType == vcl::pdf::StructElement::LILabel     ||
11725                     eType == vcl::pdf::StructElement::LIBody      ||
11726                     eType == vcl::pdf::StructElement::Table       ||
11727                     eType == vcl::pdf::StructElement::TableRow    ||
11728                     eType == vcl::pdf::StructElement::TableHeader ||
11729                     eType == vcl::pdf::StructElement::TableData   ||
11730                     eType == vcl::pdf::StructElement::Span        ||
11731                     eType == vcl::pdf::StructElement::Quote       ||
11732                     eType == vcl::pdf::StructElement::Emphasis ||
11733                     eType == vcl::pdf::StructElement::Strong ||
11734                     eType == vcl::pdf::StructElement::Note        ||
11735                     eType == vcl::pdf::StructElement::Reference   ||
11736                     eType == vcl::pdf::StructElement::BibEntry    ||
11737                     eType == vcl::pdf::StructElement::Code        ||
11738                     eType == vcl::pdf::StructElement::Link)
11739                 {
11740                         bInsert = true;
11741                 }
11742                 break;
11743             case PDFWriter::RowSpan:
11744             case PDFWriter::ColSpan:
11745                 // only for table cells
11746                 if (eType == vcl::pdf::StructElement::TableHeader ||
11747                     eType == vcl::pdf::StructElement::TableData)
11748                 {
11749                     bInsert = true;
11750                 }
11751                 break;
11752             case PDFWriter::LinkAnnotation:
11753                 if (eType == vcl::pdf::StructElement::Link)
11754                     bInsert = true;
11755                 break;
11756             case PDFWriter::NoteAnnotation:
11757                 if (eType == vcl::pdf::StructElement::Annot)
11758                     bInsert = true;
11759                 break;
11760             default: break;
11761         }
11762     }
11763 
11764     if( bInsert )
11765         m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( nValue );
11766     else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
11767         SAL_INFO("vcl.pdfwriter",
11768                  "rejecting setStructureAttributeNumerical( " << getAttributeTag( eAttr )
11769                  << ", " << static_cast<int>(nValue)
11770                  << " ) on " << getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType)
11771                  << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias
11772                  << ") element");
11773 
11774     return bInsert;
11775 }
11776 
setStructureBoundingBox(const tools::Rectangle & rRect)11777 void PDFWriterImpl::setStructureBoundingBox( const tools::Rectangle& rRect )
11778 {
11779     sal_Int32 nPageNr = m_nCurrentPage;
11780     if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() || !m_aContext.Tagged )
11781         return;
11782 
11783     if( !(m_nCurrentStructElement > 0 && m_bEmitStructure) )
11784         return;
11785 
11786     assert(m_aStructure[m_nCurrentStructElement].m_oType);
11787     vcl::pdf::StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType;
11788     if (eType == vcl::pdf::StructElement::Figure ||
11789         eType == vcl::pdf::StructElement::Formula ||
11790         eType == vcl::pdf::StructElement::Form ||
11791         eType == vcl::pdf::StructElement::Division ||
11792         eType == vcl::pdf::StructElement::Table)
11793     {
11794         m_aStructure[ m_nCurrentStructElement ].m_aBBox = rRect;
11795         // convert to default user space now, since the mapmode may change
11796         m_aPages[nPageNr].convertRect( m_aStructure[ m_nCurrentStructElement ].m_aBBox );
11797     }
11798 }
11799 
setStructureAnnotIds(::std::vector<sal_Int32> const & rAnnotIds)11800 void PDFWriterImpl::setStructureAnnotIds(::std::vector<sal_Int32> const& rAnnotIds)
11801 {
11802     assert(!(m_nCurrentPage < 0 || m_aPages.size() <= o3tl::make_unsigned(m_nCurrentPage)));
11803 
11804     if (!m_aContext.Tagged || m_nCurrentStructElement <= 0 || !m_bEmitStructure)
11805     {
11806         return;
11807     }
11808 
11809     m_aStructure[m_nCurrentStructElement].m_AnnotIds = rAnnotIds;
11810 }
11811 
setActualText(const OUString & rText)11812 void PDFWriterImpl::setActualText( const OUString& rText )
11813 {
11814     if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
11815     {
11816         m_aStructure[ m_nCurrentStructElement ].m_aActualText = rText;
11817     }
11818 }
11819 
setAlternateText(const OUString & rText)11820 void PDFWriterImpl::setAlternateText( const OUString& rText )
11821 {
11822     if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
11823     {
11824         m_aStructure[ m_nCurrentStructElement ].m_aAltText = rText;
11825     }
11826 }
11827 
setPageTransition(PDFWriter::PageTransition eType,sal_uInt32 nMilliSec,sal_Int32 nPageNr)11828 void PDFWriterImpl::setPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr )
11829 {
11830     if( nPageNr < 0 )
11831         nPageNr = m_nCurrentPage;
11832 
11833     if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
11834         return;
11835 
11836     m_aPages[ nPageNr ].m_eTransition   = eType;
11837     m_aPages[ nPageNr ].m_nTransTime    = nMilliSec;
11838 }
11839 
ensureUniqueRadioOnValues()11840 void PDFWriterImpl::ensureUniqueRadioOnValues()
11841 {
11842     // loop over radio groups
11843     for (auto const& group : m_aRadioGroupWidgets)
11844     {
11845         PDFWidget& rGroupWidget = m_aWidgets[ group.second ];
11846         // check whether all kids have a unique OnValue
11847         std::unordered_map< OUString, sal_Int32 > aOnValues;
11848         bool bIsUnique = true;
11849         for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11850         {
11851             const OUString& rVal = m_aWidgets[nKidIndex].m_aOnValue;
11852             SAL_INFO("vcl.pdfwriter", "OnValue: " << rVal);
11853             if( aOnValues.find( rVal ) == aOnValues.end() )
11854             {
11855                 aOnValues[ rVal ] = 1;
11856             }
11857             else
11858             {
11859                 bIsUnique = false;
11860                 break;
11861             }
11862         }
11863         if( ! bIsUnique )
11864         {
11865             SAL_INFO("vcl.pdfwriter", "enforcing unique OnValues" );
11866             // make unique by using ascending OnValues
11867             int nKid = 0;
11868             for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11869             {
11870                 PDFWidget& rKid = m_aWidgets[nKidIndex];
11871                 rKid.m_aOnValue = OUString::number( nKid+1 );
11872                 if( rKid.m_aValue != "Off" )
11873                     rKid.m_aValue = rKid.m_aOnValue;
11874                 ++nKid;
11875             }
11876         }
11877         // finally move the "Yes" appearance to the OnValue appearance
11878         for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11879         {
11880             PDFWidget& rKid = m_aWidgets[nKidIndex];
11881             if ( !rKid.m_aOnValue.isEmpty() )
11882             {
11883                 auto app_it = rKid.m_aAppearances.find( "N"_ostr );
11884                 if( app_it != rKid.m_aAppearances.end() )
11885                 {
11886                     auto stream_it = app_it->second.find( "Yes"_ostr );
11887                     if( stream_it != app_it->second.end() )
11888                     {
11889                         SvMemoryStream* pStream = stream_it->second;
11890                         app_it->second.erase( stream_it );
11891                         OStringBuffer aBuf( rKid.m_aOnValue.getLength()*2 );
11892                         COSWriter::appendName( rKid.m_aOnValue, aBuf );
11893                         (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
11894                     }
11895                     else
11896                         SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Yes\" stream" );
11897                 }
11898             }
11899 
11900             if ( !rKid.m_aOffValue.isEmpty() )
11901             {
11902                 auto app_it = rKid.m_aAppearances.find( "N"_ostr );
11903                 if( app_it != rKid.m_aAppearances.end() )
11904                 {
11905                     auto stream_it = app_it->second.find( "Off"_ostr );
11906                     if( stream_it != app_it->second.end() )
11907                     {
11908                         SvMemoryStream* pStream = stream_it->second;
11909                         app_it->second.erase( stream_it );
11910                         OStringBuffer aBuf( rKid.m_aOffValue.getLength()*2 );
11911                         COSWriter::appendName( rKid.m_aOffValue, aBuf );
11912                         (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
11913                     }
11914                     else
11915                         SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Off\" stream" );
11916                 }
11917             }
11918 
11919             // update selected radio button
11920             if( rKid.m_aValue != "Off" )
11921             {
11922                 rGroupWidget.m_aValue = rKid.m_aValue;
11923             }
11924         }
11925     }
11926 }
11927 
findRadioGroupWidget(const PDFWriter::RadioButtonWidget & rBtn)11928 sal_Int32 PDFWriterImpl::findRadioGroupWidget( const PDFWriter::RadioButtonWidget& rBtn )
11929 {
11930     sal_Int32 nRadioGroupWidget = -1;
11931 
11932     std::map< sal_Int32, sal_Int32 >::const_iterator it = m_aRadioGroupWidgets.find( rBtn.RadioGroup );
11933 
11934     if( it == m_aRadioGroupWidgets.end() )
11935     {
11936         m_aRadioGroupWidgets[ rBtn.RadioGroup ] = nRadioGroupWidget =
11937             sal_Int32(m_aWidgets.size());
11938 
11939         // new group, insert the radiobutton
11940         m_aWidgets.emplace_back( );
11941         m_aWidgets.back().m_nObject     = createObject();
11942         m_aWidgets.back().m_nPage       = m_nCurrentPage;
11943         m_aWidgets.back().m_eType       = PDFWriter::RadioButton;
11944         m_aWidgets.back().m_nRadioGroup = rBtn.RadioGroup;
11945         m_aWidgets.back().m_nFlags |= 0x0000C000;   // NoToggleToOff and Radio bits
11946 
11947         createWidgetFieldName( sal_Int32(m_aWidgets.size()-1), rBtn );
11948     }
11949     else
11950         nRadioGroupWidget = it->second;
11951 
11952     return nRadioGroupWidget;
11953 }
11954 
createControl(const PDFWriter::AnyWidget & rControl,sal_Int32 nPageNr)11955 sal_Int32 PDFWriterImpl::createControl( const PDFWriter::AnyWidget& rControl, sal_Int32 nPageNr )
11956 {
11957     if( nPageNr < 0 )
11958         nPageNr = m_nCurrentPage;
11959 
11960     if( nPageNr < 0 || o3tl::make_unsigned(nPageNr) >= m_aPages.size() )
11961         return -1;
11962 
11963     sal_Int32 nNewWidget = m_aWidgets.size();
11964     m_aWidgets.emplace_back( );
11965 
11966     m_aWidgets.back().m_nObject         = createObject();
11967     m_aWidgets.back().m_aRect           = rControl.Location;
11968     m_aWidgets.back().m_nPage           = nPageNr;
11969     m_aWidgets.back().m_eType           = rControl.getType();
11970 
11971     sal_Int32 nRadioGroupWidget = -1;
11972     // for unknown reasons the radio buttons of a radio group must not have a
11973     // field name, else the buttons are in fact check boxes -
11974     // that is multiple buttons of the radio group can be selected
11975     if( rControl.getType() == PDFWriter::RadioButton )
11976         nRadioGroupWidget = findRadioGroupWidget( static_cast<const PDFWriter::RadioButtonWidget&>(rControl) );
11977     else
11978     {
11979         createWidgetFieldName( nNewWidget, rControl );
11980     }
11981 
11982     // caution: m_aWidgets must not be changed after here or rNewWidget may be invalid
11983     PDFWidget& rNewWidget           = m_aWidgets[nNewWidget];
11984     rNewWidget.m_aDescription       = rControl.Description;
11985     rNewWidget.m_aText              = rControl.Text;
11986     rNewWidget.m_nTextStyle         = rControl.TextStyle &
11987         (  DrawTextFlags::Left | DrawTextFlags::Center | DrawTextFlags::Right | DrawTextFlags::Top |
11988            DrawTextFlags::VCenter | DrawTextFlags::Bottom |
11989            DrawTextFlags::MultiLine | DrawTextFlags::WordBreak  );
11990     rNewWidget.m_nTabOrder          = rControl.TabOrder;
11991 
11992     // various properties are set via the flags (/Ff) property of the field dict
11993     if( rControl.ReadOnly )
11994         rNewWidget.m_nFlags |= 1;
11995     if( rControl.getType() == PDFWriter::PushButton )
11996     {
11997         const PDFWriter::PushButtonWidget& rBtn = static_cast<const PDFWriter::PushButtonWidget&>(rControl);
11998         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11999             rNewWidget.m_nTextStyle =
12000                 DrawTextFlags::Center | DrawTextFlags::VCenter |
12001                 DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
12002 
12003         rNewWidget.m_nFlags |= 0x00010000;
12004         if( !rBtn.URL.isEmpty() )
12005             rNewWidget.m_aListEntries.push_back( rBtn.URL );
12006         rNewWidget.m_bSubmit    = rBtn.Submit;
12007         rNewWidget.m_bSubmitGet = rBtn.SubmitGet;
12008         rNewWidget.m_nDest      = rBtn.Dest;
12009         createDefaultPushButtonAppearance( rNewWidget, rBtn );
12010     }
12011     else if( rControl.getType() == PDFWriter::RadioButton )
12012     {
12013         const PDFWriter::RadioButtonWidget& rBtn = static_cast<const PDFWriter::RadioButtonWidget&>(rControl);
12014         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
12015             rNewWidget.m_nTextStyle =
12016                 DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
12017         /*  PDF sees a RadioButton group as one radio button with
12018          *  children which are in turn check boxes
12019          *
12020          *  so we need to create a radio button on demand for a new group
12021          *  and insert a checkbox for each RadioButtonWidget as its child
12022          */
12023         rNewWidget.m_eType          = PDFWriter::CheckBox;
12024         rNewWidget.m_nRadioGroup    = rBtn.RadioGroup;
12025 
12026         SAL_WARN_IF( nRadioGroupWidget < 0 || o3tl::make_unsigned(nRadioGroupWidget) >= m_aWidgets.size(), "vcl.pdfwriter", "no radio group parent" );
12027 
12028         PDFWidget& rRadioButton = m_aWidgets[nRadioGroupWidget];
12029         rRadioButton.m_aKids.push_back( rNewWidget.m_nObject );
12030         rRadioButton.m_aKidsIndex.push_back( nNewWidget );
12031         rNewWidget.m_nParent = rRadioButton.m_nObject;
12032 
12033         rNewWidget.m_aValue     = "Off";
12034         rNewWidget.m_aOnValue   = rBtn.OnValue;
12035         rNewWidget.m_aOffValue   = rBtn.OffValue;
12036         if( rRadioButton.m_aValue.isEmpty() && rBtn.Selected )
12037         {
12038             rNewWidget.m_aValue     = rNewWidget.m_aOnValue;
12039             rRadioButton.m_aValue   = rNewWidget.m_aOnValue;
12040         }
12041         createDefaultRadioButtonAppearance( rNewWidget, rBtn );
12042 
12043         // union rect of radio group
12044         tools::Rectangle aRect = rNewWidget.m_aRect;
12045         m_aPages[ nPageNr ].convertRect( aRect );
12046         rRadioButton.m_aRect.Union( aRect );
12047     }
12048     else if( rControl.getType() == PDFWriter::CheckBox )
12049     {
12050         const PDFWriter::CheckBoxWidget& rBox = static_cast<const PDFWriter::CheckBoxWidget&>(rControl);
12051         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
12052             rNewWidget.m_nTextStyle =
12053                 DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
12054 
12055         rNewWidget.m_aValue
12056             = rBox.Checked ? std::u16string_view(u"Yes") : std::u16string_view(u"Off" );
12057         rNewWidget.m_aOnValue   = rBox.OnValue;
12058         rNewWidget.m_aOffValue   = rBox.OffValue;
12059         // create default appearance before m_aRect gets transformed
12060         createDefaultCheckBoxAppearance( rNewWidget, rBox );
12061     }
12062     else if( rControl.getType() == PDFWriter::ListBox )
12063     {
12064         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
12065             rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
12066 
12067         const PDFWriter::ListBoxWidget& rLstBox = static_cast<const PDFWriter::ListBoxWidget&>(rControl);
12068         rNewWidget.m_aListEntries     = rLstBox.Entries;
12069         rNewWidget.m_aSelectedEntries = rLstBox.SelectedEntries;
12070         rNewWidget.m_aValue           = rLstBox.Text;
12071         if( rLstBox.DropDown )
12072             rNewWidget.m_nFlags |= 0x00020000;
12073         if (rLstBox.MultiSelect && !rLstBox.DropDown)
12074             rNewWidget.m_nFlags |= 0x00200000;
12075 
12076         createDefaultListBoxAppearance( rNewWidget, rLstBox );
12077     }
12078     else if( rControl.getType() == PDFWriter::ComboBox )
12079     {
12080         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
12081             rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
12082 
12083         const PDFWriter::ComboBoxWidget& rBox = static_cast<const PDFWriter::ComboBoxWidget&>(rControl);
12084         rNewWidget.m_aValue         = rBox.Text;
12085         rNewWidget.m_aListEntries   = rBox.Entries;
12086         rNewWidget.m_nFlags |= 0x00060000; // combo and edit flag
12087 
12088         PDFWriter::ListBoxWidget aLBox;
12089         aLBox.Name              = rBox.Name;
12090         aLBox.Description       = rBox.Description;
12091         aLBox.Text              = rBox.Text;
12092         aLBox.TextStyle         = rBox.TextStyle;
12093         aLBox.ReadOnly          = rBox.ReadOnly;
12094         aLBox.Border            = rBox.Border;
12095         aLBox.BorderColor       = rBox.BorderColor;
12096         aLBox.Background        = rBox.Background;
12097         aLBox.BackgroundColor   = rBox.BackgroundColor;
12098         aLBox.TextFont          = rBox.TextFont;
12099         aLBox.TextColor         = rBox.TextColor;
12100         aLBox.DropDown          = true;
12101         aLBox.MultiSelect       = false;
12102         aLBox.Entries           = rBox.Entries;
12103 
12104         createDefaultListBoxAppearance( rNewWidget, aLBox );
12105     }
12106     else if( rControl.getType() == PDFWriter::Edit )
12107     {
12108         if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
12109             rNewWidget.m_nTextStyle = DrawTextFlags::Left | DrawTextFlags::VCenter;
12110 
12111         const PDFWriter::EditWidget& rEdit = static_cast<const  PDFWriter::EditWidget&>(rControl);
12112         if( rEdit.MultiLine )
12113         {
12114             rNewWidget.m_nFlags |= 0x00001000;
12115             rNewWidget.m_nTextStyle |= DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
12116         }
12117         if( rEdit.Password )
12118             rNewWidget.m_nFlags |= 0x00002000;
12119         if (rEdit.FileSelect)
12120             rNewWidget.m_nFlags |= 0x00100000;
12121         rNewWidget.m_nMaxLen = rEdit.MaxLen;
12122         rNewWidget.m_nFormat = rEdit.Format;
12123         rNewWidget.m_aCurrencySymbol = rEdit.CurrencySymbol;
12124         rNewWidget.m_nDecimalAccuracy = rEdit.DecimalAccuracy;
12125         rNewWidget.m_bPrependCurrencySymbol = rEdit.PrependCurrencySymbol;
12126         rNewWidget.m_aTimeFormat = rEdit.TimeFormat;
12127         rNewWidget.m_aDateFormat = rEdit.DateFormat;
12128         rNewWidget.m_aValue = rEdit.Text;
12129 
12130         createDefaultEditAppearance( rNewWidget, rEdit );
12131     }
12132 #if HAVE_FEATURE_NSS
12133     else if( rControl.getType() == PDFWriter::Signature)
12134     {
12135         rNewWidget.m_aRect = tools::Rectangle(0, 0, 0, 0);
12136 
12137         m_nSignatureObject = createObject();
12138         rNewWidget.m_aValue = OUString::number( m_nSignatureObject );
12139         rNewWidget.m_aValue += " 0 R";
12140         // let's add a fake appearance
12141         rNewWidget.m_aAppearances[ "N"_ostr ][ "Standard"_ostr ] = new SvMemoryStream();
12142     }
12143 #endif
12144 
12145     // if control is a signature, do not convert coordinates since we
12146     // need /Rect [ 0 0 0 0 ]
12147     if ( rControl.getType() != PDFWriter::Signature )
12148     {
12149         // convert to default user space now, since the mapmode may change
12150         // note: create default appearances before m_aRect gets transformed
12151         m_aPages[ nPageNr ].convertRect( rNewWidget.m_aRect );
12152     }
12153 
12154     // insert widget to page's annotation list
12155     m_aPages[ nPageNr ].m_aAnnotations.push_back( rNewWidget.m_nObject );
12156 
12157     return nNewWidget;
12158 }
12159 
MARK(const char * pString)12160 void PDFWriterImpl::MARK( const char* pString )
12161 {
12162     beginStructureElementMCSeq();
12163     if (g_bDebugDisableCompression)
12164         emitComment( pString );
12165 }
12166 
getObject() const12167 sal_Int32 ReferenceXObjectEmit::getObject() const
12168 {
12169     if (m_nFormObject > 0)
12170         return m_nFormObject;
12171     else
12172         return m_nBitmapObject;
12173 }
12174 }
12175 
12176 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
12177