bump product version to 6.4.0.3
[LibreOffice.git] / vcl / source / gdi / pdfwriter_impl.cxx
blob83d5c75bc912e98b9de71aa4b1d85d26dc382fa6
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <config_features.h>
22 #include <sal/types.h>
24 #include <math.h>
25 #include <algorithm>
27 #include <lcms2.h>
29 #include <basegfx/matrix/b2dhommatrix.hxx>
30 #include <basegfx/polygon/b2dpolygon.hxx>
31 #include <basegfx/polygon/b2dpolygontools.hxx>
32 #include <basegfx/polygon/b2dpolypolygon.hxx>
33 #include <basegfx/polygon/b2dpolypolygoncutter.hxx>
34 #include <basegfx/polygon/b2dpolypolygontools.hxx>
35 #include <memory>
36 #include <com/sun/star/io/XOutputStream.hpp>
37 #include <com/sun/star/util/URL.hpp>
38 #include <com/sun/star/util/URLTransformer.hpp>
39 #include <comphelper/processfactory.hxx>
40 #include <comphelper/string.hxx>
41 #include <cppuhelper/implbase.hxx>
42 #include <i18nlangtag/languagetag.hxx>
43 #include <o3tl/numeric.hxx>
44 #include <osl/file.hxx>
45 #include <osl/thread.h>
46 #include <rtl/digest.h>
47 #include <rtl/ustrbuf.hxx>
48 #include <sal/log.hxx>
49 #include <svl/urihelper.hxx>
50 #include <tools/fract.hxx>
51 #include <tools/helpers.hxx>
52 #include <tools/stream.hxx>
53 #include <tools/urlobj.hxx>
54 #include <tools/zcodec.hxx>
55 #include <svl/cryptosign.hxx>
56 #include <vcl/bitmapex.hxx>
57 #include <vcl/bitmapaccess.hxx>
58 #include <vcl/canvastools.hxx>
59 #include <vcl/cvtgrf.hxx>
60 #include <vcl/fontcharmap.hxx>
61 #include <vcl/lineinfo.hxx>
62 #include <vcl/metric.hxx>
63 #include <vcl/settings.hxx>
64 #include <vcl/strhelper.hxx>
65 #include <vcl/svapp.hxx>
66 #include <vcl/virdev.hxx>
67 #include <vcl/filter/pdfdocument.hxx>
68 #include <comphelper/hash.hxx>
70 #include <fontsubset.hxx>
71 #include <PhysicalFontFace.hxx>
72 #include <salgdi.hxx>
73 #include <textlayout.hxx>
74 #include <textlineinfo.hxx>
75 #include <bitmapwriteaccess.hxx>
76 #include <impglyphitem.hxx>
78 #include "pdfwriter_impl.hxx"
80 #ifdef _WIN32
81 // WinCrypt headers for PDF signing
82 // Note: this uses Windows 7 APIs and requires the relevant data types
83 #include <prewin.h>
84 #include <wincrypt.h>
85 #include <postwin.h>
86 #endif
88 #include <config_eot.h>
90 #if ENABLE_EOT
91 #include <libeot/libeot.h>
92 #endif
94 using namespace vcl;
95 using namespace::com::sun::star;
97 static bool g_bDebugDisableCompression = getenv("VCL_DEBUG_DISABLE_PDFCOMPRESSION");
99 #if HAVE_FEATURE_NSS
100 // Is this length truly the maximum possible, or just a number that
101 // seemed large enough when the author tested this (with some type of
102 // certificates)? I suspect the latter.
104 // Used to be 0x4000 = 16384, but a sample signed PDF (produced by
105 // some other software) provided by the customer has a signature
106 // content that is 30000 bytes. The SampleSignedPDFDocument.pdf from
107 // Adobe has one that is 21942 bytes. So let's be careful. Pity this
108 // can't be dynamic, at least not without restructuring the code. Also
109 // note that the checks in the code for this being too small
110 // apparently are broken, if this overflows you end up with an invalid
111 // PDF. Need to fix that.
113 #define MAX_SIGNATURE_CONTENT_LENGTH 50000
114 #endif
116 static const sal_Int32 nLog10Divisor = 3;
117 static const double fDivisor = 1000.0;
119 static double pixelToPoint( double px ) { return px/fDivisor; }
120 static sal_Int32 pointToPixel( double pt ) { return sal_Int32(pt*fDivisor); }
122 const sal_uInt8 PDFWriterImpl::s_nPadString[32] =
124 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
125 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A
128 static void appendHex( sal_Int8 nInt, OStringBuffer& rBuffer )
130 static const sal_Char pHexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7',
131 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
132 rBuffer.append( pHexDigits[ (nInt >> 4) & 15 ] );
133 rBuffer.append( pHexDigits[ nInt & 15 ] );
136 static void appendName( const OUString& rStr, OStringBuffer& rBuffer )
138 // FIXME i59651 add a check for max length of 127 chars? Per PDF spec 1.4, appendix C.1
139 // I guess than when reading the #xx sequence it will count for a single character.
140 OString aStr( OUStringToOString( rStr, RTL_TEXTENCODING_UTF8 ) );
141 int nLen = aStr.getLength();
142 for( int i = 0; i < nLen; i++ )
144 /* #i16920# PDF recommendation: output UTF8, any byte
145 * outside the interval [33(=ASCII'!');126(=ASCII'~')]
146 * should be escaped hexadecimal
147 * for the sake of ghostscript which also reads PDF
148 * but has a narrower acceptance rate we only pass
149 * alphanumerics and '-' literally.
151 if( (aStr[i] >= 'A' && aStr[i] <= 'Z' ) ||
152 (aStr[i] >= 'a' && aStr[i] <= 'z' ) ||
153 (aStr[i] >= '0' && aStr[i] <= '9' ) ||
154 aStr[i] == '-' )
156 rBuffer.append( aStr[i] );
158 else
160 rBuffer.append( '#' );
161 appendHex( static_cast<sal_Int8>(aStr[i]), rBuffer );
166 static void appendName( const sal_Char* pStr, OStringBuffer& rBuffer )
168 // FIXME i59651 see above
169 while( pStr && *pStr )
171 if( (*pStr >= 'A' && *pStr <= 'Z' ) ||
172 (*pStr >= 'a' && *pStr <= 'z' ) ||
173 (*pStr >= '0' && *pStr <= '9' ) ||
174 *pStr == '-' )
176 rBuffer.append( *pStr );
178 else
180 rBuffer.append( '#' );
181 appendHex( static_cast<sal_Int8>(*pStr), rBuffer );
183 pStr++;
187 //used only to emit encoded passwords
188 static void appendLiteralString( const sal_Char* pStr, sal_Int32 nLength, OStringBuffer& rBuffer )
190 while( nLength )
192 switch( *pStr )
194 case '\n' :
195 rBuffer.append( "\\n" );
196 break;
197 case '\r' :
198 rBuffer.append( "\\r" );
199 break;
200 case '\t' :
201 rBuffer.append( "\\t" );
202 break;
203 case '\b' :
204 rBuffer.append( "\\b" );
205 break;
206 case '\f' :
207 rBuffer.append( "\\f" );
208 break;
209 case '(' :
210 case ')' :
211 case '\\' :
212 rBuffer.append( "\\" );
213 rBuffer.append( static_cast<sal_Char>(*pStr) );
214 break;
215 default:
216 rBuffer.append( static_cast<sal_Char>(*pStr) );
217 break;
219 pStr++;
220 nLength--;
225 * Convert a string before using it.
227 * This string conversion function is needed because the destination name
228 * in a PDF file seen through an Internet browser should be
229 * specially crafted, in order to be used directly by the browser.
230 * In this way the fragment part of a hyperlink to a PDF file (e.g. something
231 * as 'test1/test2/a-file.pdf\#thefragment) will be (hopefully) interpreted by the
232 * PDF reader (currently only Adobe Reader plug-in seems to be working that way) called
233 * from inside the Internet browser as: 'open the file test1/test2/a-file.pdf
234 * and go to named destination thefragment using default zoom'.
235 * The conversion is needed because in case of a fragment in the form: Slide%201
236 * (meaning Slide 1) as it is converted obeying the Inet rules, it will become Slide25201
237 * using this conversion, in both the generated named destinations, fragment and GoToR
238 * destination.
240 * The names for destinations are name objects and so they don't need to be encrypted
241 * even though they expose the content of PDF file (e.g. guessing the PDF content from the
242 * destination name).
244 * Further limitation: it is advisable to use standard ASCII characters for
245 * OOo bookmarks.
247 static void appendDestinationName( const OUString& rString, OStringBuffer& rBuffer )
249 const sal_Unicode* pStr = rString.getStr();
250 sal_Int32 nLen = rString.getLength();
251 for( int i = 0; i < nLen; i++ )
253 sal_Unicode aChar = pStr[i];
254 if( (aChar >= '0' && aChar <= '9' ) ||
255 (aChar >= 'a' && aChar <= 'z' ) ||
256 (aChar >= 'A' && aChar <= 'Z' ) ||
257 aChar == '-' )
259 rBuffer.append(static_cast<sal_Char>(aChar));
261 else
263 sal_Int8 aValueHigh = sal_Int8(aChar >> 8);
264 if(aValueHigh > 0)
265 appendHex( aValueHigh, rBuffer );
266 appendHex( static_cast<sal_Int8>(aChar & 255 ), rBuffer );
271 void PDFWriter::AppendUnicodeTextString(const OUString& rString, OStringBuffer& rBuffer)
273 rBuffer.append( "FEFF" );
274 const sal_Unicode* pStr = rString.getStr();
275 sal_Int32 nLen = rString.getLength();
276 for( int i = 0; i < nLen; i++ )
278 sal_Unicode aChar = pStr[i];
279 appendHex( static_cast<sal_Int8>(aChar >> 8), rBuffer );
280 appendHex( static_cast<sal_Int8>(aChar & 255 ), rBuffer );
284 void PDFWriterImpl::createWidgetFieldName( sal_Int32 i_nWidgetIndex, const PDFWriter::AnyWidget& i_rControl )
286 /* #i80258# previously we use appendName here
287 however we need a slightly different coding scheme than the normal
288 name encoding for field names
290 const OUString& rName = (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2) ? i_rControl.Name : i_rControl.Text;
291 OString aStr( OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ) );
292 int nLen = aStr.getLength();
294 OStringBuffer aBuffer( rName.getLength()+64 );
295 for( int i = 0; i < nLen; i++ )
297 /* #i16920# PDF recommendation: output UTF8, any byte
298 * outside the interval [32(=ASCII' ');126(=ASCII'~')]
299 * should be escaped hexadecimal
301 if( aStr[i] >= 32 && aStr[i] <= 126 )
302 aBuffer.append( aStr[i] );
303 else
305 aBuffer.append( '#' );
306 appendHex( static_cast<sal_Int8>(aStr[i]), aBuffer );
310 OString aFullName( aBuffer.makeStringAndClear() );
312 /* #i82785# create hierarchical fields down to the for each dot in i_rName */
313 sal_Int32 nTokenIndex = 0, nLastTokenIndex = 0;
314 OString aPartialName;
315 OString aDomain;
318 nLastTokenIndex = nTokenIndex;
319 aPartialName = aFullName.getToken( 0, '.', nTokenIndex );
320 if( nTokenIndex != -1 )
322 // find or create a hierarchical field
323 // first find the fully qualified name up to this field
324 aDomain = aFullName.copy( 0, nTokenIndex-1 );
325 std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
326 if( it == m_aFieldNameMap.end() )
328 // create new hierarchy field
329 sal_Int32 nNewWidget = m_aWidgets.size();
330 m_aWidgets.emplace_back( );
331 m_aWidgets[nNewWidget].m_nObject = createObject();
332 m_aWidgets[nNewWidget].m_eType = PDFWriter::Hierarchy;
333 m_aWidgets[nNewWidget].m_aName = aPartialName;
334 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
335 m_aFieldNameMap[aDomain] = nNewWidget;
336 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
337 if( nLastTokenIndex > 0 )
339 // this field is not a root field and
340 // needs to be inserted to its parent
341 OString aParentDomain( aDomain.copy( 0, nLastTokenIndex-1 ) );
342 it = m_aFieldNameMap.find( aParentDomain );
343 OSL_ENSURE( it != m_aFieldNameMap.end(), "field name not found" );
344 if( it != m_aFieldNameMap.end() )
346 OSL_ENSURE( it->second < sal_Int32(m_aWidgets.size()), "invalid field number entry" );
347 if( it->second < sal_Int32(m_aWidgets.size()) )
349 PDFWidget& rParentField( m_aWidgets[it->second] );
350 rParentField.m_aKids.push_back( m_aWidgets[nNewWidget].m_nObject );
351 rParentField.m_aKidsIndex.push_back( nNewWidget );
352 m_aWidgets[nNewWidget].m_nParent = rParentField.m_nObject;
357 else if( m_aWidgets[it->second].m_eType != PDFWriter::Hierarchy )
359 // this is invalid, someone tries to have a terminal field as parent
360 // example: a button with the name foo.bar exists and
361 // another button is named foo.bar.no
362 // workaround: put the second terminal field as much up in the hierarchy as
363 // necessary to have a non-terminal field as parent (or none at all)
364 // since it->second already is terminal, we just need to use its parent
365 aDomain.clear();
366 aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
367 if( nLastTokenIndex > 0 )
369 aDomain = aFullName.copy( 0, nLastTokenIndex-1 );
370 aFullName = aDomain + "." + aPartialName;
372 else
373 aFullName = aPartialName;
374 break;
377 } while( nTokenIndex != -1 );
379 // insert widget into its hierarchy field
380 if( !aDomain.isEmpty() )
382 std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
383 if( it != m_aFieldNameMap.end() )
385 OSL_ENSURE( it->second >= 0 && it->second < sal_Int32( m_aWidgets.size() ), "invalid field index" );
386 if( it->second >= 0 && it->second < sal_Int32(m_aWidgets.size()) )
388 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[it->second].m_nObject;
389 m_aWidgets[it->second].m_aKids.push_back( m_aWidgets[i_nWidgetIndex].m_nObject);
390 m_aWidgets[it->second].m_aKidsIndex.push_back( i_nWidgetIndex );
395 if( aPartialName.isEmpty() )
397 // how funny, an empty field name
398 if( i_rControl.getType() == PDFWriter::RadioButton )
400 aPartialName = "RadioGroup" +
401 OString::number( static_cast<const PDFWriter::RadioButtonWidget&>(i_rControl).RadioGroup );
403 else
404 aPartialName = OString( "Widget" );
407 if( ! m_aContext.AllowDuplicateFieldNames )
409 std::unordered_map<OString, sal_Int32>::iterator it = m_aFieldNameMap.find( aFullName );
411 if( it != m_aFieldNameMap.end() ) // not unique
413 std::unordered_map< OString, sal_Int32 >::const_iterator check_it;
414 OString aTry;
415 sal_Int32 nTry = 2;
418 OStringBuffer aUnique( aFullName.getLength() + 16 );
419 aUnique.append( aFullName );
420 aUnique.append( '_' );
421 aUnique.append( nTry++ );
422 aTry = aUnique.makeStringAndClear();
423 check_it = m_aFieldNameMap.find( aTry );
424 } while( check_it != m_aFieldNameMap.end() );
425 aFullName = aTry;
426 m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
427 aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
429 else
430 m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
433 // finally
434 m_aWidgets[i_nWidgetIndex].m_aName = aPartialName;
437 static void appendFixedInt( sal_Int32 nValue, OStringBuffer& rBuffer )
439 if( nValue < 0 )
441 rBuffer.append( '-' );
442 nValue = -nValue;
444 sal_Int32 nFactor = 1, nDiv = nLog10Divisor;
445 while( nDiv-- )
446 nFactor *= 10;
448 sal_Int32 nInt = nValue / nFactor;
449 rBuffer.append( nInt );
450 if (nFactor > 1 && nValue % nFactor)
452 rBuffer.append( '.' );
455 nFactor /= 10;
456 rBuffer.append((nValue / nFactor) % 10);
458 while (nFactor > 1 && nValue % nFactor); // omit trailing zeros
462 // appends a double. PDF does not accept exponential format, only fixed point
463 static void appendDouble( double fValue, OStringBuffer& rBuffer, sal_Int32 nPrecision = 5 )
465 bool bNeg = false;
466 if( fValue < 0.0 )
468 bNeg = true;
469 fValue=-fValue;
472 sal_Int64 nInt = static_cast<sal_Int64>(fValue);
473 fValue -= static_cast<double>(nInt);
474 // optimizing hardware may lead to a value of 1.0 after the subtraction
475 if( rtl::math::approxEqual(fValue, 1.0) || log10( 1.0-fValue ) <= -nPrecision )
477 nInt++;
478 fValue = 0.0;
480 sal_Int64 nFrac = 0;
481 if( fValue )
483 fValue *= pow( 10.0, static_cast<double>(nPrecision) );
484 nFrac = static_cast<sal_Int64>(fValue);
486 if( bNeg && ( nInt || nFrac ) )
487 rBuffer.append( '-' );
488 rBuffer.append( nInt );
489 if( nFrac )
491 int i;
492 rBuffer.append( '.' );
493 sal_Int64 nBound = static_cast<sal_Int64>(pow( 10.0, nPrecision - 1.0 )+0.5);
494 for ( i = 0; ( i < nPrecision ) && nFrac; i++ )
496 sal_Int64 nNumb = nFrac / nBound;
497 nFrac -= nNumb * nBound;
498 rBuffer.append( nNumb );
499 nBound /= 10;
504 static void appendColor( const Color& rColor, OStringBuffer& rBuffer, bool bConvertToGrey )
507 if( rColor != COL_TRANSPARENT )
509 if( bConvertToGrey )
511 sal_uInt8 cByte = rColor.GetLuminance();
512 appendDouble( static_cast<double>(cByte) / 255.0, rBuffer );
514 else
516 appendDouble( static_cast<double>(rColor.GetRed()) / 255.0, rBuffer );
517 rBuffer.append( ' ' );
518 appendDouble( static_cast<double>(rColor.GetGreen()) / 255.0, rBuffer );
519 rBuffer.append( ' ' );
520 appendDouble( static_cast<double>(rColor.GetBlue()) / 255.0, rBuffer );
525 void PDFWriterImpl::appendStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
527 if( rColor != COL_TRANSPARENT )
529 bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
530 appendColor( rColor, rBuffer, bGrey );
531 rBuffer.append( bGrey ? " G" : " RG" );
535 void PDFWriterImpl::appendNonStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
537 if( rColor != COL_TRANSPARENT )
539 bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
540 appendColor( rColor, rBuffer, bGrey );
541 rBuffer.append( bGrey ? " g" : " rg" );
545 // matrix helper class
546 // TODO: use basegfx matrix class instead or derive from it
547 namespace vcl // TODO: use anonymous namespace to keep this class local
549 /* for sparse matrices of the form (2D linear transformations)
550 * f[0] f[1] 0
551 * f[2] f[3] 0
552 * f[4] f[5] 1
554 class Matrix3
556 double f[6];
558 void set( const double *pn ) { for( int i = 0 ; i < 6; i++ ) f[i] = pn[i]; }
559 public:
560 Matrix3();
562 void skew( double alpha, double beta );
563 void scale( double sx, double sy );
564 void rotate( double angle );
565 void translate( double tx, double ty );
566 void invert();
568 void append( PDFWriterImpl::PDFPage const & rPage, OStringBuffer& rBuffer );
570 Point transform( const Point& rPoint ) const;
574 Matrix3::Matrix3()
576 // initialize to unity
577 f[0] = 1.0;
578 f[1] = 0.0;
579 f[2] = 0.0;
580 f[3] = 1.0;
581 f[4] = 0.0;
582 f[5] = 0.0;
585 Point Matrix3::transform( const Point& rOrig ) const
587 double x = static_cast<double>(rOrig.X()), y = static_cast<double>(rOrig.Y());
588 return Point( static_cast<int>(x*f[0] + y*f[2] + f[4]), static_cast<int>(x*f[1] + y*f[3] + f[5]) );
591 void Matrix3::skew( double alpha, double beta )
593 double fn[6];
594 double tb = tan( beta );
595 fn[0] = f[0] + f[2]*tb;
596 fn[1] = f[1];
597 fn[2] = f[2] + f[3]*tb;
598 fn[3] = f[3];
599 fn[4] = f[4] + f[5]*tb;
600 fn[5] = f[5];
601 if( alpha != 0.0 )
603 double ta = tan( alpha );
604 fn[1] += f[0]*ta;
605 fn[3] += f[2]*ta;
606 fn[5] += f[4]*ta;
608 set( fn );
611 void Matrix3::scale( double sx, double sy )
613 double fn[6];
614 fn[0] = sx*f[0];
615 fn[1] = sy*f[1];
616 fn[2] = sx*f[2];
617 fn[3] = sy*f[3];
618 fn[4] = sx*f[4];
619 fn[5] = sy*f[5];
620 set( fn );
623 void Matrix3::rotate( double angle )
625 double fn[6];
626 double fSin = sin(angle);
627 double fCos = cos(angle);
628 fn[0] = f[0]*fCos - f[1]*fSin;
629 fn[1] = f[0]*fSin + f[1]*fCos;
630 fn[2] = f[2]*fCos - f[3]*fSin;
631 fn[3] = f[2]*fSin + f[3]*fCos;
632 fn[4] = f[4]*fCos - f[5]*fSin;
633 fn[5] = f[4]*fSin + f[5]*fCos;
634 set( fn );
637 void Matrix3::translate( double tx, double ty )
639 f[4] += tx;
640 f[5] += ty;
643 void Matrix3::invert()
645 // short circuit trivial cases
646 if( f[1]==f[2] && f[1]==0.0 && f[0]==f[3] && f[0]==1.0 )
648 f[4] = -f[4];
649 f[5] = -f[5];
650 return;
653 // check determinant
654 const double fDet = f[0]*f[3]-f[1]*f[2];
655 if( fDet == 0.0 )
656 return;
658 // invert the matrix
659 double fn[6];
660 fn[0] = +f[3] / fDet;
661 fn[1] = -f[1] / fDet;
662 fn[2] = -f[2] / fDet;
663 fn[3] = +f[0] / fDet;
665 // apply inversion to translation
666 fn[4] = -(f[4]*fn[0] + f[5]*fn[2]);
667 fn[5] = -(f[4]*fn[1] + f[5]*fn[3]);
669 set( fn );
672 void Matrix3::append( PDFWriterImpl::PDFPage const & rPage, OStringBuffer& rBuffer )
674 appendDouble( f[0], rBuffer );
675 rBuffer.append( ' ' );
676 appendDouble( f[1], rBuffer );
677 rBuffer.append( ' ' );
678 appendDouble( f[2], rBuffer );
679 rBuffer.append( ' ' );
680 appendDouble( f[3], rBuffer );
681 rBuffer.append( ' ' );
682 rPage.appendPoint( Point( static_cast<long>(f[4]), static_cast<long>(f[5]) ), rBuffer );
685 static void appendResourceMap( OStringBuffer& rBuf, const char* pPrefix, const PDFWriterImpl::ResourceMap& rList )
687 if( rList.empty() )
688 return;
689 rBuf.append( '/' );
690 rBuf.append( pPrefix );
691 rBuf.append( "<<" );
692 int ni = 0;
693 for (auto const& item : rList)
695 if( !item.first.isEmpty() && item.second > 0 )
697 rBuf.append( '/' );
698 rBuf.append( item.first );
699 rBuf.append( ' ' );
700 rBuf.append( item.second );
701 rBuf.append( " 0 R" );
702 if( ((++ni) & 7) == 0 )
703 rBuf.append( '\n' );
706 rBuf.append( ">>\n" );
709 void PDFWriterImpl::ResourceDict::append( OStringBuffer& rBuf, sal_Int32 nFontDictObject )
711 rBuf.append( "<</Font " );
712 rBuf.append( nFontDictObject );
713 rBuf.append( " 0 R\n" );
714 appendResourceMap( rBuf, "XObject", m_aXObjects );
715 appendResourceMap( rBuf, "ExtGState", m_aExtGStates );
716 appendResourceMap( rBuf, "Shading", m_aShadings );
717 appendResourceMap( rBuf, "Pattern", m_aPatterns );
718 rBuf.append( "/ProcSet[/PDF/Text" );
719 if( !m_aXObjects.empty() )
720 rBuf.append( "/ImageC/ImageI/ImageB" );
721 rBuf.append( "]\n>>\n" );
724 PDFWriterImpl::PDFPage::PDFPage( PDFWriterImpl* pWriter, double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
726 m_pWriter( pWriter ),
727 m_nPageWidth( nPageWidth ),
728 m_nPageHeight( nPageHeight ),
729 m_eOrientation( eOrientation ),
730 m_nPageObject( 0 ), // invalid object number
731 m_nStreamLengthObject( 0 ),
732 m_nBeginStreamPos( 0 ),
733 m_eTransition( PDFWriter::PageTransition::Regular ),
734 m_nTransTime( 0 )
736 // object ref must be only ever updated in emit()
737 m_nPageObject = m_pWriter->createObject();
740 PDFWriterImpl::PDFPage::~PDFPage()
744 void PDFWriterImpl::PDFPage::beginStream()
746 if (g_bDebugDisableCompression)
748 m_pWriter->emitComment("PDFWriterImpl::PDFPage::beginStream, +");
750 m_aStreamObjects.push_back(m_pWriter->createObject());
751 if( ! m_pWriter->updateObject( m_aStreamObjects.back() ) )
752 return;
754 m_nStreamLengthObject = m_pWriter->createObject();
755 // write content stream header
756 OStringBuffer aLine;
757 aLine.append( m_aStreamObjects.back() );
758 aLine.append( " 0 obj\n<</Length " );
759 aLine.append( m_nStreamLengthObject );
760 aLine.append( " 0 R" );
761 if (!g_bDebugDisableCompression)
762 aLine.append( "/Filter/FlateDecode" );
763 aLine.append( ">>\nstream\n" );
764 if( ! m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() ) )
765 return;
766 if (osl::File::E_None != m_pWriter->m_aFile.getPos(m_nBeginStreamPos))
768 m_pWriter->m_aFile.close();
769 m_pWriter->m_bOpen = false;
771 if (!g_bDebugDisableCompression)
772 m_pWriter->beginCompression();
773 m_pWriter->checkAndEnableStreamEncryption( m_aStreamObjects.back() );
776 void PDFWriterImpl::PDFPage::endStream()
778 if (!g_bDebugDisableCompression)
779 m_pWriter->endCompression();
780 sal_uInt64 nEndStreamPos;
781 if (osl::File::E_None != m_pWriter->m_aFile.getPos(nEndStreamPos))
783 m_pWriter->m_aFile.close();
784 m_pWriter->m_bOpen = false;
785 return;
787 m_pWriter->disableStreamEncryption();
788 if( ! m_pWriter->writeBuffer( "\nendstream\nendobj\n\n", 19 ) )
789 return;
790 // emit stream length object
791 if( ! m_pWriter->updateObject( m_nStreamLengthObject ) )
792 return;
793 OString aLine =
794 OString::number( m_nStreamLengthObject ) +
795 " 0 obj\n" +
796 OString::number( static_cast<sal_Int64>(nEndStreamPos-m_nBeginStreamPos) ) +
797 "\nendobj\n\n";
798 m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() );
801 bool PDFWriterImpl::PDFPage::emit(sal_Int32 nParentObject )
803 // emit page object
804 if( ! m_pWriter->updateObject( m_nPageObject ) )
805 return false;
806 OStringBuffer aLine;
808 aLine.append( m_nPageObject );
809 aLine.append( " 0 obj\n"
810 "<</Type/Page/Parent " );
811 aLine.append( nParentObject );
812 aLine.append( " 0 R" );
813 aLine.append( "/Resources " );
814 aLine.append( m_pWriter->getResourceDictObj() );
815 aLine.append( " 0 R" );
816 if( m_nPageWidth && m_nPageHeight )
818 aLine.append( "/MediaBox[0 0 " );
819 aLine.append( m_nPageWidth );
820 aLine.append( ' ' );
821 aLine.append( m_nPageHeight );
822 aLine.append( "]" );
824 switch( m_eOrientation )
826 case PDFWriter::Orientation::Portrait: aLine.append( "/Rotate 0\n" );break;
827 case PDFWriter::Orientation::Inherit: break;
829 int nAnnots = m_aAnnotations.size();
830 if( nAnnots > 0 )
832 aLine.append( "/Annots[\n" );
833 for( int i = 0; i < nAnnots; i++ )
835 aLine.append( m_aAnnotations[i] );
836 aLine.append( " 0 R" );
837 aLine.append( ((i+1)%15) ? " " : "\n" );
839 aLine.append( "]\n" );
841 if( !m_aMCIDParents.empty() )
843 OStringBuffer aStructParents( 1024 );
844 aStructParents.append( "[ " );
845 int nParents = m_aMCIDParents.size();
846 for( int i = 0; i < nParents; i++ )
848 aStructParents.append( m_aMCIDParents[i] );
849 aStructParents.append( " 0 R" );
850 aStructParents.append( ((i%10) == 9) ? "\n" : " " );
852 aStructParents.append( "]" );
853 m_pWriter->m_aStructParentTree.push_back( aStructParents.makeStringAndClear() );
855 aLine.append( "/StructParents " );
856 aLine.append( sal_Int32(m_pWriter->m_aStructParentTree.size()-1) );
857 aLine.append( "\n" );
859 if( m_eTransition != PDFWriter::PageTransition::Regular && m_nTransTime > 0 )
861 // transition duration
862 aLine.append( "/Trans<</D " );
863 appendDouble( static_cast<double>(m_nTransTime)/1000.0, aLine, 3 );
864 aLine.append( "\n" );
865 const char *pStyle = nullptr, *pDm = nullptr, *pM = nullptr, *pDi = nullptr;
866 switch( m_eTransition )
868 case PDFWriter::PageTransition::SplitHorizontalInward:
869 pStyle = "Split"; pDm = "H"; pM = "I"; break;
870 case PDFWriter::PageTransition::SplitHorizontalOutward:
871 pStyle = "Split"; pDm = "H"; pM = "O"; break;
872 case PDFWriter::PageTransition::SplitVerticalInward:
873 pStyle = "Split"; pDm = "V"; pM = "I"; break;
874 case PDFWriter::PageTransition::SplitVerticalOutward:
875 pStyle = "Split"; pDm = "V"; pM = "O"; break;
876 case PDFWriter::PageTransition::BlindsHorizontal:
877 pStyle = "Blinds"; pDm = "H"; break;
878 case PDFWriter::PageTransition::BlindsVertical:
879 pStyle = "Blinds"; pDm = "V"; break;
880 case PDFWriter::PageTransition::BoxInward:
881 pStyle = "Box"; pM = "I"; break;
882 case PDFWriter::PageTransition::BoxOutward:
883 pStyle = "Box"; pM = "O"; break;
884 case PDFWriter::PageTransition::WipeLeftToRight:
885 pStyle = "Wipe"; pDi = "0"; break;
886 case PDFWriter::PageTransition::WipeBottomToTop:
887 pStyle = "Wipe"; pDi = "90"; break;
888 case PDFWriter::PageTransition::WipeRightToLeft:
889 pStyle = "Wipe"; pDi = "180"; break;
890 case PDFWriter::PageTransition::WipeTopToBottom:
891 pStyle = "Wipe"; pDi = "270"; break;
892 case PDFWriter::PageTransition::Dissolve:
893 pStyle = "Dissolve"; break;
894 case PDFWriter::PageTransition::Regular:
895 break;
897 // transition style
898 if( pStyle )
900 aLine.append( "/S/" );
901 aLine.append( pStyle );
902 aLine.append( "\n" );
904 if( pDm )
906 aLine.append( "/Dm/" );
907 aLine.append( pDm );
908 aLine.append( "\n" );
910 if( pM )
912 aLine.append( "/M/" );
913 aLine.append( pM );
914 aLine.append( "\n" );
916 if( pDi )
918 aLine.append( "/Di " );
919 aLine.append( pDi );
920 aLine.append( "\n" );
922 aLine.append( ">>\n" );
924 if( m_pWriter->getVersion() > PDFWriter::PDFVersion::PDF_1_3 && ! m_pWriter->m_bIsPDF_A1 )
926 aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/I true>>" );
928 aLine.append( "/Contents" );
929 unsigned int nStreamObjects = m_aStreamObjects.size();
930 if( nStreamObjects > 1 )
931 aLine.append( '[' );
932 for(sal_Int32 i : m_aStreamObjects)
934 aLine.append( ' ' );
935 aLine.append( i );
936 aLine.append( " 0 R" );
938 if( nStreamObjects > 1 )
939 aLine.append( ']' );
940 aLine.append( ">>\nendobj\n\n" );
941 return m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() );
944 namespace vcl
946 template < class GEOMETRY >
947 static GEOMETRY lcl_convert( const MapMode& _rSource, const MapMode& _rDest, OutputDevice* _pPixelConversion, const GEOMETRY& _rObject )
949 GEOMETRY aPoint;
950 if ( MapUnit::MapPixel == _rSource.GetMapUnit() )
952 aPoint = _pPixelConversion->PixelToLogic( _rObject, _rDest );
954 else
956 aPoint = OutputDevice::LogicToLogic( _rObject, _rSource, _rDest );
958 return aPoint;
962 void PDFWriterImpl::PDFPage::appendPoint( const Point& rPoint, OStringBuffer& rBuffer ) const
964 Point aPoint( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
965 m_pWriter->m_aMapMode,
966 m_pWriter,
967 rPoint ) );
969 sal_Int32 nValue = aPoint.X();
971 appendFixedInt( nValue, rBuffer );
973 rBuffer.append( ' ' );
975 nValue = pointToPixel(getHeight()) - aPoint.Y();
977 appendFixedInt( nValue, rBuffer );
980 void PDFWriterImpl::PDFPage::appendPixelPoint( const basegfx::B2DPoint& rPoint, OStringBuffer& rBuffer ) const
982 double fValue = pixelToPoint(rPoint.getX());
984 appendDouble( fValue, rBuffer, nLog10Divisor );
985 rBuffer.append( ' ' );
986 fValue = getHeight() - pixelToPoint(rPoint.getY());
987 appendDouble( fValue, rBuffer, nLog10Divisor );
990 void PDFWriterImpl::PDFPage::appendRect( const tools::Rectangle& rRect, OStringBuffer& rBuffer ) const
992 appendPoint( rRect.BottomLeft() + Point( 0, 1 ), rBuffer );
993 rBuffer.append( ' ' );
994 appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), rBuffer, false );
995 rBuffer.append( ' ' );
996 appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), rBuffer );
997 rBuffer.append( " re" );
1000 void PDFWriterImpl::PDFPage::convertRect( tools::Rectangle& rRect ) const
1002 Point aLL = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1003 m_pWriter->m_aMapMode,
1004 m_pWriter,
1005 rRect.BottomLeft() + Point( 0, 1 )
1007 Size aSize = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1008 m_pWriter->m_aMapMode,
1009 m_pWriter,
1010 rRect.GetSize() );
1011 rRect.SetLeft( aLL.X() );
1012 rRect.SetRight( aLL.X() + aSize.Width() );
1013 rRect.SetTop( pointToPixel(getHeight()) - aLL.Y() );
1014 rRect.SetBottom( rRect.Top() + aSize.Height() );
1017 void PDFWriterImpl::PDFPage::appendPolygon( const tools::Polygon& rPoly, OStringBuffer& rBuffer, bool bClose ) const
1019 sal_uInt16 nPoints = rPoly.GetSize();
1021 * #108582# applications do weird things
1023 sal_uInt32 nBufLen = rBuffer.getLength();
1024 if( nPoints > 0 )
1026 const PolyFlags* pFlagArray = rPoly.GetConstFlagAry();
1027 appendPoint( rPoly[0], rBuffer );
1028 rBuffer.append( " m\n" );
1029 for( sal_uInt16 i = 1; i < nPoints; i++ )
1031 if( pFlagArray && pFlagArray[i] == PolyFlags::Control && nPoints-i > 2 )
1033 // bezier
1034 SAL_WARN_IF( pFlagArray[i+1] != PolyFlags::Control || pFlagArray[i+2] == PolyFlags::Control, "vcl.pdfwriter", "unexpected sequence of control points" );
1035 appendPoint( rPoly[i], rBuffer );
1036 rBuffer.append( " " );
1037 appendPoint( rPoly[i+1], rBuffer );
1038 rBuffer.append( " " );
1039 appendPoint( rPoly[i+2], rBuffer );
1040 rBuffer.append( " c" );
1041 i += 2; // add additionally consumed points
1043 else
1045 // line
1046 appendPoint( rPoly[i], rBuffer );
1047 rBuffer.append( " l" );
1049 if( (rBuffer.getLength() - nBufLen) > 65 )
1051 rBuffer.append( "\n" );
1052 nBufLen = rBuffer.getLength();
1054 else
1055 rBuffer.append( " " );
1057 if( bClose )
1058 rBuffer.append( "h\n" );
1062 void PDFWriterImpl::PDFPage::appendPolygon( const basegfx::B2DPolygon& rPoly, OStringBuffer& rBuffer ) const
1064 basegfx::B2DPolygon aPoly( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1065 m_pWriter->m_aMapMode,
1066 m_pWriter,
1067 rPoly ) );
1069 if( basegfx::utils::isRectangle( aPoly ) )
1071 basegfx::B2DRange aRange( aPoly.getB2DRange() );
1072 basegfx::B2DPoint aBL( aRange.getMinX(), aRange.getMaxY() );
1073 appendPixelPoint( aBL, rBuffer );
1074 rBuffer.append( ' ' );
1075 appendMappedLength( aRange.getWidth(), rBuffer, false, nLog10Divisor );
1076 rBuffer.append( ' ' );
1077 appendMappedLength( aRange.getHeight(), rBuffer, true, nLog10Divisor );
1078 rBuffer.append( " re\n" );
1079 return;
1081 sal_uInt32 nPoints = aPoly.count();
1082 if( nPoints > 0 )
1084 sal_uInt32 nBufLen = rBuffer.getLength();
1085 basegfx::B2DPoint aLastPoint( aPoly.getB2DPoint( 0 ) );
1086 appendPixelPoint( aLastPoint, rBuffer );
1087 rBuffer.append( " m\n" );
1088 for( sal_uInt32 i = 1; i <= nPoints; i++ )
1090 if( i != nPoints || aPoly.isClosed() )
1092 sal_uInt32 nCurPoint = i % nPoints;
1093 sal_uInt32 nLastPoint = i-1;
1094 basegfx::B2DPoint aPoint( aPoly.getB2DPoint( nCurPoint ) );
1095 if( aPoly.isNextControlPointUsed( nLastPoint ) &&
1096 aPoly.isPrevControlPointUsed( nCurPoint ) )
1098 appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer );
1099 rBuffer.append( ' ' );
1100 appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer );
1101 rBuffer.append( ' ' );
1102 appendPixelPoint( aPoint, rBuffer );
1103 rBuffer.append( " c" );
1105 else if( aPoly.isNextControlPointUsed( nLastPoint ) )
1107 appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer );
1108 rBuffer.append( ' ' );
1109 appendPixelPoint( aPoint, rBuffer );
1110 rBuffer.append( " y" );
1112 else if( aPoly.isPrevControlPointUsed( nCurPoint ) )
1114 appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer );
1115 rBuffer.append( ' ' );
1116 appendPixelPoint( aPoint, rBuffer );
1117 rBuffer.append( " v" );
1119 else
1121 appendPixelPoint( aPoint, rBuffer );
1122 rBuffer.append( " l" );
1124 if( (rBuffer.getLength() - nBufLen) > 65 )
1126 rBuffer.append( "\n" );
1127 nBufLen = rBuffer.getLength();
1129 else
1130 rBuffer.append( " " );
1133 rBuffer.append( "h\n" );
1137 void PDFWriterImpl::PDFPage::appendPolyPolygon( const tools::PolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
1139 sal_uInt16 nPolygons = rPolyPoly.Count();
1140 for( sal_uInt16 n = 0; n < nPolygons; n++ )
1141 appendPolygon( rPolyPoly[n], rBuffer );
1144 void PDFWriterImpl::PDFPage::appendPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
1146 for(auto const& rPolygon : rPolyPoly)
1147 appendPolygon( rPolygon, rBuffer );
1150 void PDFWriterImpl::PDFPage::appendMappedLength( sal_Int32 nLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32* pOutLength ) const
1152 sal_Int32 nValue = nLength;
1153 if ( nLength < 0 )
1155 rBuffer.append( '-' );
1156 nValue = -nLength;
1158 Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1159 m_pWriter->m_aMapMode,
1160 m_pWriter,
1161 Size( nValue, nValue ) ) );
1162 nValue = bVertical ? aSize.Height() : aSize.Width();
1163 if( pOutLength )
1164 *pOutLength = ((nLength < 0 ) ? -nValue : nValue);
1166 appendFixedInt( nValue, rBuffer );
1169 void PDFWriterImpl::PDFPage::appendMappedLength( double fLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32 nPrecision ) const
1171 Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1172 m_pWriter->m_aMapMode,
1173 m_pWriter,
1174 Size( 1000, 1000 ) ) );
1175 fLength *= pixelToPoint(static_cast<double>(bVertical ? aSize.Height() : aSize.Width()) / 1000.0);
1176 appendDouble( fLength, rBuffer, nPrecision );
1179 bool PDFWriterImpl::PDFPage::appendLineInfo( const LineInfo& rInfo, OStringBuffer& rBuffer ) const
1181 if(LineStyle::Dash == rInfo.GetStyle() && rInfo.GetDashLen() != rInfo.GetDotLen())
1183 // dashed and non-degraded case, check for implementation limits of dash array
1184 // in PDF reader apps (e.g. acroread)
1185 if(2 * (rInfo.GetDashCount() + rInfo.GetDotCount()) > 10)
1187 return false;
1191 if(basegfx::B2DLineJoin::NONE != rInfo.GetLineJoin())
1193 // LineJoin used, ExtLineInfo required
1194 return false;
1197 if(css::drawing::LineCap_BUTT != rInfo.GetLineCap())
1199 // LineCap used, ExtLineInfo required
1200 return false;
1203 if( rInfo.GetStyle() == LineStyle::Dash )
1205 rBuffer.append( "[ " );
1206 if( rInfo.GetDashLen() == rInfo.GetDotLen() ) // degraded case
1208 appendMappedLength( rInfo.GetDashLen(), rBuffer );
1209 rBuffer.append( ' ' );
1210 appendMappedLength( rInfo.GetDistance(), rBuffer );
1211 rBuffer.append( ' ' );
1213 else
1215 for( int n = 0; n < rInfo.GetDashCount(); n++ )
1217 appendMappedLength( rInfo.GetDashLen(), rBuffer );
1218 rBuffer.append( ' ' );
1219 appendMappedLength( rInfo.GetDistance(), rBuffer );
1220 rBuffer.append( ' ' );
1222 for( int m = 0; m < rInfo.GetDotCount(); m++ )
1224 appendMappedLength( rInfo.GetDotLen(), rBuffer );
1225 rBuffer.append( ' ' );
1226 appendMappedLength( rInfo.GetDistance(), rBuffer );
1227 rBuffer.append( ' ' );
1230 rBuffer.append( "] 0 d\n" );
1233 if( rInfo.GetWidth() > 1 )
1235 appendMappedLength( rInfo.GetWidth(), rBuffer );
1236 rBuffer.append( " w\n" );
1238 else if( rInfo.GetWidth() == 0 )
1240 // "pixel" line
1241 appendDouble( 72.0/double(m_pWriter->GetDPIX()), rBuffer );
1242 rBuffer.append( " w\n" );
1245 return true;
1248 void PDFWriterImpl::PDFPage::appendWaveLine( sal_Int32 nWidth, sal_Int32 nY, sal_Int32 nDelta, OStringBuffer& rBuffer ) const
1250 if( nWidth <= 0 )
1251 return;
1252 if( nDelta < 1 )
1253 nDelta = 1;
1255 rBuffer.append( "0 " );
1256 appendMappedLength( nY, rBuffer );
1257 rBuffer.append( " m\n" );
1258 for( sal_Int32 n = 0; n < nWidth; )
1260 n += nDelta;
1261 appendMappedLength( n, rBuffer, false );
1262 rBuffer.append( ' ' );
1263 appendMappedLength( nDelta+nY, rBuffer );
1264 rBuffer.append( ' ' );
1265 n += nDelta;
1266 appendMappedLength( n, rBuffer, false );
1267 rBuffer.append( ' ' );
1268 appendMappedLength( nY, rBuffer );
1269 rBuffer.append( " v " );
1270 if( n < nWidth )
1272 n += nDelta;
1273 appendMappedLength( n, rBuffer, false );
1274 rBuffer.append( ' ' );
1275 appendMappedLength( nY-nDelta, rBuffer );
1276 rBuffer.append( ' ' );
1277 n += nDelta;
1278 appendMappedLength( n, rBuffer, false );
1279 rBuffer.append( ' ' );
1280 appendMappedLength( nY, rBuffer );
1281 rBuffer.append( " v\n" );
1284 rBuffer.append( "S\n" );
1287 PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext,
1288 const css::uno::Reference< css::beans::XMaterialHolder >& xEnc,
1289 PDFWriter& i_rOuterFace)
1290 : VirtualDevice(Application::GetDefaultDevice(), DeviceFormat::DEFAULT, DeviceFormat::NONE, OUTDEV_PDF),
1291 m_aMapMode( MapUnit::MapPoint, Point(), Fraction( 1, pointToPixel(1) ), Fraction( 1, pointToPixel(1) ) ),
1292 m_nCurrentStructElement( 0 ),
1293 m_bEmitStructure( true ),
1294 m_nNextFID( 1 ),
1295 m_nCurrentPage( -1 ),
1296 m_nCatalogObject(0),
1297 m_nSignatureObject( -1 ),
1298 m_nSignatureContentOffset( 0 ),
1299 m_nSignatureLastByteRangeNoOffset( 0 ),
1300 m_nResourceDict( -1 ),
1301 m_nFontDictObject( -1 ),
1302 m_aContext(rContext),
1303 m_aFile(m_aContext.URL),
1304 m_bOpen(false),
1305 m_DocDigest(::comphelper::HashType::MD5),
1306 m_aCipher( nullptr ),
1307 m_nKeyLength(0),
1308 m_nRC4KeyLength(0),
1309 m_bEncryptThisStream( false ),
1310 m_nAccessPermissions(0),
1311 m_bIsPDF_A1( false ),
1312 m_bIsPDF_A2( false ),
1313 m_rOuterFace( i_rOuterFace )
1315 m_aStructure.emplace_back( );
1316 m_aStructure[0].m_nOwnElement = 0;
1317 m_aStructure[0].m_nParentElement = 0;
1319 Font aFont;
1320 aFont.SetFamilyName( "Times" );
1321 aFont.SetFontSize( Size( 0, 12 ) );
1323 GraphicsState aState;
1324 aState.m_aMapMode = m_aMapMode;
1325 aState.m_aFont = aFont;
1326 m_aGraphicsStack.push_front( aState );
1328 osl::File::RC aError = m_aFile.open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create);
1329 if (aError != osl::File::E_None)
1331 if (aError == osl::File::E_EXIST)
1333 aError = m_aFile.open(osl_File_OpenFlag_Write);
1334 if (aError == osl::File::E_None)
1335 aError = m_aFile.setSize(0);
1338 if (aError != osl::File::E_None)
1339 return;
1341 m_bOpen = true;
1343 // setup DocInfo
1344 setupDocInfo();
1346 /* prepare the cypher engine, can be done in CTOR, free in DTOR */
1347 m_aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream );
1349 if( xEnc.is() )
1350 prepareEncryption( xEnc );
1352 if( m_aContext.Encryption.Encrypt() )
1354 // sanity check
1355 if( m_aContext.Encryption.OValue.size() != ENCRYPTED_PWD_SIZE ||
1356 m_aContext.Encryption.UValue.size() != ENCRYPTED_PWD_SIZE ||
1357 m_aContext.Encryption.EncryptionKey.size() != MAXIMUM_RC4_KEY_LENGTH
1360 // the field lengths are invalid ? This was not setup by initEncryption.
1361 // do not encrypt after all
1362 m_aContext.Encryption.OValue.clear();
1363 m_aContext.Encryption.UValue.clear();
1364 OSL_ENSURE( false, "encryption data failed sanity check, encryption disabled" );
1366 else // setup key lengths
1367 m_nAccessPermissions = computeAccessPermissions( m_aContext.Encryption, m_nKeyLength, m_nRC4KeyLength );
1370 // write header
1371 OStringBuffer aBuffer( 20 );
1372 aBuffer.append( "%PDF-" );
1373 switch( m_aContext.Version )
1375 case PDFWriter::PDFVersion::PDF_1_2: aBuffer.append( "1.2" );break;
1376 case PDFWriter::PDFVersion::PDF_1_3: aBuffer.append( "1.3" );break;
1377 case PDFWriter::PDFVersion::PDF_A_1:
1378 case PDFWriter::PDFVersion::PDF_1_4: aBuffer.append( "1.4" );break;
1379 default:
1380 case PDFWriter::PDFVersion::PDF_1_5: aBuffer.append( "1.5" );break;
1381 case PDFWriter::PDFVersion::PDF_1_6: aBuffer.append( "1.6" );break;
1383 // append something binary as comment (suggested in PDF Reference)
1384 aBuffer.append( "\n%\303\244\303\274\303\266\303\237\n" );
1385 if( !writeBuffer( aBuffer.getStr(), aBuffer.getLength() ) )
1387 m_aFile.close();
1388 m_bOpen = false;
1389 return;
1392 // insert outline root
1393 m_aOutline.emplace_back( );
1395 m_bIsPDF_A1 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_1);
1396 if( m_bIsPDF_A1 )
1397 m_aContext.Version = PDFWriter::PDFVersion::PDF_1_4; //meaning we need PDF 1.4, PDF/A flavour
1399 m_bIsPDF_A2 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_2);
1400 if( m_bIsPDF_A2 )
1401 m_aContext.Version = PDFWriter::PDFVersion::PDF_1_6; //we could even use 1.7 features
1403 if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 )
1404 SetReferenceDevice( VirtualDevice::RefDevMode::PDF1 );
1405 else
1406 SetReferenceDevice( m_aContext.DPIx, m_aContext.DPIy );
1408 SetOutputSizePixel( Size( 640, 480 ) );
1409 SetMapMode(MapMode(MapUnit::MapMM));
1412 PDFWriterImpl::~PDFWriterImpl()
1414 disposeOnce();
1417 void PDFWriterImpl::dispose()
1419 if( m_aCipher )
1420 rtl_cipher_destroyARCFOUR( m_aCipher );
1421 m_aPages.clear();
1422 VirtualDevice::dispose();
1425 void PDFWriterImpl::setupDocInfo()
1427 std::vector< sal_uInt8 > aId;
1428 m_aCreationDateString = PDFWriter::GetDateTime();
1429 computeDocumentIdentifier( aId, m_aContext.DocumentInfo, m_aCreationDateString, m_aCreationMetaDateString );
1430 if( m_aContext.Encryption.DocumentIdentifier.empty() )
1431 m_aContext.Encryption.DocumentIdentifier = aId;
1434 OString PDFWriter::GetDateTime()
1436 OStringBuffer aRet;
1438 TimeValue aTVal, aGMT;
1439 oslDateTime aDT;
1440 osl_getSystemTime(&aGMT);
1441 osl_getLocalTimeFromSystemTime(&aGMT, &aTVal);
1442 osl_getDateTimeFromTimeValue(&aTVal, &aDT);
1443 aRet.append("D:");
1444 aRet.append(static_cast<sal_Char>('0' + ((aDT.Year / 1000) % 10)));
1445 aRet.append(static_cast<sal_Char>('0' + ((aDT.Year / 100) % 10)));
1446 aRet.append(static_cast<sal_Char>('0' + ((aDT.Year / 10) % 10)));
1447 aRet.append(static_cast<sal_Char>('0' + (aDT.Year % 10)));
1448 aRet.append(static_cast<sal_Char>('0' + ((aDT.Month / 10) % 10)));
1449 aRet.append(static_cast<sal_Char>('0' + (aDT.Month % 10)));
1450 aRet.append(static_cast<sal_Char>('0' + ((aDT.Day / 10) % 10)));
1451 aRet.append(static_cast<sal_Char>('0' + (aDT.Day % 10)));
1452 aRet.append(static_cast<sal_Char>('0' + ((aDT.Hours / 10) % 10)));
1453 aRet.append(static_cast<sal_Char>('0' + (aDT.Hours % 10)));
1454 aRet.append(static_cast<sal_Char>('0' + ((aDT.Minutes / 10) % 10)));
1455 aRet.append(static_cast<sal_Char>('0' + (aDT.Minutes % 10)));
1456 aRet.append(static_cast<sal_Char>('0' + ((aDT.Seconds / 10) % 10)));
1457 aRet.append(static_cast<sal_Char>('0' + (aDT.Seconds % 10)));
1459 sal_uInt32 nDelta = 0;
1460 if (aGMT.Seconds > aTVal.Seconds)
1462 aRet.append("-");
1463 nDelta = aGMT.Seconds-aTVal.Seconds;
1465 else if (aGMT.Seconds < aTVal.Seconds)
1467 aRet.append("+");
1468 nDelta = aTVal.Seconds-aGMT.Seconds;
1470 else
1471 aRet.append("Z");
1473 if (nDelta)
1475 aRet.append(static_cast<sal_Char>('0' + ((nDelta / 36000) % 10)));
1476 aRet.append(static_cast<sal_Char>('0' + ((nDelta / 3600) % 10)));
1477 aRet.append("'");
1478 aRet.append(static_cast<sal_Char>('0' + ((nDelta / 600) % 6)));
1479 aRet.append(static_cast<sal_Char>('0' + ((nDelta / 60) % 10)));
1481 aRet.append( "'" );
1483 return aRet.makeStringAndClear();
1486 void PDFWriterImpl::computeDocumentIdentifier( std::vector< sal_uInt8 >& o_rIdentifier,
1487 const vcl::PDFWriter::PDFDocInfo& i_rDocInfo,
1488 const OString& i_rCString1,
1489 OString& o_rCString2
1492 o_rIdentifier.clear();
1494 //build the document id
1495 OString aInfoValuesOut;
1496 OStringBuffer aID( 1024 );
1497 if( !i_rDocInfo.Title.isEmpty() )
1498 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Title, aID);
1499 if( !i_rDocInfo.Author.isEmpty() )
1500 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Author, aID);
1501 if( !i_rDocInfo.Subject.isEmpty() )
1502 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Subject, aID);
1503 if( !i_rDocInfo.Keywords.isEmpty() )
1504 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Keywords, aID);
1505 if( !i_rDocInfo.Creator.isEmpty() )
1506 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Creator, aID);
1507 if( !i_rDocInfo.Producer.isEmpty() )
1508 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Producer, aID);
1510 TimeValue aTVal, aGMT;
1511 oslDateTime aDT;
1512 osl_getSystemTime( &aGMT );
1513 osl_getLocalTimeFromSystemTime( &aGMT, &aTVal );
1514 osl_getDateTimeFromTimeValue( &aTVal, &aDT );
1515 OStringBuffer aCreationMetaDateString(64);
1517 // i59651: we fill the Metadata date string as well, if PDF/A is requested
1518 // according to ISO 19005-1:2005 6.7.3 the date is corrected for
1519 // local time zone offset UTC only, whereas Acrobat 8 seems
1520 // to use the localtime notation only
1521 // according to a recommendation in XMP Specification (Jan 2004, page 75)
1522 // the Acrobat way seems the right approach
1523 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Year/1000)%10)) );
1524 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Year/100)%10)) );
1525 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Year/10)%10)) );
1526 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Year)%10)) );
1527 aCreationMetaDateString.append( "-" );
1528 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Month/10)%10)) );
1529 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Month)%10)) );
1530 aCreationMetaDateString.append( "-" );
1531 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Day/10)%10)) );
1532 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Day)%10)) );
1533 aCreationMetaDateString.append( "T" );
1534 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Hours/10)%10)) );
1535 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Hours)%10)) );
1536 aCreationMetaDateString.append( ":" );
1537 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Minutes/10)%10)) );
1538 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Minutes)%10)) );
1539 aCreationMetaDateString.append( ":" );
1540 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Seconds/10)%10)) );
1541 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Seconds)%10)) );
1543 sal_uInt32 nDelta = 0;
1544 if( aGMT.Seconds > aTVal.Seconds )
1546 nDelta = aGMT.Seconds-aTVal.Seconds;
1547 aCreationMetaDateString.append( "-" );
1549 else if( aGMT.Seconds < aTVal.Seconds )
1551 nDelta = aTVal.Seconds-aGMT.Seconds;
1552 aCreationMetaDateString.append( "+" );
1554 else
1556 aCreationMetaDateString.append( "Z" );
1559 if( nDelta )
1561 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((nDelta/36000)%10)) );
1562 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((nDelta/3600)%10)) );
1563 aCreationMetaDateString.append( ":" );
1564 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((nDelta/600)%6)) );
1565 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((nDelta/60)%10)) );
1567 aID.append( i_rCString1.getStr(), i_rCString1.getLength() );
1569 aInfoValuesOut = aID.makeStringAndClear();
1570 o_rCString2 = aCreationMetaDateString.makeStringAndClear();
1572 ::comphelper::Hash aDigest(::comphelper::HashType::MD5);
1573 aDigest.update(reinterpret_cast<unsigned char const*>(&aGMT), sizeof(aGMT));
1574 aDigest.update(reinterpret_cast<unsigned char const*>(aInfoValuesOut.getStr()), aInfoValuesOut.getLength());
1575 //the binary form of the doc id is needed for encryption stuff
1576 o_rIdentifier = aDigest.finalize();
1579 /* i12626 methods */
1581 check if the Unicode string must be encrypted or not, perform the requested task,
1582 append the string as unicode hex, encrypted if needed
1584 inline void PDFWriterImpl::appendUnicodeTextStringEncrypt( const OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
1586 rOutBuffer.append( "<" );
1587 if( m_aContext.Encryption.Encrypt() )
1589 const sal_Unicode* pStr = rInString.getStr();
1590 sal_Int32 nLen = rInString.getLength();
1591 //prepare a unicode string, encrypt it
1592 enableStringEncryption( nInObjectNumber );
1593 sal_uInt8 *pCopy = m_vEncryptionBuffer.data();
1594 sal_Int32 nChars = 2 + (nLen * 2);
1595 m_vEncryptionBuffer.resize(nChars);
1596 *pCopy++ = 0xFE;
1597 *pCopy++ = 0xFF;
1598 // we need to prepare a byte stream from the unicode string buffer
1599 for( int i = 0; i < nLen; i++ )
1601 sal_Unicode aUnChar = pStr[i];
1602 *pCopy++ = static_cast<sal_uInt8>( aUnChar >> 8 );
1603 *pCopy++ = static_cast<sal_uInt8>( aUnChar & 255 );
1605 //encrypt in place
1606 rtl_cipher_encodeARCFOUR( m_aCipher, m_vEncryptionBuffer.data(), nChars, m_vEncryptionBuffer.data(), nChars );
1607 //now append, hexadecimal (appendHex), the encrypted result
1608 for(int i = 0; i < nChars; i++)
1609 appendHex( m_vEncryptionBuffer[i], rOutBuffer );
1611 else
1612 PDFWriter::AppendUnicodeTextString(rInString, rOutBuffer);
1613 rOutBuffer.append( ">" );
1616 inline void PDFWriterImpl::appendLiteralStringEncrypt( OStringBuffer const & rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
1618 rOutBuffer.append( "(" );
1619 sal_Int32 nChars = rInString.getLength();
1620 //check for encryption, if ok, encrypt the string, then convert with appndLiteralString
1621 if( m_aContext.Encryption.Encrypt() )
1623 m_vEncryptionBuffer.resize(nChars);
1624 //encrypt the string in a buffer, then append it
1625 enableStringEncryption( nInObjectNumber );
1626 rtl_cipher_encodeARCFOUR( m_aCipher, rInString.getStr(), nChars, m_vEncryptionBuffer.data(), nChars );
1627 appendLiteralString( reinterpret_cast<sal_Char*>(m_vEncryptionBuffer.data()), nChars, rOutBuffer );
1629 else
1630 appendLiteralString( rInString.getStr(), nChars , rOutBuffer );
1631 rOutBuffer.append( ")" );
1634 inline void PDFWriterImpl::appendLiteralStringEncrypt( const OString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
1636 OStringBuffer aBufferString( rInString );
1637 appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer);
1640 void PDFWriterImpl::appendLiteralStringEncrypt( const OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer, rtl_TextEncoding nEnc )
1642 OString aBufferString( OUStringToOString( rInString, nEnc ) );
1643 sal_Int32 nLen = aBufferString.getLength();
1644 OStringBuffer aBuf( nLen );
1645 const sal_Char* pT = aBufferString.getStr();
1647 for( sal_Int32 i = 0; i < nLen; i++, pT++ )
1649 if( (*pT & 0x80) == 0 )
1650 aBuf.append( *pT );
1651 else
1653 aBuf.append( '<' );
1654 appendHex( *pT, aBuf );
1655 aBuf.append( '>' );
1658 aBufferString = aBuf.makeStringAndClear();
1659 appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer);
1662 /* end i12626 methods */
1664 void PDFWriterImpl::emitComment( const char* pComment )
1666 OString aLine = "% " + rtl::OStringView(pComment) + "\n";
1667 writeBuffer( aLine.getStr(), aLine.getLength() );
1670 bool PDFWriterImpl::compressStream( SvMemoryStream* pStream )
1672 if (!g_bDebugDisableCompression)
1674 sal_uLong nEndPos = pStream->TellEnd();
1675 pStream->Seek( STREAM_SEEK_TO_BEGIN );
1676 ZCodec aCodec( 0x4000, 0x4000 );
1677 SvMemoryStream aStream;
1678 aCodec.BeginCompression();
1679 aCodec.Write( aStream, static_cast<const sal_uInt8*>(pStream->GetData()), nEndPos );
1680 aCodec.EndCompression();
1681 nEndPos = aStream.Tell();
1682 pStream->Seek( STREAM_SEEK_TO_BEGIN );
1683 aStream.Seek( STREAM_SEEK_TO_BEGIN );
1684 pStream->SetStreamSize( nEndPos );
1685 pStream->WriteBytes( aStream.GetData(), nEndPos );
1686 return true;
1688 else
1689 return false;
1692 void PDFWriterImpl::beginCompression()
1694 if (!g_bDebugDisableCompression)
1696 m_pCodec = std::make_unique<ZCodec>( 0x4000, 0x4000 );
1697 m_pMemStream = std::make_unique<SvMemoryStream>();
1698 m_pCodec->BeginCompression();
1702 void PDFWriterImpl::endCompression()
1704 if (!g_bDebugDisableCompression && m_pCodec)
1706 m_pCodec->EndCompression();
1707 m_pCodec.reset();
1708 sal_uInt64 nLen = m_pMemStream->Tell();
1709 m_pMemStream->Seek( 0 );
1710 writeBuffer( m_pMemStream->GetData(), nLen );
1711 m_pMemStream.reset();
1715 bool PDFWriterImpl::writeBuffer( const void* pBuffer, sal_uInt64 nBytes )
1717 if( ! m_bOpen ) // we are already down the drain
1718 return false;
1720 if( ! nBytes ) // huh ?
1721 return true;
1723 if( !m_aOutputStreams.empty() )
1725 m_aOutputStreams.front().m_pStream->Seek( STREAM_SEEK_TO_END );
1726 m_aOutputStreams.front().m_pStream->WriteBytes(
1727 pBuffer, sal::static_int_cast<std::size_t>(nBytes));
1728 return true;
1731 sal_uInt64 nWritten;
1732 if( m_pCodec )
1734 m_pCodec->Write( *m_pMemStream, static_cast<const sal_uInt8*>(pBuffer), static_cast<sal_uLong>(nBytes) );
1735 nWritten = nBytes;
1737 else
1739 bool buffOK = true;
1740 if( m_bEncryptThisStream )
1742 /* implement the encryption part of the PDF spec encryption algorithm 3.1 */
1743 m_vEncryptionBuffer.resize(nBytes);
1744 if( buffOK )
1745 rtl_cipher_encodeARCFOUR( m_aCipher,
1746 pBuffer, static_cast<sal_Size>(nBytes),
1747 m_vEncryptionBuffer.data(), static_cast<sal_Size>(nBytes) );
1750 const void* pWriteBuffer = ( m_bEncryptThisStream && buffOK ) ? m_vEncryptionBuffer.data() : pBuffer;
1751 m_DocDigest.update(static_cast<unsigned char const*>(pWriteBuffer), static_cast<sal_uInt32>(nBytes));
1753 if (m_aFile.write(pWriteBuffer, nBytes, nWritten) != osl::File::E_None)
1754 nWritten = 0;
1756 if( nWritten != nBytes )
1758 m_aFile.close();
1759 m_bOpen = false;
1763 return nWritten == nBytes;
1766 void PDFWriterImpl::newPage( double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
1768 endPage();
1769 m_nCurrentPage = m_aPages.size();
1770 m_aPages.emplace_back(this, nPageWidth, nPageHeight, eOrientation );
1771 m_aPages.back().beginStream();
1773 // setup global graphics state
1774 // linewidth is "1 pixel" by default
1775 OStringBuffer aBuf( 16 );
1776 appendDouble( 72.0/double(GetDPIX()), aBuf );
1777 aBuf.append( " w\n" );
1778 writeBuffer( aBuf.getStr(), aBuf.getLength() );
1781 void PDFWriterImpl::endPage()
1783 if( m_aPages.empty() )
1784 return;
1786 // close eventual MC sequence
1787 endStructureElementMCSeq();
1789 // sanity check
1790 if( !m_aOutputStreams.empty() )
1792 OSL_FAIL( "redirection across pages !!!" );
1793 m_aOutputStreams.clear(); // leak !
1794 m_aMapMode.SetOrigin( Point() );
1797 m_aGraphicsStack.clear();
1798 m_aGraphicsStack.emplace_back( );
1800 // this should pop the PDF graphics stack if necessary
1801 updateGraphicsState();
1803 m_aPages.back().endStream();
1805 // reset the default font
1806 Font aFont;
1807 aFont.SetFamilyName( "Times" );
1808 aFont.SetFontSize( Size( 0, 12 ) );
1810 m_aCurrentPDFState = m_aGraphicsStack.front();
1811 m_aGraphicsStack.front().m_aFont = aFont;
1813 for (auto & bitmap : m_aBitmaps)
1815 if( ! bitmap.m_aBitmap.IsEmpty() )
1817 writeBitmapObject(bitmap);
1818 bitmap.m_aBitmap = BitmapEx();
1821 for (auto & jpeg : m_aJPGs)
1823 if( jpeg.m_pStream )
1825 writeJPG( jpeg );
1826 jpeg.m_pStream.reset();
1827 jpeg.m_aMask = Bitmap();
1830 for (auto & item : m_aTransparentObjects)
1832 if( item.m_pContentStream )
1834 writeTransparentObject(item);
1835 item.m_pContentStream.reset();
1841 sal_Int32 PDFWriterImpl::createObject()
1843 m_aObjects.push_back( ~0U );
1844 return m_aObjects.size();
1847 bool PDFWriterImpl::updateObject( sal_Int32 n )
1849 if( ! m_bOpen )
1850 return false;
1852 sal_uInt64 nOffset = ~0U;
1853 osl::File::RC aError = m_aFile.getPos(nOffset);
1854 SAL_WARN_IF( aError != osl::File::E_None, "vcl.pdfwriter", "could not register object" );
1855 if (aError != osl::File::E_None)
1857 m_aFile.close();
1858 m_bOpen = false;
1860 m_aObjects[ n-1 ] = nOffset;
1861 return aError == osl::File::E_None;
1864 #define CHECK_RETURN( x ) if( !(x) ) return 0
1865 #define CHECK_RETURN2( x ) if( !(x) ) return
1867 sal_Int32 PDFWriterImpl::emitStructParentTree( sal_Int32 nObject )
1869 if( nObject > 0 )
1871 OStringBuffer aLine( 1024 );
1873 aLine.append( nObject );
1874 aLine.append( " 0 obj\n"
1875 "<</Nums[\n" );
1876 sal_Int32 nTreeItems = m_aStructParentTree.size();
1877 for( sal_Int32 n = 0; n < nTreeItems; n++ )
1879 aLine.append( n );
1880 aLine.append( ' ' );
1881 aLine.append( m_aStructParentTree[n] );
1882 aLine.append( "\n" );
1884 aLine.append( "]>>\nendobj\n\n" );
1885 CHECK_RETURN( updateObject( nObject ) );
1886 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
1888 return nObject;
1891 const sal_Char* PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr )
1893 static std::map< PDFWriter::StructAttribute, const char* > aAttributeStrings;
1894 // fill maps once
1895 if( aAttributeStrings.empty() )
1897 aAttributeStrings[ PDFWriter::Placement ] = "Placement";
1898 aAttributeStrings[ PDFWriter::WritingMode ] = "WritingMode";
1899 aAttributeStrings[ PDFWriter::SpaceBefore ] = "SpaceBefore";
1900 aAttributeStrings[ PDFWriter::SpaceAfter ] = "SpaceAfter";
1901 aAttributeStrings[ PDFWriter::StartIndent ] = "StartIndent";
1902 aAttributeStrings[ PDFWriter::EndIndent ] = "EndIndent";
1903 aAttributeStrings[ PDFWriter::TextIndent ] = "TextIndent";
1904 aAttributeStrings[ PDFWriter::TextAlign ] = "TextAlign";
1905 aAttributeStrings[ PDFWriter::Width ] = "Width";
1906 aAttributeStrings[ PDFWriter::Height ] = "Height";
1907 aAttributeStrings[ PDFWriter::BlockAlign ] = "BlockAlign";
1908 aAttributeStrings[ PDFWriter::InlineAlign ] = "InlineAlign";
1909 aAttributeStrings[ PDFWriter::LineHeight ] = "LineHeight";
1910 aAttributeStrings[ PDFWriter::BaselineShift ] = "BaselineShift";
1911 aAttributeStrings[ PDFWriter::TextDecorationType ] = "TextDecorationType";
1912 aAttributeStrings[ PDFWriter::ListNumbering ] = "ListNumbering";
1913 aAttributeStrings[ PDFWriter::RowSpan ] = "RowSpan";
1914 aAttributeStrings[ PDFWriter::ColSpan ] = "ColSpan";
1915 aAttributeStrings[ PDFWriter::LinkAnnotation ] = "LinkAnnotation";
1918 std::map< PDFWriter::StructAttribute, const char* >::const_iterator it =
1919 aAttributeStrings.find( eAttr );
1921 if( it == aAttributeStrings.end() )
1922 SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttribute " << eAttr);
1924 return it != aAttributeStrings.end() ? it->second : "";
1927 const sal_Char* PDFWriterImpl::getAttributeValueTag( PDFWriter::StructAttributeValue eVal )
1929 static std::map< PDFWriter::StructAttributeValue, const char* > aValueStrings;
1931 if( aValueStrings.empty() )
1933 aValueStrings[ PDFWriter::NONE ] = "None";
1934 aValueStrings[ PDFWriter::Block ] = "Block";
1935 aValueStrings[ PDFWriter::Inline ] = "Inline";
1936 aValueStrings[ PDFWriter::Before ] = "Before";
1937 aValueStrings[ PDFWriter::After ] = "After";
1938 aValueStrings[ PDFWriter::Start ] = "Start";
1939 aValueStrings[ PDFWriter::End ] = "End";
1940 aValueStrings[ PDFWriter::LrTb ] = "LrTb";
1941 aValueStrings[ PDFWriter::RlTb ] = "RlTb";
1942 aValueStrings[ PDFWriter::TbRl ] = "TbRl";
1943 aValueStrings[ PDFWriter::Center ] = "Center";
1944 aValueStrings[ PDFWriter::Justify ] = "Justify";
1945 aValueStrings[ PDFWriter::Auto ] = "Auto";
1946 aValueStrings[ PDFWriter::Middle ] = "Middle";
1947 aValueStrings[ PDFWriter::Normal ] = "Normal";
1948 aValueStrings[ PDFWriter::Underline ] = "Underline";
1949 aValueStrings[ PDFWriter::Overline ] = "Overline";
1950 aValueStrings[ PDFWriter::LineThrough ] = "LineThrough";
1951 aValueStrings[ PDFWriter::Disc ] = "Disc";
1952 aValueStrings[ PDFWriter::Circle ] = "Circle";
1953 aValueStrings[ PDFWriter::Square ] = "Square";
1954 aValueStrings[ PDFWriter::Decimal ] = "Decimal";
1955 aValueStrings[ PDFWriter::UpperRoman ] = "UpperRoman";
1956 aValueStrings[ PDFWriter::LowerRoman ] = "LowerRoman";
1957 aValueStrings[ PDFWriter::UpperAlpha ] = "UpperAlpha";
1958 aValueStrings[ PDFWriter::LowerAlpha ] = "LowerAlpha";
1961 std::map< PDFWriter::StructAttributeValue, const char* >::const_iterator it =
1962 aValueStrings.find( eVal );
1964 if( it == aValueStrings.end() )
1965 SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttributeValue " << eVal);
1967 return it != aValueStrings.end() ? it->second : "";
1970 static void appendStructureAttributeLine( PDFWriter::StructAttribute i_eAttr, const PDFWriterImpl::PDFStructureAttribute& i_rVal, OStringBuffer& o_rLine, bool i_bIsFixedInt )
1972 o_rLine.append( "/" );
1973 o_rLine.append( PDFWriterImpl::getAttributeTag( i_eAttr ) );
1975 if( i_rVal.eValue != PDFWriter::Invalid )
1977 o_rLine.append( "/" );
1978 o_rLine.append( PDFWriterImpl::getAttributeValueTag( i_rVal.eValue ) );
1980 else
1982 // numerical value
1983 o_rLine.append( " " );
1984 if( i_bIsFixedInt )
1985 appendFixedInt( i_rVal.nValue, o_rLine );
1986 else
1987 o_rLine.append( i_rVal.nValue );
1989 o_rLine.append( "\n" );
1992 OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle )
1994 // create layout, list and table attribute sets
1995 OStringBuffer aLayout(256), aList(64), aTable(64);
1996 for (auto const& attribute : i_rEle.m_aAttributes)
1998 if( attribute.first == PDFWriter::ListNumbering )
1999 appendStructureAttributeLine( attribute.first, attribute.second, aList, true );
2000 else if( attribute.first == PDFWriter::RowSpan ||
2001 attribute.first == PDFWriter::ColSpan )
2002 appendStructureAttributeLine( attribute.first, attribute.second, aTable, false );
2003 else if( attribute.first == PDFWriter::LinkAnnotation )
2005 sal_Int32 nLink = attribute.second.nValue;
2006 std::map< sal_Int32, sal_Int32 >::const_iterator link_it =
2007 m_aLinkPropertyMap.find( nLink );
2008 if( link_it != m_aLinkPropertyMap.end() )
2009 nLink = link_it->second;
2010 if( nLink >= 0 && nLink < static_cast<sal_Int32>(m_aLinks.size()) )
2012 // update struct parent of link
2013 OString aStructParentEntry =
2014 OString::number( i_rEle.m_nObject ) +
2015 " 0 R";
2016 m_aStructParentTree.push_back( aStructParentEntry );
2017 m_aLinks[ nLink ].m_nStructParent = m_aStructParentTree.size()-1;
2019 sal_Int32 nRefObject = createObject();
2020 if (updateObject(nRefObject))
2022 OString aRef =
2023 OString::number( nRefObject ) +
2024 " 0 obj\n"
2025 "<</Type/OBJR/Obj " +
2026 OString::number( m_aLinks[ nLink ].m_nObject ) +
2027 " 0 R>>\n"
2028 "endobj\n\n";
2029 writeBuffer( aRef.getStr(), aRef.getLength() );
2032 i_rEle.m_aKids.emplace_back( nRefObject );
2034 else
2036 OSL_FAIL( "unresolved link id for Link structure" );
2037 SAL_INFO("vcl.pdfwriter", "unresolved link id " << nLink << " for Link structure");
2038 if (g_bDebugDisableCompression)
2040 OString aLine = "unresolved link id " +
2041 OString::number( nLink ) +
2042 " for Link structure";
2043 emitComment( aLine.getStr() );
2047 else
2048 appendStructureAttributeLine( attribute.first, attribute.second, aLayout, true );
2050 if( ! i_rEle.m_aBBox.IsEmpty() )
2052 aLayout.append( "/BBox[" );
2053 appendFixedInt( i_rEle.m_aBBox.Left(), aLayout );
2054 aLayout.append( " " );
2055 appendFixedInt( i_rEle.m_aBBox.Top(), aLayout );
2056 aLayout.append( " " );
2057 appendFixedInt( i_rEle.m_aBBox.Right(), aLayout );
2058 aLayout.append( " " );
2059 appendFixedInt( i_rEle.m_aBBox.Bottom(), aLayout );
2060 aLayout.append( "]\n" );
2063 std::vector< sal_Int32 > aAttribObjects;
2064 if( !aLayout.isEmpty() )
2066 aAttribObjects.push_back( createObject() );
2067 if (updateObject( aAttribObjects.back() ))
2069 OStringBuffer aObj( 64 );
2070 aObj.append( aAttribObjects.back() );
2071 aObj.append( " 0 obj\n"
2072 "<</O/Layout\n" );
2073 aLayout.append( ">>\nendobj\n\n" );
2074 writeBuffer( aObj.getStr(), aObj.getLength() );
2075 writeBuffer( aLayout.getStr(), aLayout.getLength() );
2078 if( !aList.isEmpty() )
2080 aAttribObjects.push_back( createObject() );
2081 if (updateObject( aAttribObjects.back() ))
2083 OStringBuffer aObj( 64 );
2084 aObj.append( aAttribObjects.back() );
2085 aObj.append( " 0 obj\n"
2086 "<</O/List\n" );
2087 aList.append( ">>\nendobj\n\n" );
2088 writeBuffer( aObj.getStr(), aObj.getLength() );
2089 writeBuffer( aList.getStr(), aList.getLength() );
2092 if( !aTable.isEmpty() )
2094 aAttribObjects.push_back( createObject() );
2095 if (updateObject( aAttribObjects.back() ))
2097 OStringBuffer aObj( 64 );
2098 aObj.append( aAttribObjects.back() );
2099 aObj.append( " 0 obj\n"
2100 "<</O/Table\n" );
2101 aTable.append( ">>\nendobj\n\n" );
2102 writeBuffer( aObj.getStr(), aObj.getLength() );
2103 writeBuffer( aTable.getStr(), aTable.getLength() );
2107 OStringBuffer aRet( 64 );
2108 if( aAttribObjects.size() > 1 )
2109 aRet.append( " [" );
2110 for (auto const& attrib : aAttribObjects)
2112 aRet.append( " " );
2113 aRet.append( attrib );
2114 aRet.append( " 0 R" );
2116 if( aAttribObjects.size() > 1 )
2117 aRet.append( " ]" );
2118 return aRet.makeStringAndClear();
2121 sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle )
2124 // do not emit NonStruct and its children
2125 rEle.m_eType == PDFWriter::NonStructElement &&
2126 rEle.m_nOwnElement != rEle.m_nParentElement // but of course emit the struct tree root
2128 return 0;
2130 for (auto const& child : rEle.m_aChildren)
2132 if( child > 0 && child < sal_Int32(m_aStructure.size()) )
2134 PDFStructureElement& rChild = m_aStructure[ child ];
2135 if( rChild.m_eType != PDFWriter::NonStructElement )
2137 if( rChild.m_nParentElement == rEle.m_nOwnElement )
2138 emitStructure( rChild );
2139 else
2141 OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure element" );
2142 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure element with id " << child);
2146 else
2148 OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
2149 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure id " << child);
2153 OStringBuffer aLine( 512 );
2154 aLine.append( rEle.m_nObject );
2155 aLine.append( " 0 obj\n"
2156 "<</Type" );
2157 sal_Int32 nParentTree = -1;
2158 if( rEle.m_nOwnElement == rEle.m_nParentElement )
2160 nParentTree = createObject();
2161 CHECK_RETURN( nParentTree );
2162 aLine.append( "/StructTreeRoot\n" );
2163 aLine.append( "/ParentTree " );
2164 aLine.append( nParentTree );
2165 aLine.append( " 0 R\n" );
2166 if( ! m_aRoleMap.empty() )
2168 aLine.append( "/RoleMap<<" );
2169 for (auto const& role : m_aRoleMap)
2171 aLine.append( '/' );
2172 aLine.append(role.first);
2173 aLine.append( '/' );
2174 aLine.append( role.second );
2175 aLine.append( '\n' );
2177 aLine.append( ">>\n" );
2180 else
2182 aLine.append( "/StructElem\n"
2183 "/S/" );
2184 if( !rEle.m_aAlias.isEmpty() )
2185 aLine.append( rEle.m_aAlias );
2186 else
2187 aLine.append( getStructureTag( rEle.m_eType ) );
2188 aLine.append( "\n"
2189 "/P " );
2190 aLine.append( m_aStructure[ rEle.m_nParentElement ].m_nObject );
2191 aLine.append( " 0 R\n"
2192 "/Pg " );
2193 aLine.append( rEle.m_nFirstPageObject );
2194 aLine.append( " 0 R\n" );
2195 if( !rEle.m_aActualText.isEmpty() )
2197 aLine.append( "/ActualText" );
2198 appendUnicodeTextStringEncrypt( rEle.m_aActualText, rEle.m_nObject, aLine );
2199 aLine.append( "\n" );
2201 if( !rEle.m_aAltText.isEmpty() )
2203 aLine.append( "/Alt" );
2204 appendUnicodeTextStringEncrypt( rEle.m_aAltText, rEle.m_nObject, aLine );
2205 aLine.append( "\n" );
2208 if( (! rEle.m_aBBox.IsEmpty()) || (! rEle.m_aAttributes.empty()) )
2210 OString aAttribs = emitStructureAttributes( rEle );
2211 if( !aAttribs.isEmpty() )
2213 aLine.append( "/A" );
2214 aLine.append( aAttribs );
2215 aLine.append( "\n" );
2218 if( !rEle.m_aLocale.Language.isEmpty() )
2220 /* PDF allows only RFC 3066, which is only partly BCP 47 and does not
2221 * include script tags and others.
2222 * http://pdf.editme.com/pdfua-naturalLanguageSpecification
2223 * http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf#page=886
2224 * https://www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf#M13.9.19332.1Heading.97.Natural.Language.Specification
2225 * */
2226 LanguageTag aLanguageTag( rEle.m_aLocale);
2227 OUString aLanguage, aScript, aCountry;
2228 aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
2229 if (!aLanguage.isEmpty())
2231 OUStringBuffer aLocBuf( 16 );
2232 aLocBuf.append( aLanguage );
2233 if( !aCountry.isEmpty() )
2235 aLocBuf.append( '-' );
2236 aLocBuf.append( aCountry );
2238 aLine.append( "/Lang" );
2239 appendLiteralStringEncrypt( aLocBuf.makeStringAndClear(), rEle.m_nObject, aLine );
2240 aLine.append( "\n" );
2243 if( ! rEle.m_aKids.empty() )
2245 unsigned int i = 0;
2246 aLine.append( "/K[" );
2247 for (auto const& kid : rEle.m_aKids)
2249 if( kid.nMCID == -1 )
2251 aLine.append( kid.nObject );
2252 aLine.append( " 0 R" );
2253 aLine.append( ( (i & 15) == 15 ) ? "\n" : " " );
2255 else
2257 if( kid.nObject == rEle.m_nFirstPageObject )
2259 aLine.append( kid.nMCID );
2260 aLine.append( " " );
2262 else
2264 aLine.append( "<</Type/MCR/Pg " );
2265 aLine.append( kid.nObject );
2266 aLine.append( " 0 R /MCID " );
2267 aLine.append( kid.nMCID );
2268 aLine.append( ">>\n" );
2271 ++i;
2273 aLine.append( "]\n" );
2275 aLine.append( ">>\nendobj\n\n" );
2277 CHECK_RETURN( updateObject( rEle.m_nObject ) );
2278 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2280 CHECK_RETURN( emitStructParentTree( nParentTree ) );
2282 return rEle.m_nObject;
2285 bool PDFWriterImpl::emitGradients()
2287 for (auto const& gradient : m_aGradients)
2289 if ( !writeGradientFunction( gradient ) ) return false;
2291 return true;
2294 bool PDFWriterImpl::emitTilings()
2296 OStringBuffer aTilingObj( 1024 );
2298 for (auto & tiling : m_aTilings)
2300 SAL_WARN_IF( !tiling.m_pTilingStream, "vcl.pdfwriter", "tiling without stream" );
2301 if( ! tiling.m_pTilingStream )
2302 continue;
2304 aTilingObj.setLength( 0 );
2306 if (g_bDebugDisableCompression)
2308 emitComment( "PDFWriterImpl::emitTilings" );
2311 sal_Int32 nX = static_cast<sal_Int32>(tiling.m_aRectangle.Left());
2312 sal_Int32 nY = static_cast<sal_Int32>(tiling.m_aRectangle.Top());
2313 sal_Int32 nW = static_cast<sal_Int32>(tiling.m_aRectangle.GetWidth());
2314 sal_Int32 nH = static_cast<sal_Int32>(tiling.m_aRectangle.GetHeight());
2315 if( tiling.m_aCellSize.Width() == 0 )
2316 tiling.m_aCellSize.setWidth( nW );
2317 if( tiling.m_aCellSize.Height() == 0 )
2318 tiling.m_aCellSize.setHeight( nH );
2320 bool bDeflate = compressStream( tiling.m_pTilingStream.get() );
2321 sal_uInt64 const nTilingStreamSize = tiling.m_pTilingStream->TellEnd();
2322 tiling.m_pTilingStream->Seek( STREAM_SEEK_TO_BEGIN );
2324 // write pattern object
2325 aTilingObj.append( tiling.m_nObject );
2326 aTilingObj.append( " 0 obj\n" );
2327 aTilingObj.append( "<</Type/Pattern/PatternType 1\n"
2328 "/PaintType 1\n"
2329 "/TilingType 2\n"
2330 "/BBox[" );
2331 appendFixedInt( nX, aTilingObj );
2332 aTilingObj.append( ' ' );
2333 appendFixedInt( nY, aTilingObj );
2334 aTilingObj.append( ' ' );
2335 appendFixedInt( nX+nW, aTilingObj );
2336 aTilingObj.append( ' ' );
2337 appendFixedInt( nY+nH, aTilingObj );
2338 aTilingObj.append( "]\n"
2339 "/XStep " );
2340 appendFixedInt( tiling.m_aCellSize.Width(), aTilingObj );
2341 aTilingObj.append( "\n"
2342 "/YStep " );
2343 appendFixedInt( tiling.m_aCellSize.Height(), aTilingObj );
2344 aTilingObj.append( "\n" );
2345 if( tiling.m_aTransform.matrix[0] != 1.0 ||
2346 tiling.m_aTransform.matrix[1] != 0.0 ||
2347 tiling.m_aTransform.matrix[3] != 0.0 ||
2348 tiling.m_aTransform.matrix[4] != 1.0 ||
2349 tiling.m_aTransform.matrix[2] != 0.0 ||
2350 tiling.m_aTransform.matrix[5] != 0.0 )
2352 aTilingObj.append( "/Matrix [" );
2353 // TODO: scaling, mirroring on y, etc
2354 appendDouble( tiling.m_aTransform.matrix[0], aTilingObj );
2355 aTilingObj.append( ' ' );
2356 appendDouble( tiling.m_aTransform.matrix[1], aTilingObj );
2357 aTilingObj.append( ' ' );
2358 appendDouble( tiling.m_aTransform.matrix[3], aTilingObj );
2359 aTilingObj.append( ' ' );
2360 appendDouble( tiling.m_aTransform.matrix[4], aTilingObj );
2361 aTilingObj.append( ' ' );
2362 appendDouble( tiling.m_aTransform.matrix[2], aTilingObj );
2363 aTilingObj.append( ' ' );
2364 appendDouble( tiling.m_aTransform.matrix[5], aTilingObj );
2365 aTilingObj.append( "]\n" );
2367 aTilingObj.append( "/Resources" );
2368 tiling.m_aResources.append( aTilingObj, getFontDictObject() );
2369 if( bDeflate )
2370 aTilingObj.append( "/Filter/FlateDecode" );
2371 aTilingObj.append( "/Length " );
2372 aTilingObj.append( static_cast<sal_Int32>(nTilingStreamSize) );
2373 aTilingObj.append( ">>\nstream\n" );
2374 if ( !updateObject( tiling.m_nObject ) ) return false;
2375 if ( !writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) ) return false;
2376 checkAndEnableStreamEncryption( tiling.m_nObject );
2377 bool written = writeBuffer( tiling.m_pTilingStream->GetData(), nTilingStreamSize );
2378 tiling.m_pTilingStream.reset();
2379 if( !written )
2380 return false;
2381 disableStreamEncryption();
2382 aTilingObj.setLength( 0 );
2383 aTilingObj.append( "\nendstream\nendobj\n\n" );
2384 if ( !writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) ) return false;
2386 return true;
2389 sal_Int32 PDFWriterImpl::emitBuildinFont(const pdf::BuildinFontFace* pFD, sal_Int32 nFontObject)
2391 if( !pFD )
2392 return 0;
2393 const pdf::BuildinFont& rBuildinFont = pFD->GetBuildinFont();
2395 OStringBuffer aLine( 1024 );
2397 if( nFontObject <= 0 )
2398 nFontObject = createObject();
2399 CHECK_RETURN( updateObject( nFontObject ) );
2400 aLine.append( nFontObject );
2401 aLine.append( " 0 obj\n"
2402 "<</Type/Font/Subtype/Type1/BaseFont/" );
2403 appendName( rBuildinFont.m_pPSName, aLine );
2404 aLine.append( "\n" );
2405 if( rBuildinFont.m_eCharSet == RTL_TEXTENCODING_MS_1252 )
2406 aLine.append( "/Encoding/WinAnsiEncoding\n" );
2407 aLine.append( ">>\nendobj\n\n" );
2408 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2409 return nFontObject;
2412 std::map< sal_Int32, sal_Int32 > PDFWriterImpl::emitSystemFont( const PhysicalFontFace* pFont, EmbedFont const & rEmbed )
2414 std::map< sal_Int32, sal_Int32 > aRet;
2416 sal_Int32 nFontDescriptor = 0;
2417 OString aSubType( "/Type1" );
2418 FontSubsetInfo aInfo;
2419 // fill in dummy values
2420 aInfo.m_nAscent = 1000;
2421 aInfo.m_nDescent = 200;
2422 aInfo.m_nCapHeight = 1000;
2423 aInfo.m_aFontBBox = tools::Rectangle( Point( -200, -200 ), Size( 1700, 1700 ) );
2424 aInfo.m_aPSName = pFont->GetFamilyName();
2425 sal_Int32 pWidths[256] = {};
2427 SalGraphics *pGraphics = GetGraphics();
2429 assert(pGraphics);
2431 aSubType = OString( "/TrueType" );
2432 std::vector< sal_Int32 > aGlyphWidths;
2433 Ucs2UIntMap aUnicodeMap;
2434 pGraphics->GetGlyphWidths( pFont, false, aGlyphWidths, aUnicodeMap );
2436 OUString aTmpName;
2437 osl_createTempFile( nullptr, nullptr, &aTmpName.pData );
2438 sal_GlyphId aGlyphIds[ 256 ] = {};
2439 sal_uInt8 pEncoding[ 256 ] = {};
2440 sal_Int32 pDuWidths[ 256 ] = {};
2442 for( sal_Ucs c = 32; c < 256; c++ )
2444 pEncoding[c] = c;
2445 aGlyphIds[c] = 0;
2446 if( aUnicodeMap.find( c ) != aUnicodeMap.end() )
2447 pWidths[ c ] = aGlyphWidths[ aUnicodeMap[ c ] ];
2449 //TODO: surely this is utterly broken because aGlyphIds is just all zeros, if we
2450 //had the right glyphids here then I imagine we could replace pDuWidths with
2451 //pWidths and remove pWidths assignment above. i.e. start with the glyph ids
2452 //and map those to unicode rather than try and reverse map them ?
2453 pGraphics->CreateFontSubset( aTmpName, pFont, aGlyphIds, pEncoding, pDuWidths, 256, aInfo );
2454 osl_removeFile( aTmpName.pData );
2456 // write font descriptor
2457 nFontDescriptor = emitFontDescriptor( pFont, aInfo, 0, 0 );
2458 if( nFontDescriptor )
2460 // write font object
2461 sal_Int32 nObject = createObject();
2462 if( updateObject( nObject ) )
2464 OStringBuffer aLine( 1024 );
2465 aLine.append( nObject );
2466 aLine.append( " 0 obj\n"
2467 "<</Type/Font/Subtype" );
2468 aLine.append( aSubType );
2469 aLine.append( "/BaseFont/" );
2470 appendName( aInfo.m_aPSName, aLine );
2471 aLine.append( "\n" );
2472 if( !pFont->IsSymbolFont() )
2473 aLine.append( "/Encoding/WinAnsiEncoding\n" );
2474 aLine.append( "/FirstChar 32 /LastChar 255\n"
2475 "/Widths[" );
2476 for( int i = 32; i < 256; i++ )
2478 aLine.append( pWidths[i] );
2479 aLine.append( ((i&15) == 15) ? "\n" : " " );
2481 aLine.append( "]\n"
2482 "/FontDescriptor " );
2483 aLine.append( nFontDescriptor );
2484 aLine.append( " 0 R>>\n"
2485 "endobj\n\n" );
2486 writeBuffer( aLine.getStr(), aLine.getLength() );
2488 aRet[ rEmbed.m_nNormalFontID ] = nObject;
2492 return aRet;
2495 typedef int ThreeInts[3];
2496 static bool getPfbSegmentLengths( const unsigned char* pFontBytes, int nByteLen,
2497 ThreeInts& rSegmentLengths )
2499 if( !pFontBytes || (nByteLen < 0) )
2500 return false;
2501 const unsigned char* pPtr = pFontBytes;
2502 const unsigned char* pEnd = pFontBytes + nByteLen;
2504 for(int & rSegmentLength : rSegmentLengths) {
2505 // read segment1 header
2506 if( pPtr+6 >= pEnd )
2507 return false;
2508 if( (pPtr[0] != 0x80) || (pPtr[1] >= 0x03) )
2509 return false;
2510 const int nLen = (pPtr[5]<<24) + (pPtr[4]<<16) + (pPtr[3]<<8) + pPtr[2];
2511 if( nLen <= 0)
2512 return false;
2513 rSegmentLength = nLen;
2514 pPtr += nLen + 6;
2517 // read segment-end header
2518 if( pPtr+2 >= pEnd )
2519 return false;
2520 if( (pPtr[0] != 0x80) || (pPtr[1] != 0x03) )
2521 return false;
2523 return true;
2526 static void appendSubsetName( int nSubsetID, const OUString& rPSName, OStringBuffer& rBuffer )
2528 if( nSubsetID )
2530 for( int i = 0; i < 6; i++ )
2532 int nOffset = nSubsetID % 26;
2533 nSubsetID /= 26;
2534 rBuffer.append( static_cast<sal_Char>('A'+nOffset) );
2536 rBuffer.append( '+' );
2538 appendName( rPSName, rBuffer );
2541 sal_Int32 PDFWriterImpl::createToUnicodeCMap( sal_uInt8 const * pEncoding,
2542 const sal_Ucs* pCodeUnits,
2543 const sal_Int32* pCodeUnitsPerGlyph,
2544 const sal_Int32* pEncToUnicodeIndex,
2545 int nGlyphs )
2547 int nMapped = 0;
2548 for (int n = 0; n < nGlyphs; ++n)
2549 if( pCodeUnits[pEncToUnicodeIndex[n]] && pCodeUnitsPerGlyph[n] )
2550 nMapped++;
2552 if( nMapped == 0 )
2553 return 0;
2555 sal_Int32 nStream = createObject();
2556 CHECK_RETURN( updateObject( nStream ) );
2558 OStringBuffer aContents( 1024 );
2559 aContents.append(
2560 "/CIDInit/ProcSet findresource begin\n"
2561 "12 dict begin\n"
2562 "begincmap\n"
2563 "/CIDSystemInfo<<\n"
2564 "/Registry (Adobe)\n"
2565 "/Ordering (UCS)\n"
2566 "/Supplement 0\n"
2567 ">> def\n"
2568 "/CMapName/Adobe-Identity-UCS def\n"
2569 "/CMapType 2 def\n"
2570 "1 begincodespacerange\n"
2571 "<00> <FF>\n"
2572 "endcodespacerange\n"
2574 int nCount = 0;
2575 for (int n = 0; n < nGlyphs; ++n)
2577 if( pCodeUnits[pEncToUnicodeIndex[n]] && pCodeUnitsPerGlyph[n] )
2579 if( (nCount % 100) == 0 )
2581 if( nCount )
2582 aContents.append( "endbfchar\n" );
2583 aContents.append( static_cast<sal_Int32>(std::min(nMapped-nCount, 100)) );
2584 aContents.append( " beginbfchar\n" );
2586 aContents.append( '<' );
2587 appendHex( static_cast<sal_Int8>(pEncoding[n]), aContents );
2588 aContents.append( "> <" );
2589 // TODO: handle code points>U+FFFF
2590 sal_Int32 nIndex = pEncToUnicodeIndex[n];
2591 for( sal_Int32 j = 0; j < pCodeUnitsPerGlyph[n]; j++ )
2593 appendHex( static_cast<sal_Int8>(pCodeUnits[nIndex + j] / 256), aContents );
2594 appendHex( static_cast<sal_Int8>(pCodeUnits[nIndex + j] & 255), aContents );
2596 aContents.append( ">\n" );
2597 nCount++;
2600 aContents.append( "endbfchar\n"
2601 "endcmap\n"
2602 "CMapName currentdict /CMap defineresource pop\n"
2603 "end\n"
2604 "end\n" );
2605 SvMemoryStream aStream;
2606 if (!g_bDebugDisableCompression)
2608 ZCodec aCodec( 0x4000, 0x4000 );
2609 aCodec.BeginCompression();
2610 aCodec.Write( aStream, reinterpret_cast<const sal_uInt8*>(aContents.getStr()), aContents.getLength() );
2611 aCodec.EndCompression();
2614 if (g_bDebugDisableCompression)
2616 emitComment( "PDFWriterImpl::createToUnicodeCMap" );
2618 OStringBuffer aLine( 40 );
2620 aLine.append( nStream );
2621 aLine.append( " 0 obj\n<</Length " );
2622 sal_Int32 nLen = 0;
2623 if (!g_bDebugDisableCompression)
2625 nLen = static_cast<sal_Int32>(aStream.Tell());
2626 aStream.Seek( 0 );
2627 aLine.append( nLen );
2628 aLine.append( "/Filter/FlateDecode" );
2630 else
2631 aLine.append( aContents.getLength() );
2632 aLine.append( ">>\nstream\n" );
2633 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2634 checkAndEnableStreamEncryption( nStream );
2635 if (!g_bDebugDisableCompression)
2637 CHECK_RETURN( writeBuffer( aStream.GetData(), nLen ) );
2639 else
2641 CHECK_RETURN( writeBuffer( aContents.getStr(), aContents.getLength() ) );
2643 disableStreamEncryption();
2644 aLine.setLength( 0 );
2645 aLine.append( "\nendstream\n"
2646 "endobj\n\n" );
2647 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2648 return nStream;
2651 sal_Int32 PDFWriterImpl::emitFontDescriptor( const PhysicalFontFace* pFont, FontSubsetInfo const & rInfo, sal_Int32 nSubsetID, sal_Int32 nFontStream )
2653 OStringBuffer aLine( 1024 );
2654 // get font flags, see PDF reference 1.4 p. 358
2655 // possibly characters outside Adobe standard encoding
2656 // so set Symbolic flag
2657 sal_Int32 nFontFlags = (1<<2);
2658 if( pFont->GetItalic() == ITALIC_NORMAL || pFont->GetItalic() == ITALIC_OBLIQUE )
2659 nFontFlags |= (1 << 6);
2660 if( pFont->GetPitch() == PITCH_FIXED )
2661 nFontFlags |= 1;
2662 if( pFont->GetFamilyType() == FAMILY_SCRIPT )
2663 nFontFlags |= (1 << 3);
2664 else if( pFont->GetFamilyType() == FAMILY_ROMAN )
2665 nFontFlags |= (1 << 1);
2667 sal_Int32 nFontDescriptor = createObject();
2668 CHECK_RETURN( updateObject( nFontDescriptor ) );
2669 aLine.setLength( 0 );
2670 aLine.append( nFontDescriptor );
2671 aLine.append( " 0 obj\n"
2672 "<</Type/FontDescriptor/FontName/" );
2673 appendSubsetName( nSubsetID, rInfo.m_aPSName, aLine );
2674 aLine.append( "\n"
2675 "/Flags " );
2676 aLine.append( nFontFlags );
2677 aLine.append( "\n"
2678 "/FontBBox[" );
2679 // note: Top and Bottom are reversed in VCL and PDF rectangles
2680 aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.TopLeft().X()) );
2681 aLine.append( ' ' );
2682 aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.TopLeft().Y()) );
2683 aLine.append( ' ' );
2684 aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.BottomRight().X()) );
2685 aLine.append( ' ' );
2686 aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.BottomRight().Y()+1) );
2687 aLine.append( "]/ItalicAngle " );
2688 if( pFont->GetItalic() == ITALIC_OBLIQUE || pFont->GetItalic() == ITALIC_NORMAL )
2689 aLine.append( "-30" );
2690 else
2691 aLine.append( "0" );
2692 aLine.append( "\n"
2693 "/Ascent " );
2694 aLine.append( static_cast<sal_Int32>(rInfo.m_nAscent) );
2695 aLine.append( "\n"
2696 "/Descent " );
2697 aLine.append( static_cast<sal_Int32>(-rInfo.m_nDescent) );
2698 aLine.append( "\n"
2699 "/CapHeight " );
2700 aLine.append( static_cast<sal_Int32>(rInfo.m_nCapHeight) );
2701 // According to PDF reference 1.4 StemV is required
2702 // seems a tad strange to me, but well ...
2703 aLine.append( "\n"
2704 "/StemV 80\n" );
2705 if( nFontStream )
2707 aLine.append( "/FontFile" );
2708 switch( rInfo.m_nFontType )
2710 case FontType::SFNT_TTF:
2711 aLine.append( '2' );
2712 break;
2713 case FontType::TYPE1_PFA:
2714 case FontType::TYPE1_PFB:
2715 case FontType::ANY_TYPE1:
2716 break;
2717 default:
2718 OSL_FAIL( "unknown fonttype in PDF font descriptor" );
2719 return 0;
2721 aLine.append( ' ' );
2722 aLine.append( nFontStream );
2723 aLine.append( " 0 R\n" );
2725 aLine.append( ">>\n"
2726 "endobj\n\n" );
2727 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2729 return nFontDescriptor;
2732 void PDFWriterImpl::appendBuildinFontsToDict( OStringBuffer& rDict ) const
2734 for (auto const& item : m_aBuildinFontToObjectMap)
2736 rDict.append( pdf::BuildinFontFace::Get(item.first).getNameObject() );
2737 rDict.append( ' ' );
2738 rDict.append( item.second );
2739 rDict.append( " 0 R" );
2743 bool PDFWriterImpl::emitFonts()
2745 SalGraphics *pGraphics = GetGraphics();
2747 if (!pGraphics)
2748 return false;
2750 OStringBuffer aLine( 1024 );
2752 std::map< sal_Int32, sal_Int32 > aFontIDToObject;
2754 OUString aTmpName;
2755 osl_createTempFile( nullptr, nullptr, &aTmpName.pData );
2756 for (const auto & subset : m_aSubsets)
2758 for (auto & s_subset :subset.second.m_aSubsets)
2760 sal_GlyphId aGlyphIds[ 256 ] = {};
2761 sal_Int32 pWidths[ 256 ];
2762 sal_uInt8 pEncoding[ 256 ] = {};
2763 sal_Int32 pEncToUnicodeIndex[ 256 ] = {};
2764 sal_Int32 pCodeUnitsPerGlyph[ 256 ] = {};
2765 std::vector<sal_Ucs> aCodeUnits;
2766 aCodeUnits.reserve( 256 );
2767 int nGlyphs = 1;
2768 // fill arrays and prepare encoding index map
2769 sal_Int32 nToUnicodeStream = 0;
2771 for (auto const& item : s_subset.m_aMapping)
2773 sal_uInt8 nEnc = item.second.getGlyphId();
2775 SAL_WARN_IF( aGlyphIds[nEnc] != 0 || pEncoding[nEnc] != 0, "vcl.pdfwriter", "duplicate glyph" );
2776 SAL_WARN_IF( nEnc > s_subset.m_aMapping.size(), "vcl.pdfwriter", "invalid glyph encoding" );
2778 aGlyphIds[ nEnc ] = item.first;
2779 pEncoding[ nEnc ] = nEnc;
2780 pEncToUnicodeIndex[ nEnc ] = static_cast<sal_Int32>(aCodeUnits.size());
2781 pCodeUnitsPerGlyph[ nEnc ] = item.second.countCodes();
2782 for( sal_Int32 n = 0; n < pCodeUnitsPerGlyph[ nEnc ]; n++ )
2783 aCodeUnits.push_back( item.second.getCode( n ) );
2784 if( item.second.getCode(0) )
2785 nToUnicodeStream = 1;
2786 if( nGlyphs < 256 )
2787 nGlyphs++;
2788 else
2790 OSL_FAIL( "too many glyphs for subset" );
2793 FontSubsetInfo aSubsetInfo;
2794 if( pGraphics->CreateFontSubset( aTmpName, subset.first, aGlyphIds, pEncoding, pWidths, nGlyphs, aSubsetInfo ) )
2796 // create font stream
2797 osl::File aFontFile(aTmpName);
2798 if (osl::File::E_None != aFontFile.open(osl_File_OpenFlag_Read)) return false;
2799 // get file size
2800 sal_uInt64 nLength1;
2801 if ( osl::File::E_None != aFontFile.setPos(osl_Pos_End, 0) ) return false;
2802 if ( osl::File::E_None != aFontFile.getPos(nLength1) ) return false;
2803 if ( osl::File::E_None != aFontFile.setPos(osl_Pos_Absolut, 0) ) return false;
2805 if (g_bDebugDisableCompression)
2807 emitComment( "PDFWriterImpl::emitFonts" );
2809 sal_Int32 nFontStream = createObject();
2810 sal_Int32 nStreamLengthObject = createObject();
2811 if ( !updateObject( nFontStream ) ) return false;
2812 aLine.setLength( 0 );
2813 aLine.append( nFontStream );
2814 aLine.append( " 0 obj\n"
2815 "<</Length " );
2816 aLine.append( nStreamLengthObject );
2817 if (!g_bDebugDisableCompression)
2818 aLine.append( " 0 R"
2819 "/Filter/FlateDecode"
2820 "/Length1 " );
2821 else
2822 aLine.append( " 0 R"
2823 "/Length1 " );
2825 sal_uInt64 nStartPos = 0;
2826 if( aSubsetInfo.m_nFontType == FontType::SFNT_TTF )
2828 aLine.append( static_cast<sal_Int32>(nLength1) );
2830 aLine.append( ">>\n"
2831 "stream\n" );
2832 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
2833 if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
2835 // copy font file
2836 beginCompression();
2837 checkAndEnableStreamEncryption( nFontStream );
2838 sal_Bool bEOF = false;
2841 char buf[8192];
2842 sal_uInt64 nRead;
2843 if ( osl::File::E_None != aFontFile.read(buf, sizeof(buf), nRead) ) return false;
2844 if ( !writeBuffer( buf, nRead ) ) return false;
2845 if ( osl::File::E_None != aFontFile.isEndOfFile(&bEOF) ) return false;
2846 } while( ! bEOF );
2848 else if( aSubsetInfo.m_nFontType & FontType::CFF_FONT)
2850 // TODO: implement
2851 OSL_FAIL( "PDFWriterImpl does not support CFF-font subsets yet!" );
2853 else if( aSubsetInfo.m_nFontType & FontType::TYPE1_PFB) // TODO: also support PFA?
2855 std::unique_ptr<unsigned char[]> xBuffer(new unsigned char[nLength1]);
2857 sal_uInt64 nBytesRead = 0;
2858 if ( osl::File::E_None != aFontFile.read(xBuffer.get(), nLength1, nBytesRead) ) return false;
2859 SAL_WARN_IF( nBytesRead!=nLength1, "vcl.pdfwriter", "PDF-FontSubset read incomplete!" );
2860 if ( osl::File::E_None != aFontFile.setPos(osl_Pos_Absolut, 0) ) return false;
2861 // get the PFB-segment lengths
2862 ThreeInts aSegmentLengths = {0,0,0};
2863 getPfbSegmentLengths(xBuffer.get(), static_cast<int>(nBytesRead), aSegmentLengths);
2864 // the lengths below are mandatory for PDF-exported Type1 fonts
2865 // because the PFB segment headers get stripped! WhyOhWhy.
2866 aLine.append( static_cast<sal_Int32>(aSegmentLengths[0]) );
2867 aLine.append( "/Length2 " );
2868 aLine.append( static_cast<sal_Int32>(aSegmentLengths[1]) );
2869 aLine.append( "/Length3 " );
2870 aLine.append( static_cast<sal_Int32>(aSegmentLengths[2]) );
2872 aLine.append( ">>\n"
2873 "stream\n" );
2874 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
2875 if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
2877 // emit PFB-sections without section headers
2878 beginCompression();
2879 checkAndEnableStreamEncryption( nFontStream );
2880 if ( !writeBuffer( &xBuffer[6], aSegmentLengths[0] ) ) return false;
2881 if ( !writeBuffer( &xBuffer[12] + aSegmentLengths[0], aSegmentLengths[1] ) ) return false;
2882 if ( !writeBuffer( &xBuffer[18] + aSegmentLengths[0] + aSegmentLengths[1], aSegmentLengths[2] ) ) return false;
2884 else
2886 SAL_INFO("vcl.pdfwriter", "PDF: CreateFontSubset result in not yet supported format=" << static_cast<int>(aSubsetInfo.m_nFontType));
2887 aLine.append( "0 >>\nstream\n" );
2890 endCompression();
2891 disableStreamEncryption();
2892 // close the file
2893 aFontFile.close();
2895 sal_uInt64 nEndPos = 0;
2896 if ( osl::File::E_None != m_aFile.getPos(nEndPos) ) return false;
2897 // end the stream
2898 aLine.setLength( 0 );
2899 aLine.append( "\nendstream\nendobj\n\n" );
2900 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
2902 // emit stream length object
2903 if ( !updateObject( nStreamLengthObject ) ) return false;
2904 aLine.setLength( 0 );
2905 aLine.append( nStreamLengthObject );
2906 aLine.append( " 0 obj\n" );
2907 aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
2908 aLine.append( "\nendobj\n\n" );
2909 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
2911 // write font descriptor
2912 sal_Int32 nFontDescriptor = emitFontDescriptor( subset.first, aSubsetInfo, s_subset.m_nFontID, nFontStream );
2914 if( nToUnicodeStream )
2915 nToUnicodeStream = createToUnicodeCMap( pEncoding, aCodeUnits.data(), pCodeUnitsPerGlyph, pEncToUnicodeIndex, nGlyphs );
2917 sal_Int32 nFontObject = createObject();
2918 if ( !updateObject( nFontObject ) ) return false;
2919 aLine.setLength( 0 );
2920 aLine.append( nFontObject );
2922 aLine.append( " 0 obj\n" );
2923 aLine.append( (aSubsetInfo.m_nFontType & FontType::ANY_TYPE1) ?
2924 "<</Type/Font/Subtype/Type1/BaseFont/" :
2925 "<</Type/Font/Subtype/TrueType/BaseFont/" );
2926 appendSubsetName( s_subset.m_nFontID, aSubsetInfo.m_aPSName, aLine );
2927 aLine.append( "\n"
2928 "/FirstChar 0\n"
2929 "/LastChar " );
2930 aLine.append( static_cast<sal_Int32>(nGlyphs-1) );
2931 aLine.append( "\n"
2932 "/Widths[" );
2933 for( int i = 0; i < nGlyphs; i++ )
2935 aLine.append( pWidths[ i ] );
2936 aLine.append( ((i & 15) == 15) ? "\n" : " " );
2938 aLine.append( "]\n"
2939 "/FontDescriptor " );
2940 aLine.append( nFontDescriptor );
2941 aLine.append( " 0 R\n" );
2942 if( nToUnicodeStream )
2944 aLine.append( "/ToUnicode " );
2945 aLine.append( nToUnicodeStream );
2946 aLine.append( " 0 R\n" );
2948 aLine.append( ">>\n"
2949 "endobj\n\n" );
2950 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
2952 aFontIDToObject[ s_subset.m_nFontID ] = nFontObject;
2954 else
2956 const PhysicalFontFace* pFont = subset.first;
2957 OStringBuffer aErrorComment( 256 );
2958 aErrorComment.append( "CreateFontSubset failed for font \"" );
2959 aErrorComment.append( OUStringToOString( pFont->GetFamilyName(), RTL_TEXTENCODING_UTF8 ) );
2960 aErrorComment.append( '\"' );
2961 if( pFont->GetItalic() == ITALIC_NORMAL )
2962 aErrorComment.append( " italic" );
2963 else if( pFont->GetItalic() == ITALIC_OBLIQUE )
2964 aErrorComment.append( " oblique" );
2965 aErrorComment.append( " weight=" );
2966 aErrorComment.append( sal_Int32(pFont->GetWeight()) );
2967 emitComment( aErrorComment.getStr() );
2971 osl_removeFile( aTmpName.pData );
2973 // emit system fonts
2974 for (auto const& systemFont : m_aSystemFonts)
2976 std::map< sal_Int32, sal_Int32 > aObjects = emitSystemFont( systemFont.first, systemFont.second );
2977 for (auto const& item : aObjects)
2979 if ( !item.second ) return false;
2980 aFontIDToObject[ item.first ] = item.second;
2984 OStringBuffer aFontDict( 1024 );
2985 aFontDict.append( getFontDictObject() );
2986 aFontDict.append( " 0 obj\n"
2987 "<<" );
2988 int ni = 0;
2989 for (auto const& itemMap : aFontIDToObject)
2991 aFontDict.append( "/F" );
2992 aFontDict.append( itemMap.first );
2993 aFontDict.append( ' ' );
2994 aFontDict.append( itemMap.second );
2995 aFontDict.append( " 0 R" );
2996 if( ((++ni) & 7) == 0 )
2997 aFontDict.append( '\n' );
2999 // emit builtin font for widget appearances / variable text
3000 for (auto & item : m_aBuildinFontToObjectMap)
3002 rtl::Reference<pdf::BuildinFontFace> aData(new pdf::BuildinFontFace(item.first));
3003 item.second = emitBuildinFont( aData.get(), item.second );
3006 appendBuildinFontsToDict(aFontDict);
3007 aFontDict.append( "\n>>\nendobj\n\n" );
3009 if ( !updateObject( getFontDictObject() ) ) return false;
3010 if ( !writeBuffer( aFontDict.getStr(), aFontDict.getLength() ) ) return false;
3011 return true;
3014 sal_Int32 PDFWriterImpl::emitResources()
3016 // emit shadings
3017 if( ! m_aGradients.empty() )
3018 CHECK_RETURN( emitGradients() );
3019 // emit tilings
3020 if( ! m_aTilings.empty() )
3021 CHECK_RETURN( emitTilings() );
3023 // emit font dict
3024 CHECK_RETURN( emitFonts() );
3026 // emit Resource dict
3027 OStringBuffer aLine( 512 );
3028 sal_Int32 nResourceDict = getResourceDictObj();
3029 CHECK_RETURN( updateObject( nResourceDict ) );
3030 aLine.setLength( 0 );
3031 aLine.append( nResourceDict );
3032 aLine.append( " 0 obj\n" );
3033 m_aGlobalResourceDict.append( aLine, getFontDictObject() );
3034 aLine.append( "endobj\n\n" );
3035 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3036 return nResourceDict;
3039 sal_Int32 PDFWriterImpl::updateOutlineItemCount( std::vector< sal_Int32 >& rCounts,
3040 sal_Int32 nItemLevel,
3041 sal_Int32 nCurrentItemId )
3043 /* The /Count number of an item is
3044 positive: the number of visible subitems
3045 negative: the negative number of subitems that will become visible if
3046 the item gets opened
3047 see PDF ref 1.4 p 478
3050 sal_Int32 nCount = 0;
3052 if( m_aContext.OpenBookmarkLevels < 0 || // all levels are visible
3053 m_aContext.OpenBookmarkLevels >= nItemLevel // this level is visible
3056 PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
3057 sal_Int32 nChildren = rItem.m_aChildren.size();
3058 for( sal_Int32 i = 0; i < nChildren; i++ )
3059 nCount += updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
3060 rCounts[nCurrentItemId] = nCount;
3061 // return 1 (this item) + visible sub items
3062 if( nCount < 0 )
3063 nCount = 0;
3064 nCount++;
3066 else
3068 // this bookmark level is invisible
3069 PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
3070 sal_Int32 nChildren = rItem.m_aChildren.size();
3071 rCounts[ nCurrentItemId ] = -sal_Int32(rItem.m_aChildren.size());
3072 for( sal_Int32 i = 0; i < nChildren; i++ )
3073 updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
3074 nCount = -1;
3077 return nCount;
3080 sal_Int32 PDFWriterImpl::emitOutline()
3082 int i, nItems = m_aOutline.size();
3084 // do we have an outline at all ?
3085 if( nItems < 2 )
3086 return 0;
3088 // reserve object numbers for all outline items
3089 for( i = 0; i < nItems; ++i )
3090 m_aOutline[i].m_nObject = createObject();
3092 // update all parent, next and prev object ids
3093 for( i = 0; i < nItems; ++i )
3095 PDFOutlineEntry& rItem = m_aOutline[i];
3096 int nChildren = rItem.m_aChildren.size();
3098 if( nChildren )
3100 for( int n = 0; n < nChildren; ++n )
3102 PDFOutlineEntry& rChild = m_aOutline[ rItem.m_aChildren[n] ];
3104 rChild.m_nParentObject = rItem.m_nObject;
3105 rChild.m_nPrevObject = (n > 0) ? m_aOutline[ rItem.m_aChildren[n-1] ].m_nObject : 0;
3106 rChild.m_nNextObject = (n < nChildren-1) ? m_aOutline[ rItem.m_aChildren[n+1] ].m_nObject : 0;
3112 // calculate Count entries for all items
3113 std::vector< sal_Int32 > aCounts( nItems );
3114 updateOutlineItemCount( aCounts, 0, 0 );
3116 // emit hierarchy
3117 for( i = 0; i < nItems; ++i )
3119 PDFOutlineEntry& rItem = m_aOutline[i];
3120 OStringBuffer aLine( 1024 );
3122 CHECK_RETURN( updateObject( rItem.m_nObject ) );
3123 aLine.append( rItem.m_nObject );
3124 aLine.append( " 0 obj\n" );
3125 aLine.append( "<<" );
3126 // number of visible children (all levels)
3127 if( i > 0 || aCounts[0] > 0 )
3129 aLine.append( "/Count " );
3130 aLine.append( aCounts[i] );
3132 if( ! rItem.m_aChildren.empty() )
3134 // children list: First, Last
3135 aLine.append( "/First " );
3136 aLine.append( m_aOutline[rItem.m_aChildren.front()].m_nObject );
3137 aLine.append( " 0 R/Last " );
3138 aLine.append( m_aOutline[rItem.m_aChildren.back()].m_nObject );
3139 aLine.append( " 0 R\n" );
3141 if( i > 0 )
3143 // Title, Dest, Parent, Prev, Next
3144 aLine.append( "/Title" );
3145 appendUnicodeTextStringEncrypt( rItem.m_aTitle, rItem.m_nObject, aLine );
3146 aLine.append( "\n" );
3147 // Dest is not required
3148 if( rItem.m_nDestID >= 0 && rItem.m_nDestID < static_cast<sal_Int32>(m_aDests.size()) )
3150 aLine.append( "/Dest" );
3151 appendDest( rItem.m_nDestID, aLine );
3153 aLine.append( "/Parent " );
3154 aLine.append( rItem.m_nParentObject );
3155 aLine.append( " 0 R" );
3156 if( rItem.m_nPrevObject )
3158 aLine.append( "/Prev " );
3159 aLine.append( rItem.m_nPrevObject );
3160 aLine.append( " 0 R" );
3162 if( rItem.m_nNextObject )
3164 aLine.append( "/Next " );
3165 aLine.append( rItem.m_nNextObject );
3166 aLine.append( " 0 R" );
3169 aLine.append( ">>\nendobj\n\n" );
3170 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3173 return m_aOutline[0].m_nObject;
3176 #undef CHECK_RETURN
3177 #define CHECK_RETURN( x ) if( !x ) return false
3179 bool PDFWriterImpl::appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer )
3181 if( nDestID < 0 || nDestID >= static_cast<sal_Int32>(m_aDests.size()) )
3183 SAL_INFO("vcl.pdfwriter", "ERROR: invalid dest " << static_cast<int>(nDestID) << " requested");
3184 return false;
3187 const PDFDest& rDest = m_aDests[ nDestID ];
3188 const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ];
3190 rBuffer.append( '[' );
3191 rBuffer.append( rDestPage.m_nPageObject );
3192 rBuffer.append( " 0 R" );
3194 switch( rDest.m_eType )
3196 case PDFWriter::DestAreaType::XYZ:
3197 default:
3198 rBuffer.append( "/XYZ " );
3199 appendFixedInt( rDest.m_aRect.Left(), rBuffer );
3200 rBuffer.append( ' ' );
3201 appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
3202 rBuffer.append( " 0" );
3203 break;
3204 case PDFWriter::DestAreaType::FitRectangle:
3205 rBuffer.append( "/FitR " );
3206 appendFixedInt( rDest.m_aRect.Left(), rBuffer );
3207 rBuffer.append( ' ' );
3208 appendFixedInt( rDest.m_aRect.Top(), rBuffer );
3209 rBuffer.append( ' ' );
3210 appendFixedInt( rDest.m_aRect.Right(), rBuffer );
3211 rBuffer.append( ' ' );
3212 appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
3213 break;
3215 rBuffer.append( ']' );
3217 return true;
3220 bool PDFWriterImpl::emitScreenAnnotations()
3222 int nAnnots = m_aScreens.size();
3223 for (int i = 0; i < nAnnots; i++)
3225 const PDFScreen& rScreen = m_aScreens[i];
3227 OStringBuffer aLine;
3228 bool bEmbed = false;
3229 if (!rScreen.m_aTempFileURL.isEmpty())
3231 bEmbed = true;
3232 if (!updateObject(rScreen.m_nTempFileObject))
3233 continue;
3235 SvFileStream aFileStream(rScreen.m_aTempFileURL, StreamMode::READ);
3236 SvMemoryStream aMemoryStream;
3237 aMemoryStream.WriteStream(aFileStream);
3239 aLine.append(rScreen.m_nTempFileObject);
3240 aLine.append(" 0 obj\n");
3241 aLine.append("<< /Type /EmbeddedFile /Length ");
3242 aLine.append(static_cast<sal_Int64>(aMemoryStream.GetSize()));
3243 aLine.append(" >>\nstream\n");
3244 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
3245 aLine.setLength(0);
3247 CHECK_RETURN(writeBuffer(aMemoryStream.GetData(), aMemoryStream.GetSize()));
3249 aLine.append("\nendstream\nendobj\n\n");
3250 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
3251 aLine.setLength(0);
3254 if (!updateObject(rScreen.m_nObject))
3255 continue;
3257 // Annot dictionary.
3258 aLine.append(rScreen.m_nObject);
3259 aLine.append(" 0 obj\n");
3260 aLine.append("<</Type/Annot");
3261 aLine.append("/Subtype/Screen/Rect[");
3262 appendFixedInt(rScreen.m_aRect.Left(), aLine);
3263 aLine.append(' ');
3264 appendFixedInt(rScreen.m_aRect.Top(), aLine);
3265 aLine.append(' ');
3266 appendFixedInt(rScreen.m_aRect.Right(), aLine);
3267 aLine.append(' ');
3268 appendFixedInt(rScreen.m_aRect.Bottom(), aLine);
3269 aLine.append("]");
3271 // Action dictionary.
3272 aLine.append("/A<</Type/Action /S/Rendition /AN ");
3273 aLine.append(rScreen.m_nObject);
3274 aLine.append(" 0 R ");
3276 // Rendition dictionary.
3277 aLine.append("/R<</Type/Rendition /S/MR ");
3279 // MediaClip dictionary.
3280 aLine.append("/C<</Type/MediaClip /S/MCD ");
3281 if (bEmbed)
3283 aLine.append("/D << /Type /Filespec /F (<embedded file>) /EF << /F ");
3284 aLine.append(rScreen.m_nTempFileObject);
3285 aLine.append(" 0 R >> >>");
3287 else
3289 // Linked.
3290 aLine.append("/D << /Type /Filespec /FS /URL /F ");
3291 appendLiteralStringEncrypt(rScreen.m_aURL, rScreen.m_nObject, aLine, osl_getThreadTextEncoding());
3292 aLine.append(" >>");
3294 // Allow playing the video via a tempfile.
3295 aLine.append("/P <</TF (TEMPACCESS)>>");
3296 // Until the real MIME type (instead of application/vnd.sun.star.media) is available here.
3297 aLine.append("/CT (video/mp4)");
3298 aLine.append(">>");
3300 // End Rendition dictionary by requesting play/pause/stop controls.
3301 aLine.append("/P<</BE<</C true >>>>");
3302 aLine.append(">>");
3304 // End Action dictionary.
3305 aLine.append("/OP 0 >>");
3307 // End Annot dictionary.
3308 aLine.append("/P ");
3309 aLine.append(m_aPages[rScreen.m_nPage].m_nPageObject);
3310 aLine.append(" 0 R\n>>\nendobj\n\n");
3311 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
3314 return true;
3317 bool PDFWriterImpl::emitLinkAnnotations()
3319 int nAnnots = m_aLinks.size();
3320 for( int i = 0; i < nAnnots; i++ )
3322 const PDFLink& rLink = m_aLinks[i];
3323 if( ! updateObject( rLink.m_nObject ) )
3324 continue;
3326 OStringBuffer aLine( 1024 );
3327 aLine.append( rLink.m_nObject );
3328 aLine.append( " 0 obj\n" );
3329 // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
3330 // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
3331 aLine.append( "<</Type/Annot" );
3332 if( m_bIsPDF_A1 || m_bIsPDF_A2 )
3333 aLine.append( "/F 4" );
3334 aLine.append( "/Subtype/Link/Border[0 0 0]/Rect[" );
3336 appendFixedInt( rLink.m_aRect.Left()-7, aLine );//the +7 to have a better shape of the border rectangle
3337 aLine.append( ' ' );
3338 appendFixedInt( rLink.m_aRect.Top(), aLine );
3339 aLine.append( ' ' );
3340 appendFixedInt( rLink.m_aRect.Right()+7, aLine );//the +7 to have a better shape of the border rectangle
3341 aLine.append( ' ' );
3342 appendFixedInt( rLink.m_aRect.Bottom(), aLine );
3343 aLine.append( "]" );
3344 if( rLink.m_nDest >= 0 )
3346 aLine.append( "/Dest" );
3347 appendDest( rLink.m_nDest, aLine );
3349 else
3352 destination is external to the document, so
3353 we check in the following sequence:
3355 if target type is neither .pdf, nor .od[tpgs], then
3356 check if relative or absolute and act accordingly (use URI or 'launch application' as requested)
3357 end processing
3358 else if target is .od[tpgs]: then
3359 if conversion of type from od[tpgs] to pdf is requested, convert it and this becomes the new target file
3360 processing continue
3362 if (new)target is .pdf : then
3363 if GotToR is requested, then
3364 convert the target in GoToR where the fragment of the URI is
3365 considered the named destination in the target file, set relative or absolute as requested
3366 else strip the fragment from URL and then set URI or 'launch application' as requested
3369 // FIXME: check if the decode mechanisms for URL processing throughout this implementation
3370 // are the correct one!!
3372 // extract target file type
3373 auto url(URIHelper::resolveIdnaHost(rLink.m_aURL));
3375 INetURLObject aDocumentURL( m_aContext.BaseURL );
3376 INetURLObject aTargetURL( url );
3377 bool bSetGoToRMode = false;
3378 bool bTargetHasPDFExtension = false;
3379 INetProtocol eTargetProtocol = aTargetURL.GetProtocol();
3380 bool bIsUNCPath = false;
3382 // check if the protocol is a known one, or if there is no protocol at all (on target only)
3383 // if there is no protocol, make the target relative to the current document directory
3384 // getting the needed URL information from the current document path
3385 if( eTargetProtocol == INetProtocol::NotValid )
3387 if( url.getLength() > 4 && url.startsWith("\\\\\\\\"))
3389 bIsUNCPath = true;
3391 else
3393 INetURLObject aNewBase( aDocumentURL );//duplicate document URL
3394 aNewBase.removeSegment(); //remove last segment from it, obtaining the base URL of the
3395 //target document
3396 aNewBase.insertName( url );
3397 aTargetURL = aNewBase;//reassign the new target URL
3398 //recompute the target protocol, with the new URL
3399 //normal URL processing resumes
3400 eTargetProtocol = aTargetURL.GetProtocol();
3404 OUString aFileExtension = aTargetURL.GetFileExtension();
3406 // Check if the URL ends in '/': if yes it's a directory,
3407 // it will be forced to a URI link.
3408 // possibly a malformed URI, leave it as it is, force as URI
3409 if( aTargetURL.hasFinalSlash() )
3410 m_aContext.DefaultLinkAction = PDFWriter::URIAction;
3412 if( !aFileExtension.isEmpty() )
3414 if( m_aContext.ConvertOOoTargetToPDFTarget )
3416 bool bChangeFileExtensionToPDF = false;
3417 //examine the file type (.odm .odt. .odp, odg, ods)
3418 if( aFileExtension.equalsIgnoreAsciiCase( "odm" ) )
3419 bChangeFileExtensionToPDF = true;
3420 if( aFileExtension.equalsIgnoreAsciiCase( "odt" ) )
3421 bChangeFileExtensionToPDF = true;
3422 else if( aFileExtension.equalsIgnoreAsciiCase( "odp" ) )
3423 bChangeFileExtensionToPDF = true;
3424 else if( aFileExtension.equalsIgnoreAsciiCase( "odg" ) )
3425 bChangeFileExtensionToPDF = true;
3426 else if( aFileExtension.equalsIgnoreAsciiCase( "ods" ) )
3427 bChangeFileExtensionToPDF = true;
3428 if( bChangeFileExtensionToPDF )
3429 aTargetURL.setExtension("pdf" );
3431 //check if extension is pdf, see if GoToR should be forced
3432 bTargetHasPDFExtension = aTargetURL.GetFileExtension().equalsIgnoreAsciiCase( "pdf" );
3433 if( m_aContext.ForcePDFAction && bTargetHasPDFExtension )
3434 bSetGoToRMode = true;
3436 //prepare the URL, if relative or not
3437 INetProtocol eBaseProtocol = aDocumentURL.GetProtocol();
3438 //queue the string common to all types of actions
3439 aLine.append( "/A<</Type/Action/S");
3440 if( bIsUNCPath ) // handle Win UNC paths
3442 aLine.append( "/Launch/Win<</F" );
3443 // INetURLObject is not good with UNC paths, use original path
3444 appendLiteralStringEncrypt( url, rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
3445 aLine.append( ">>" );
3447 else
3449 bool bSetRelative = false;
3450 bool bFileSpec = false;
3451 //check if relative file link is requested and if the protocol is 'file://'
3452 if( m_aContext.RelFsys && eBaseProtocol == eTargetProtocol && eTargetProtocol == INetProtocol::File )
3453 bSetRelative = true;
3455 OUString aFragment = aTargetURL.GetMark( INetURLObject::DecodeMechanism::NONE /*DecodeMechanism::WithCharset*/ ); //fragment as is,
3456 if( !bSetGoToRMode )
3458 switch( m_aContext.DefaultLinkAction )
3460 default:
3461 case PDFWriter::URIAction :
3462 case PDFWriter::URIActionDestination :
3463 aLine.append( "/URI/URI" );
3464 break;
3465 case PDFWriter::LaunchAction:
3466 // now:
3467 // if a launch action is requested and the hyperlink target has a fragment
3468 // and the target file does not have a pdf extension, or it's not a 'file:://'
3469 // protocol then force the uri action on it
3470 // This code will permit the correct opening of application on web pages,
3471 // the one that normally have fragments (but I may be wrong...)
3472 // and will force the use of URI when the protocol is not file:
3473 if( (!aFragment.isEmpty() && !bTargetHasPDFExtension) ||
3474 eTargetProtocol != INetProtocol::File )
3476 aLine.append( "/URI/URI" );
3478 else
3480 aLine.append( "/Launch/F" );
3481 bFileSpec = true;
3483 break;
3487 //fragment are encoded in the same way as in the named destination processing
3488 if( bSetGoToRMode )
3490 //add the fragment
3491 OUString aURLNoMark = aTargetURL.GetURLNoMark( INetURLObject::DecodeMechanism::WithCharset );
3492 aLine.append("/GoToR");
3493 aLine.append("/F");
3494 appendLiteralStringEncrypt( bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURLNoMark,
3495 INetURLObject::EncodeMechanism::WasEncoded,
3496 INetURLObject::DecodeMechanism::WithCharset ) :
3497 aURLNoMark, rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
3498 if( !aFragment.isEmpty() )
3500 aLine.append("/D/");
3501 appendDestinationName( aFragment , aLine );
3504 else
3506 // change the fragment to accommodate the bookmark (only if the file extension
3507 // is PDF and the requested action is of the correct type)
3508 if(m_aContext.DefaultLinkAction == PDFWriter::URIActionDestination &&
3509 bTargetHasPDFExtension && !aFragment.isEmpty() )
3511 OStringBuffer aLineLoc( 1024 );
3512 appendDestinationName( aFragment , aLineLoc );
3513 //substitute the fragment
3514 aTargetURL.SetMark( OStringToOUString(aLineLoc.makeStringAndClear(), RTL_TEXTENCODING_ASCII_US) );
3516 OUString aURL = aTargetURL.GetMainURL( bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE );
3517 appendLiteralStringEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURL,
3518 INetURLObject::EncodeMechanism::WasEncoded,
3519 bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE
3521 aURL , rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
3524 aLine.append( ">>\n" );
3526 if( rLink.m_nStructParent > 0 )
3528 aLine.append( "/StructParent " );
3529 aLine.append( rLink.m_nStructParent );
3531 aLine.append( ">>\nendobj\n\n" );
3532 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3535 return true;
3538 bool PDFWriterImpl::emitNoteAnnotations()
3540 // emit note annotations
3541 int nAnnots = m_aNotes.size();
3542 for( int i = 0; i < nAnnots; i++ )
3544 const PDFNoteEntry& rNote = m_aNotes[i];
3545 if( ! updateObject( rNote.m_nObject ) )
3546 return false;
3548 OStringBuffer aLine( 1024 );
3549 aLine.append( rNote.m_nObject );
3550 aLine.append( " 0 obj\n" );
3551 // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
3552 // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
3553 aLine.append( "<</Type/Annot" );
3554 if( m_bIsPDF_A1 || m_bIsPDF_A2 )
3555 aLine.append( "/F 4" );
3556 aLine.append( "/Subtype/Text/Rect[" );
3558 appendFixedInt( rNote.m_aRect.Left(), aLine );
3559 aLine.append( ' ' );
3560 appendFixedInt( rNote.m_aRect.Top(), aLine );
3561 aLine.append( ' ' );
3562 appendFixedInt( rNote.m_aRect.Right(), aLine );
3563 aLine.append( ' ' );
3564 appendFixedInt( rNote.m_aRect.Bottom(), aLine );
3565 aLine.append( "]" );
3567 // contents of the note (type text string)
3568 aLine.append( "/Contents\n" );
3569 appendUnicodeTextStringEncrypt( rNote.m_aContents.Contents, rNote.m_nObject, aLine );
3570 aLine.append( "\n" );
3572 // optional title
3573 if( !rNote.m_aContents.Title.isEmpty() )
3575 aLine.append( "/T" );
3576 appendUnicodeTextStringEncrypt( rNote.m_aContents.Title, rNote.m_nObject, aLine );
3577 aLine.append( "\n" );
3580 aLine.append( ">>\nendobj\n\n" );
3581 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3583 return true;
3586 Font PDFWriterImpl::replaceFont( const vcl::Font& rControlFont, const vcl::Font& rAppSetFont )
3588 bool bAdjustSize = false;
3590 Font aFont( rControlFont );
3591 if( aFont.GetFamilyName().isEmpty() )
3593 aFont = rAppSetFont;
3594 if( rControlFont.GetFontHeight() )
3595 aFont.SetFontSize( Size( 0, rControlFont.GetFontHeight() ) );
3596 else
3597 bAdjustSize = true;
3598 if( rControlFont.GetItalic() != ITALIC_DONTKNOW )
3599 aFont.SetItalic( rControlFont.GetItalic() );
3600 if( rControlFont.GetWeight() != WEIGHT_DONTKNOW )
3601 aFont.SetWeight( rControlFont.GetWeight() );
3603 else if( ! aFont.GetFontHeight() )
3605 aFont.SetFontSize( rAppSetFont.GetFontSize() );
3606 bAdjustSize = true;
3608 if( bAdjustSize )
3610 Size aFontSize = aFont.GetFontSize();
3611 OutputDevice* pDefDev = Application::GetDefaultDevice();
3612 aFontSize = OutputDevice::LogicToLogic( aFontSize, pDefDev->GetMapMode(), getMapMode() );
3613 aFont.SetFontSize( aFontSize );
3615 return aFont;
3618 sal_Int32 PDFWriterImpl::getBestBuildinFont( const vcl::Font& rFont )
3620 sal_Int32 nBest = 4; // default to Helvetica
3621 OUString aFontName( rFont.GetFamilyName() );
3622 aFontName = aFontName.toAsciiLowerCase();
3624 if( aFontName.indexOf( "times" ) != -1 )
3625 nBest = 8;
3626 else if( aFontName.indexOf( "courier" ) != -1 )
3627 nBest = 0;
3628 else if( aFontName.indexOf( "dingbats" ) != -1 )
3629 nBest = 13;
3630 else if( aFontName.indexOf( "symbol" ) != -1 )
3631 nBest = 12;
3632 if( nBest < 12 )
3634 if( rFont.GetItalic() == ITALIC_OBLIQUE || rFont.GetItalic() == ITALIC_NORMAL )
3635 nBest += 1;
3636 if( rFont.GetWeight() > WEIGHT_MEDIUM )
3637 nBest += 2;
3640 if( m_aBuildinFontToObjectMap.find( nBest ) == m_aBuildinFontToObjectMap.end() )
3641 m_aBuildinFontToObjectMap[ nBest ] = createObject();
3643 return nBest;
3646 static const Color& replaceColor( const Color& rCol1, const Color& rCol2 )
3648 return (rCol1 == COL_TRANSPARENT) ? rCol2 : rCol1;
3651 void PDFWriterImpl::createDefaultPushButtonAppearance( PDFWidget& rButton, const PDFWriter::PushButtonWidget& rWidget )
3653 const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
3655 // save graphics state
3656 push( PushFlags::ALL );
3658 // transform relative to control's coordinates since an
3659 // appearance stream is a form XObject
3660 // this relies on the m_aRect member of rButton NOT already being transformed
3661 // to default user space
3662 if( rWidget.Background || rWidget.Border )
3664 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetLightColor() ) : COL_TRANSPARENT );
3665 setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetDialogColor() ) : COL_TRANSPARENT );
3666 drawRectangle( rWidget.Location );
3668 // prepare font to use
3669 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetPushButtonFont() );
3670 setFont( aFont );
3671 setTextColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ) );
3673 drawText( rButton.m_aRect, rButton.m_aText, rButton.m_nTextStyle );
3675 // create DA string while local mapmode is still in place
3676 // (that is before endRedirect())
3677 OStringBuffer aDA( 256 );
3678 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ), aDA );
3679 Font aDummyFont( "Helvetica", aFont.GetFontSize() );
3680 sal_Int32 nDummyBuildin = getBestBuildinFont( aDummyFont );
3681 aDA.append( ' ' );
3682 aDA.append(pdf::BuildinFontFace::Get(nDummyBuildin).getNameObject());
3683 aDA.append( ' ' );
3684 m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
3685 aDA.append( " Tf" );
3686 rButton.m_aDAString = aDA.makeStringAndClear();
3688 pop();
3690 rButton.m_aAppearances[ "N" ][ "Standard" ] = new SvMemoryStream();
3692 /* seems like a bad hack but at least works in both AR5 and 6:
3693 we draw the button ourselves and tell AR
3694 the button would be totally transparent with no text
3696 One would expect that simply setting a normal appearance
3697 should suffice, but no, as soon as the user actually presses
3698 the button and an action is tied to it (gasp! a button that
3699 does something) the appearance gets replaced by some crap that AR
3700 creates on the fly even if no DA or MK is given. On AR6 at least
3701 the DA and MK work as expected, but on AR5 this creates a region
3702 filled with the background color but nor text. Urgh.
3704 rButton.m_aMKDict = "/BC [] /BG [] /CA";
3705 rButton.m_aMKDictCAString = "";
3708 Font PDFWriterImpl::drawFieldBorder( PDFWidget& rIntern,
3709 const PDFWriter::AnyWidget& rWidget,
3710 const StyleSettings& rSettings )
3712 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetFieldFont() );
3714 if( rWidget.Background || rWidget.Border )
3716 if( rWidget.Border && rWidget.BorderColor == COL_TRANSPARENT )
3718 sal_Int32 nDelta = GetDPIX() / 500;
3719 if( nDelta < 1 )
3720 nDelta = 1;
3721 setLineColor( COL_TRANSPARENT );
3722 tools::Rectangle aRect = rIntern.m_aRect;
3723 setFillColor( rSettings.GetLightBorderColor() );
3724 drawRectangle( aRect );
3725 aRect.AdjustLeft(nDelta ); aRect.AdjustTop(nDelta );
3726 aRect.AdjustRight( -nDelta ); aRect.AdjustBottom( -nDelta );
3727 setFillColor( rSettings.GetFieldColor() );
3728 drawRectangle( aRect );
3729 setFillColor( rSettings.GetLightColor() );
3730 drawRectangle( tools::Rectangle( Point( aRect.Left(), aRect.Bottom()-nDelta ), aRect.BottomRight() ) );
3731 drawRectangle( tools::Rectangle( Point( aRect.Right()-nDelta, aRect.Top() ), aRect.BottomRight() ) );
3732 setFillColor( rSettings.GetDarkShadowColor() );
3733 drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Left()+nDelta, aRect.Bottom() ) ) );
3734 drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Right(), aRect.Top()+nDelta ) ) );
3736 else
3738 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetShadowColor() ) : COL_TRANSPARENT );
3739 setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
3740 drawRectangle( rIntern.m_aRect );
3743 if( rWidget.Border )
3745 // adjust edit area accounting for border
3746 sal_Int32 nDelta = aFont.GetFontHeight()/4;
3747 if( nDelta < 1 )
3748 nDelta = 1;
3749 rIntern.m_aRect.AdjustLeft(nDelta );
3750 rIntern.m_aRect.AdjustTop(nDelta );
3751 rIntern.m_aRect.AdjustRight( -nDelta );
3752 rIntern.m_aRect.AdjustBottom( -nDelta );
3755 return aFont;
3758 void PDFWriterImpl::createDefaultEditAppearance( PDFWidget& rEdit, const PDFWriter::EditWidget& rWidget )
3760 const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
3761 SvMemoryStream* pEditStream = new SvMemoryStream( 1024, 1024 );
3763 push( PushFlags::ALL );
3765 // prepare font to use, draw field border
3766 Font aFont = drawFieldBorder( rEdit, rWidget, rSettings );
3767 sal_Int32 nBest = getSystemFont( aFont );
3769 // prepare DA string
3770 OStringBuffer aDA( 32 );
3771 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
3772 aDA.append( ' ' );
3773 aDA.append( "/F" );
3774 aDA.append( nBest );
3776 OStringBuffer aDR( 32 );
3777 aDR.append( "/Font " );
3778 aDR.append( getFontDictObject() );
3779 aDR.append( " 0 R" );
3780 rEdit.m_aDRDict = aDR.makeStringAndClear();
3781 aDA.append( ' ' );
3782 m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
3783 aDA.append( " Tf" );
3785 /* create an empty appearance stream, let the viewer create
3786 the appearance at runtime. This is because AR5 seems to
3787 paint the widget appearance always, and a dynamically created
3788 appearance on top of it. AR6 is well behaved in that regard, so
3789 that behaviour seems to be a bug. Anyway this empty appearance
3790 relies on /NeedAppearances in the AcroForm dictionary set to "true"
3792 beginRedirect( pEditStream, rEdit.m_aRect );
3793 OString aAppearance = "/Tx BMC\nEMC\n";
3794 writeBuffer( aAppearance.getStr(), aAppearance.getLength() );
3796 endRedirect();
3797 pop();
3799 rEdit.m_aAppearances[ "N" ][ "Standard" ] = pEditStream;
3801 rEdit.m_aDAString = aDA.makeStringAndClear();
3804 void PDFWriterImpl::createDefaultListBoxAppearance( PDFWidget& rBox, const PDFWriter::ListBoxWidget& rWidget )
3806 const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
3807 SvMemoryStream* pListBoxStream = new SvMemoryStream( 1024, 1024 );
3809 push( PushFlags::ALL );
3811 // prepare font to use, draw field border
3812 Font aFont = drawFieldBorder( rBox, rWidget, rSettings );
3813 sal_Int32 nBest = getSystemFont( aFont );
3815 beginRedirect( pListBoxStream, rBox.m_aRect );
3817 setLineColor( COL_TRANSPARENT );
3818 setFillColor( replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) );
3819 drawRectangle( rBox.m_aRect );
3821 // empty appearance, see createDefaultEditAppearance for reference
3822 OString aAppearance = "/Tx BMC\nEMC\n";
3823 writeBuffer( aAppearance.getStr(), aAppearance.getLength() );
3825 endRedirect();
3826 pop();
3828 rBox.m_aAppearances[ "N" ][ "Standard" ] = pListBoxStream;
3830 // prepare DA string
3831 OStringBuffer aDA( 256 );
3832 // prepare DA string
3833 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
3834 aDA.append( ' ' );
3835 aDA.append( "/F" );
3836 aDA.append( nBest );
3838 OStringBuffer aDR( 32 );
3839 aDR.append( "/Font " );
3840 aDR.append( getFontDictObject() );
3841 aDR.append( " 0 R" );
3842 rBox.m_aDRDict = aDR.makeStringAndClear();
3843 aDA.append( ' ' );
3844 m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
3845 aDA.append( " Tf" );
3846 rBox.m_aDAString = aDA.makeStringAndClear();
3849 void PDFWriterImpl::createDefaultCheckBoxAppearance( PDFWidget& rBox, const PDFWriter::CheckBoxWidget& rWidget )
3851 const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
3853 // save graphics state
3854 push( PushFlags::ALL );
3856 if( rWidget.Background || rWidget.Border )
3858 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
3859 setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
3860 drawRectangle( rBox.m_aRect );
3863 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
3864 setFont( aFont );
3865 Size aFontSize = aFont.GetFontSize();
3866 if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
3867 aFontSize.setHeight( rBox.m_aRect.GetHeight() );
3868 sal_Int32 nDelta = aFontSize.Height()/10;
3869 if( nDelta < 1 )
3870 nDelta = 1;
3872 tools::Rectangle aCheckRect, aTextRect;
3874 aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
3875 aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
3876 aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
3877 aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
3879 // #i74206# handle small controls without text area
3880 while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
3882 aCheckRect.AdjustRight( -nDelta );
3883 aCheckRect.AdjustTop(nDelta/2 );
3884 aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
3887 aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
3888 aTextRect.SetTop( rBox.m_aRect.Top() );
3889 aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
3890 aTextRect.SetBottom( rBox.m_aRect.Bottom() );
3892 setLineColor( COL_BLACK );
3893 setFillColor( COL_TRANSPARENT );
3894 OStringBuffer aLW( 32 );
3895 aLW.append( "q " );
3896 m_aPages[m_nCurrentPage].appendMappedLength( nDelta, aLW );
3897 aLW.append( " w " );
3898 writeBuffer( aLW.getStr(), aLW.getLength() );
3899 drawRectangle( aCheckRect );
3900 writeBuffer( " Q\n", 3 );
3901 setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
3902 drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
3904 pop();
3906 OStringBuffer aDA( 256 );
3908 // tdf#93853 don't rely on Zapf (or any other 'standard' font)
3909 // being present, but our own OpenSymbol - N.B. PDF/A for good
3910 // reasons require even the standard PS fonts to be embedded!
3911 Push();
3912 SetFont( Font( OUString( "OpenSymbol" ), aFont.GetFontSize() ) );
3913 FontCharMapRef pMap;
3914 GetFontCharMap(pMap);
3915 const LogicalFontInstance* pFontInstance = GetFontInstance();
3916 const PhysicalFontFace* pDevFont = pFontInstance->GetFontFace();
3917 Pop();
3919 // make sure OpenSymbol is embedded, and includes our checkmark
3920 const sal_Unicode cMark=0x2713;
3921 const GlyphItem aItem(0, 0, pMap->GetGlyphIndex(cMark),
3922 Point(), GlyphItemFlags::NONE, 0, 0,
3923 const_cast<LogicalFontInstance*>(pFontInstance));
3924 const std::vector<sal_Ucs> aCodeUnits={ cMark };
3925 sal_uInt8 nMappedGlyph;
3926 sal_Int32 nMappedFontObject;
3927 registerGlyph(&aItem, pDevFont, aCodeUnits, nMappedGlyph, nMappedFontObject);
3929 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
3930 aDA.append( ' ' );
3931 aDA.append( "/F" );
3932 aDA.append( nMappedFontObject );
3933 aDA.append( " 0 Tf" );
3935 OStringBuffer aDR( 32 );
3936 aDR.append( "/Font " );
3937 aDR.append( getFontDictObject() );
3938 aDR.append( " 0 R" );
3939 rBox.m_aDRDict = aDR.makeStringAndClear();
3940 rBox.m_aDAString = aDA.makeStringAndClear();
3941 rBox.m_aMKDict = "/CA";
3942 rBox.m_aMKDictCAString = "8";
3943 rBox.m_aRect = aCheckRect;
3945 // create appearance streams
3946 sal_Int32 nCharXOffset = 1000 - 787; // metrics from OpenSymbol
3947 nCharXOffset *= aCheckRect.GetHeight();
3948 nCharXOffset /= 2000;
3949 sal_Int32 nCharYOffset = 1000 - (820-143); // metrics from Zapf
3950 nCharYOffset *= aCheckRect.GetHeight();
3951 nCharYOffset /= 2000;
3953 // write 'checked' appearance stream
3954 SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
3955 beginRedirect( pCheckStream, aCheckRect );
3956 aDA.append( "/Tx BMC\nq BT\n" );
3957 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
3958 aDA.append( ' ' );
3959 aDA.append( "/F" );
3960 aDA.append( nMappedFontObject );
3961 aDA.append( ' ' );
3962 m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
3963 aDA.append( " Tf\n" );
3964 m_aPages[ m_nCurrentPage ].appendMappedLength( nCharXOffset, aDA );
3965 aDA.append( " " );
3966 m_aPages[ m_nCurrentPage ].appendMappedLength( nCharYOffset, aDA );
3967 aDA.append( " Td <" );
3968 appendHex( nMappedGlyph, aDA );
3969 aDA.append( "> Tj\nET\nQ\nEMC\n" );
3970 writeBuffer( aDA.getStr(), aDA.getLength() );
3971 endRedirect();
3972 rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream;
3974 // write 'unchecked' appearance stream
3975 SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
3976 beginRedirect( pUncheckStream, aCheckRect );
3977 writeBuffer( "/Tx BMC\nEMC\n", 12 );
3978 endRedirect();
3979 rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream;
3982 void PDFWriterImpl::createDefaultRadioButtonAppearance( PDFWidget& rBox, const PDFWriter::RadioButtonWidget& rWidget )
3984 const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
3986 // save graphics state
3987 push( PushFlags::ALL );
3989 if( rWidget.Background || rWidget.Border )
3991 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
3992 setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
3993 drawRectangle( rBox.m_aRect );
3996 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
3997 setFont( aFont );
3998 Size aFontSize = aFont.GetFontSize();
3999 if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
4000 aFontSize.setHeight( rBox.m_aRect.GetHeight() );
4001 sal_Int32 nDelta = aFontSize.Height()/10;
4002 if( nDelta < 1 )
4003 nDelta = 1;
4005 tools::Rectangle aCheckRect, aTextRect;
4007 aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
4008 aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
4009 aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
4010 aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
4012 // #i74206# handle small controls without text area
4013 while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
4015 aCheckRect.AdjustRight( -nDelta );
4016 aCheckRect.AdjustTop(nDelta/2 );
4017 aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
4020 aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
4021 aTextRect.SetTop( rBox.m_aRect.Top() );
4022 aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
4023 aTextRect.SetBottom( rBox.m_aRect.Bottom() );
4025 setLineColor( COL_BLACK );
4026 setFillColor( COL_TRANSPARENT );
4027 OStringBuffer aLW( 32 );
4028 aLW.append( "q " );
4029 m_aPages[ m_nCurrentPage ].appendMappedLength( nDelta, aLW );
4030 aLW.append( " w " );
4031 writeBuffer( aLW.getStr(), aLW.getLength() );
4032 drawEllipse( aCheckRect );
4033 writeBuffer( " Q\n", 3 );
4034 setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4035 drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
4037 pop();
4039 OStringBuffer aDA( 256 );
4040 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
4041 rBox.m_aDAString = aDA.makeStringAndClear();
4042 //to encrypt this (el)
4043 rBox.m_aMKDict = "/CA";
4044 //after this assignment, to m_aMKDic cannot be added anything
4045 rBox.m_aMKDictCAString = "l";
4047 rBox.m_aRect = aCheckRect;
4049 // create appearance streams
4050 push( PushFlags::ALL);
4051 SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
4053 beginRedirect( pCheckStream, aCheckRect );
4054 aDA.append( "/Tx BMC\nq BT\n" );
4055 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
4056 aDA.append( ' ' );
4057 m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
4058 aDA.append( " 0 0 Td\nET\nQ\n" );
4059 writeBuffer( aDA.getStr(), aDA.getLength() );
4060 setFillColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4061 setLineColor( COL_TRANSPARENT );
4062 aCheckRect.AdjustLeft(3*nDelta );
4063 aCheckRect.AdjustTop(3*nDelta );
4064 aCheckRect.AdjustBottom( -(3*nDelta) );
4065 aCheckRect.AdjustRight( -(3*nDelta) );
4066 drawEllipse( aCheckRect );
4067 writeBuffer( "\nEMC\n", 5 );
4068 endRedirect();
4070 pop();
4071 rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream;
4073 SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
4074 beginRedirect( pUncheckStream, aCheckRect );
4075 writeBuffer( "/Tx BMC\nEMC\n", 12 );
4076 endRedirect();
4077 rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream;
4080 bool PDFWriterImpl::emitAppearances( PDFWidget& rWidget, OStringBuffer& rAnnotDict )
4082 // TODO: check and insert default streams
4083 OString aStandardAppearance;
4084 switch( rWidget.m_eType )
4086 case PDFWriter::CheckBox:
4087 aStandardAppearance = OUStringToOString( rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US );
4088 break;
4089 default:
4090 break;
4093 if( !rWidget.m_aAppearances.empty() )
4095 rAnnotDict.append( "/AP<<\n" );
4096 for (auto & dict_item : rWidget.m_aAppearances)
4098 rAnnotDict.append( "/" );
4099 rAnnotDict.append( dict_item.first );
4100 bool bUseSubDict = (dict_item.second.size() > 1);
4102 // PDF/A requires sub-dicts for /FT/Btn objects (clause
4103 // 6.3.3)
4104 if( m_bIsPDF_A1 || m_bIsPDF_A2 )
4106 if( rWidget.m_eType == PDFWriter::RadioButton ||
4107 rWidget.m_eType == PDFWriter::CheckBox ||
4108 rWidget.m_eType == PDFWriter::PushButton )
4110 bUseSubDict = true;
4114 rAnnotDict.append( bUseSubDict ? "<<" : " " );
4116 for (auto const& stream_item : dict_item.second)
4118 SvMemoryStream* pApppearanceStream = stream_item.second;
4119 dict_item.second[ stream_item.first ] = nullptr;
4121 bool bDeflate = compressStream( pApppearanceStream );
4123 sal_Int64 nStreamLen = pApppearanceStream->TellEnd();
4124 pApppearanceStream->Seek( STREAM_SEEK_TO_BEGIN );
4125 sal_Int32 nObject = createObject();
4126 CHECK_RETURN( updateObject( nObject ) );
4127 if (g_bDebugDisableCompression)
4129 emitComment( "PDFWriterImpl::emitAppearances" );
4131 OStringBuffer aLine;
4132 aLine.append( nObject );
4134 aLine.append( " 0 obj\n"
4135 "<</Type/XObject\n"
4136 "/Subtype/Form\n"
4137 "/BBox[0 0 " );
4138 appendFixedInt( rWidget.m_aRect.GetWidth()-1, aLine );
4139 aLine.append( " " );
4140 appendFixedInt( rWidget.m_aRect.GetHeight()-1, aLine );
4141 aLine.append( "]\n"
4142 "/Resources " );
4143 aLine.append( getResourceDictObj() );
4144 aLine.append( " 0 R\n"
4145 "/Length " );
4146 aLine.append( nStreamLen );
4147 aLine.append( "\n" );
4148 if( bDeflate )
4149 aLine.append( "/Filter/FlateDecode\n" );
4150 aLine.append( ">>\nstream\n" );
4151 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
4152 checkAndEnableStreamEncryption( nObject );
4153 CHECK_RETURN( writeBuffer( pApppearanceStream->GetData(), nStreamLen ) );
4154 disableStreamEncryption();
4155 CHECK_RETURN( writeBuffer( "\nendstream\nendobj\n\n", 19 ) );
4157 if( bUseSubDict )
4159 rAnnotDict.append( " /" );
4160 rAnnotDict.append( stream_item.first );
4161 rAnnotDict.append( " " );
4163 rAnnotDict.append( nObject );
4164 rAnnotDict.append( " 0 R" );
4166 delete pApppearanceStream;
4169 rAnnotDict.append( bUseSubDict ? ">>\n" : "\n" );
4171 rAnnotDict.append( ">>\n" );
4172 if( !aStandardAppearance.isEmpty() )
4174 rAnnotDict.append( "/AS /" );
4175 rAnnotDict.append( aStandardAppearance );
4176 rAnnotDict.append( "\n" );
4180 return true;
4183 bool PDFWriterImpl::emitWidgetAnnotations()
4185 ensureUniqueRadioOnValues();
4187 int nAnnots = m_aWidgets.size();
4188 for( int a = 0; a < nAnnots; a++ )
4190 PDFWidget& rWidget = m_aWidgets[a];
4192 OStringBuffer aLine( 1024 );
4193 OStringBuffer aValue( 256 );
4194 aLine.append( rWidget.m_nObject );
4195 aLine.append( " 0 obj\n"
4196 "<<" );
4197 if( rWidget.m_eType != PDFWriter::Hierarchy )
4199 // emit widget annotation only for terminal fields
4200 if( rWidget.m_aKids.empty() )
4202 int iRectMargin;
4204 aLine.append( "/Type/Annot/Subtype/Widget/F " );
4206 if (rWidget.m_eType == PDFWriter::Signature)
4208 aLine.append( "132\n" ); // Print & Locked
4209 iRectMargin = 0;
4211 else
4213 aLine.append( "4\n" );
4214 iRectMargin = 1;
4217 aLine.append("/Rect[" );
4218 appendFixedInt( rWidget.m_aRect.Left()-iRectMargin, aLine );
4219 aLine.append( ' ' );
4220 appendFixedInt( rWidget.m_aRect.Top()+iRectMargin, aLine );
4221 aLine.append( ' ' );
4222 appendFixedInt( rWidget.m_aRect.Right()+iRectMargin, aLine );
4223 aLine.append( ' ' );
4224 appendFixedInt( rWidget.m_aRect.Bottom()-iRectMargin, aLine );
4225 aLine.append( "]\n" );
4227 aLine.append( "/FT/" );
4228 switch( rWidget.m_eType )
4230 case PDFWriter::RadioButton:
4231 case PDFWriter::CheckBox:
4232 // for radio buttons only the RadioButton field, not the
4233 // CheckBox children should have a value, else acrobat reader
4234 // does not always check the right button
4235 // of course real check boxes (not belonging to a radio group)
4236 // need their values, too
4237 if( rWidget.m_eType == PDFWriter::RadioButton || rWidget.m_nRadioGroup < 0 )
4239 aValue.append( "/" );
4240 // check for radio group with all buttons unpressed
4241 if( rWidget.m_aValue.isEmpty() )
4242 aValue.append( "Off" );
4243 else
4244 appendName( rWidget.m_aValue, aValue );
4246 [[fallthrough]];
4247 case PDFWriter::PushButton:
4248 aLine.append( "Btn" );
4249 break;
4250 case PDFWriter::ListBox:
4251 if( rWidget.m_nFlags & 0x200000 ) // multiselect
4253 aValue.append( "[" );
4254 for( size_t i = 0; i < rWidget.m_aSelectedEntries.size(); i++ )
4256 sal_Int32 nEntry = rWidget.m_aSelectedEntries[i];
4257 if( nEntry >= 0 && nEntry < sal_Int32(rWidget.m_aListEntries.size()) )
4258 appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ nEntry ], rWidget.m_nObject, aValue );
4260 aValue.append( "]" );
4262 else if( !rWidget.m_aSelectedEntries.empty() &&
4263 rWidget.m_aSelectedEntries[0] >= 0 &&
4264 rWidget.m_aSelectedEntries[0] < sal_Int32(rWidget.m_aListEntries.size()) )
4266 appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ rWidget.m_aSelectedEntries[0] ], rWidget.m_nObject, aValue );
4268 else
4269 appendUnicodeTextStringEncrypt( OUString(), rWidget.m_nObject, aValue );
4270 aLine.append( "Ch" );
4271 break;
4272 case PDFWriter::ComboBox:
4273 appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue );
4274 aLine.append( "Ch" );
4275 break;
4276 case PDFWriter::Edit:
4277 aLine.append( "Tx" );
4278 appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue );
4279 break;
4280 case PDFWriter::Signature:
4281 aLine.append( "Sig" );
4282 aValue.append(OUStringToOString(rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US));
4283 break;
4284 case PDFWriter::Hierarchy: // make the compiler happy
4285 break;
4287 aLine.append( "\n" );
4288 aLine.append( "/P " );
4289 aLine.append( m_aPages[ rWidget.m_nPage ].m_nPageObject );
4290 aLine.append( " 0 R\n" );
4292 if( rWidget.m_nParent )
4294 aLine.append( "/Parent " );
4295 aLine.append( rWidget.m_nParent );
4296 aLine.append( " 0 R\n" );
4298 if( !rWidget.m_aKids.empty() )
4300 aLine.append( "/Kids[" );
4301 for( size_t i = 0; i < rWidget.m_aKids.size(); i++ )
4303 aLine.append( rWidget.m_aKids[i] );
4304 aLine.append( " 0 R" );
4305 aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
4307 aLine.append( "]\n" );
4309 if( !rWidget.m_aName.isEmpty() )
4311 aLine.append( "/T" );
4312 appendLiteralStringEncrypt( rWidget.m_aName, rWidget.m_nObject, aLine );
4313 aLine.append( "\n" );
4315 if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2 && !rWidget.m_aDescription.isEmpty() )
4317 // the alternate field name should be unicode able since it is
4318 // supposed to be used in UI
4319 aLine.append( "/TU" );
4320 appendUnicodeTextStringEncrypt( rWidget.m_aDescription, rWidget.m_nObject, aLine );
4321 aLine.append( "\n" );
4324 if( rWidget.m_nFlags )
4326 aLine.append( "/Ff " );
4327 aLine.append( rWidget.m_nFlags );
4328 aLine.append( "\n" );
4330 if( !aValue.isEmpty() )
4332 OString aVal = aValue.makeStringAndClear();
4333 aLine.append( "/V " );
4334 aLine.append( aVal );
4335 aLine.append( "\n"
4336 "/DV " );
4337 aLine.append( aVal );
4338 aLine.append( "\n" );
4340 if( rWidget.m_eType == PDFWriter::ListBox || rWidget.m_eType == PDFWriter::ComboBox )
4342 sal_Int32 nTI = -1;
4343 aLine.append( "/Opt[\n" );
4344 sal_Int32 i = 0;
4345 for (auto const& entry : rWidget.m_aListEntries)
4347 appendUnicodeTextStringEncrypt( entry, rWidget.m_nObject, aLine );
4348 aLine.append( "\n" );
4349 if( entry == rWidget.m_aValue )
4350 nTI = i;
4351 ++i;
4353 aLine.append( "]\n" );
4354 if( nTI > 0 )
4356 aLine.append( "/TI " );
4357 aLine.append( nTI );
4358 aLine.append( "\n" );
4359 if( rWidget.m_nFlags & 0x200000 ) // Multiselect
4361 aLine.append( "/I [" );
4362 aLine.append( nTI );
4363 aLine.append( "]\n" );
4367 if( rWidget.m_eType == PDFWriter::Edit && rWidget.m_nMaxLen > 0 )
4369 aLine.append( "/MaxLen " );
4370 aLine.append( rWidget.m_nMaxLen );
4371 aLine.append( "\n" );
4373 if( rWidget.m_eType == PDFWriter::PushButton )
4375 if(!m_bIsPDF_A1)
4377 OStringBuffer aDest;
4378 if( rWidget.m_nDest != -1 && appendDest( m_aDestinationIdTranslation[ rWidget.m_nDest ], aDest ) )
4380 aLine.append( "/AA<</D<</Type/Action/S/GoTo/D " );
4381 aLine.append( aDest.makeStringAndClear() );
4382 aLine.append( ">>>>\n" );
4384 else if( rWidget.m_aListEntries.empty() )
4386 if( !m_bIsPDF_A2 )
4388 // create a reset form action
4389 aLine.append( "/AA<</D<</Type/Action/S/ResetForm>>>>\n" );
4392 else if( rWidget.m_bSubmit )
4394 // create a submit form action
4395 aLine.append( "/AA<</D<</Type/Action/S/SubmitForm/F" );
4396 appendLiteralStringEncrypt( rWidget.m_aListEntries.front(), rWidget.m_nObject, aLine, osl_getThreadTextEncoding() );
4397 aLine.append( "/Flags " );
4399 sal_Int32 nFlags = 0;
4400 switch( m_aContext.SubmitFormat )
4402 case PDFWriter::HTML:
4403 nFlags |= 4;
4404 break;
4405 case PDFWriter::XML:
4406 if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
4407 nFlags |= 32;
4408 break;
4409 case PDFWriter::PDF:
4410 if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
4411 nFlags |= 256;
4412 break;
4413 case PDFWriter::FDF:
4414 default:
4415 break;
4417 if( rWidget.m_bSubmitGet )
4418 nFlags |= 8;
4419 aLine.append( nFlags );
4420 aLine.append( ">>>>\n" );
4422 else
4424 // create a URI action
4425 aLine.append( "/AA<</D<</Type/Action/S/URI/URI(" );
4426 aLine.append( OUStringToOString( rWidget.m_aListEntries.front(), RTL_TEXTENCODING_ASCII_US ) );
4427 aLine.append( ")>>>>\n" );
4430 else
4431 m_aErrors.insert( PDFWriter::Warning_FormAction_Omitted_PDFA );
4433 if( !rWidget.m_aDAString.isEmpty() )
4435 if( !rWidget.m_aDRDict.isEmpty() )
4437 aLine.append( "/DR<<" );
4438 aLine.append( rWidget.m_aDRDict );
4439 aLine.append( ">>\n" );
4441 else
4443 aLine.append( "/DR<</Font<<" );
4444 appendBuildinFontsToDict( aLine );
4445 aLine.append( ">>>>\n" );
4447 aLine.append( "/DA" );
4448 appendLiteralStringEncrypt( rWidget.m_aDAString, rWidget.m_nObject, aLine );
4449 aLine.append( "\n" );
4450 if( rWidget.m_nTextStyle & DrawTextFlags::Center )
4451 aLine.append( "/Q 1\n" );
4452 else if( rWidget.m_nTextStyle & DrawTextFlags::Right )
4453 aLine.append( "/Q 2\n" );
4455 // appearance characteristics for terminal fields
4456 // which are supposed to have an appearance constructed
4457 // by the viewer application
4458 if( !rWidget.m_aMKDict.isEmpty() )
4460 aLine.append( "/MK<<" );
4461 aLine.append( rWidget.m_aMKDict );
4462 //add the CA string, encrypting it
4463 appendLiteralStringEncrypt(rWidget.m_aMKDictCAString, rWidget.m_nObject, aLine);
4464 aLine.append( ">>\n" );
4467 CHECK_RETURN( emitAppearances( rWidget, aLine ) );
4469 aLine.append( ">>\n"
4470 "endobj\n\n" );
4471 CHECK_RETURN( updateObject( rWidget.m_nObject ) );
4472 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
4474 return true;
4477 bool PDFWriterImpl::emitAnnotations()
4479 if( m_aPages.empty() )
4480 return false;
4482 CHECK_RETURN( emitLinkAnnotations() );
4483 CHECK_RETURN(emitScreenAnnotations());
4484 CHECK_RETURN( emitNoteAnnotations() );
4485 CHECK_RETURN( emitWidgetAnnotations() );
4487 return true;
4490 bool PDFWriterImpl::emitEmbeddedFiles()
4492 for (auto& rEmbeddedFile : m_aEmbeddedFiles)
4494 if (!updateObject(rEmbeddedFile.m_nObject))
4495 continue;
4497 OStringBuffer aLine;
4498 aLine.append(rEmbeddedFile.m_nObject);
4499 aLine.append(" 0 obj\n");
4500 aLine.append("<< /Type /EmbeddedFile /Length ");
4501 aLine.append(static_cast<sal_Int64>(rEmbeddedFile.m_pData->size()));
4502 aLine.append(" >>\nstream\n");
4503 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
4504 aLine.setLength(0);
4506 CHECK_RETURN(writeBuffer(rEmbeddedFile.m_pData->data(), rEmbeddedFile.m_pData->size()));
4508 aLine.append("\nendstream\nendobj\n\n");
4509 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
4511 return true;
4514 #undef CHECK_RETURN
4515 #define CHECK_RETURN( x ) if( !x ) return false
4517 bool PDFWriterImpl::emitCatalog()
4519 // build page tree
4520 // currently there is only one node that contains all leaves
4522 // first create a page tree node id
4523 sal_Int32 nTreeNode = createObject();
4525 // emit global resource dictionary (page emit needs it)
4526 CHECK_RETURN( emitResources() );
4528 // emit all pages
4529 for (auto & page : m_aPages)
4530 if( ! page.emit( nTreeNode ) )
4531 return false;
4533 sal_Int32 nNamedDestinationsDictionary = emitNamedDestinations();
4535 sal_Int32 nOutlineDict = emitOutline();
4537 // emit Output intent
4538 sal_Int32 nOutputIntentObject = emitOutputIntent();
4540 // emit metadata
4541 sal_Int32 nMetadataObject = emitDocumentMetadata();
4543 sal_Int32 nStructureDict = 0;
4544 if(m_aStructure.size() > 1)
4546 // check if dummy structure containers are needed
4547 addInternalStructureContainer(m_aStructure[0]);
4548 nStructureDict = m_aStructure[0].m_nObject = createObject();
4549 emitStructure( m_aStructure[ 0 ] );
4552 // adjust tree node file offset
4553 if( ! updateObject( nTreeNode ) )
4554 return false;
4556 // emit tree node
4557 OStringBuffer aLine( 2048 );
4558 aLine.append( nTreeNode );
4559 aLine.append( " 0 obj\n" );
4560 aLine.append( "<</Type/Pages\n" );
4561 aLine.append( "/Resources " );
4562 aLine.append( getResourceDictObj() );
4563 aLine.append( " 0 R\n" );
4565 sal_Int32 nMediaBoxWidth = 0;
4566 sal_Int32 nMediaBoxHeight = 0;
4567 if( m_aPages.empty() ) // sanity check, this should not happen
4569 nMediaBoxWidth = g_nInheritedPageWidth;
4570 nMediaBoxHeight = g_nInheritedPageHeight;
4572 else
4574 for (auto const& page : m_aPages)
4576 if( page.m_nPageWidth > nMediaBoxWidth )
4577 nMediaBoxWidth = page.m_nPageWidth;
4578 if( page.m_nPageHeight > nMediaBoxHeight )
4579 nMediaBoxHeight = page.m_nPageHeight;
4582 aLine.append( "/MediaBox[ 0 0 " );
4583 aLine.append( nMediaBoxWidth );
4584 aLine.append( ' ' );
4585 aLine.append( nMediaBoxHeight );
4586 aLine.append( " ]\n"
4587 "/Kids[ " );
4588 unsigned int i = 0;
4589 for (const auto & page : m_aPages)
4591 aLine.append( page.m_nPageObject );
4592 aLine.append( " 0 R" );
4593 aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
4594 ++i;
4596 aLine.append( "]\n"
4597 "/Count " );
4598 aLine.append( static_cast<sal_Int32>(m_aPages.size()) );
4599 aLine.append( ">>\n"
4600 "endobj\n\n" );
4601 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
4603 // emit annotation objects
4604 CHECK_RETURN( emitAnnotations() );
4605 CHECK_RETURN( emitEmbeddedFiles() );
4607 // emit Catalog
4608 m_nCatalogObject = createObject();
4609 if( ! updateObject( m_nCatalogObject ) )
4610 return false;
4611 aLine.setLength( 0 );
4612 aLine.append( m_nCatalogObject );
4613 aLine.append( " 0 obj\n"
4614 "<</Type/Catalog/Pages " );
4615 aLine.append( nTreeNode );
4616 aLine.append( " 0 R\n" );
4618 // check if there are named destinations to emit (root must be inside the catalog)
4619 if( nNamedDestinationsDictionary )
4621 aLine.append("/Dests ");
4622 aLine.append( nNamedDestinationsDictionary );
4623 aLine.append( " 0 R\n" );
4626 if( m_aContext.PageLayout != PDFWriter::DefaultLayout )
4627 switch( m_aContext.PageLayout )
4629 default :
4630 case PDFWriter::SinglePage :
4631 aLine.append( "/PageLayout/SinglePage\n" );
4632 break;
4633 case PDFWriter::Continuous :
4634 aLine.append( "/PageLayout/OneColumn\n" );
4635 break;
4636 case PDFWriter::ContinuousFacing :
4637 // the flag m_aContext.FirstPageLeft below is used to set the page on the left side
4638 aLine.append( "/PageLayout/TwoColumnRight\n" );//odd page on the right side
4639 break;
4641 if( m_aContext.PDFDocumentMode != PDFWriter::ModeDefault && !m_aContext.OpenInFullScreenMode )
4642 switch( m_aContext.PDFDocumentMode )
4644 default :
4645 aLine.append( "/PageMode/UseNone\n" );
4646 break;
4647 case PDFWriter::UseOutlines :
4648 aLine.append( "/PageMode/UseOutlines\n" ); //document is opened with outline pane open
4649 break;
4650 case PDFWriter::UseThumbs :
4651 aLine.append( "/PageMode/UseThumbs\n" ); //document is opened with thumbnails pane open
4652 break;
4654 else if( m_aContext.OpenInFullScreenMode )
4655 aLine.append( "/PageMode/FullScreen\n" ); //document is opened full screen
4657 OStringBuffer aInitPageRef;
4658 if( m_aContext.InitialPage >= 0 && m_aContext.InitialPage < static_cast<sal_Int32>(m_aPages.size()) )
4660 aInitPageRef.append( m_aPages[m_aContext.InitialPage].m_nPageObject );
4661 aInitPageRef.append( " 0 R" );
4663 else
4664 aInitPageRef.append( "0" );
4666 switch( m_aContext.PDFDocumentAction )
4668 case PDFWriter::ActionDefault : //do nothing, this is the Acrobat default
4669 default:
4670 if( aInitPageRef.getLength() > 1 )
4672 aLine.append( "/OpenAction[" );
4673 aLine.append( aInitPageRef.makeStringAndClear() );
4674 aLine.append( " /XYZ null null 0]\n" );
4676 break;
4677 case PDFWriter::FitInWindow :
4678 aLine.append( "/OpenAction[" );
4679 aLine.append( aInitPageRef.makeStringAndClear() );
4680 aLine.append( " /Fit]\n" ); //Open fit page
4681 break;
4682 case PDFWriter::FitWidth :
4683 aLine.append( "/OpenAction[" );
4684 aLine.append( aInitPageRef.makeStringAndClear() );
4685 aLine.append( " /FitH " );
4686 aLine.append( g_nInheritedPageHeight );//Open fit width
4687 aLine.append( "]\n" );
4688 break;
4689 case PDFWriter::FitVisible :
4690 aLine.append( "/OpenAction[" );
4691 aLine.append( aInitPageRef.makeStringAndClear() );
4692 aLine.append( " /FitBH " );
4693 aLine.append( g_nInheritedPageHeight );//Open fit visible
4694 aLine.append( "]\n" );
4695 break;
4696 case PDFWriter::ActionZoom :
4697 aLine.append( "/OpenAction[" );
4698 aLine.append( aInitPageRef.makeStringAndClear() );
4699 aLine.append( " /XYZ null null " );
4700 if( m_aContext.Zoom >= 50 && m_aContext.Zoom <= 1600 )
4701 aLine.append( static_cast<double>(m_aContext.Zoom)/100.0 );
4702 else
4703 aLine.append( "0" );
4704 aLine.append( "]\n" );
4705 break;
4708 // viewer preferences, if we had some, then emit
4709 if( m_aContext.HideViewerToolbar ||
4710 ( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 && !m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle ) ||
4711 m_aContext.HideViewerMenubar ||
4712 m_aContext.HideViewerWindowControls || m_aContext.FitWindow ||
4713 m_aContext.CenterWindow || (m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing ) ||
4714 m_aContext.OpenInFullScreenMode )
4716 aLine.append( "/ViewerPreferences<<" );
4717 if( m_aContext.HideViewerToolbar )
4718 aLine.append( "/HideToolbar true\n" );
4719 if( m_aContext.HideViewerMenubar )
4720 aLine.append( "/HideMenubar true\n" );
4721 if( m_aContext.HideViewerWindowControls )
4722 aLine.append( "/HideWindowUI true\n" );
4723 if( m_aContext.FitWindow )
4724 aLine.append( "/FitWindow true\n" );
4725 if( m_aContext.CenterWindow )
4726 aLine.append( "/CenterWindow true\n" );
4727 if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 && !m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle )
4728 aLine.append( "/DisplayDocTitle true\n" );
4729 if( m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing )
4730 aLine.append( "/Direction/R2L\n" );
4731 if( m_aContext.OpenInFullScreenMode )
4732 switch( m_aContext.PDFDocumentMode )
4734 default :
4735 case PDFWriter::ModeDefault :
4736 aLine.append( "/NonFullScreenPageMode/UseNone\n" );
4737 break;
4738 case PDFWriter::UseOutlines :
4739 aLine.append( "/NonFullScreenPageMode/UseOutlines\n" );
4740 break;
4741 case PDFWriter::UseThumbs :
4742 aLine.append( "/NonFullScreenPageMode/UseThumbs\n" );
4743 break;
4745 aLine.append( ">>\n" );
4748 if( nOutlineDict )
4750 aLine.append( "/Outlines " );
4751 aLine.append( nOutlineDict );
4752 aLine.append( " 0 R\n" );
4754 if( nStructureDict )
4756 aLine.append( "/StructTreeRoot " );
4757 aLine.append( nStructureDict );
4758 aLine.append( " 0 R\n" );
4760 if( !m_aContext.DocumentLocale.Language.isEmpty() )
4762 /* PDF allows only RFC 3066, see above in emitStructure(). */
4763 LanguageTag aLanguageTag( m_aContext.DocumentLocale);
4764 OUString aLanguage, aScript, aCountry;
4765 aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
4766 if (!aLanguage.isEmpty())
4768 OUStringBuffer aLocBuf( 16 );
4769 aLocBuf.append( aLanguage );
4770 if( !aCountry.isEmpty() )
4772 aLocBuf.append( '-' );
4773 aLocBuf.append( aCountry );
4775 aLine.append( "/Lang" );
4776 appendLiteralStringEncrypt( aLocBuf.makeStringAndClear(), m_nCatalogObject, aLine );
4777 aLine.append( "\n" );
4780 if( m_aContext.Tagged && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
4782 aLine.append( "/MarkInfo<</Marked true>>\n" );
4784 if( !m_aWidgets.empty() )
4786 aLine.append( "/AcroForm<</Fields[\n" );
4787 int nWidgets = m_aWidgets.size();
4788 int nOut = 0;
4789 for( int j = 0; j < nWidgets; j++ )
4791 // output only root fields
4792 if( m_aWidgets[j].m_nParent < 1 )
4794 aLine.append( m_aWidgets[j].m_nObject );
4795 aLine.append( (nOut++ % 5)==4 ? " 0 R\n" : " 0 R " );
4798 aLine.append( "\n]" );
4800 #if HAVE_FEATURE_NSS
4801 if (m_nSignatureObject != -1)
4802 aLine.append( "/SigFlags 3");
4803 #endif
4805 aLine.append( "/DR " );
4806 aLine.append( getResourceDictObj() );
4807 aLine.append( " 0 R" );
4808 // NeedAppearances must not be used if PDF is signed
4809 if( m_bIsPDF_A1 || m_bIsPDF_A2
4810 #if HAVE_FEATURE_NSS
4811 || ( m_nSignatureObject != -1 )
4812 #endif
4814 aLine.append( ">>\n" );
4815 else
4816 aLine.append( "/NeedAppearances true>>\n" );
4819 //check if there is a Metadata object
4820 if( nOutputIntentObject )
4822 aLine.append("/OutputIntents[");
4823 aLine.append( nOutputIntentObject );
4824 aLine.append( " 0 R]" );
4827 if( nMetadataObject )
4829 aLine.append("/Metadata ");
4830 aLine.append( nMetadataObject );
4831 aLine.append( " 0 R" );
4834 aLine.append( ">>\n"
4835 "endobj\n\n" );
4836 return writeBuffer( aLine.getStr(), aLine.getLength() );
4839 #if HAVE_FEATURE_NSS
4841 bool PDFWriterImpl::emitSignature()
4843 if( !updateObject( m_nSignatureObject ) )
4844 return false;
4846 OStringBuffer aLine( 0x5000 );
4847 aLine.append( m_nSignatureObject );
4848 aLine.append( " 0 obj\n" );
4849 aLine.append("<</Contents <" );
4851 sal_uInt64 nOffset = ~0U;
4852 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nOffset) ) );
4854 m_nSignatureContentOffset = nOffset + aLine.getLength();
4856 // reserve some space for the PKCS#7 object
4857 OStringBuffer aContentFiller( MAX_SIGNATURE_CONTENT_LENGTH );
4858 comphelper::string::padToLength(aContentFiller, MAX_SIGNATURE_CONTENT_LENGTH, '0');
4859 aLine.append( aContentFiller.makeStringAndClear() );
4860 aLine.append( ">\n/Type/Sig/SubFilter/adbe.pkcs7.detached");
4862 if( !m_aContext.DocumentInfo.Author.isEmpty() )
4864 aLine.append( "/Name" );
4865 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Author, m_nSignatureObject, aLine );
4868 aLine.append( " /M ");
4869 appendLiteralStringEncrypt( m_aCreationDateString, m_nSignatureObject, aLine );
4871 aLine.append( " /ByteRange [ 0 ");
4872 aLine.append( m_nSignatureContentOffset - 1 );
4873 aLine.append( " " );
4874 aLine.append( m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1 );
4875 aLine.append( " " );
4877 m_nSignatureLastByteRangeNoOffset = nOffset + aLine.getLength();
4879 // mark the last ByteRange no and add some space. Now, we don't know
4880 // how many bytes we need for this ByteRange value
4881 // The real value will be overwritten in the finalizeSignature method
4882 OStringBuffer aByteRangeFiller( 100 );
4883 comphelper::string::padToLength(aByteRangeFiller, 100, ' ');
4884 aLine.append( aByteRangeFiller.makeStringAndClear() );
4885 aLine.append(" /Filter/Adobe.PPKMS");
4887 //emit reason, location and contactinfo
4888 if ( !m_aContext.SignReason.isEmpty() )
4890 aLine.append("/Reason");
4891 appendUnicodeTextStringEncrypt( m_aContext.SignReason, m_nSignatureObject, aLine );
4894 if ( !m_aContext.SignLocation.isEmpty() )
4896 aLine.append("/Location");
4897 appendUnicodeTextStringEncrypt( m_aContext.SignLocation, m_nSignatureObject, aLine );
4900 if ( !m_aContext.SignContact.isEmpty() )
4902 aLine.append("/ContactInfo");
4903 appendUnicodeTextStringEncrypt( m_aContext.SignContact, m_nSignatureObject, aLine );
4906 aLine.append(" >>\nendobj\n\n" );
4908 return writeBuffer( aLine.getStr(), aLine.getLength() );
4911 bool PDFWriterImpl::finalizeSignature()
4913 if (!m_aContext.SignCertificate.is())
4914 return false;
4916 // 1- calculate last ByteRange value
4917 sal_uInt64 nOffset = ~0U;
4918 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nOffset) ) );
4920 sal_Int64 nLastByteRangeNo = nOffset - (m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1);
4922 // 2- overwrite the value to the m_nSignatureLastByteRangeNoOffset position
4923 sal_uInt64 nWritten = 0;
4924 CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureLastByteRangeNoOffset) ) );
4925 OString aByteRangeNo = OString::number( nLastByteRangeNo ) + " ]";
4927 if (m_aFile.write(aByteRangeNo.getStr(), aByteRangeNo.getLength(), nWritten) != osl::File::E_None)
4929 CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset)) );
4930 return false;
4933 // 3- create the PKCS#7 object using NSS
4935 // Prepare buffer and calculate PDF file digest
4936 CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, 0)) );
4938 std::unique_ptr<char[]> buffer1(new char[m_nSignatureContentOffset + 1]);
4939 sal_uInt64 bytesRead1;
4941 //FIXME: Check if hash is calculated from the correct byterange
4942 if (osl::File::E_None != m_aFile.read(buffer1.get(), m_nSignatureContentOffset - 1 , bytesRead1) ||
4943 bytesRead1 != static_cast<sal_uInt64>(m_nSignatureContentOffset) - 1)
4945 SAL_WARN("vcl.pdfwriter", "First buffer read failed");
4946 return false;
4949 std::unique_ptr<char[]> buffer2(new char[nLastByteRangeNo + 1]);
4950 sal_uInt64 bytesRead2;
4952 if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1) ||
4953 osl::File::E_None != m_aFile.read(buffer2.get(), nLastByteRangeNo, bytesRead2) ||
4954 bytesRead2 != static_cast<sal_uInt64>(nLastByteRangeNo))
4956 SAL_WARN("vcl.pdfwriter", "Second buffer read failed");
4957 return false;
4960 OStringBuffer aCMSHexBuffer;
4961 svl::crypto::Signing aSigning(m_aContext.SignCertificate);
4962 aSigning.AddDataRange(buffer1.get(), bytesRead1);
4963 aSigning.AddDataRange(buffer2.get(), bytesRead2);
4964 aSigning.SetSignTSA(m_aContext.SignTSA);
4965 aSigning.SetSignPassword(m_aContext.SignPassword);
4966 if (!aSigning.Sign(aCMSHexBuffer))
4968 SAL_WARN("vcl.pdfwriter", "PDFWriter::Sign() failed");
4969 return false;
4972 assert(aCMSHexBuffer.getLength() <= MAX_SIGNATURE_CONTENT_LENGTH);
4974 // Set file pointer to the m_nSignatureContentOffset, we're ready to overwrite PKCS7 object
4975 nWritten = 0;
4976 CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset)) );
4977 m_aFile.write(aCMSHexBuffer.getStr(), aCMSHexBuffer.getLength(), nWritten);
4979 return osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset);
4982 #endif //HAVE_FEATURE_NSS
4984 sal_Int32 PDFWriterImpl::emitInfoDict( )
4986 sal_Int32 nObject = createObject();
4988 if( updateObject( nObject ) )
4990 OStringBuffer aLine( 1024 );
4991 aLine.append( nObject );
4992 aLine.append( " 0 obj\n"
4993 "<<" );
4994 if( !m_aContext.DocumentInfo.Title.isEmpty() )
4996 aLine.append( "/Title" );
4997 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Title, nObject, aLine );
4998 aLine.append( "\n" );
5000 if( !m_aContext.DocumentInfo.Author.isEmpty() )
5002 aLine.append( "/Author" );
5003 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Author, nObject, aLine );
5004 aLine.append( "\n" );
5006 if( !m_aContext.DocumentInfo.Subject.isEmpty() )
5008 aLine.append( "/Subject" );
5009 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Subject, nObject, aLine );
5010 aLine.append( "\n" );
5012 if( !m_aContext.DocumentInfo.Keywords.isEmpty() )
5014 aLine.append( "/Keywords" );
5015 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Keywords, nObject, aLine );
5016 aLine.append( "\n" );
5018 if( !m_aContext.DocumentInfo.Creator.isEmpty() )
5020 aLine.append( "/Creator" );
5021 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Creator, nObject, aLine );
5022 aLine.append( "\n" );
5024 if( !m_aContext.DocumentInfo.Producer.isEmpty() )
5026 aLine.append( "/Producer" );
5027 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Producer, nObject, aLine );
5028 aLine.append( "\n" );
5031 aLine.append( "/CreationDate" );
5032 appendLiteralStringEncrypt( m_aCreationDateString, nObject, aLine );
5033 aLine.append( ">>\nendobj\n\n" );
5034 if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
5035 nObject = 0;
5037 else
5038 nObject = 0;
5040 return nObject;
5043 // Part of this function may be shared with method appendDest.
5044 sal_Int32 PDFWriterImpl::emitNamedDestinations()
5046 sal_Int32 nCount = m_aNamedDests.size();
5047 if( nCount <= 0 )
5048 return 0;//define internal error
5050 //get the object number for all the destinations
5051 sal_Int32 nObject = createObject();
5053 if( updateObject( nObject ) )
5055 //emit the dictionary
5056 OStringBuffer aLine( 1024 );
5057 aLine.append( nObject );
5058 aLine.append( " 0 obj\n"
5059 "<<" );
5061 sal_Int32 nDestID;
5062 for( nDestID = 0; nDestID < nCount; nDestID++ )
5064 const PDFNamedDest& rDest = m_aNamedDests[ nDestID ];
5065 // In order to correctly function both under an Internet browser and
5066 // directly with a reader (provided the reader has the feature) we
5067 // need to set the name of the destination the same way it will be encoded
5068 // in an Internet link
5069 INetURLObject aLocalURL( "http://ahost.ax" ); //dummy location, won't be used
5070 aLocalURL.SetMark( rDest.m_aDestName );
5072 const OUString aName = aLocalURL.GetMark( INetURLObject::DecodeMechanism::NONE ); //same coding as
5073 // in link creation ( see PDFWriterImpl::emitLinkAnnotations )
5074 const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ];
5076 aLine.append( '/' );
5077 appendDestinationName( aName, aLine ); // this conversion must be done when forming the link to target ( see in emitCatalog )
5078 aLine.append( '[' ); // the '[' can be emitted immediately, because the appendDestinationName function
5079 //maps the preceding character properly
5080 aLine.append( rDestPage.m_nPageObject );
5081 aLine.append( " 0 R" );
5083 switch( rDest.m_eType )
5085 case PDFWriter::DestAreaType::XYZ:
5086 default:
5087 aLine.append( "/XYZ " );
5088 appendFixedInt( rDest.m_aRect.Left(), aLine );
5089 aLine.append( ' ' );
5090 appendFixedInt( rDest.m_aRect.Bottom(), aLine );
5091 aLine.append( " 0" );
5092 break;
5093 case PDFWriter::DestAreaType::FitRectangle:
5094 aLine.append( "/FitR " );
5095 appendFixedInt( rDest.m_aRect.Left(), aLine );
5096 aLine.append( ' ' );
5097 appendFixedInt( rDest.m_aRect.Top(), aLine );
5098 aLine.append( ' ' );
5099 appendFixedInt( rDest.m_aRect.Right(), aLine );
5100 aLine.append( ' ' );
5101 appendFixedInt( rDest.m_aRect.Bottom(), aLine );
5102 break;
5104 aLine.append( "]\n" );
5107 //close
5108 aLine.append( ">>\nendobj\n\n" );
5109 if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
5110 nObject = 0;
5112 else
5113 nObject = 0;
5115 return nObject;
5118 // emits the output intent dictionary
5119 sal_Int32 PDFWriterImpl::emitOutputIntent()
5121 if( !m_bIsPDF_A1 && !m_bIsPDF_A2 )
5122 return 0;
5124 //emit the sRGB standard profile, in ICC format, in a stream, per IEC61966-2.1
5126 OStringBuffer aLine( 1024 );
5127 sal_Int32 nICCObject = createObject();
5128 sal_Int32 nStreamLengthObject = createObject();
5130 aLine.append( nICCObject );
5131 // sRGB has 3 colors, hence /N 3 below (PDF 1.4 table 4.16)
5132 aLine.append( " 0 obj\n<</N 3/Length " );
5133 aLine.append( nStreamLengthObject );
5134 aLine.append( " 0 R" );
5135 if (!g_bDebugDisableCompression)
5136 aLine.append( "/Filter/FlateDecode" );
5137 aLine.append( ">>\nstream\n" );
5138 if ( !updateObject( nICCObject ) ) return 0;
5139 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0;
5140 //get file position
5141 sal_uInt64 nBeginStreamPos = 0;
5142 if (osl::File::E_None != m_aFile.getPos(nBeginStreamPos))
5143 return 0;
5144 beginCompression();
5145 checkAndEnableStreamEncryption( nICCObject );
5146 cmsHPROFILE hProfile = cmsCreate_sRGBProfile();
5147 //force ICC profile version 2.1
5148 cmsSetProfileVersion(hProfile, 2.1);
5149 cmsUInt32Number nBytesNeeded = 0;
5150 cmsSaveProfileToMem(hProfile, nullptr, &nBytesNeeded);
5151 if (!nBytesNeeded)
5152 return 0;
5153 std::vector<unsigned char> aBuffer(nBytesNeeded);
5154 cmsSaveProfileToMem(hProfile, aBuffer.data(), &nBytesNeeded);
5155 cmsCloseProfile(hProfile);
5156 bool written = writeBuffer( aBuffer.data(), static_cast<sal_Int32>(aBuffer.size()) );
5157 disableStreamEncryption();
5158 endCompression();
5160 sal_uInt64 nEndStreamPos = 0;
5161 if (m_aFile.getPos(nEndStreamPos) != osl::File::E_None)
5162 return 0;
5164 if( !written )
5165 return 0;
5166 if( ! writeBuffer( "\nendstream\nendobj\n\n", 19 ) )
5167 return 0 ;
5168 aLine.setLength( 0 );
5170 //emit the stream length object
5171 if ( !updateObject( nStreamLengthObject ) ) return 0;
5172 aLine.setLength( 0 );
5173 aLine.append( nStreamLengthObject );
5174 aLine.append( " 0 obj\n" );
5175 aLine.append( static_cast<sal_Int64>(nEndStreamPos-nBeginStreamPos) );
5176 aLine.append( "\nendobj\n\n" );
5177 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0;
5178 aLine.setLength( 0 );
5180 //emit the OutputIntent dictionary
5181 sal_Int32 nOIObject = createObject();
5182 if ( !updateObject( nOIObject ) ) return 0;
5183 aLine.append( nOIObject );
5184 aLine.append( " 0 obj\n"
5185 "<</Type/OutputIntent/S/GTS_PDFA1/OutputConditionIdentifier");
5187 OUString const aComment( "sRGB IEC61966-2.1" );
5188 appendLiteralStringEncrypt( aComment ,nOIObject, aLine );
5189 aLine.append("/DestOutputProfile ");
5190 aLine.append( nICCObject );
5191 aLine.append( " 0 R>>\nendobj\n\n" );
5192 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0;
5194 return nOIObject;
5197 // formats the string for the XML stream
5198 static void escapeStringXML( const OUString& rStr, OUString &rValue)
5200 const sal_Unicode* pUni = rStr.getStr();
5201 int nLen = rStr.getLength();
5202 for( ; nLen; nLen--, pUni++ )
5204 switch( *pUni )
5206 case u'&':
5207 rValue += "&amp;";
5208 break;
5209 case u'<':
5210 rValue += "&lt;";
5211 break;
5212 case u'>':
5213 rValue += "&gt;";
5214 break;
5215 case u'\'':
5216 rValue += "&apos;";
5217 break;
5218 case u'"':
5219 rValue += "&quot;";
5220 break;
5221 default:
5222 rValue += OUStringChar( *pUni );
5223 break;
5228 // emits the document metadata
5229 sal_Int32 PDFWriterImpl::emitDocumentMetadata()
5231 if( !m_bIsPDF_A1 && !m_bIsPDF_A2 )
5232 return 0;
5234 //get the object number for all the destinations
5235 sal_Int32 nObject = createObject();
5237 if( updateObject( nObject ) )
5239 // the following string are written in UTF-8 unicode
5240 OStringBuffer aMetadataStream( 8192 );
5242 aMetadataStream.append( "<?xpacket begin=\"" );
5243 // these lines write Unicode "zero width non-breaking space character" (U+FEFF)
5244 // (aka byte-order mark ) used as a byte-order marker.
5245 aMetadataStream.append( OUStringToOString( OUString( u'\xFEFF' ), RTL_TEXTENCODING_UTF8 ) );
5246 aMetadataStream.append( "\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n" );
5247 aMetadataStream.append( "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\">\n" );
5248 aMetadataStream.append( " <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n" );
5249 //PDF/A part ( ISO 19005-1:2005 - 6.7.11 )
5250 aMetadataStream.append( " <rdf:Description rdf:about=\"\"\n" );
5251 aMetadataStream.append( " xmlns:pdfaid=\"http://www.aiim.org/pdfa/ns/id/\">\n" );
5252 if( m_bIsPDF_A2 )
5254 aMetadataStream.append( " <pdfaid:part>2</pdfaid:part>\n" );
5255 aMetadataStream.append( " <pdfaid:conformance>B</pdfaid:conformance>\n" );
5257 else
5259 aMetadataStream.append( " <pdfaid:part>1</pdfaid:part>\n" );
5260 aMetadataStream.append( " <pdfaid:conformance>A</pdfaid:conformance>\n" );
5262 aMetadataStream.append( " </rdf:Description>\n" );
5263 //... Dublin Core properties go here
5264 if( !m_aContext.DocumentInfo.Title.isEmpty() ||
5265 !m_aContext.DocumentInfo.Author.isEmpty() ||
5266 !m_aContext.DocumentInfo.Subject.isEmpty() )
5268 aMetadataStream.append( " <rdf:Description rdf:about=\"\"\n" );
5269 aMetadataStream.append( " xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n" );
5270 if( !m_aContext.DocumentInfo.Title.isEmpty() )
5272 // this is according to PDF/A-1, technical corrigendum 1 (2007-04-01)
5273 aMetadataStream.append( " <dc:title>\n" );
5274 aMetadataStream.append( " <rdf:Alt>\n" );
5275 aMetadataStream.append( " <rdf:li xml:lang=\"x-default\">" );
5276 OUString aTitle;
5277 escapeStringXML( m_aContext.DocumentInfo.Title, aTitle );
5278 aMetadataStream.append( OUStringToOString( aTitle, RTL_TEXTENCODING_UTF8 ) );
5279 aMetadataStream.append( "</rdf:li>\n" );
5280 aMetadataStream.append( " </rdf:Alt>\n" );
5281 aMetadataStream.append( " </dc:title>\n" );
5283 if( !m_aContext.DocumentInfo.Author.isEmpty() )
5285 aMetadataStream.append( " <dc:creator>\n" );
5286 aMetadataStream.append( " <rdf:Seq>\n" );
5287 aMetadataStream.append( " <rdf:li>" );
5288 OUString aAuthor;
5289 escapeStringXML( m_aContext.DocumentInfo.Author, aAuthor );
5290 aMetadataStream.append( OUStringToOString( aAuthor , RTL_TEXTENCODING_UTF8 ) );
5291 aMetadataStream.append( "</rdf:li>\n" );
5292 aMetadataStream.append( " </rdf:Seq>\n" );
5293 aMetadataStream.append( " </dc:creator>\n" );
5295 if( !m_aContext.DocumentInfo.Subject.isEmpty() )
5297 // this is according to PDF/A-1, technical corrigendum 1 (2007-04-01)
5298 aMetadataStream.append( " <dc:description>\n" );
5299 aMetadataStream.append( " <rdf:Alt>\n" );
5300 aMetadataStream.append( " <rdf:li xml:lang=\"x-default\">" );
5301 OUString aSubject;
5302 escapeStringXML( m_aContext.DocumentInfo.Subject, aSubject );
5303 aMetadataStream.append( OUStringToOString( aSubject , RTL_TEXTENCODING_UTF8 ) );
5304 aMetadataStream.append( "</rdf:li>\n" );
5305 aMetadataStream.append( " </rdf:Alt>\n" );
5306 aMetadataStream.append( " </dc:description>\n" );
5308 aMetadataStream.append( " </rdf:Description>\n" );
5311 //... PDF properties go here
5312 if( !m_aContext.DocumentInfo.Producer.isEmpty() ||
5313 !m_aContext.DocumentInfo.Keywords.isEmpty() )
5315 aMetadataStream.append( " <rdf:Description rdf:about=\"\"\n" );
5316 aMetadataStream.append( " xmlns:pdf=\"http://ns.adobe.com/pdf/1.3/\">\n" );
5317 if( !m_aContext.DocumentInfo.Producer.isEmpty() )
5319 aMetadataStream.append( " <pdf:Producer>" );
5320 OUString aProducer;
5321 escapeStringXML( m_aContext.DocumentInfo.Producer, aProducer );
5322 aMetadataStream.append( OUStringToOString( aProducer , RTL_TEXTENCODING_UTF8 ) );
5323 aMetadataStream.append( "</pdf:Producer>\n" );
5325 if( !m_aContext.DocumentInfo.Keywords.isEmpty() )
5327 aMetadataStream.append( " <pdf:Keywords>" );
5328 OUString aKeywords;
5329 escapeStringXML( m_aContext.DocumentInfo.Keywords, aKeywords );
5330 aMetadataStream.append( OUStringToOString( aKeywords , RTL_TEXTENCODING_UTF8 ) );
5331 aMetadataStream.append( "</pdf:Keywords>\n" );
5333 aMetadataStream.append( " </rdf:Description>\n" );
5336 aMetadataStream.append( " <rdf:Description rdf:about=\"\"\n" );
5337 aMetadataStream.append( " xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\">\n" );
5338 if( !m_aContext.DocumentInfo.Creator.isEmpty() )
5340 aMetadataStream.append( " <xmp:CreatorTool>" );
5341 OUString aCreator;
5342 escapeStringXML( m_aContext.DocumentInfo.Creator, aCreator );
5343 aMetadataStream.append( OUStringToOString( aCreator , RTL_TEXTENCODING_UTF8 ) );
5344 aMetadataStream.append( "</xmp:CreatorTool>\n" );
5346 //creation date
5347 aMetadataStream.append( " <xmp:CreateDate>" );
5348 aMetadataStream.append( m_aCreationMetaDateString );
5349 aMetadataStream.append( "</xmp:CreateDate>\n" );
5351 aMetadataStream.append( " </rdf:Description>\n" );
5352 aMetadataStream.append( " </rdf:RDF>\n" );
5353 aMetadataStream.append( "</x:xmpmeta>\n" );
5355 //add the padding
5356 for( sal_Int32 nSpaces = 1; nSpaces <= 2100; nSpaces++ )
5358 aMetadataStream.append( " " );
5359 if( nSpaces % 100 == 0 )
5360 aMetadataStream.append( "\n" );
5363 aMetadataStream.append( "<?xpacket end=\"w\"?>\n" );
5365 OStringBuffer aMetadataObj( 1024 );
5367 aMetadataObj.append( nObject );
5368 aMetadataObj.append( " 0 obj\n" );
5370 aMetadataObj.append( "<</Type/Metadata/Subtype/XML/Length " );
5372 aMetadataObj.append( aMetadataStream.getLength() );
5373 aMetadataObj.append( ">>\nstream\n" );
5374 if ( !writeBuffer( aMetadataObj.getStr(), aMetadataObj.getLength() ) )
5375 return 0;
5376 //emit the stream
5377 if ( !writeBuffer( aMetadataStream.getStr(), aMetadataStream.getLength() ) )
5378 return 0;
5380 aMetadataObj.setLength( 0 );
5381 aMetadataObj.append( "\nendstream\nendobj\n\n" );
5382 if( ! writeBuffer( aMetadataObj.getStr(), aMetadataObj.getLength() ) )
5383 nObject = 0;
5385 else
5386 nObject = 0;
5388 return nObject;
5391 bool PDFWriterImpl::emitTrailer()
5393 // emit doc info
5394 sal_Int32 nDocInfoObject = emitInfoDict( );
5396 sal_Int32 nSecObject = 0;
5398 if( m_aContext.Encryption.Encrypt() )
5400 //emit the security information
5401 //must be emitted as indirect dictionary object, since
5402 //Acrobat Reader 5 works only with this kind of implementation
5403 nSecObject = createObject();
5405 if( updateObject( nSecObject ) )
5407 OStringBuffer aLineS( 1024 );
5408 aLineS.append( nSecObject );
5409 aLineS.append( " 0 obj\n"
5410 "<</Filter/Standard/V " );
5411 // check the version
5412 aLineS.append( "2/Length 128/R 3" );
5414 // emit the owner password, must not be encrypted
5415 aLineS.append( "/O(" );
5416 appendLiteralString( reinterpret_cast<char*>(m_aContext.Encryption.OValue.data()), sal_Int32(m_aContext.Encryption.OValue.size()), aLineS );
5417 aLineS.append( ")/U(" );
5418 appendLiteralString( reinterpret_cast<char*>(m_aContext.Encryption.UValue.data()), sal_Int32(m_aContext.Encryption.UValue.size()), aLineS );
5419 aLineS.append( ")/P " );// the permission set
5420 aLineS.append( m_nAccessPermissions );
5421 aLineS.append( ">>\nendobj\n\n" );
5422 if( !writeBuffer( aLineS.getStr(), aLineS.getLength() ) )
5423 nSecObject = 0;
5425 else
5426 nSecObject = 0;
5428 // emit xref table
5429 // remember start
5430 sal_uInt64 nXRefOffset = 0;
5431 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nXRefOffset )) );
5432 CHECK_RETURN( writeBuffer( "xref\n", 5 ) );
5434 sal_Int32 nObjects = m_aObjects.size();
5435 OStringBuffer aLine;
5436 aLine.append( "0 " );
5437 aLine.append( static_cast<sal_Int32>(nObjects+1) );
5438 aLine.append( "\n" );
5439 aLine.append( "0000000000 65535 f \n" );
5440 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
5442 for( sal_Int32 i = 0; i < nObjects; i++ )
5444 aLine.setLength( 0 );
5445 OString aOffset = OString::number( m_aObjects[i] );
5446 for( sal_Int32 j = 0; j < (10-aOffset.getLength()); j++ )
5447 aLine.append( '0' );
5448 aLine.append( aOffset );
5449 aLine.append( " 00000 n \n" );
5450 SAL_WARN_IF( aLine.getLength() != 20, "vcl.pdfwriter", "invalid xref entry" );
5451 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
5454 // prepare document checksum
5455 OStringBuffer aDocChecksum( 2*RTL_DIGEST_LENGTH_MD5+1 );
5456 ::std::vector<unsigned char> const nMD5Sum(m_DocDigest.finalize());
5457 for (sal_uInt8 i : nMD5Sum)
5458 appendHex( i, aDocChecksum );
5459 // document id set in setDocInfo method
5460 // emit trailer
5461 aLine.setLength( 0 );
5462 aLine.append( "trailer\n"
5463 "<</Size " );
5464 aLine.append( static_cast<sal_Int32>(nObjects+1) );
5465 aLine.append( "/Root " );
5466 aLine.append( m_nCatalogObject );
5467 aLine.append( " 0 R\n" );
5468 if( nSecObject )
5470 aLine.append( "/Encrypt ");
5471 aLine.append( nSecObject );
5472 aLine.append( " 0 R\n" );
5474 if( nDocInfoObject )
5476 aLine.append( "/Info " );
5477 aLine.append( nDocInfoObject );
5478 aLine.append( " 0 R\n" );
5480 if( ! m_aContext.Encryption.DocumentIdentifier.empty() )
5482 aLine.append( "/ID [ <" );
5483 for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
5485 appendHex( sal_Int8(item), aLine );
5487 aLine.append( ">\n"
5488 "<" );
5489 for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
5491 appendHex( sal_Int8(item), aLine );
5493 aLine.append( "> ]\n" );
5495 if( !aDocChecksum.isEmpty() )
5497 aLine.append( "/DocChecksum /" );
5498 aLine.append( aDocChecksum.makeStringAndClear() );
5499 aLine.append( "\n" );
5501 if( !m_aAdditionalStreams.empty() )
5503 aLine.append( "/AdditionalStreams [" );
5504 for(const PDFAddStream & rAdditionalStream : m_aAdditionalStreams)
5506 aLine.append( "/" );
5507 appendName( rAdditionalStream.m_aMimeType, aLine );
5508 aLine.append( " " );
5509 aLine.append( rAdditionalStream.m_nStreamObject );
5510 aLine.append( " 0 R\n" );
5512 aLine.append( "]\n" );
5514 aLine.append( ">>\n"
5515 "startxref\n" );
5516 aLine.append( static_cast<sal_Int64>(nXRefOffset) );
5517 aLine.append( "\n"
5518 "%%EOF\n" );
5519 return writeBuffer( aLine.getStr(), aLine.getLength() );
5522 struct AnnotationSortEntry
5524 sal_Int32 nTabOrder;
5525 sal_Int32 nObject;
5526 sal_Int32 nWidgetIndex;
5528 AnnotationSortEntry( sal_Int32 nTab, sal_Int32 nObj, sal_Int32 nI ) :
5529 nTabOrder( nTab ),
5530 nObject( nObj ),
5531 nWidgetIndex( nI )
5535 struct AnnotSortContainer
5537 std::set< sal_Int32 > aObjects;
5538 std::vector< AnnotationSortEntry > aSortedAnnots;
5541 struct AnnotSorterLess
5543 std::vector< PDFWriterImpl::PDFWidget >& m_rWidgets;
5545 explicit AnnotSorterLess( std::vector< PDFWriterImpl::PDFWidget >& rWidgets ) : m_rWidgets( rWidgets ) {}
5547 bool operator()( const AnnotationSortEntry& rLeft, const AnnotationSortEntry& rRight )
5549 if( rLeft.nTabOrder < rRight.nTabOrder )
5550 return true;
5551 if( rRight.nTabOrder < rLeft.nTabOrder )
5552 return false;
5553 if( rLeft.nWidgetIndex < 0 && rRight.nWidgetIndex < 0 )
5554 return false;
5555 if( rRight.nWidgetIndex < 0 )
5556 return true;
5557 if( rLeft.nWidgetIndex < 0 )
5558 return false;
5559 // remember: widget rects are in PDF coordinates, so they are ordered down up
5560 if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() >
5561 m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() )
5562 return true;
5563 if( m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() >
5564 m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() )
5565 return false;
5566 if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Left() <
5567 m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Left() )
5568 return true;
5569 return false;
5573 void PDFWriterImpl::sortWidgets()
5575 // sort widget annotations on each page as per their
5576 // TabOrder attribute
5577 std::unordered_map< sal_Int32, AnnotSortContainer > sorted;
5578 int nWidgets = m_aWidgets.size();
5579 for( int nW = 0; nW < nWidgets; nW++ )
5581 const PDFWidget& rWidget = m_aWidgets[nW];
5582 if( rWidget.m_nPage >= 0 )
5584 AnnotSortContainer& rCont = sorted[ rWidget.m_nPage ];
5585 // optimize vector allocation
5586 if( rCont.aSortedAnnots.empty() )
5587 rCont.aSortedAnnots.reserve( m_aPages[ rWidget.m_nPage ].m_aAnnotations.size() );
5588 // insert widget to tab sorter
5589 // RadioButtons are not page annotations, only their individual check boxes are
5590 if( rWidget.m_eType != PDFWriter::RadioButton )
5592 rCont.aObjects.insert( rWidget.m_nObject );
5593 rCont.aSortedAnnots.emplace_back( rWidget.m_nTabOrder, rWidget.m_nObject, nW );
5597 for (auto & item : sorted)
5599 // append entries for non widget annotations
5600 PDFPage& rPage = m_aPages[ item.first ];
5601 unsigned int nAnnots = rPage.m_aAnnotations.size();
5602 for( unsigned int nA = 0; nA < nAnnots; nA++ )
5603 if( item.second.aObjects.find( rPage.m_aAnnotations[nA] ) == item.second.aObjects.end())
5604 item.second.aSortedAnnots.emplace_back( 10000, rPage.m_aAnnotations[nA], -1 );
5606 AnnotSorterLess aLess( m_aWidgets );
5607 std::stable_sort( item.second.aSortedAnnots.begin(), item.second.aSortedAnnots.end(), aLess );
5608 // sanity check
5609 if( item.second.aSortedAnnots.size() == nAnnots)
5611 for( unsigned int nA = 0; nA < nAnnots; nA++ )
5612 rPage.m_aAnnotations[nA] = item.second.aSortedAnnots[nA].nObject;
5614 else
5616 SAL_WARN( "vcl.pdfwriter", "wrong number of sorted annotations" );
5617 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::sortWidgets(): wrong number of sorted assertions "
5618 "on page nr " << item.first << ", " <<
5619 static_cast<long int>(item.second.aSortedAnnots.size()) << " sorted and " <<
5620 static_cast<long int>(nAnnots) << " unsorted");
5624 // FIXME: implement tab order in structure tree for PDF 1.5
5627 namespace vcl {
5628 class PDFStreamIf :
5629 public cppu::WeakImplHelper< css::io::XOutputStream >
5631 VclPtr<PDFWriterImpl> m_pWriter;
5632 bool m_bWrite;
5633 public:
5634 explicit PDFStreamIf( PDFWriterImpl* pWriter ) : m_pWriter( pWriter ), m_bWrite( true ) {}
5636 virtual void SAL_CALL writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override;
5637 virtual void SAL_CALL flush() override;
5638 virtual void SAL_CALL closeOutput() override;
5642 void SAL_CALL PDFStreamIf::writeBytes( const css::uno::Sequence< sal_Int8 >& aData )
5644 if( m_bWrite && aData.hasElements() )
5646 sal_Int32 nBytes = aData.getLength();
5647 m_pWriter->writeBuffer( aData.getConstArray(), nBytes );
5651 void SAL_CALL PDFStreamIf::flush()
5655 void SAL_CALL PDFStreamIf::closeOutput()
5657 m_bWrite = false;
5660 bool PDFWriterImpl::emitAdditionalStreams()
5662 unsigned int nStreams = m_aAdditionalStreams.size();
5663 for( unsigned int i = 0; i < nStreams; i++ )
5665 PDFAddStream& rStream = m_aAdditionalStreams[i];
5666 rStream.m_nStreamObject = createObject();
5667 sal_Int32 nSizeObject = createObject();
5669 if( ! updateObject( rStream.m_nStreamObject ) )
5670 return false;
5672 OStringBuffer aLine;
5673 aLine.append( rStream.m_nStreamObject );
5674 aLine.append( " 0 obj\n<</Length " );
5675 aLine.append( nSizeObject );
5676 aLine.append( " 0 R" );
5677 if( rStream.m_bCompress )
5678 aLine.append( "/Filter/FlateDecode" );
5679 aLine.append( ">>\nstream\n" );
5680 if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
5681 return false;
5682 sal_uInt64 nBeginStreamPos = 0, nEndStreamPos = 0;
5683 if( osl::File::E_None != m_aFile.getPos(nBeginStreamPos) )
5685 m_aFile.close();
5686 m_bOpen = false;
5688 if( rStream.m_bCompress )
5689 beginCompression();
5691 checkAndEnableStreamEncryption( rStream.m_nStreamObject );
5692 css::uno::Reference< css::io::XOutputStream > xStream( new PDFStreamIf( this ) );
5693 assert(rStream.m_pStream);
5694 if (!rStream.m_pStream)
5695 return false;
5696 rStream.m_pStream->write( xStream );
5697 xStream.clear();
5698 delete rStream.m_pStream;
5699 rStream.m_pStream = nullptr;
5700 disableStreamEncryption();
5702 if( rStream.m_bCompress )
5703 endCompression();
5705 if (osl::File::E_None != m_aFile.getPos(nEndStreamPos))
5707 m_aFile.close();
5708 m_bOpen = false;
5709 return false;
5711 if( ! writeBuffer( "\nendstream\nendobj\n\n", 19 ) )
5712 return false ;
5713 // emit stream length object
5714 if( ! updateObject( nSizeObject ) )
5715 return false;
5716 aLine.setLength( 0 );
5717 aLine.append( nSizeObject );
5718 aLine.append( " 0 obj\n" );
5719 aLine.append( static_cast<sal_Int64>(nEndStreamPos-nBeginStreamPos) );
5720 aLine.append( "\nendobj\n\n" );
5721 if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
5722 return false;
5724 return true;
5727 bool PDFWriterImpl::emit()
5729 endPage();
5731 // resort structure tree and annotations if necessary
5732 // needed for widget tab order
5733 sortWidgets();
5735 #if HAVE_FEATURE_NSS
5736 if( m_aContext.SignPDF )
5738 // sign the document
5739 PDFWriter::SignatureWidget aSignature;
5740 aSignature.Name = "Signature1";
5741 createControl( aSignature, 0 );
5743 #endif
5745 // emit additional streams
5746 CHECK_RETURN( emitAdditionalStreams() );
5748 // emit catalog
5749 CHECK_RETURN( emitCatalog() );
5751 #if HAVE_FEATURE_NSS
5752 if (m_nSignatureObject != -1) // if document is signed, emit sigdict
5754 if( !emitSignature() )
5756 m_aErrors.insert( PDFWriter::Error_Signature_Failed );
5757 return false;
5760 #endif
5762 // emit trailer
5763 CHECK_RETURN( emitTrailer() );
5765 #if HAVE_FEATURE_NSS
5766 if (m_nSignatureObject != -1) // finalize the signature
5768 if( !finalizeSignature() )
5770 m_aErrors.insert( PDFWriter::Error_Signature_Failed );
5771 return false;
5774 #endif
5776 m_aFile.close();
5777 m_bOpen = false;
5779 return true;
5783 sal_Int32 PDFWriterImpl::getSystemFont( const vcl::Font& i_rFont )
5785 Push();
5787 SetFont( i_rFont );
5789 const PhysicalFontFace* pDevFont = GetFontInstance()->GetFontFace();
5790 sal_Int32 nFontID = 0;
5791 FontEmbedData::iterator it = m_aSystemFonts.find( pDevFont );
5792 if( it != m_aSystemFonts.end() )
5793 nFontID = it->second.m_nNormalFontID;
5794 else
5796 nFontID = m_nNextFID++;
5797 m_aSystemFonts[ pDevFont ] = EmbedFont();
5798 m_aSystemFonts[ pDevFont ].m_nNormalFontID = nFontID;
5801 Pop();
5802 return nFontID;
5805 void PDFWriterImpl::registerGlyph(const GlyphItem* pGlyph,
5806 const PhysicalFontFace* pFont,
5807 const std::vector<sal_Ucs>& rCodeUnits,
5808 sal_uInt8& nMappedGlyph,
5809 sal_Int32& nMappedFontObject)
5811 const int nFontGlyphId = pGlyph->glyphId();
5812 FontSubset& rSubset = m_aSubsets[ pFont ];
5813 // search for font specific glyphID
5814 FontMapping::iterator it = rSubset.m_aMapping.find( nFontGlyphId );
5815 if( it != rSubset.m_aMapping.end() )
5817 nMappedFontObject = it->second.m_nFontID;
5818 nMappedGlyph = it->second.m_nSubsetGlyphID;
5820 else
5822 // create new subset if necessary
5823 if( rSubset.m_aSubsets.empty()
5824 || (rSubset.m_aSubsets.back().m_aMapping.size() > 254) )
5826 rSubset.m_aSubsets.emplace_back( m_nNextFID++ );
5829 // copy font id
5830 nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID;
5831 // create new glyph in subset
5832 sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(rSubset.m_aSubsets.back().m_aMapping.size()+1);
5833 nMappedGlyph = nNewId;
5835 // add new glyph to emitted font subset
5836 GlyphEmit& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[ nFontGlyphId ];
5837 rNewGlyphEmit.setGlyphId( nNewId );
5838 for (const auto nCode : rCodeUnits)
5839 rNewGlyphEmit.addCode(nCode);
5841 // add new glyph to font mapping
5842 Glyph& rNewGlyph = rSubset.m_aMapping[ nFontGlyphId ];
5843 rNewGlyph.m_nFontID = nMappedFontObject;
5844 rNewGlyph.m_nSubsetGlyphID = nNewId;
5848 void PDFWriterImpl::drawRelief( SalLayout& rLayout, const OUString& rText, bool bTextLines )
5850 push( PushFlags::ALL );
5852 FontRelief eRelief = m_aCurrentPDFState.m_aFont.GetRelief();
5854 Color aTextColor = m_aCurrentPDFState.m_aFont.GetColor();
5855 Color aTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
5856 Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
5857 Color aReliefColor( COL_LIGHTGRAY );
5858 if( aTextColor == COL_BLACK )
5859 aTextColor = COL_WHITE;
5860 if( aTextLineColor == COL_BLACK )
5861 aTextLineColor = COL_WHITE;
5862 if( aOverlineColor == COL_BLACK )
5863 aOverlineColor = COL_WHITE;
5864 // coverity[copy_paste_error : FALSE] - aReliefColor depending on aTextColor is correct
5865 if( aTextColor == COL_WHITE )
5866 aReliefColor = COL_BLACK;
5868 Font aSetFont = m_aCurrentPDFState.m_aFont;
5869 aSetFont.SetRelief( FontRelief::NONE );
5870 aSetFont.SetShadow( false );
5872 aSetFont.SetColor( aReliefColor );
5873 setTextLineColor( aReliefColor );
5874 setOverlineColor( aReliefColor );
5875 setFont( aSetFont );
5876 long nOff = 1 + GetDPIX()/300;
5877 if( eRelief == FontRelief::Engraved )
5878 nOff = -nOff;
5880 rLayout.DrawOffset() += Point( nOff, nOff );
5881 updateGraphicsState();
5882 drawLayout( rLayout, rText, bTextLines );
5884 rLayout.DrawOffset() -= Point( nOff, nOff );
5885 setTextLineColor( aTextLineColor );
5886 setOverlineColor( aOverlineColor );
5887 aSetFont.SetColor( aTextColor );
5888 setFont( aSetFont );
5889 updateGraphicsState();
5890 drawLayout( rLayout, rText, bTextLines );
5892 // clean up the mess
5893 pop();
5896 void PDFWriterImpl::drawShadow( SalLayout& rLayout, const OUString& rText, bool bTextLines )
5898 Font aSaveFont = m_aCurrentPDFState.m_aFont;
5899 Color aSaveTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
5900 Color aSaveOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
5902 Font& rFont = m_aCurrentPDFState.m_aFont;
5903 if( rFont.GetColor() == COL_BLACK || rFont.GetColor().GetLuminance() < 8 )
5904 rFont.SetColor( COL_LIGHTGRAY );
5905 else
5906 rFont.SetColor( COL_BLACK );
5907 rFont.SetShadow( false );
5908 rFont.SetOutline( false );
5909 setFont( rFont );
5910 setTextLineColor( rFont.GetColor() );
5911 setOverlineColor( rFont.GetColor() );
5912 updateGraphicsState();
5914 long nOff = 1 + ((GetFontInstance()->mnLineHeight-24)/24);
5915 if( rFont.IsOutline() )
5916 nOff++;
5917 rLayout.DrawBase() += Point( nOff, nOff );
5918 drawLayout( rLayout, rText, bTextLines );
5919 rLayout.DrawBase() -= Point( nOff, nOff );
5921 setFont( aSaveFont );
5922 setTextLineColor( aSaveTextLineColor );
5923 setOverlineColor( aSaveOverlineColor );
5924 updateGraphicsState();
5927 void PDFWriterImpl::drawVerticalGlyphs(
5928 const std::vector<PDFWriterImpl::PDFGlyph>& rGlyphs,
5929 OStringBuffer& rLine,
5930 const Point& rAlignOffset,
5931 const Matrix3& rRotScale,
5932 double fAngle,
5933 double fXScale,
5934 double fSkew,
5935 sal_Int32 nFontHeight )
5937 long nXOffset = 0;
5938 Point aCurPos( rGlyphs[0].m_aPos );
5939 aCurPos = PixelToLogic( aCurPos );
5940 aCurPos += rAlignOffset;
5941 for( size_t i = 0; i < rGlyphs.size(); i++ )
5943 // have to emit each glyph on its own
5944 double fDeltaAngle = 0.0;
5945 double fYScale = 1.0;
5946 double fTempXScale = fXScale;
5947 double fSkewB = fSkew;
5948 double fSkewA = 0.0;
5950 Point aDeltaPos;
5951 if (rGlyphs[i].m_pGlyph->IsVertical())
5953 fDeltaAngle = M_PI/2.0;
5954 aDeltaPos.setX( GetFontMetric().GetAscent() );
5955 aDeltaPos.setY( static_cast<int>(static_cast<double>(GetFontMetric().GetDescent()) * fXScale) );
5956 fYScale = fXScale;
5957 fTempXScale = 1.0;
5958 fSkewA = -fSkewB;
5959 fSkewB = 0.0;
5961 aDeltaPos += PixelToLogic( Point( static_cast<int>(static_cast<double>(nXOffset)/fXScale), 0 ) ) - PixelToLogic( Point() );
5962 if( i < rGlyphs.size()-1 )
5963 // #i120627# the text on the Y axis is reversed when export ppt file to PDF format
5965 long nOffsetX = rGlyphs[i+1].m_aPos.X() - rGlyphs[i].m_aPos.X();
5966 long nOffsetY = rGlyphs[i+1].m_aPos.Y() - rGlyphs[i].m_aPos.Y();
5967 nXOffset += static_cast<int>(sqrt(double(nOffsetX*nOffsetX + nOffsetY*nOffsetY)));
5969 if (!rGlyphs[i].m_pGlyph->glyphId())
5970 continue;
5972 aDeltaPos = rRotScale.transform( aDeltaPos );
5974 Matrix3 aMat;
5975 if( fSkewB != 0.0 || fSkewA != 0.0 )
5976 aMat.skew( fSkewA, fSkewB );
5977 aMat.scale( fTempXScale, fYScale );
5978 aMat.rotate( fAngle+fDeltaAngle );
5979 aMat.translate( aCurPos.X()+aDeltaPos.X(), aCurPos.Y()+aDeltaPos.Y() );
5980 aMat.append( m_aPages.back(), rLine );
5981 rLine.append( " Tm" );
5982 if( i == 0 || rGlyphs[i-1].m_nMappedFontId != rGlyphs[i].m_nMappedFontId )
5984 rLine.append( " /F" );
5985 rLine.append( rGlyphs[i].m_nMappedFontId );
5986 rLine.append( ' ' );
5987 m_aPages.back().appendMappedLength( nFontHeight, rLine );
5988 rLine.append( " Tf" );
5990 rLine.append( "<" );
5991 appendHex( rGlyphs[i].m_nMappedGlyphId, rLine );
5992 rLine.append( ">Tj\n" );
5996 void PDFWriterImpl::drawHorizontalGlyphs(
5997 const std::vector<PDFWriterImpl::PDFGlyph>& rGlyphs,
5998 OStringBuffer& rLine,
5999 const Point& rAlignOffset,
6000 bool bFirst,
6001 double fAngle,
6002 double fXScale,
6003 double fSkew,
6004 sal_Int32 nFontHeight,
6005 sal_Int32 nPixelFontHeight
6008 // horizontal (= normal) case
6010 // fill in run end indices
6011 // end is marked by index of the first glyph of the next run
6012 // a run is marked by same mapped font id and same Y position
6013 std::vector< sal_uInt32 > aRunEnds;
6014 aRunEnds.reserve( rGlyphs.size() );
6015 for( size_t i = 1; i < rGlyphs.size(); i++ )
6017 if( rGlyphs[i].m_nMappedFontId != rGlyphs[i-1].m_nMappedFontId ||
6018 rGlyphs[i].m_aPos.Y() != rGlyphs[i-1].m_aPos.Y() )
6020 aRunEnds.push_back(i);
6023 // last run ends at last glyph
6024 aRunEnds.push_back( rGlyphs.size() );
6026 // loop over runs of the same font
6027 sal_uInt32 nBeginRun = 0;
6028 for( size_t nRun = 0; nRun < aRunEnds.size(); nRun++ )
6030 // setup text matrix
6031 Point aCurPos = rGlyphs[nBeginRun].m_aPos;
6032 // back transformation to current coordinate system
6033 aCurPos = PixelToLogic( aCurPos );
6034 aCurPos += rAlignOffset;
6035 // the first run can be set with "Td" operator
6036 // subsequent use of that operator would move
6037 // the textline matrix relative to what was set before
6038 // making use of that would drive us into rounding issues
6039 Matrix3 aMat;
6040 if( bFirst && nRun == 0 && fAngle == 0.0 && fXScale == 1.0 && fSkew == 0.0 )
6042 m_aPages.back().appendPoint( aCurPos, rLine );
6043 rLine.append( " Td " );
6045 else
6047 if( fSkew != 0.0 )
6048 aMat.skew( 0.0, fSkew );
6049 aMat.scale( fXScale, 1.0 );
6050 aMat.rotate( fAngle );
6051 aMat.translate( aCurPos.X(), aCurPos.Y() );
6052 aMat.append( m_aPages.back(), rLine );
6053 rLine.append( " Tm\n" );
6055 // set up correct font
6056 rLine.append( "/F" );
6057 rLine.append( rGlyphs[nBeginRun].m_nMappedFontId );
6058 rLine.append( ' ' );
6059 m_aPages.back().appendMappedLength( nFontHeight, rLine );
6060 rLine.append( " Tf" );
6062 // output glyphs using Tj or TJ
6063 OStringBuffer aKernedLine( 256 ), aUnkernedLine( 256 );
6064 aKernedLine.append( "[<" );
6065 aUnkernedLine.append( '<' );
6066 appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aKernedLine );
6067 appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aUnkernedLine );
6069 aMat.invert();
6070 bool bNeedKern = false;
6071 for( sal_uInt32 nPos = nBeginRun+1; nPos < aRunEnds[nRun]; nPos++ )
6073 appendHex( rGlyphs[nPos].m_nMappedGlyphId, aUnkernedLine );
6074 // check if default glyph positioning is sufficient
6075 const Point aThisPos = aMat.transform( rGlyphs[nPos].m_aPos );
6076 const Point aPrevPos = aMat.transform( rGlyphs[nPos-1].m_aPos );
6077 double fAdvance = aThisPos.X() - aPrevPos.X();
6078 fAdvance *= 1000.0 / nPixelFontHeight;
6079 const double fAdjustment = rGlyphs[nPos-1].m_nNativeWidth - fAdvance + 0.5;
6080 SAL_WARN_IF(
6081 fAdjustment < SAL_MIN_INT32 || fAdjustment > SAL_MAX_INT32, "vcl.pdfwriter",
6082 "adjustment " << fAdjustment << " outside 32-bit int");
6083 const sal_Int32 nAdjustment = static_cast<sal_Int32>(
6084 std::clamp(fAdjustment, double(SAL_MIN_INT32), double(SAL_MAX_INT32)));
6085 if( nAdjustment != 0 )
6087 // apply individual glyph positioning
6088 bNeedKern = true;
6089 aKernedLine.append( ">" );
6090 aKernedLine.append( nAdjustment );
6091 aKernedLine.append( "<" );
6093 appendHex( rGlyphs[nPos].m_nMappedGlyphId, aKernedLine );
6095 aKernedLine.append( ">]TJ\n" );
6096 aUnkernedLine.append( ">Tj\n" );
6097 rLine.append(
6098 (bNeedKern ? aKernedLine : aUnkernedLine).makeStringAndClear() );
6100 // set beginning of next run
6101 nBeginRun = aRunEnds[nRun];
6105 void PDFWriterImpl::drawLayout( SalLayout& rLayout, const OUString& rText, bool bTextLines )
6107 // relief takes precedence over shadow (see outdev3.cxx)
6108 if( m_aCurrentPDFState.m_aFont.GetRelief() != FontRelief::NONE )
6110 drawRelief( rLayout, rText, bTextLines );
6111 return;
6113 else if( m_aCurrentPDFState.m_aFont.IsShadow() )
6114 drawShadow( rLayout, rText, bTextLines );
6116 OStringBuffer aLine( 512 );
6118 const int nMaxGlyphs = 256;
6120 std::vector<sal_Ucs> aCodeUnits;
6121 bool bVertical = m_aCurrentPDFState.m_aFont.IsVertical();
6122 int nIndex = 0;
6123 double fXScale = 1.0;
6124 double fSkew = 0.0;
6125 sal_Int32 nPixelFontHeight = GetFontInstance()->GetFontSelectPattern().mnHeight;
6126 TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
6128 // transform font height back to current units
6129 // note: the layout calculates in outdevs device pixel !!
6130 sal_Int32 nFontHeight = ImplDevicePixelToLogicHeight( nPixelFontHeight );
6131 if( m_aCurrentPDFState.m_aFont.GetAverageFontWidth() )
6133 Font aFont( m_aCurrentPDFState.m_aFont );
6134 aFont.SetAverageFontWidth( 0 );
6135 FontMetric aMetric = GetFontMetric( aFont );
6136 if( aMetric.GetAverageFontWidth() != m_aCurrentPDFState.m_aFont.GetAverageFontWidth() )
6138 fXScale =
6139 static_cast<double>(m_aCurrentPDFState.m_aFont.GetAverageFontWidth()) /
6140 static_cast<double>(aMetric.GetAverageFontWidth());
6144 // perform artificial italics if necessary
6145 if( ( m_aCurrentPDFState.m_aFont.GetItalic() == ITALIC_NORMAL ||
6146 m_aCurrentPDFState.m_aFont.GetItalic() == ITALIC_OBLIQUE ) &&
6147 !( GetFontInstance()->GetFontFace()->GetItalic() == ITALIC_NORMAL ||
6148 GetFontInstance()->GetFontFace()->GetItalic() == ITALIC_OBLIQUE )
6151 fSkew = M_PI/12.0;
6154 // if the mapmode is distorted we need to adjust for that also
6155 if( m_aCurrentPDFState.m_aMapMode.GetScaleX() != m_aCurrentPDFState.m_aMapMode.GetScaleY() )
6157 fXScale *= double(m_aCurrentPDFState.m_aMapMode.GetScaleX()) / double(m_aCurrentPDFState.m_aMapMode.GetScaleY());
6160 int nAngle = m_aCurrentPDFState.m_aFont.GetOrientation();
6161 // normalize angles
6162 while( nAngle < 0 )
6163 nAngle += 3600;
6164 nAngle = nAngle % 3600;
6165 double fAngle = static_cast<double>(nAngle) * M_PI / 1800.0;
6167 Matrix3 aRotScale;
6168 aRotScale.scale( fXScale, 1.0 );
6169 if( fAngle != 0.0 )
6170 aRotScale.rotate( -fAngle );
6172 bool bPop = false;
6173 bool bABold = false;
6174 // artificial bold necessary ?
6175 if( GetFontInstance()->GetFontFace()->GetWeight() <= WEIGHT_MEDIUM &&
6176 GetFontInstance()->GetFontSelectPattern().GetWeight() > WEIGHT_MEDIUM )
6178 aLine.append("q ");
6179 bPop = true;
6180 bABold = true;
6182 // setup text colors (if necessary)
6183 Color aStrokeColor( COL_TRANSPARENT );
6184 Color aNonStrokeColor( COL_TRANSPARENT );
6186 if( m_aCurrentPDFState.m_aFont.IsOutline() )
6188 aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6189 aNonStrokeColor = COL_WHITE;
6191 else
6192 aNonStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6193 if( bABold )
6194 aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6196 if( aStrokeColor != COL_TRANSPARENT && aStrokeColor != m_aCurrentPDFState.m_aLineColor )
6198 if( ! bPop )
6199 aLine.append( "q " );
6200 bPop = true;
6201 appendStrokingColor( aStrokeColor, aLine );
6202 aLine.append( "\n" );
6204 if( aNonStrokeColor != COL_TRANSPARENT && aNonStrokeColor != m_aCurrentPDFState.m_aFillColor )
6206 if( ! bPop )
6207 aLine.append( "q " );
6208 bPop = true;
6209 appendNonStrokingColor( aNonStrokeColor, aLine );
6210 aLine.append( "\n" );
6213 // begin text object
6214 aLine.append( "BT\n" );
6215 // outline attribute ?
6216 if( m_aCurrentPDFState.m_aFont.IsOutline() || bABold )
6218 // set correct text mode, set stroke width
6219 aLine.append( "2 Tr " ); // fill, then stroke
6221 if( m_aCurrentPDFState.m_aFont.IsOutline() )
6223 // unclear what to do in case of outline and artificial bold
6224 // for the time being outline wins
6225 aLine.append( "0.25 w \n" );
6227 else
6229 double fW = static_cast<double>(m_aCurrentPDFState.m_aFont.GetFontHeight()) / 30.0;
6230 m_aPages.back().appendMappedLength( fW, aLine );
6231 aLine.append ( " w\n" );
6235 FontMetric aRefDevFontMetric = GetFontMetric();
6236 const PhysicalFontFace* pDevFont = GetFontInstance()->GetFontFace();
6237 const GlyphItem* pGlyph = nullptr;
6238 const PhysicalFontFace* pFallbackFont = nullptr;
6240 // collect the glyphs into a single array
6241 std::vector< PDFGlyph > aGlyphs;
6242 aGlyphs.reserve( nMaxGlyphs );
6243 // first get all the glyphs and register them; coordinates still in Pixel
6244 Point aPos;
6245 while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pFallbackFont))
6247 const auto* pFont = pFallbackFont ? pFallbackFont : pDevFont;
6249 aCodeUnits.clear();
6251 // tdf#66597, tdf#115117
6253 // Here is how we embed textual content in PDF files, to allow for
6254 // better text extraction for complex and typography-rich text.
6256 // * If there is many to one or many to many mapping, use an
6257 // ActualText span embedding the original string, since ToUnicode
6258 // can’t handle these.
6259 // * If the one glyph is used for several Unicode code points, also
6260 // use ActualText since ToUnicode can map each glyph in the font
6261 // only once.
6262 // * Limit ActualText to single cluster at a time, since using it
6263 // for whole words or sentences breaks text selection and
6264 // highlighting in PDF viewers (there will be no way to tell
6265 // which glyphs belong to which characters).
6266 // * Keep generating (now) redundant ToUnicode entries for
6267 // compatibility with old tools not supporting ActualText.
6269 assert(pGlyph->charCount() >= 0);
6270 for (int n = 0; n < pGlyph->charCount(); n++)
6271 aCodeUnits.push_back(rText[pGlyph->charPos() + n]);
6273 bool bUseActualText = false;
6275 // If this is a start of complex cluster, use ActualText.
6276 if (pGlyph->IsClusterStart())
6277 bUseActualText = true;
6279 // Or part of a complex cluster, will be handled by the ActualText
6280 // of its cluster start.
6281 if (pGlyph->IsInCluster())
6282 assert(aCodeUnits.empty());
6284 // A glyph can’t have more than one ToUnicode entry, use ActualText
6285 // instead.
6286 if (!aCodeUnits.empty() && !bUseActualText)
6288 for (const auto& rSubset : m_aSubsets[pFont].m_aSubsets)
6290 const auto& it = rSubset.m_aMapping.find(pGlyph->glyphId());
6291 if (it != rSubset.m_aMapping.cend() && it->second.codes() != aCodeUnits)
6293 bUseActualText = true;
6294 aCodeUnits.clear();
6299 assert(!aCodeUnits.empty() || bUseActualText || pGlyph->IsInCluster());
6301 sal_uInt8 nMappedGlyph;
6302 sal_Int32 nMappedFontObject;
6303 registerGlyph(pGlyph, pFont, aCodeUnits, nMappedGlyph, nMappedFontObject);
6305 sal_Int32 nGlyphWidth = 0;
6306 SalGraphics *pGraphics = GetGraphics();
6307 if (pGraphics)
6308 nGlyphWidth = m_aFontCache.getGlyphWidth(pFont,
6309 pGlyph->glyphId(),
6310 pGlyph->IsVertical(),
6311 pGraphics);
6313 int nCharPos = -1;
6314 if (bUseActualText || pGlyph->IsInCluster())
6315 nCharPos = pGlyph->charPos();
6317 aGlyphs.emplace_back(aPos,
6318 pGlyph,
6319 nGlyphWidth,
6320 nMappedFontObject,
6321 nMappedGlyph,
6322 nCharPos);
6325 // Avoid fill color when map mode is in pixels, the below code assumes
6326 // logic map mode.
6327 bool bPixel = m_aCurrentPDFState.m_aMapMode.GetMapUnit() == MapUnit::MapPixel;
6328 if (m_aCurrentPDFState.m_aFont.GetFillColor() != COL_TRANSPARENT && !bPixel)
6330 // PDF doesn't have a text fill color, so draw a rectangle before
6331 // drawing the actual text.
6332 push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR);
6333 setFillColor(m_aCurrentPDFState.m_aFont.GetFillColor());
6334 // Avoid border around the rectangle for Writer shape text.
6335 setLineColor(COL_TRANSPARENT);
6337 // The rectangle is the bounding box of the text, but also includes
6338 // ascent / descent to match the on-screen rendering.
6339 tools::Rectangle aRectangle;
6340 // This is the top left of the text without ascent / descent.
6341 aRectangle.SetPos(PixelToLogic(rLayout.GetDrawPosition()));
6342 aRectangle.setY(aRectangle.getY() - aRefDevFontMetric.GetAscent());
6343 aRectangle.SetSize(PixelToLogic(Size(rLayout.GetTextWidth(), 0)));
6344 // This includes ascent / descent.
6345 aRectangle.setHeight(aRefDevFontMetric.GetLineHeight());
6347 const LogicalFontInstance* pFontInstance = GetFontInstance();
6348 if (pFontInstance->mnOrientation)
6350 // Adapt rectangle for rotated text.
6351 tools::Polygon aPolygon(aRectangle);
6352 aPolygon.Rotate(PixelToLogic(rLayout.GetDrawPosition()), pFontInstance->mnOrientation);
6353 drawPolygon(aPolygon);
6355 else
6356 drawRectangle(aRectangle);
6358 pop();
6361 Point aAlignOffset;
6362 if ( eAlign == ALIGN_BOTTOM )
6363 aAlignOffset.AdjustY( -(aRefDevFontMetric.GetDescent()) );
6364 else if ( eAlign == ALIGN_TOP )
6365 aAlignOffset.AdjustY(aRefDevFontMetric.GetAscent() );
6366 if( aAlignOffset.X() || aAlignOffset.Y() )
6367 aAlignOffset = aRotScale.transform( aAlignOffset );
6369 /* #159153# do not emit an empty glyph vector; this can happen if e.g. the original
6370 string contained only one of the UTF16 BOMs
6372 if( ! aGlyphs.empty() )
6374 size_t nStart = 0;
6375 size_t nEnd = 0;
6376 while (nStart < aGlyphs.size())
6378 while (nEnd < aGlyphs.size() && aGlyphs[nEnd].m_nCharPos == aGlyphs[nStart].m_nCharPos)
6379 nEnd++;
6381 std::vector<PDFGlyph> aRun(aGlyphs.begin() + nStart, aGlyphs.begin() + nEnd);
6383 int nCharPos, nCharCount;
6384 if (!aRun.front().m_pGlyph->IsRTLGlyph())
6386 nCharPos = aRun.front().m_nCharPos;
6387 nCharCount = aRun.front().m_pGlyph->charCount();
6389 else
6391 nCharPos = aRun.back().m_nCharPos;
6392 nCharCount = aRun.back().m_pGlyph->charCount();
6395 if (nCharPos >= 0 && nCharCount)
6397 aLine.append("/Span<</ActualText<FEFF");
6398 for (int i = 0; i < nCharCount; i++)
6400 sal_Unicode aChar = rText[nCharPos + i];
6401 appendHex(static_cast<sal_Int8>(aChar >> 8), aLine);
6402 appendHex(static_cast<sal_Int8>(aChar & 255), aLine);
6404 aLine.append( ">>>\nBDC\n" );
6407 if (bVertical)
6408 drawVerticalGlyphs(aRun, aLine, aAlignOffset, aRotScale, fAngle, fXScale, fSkew, nFontHeight);
6409 else
6410 drawHorizontalGlyphs(aRun, aLine, aAlignOffset, nStart == 0, fAngle, fXScale, fSkew, nFontHeight, nPixelFontHeight);
6412 if (nCharPos >= 0 && nCharCount)
6413 aLine.append( "EMC\n" );
6415 nStart = nEnd;
6419 // end textobject
6420 aLine.append( "ET\n" );
6421 if( bPop )
6422 aLine.append( "Q\n" );
6424 writeBuffer( aLine.getStr(), aLine.getLength() );
6426 // draw eventual textlines
6427 FontStrikeout eStrikeout = m_aCurrentPDFState.m_aFont.GetStrikeout();
6428 FontLineStyle eUnderline = m_aCurrentPDFState.m_aFont.GetUnderline();
6429 FontLineStyle eOverline = m_aCurrentPDFState.m_aFont.GetOverline();
6430 if( bTextLines &&
6432 ( eUnderline != LINESTYLE_NONE && eUnderline != LINESTYLE_DONTKNOW ) ||
6433 ( eOverline != LINESTYLE_NONE && eOverline != LINESTYLE_DONTKNOW ) ||
6434 ( eStrikeout != STRIKEOUT_NONE && eStrikeout != STRIKEOUT_DONTKNOW )
6438 bool bUnderlineAbove = m_aCurrentPDFState.m_aFont.IsUnderlineAbove();
6439 if( m_aCurrentPDFState.m_aFont.IsWordLineMode() )
6441 Point aStartPt;
6442 sal_Int32 nWidth = 0;
6443 nIndex = 0;
6444 while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex))
6446 if (!pGlyph->IsSpacing())
6448 if( !nWidth )
6449 aStartPt = aPos;
6451 nWidth += pGlyph->m_nNewWidth;
6453 else if( nWidth > 0 )
6455 drawTextLine( PixelToLogic( aStartPt ),
6456 ImplDevicePixelToLogicWidth( nWidth ),
6457 eStrikeout, eUnderline, eOverline, bUnderlineAbove );
6458 nWidth = 0;
6462 if( nWidth > 0 )
6464 drawTextLine( PixelToLogic( aStartPt ),
6465 ImplDevicePixelToLogicWidth( nWidth ),
6466 eStrikeout, eUnderline, eOverline, bUnderlineAbove );
6469 else
6471 Point aStartPt = rLayout.GetDrawPosition();
6472 int nWidth = rLayout.GetTextWidth() / rLayout.GetUnitsPerPixel();
6473 drawTextLine( PixelToLogic( aStartPt ),
6474 ImplDevicePixelToLogicWidth( nWidth ),
6475 eStrikeout, eUnderline, eOverline, bUnderlineAbove );
6479 // write eventual emphasis marks
6480 if( !(m_aCurrentPDFState.m_aFont.GetEmphasisMark() & FontEmphasisMark::Style) )
6481 return;
6483 tools::PolyPolygon aEmphPoly;
6484 tools::Rectangle aEmphRect1;
6485 tools::Rectangle aEmphRect2;
6486 long nEmphYOff;
6487 long nEmphWidth;
6488 long nEmphHeight;
6489 bool bEmphPolyLine;
6490 FontEmphasisMark nEmphMark;
6492 push( PushFlags::ALL );
6494 aLine.setLength( 0 );
6495 aLine.append( "q\n" );
6497 nEmphMark = OutputDevice::ImplGetEmphasisMarkStyle( m_aCurrentPDFState.m_aFont );
6498 if ( nEmphMark & FontEmphasisMark::PosBelow )
6499 nEmphHeight = GetEmphasisDescent();
6500 else
6501 nEmphHeight = GetEmphasisAscent();
6502 ImplGetEmphasisMark( aEmphPoly,
6503 bEmphPolyLine,
6504 aEmphRect1,
6505 aEmphRect2,
6506 nEmphYOff,
6507 nEmphWidth,
6508 nEmphMark,
6509 ImplDevicePixelToLogicWidth(nEmphHeight) );
6510 if ( bEmphPolyLine )
6512 setLineColor( m_aCurrentPDFState.m_aFont.GetColor() );
6513 setFillColor( COL_TRANSPARENT );
6515 else
6517 setFillColor( m_aCurrentPDFState.m_aFont.GetColor() );
6518 setLineColor( COL_TRANSPARENT );
6520 writeBuffer( aLine.getStr(), aLine.getLength() );
6522 Point aOffset(0,0);
6524 if ( nEmphMark & FontEmphasisMark::PosBelow )
6525 aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetDescent() + nEmphYOff );
6526 else
6527 aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetAscent() + nEmphYOff) );
6529 long nEmphWidth2 = nEmphWidth / 2;
6530 long nEmphHeight2 = nEmphHeight / 2;
6531 aOffset += Point( nEmphWidth2, nEmphHeight2 );
6533 if ( eAlign == ALIGN_BOTTOM )
6534 aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetDescent()) );
6535 else if ( eAlign == ALIGN_TOP )
6536 aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetAscent() );
6538 nIndex = 0;
6539 while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex))
6541 if (pGlyph->IsSpacing())
6543 Point aAdjOffset = aOffset;
6544 aAdjOffset.AdjustX((pGlyph->m_nNewWidth - nEmphWidth) / 2 );
6545 aAdjOffset = aRotScale.transform( aAdjOffset );
6547 aAdjOffset -= Point( nEmphWidth2, nEmphHeight2 );
6549 aPos += aAdjOffset;
6550 aPos = PixelToLogic( aPos );
6551 drawEmphasisMark( aPos.X(), aPos.Y(),
6552 aEmphPoly, bEmphPolyLine,
6553 aEmphRect1, aEmphRect2 );
6557 writeBuffer( "Q\n", 2 );
6558 pop();
6562 void PDFWriterImpl::drawEmphasisMark( long nX, long nY,
6563 const tools::PolyPolygon& rPolyPoly, bool bPolyLine,
6564 const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 )
6566 // TODO: pass nWidth as width of this mark
6567 // long nWidth = 0;
6569 if ( rPolyPoly.Count() )
6571 if ( bPolyLine )
6573 tools::Polygon aPoly = rPolyPoly.GetObject( 0 );
6574 aPoly.Move( nX, nY );
6575 drawPolyLine( aPoly );
6577 else
6579 tools::PolyPolygon aPolyPoly = rPolyPoly;
6580 aPolyPoly.Move( nX, nY );
6581 drawPolyPolygon( aPolyPoly );
6585 if ( !rRect1.IsEmpty() )
6587 tools::Rectangle aRect( Point( nX+rRect1.Left(),
6588 nY+rRect1.Top() ), rRect1.GetSize() );
6589 drawRectangle( aRect );
6592 if ( !rRect2.IsEmpty() )
6594 tools::Rectangle aRect( Point( nX+rRect2.Left(),
6595 nY+rRect2.Top() ), rRect2.GetSize() );
6597 drawRectangle( aRect );
6601 void PDFWriterImpl::drawText( const Point& rPos, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen, bool bTextLines )
6603 MARK( "drawText" );
6605 updateGraphicsState();
6607 // get a layout from the OutputDevice's SalGraphics
6608 // this also enforces font substitution and sets the font on SalGraphics
6609 std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos );
6610 if( pLayout )
6612 drawLayout( *pLayout, rText, bTextLines );
6616 void PDFWriterImpl::drawTextArray( const Point& rPos, const OUString& rText, const long* pDXArray, sal_Int32 nIndex, sal_Int32 nLen )
6618 MARK( "drawText with array" );
6620 updateGraphicsState();
6622 // get a layout from the OutputDevice's SalGraphics
6623 // this also enforces font substitution and sets the font on SalGraphics
6624 std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, 0, pDXArray );
6625 if( pLayout )
6627 drawLayout( *pLayout, rText, true );
6631 void PDFWriterImpl::drawStretchText( const Point& rPos, sal_uLong nWidth, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen )
6633 MARK( "drawStretchText" );
6635 updateGraphicsState();
6637 // get a layout from the OutputDevice's SalGraphics
6638 // this also enforces font substitution and sets the font on SalGraphics
6639 std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, nWidth );
6640 if( pLayout )
6642 drawLayout( *pLayout, rText, true );
6646 void PDFWriterImpl::drawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle )
6648 long nWidth = rRect.GetWidth();
6649 long nHeight = rRect.GetHeight();
6651 if ( nWidth <= 0 || nHeight <= 0 )
6652 return;
6654 MARK( "drawText with rectangle" );
6656 updateGraphicsState();
6658 // clip with rectangle
6659 OStringBuffer aLine;
6660 aLine.append( "q " );
6661 m_aPages.back().appendRect( rRect, aLine );
6662 aLine.append( " W* n\n" );
6663 writeBuffer( aLine.getStr(), aLine.getLength() );
6665 // if disabled text is needed, put in here
6667 Point aPos = rRect.TopLeft();
6669 long nTextHeight = GetTextHeight();
6670 sal_Int32 nMnemonicPos = -1;
6672 OUString aStr = rOrigStr;
6673 if ( nStyle & DrawTextFlags::Mnemonic )
6674 aStr = OutputDevice::GetNonMnemonicString( aStr, nMnemonicPos );
6676 // multiline text
6677 if ( nStyle & DrawTextFlags::MultiLine )
6679 OUString aLastLine;
6680 ImplMultiTextLineInfo aMultiLineInfo;
6681 ImplTextLineInfo* pLineInfo;
6682 sal_Int32 i;
6683 sal_Int32 nLines;
6684 sal_Int32 nFormatLines;
6686 if ( nTextHeight )
6688 vcl::DefaultTextLayout aLayout( *this );
6689 OutputDevice::ImplGetTextLines( aMultiLineInfo, nWidth, aStr, nStyle, aLayout );
6690 nLines = nHeight/nTextHeight;
6691 nFormatLines = aMultiLineInfo.Count();
6692 if ( !nLines )
6693 nLines = 1;
6694 if ( nFormatLines > nLines )
6696 if ( nStyle & DrawTextFlags::EndEllipsis )
6698 // handle last line
6699 nFormatLines = nLines-1;
6701 pLineInfo = aMultiLineInfo.GetLine( nFormatLines );
6702 aLastLine = convertLineEnd(aStr.copy(pLineInfo->GetIndex()), LINEEND_LF);
6703 // replace line feed by space
6704 aLastLine = aLastLine.replace('\n', ' ');
6705 aLastLine = GetEllipsisString( aLastLine, nWidth, nStyle );
6706 nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom);
6707 nStyle |= DrawTextFlags::Top;
6711 // vertical alignment
6712 if ( nStyle & DrawTextFlags::Bottom )
6713 aPos.AdjustY(nHeight-(nFormatLines*nTextHeight) );
6714 else if ( nStyle & DrawTextFlags::VCenter )
6715 aPos.AdjustY((nHeight-(nFormatLines*nTextHeight))/2 );
6717 // draw all lines excluding the last
6718 for ( i = 0; i < nFormatLines; i++ )
6720 pLineInfo = aMultiLineInfo.GetLine( i );
6721 if ( nStyle & DrawTextFlags::Right )
6722 aPos.AdjustX(nWidth-pLineInfo->GetWidth() );
6723 else if ( nStyle & DrawTextFlags::Center )
6724 aPos.AdjustX((nWidth-pLineInfo->GetWidth())/2 );
6725 sal_Int32 nIndex = pLineInfo->GetIndex();
6726 sal_Int32 nLineLen = pLineInfo->GetLen();
6727 drawText( aPos, aStr, nIndex, nLineLen );
6728 // mnemonics should not appear in documents,
6729 // if the need arises, put them in here
6730 aPos.AdjustY(nTextHeight );
6731 aPos.setX( rRect.Left() );
6734 // output last line left adjusted since it was shortened
6735 if (!aLastLine.isEmpty())
6736 drawText( aPos, aLastLine, 0, aLastLine.getLength() );
6739 else
6741 long nTextWidth = GetTextWidth( aStr );
6743 // Evt. Text kuerzen
6744 if ( nTextWidth > nWidth )
6746 if ( nStyle & (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis) )
6748 aStr = GetEllipsisString( aStr, nWidth, nStyle );
6749 nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right);
6750 nStyle |= DrawTextFlags::Left;
6751 nTextWidth = GetTextWidth( aStr );
6755 // vertical alignment
6756 if ( nStyle & DrawTextFlags::Right )
6757 aPos.AdjustX(nWidth-nTextWidth );
6758 else if ( nStyle & DrawTextFlags::Center )
6759 aPos.AdjustX((nWidth-nTextWidth)/2 );
6761 if ( nStyle & DrawTextFlags::Bottom )
6762 aPos.AdjustY(nHeight-nTextHeight );
6763 else if ( nStyle & DrawTextFlags::VCenter )
6764 aPos.AdjustY((nHeight-nTextHeight)/2 );
6766 // mnemonics should be inserted here if the need arises
6768 // draw the actual text
6769 drawText( aPos, aStr, 0, aStr.getLength() );
6772 // reset clip region to original value
6773 aLine.setLength( 0 );
6774 aLine.append( "Q\n" );
6775 writeBuffer( aLine.getStr(), aLine.getLength() );
6778 void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop )
6780 MARK( "drawLine" );
6782 updateGraphicsState();
6784 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
6785 return;
6787 OStringBuffer aLine;
6788 m_aPages.back().appendPoint( rStart, aLine );
6789 aLine.append( " m " );
6790 m_aPages.back().appendPoint( rStop, aLine );
6791 aLine.append( " l S\n" );
6793 writeBuffer( aLine.getStr(), aLine.getLength() );
6796 void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo )
6798 MARK( "drawLine with LineInfo" );
6799 updateGraphicsState();
6801 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
6802 return;
6804 if( rInfo.GetStyle() == LineStyle::Solid && rInfo.GetWidth() < 2 )
6806 drawLine( rStart, rStop );
6807 return;
6810 OStringBuffer aLine;
6812 aLine.append( "q " );
6813 if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
6815 m_aPages.back().appendPoint( rStart, aLine );
6816 aLine.append( " m " );
6817 m_aPages.back().appendPoint( rStop, aLine );
6818 aLine.append( " l S Q\n" );
6820 writeBuffer( aLine.getStr(), aLine.getLength() );
6822 else
6824 PDFWriter::ExtLineInfo aInfo;
6825 convertLineInfoToExtLineInfo( rInfo, aInfo );
6826 Point aPolyPoints[2] = { rStart, rStop };
6827 tools::Polygon aPoly( 2, aPolyPoints );
6828 drawPolyLine( aPoly, aInfo );
6832 #define HCONV( x ) ImplDevicePixelToLogicHeight( x )
6834 void PDFWriterImpl::drawWaveTextLine( OStringBuffer& aLine, long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
6836 // note: units in pFontInstance are ref device pixel
6837 const LogicalFontInstance* pFontInstance = GetFontInstance();
6838 long nLineHeight = 0;
6839 long nLinePos = 0;
6841 appendStrokingColor( aColor, aLine );
6842 aLine.append( "\n" );
6844 if ( bIsAbove )
6846 if ( !pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() )
6847 ImplInitAboveTextLineSize();
6848 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() );
6849 nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineOffset() );
6851 else
6853 if ( !pFontInstance->mxFontMetric->GetWavelineUnderlineSize() )
6854 ImplInitTextLineSize();
6855 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineSize() );
6856 nLinePos = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineOffset() );
6858 if ( (eTextLine == LINESTYLE_SMALLWAVE) && (nLineHeight > 3) )
6859 nLineHeight = 3;
6861 long nLineWidth = GetDPIX()/450;
6862 if ( ! nLineWidth )
6863 nLineWidth = 1;
6865 if ( eTextLine == LINESTYLE_BOLDWAVE )
6866 nLineWidth = 3*nLineWidth;
6868 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineWidth), aLine );
6869 aLine.append( " w " );
6871 if ( eTextLine == LINESTYLE_DOUBLEWAVE )
6873 long nOrgLineHeight = nLineHeight;
6874 nLineHeight /= 3;
6875 if ( nLineHeight < 2 )
6877 if ( nOrgLineHeight > 1 )
6878 nLineHeight = 2;
6879 else
6880 nLineHeight = 1;
6882 long nLineDY = nOrgLineHeight-(nLineHeight*2);
6883 if ( nLineDY < nLineWidth )
6884 nLineDY = nLineWidth;
6885 long nLineDY2 = nLineDY/2;
6886 if ( !nLineDY2 )
6887 nLineDY2 = 1;
6889 nLinePos -= nLineWidth-nLineDY2;
6891 m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
6893 nLinePos += nLineWidth+nLineDY;
6894 m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
6896 else
6898 if ( eTextLine != LINESTYLE_BOLDWAVE )
6899 nLinePos -= nLineWidth/2;
6900 m_aPages.back().appendWaveLine( nWidth, -nLinePos, nLineHeight, aLine );
6904 void PDFWriterImpl::drawStraightTextLine( OStringBuffer& aLine, long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
6906 // note: units in pFontInstance are ref device pixel
6907 const LogicalFontInstance* pFontInstance = GetFontInstance();
6908 long nLineHeight = 0;
6909 long nLinePos = 0;
6910 long nLinePos2 = 0;
6912 if ( eTextLine > LINESTYLE_BOLDWAVE )
6913 eTextLine = LINESTYLE_SINGLE;
6915 switch ( eTextLine )
6917 case LINESTYLE_SINGLE:
6918 case LINESTYLE_DOTTED:
6919 case LINESTYLE_DASH:
6920 case LINESTYLE_LONGDASH:
6921 case LINESTYLE_DASHDOT:
6922 case LINESTYLE_DASHDOTDOT:
6923 if ( bIsAbove )
6925 if ( !pFontInstance->mxFontMetric->GetAboveUnderlineSize() )
6926 ImplInitAboveTextLineSize();
6927 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveUnderlineSize() );
6928 nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveUnderlineOffset() );
6930 else
6932 if ( !pFontInstance->mxFontMetric->GetUnderlineSize() )
6933 ImplInitTextLineSize();
6934 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetUnderlineSize() );
6935 nLinePos = HCONV( pFontInstance->mxFontMetric->GetUnderlineOffset() );
6937 break;
6938 case LINESTYLE_BOLD:
6939 case LINESTYLE_BOLDDOTTED:
6940 case LINESTYLE_BOLDDASH:
6941 case LINESTYLE_BOLDLONGDASH:
6942 case LINESTYLE_BOLDDASHDOT:
6943 case LINESTYLE_BOLDDASHDOTDOT:
6944 if ( bIsAbove )
6946 if ( !pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() )
6947 ImplInitAboveTextLineSize();
6948 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() );
6949 nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset() );
6951 else
6953 if ( !pFontInstance->mxFontMetric->GetBoldUnderlineSize() )
6954 ImplInitTextLineSize();
6955 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetBoldUnderlineSize() );
6956 nLinePos = HCONV( pFontInstance->mxFontMetric->GetBoldUnderlineOffset() );
6957 nLinePos += nLineHeight/2;
6959 break;
6960 case LINESTYLE_DOUBLE:
6961 if ( bIsAbove )
6963 if ( !pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() )
6964 ImplInitAboveTextLineSize();
6965 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() );
6966 nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1() );
6967 nLinePos2 = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2() );
6969 else
6971 if ( !pFontInstance->mxFontMetric->GetDoubleUnderlineSize() )
6972 ImplInitTextLineSize();
6973 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineSize() );
6974 nLinePos = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1() );
6975 nLinePos2 = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2() );
6977 break;
6978 default:
6979 break;
6982 if ( !nLineHeight )
6983 return;
6985 // outline attribute ?
6986 if (m_aCurrentPDFState.m_aFont.IsOutline() && eTextLine == LINESTYLE_SINGLE)
6988 appendStrokingColor(aColor, aLine); // stroke with text color
6989 aLine.append( " " );
6990 appendNonStrokingColor(COL_WHITE, aLine); // fill with white
6991 aLine.append( "\n" );
6992 aLine.append( "0.25 w \n" ); // same line thickness as in drawLayout
6994 // draw rectangle instead
6995 aLine.append( "0 " );
6996 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos * 1.5), aLine );
6997 aLine.append( " " );
6998 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
6999 aLine.append( ' ' );
7000 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7001 aLine.append( " re h B\n" );
7002 return;
7005 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7006 aLine.append( " w " );
7007 appendStrokingColor( aColor, aLine );
7008 aLine.append( "\n" );
7010 switch ( eTextLine )
7012 case LINESTYLE_DOTTED:
7013 case LINESTYLE_BOLDDOTTED:
7014 aLine.append( "[ " );
7015 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7016 aLine.append( " ] 0 d\n" );
7017 break;
7018 case LINESTYLE_DASH:
7019 case LINESTYLE_LONGDASH:
7020 case LINESTYLE_BOLDDASH:
7021 case LINESTYLE_BOLDLONGDASH:
7023 sal_Int32 nDashLength = 4*nLineHeight;
7024 sal_Int32 nVoidLength = 2*nLineHeight;
7025 if ( ( eTextLine == LINESTYLE_LONGDASH ) || ( eTextLine == LINESTYLE_BOLDLONGDASH ) )
7026 nDashLength = 8*nLineHeight;
7028 aLine.append( "[ " );
7029 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7030 aLine.append( ' ' );
7031 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7032 aLine.append( " ] 0 d\n" );
7034 break;
7035 case LINESTYLE_DASHDOT:
7036 case LINESTYLE_BOLDDASHDOT:
7038 sal_Int32 nDashLength = 4*nLineHeight;
7039 sal_Int32 nVoidLength = 2*nLineHeight;
7040 aLine.append( "[ " );
7041 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7042 aLine.append( ' ' );
7043 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7044 aLine.append( ' ' );
7045 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7046 aLine.append( ' ' );
7047 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7048 aLine.append( " ] 0 d\n" );
7050 break;
7051 case LINESTYLE_DASHDOTDOT:
7052 case LINESTYLE_BOLDDASHDOTDOT:
7054 sal_Int32 nDashLength = 4*nLineHeight;
7055 sal_Int32 nVoidLength = 2*nLineHeight;
7056 aLine.append( "[ " );
7057 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7058 aLine.append( ' ' );
7059 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7060 aLine.append( ' ' );
7061 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7062 aLine.append( ' ' );
7063 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7064 aLine.append( ' ' );
7065 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7066 aLine.append( ' ' );
7067 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7068 aLine.append( " ] 0 d\n" );
7070 break;
7071 default:
7072 break;
7075 aLine.append( "0 " );
7076 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7077 aLine.append( " m " );
7078 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
7079 aLine.append( ' ' );
7080 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7081 aLine.append( " l S\n" );
7082 if ( eTextLine == LINESTYLE_DOUBLE )
7084 aLine.append( "0 " );
7085 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7086 aLine.append( " m " );
7087 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
7088 aLine.append( ' ' );
7089 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7090 aLine.append( " l S\n" );
7095 void PDFWriterImpl::drawStrikeoutLine( OStringBuffer& aLine, long nWidth, FontStrikeout eStrikeout, Color aColor )
7097 // note: units in pFontInstance are ref device pixel
7098 const LogicalFontInstance* pFontInstance = GetFontInstance();
7099 long nLineHeight = 0;
7100 long nLinePos = 0;
7101 long nLinePos2 = 0;
7103 if ( eStrikeout > STRIKEOUT_X )
7104 eStrikeout = STRIKEOUT_SINGLE;
7106 switch ( eStrikeout )
7108 case STRIKEOUT_SINGLE:
7109 if ( !pFontInstance->mxFontMetric->GetStrikeoutSize() )
7110 ImplInitTextLineSize();
7111 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetStrikeoutSize() );
7112 nLinePos = HCONV( pFontInstance->mxFontMetric->GetStrikeoutOffset() );
7113 break;
7114 case STRIKEOUT_BOLD:
7115 if ( !pFontInstance->mxFontMetric->GetBoldStrikeoutSize() )
7116 ImplInitTextLineSize();
7117 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetBoldStrikeoutSize() );
7118 nLinePos = HCONV( pFontInstance->mxFontMetric->GetBoldStrikeoutOffset() );
7119 break;
7120 case STRIKEOUT_DOUBLE:
7121 if ( !pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() )
7122 ImplInitTextLineSize();
7123 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() );
7124 nLinePos = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1() );
7125 nLinePos2 = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2() );
7126 break;
7127 default:
7128 break;
7131 if ( !nLineHeight )
7132 return;
7134 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7135 aLine.append( " w " );
7136 appendStrokingColor( aColor, aLine );
7137 aLine.append( "\n" );
7139 aLine.append( "0 " );
7140 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7141 aLine.append( " m " );
7142 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
7143 aLine.append( ' ' );
7144 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7145 aLine.append( " l S\n" );
7147 if ( eStrikeout == STRIKEOUT_DOUBLE )
7149 aLine.append( "0 " );
7150 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7151 aLine.append( " m " );
7152 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
7153 aLine.append( ' ' );
7154 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7155 aLine.append( " l S\n" );
7160 void PDFWriterImpl::drawStrikeoutChar( const Point& rPos, long nWidth, FontStrikeout eStrikeout )
7162 //See qadevOOo/testdocs/StrikeThrough.odt for examples if you need
7163 //to tweak this
7165 OUString aStrikeoutChar = eStrikeout == STRIKEOUT_SLASH ? OUString( "/" ) : OUString( "X" );
7166 OUString aStrikeout = aStrikeoutChar;
7167 while( GetTextWidth( aStrikeout ) < nWidth )
7168 aStrikeout += aStrikeout;
7170 // do not get broader than nWidth modulo 1 character
7171 while( GetTextWidth( aStrikeout ) >= nWidth )
7172 aStrikeout = aStrikeout.replaceAt( 0, 1, "" );
7173 aStrikeout += aStrikeoutChar;
7174 bool bShadow = m_aCurrentPDFState.m_aFont.IsShadow();
7175 if ( bShadow )
7177 Font aFont = m_aCurrentPDFState.m_aFont;
7178 aFont.SetShadow( false );
7179 setFont( aFont );
7180 updateGraphicsState();
7183 // strikeout string is left aligned non-CTL text
7184 ComplexTextLayoutFlags nOrigTLM = GetLayoutMode();
7185 SetLayoutMode(ComplexTextLayoutFlags::BiDiStrong);
7187 push( PushFlags::CLIPREGION );
7188 FontMetric aRefDevFontMetric = GetFontMetric();
7189 tools::Rectangle aRect;
7190 aRect.SetLeft( rPos.X() );
7191 aRect.SetRight( aRect.Left()+nWidth );
7192 aRect.SetBottom( rPos.Y()+aRefDevFontMetric.GetDescent() );
7193 aRect.SetTop( rPos.Y()-aRefDevFontMetric.GetAscent() );
7195 const LogicalFontInstance* pFontInstance = GetFontInstance();
7196 if (pFontInstance->mnOrientation)
7198 tools::Polygon aPoly( aRect );
7199 aPoly.Rotate( rPos, pFontInstance->mnOrientation);
7200 aRect = aPoly.GetBoundRect();
7203 intersectClipRegion( aRect );
7204 drawText( rPos, aStrikeout, 0, aStrikeout.getLength(), false );
7205 pop();
7207 SetLayoutMode( nOrigTLM );
7209 if ( bShadow )
7211 Font aFont = m_aCurrentPDFState.m_aFont;
7212 aFont.SetShadow( true );
7213 setFont( aFont );
7214 updateGraphicsState();
7218 void PDFWriterImpl::drawTextLine( const Point& rPos, long nWidth, FontStrikeout eStrikeout, FontLineStyle eUnderline, FontLineStyle eOverline, bool bUnderlineAbove )
7220 if ( !nWidth ||
7221 ( ((eStrikeout == STRIKEOUT_NONE)||(eStrikeout == STRIKEOUT_DONTKNOW)) &&
7222 ((eUnderline == LINESTYLE_NONE)||(eUnderline == LINESTYLE_DONTKNOW)) &&
7223 ((eOverline == LINESTYLE_NONE)||(eOverline == LINESTYLE_DONTKNOW)) ) )
7224 return;
7226 MARK( "drawTextLine" );
7227 updateGraphicsState();
7229 // note: units in pFontInstance are ref device pixel
7230 const LogicalFontInstance* pFontInstance = GetFontInstance();
7231 Color aUnderlineColor = m_aCurrentPDFState.m_aTextLineColor;
7232 Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
7233 Color aStrikeoutColor = m_aCurrentPDFState.m_aFont.GetColor();
7234 bool bStrikeoutDone = false;
7235 bool bUnderlineDone = false;
7236 bool bOverlineDone = false;
7238 if ( (eStrikeout == STRIKEOUT_SLASH) || (eStrikeout == STRIKEOUT_X) )
7240 drawStrikeoutChar( rPos, nWidth, eStrikeout );
7241 bStrikeoutDone = true;
7244 Point aPos( rPos );
7245 TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
7246 if( eAlign == ALIGN_TOP )
7247 aPos.AdjustY(HCONV( pFontInstance->mxFontMetric->GetAscent() ));
7248 else if( eAlign == ALIGN_BOTTOM )
7249 aPos.AdjustY( -HCONV( pFontInstance->mxFontMetric->GetDescent() ) );
7251 OStringBuffer aLine( 512 );
7252 // save GS
7253 aLine.append( "q " );
7255 // rotate and translate matrix
7256 double fAngle = static_cast<double>(m_aCurrentPDFState.m_aFont.GetOrientation()) * M_PI / 1800.0;
7257 Matrix3 aMat;
7258 aMat.rotate( fAngle );
7259 aMat.translate( aPos.X(), aPos.Y() );
7260 aMat.append( m_aPages.back(), aLine );
7261 aLine.append( " cm\n" );
7263 if ( aUnderlineColor.GetTransparency() != 0 )
7264 aUnderlineColor = aStrikeoutColor;
7266 if ( (eUnderline == LINESTYLE_SMALLWAVE) ||
7267 (eUnderline == LINESTYLE_WAVE) ||
7268 (eUnderline == LINESTYLE_DOUBLEWAVE) ||
7269 (eUnderline == LINESTYLE_BOLDWAVE) )
7271 drawWaveTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
7272 bUnderlineDone = true;
7275 if ( (eOverline == LINESTYLE_SMALLWAVE) ||
7276 (eOverline == LINESTYLE_WAVE) ||
7277 (eOverline == LINESTYLE_DOUBLEWAVE) ||
7278 (eOverline == LINESTYLE_BOLDWAVE) )
7280 drawWaveTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
7281 bOverlineDone = true;
7284 if ( !bUnderlineDone )
7286 drawStraightTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
7289 if ( !bOverlineDone )
7291 drawStraightTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
7294 if ( !bStrikeoutDone )
7296 drawStrikeoutLine( aLine, nWidth, eStrikeout, aStrikeoutColor );
7299 aLine.append( "Q\n" );
7300 writeBuffer( aLine.getStr(), aLine.getLength() );
7303 void PDFWriterImpl::drawPolygon( const tools::Polygon& rPoly )
7305 MARK( "drawPolygon" );
7307 updateGraphicsState();
7309 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7310 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7311 return;
7313 int nPoints = rPoly.GetSize();
7314 OStringBuffer aLine( 20 * nPoints );
7315 m_aPages.back().appendPolygon( rPoly, aLine );
7316 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7317 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7318 aLine.append( "B*\n" );
7319 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7320 aLine.append( "S\n" );
7321 else
7322 aLine.append( "f*\n" );
7324 writeBuffer( aLine.getStr(), aLine.getLength() );
7327 void PDFWriterImpl::drawPolyPolygon( const tools::PolyPolygon& rPolyPoly )
7329 MARK( "drawPolyPolygon" );
7331 updateGraphicsState();
7333 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7334 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7335 return;
7337 int nPolygons = rPolyPoly.Count();
7339 OStringBuffer aLine( 40 * nPolygons );
7340 m_aPages.back().appendPolyPolygon( rPolyPoly, aLine );
7341 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7342 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7343 aLine.append( "B*\n" );
7344 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7345 aLine.append( "S\n" );
7346 else
7347 aLine.append( "f*\n" );
7349 writeBuffer( aLine.getStr(), aLine.getLength() );
7352 void PDFWriterImpl::drawTransparent( const tools::PolyPolygon& rPolyPoly, sal_uInt32 nTransparentPercent )
7354 SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
7355 nTransparentPercent = nTransparentPercent % 100;
7357 MARK( "drawTransparent" );
7359 updateGraphicsState();
7361 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7362 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7363 return;
7365 if( m_bIsPDF_A1 || m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
7367 m_aErrors.insert( m_bIsPDF_A1 ?
7368 PDFWriter::Warning_Transparency_Omitted_PDFA :
7369 PDFWriter::Warning_Transparency_Omitted_PDF13 );
7371 drawPolyPolygon( rPolyPoly );
7372 return;
7375 // create XObject
7376 m_aTransparentObjects.emplace_back( );
7377 // FIXME: polygons with beziers may yield incorrect bound rect
7378 m_aTransparentObjects.back().m_aBoundRect = rPolyPoly.GetBoundRect();
7379 // convert rectangle to default user space
7380 m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
7381 m_aTransparentObjects.back().m_nObject = createObject();
7382 m_aTransparentObjects.back().m_nExtGStateObject = createObject();
7383 m_aTransparentObjects.back().m_fAlpha = static_cast<double>(100-nTransparentPercent) / 100.0;
7384 m_aTransparentObjects.back().m_pContentStream.reset(new SvMemoryStream( 256, 256 ));
7385 // create XObject's content stream
7386 OStringBuffer aContent( 256 );
7387 m_aPages.back().appendPolyPolygon( rPolyPoly, aContent );
7388 if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT &&
7389 m_aCurrentPDFState.m_aFillColor != COL_TRANSPARENT )
7390 aContent.append( " B*\n" );
7391 else if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT )
7392 aContent.append( " S\n" );
7393 else
7394 aContent.append( " f*\n" );
7395 m_aTransparentObjects.back().m_pContentStream->WriteBytes(
7396 aContent.getStr(), aContent.getLength() );
7398 OStringBuffer aObjName( 16 );
7399 aObjName.append( "Tr" );
7400 aObjName.append( m_aTransparentObjects.back().m_nObject );
7401 OString aTrName( aObjName.makeStringAndClear() );
7402 aObjName.append( "EGS" );
7403 aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
7404 OString aExtName( aObjName.makeStringAndClear() );
7406 OString aLine =
7407 // insert XObject
7408 "q /" +
7409 aExtName +
7410 " gs /" +
7411 aTrName +
7412 " Do Q\n";
7413 writeBuffer( aLine.getStr(), aLine.getLength() );
7415 pushResource( ResXObject, aTrName, m_aTransparentObjects.back().m_nObject );
7416 pushResource( ResExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
7419 void PDFWriterImpl::pushResource( ResourceKind eKind, const OString& rResource, sal_Int32 nObject )
7421 if( nObject >= 0 )
7423 switch( eKind )
7425 case ResXObject:
7426 m_aGlobalResourceDict.m_aXObjects[ rResource ] = nObject;
7427 if( ! m_aOutputStreams.empty() )
7428 m_aOutputStreams.front().m_aResourceDict.m_aXObjects[ rResource ] = nObject;
7429 break;
7430 case ResExtGState:
7431 m_aGlobalResourceDict.m_aExtGStates[ rResource ] = nObject;
7432 if( ! m_aOutputStreams.empty() )
7433 m_aOutputStreams.front().m_aResourceDict.m_aExtGStates[ rResource ] = nObject;
7434 break;
7435 case ResShading:
7436 m_aGlobalResourceDict.m_aShadings[ rResource ] = nObject;
7437 if( ! m_aOutputStreams.empty() )
7438 m_aOutputStreams.front().m_aResourceDict.m_aShadings[ rResource ] = nObject;
7439 break;
7440 case ResPattern:
7441 m_aGlobalResourceDict.m_aPatterns[ rResource ] = nObject;
7442 if( ! m_aOutputStreams.empty() )
7443 m_aOutputStreams.front().m_aResourceDict.m_aPatterns[ rResource ] = nObject;
7444 break;
7449 void PDFWriterImpl::beginRedirect( SvStream* pStream, const tools::Rectangle& rTargetRect )
7451 push( PushFlags::ALL );
7453 // force reemitting clip region inside the new stream, and
7454 // prevent emitting an unbalanced "Q" at the start
7455 clearClipRegion();
7456 // this is needed to point m_aCurrentPDFState at the pushed state
7457 // ... but it's pointless to actually write into the "outer" stream here!
7458 updateGraphicsState(NOWRITE);
7460 m_aOutputStreams.push_front( StreamRedirect() );
7461 m_aOutputStreams.front().m_pStream = pStream;
7462 m_aOutputStreams.front().m_aMapMode = m_aMapMode;
7464 if( !rTargetRect.IsEmpty() )
7466 m_aOutputStreams.front().m_aTargetRect =
7467 lcl_convert( m_aGraphicsStack.front().m_aMapMode,
7468 m_aMapMode,
7469 this,
7470 rTargetRect );
7471 Point aDelta = m_aOutputStreams.front().m_aTargetRect.BottomLeft();
7472 long nPageHeight = pointToPixel(m_aPages[m_nCurrentPage].getHeight());
7473 aDelta.setY( -(nPageHeight - m_aOutputStreams.front().m_aTargetRect.Bottom()) );
7474 m_aMapMode.SetOrigin( m_aMapMode.GetOrigin() + aDelta );
7477 // setup graphics state for independent object stream
7479 // force reemitting colors
7480 m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
7481 m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
7484 SvStream* PDFWriterImpl::endRedirect()
7486 SvStream* pStream = nullptr;
7487 if( ! m_aOutputStreams.empty() )
7489 pStream = m_aOutputStreams.front().m_pStream;
7490 m_aMapMode = m_aOutputStreams.front().m_aMapMode;
7491 m_aOutputStreams.pop_front();
7494 pop();
7496 m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
7497 m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
7499 // needed after pop() to set m_aCurrentPDFState
7500 updateGraphicsState(NOWRITE);
7502 return pStream;
7505 void PDFWriterImpl::beginTransparencyGroup()
7507 updateGraphicsState();
7508 if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
7509 beginRedirect( new SvMemoryStream( 1024, 1024 ), tools::Rectangle() );
7512 void PDFWriterImpl::endTransparencyGroup( const tools::Rectangle& rBoundingBox, sal_uInt32 nTransparentPercent )
7514 SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
7515 nTransparentPercent = nTransparentPercent % 100;
7517 if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
7518 return;
7520 // create XObject
7521 m_aTransparentObjects.emplace_back( );
7522 m_aTransparentObjects.back().m_aBoundRect = rBoundingBox;
7523 // convert rectangle to default user space
7524 m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
7525 m_aTransparentObjects.back().m_nObject = createObject();
7526 m_aTransparentObjects.back().m_fAlpha = static_cast<double>(100-nTransparentPercent) / 100.0;
7527 // get XObject's content stream
7528 m_aTransparentObjects.back().m_pContentStream.reset( static_cast<SvMemoryStream*>(endRedirect()) );
7529 m_aTransparentObjects.back().m_nExtGStateObject = createObject();
7531 OStringBuffer aObjName( 16 );
7532 aObjName.append( "Tr" );
7533 aObjName.append( m_aTransparentObjects.back().m_nObject );
7534 OString aTrName( aObjName.makeStringAndClear() );
7535 aObjName.append( "EGS" );
7536 aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
7537 OString aExtName( aObjName.makeStringAndClear() );
7539 OString aLine =
7540 // insert XObject
7541 "q /" +
7542 aExtName +
7543 " gs /" +
7544 aTrName +
7545 " Do Q\n";
7546 writeBuffer( aLine.getStr(), aLine.getLength() );
7548 pushResource( ResXObject, aTrName, m_aTransparentObjects.back().m_nObject );
7549 pushResource( ResExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
7553 void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect )
7555 MARK( "drawRectangle" );
7557 updateGraphicsState();
7559 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7560 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7561 return;
7563 OStringBuffer aLine( 40 );
7564 m_aPages.back().appendRect( rRect, aLine );
7566 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7567 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7568 aLine.append( " B*\n" );
7569 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7570 aLine.append( " S\n" );
7571 else
7572 aLine.append( " f*\n" );
7574 writeBuffer( aLine.getStr(), aLine.getLength() );
7577 void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound )
7579 MARK( "drawRectangle with rounded edges" );
7581 if( !nHorzRound && !nVertRound )
7582 drawRectangle( rRect );
7584 updateGraphicsState();
7586 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7587 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7588 return;
7590 if( nHorzRound > static_cast<sal_uInt32>(rRect.GetWidth())/2 )
7591 nHorzRound = rRect.GetWidth()/2;
7592 if( nVertRound > static_cast<sal_uInt32>(rRect.GetHeight())/2 )
7593 nVertRound = rRect.GetHeight()/2;
7595 Point aPoints[16];
7596 const double kappa = 0.5522847498;
7597 const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(nHorzRound))+0.5);
7598 const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(nVertRound))+0.5);
7600 aPoints[1] = Point( rRect.TopLeft().X() + nHorzRound, rRect.TopLeft().Y() );
7601 aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() );
7602 aPoints[2] = Point( rRect.TopRight().X()+1 - nHorzRound, aPoints[1].Y() );
7603 aPoints[3] = Point( aPoints[2].X()+kx, aPoints[2].Y() );
7605 aPoints[5] = Point( rRect.TopRight().X()+1, rRect.TopRight().Y()+nVertRound );
7606 aPoints[4] = Point( aPoints[5].X(), aPoints[5].Y()-ky );
7607 aPoints[6] = Point( aPoints[5].X(), rRect.BottomRight().Y()+1 - nVertRound );
7608 aPoints[7] = Point( aPoints[6].X(), aPoints[6].Y()+ky );
7610 aPoints[9] = Point( rRect.BottomRight().X()+1-nHorzRound, rRect.BottomRight().Y()+1 );
7611 aPoints[8] = Point( aPoints[9].X()+kx, aPoints[9].Y() );
7612 aPoints[10] = Point( rRect.BottomLeft().X() + nHorzRound, aPoints[9].Y() );
7613 aPoints[11] = Point( aPoints[10].X()-kx, aPoints[10].Y() );
7615 aPoints[13] = Point( rRect.BottomLeft().X(), rRect.BottomLeft().Y()+1-nVertRound );
7616 aPoints[12] = Point( aPoints[13].X(), aPoints[13].Y()+ky );
7617 aPoints[14] = Point( rRect.TopLeft().X(), rRect.TopLeft().Y()+nVertRound );
7618 aPoints[15] = Point( aPoints[14].X(), aPoints[14].Y()-ky );
7620 OStringBuffer aLine( 80 );
7621 m_aPages.back().appendPoint( aPoints[1], aLine );
7622 aLine.append( " m " );
7623 m_aPages.back().appendPoint( aPoints[2], aLine );
7624 aLine.append( " l " );
7625 m_aPages.back().appendPoint( aPoints[3], aLine );
7626 aLine.append( ' ' );
7627 m_aPages.back().appendPoint( aPoints[4], aLine );
7628 aLine.append( ' ' );
7629 m_aPages.back().appendPoint( aPoints[5], aLine );
7630 aLine.append( " c\n" );
7631 m_aPages.back().appendPoint( aPoints[6], aLine );
7632 aLine.append( " l " );
7633 m_aPages.back().appendPoint( aPoints[7], aLine );
7634 aLine.append( ' ' );
7635 m_aPages.back().appendPoint( aPoints[8], aLine );
7636 aLine.append( ' ' );
7637 m_aPages.back().appendPoint( aPoints[9], aLine );
7638 aLine.append( " c\n" );
7639 m_aPages.back().appendPoint( aPoints[10], aLine );
7640 aLine.append( " l " );
7641 m_aPages.back().appendPoint( aPoints[11], aLine );
7642 aLine.append( ' ' );
7643 m_aPages.back().appendPoint( aPoints[12], aLine );
7644 aLine.append( ' ' );
7645 m_aPages.back().appendPoint( aPoints[13], aLine );
7646 aLine.append( " c\n" );
7647 m_aPages.back().appendPoint( aPoints[14], aLine );
7648 aLine.append( " l " );
7649 m_aPages.back().appendPoint( aPoints[15], aLine );
7650 aLine.append( ' ' );
7651 m_aPages.back().appendPoint( aPoints[0], aLine );
7652 aLine.append( ' ' );
7653 m_aPages.back().appendPoint( aPoints[1], aLine );
7654 aLine.append( " c " );
7656 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7657 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7658 aLine.append( "b*\n" );
7659 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7660 aLine.append( "s\n" );
7661 else
7662 aLine.append( "f*\n" );
7664 writeBuffer( aLine.getStr(), aLine.getLength() );
7667 void PDFWriterImpl::drawEllipse( const tools::Rectangle& rRect )
7669 MARK( "drawEllipse" );
7671 updateGraphicsState();
7673 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7674 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7675 return;
7677 Point aPoints[12];
7678 const double kappa = 0.5522847498;
7679 const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetWidth())/2.0)+0.5);
7680 const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetHeight())/2.0)+0.5);
7682 aPoints[1] = Point( rRect.TopLeft().X() + rRect.GetWidth()/2, rRect.TopLeft().Y() );
7683 aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() );
7684 aPoints[2] = Point( aPoints[1].X() + kx, aPoints[1].Y() );
7686 aPoints[4] = Point( rRect.TopRight().X()+1, rRect.TopRight().Y() + rRect.GetHeight()/2 );
7687 aPoints[3] = Point( aPoints[4].X(), aPoints[4].Y() - ky );
7688 aPoints[5] = Point( aPoints[4].X(), aPoints[4].Y() + ky );
7690 aPoints[7] = Point( rRect.BottomLeft().X() + rRect.GetWidth()/2, rRect.BottomLeft().Y()+1 );
7691 aPoints[6] = Point( aPoints[7].X() + kx, aPoints[7].Y() );
7692 aPoints[8] = Point( aPoints[7].X() - kx, aPoints[7].Y() );
7694 aPoints[10] = Point( rRect.TopLeft().X(), rRect.TopLeft().Y() + rRect.GetHeight()/2 );
7695 aPoints[9] = Point( aPoints[10].X(), aPoints[10].Y() + ky );
7696 aPoints[11] = Point( aPoints[10].X(), aPoints[10].Y() - ky );
7698 OStringBuffer aLine( 80 );
7699 m_aPages.back().appendPoint( aPoints[1], aLine );
7700 aLine.append( " m " );
7701 m_aPages.back().appendPoint( aPoints[2], aLine );
7702 aLine.append( ' ' );
7703 m_aPages.back().appendPoint( aPoints[3], aLine );
7704 aLine.append( ' ' );
7705 m_aPages.back().appendPoint( aPoints[4], aLine );
7706 aLine.append( " c\n" );
7707 m_aPages.back().appendPoint( aPoints[5], aLine );
7708 aLine.append( ' ' );
7709 m_aPages.back().appendPoint( aPoints[6], aLine );
7710 aLine.append( ' ' );
7711 m_aPages.back().appendPoint( aPoints[7], aLine );
7712 aLine.append( " c\n" );
7713 m_aPages.back().appendPoint( aPoints[8], aLine );
7714 aLine.append( ' ' );
7715 m_aPages.back().appendPoint( aPoints[9], aLine );
7716 aLine.append( ' ' );
7717 m_aPages.back().appendPoint( aPoints[10], aLine );
7718 aLine.append( " c\n" );
7719 m_aPages.back().appendPoint( aPoints[11], aLine );
7720 aLine.append( ' ' );
7721 m_aPages.back().appendPoint( aPoints[0], aLine );
7722 aLine.append( ' ' );
7723 m_aPages.back().appendPoint( aPoints[1], aLine );
7724 aLine.append( " c " );
7726 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7727 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7728 aLine.append( "b*\n" );
7729 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7730 aLine.append( "s\n" );
7731 else
7732 aLine.append( "f*\n" );
7734 writeBuffer( aLine.getStr(), aLine.getLength() );
7737 static double calcAngle( const tools::Rectangle& rRect, const Point& rPoint )
7739 Point aOrigin((rRect.Left()+rRect.Right()+1)/2,
7740 (rRect.Top()+rRect.Bottom()+1)/2);
7741 Point aPoint = rPoint - aOrigin;
7743 double fX = static_cast<double>(aPoint.X());
7744 double fY = static_cast<double>(-aPoint.Y());
7746 if ((rRect.GetHeight() == 0) || (rRect.GetWidth() == 0))
7747 throw o3tl::divide_by_zero();
7749 if( rRect.GetWidth() > rRect.GetHeight() )
7750 fY = fY*(static_cast<double>(rRect.GetWidth())/static_cast<double>(rRect.GetHeight()));
7751 else if( rRect.GetHeight() > rRect.GetWidth() )
7752 fX = fX*(static_cast<double>(rRect.GetHeight())/static_cast<double>(rRect.GetWidth()));
7753 return atan2( fY, fX );
7756 void PDFWriterImpl::drawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop, bool bWithPie, bool bWithChord )
7758 MARK( "drawArc" );
7760 updateGraphicsState();
7762 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7763 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7764 return;
7766 // calculate start and stop angles
7767 const double fStartAngle = calcAngle( rRect, rStart );
7768 double fStopAngle = calcAngle( rRect, rStop );
7769 while( fStopAngle < fStartAngle )
7770 fStopAngle += 2.0*M_PI;
7771 const int nFragments = static_cast<int>((fStopAngle-fStartAngle)/(M_PI/2.0))+1;
7772 const double fFragmentDelta = (fStopAngle-fStartAngle)/static_cast<double>(nFragments);
7773 const double kappa = fabs( 4.0 * (1.0-cos(fFragmentDelta/2.0))/sin(fFragmentDelta/2.0) / 3.0);
7774 const double halfWidth = static_cast<double>(rRect.GetWidth())/2.0;
7775 const double halfHeight = static_cast<double>(rRect.GetHeight())/2.0;
7777 const Point aCenter( (rRect.Left()+rRect.Right()+1)/2,
7778 (rRect.Top()+rRect.Bottom()+1)/2 );
7780 OStringBuffer aLine( 30*nFragments );
7781 Point aPoint( static_cast<int>(halfWidth * cos(fStartAngle) ),
7782 -static_cast<int>(halfHeight * sin(fStartAngle) ) );
7783 aPoint += aCenter;
7784 m_aPages.back().appendPoint( aPoint, aLine );
7785 aLine.append( " m " );
7786 if( !basegfx::fTools::equal(fStartAngle, fStopAngle) )
7788 for( int i = 0; i < nFragments; i++ )
7790 const double fStartFragment = fStartAngle + static_cast<double>(i)*fFragmentDelta;
7791 const double fStopFragment = fStartFragment + fFragmentDelta;
7792 aPoint = Point( static_cast<int>(halfWidth * (cos(fStartFragment) - kappa*sin(fStartFragment) ) ),
7793 -static_cast<int>(halfHeight * (sin(fStartFragment) + kappa*cos(fStartFragment) ) ) );
7794 aPoint += aCenter;
7795 m_aPages.back().appendPoint( aPoint, aLine );
7796 aLine.append( ' ' );
7798 aPoint = Point( static_cast<int>(halfWidth * (cos(fStopFragment) + kappa*sin(fStopFragment) ) ),
7799 -static_cast<int>(halfHeight * (sin(fStopFragment) - kappa*cos(fStopFragment) ) ) );
7800 aPoint += aCenter;
7801 m_aPages.back().appendPoint( aPoint, aLine );
7802 aLine.append( ' ' );
7804 aPoint = Point( static_cast<int>(halfWidth * cos(fStopFragment) ),
7805 -static_cast<int>(halfHeight * sin(fStopFragment) ) );
7806 aPoint += aCenter;
7807 m_aPages.back().appendPoint( aPoint, aLine );
7808 aLine.append( " c\n" );
7811 if( bWithChord || bWithPie )
7813 if( bWithPie )
7815 m_aPages.back().appendPoint( aCenter, aLine );
7816 aLine.append( " l " );
7818 aLine.append( "h " );
7820 if( ! bWithChord && ! bWithPie )
7821 aLine.append( "S\n" );
7822 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7823 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7824 aLine.append( "B*\n" );
7825 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7826 aLine.append( "S\n" );
7827 else
7828 aLine.append( "f*\n" );
7830 writeBuffer( aLine.getStr(), aLine.getLength() );
7833 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly )
7835 MARK( "drawPolyLine" );
7837 sal_uInt16 nPoints = rPoly.GetSize();
7838 if( nPoints < 2 )
7839 return;
7841 updateGraphicsState();
7843 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
7844 return;
7846 OStringBuffer aLine( 20 * nPoints );
7847 m_aPages.back().appendPolygon( rPoly, aLine, rPoly[0] == rPoly[nPoints-1] );
7848 aLine.append( "S\n" );
7850 writeBuffer( aLine.getStr(), aLine.getLength() );
7853 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const LineInfo& rInfo )
7855 MARK( "drawPolyLine with LineInfo" );
7857 updateGraphicsState();
7859 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
7860 return;
7862 OStringBuffer aLine;
7863 aLine.append( "q " );
7864 if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
7866 writeBuffer( aLine.getStr(), aLine.getLength() );
7867 drawPolyLine( rPoly );
7868 writeBuffer( "Q\n", 2 );
7870 else
7872 PDFWriter::ExtLineInfo aInfo;
7873 convertLineInfoToExtLineInfo( rInfo, aInfo );
7874 drawPolyLine( rPoly, aInfo );
7878 void PDFWriterImpl::convertLineInfoToExtLineInfo( const LineInfo& rIn, PDFWriter::ExtLineInfo& rOut )
7880 SAL_WARN_IF( rIn.GetStyle() != LineStyle::Dash, "vcl.pdfwriter", "invalid conversion" );
7881 rOut.m_fLineWidth = rIn.GetWidth();
7882 rOut.m_fTransparency = 0.0;
7883 rOut.m_eCap = PDFWriter::capButt;
7884 rOut.m_eJoin = PDFWriter::joinMiter;
7885 rOut.m_fMiterLimit = 10;
7886 rOut.m_aDashArray.clear();
7888 // add DashDot to DashArray
7889 const int nDashes = rIn.GetDashCount();
7890 const int nDashLen = rIn.GetDashLen();
7891 const int nDistance = rIn.GetDistance();
7893 for( int n = 0; n < nDashes; n++ )
7895 rOut.m_aDashArray.push_back( nDashLen );
7896 rOut.m_aDashArray.push_back( nDistance );
7898 const int nDots = rIn.GetDotCount();
7899 const int nDotLen = rIn.GetDotLen();
7901 for( int n = 0; n < nDots; n++ )
7903 rOut.m_aDashArray.push_back( nDotLen );
7904 rOut.m_aDashArray.push_back( nDistance );
7907 // add LineJoin
7908 switch(rIn.GetLineJoin())
7910 case basegfx::B2DLineJoin::Bevel :
7912 rOut.m_eJoin = PDFWriter::joinBevel;
7913 break;
7915 // Pdf has no 'none' lineJoin, default is miter
7916 case basegfx::B2DLineJoin::NONE :
7917 case basegfx::B2DLineJoin::Miter :
7919 rOut.m_eJoin = PDFWriter::joinMiter;
7920 break;
7922 case basegfx::B2DLineJoin::Round :
7924 rOut.m_eJoin = PDFWriter::joinRound;
7925 break;
7929 // add LineCap
7930 switch(rIn.GetLineCap())
7932 default: /* css::drawing::LineCap_BUTT */
7934 rOut.m_eCap = PDFWriter::capButt;
7935 break;
7937 case css::drawing::LineCap_ROUND:
7939 rOut.m_eCap = PDFWriter::capRound;
7940 break;
7942 case css::drawing::LineCap_SQUARE:
7944 rOut.m_eCap = PDFWriter::capSquare;
7945 break;
7950 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const PDFWriter::ExtLineInfo& rInfo )
7952 MARK( "drawPolyLine with ExtLineInfo" );
7954 updateGraphicsState();
7956 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
7957 return;
7959 if( rInfo.m_fTransparency >= 1.0 )
7960 return;
7962 if( rInfo.m_fTransparency != 0.0 )
7963 beginTransparencyGroup();
7965 OStringBuffer aLine;
7966 aLine.append( "q " );
7967 m_aPages.back().appendMappedLength( rInfo.m_fLineWidth, aLine );
7968 aLine.append( " w" );
7969 if( rInfo.m_aDashArray.size() < 10 ) // implementation limit of acrobat reader
7971 switch( rInfo.m_eCap )
7973 default:
7974 case PDFWriter::capButt: aLine.append( " 0 J" );break;
7975 case PDFWriter::capRound: aLine.append( " 1 J" );break;
7976 case PDFWriter::capSquare: aLine.append( " 2 J" );break;
7978 switch( rInfo.m_eJoin )
7980 default:
7981 case PDFWriter::joinMiter:
7983 double fLimit = rInfo.m_fMiterLimit;
7984 if( rInfo.m_fLineWidth < rInfo.m_fMiterLimit )
7985 fLimit = fLimit / rInfo.m_fLineWidth;
7986 if( fLimit < 1.0 )
7987 fLimit = 1.0;
7988 aLine.append( " 0 j " );
7989 appendDouble( fLimit, aLine );
7990 aLine.append( " M" );
7992 break;
7993 case PDFWriter::joinRound: aLine.append( " 1 j" );break;
7994 case PDFWriter::joinBevel: aLine.append( " 2 j" );break;
7996 if( !rInfo.m_aDashArray.empty() )
7998 aLine.append( " [ " );
7999 for (auto const& dash : rInfo.m_aDashArray)
8001 m_aPages.back().appendMappedLength( dash, aLine );
8002 aLine.append( ' ' );
8004 aLine.append( "] 0 d" );
8006 aLine.append( "\n" );
8007 writeBuffer( aLine.getStr(), aLine.getLength() );
8008 drawPolyLine( rPoly );
8010 else
8012 basegfx::B2DPolygon aPoly(rPoly.getB2DPolygon());
8013 basegfx::B2DPolyPolygon aPolyPoly;
8015 basegfx::utils::applyLineDashing(aPoly, rInfo.m_aDashArray, &aPolyPoly);
8017 // Old applyLineDashing subdivided the polygon. New one will create bezier curve segments.
8018 // To mimic old behaviour, apply subdivide here. If beziers shall be written (better quality)
8019 // this line needs to be removed and the loop below adapted accordingly
8020 aPolyPoly = basegfx::utils::adaptiveSubdivideByAngle(aPolyPoly);
8022 const sal_uInt32 nPolygonCount(aPolyPoly.count());
8024 for( sal_uInt32 nPoly = 0; nPoly < nPolygonCount; nPoly++ )
8026 aLine.append( (nPoly != 0 && (nPoly & 7) == 0) ? "\n" : " " );
8027 aPoly = aPolyPoly.getB2DPolygon( nPoly );
8028 const sal_uInt32 nPointCount(aPoly.count());
8030 if(nPointCount)
8032 const sal_uInt32 nEdgeCount(aPoly.isClosed() ? nPointCount : nPointCount - 1);
8033 basegfx::B2DPoint aCurrent(aPoly.getB2DPoint(0));
8035 for(sal_uInt32 a(0); a < nEdgeCount; a++)
8037 if( a > 0 )
8038 aLine.append( " " );
8039 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
8040 const basegfx::B2DPoint aNext(aPoly.getB2DPoint(nNextIndex));
8042 m_aPages.back().appendPoint( Point( FRound(aCurrent.getX()),
8043 FRound(aCurrent.getY()) ),
8044 aLine );
8045 aLine.append( " m " );
8046 m_aPages.back().appendPoint( Point( FRound(aNext.getX()),
8047 FRound(aNext.getY()) ),
8048 aLine );
8049 aLine.append( " l" );
8051 // prepare next edge
8052 aCurrent = aNext;
8056 aLine.append( " S " );
8057 writeBuffer( aLine.getStr(), aLine.getLength() );
8059 writeBuffer( "Q\n", 2 );
8061 if( rInfo.m_fTransparency != 0.0 )
8063 // FIXME: actually this may be incorrect with bezier polygons
8064 tools::Rectangle aBoundRect( rPoly.GetBoundRect() );
8065 // avoid clipping with thick lines
8066 if( rInfo.m_fLineWidth > 0.0 )
8068 sal_Int32 nLW = sal_Int32(rInfo.m_fLineWidth);
8069 aBoundRect.AdjustTop( -nLW );
8070 aBoundRect.AdjustLeft( -nLW );
8071 aBoundRect.AdjustRight(nLW );
8072 aBoundRect.AdjustBottom(nLW );
8074 endTransparencyGroup( aBoundRect, static_cast<sal_uInt16>(100.0*rInfo.m_fTransparency) );
8078 void PDFWriterImpl::drawPixel( const Point& rPoint, const Color& rColor )
8080 MARK( "drawPixel" );
8082 Color aColor = ( rColor == COL_TRANSPARENT ? m_aGraphicsStack.front().m_aLineColor : rColor );
8084 if( aColor == COL_TRANSPARENT )
8085 return;
8087 // pixels are drawn in line color, so have to set
8088 // the nonstroking color to line color
8089 Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
8090 setFillColor( aColor );
8092 updateGraphicsState();
8094 OStringBuffer aLine( 20 );
8095 m_aPages.back().appendPoint( rPoint, aLine );
8096 aLine.append( ' ' );
8097 appendDouble( 1.0/double(GetDPIX()), aLine );
8098 aLine.append( ' ' );
8099 appendDouble( 1.0/double(GetDPIY()), aLine );
8100 aLine.append( " re f\n" );
8101 writeBuffer( aLine.getStr(), aLine.getLength() );
8103 setFillColor( aOldFillColor );
8106 void PDFWriterImpl::writeTransparentObject( TransparencyEmit& rObject )
8108 CHECK_RETURN2( updateObject( rObject.m_nObject ) );
8110 bool bFlateFilter = compressStream( rObject.m_pContentStream.get() );
8111 sal_uLong nSize = rObject.m_pContentStream->TellEnd();
8112 rObject.m_pContentStream->Seek( STREAM_SEEK_TO_BEGIN );
8113 if (g_bDebugDisableCompression)
8115 emitComment( "PDFWriterImpl::writeTransparentObject" );
8117 OStringBuffer aLine( 512 );
8118 CHECK_RETURN2( updateObject( rObject.m_nObject ) );
8119 aLine.append( rObject.m_nObject );
8120 aLine.append( " 0 obj\n"
8121 "<</Type/XObject\n"
8122 "/Subtype/Form\n"
8123 "/BBox[ " );
8124 appendFixedInt( rObject.m_aBoundRect.Left(), aLine );
8125 aLine.append( ' ' );
8126 appendFixedInt( rObject.m_aBoundRect.Top(), aLine );
8127 aLine.append( ' ' );
8128 appendFixedInt( rObject.m_aBoundRect.Right(), aLine );
8129 aLine.append( ' ' );
8130 appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aLine );
8131 aLine.append( " ]\n" );
8132 if( ! rObject.m_pSoftMaskStream )
8134 if( ! m_bIsPDF_A1 )
8136 // 7.8.3 Resource dicts are required for content streams
8137 aLine.append( "/Resources " );
8138 aLine.append( getResourceDictObj() );
8139 aLine.append( " 0 R\n" );
8141 aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/K true>>\n" );
8145 aLine.append( "/Length " );
8146 aLine.append( static_cast<sal_Int32>(nSize) );
8147 aLine.append( "\n" );
8148 if( bFlateFilter )
8149 aLine.append( "/Filter/FlateDecode\n" );
8150 aLine.append( ">>\n"
8151 "stream\n" );
8152 CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8153 checkAndEnableStreamEncryption( rObject.m_nObject );
8154 CHECK_RETURN2( writeBuffer( rObject.m_pContentStream->GetData(), nSize ) );
8155 disableStreamEncryption();
8156 aLine.setLength( 0 );
8157 aLine.append( "\n"
8158 "endstream\n"
8159 "endobj\n\n" );
8160 CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8162 // write ExtGState dict for this XObject
8163 aLine.setLength( 0 );
8164 aLine.append( rObject.m_nExtGStateObject );
8165 aLine.append( " 0 obj\n"
8166 "<<" );
8167 if( ! rObject.m_pSoftMaskStream )
8169 if( m_bIsPDF_A1 )
8171 aLine.append( "/CA 1.0/ca 1.0" );
8172 m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
8174 else
8176 aLine.append( "/CA " );
8177 appendDouble( rObject.m_fAlpha, aLine );
8178 aLine.append( "\n"
8179 " /ca " );
8180 appendDouble( rObject.m_fAlpha, aLine );
8182 aLine.append( "\n" );
8184 else
8186 if( m_bIsPDF_A1 )
8188 aLine.append( "/SMask/None" );
8189 m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
8191 else
8193 sal_Int32 nMaskSize = static_cast<sal_Int32>(rObject.m_pSoftMaskStream->TellEnd());
8194 rObject.m_pSoftMaskStream->Seek( STREAM_SEEK_TO_BEGIN );
8195 sal_Int32 nMaskObject = createObject();
8196 aLine.append( "/SMask<</Type/Mask/S/Luminosity/G " );
8197 aLine.append( nMaskObject );
8198 aLine.append( " 0 R>>\n" );
8200 OStringBuffer aMask;
8201 aMask.append( nMaskObject );
8202 aMask.append( " 0 obj\n"
8203 "<</Type/XObject\n"
8204 "/Subtype/Form\n"
8205 "/BBox[" );
8206 appendFixedInt( rObject.m_aBoundRect.Left(), aMask );
8207 aMask.append( ' ' );
8208 appendFixedInt( rObject.m_aBoundRect.Top(), aMask );
8209 aMask.append( ' ' );
8210 appendFixedInt( rObject.m_aBoundRect.Right(), aMask );
8211 aMask.append( ' ' );
8212 appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aMask );
8213 aMask.append( "]\n" );
8215 // 7.8.3 Resource dicts are required for content streams
8216 aMask.append( "/Resources " );
8217 aMask.append( getResourceDictObj() );
8218 aMask.append( " 0 R\n" );
8220 aMask.append( "/Group<</S/Transparency/CS/DeviceRGB>>\n" );
8221 aMask.append( "/Length " );
8222 aMask.append( nMaskSize );
8223 aMask.append( ">>\n"
8224 "stream\n" );
8225 CHECK_RETURN2( updateObject( nMaskObject ) );
8226 checkAndEnableStreamEncryption( nMaskObject );
8227 CHECK_RETURN2( writeBuffer( aMask.getStr(), aMask.getLength() ) );
8228 CHECK_RETURN2( writeBuffer( rObject.m_pSoftMaskStream->GetData(), nMaskSize ) );
8229 disableStreamEncryption();
8230 aMask.setLength( 0 );
8231 aMask.append( "\nendstream\n"
8232 "endobj\n\n" );
8233 CHECK_RETURN2( writeBuffer( aMask.getStr(), aMask.getLength() ) );
8236 aLine.append( ">>\n"
8237 "endobj\n\n" );
8238 CHECK_RETURN2( updateObject( rObject.m_nExtGStateObject ) );
8239 CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8242 bool PDFWriterImpl::writeGradientFunction( GradientEmit const & rObject )
8244 // LO internal gradient -> PDF shading type:
8245 // * GradientStyle::Linear: axial shading, using sampled-function with 2 samples
8246 // [t=0:colorStart, t=1:colorEnd]
8247 // * GradientStyle::Axial: axial shading, using sampled-function with 3 samples
8248 // [t=0:colorEnd, t=0.5:colorStart, t=1:colorEnd]
8249 // * other styles: function shading with aSize.Width() * aSize.Height() samples
8250 sal_Int32 nFunctionObject = createObject();
8251 CHECK_RETURN( updateObject( nFunctionObject ) );
8253 ScopedVclPtrInstance< VirtualDevice > aDev;
8254 aDev->SetOutputSizePixel( rObject.m_aSize );
8255 aDev->SetMapMode( MapMode( MapUnit::MapPixel ) );
8256 if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
8257 aDev->SetDrawMode( aDev->GetDrawMode() |
8258 ( DrawModeFlags::GrayLine | DrawModeFlags::GrayFill | DrawModeFlags::GrayText |
8259 DrawModeFlags::GrayBitmap | DrawModeFlags::GrayGradient ) );
8260 aDev->DrawGradient( tools::Rectangle( Point( 0, 0 ), rObject.m_aSize ), rObject.m_aGradient );
8262 Bitmap aSample = aDev->GetBitmap( Point( 0, 0 ), rObject.m_aSize );
8263 Bitmap::ScopedReadAccess pAccess(aSample);
8265 Size aSize = aSample.GetSizePixel();
8267 sal_Int32 nStreamLengthObject = createObject();
8268 if (g_bDebugDisableCompression)
8270 emitComment( "PDFWriterImpl::writeGradientFunction" );
8272 OStringBuffer aLine( 120 );
8273 aLine.append( nFunctionObject );
8274 aLine.append( " 0 obj\n"
8275 "<</FunctionType 0\n");
8276 switch (rObject.m_aGradient.GetStyle())
8278 case GradientStyle::Linear:
8279 case GradientStyle::Axial:
8280 aLine.append("/Domain[ 0 1]\n");
8281 break;
8282 default:
8283 aLine.append("/Domain[ 0 1 0 1]\n");
8285 aLine.append("/Size[ " );
8286 switch (rObject.m_aGradient.GetStyle())
8288 case GradientStyle::Linear:
8289 aLine.append('2');
8290 break;
8291 case GradientStyle::Axial:
8292 aLine.append('3');
8293 break;
8294 default:
8295 aLine.append( static_cast<sal_Int32>(aSize.Width()) );
8296 aLine.append( ' ' );
8297 aLine.append( static_cast<sal_Int32>(aSize.Height()) );
8299 aLine.append( " ]\n"
8300 "/BitsPerSample 8\n"
8301 "/Range[ 0 1 0 1 0 1 ]\n"
8302 "/Order 3\n"
8303 "/Length " );
8304 aLine.append( nStreamLengthObject );
8305 if (!g_bDebugDisableCompression)
8306 aLine.append( " 0 R\n"
8307 "/Filter/FlateDecode"
8308 ">>\n"
8309 "stream\n" );
8310 else
8311 aLine.append( " 0 R\n"
8312 ">>\n"
8313 "stream\n" );
8314 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8316 sal_uInt64 nStartStreamPos = 0;
8317 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nStartStreamPos)) );
8319 checkAndEnableStreamEncryption( nFunctionObject );
8320 beginCompression();
8321 sal_uInt8 aCol[3];
8322 switch (rObject.m_aGradient.GetStyle())
8324 case GradientStyle::Axial:
8325 aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
8326 aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
8327 aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
8328 CHECK_RETURN( writeBuffer( aCol, 3 ) );
8329 [[fallthrough]];
8330 case GradientStyle::Linear:
8332 aCol[0] = rObject.m_aGradient.GetStartColor().GetRed();
8333 aCol[1] = rObject.m_aGradient.GetStartColor().GetGreen();
8334 aCol[2] = rObject.m_aGradient.GetStartColor().GetBlue();
8335 CHECK_RETURN( writeBuffer( aCol, 3 ) );
8337 aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
8338 aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
8339 aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
8340 CHECK_RETURN( writeBuffer( aCol, 3 ) );
8341 break;
8343 default:
8344 for( int y = aSize.Height()-1; y >= 0; y-- )
8346 for( long x = 0; x < aSize.Width(); x++ )
8348 BitmapColor aColor = pAccess->GetColor( y, x );
8349 aCol[0] = aColor.GetRed();
8350 aCol[1] = aColor.GetGreen();
8351 aCol[2] = aColor.GetBlue();
8352 CHECK_RETURN( writeBuffer( aCol, 3 ) );
8356 endCompression();
8357 disableStreamEncryption();
8359 sal_uInt64 nEndStreamPos = 0;
8360 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nEndStreamPos)) );
8362 aLine.setLength( 0 );
8363 aLine.append( "\nendstream\nendobj\n\n" );
8364 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8366 // write stream length
8367 CHECK_RETURN( updateObject( nStreamLengthObject ) );
8368 aLine.setLength( 0 );
8369 aLine.append( nStreamLengthObject );
8370 aLine.append( " 0 obj\n" );
8371 aLine.append( static_cast<sal_Int64>(nEndStreamPos-nStartStreamPos) );
8372 aLine.append( "\nendobj\n\n" );
8373 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8375 CHECK_RETURN( updateObject( rObject.m_nObject ) );
8376 aLine.setLength( 0 );
8377 aLine.append( rObject.m_nObject );
8378 aLine.append( " 0 obj\n");
8379 switch (rObject.m_aGradient.GetStyle())
8381 case GradientStyle::Linear:
8382 case GradientStyle::Axial:
8383 aLine.append("<</ShadingType 2\n");
8384 break;
8385 default:
8386 aLine.append("<</ShadingType 1\n");
8388 aLine.append("/ColorSpace/DeviceRGB\n"
8389 "/AntiAlias true\n");
8391 // Determination of shading axis
8392 // See: OutputDevice::ImplDrawLinearGradient for reference
8393 tools::Rectangle aRect;
8394 aRect.SetLeft(0);
8395 aRect.SetTop(0);
8396 aRect.SetRight( aSize.Width() );
8397 aRect.SetBottom( aSize.Height() );
8399 tools::Rectangle aBoundRect;
8400 Point aCenter;
8401 sal_uInt16 nAngle = rObject.m_aGradient.GetAngle() % 3600;
8402 rObject.m_aGradient.GetBoundRect( aRect, aBoundRect, aCenter );
8404 const bool bLinear = (rObject.m_aGradient.GetStyle() == GradientStyle::Linear);
8405 double fBorder = aBoundRect.GetHeight() * rObject.m_aGradient.GetBorder() / 100.0;
8406 if ( !bLinear )
8408 fBorder /= 2.0;
8411 aBoundRect.AdjustBottom( -fBorder );
8412 if (!bLinear)
8414 aBoundRect.AdjustTop(fBorder );
8417 switch (rObject.m_aGradient.GetStyle())
8419 case GradientStyle::Linear:
8420 case GradientStyle::Axial:
8422 aLine.append("/Domain[ 0 1 ]\n"
8423 "/Coords[ " );
8424 tools::Polygon aPoly( 2 );
8425 aPoly[0] = aBoundRect.BottomCenter();
8426 aPoly[1] = aBoundRect.TopCenter();
8427 aPoly.Rotate( aCenter, 3600 - nAngle );
8429 aLine.append( static_cast<sal_Int32>(aPoly[0].X()) );
8430 aLine.append( " " );
8431 aLine.append( static_cast<sal_Int32>(aPoly[0].Y()) );
8432 aLine.append( " " );
8433 aLine.append( static_cast<sal_Int32>(aPoly[1].X()));
8434 aLine.append( " ");
8435 aLine.append( static_cast<sal_Int32>(aPoly[1].Y()));
8436 aLine.append( " ]\n");
8437 aLine.append("/Extend [true true]\n");
8438 break;
8440 default:
8441 aLine.append("/Domain[ 0 1 0 1 ]\n"
8442 "/Matrix[ " );
8443 aLine.append( static_cast<sal_Int32>(aSize.Width()) );
8444 aLine.append( " 0 0 " );
8445 aLine.append( static_cast<sal_Int32>(aSize.Height()) );
8446 aLine.append( " 0 0 ]\n");
8448 aLine.append("/Function " );
8449 aLine.append( nFunctionObject );
8450 aLine.append( " 0 R\n"
8451 ">>\n"
8452 "endobj\n\n" );
8453 return writeBuffer( aLine.getStr(), aLine.getLength() );
8456 void PDFWriterImpl::writeJPG( JPGEmit& rObject )
8458 if (!rObject.m_aReferenceXObject.m_aPDFData.empty() && !m_aContext.UseReferenceXObject)
8460 writeReferenceXObject(rObject.m_aReferenceXObject);
8461 return;
8464 CHECK_RETURN2( rObject.m_pStream );
8465 CHECK_RETURN2( updateObject( rObject.m_nObject ) );
8467 sal_Int32 nLength = rObject.m_pStream->TellEnd();
8468 rObject.m_pStream->Seek( STREAM_SEEK_TO_BEGIN );
8470 sal_Int32 nMaskObject = 0;
8471 if( !!rObject.m_aMask )
8473 if( rObject.m_aMask.GetBitCount() == 1 ||
8474 ( rObject.m_aMask.GetBitCount() == 8 && m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 && !m_bIsPDF_A1 )
8477 nMaskObject = createObject();
8479 else if( m_bIsPDF_A1 )
8480 m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
8481 else if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
8482 m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDF13 );
8485 if (g_bDebugDisableCompression)
8487 emitComment( "PDFWriterImpl::writeJPG" );
8490 OStringBuffer aLine(200);
8491 aLine.append( rObject.m_nObject );
8492 aLine.append( " 0 obj\n"
8493 "<</Type/XObject/Subtype/Image/Width " );
8494 aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Width()) );
8495 aLine.append( " /Height " );
8496 aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Height()) );
8497 aLine.append( " /BitsPerComponent 8 " );
8498 if( rObject.m_bTrueColor )
8499 aLine.append( "/ColorSpace/DeviceRGB" );
8500 else
8501 aLine.append( "/ColorSpace/DeviceGray" );
8502 aLine.append( "/Filter/DCTDecode/Length " );
8503 aLine.append( nLength );
8504 if( nMaskObject )
8506 aLine.append( rObject.m_aMask.GetBitCount() == 1 ? " /Mask " : " /SMask " );
8507 aLine.append( nMaskObject );
8508 aLine.append( " 0 R " );
8510 aLine.append( ">>\nstream\n" );
8511 CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8513 checkAndEnableStreamEncryption( rObject.m_nObject );
8514 CHECK_RETURN2( writeBuffer( rObject.m_pStream->GetData(), nLength ) );
8515 disableStreamEncryption();
8517 aLine.setLength( 0 );
8518 aLine.append( "\nendstream\nendobj\n\n" );
8519 CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8521 if( nMaskObject )
8523 BitmapEmit aEmit;
8524 aEmit.m_nObject = nMaskObject;
8525 if( rObject.m_aMask.GetBitCount() == 1 )
8526 aEmit.m_aBitmap = BitmapEx( rObject.m_aMask, rObject.m_aMask );
8527 else if( rObject.m_aMask.GetBitCount() == 8 )
8528 aEmit.m_aBitmap = BitmapEx( rObject.m_aMask, AlphaMask( rObject.m_aMask ) );
8529 writeBitmapObject( aEmit, true );
8532 writeReferenceXObject(rObject.m_aReferenceXObject);
8535 sal_Int32 PDFWriterImpl::copyExternalResource(SvMemoryStream& rDocBuffer, filter::PDFObjectElement& rObject, std::map<sal_Int32, sal_Int32>& rCopiedResources)
8537 auto it = rCopiedResources.find(rObject.GetObjectValue());
8538 if (it != rCopiedResources.end())
8539 // This resource was already copied once, nothing to do.
8540 return it->second;
8542 sal_Int32 nObject = createObject();
8543 // Remember what is the ID of this object in our output.
8544 rCopiedResources[rObject.GetObjectValue()] = nObject;
8545 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::copyExternalResource: " << rObject.GetObjectValue() << " -> " << nObject);
8547 OStringBuffer aLine;
8548 aLine.append(nObject);
8549 aLine.append(" 0 obj\n");
8550 if (rObject.GetDictionary())
8552 aLine.append("<<");
8554 // Complex case: can't copy the dictionary byte array as is, as it may contain references.
8555 bool bDone = false;
8556 sal_uInt64 nCopyStart = 0;
8557 for (auto pReference : rObject.GetDictionaryReferences())
8559 if (pReference)
8561 filter::PDFObjectElement* pReferenced = pReference->LookupObject();
8562 if (pReferenced)
8564 // Copy the referenced object.
8565 sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced, rCopiedResources);
8567 sal_uInt64 nReferenceStart = pReference->GetObjectElement().GetLocation();
8568 sal_uInt64 nReferenceEnd = pReference->GetOffset();
8569 sal_uInt64 nOffset = 0;
8570 if (nCopyStart == 0)
8571 // Dict start -> reference start.
8572 nOffset = rObject.GetDictionaryOffset();
8573 else
8574 // Previous reference end -> reference start.
8575 nOffset = nCopyStart;
8576 aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nOffset, nReferenceStart - nOffset);
8577 // Write the updated reference.
8578 aLine.append(" ");
8579 aLine.append(nRef);
8580 aLine.append(" 0 R");
8581 // Start copying here next time.
8582 nCopyStart = nReferenceEnd;
8584 bDone = true;
8589 if (bDone)
8591 // Copy the last part here, in the complex case.
8592 sal_uInt64 nDictEnd = rObject.GetDictionaryOffset() + rObject.GetDictionaryLength();
8593 const sal_Int32 nLen = nDictEnd - nCopyStart;
8594 if (nLen < 0)
8595 SAL_WARN("vcl.pdfwriter", "copyExternalResource() failed");
8596 else
8597 aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nCopyStart, nLen);
8599 else
8600 // Can copy it as-is.
8601 aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + rObject.GetDictionaryOffset(), rObject.GetDictionaryLength());
8603 aLine.append(">>\n");
8606 if (filter::PDFStreamElement* pStream = rObject.GetStream())
8608 aLine.append("stream\n");
8609 SvMemoryStream& rStream = pStream->GetMemory();
8610 aLine.append(static_cast<const sal_Char*>(rStream.GetData()), rStream.GetSize());
8611 aLine.append("\nendstream\n");
8614 if (filter::PDFArrayElement* pArray = rObject.GetArray())
8616 aLine.append("[");
8618 const std::vector<filter::PDFElement*>& rElements = pArray->GetElements();
8619 bool bDone = false;
8620 // Complex case: can't copy the array byte array as is, as it may contain references.
8621 sal_uInt64 nCopyStart = 0;
8622 for (const auto pElement : rElements)
8624 auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
8625 if (pReference)
8627 filter::PDFObjectElement* pReferenced = pReference->LookupObject();
8628 if (pReferenced)
8630 // Copy the referenced object.
8631 sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced, rCopiedResources);
8633 sal_uInt64 nReferenceStart = pReference->GetObjectElement().GetLocation();
8634 sal_uInt64 nReferenceEnd = pReference->GetOffset();
8635 sal_uInt64 nOffset = 0;
8636 if (nCopyStart == 0)
8637 // Array start -> reference start.
8638 nOffset = rObject.GetArrayOffset();
8639 else
8640 // Previous reference end -> reference start.
8641 nOffset = nCopyStart;
8642 aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nOffset, nReferenceStart - nOffset);
8644 // Write the updated reference.
8645 aLine.append(" ");
8646 aLine.append(nRef);
8647 aLine.append(" 0 R");
8648 // Start copying here next time.
8649 nCopyStart = nReferenceEnd;
8651 bDone = true;
8656 if (bDone)
8658 // Copy the last part here, in the complex case.
8659 sal_uInt64 nArrEnd = rObject.GetArrayOffset() + rObject.GetArrayLength();
8660 const sal_Int32 nLen = nArrEnd - nCopyStart;
8661 if (nLen < 0)
8662 SAL_WARN("vcl.pdfwriter", "copyExternalResource() failed");
8663 else
8664 aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nCopyStart, nLen);
8666 else
8667 // Can copy it as-is.
8668 aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + rObject.GetArrayOffset(), rObject.GetArrayLength());
8670 aLine.append("]\n");
8673 // If the object has a number element outside a dictionary or array, copy that.
8674 if (filter::PDFNumberElement* pNumber = rObject.GetNumberElement())
8676 aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + pNumber->GetLocation(), pNumber->GetLength());
8677 aLine.append("\n");
8681 aLine.append("endobj\n\n");
8683 // We have the whole object, now write it to the output.
8684 if (!updateObject(nObject))
8685 return -1;
8686 if (!writeBuffer(aLine.getStr(), aLine.getLength()))
8687 return -1;
8689 return nObject;
8692 OString PDFWriterImpl::copyExternalResources(filter::PDFObjectElement& rPage, const OString& rKind, std::map<sal_Int32, sal_Int32>& rCopiedResources)
8694 // A name - object ID map, IDs as they appear in our output, not the
8695 // original ones.
8696 std::map<OString, sal_Int32> aRet;
8698 // Get the rKind subset of the resource dictionary.
8699 std::map<OString, filter::PDFElement*> aItems;
8700 if (auto pResources = dynamic_cast<filter::PDFDictionaryElement*>(rPage.Lookup("Resources")))
8702 // Resources is a direct dictionary.
8703 filter::PDFElement* pLookup = pResources->LookupElement(rKind);
8704 if (auto pDictionary = dynamic_cast<filter::PDFDictionaryElement*>(pLookup))
8706 // rKind is an inline dictionary.
8707 aItems = pDictionary->GetItems();
8709 else if (auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pLookup))
8711 // rKind refers to a dictionary.
8712 filter::PDFObjectElement* pReferenced = pReference->LookupObject();
8713 if (!pReferenced)
8715 return OString();
8718 aItems = pReferenced->GetDictionaryItems();
8721 else if (filter::PDFObjectElement* pPageResources = rPage.LookupObject("Resources"))
8723 // Resources is an indirect object.
8724 filter::PDFElement* pValue = pPageResources->Lookup(rKind);
8725 if (auto pDictionary = dynamic_cast<filter::PDFDictionaryElement*>(pValue))
8726 // Kind is a direct dictionary.
8727 aItems = pDictionary->GetItems();
8728 else if (filter::PDFObjectElement* pObject = pPageResources->LookupObject(rKind))
8729 // Kind is an indirect object.
8730 aItems = pObject->GetDictionaryItems();
8732 if (aItems.empty())
8733 return OString();
8735 SvMemoryStream& rDocBuffer = rPage.GetDocument().GetEditBuffer();
8737 for (const auto& rItem : aItems)
8739 // For each item copy it over to our output then insert it into aRet.
8740 auto pReference = dynamic_cast<filter::PDFReferenceElement*>(rItem.second);
8741 if (!pReference)
8742 continue;
8744 filter::PDFObjectElement* pValue = pReference->LookupObject();
8745 if (!pValue)
8746 continue;
8748 // Then copying over an object copy its dictionary and its stream.
8749 sal_Int32 nObject = copyExternalResource(rDocBuffer, *pValue, rCopiedResources);
8750 aRet[rItem.first] = nObject;
8753 // Build the dictionary entry string.
8754 OStringBuffer sRet("/" + rKind + "<<");
8755 for (const auto& rPair : aRet)
8757 sRet.append("/").append(rPair.first).append(" ").append(OString::number(rPair.second)).append(" 0 R");
8759 sRet.append(">>");
8761 return sRet.makeStringAndClear();
8764 void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
8766 if (rEmit.m_nFormObject <= 0)
8767 return;
8769 // Count /Matrix and /BBox.
8770 // vcl::ImportPDF() works with 96 DPI so use the same values here, too.
8771 sal_Int32 nOldDPIX = GetDPIX();
8772 SetDPIX(96);
8773 sal_Int32 nOldDPIY = GetDPIY();
8774 SetDPIY(96);
8775 Size aSize = PixelToLogic(rEmit.m_aPixelSize, MapMode(m_aMapMode.GetMapUnit()));
8776 SetDPIX(nOldDPIX);
8777 SetDPIY(nOldDPIY);
8778 double fScaleX = 1.0 / aSize.Width();
8779 double fScaleY = 1.0 / aSize.Height();
8781 sal_Int32 nWrappedFormObject = 0;
8782 if (!m_aContext.UseReferenceXObject)
8784 // Parse the PDF data, we need that to write the PDF dictionary of our
8785 // object.
8786 SvMemoryStream aPDFStream;
8787 aPDFStream.WriteBytes(rEmit.m_aPDFData.data(), rEmit.m_aPDFData.size());
8788 aPDFStream.Seek(0);
8789 filter::PDFDocument aPDFDocument;
8790 if (!aPDFDocument.Read(aPDFStream))
8792 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: reading the PDF document failed");
8793 return;
8795 std::vector<filter::PDFObjectElement*> aPages = aPDFDocument.GetPages();
8796 if (aPages.empty())
8798 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no pages");
8799 return;
8802 filter::PDFObjectElement* pPage = aPages[0];
8803 if (!pPage)
8805 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no page");
8806 return;
8809 std::vector<filter::PDFObjectElement*> aContentStreams;
8810 if (filter::PDFObjectElement* pContentStream = pPage->LookupObject("Contents"))
8811 aContentStreams.push_back(pContentStream);
8812 else if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Contents")))
8814 for (const auto pElement : pArray->GetElements())
8816 auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
8817 if (!pReference)
8818 continue;
8820 filter::PDFObjectElement* pObject = pReference->LookupObject();
8821 if (!pObject)
8822 continue;
8824 aContentStreams.push_back(pObject);
8828 if (aContentStreams.empty())
8830 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no content stream");
8831 return;
8834 // Maps from source object id (PDF image) to target object id (export result).
8835 std::map<sal_Int32, sal_Int32> aCopiedResources;
8837 nWrappedFormObject = createObject();
8838 // Write the form XObject wrapped below. This is a separate object from
8839 // the wrapper, this way there is no need to alter the stream contents.
8841 OStringBuffer aLine;
8842 aLine.append(nWrappedFormObject);
8843 aLine.append(" 0 obj\n");
8844 aLine.append("<< /Type /XObject");
8845 aLine.append(" /Subtype /Form");
8847 long nWidth = aSize.Width();
8848 long nHeight = aSize.Height();
8849 if (auto pRotate = dynamic_cast<filter::PDFNumberElement*>(pPage->Lookup("Rotate")))
8851 // The original page was rotated, then construct a transformation matrix which does the
8852 // same with our form object.
8853 if (rtl::math::approxEqual(pRotate->GetValue(), 90))
8855 std::swap(nWidth, nHeight);
8856 basegfx::B2DHomMatrix aMat;
8857 aMat.rotate(basegfx::deg2rad(pRotate->GetValue()));
8858 // Rotate around the origo (bottom left corner) counter-clockwise, then translate
8859 // horizontally to effectively keep the bottom left corner unchanged.
8860 aLine.append(" /Matrix [ ");
8861 aLine.append(aMat.get(0, 0));
8862 aLine.append(" ");
8863 aLine.append(aMat.get(0, 1));
8864 aLine.append(" ");
8865 aLine.append(aMat.get(1, 0));
8866 aLine.append(" ");
8867 aLine.append(aMat.get(1, 1));
8868 aLine.append(" 0 ");
8869 aLine.append(nWidth);
8870 aLine.append(" ] ");
8874 aLine.append(" /Resources <<");
8875 static const std::initializer_list<OString> aKeys =
8877 "ColorSpace",
8878 "ExtGState",
8879 "Font",
8880 "XObject",
8881 "Shading"
8883 for (const auto& rKey : aKeys)
8884 aLine.append(copyExternalResources(*pPage, rKey, aCopiedResources));
8885 aLine.append(">>");
8886 aLine.append(" /BBox [ 0 0 ");
8887 aLine.append(nWidth);
8888 aLine.append(" ");
8889 aLine.append(nHeight);
8890 aLine.append(" ]");
8892 if (!g_bDebugDisableCompression)
8893 aLine.append(" /Filter/FlateDecode");
8894 aLine.append(" /Length ");
8896 SvMemoryStream aStream;
8897 for (auto pContent : aContentStreams)
8899 filter::PDFStreamElement* pPageStream = pContent->GetStream();
8900 if (!pPageStream)
8902 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: contents has no stream");
8903 continue;
8906 SvMemoryStream& rPageStream = pPageStream->GetMemory();
8908 auto pFilter = dynamic_cast<filter::PDFNameElement*>(pContent->Lookup("Filter"));
8909 if (pFilter)
8911 if (pFilter->GetValue() != "FlateDecode")
8912 continue;
8914 SvMemoryStream aMemoryStream;
8915 ZCodec aZCodec;
8916 rPageStream.Seek(0);
8917 aZCodec.BeginCompression();
8918 aZCodec.Decompress(rPageStream, aMemoryStream);
8919 if (!aZCodec.EndCompression())
8921 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: decompression failed");
8922 continue;
8925 aStream.WriteBytes(aMemoryStream.GetData(), aMemoryStream.GetSize());
8927 else
8928 aStream.WriteBytes(rPageStream.GetData(), rPageStream.GetSize());
8931 compressStream(&aStream);
8932 sal_Int32 nLength = aStream.Tell();
8933 aLine.append(nLength);
8935 aLine.append(">>\nstream\n");
8936 // Copy the original page streams to the form XObject stream.
8937 aLine.append(static_cast<const sal_Char*>(aStream.GetData()), aStream.GetSize());
8938 aLine.append("\nendstream\nendobj\n\n");
8939 if (!updateObject(nWrappedFormObject))
8940 return;
8941 if (!writeBuffer(aLine.getStr(), aLine.getLength()))
8942 return;
8945 OStringBuffer aLine;
8946 if (!updateObject(rEmit.m_nFormObject))
8947 return;
8949 // Now have all the info to write the form XObject.
8950 aLine.append(rEmit.m_nFormObject);
8951 aLine.append(" 0 obj\n");
8952 aLine.append("<< /Type /XObject");
8953 aLine.append(" /Subtype /Form");
8954 aLine.append(" /Resources << /XObject<<");
8956 sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject;
8957 aLine.append(" /Im");
8958 aLine.append(nObject);
8959 aLine.append(" ");
8960 aLine.append(nObject);
8961 aLine.append(" 0 R");
8963 aLine.append(">> >>");
8964 aLine.append(" /Matrix [ ");
8965 appendDouble(fScaleX, aLine);
8966 aLine.append(" 0 0 ");
8967 appendDouble(fScaleY, aLine);
8968 aLine.append(" 0 0 ]");
8969 aLine.append(" /BBox [ 0 0 ");
8970 aLine.append(aSize.Width());
8971 aLine.append(" ");
8972 aLine.append(aSize.Height());
8973 aLine.append(" ]\n");
8975 if (m_aContext.UseReferenceXObject && rEmit.m_nEmbeddedObject > 0)
8977 // Write the reference dictionary.
8978 aLine.append("/Ref<< /F << /Type /Filespec /F (<embedded file>) /EF << /F ");
8979 aLine.append(rEmit.m_nEmbeddedObject);
8980 aLine.append(" 0 R >> >> /Page 0 >>\n");
8983 aLine.append("/Length ");
8985 OStringBuffer aStream;
8986 aStream.append("q ");
8987 if (m_aContext.UseReferenceXObject)
8989 // Reference XObject markup is used, just refer to the fallback bitmap
8990 // here.
8991 aStream.append(aSize.Width());
8992 aStream.append(" 0 0 ");
8993 aStream.append(aSize.Height());
8994 aStream.append(" 0 0 cm\n");
8995 aStream.append("/Im");
8996 aStream.append(rEmit.m_nBitmapObject);
8997 aStream.append(" Do\n");
8999 else
9001 // Reset line width to the default.
9002 aStream.append(" 1 w\n");
9004 // No reference XObject, draw the form XObject containing the original
9005 // page streams.
9006 aStream.append("/Im");
9007 aStream.append(nWrappedFormObject);
9008 aStream.append(" Do\n");
9010 aStream.append("Q");
9011 aLine.append(aStream.getLength());
9013 aLine.append(">>\nstream\n");
9014 aLine.append(aStream.getStr());
9015 aLine.append("\nendstream\nendobj\n\n");
9016 CHECK_RETURN2(writeBuffer(aLine.getStr(), aLine.getLength()));
9019 namespace
9021 unsigned char reverseByte(unsigned char b)
9023 b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
9024 b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
9025 b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
9026 return b;
9029 //tdf#103051 convert any N1BitLsbPal to N1BitMsbPal
9030 Bitmap getExportBitmap(const Bitmap &rBitmap)
9032 Bitmap::ScopedReadAccess pAccess(const_cast<Bitmap&>(rBitmap));
9033 const ScanlineFormat eFormat = pAccess->GetScanlineFormat();
9034 if (eFormat != ScanlineFormat::N1BitLsbPal)
9035 return rBitmap;
9036 Bitmap aNewBmp(rBitmap);
9037 BitmapScopedWriteAccess xWriteAcc(aNewBmp);
9038 const int nScanLineBytes = (pAccess->Width() + 7U) / 8U;
9039 for (long nY = 0L; nY < xWriteAcc->Height(); ++nY)
9041 Scanline pBitSwap = xWriteAcc->GetScanline(nY);
9042 for (int x = 0; x < nScanLineBytes; ++x)
9043 pBitSwap[x] = reverseByte(pBitSwap[x]);
9045 return aNewBmp;
9049 bool PDFWriterImpl::writeBitmapObject( BitmapEmit& rObject, bool bMask )
9051 if (!rObject.m_aReferenceXObject.m_aPDFData.empty() && !m_aContext.UseReferenceXObject)
9053 writeReferenceXObject(rObject.m_aReferenceXObject);
9054 return true;
9057 CHECK_RETURN( updateObject( rObject.m_nObject ) );
9059 Bitmap aBitmap;
9060 Color aTransparentColor( COL_TRANSPARENT );
9061 bool bWriteMask = false;
9062 if( ! bMask )
9064 aBitmap = getExportBitmap(rObject.m_aBitmap.GetBitmap());
9065 if( rObject.m_aBitmap.IsAlpha() )
9067 if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
9068 bWriteMask = true;
9069 // else draw without alpha channel
9071 else
9073 switch( rObject.m_aBitmap.GetTransparentType() )
9075 case TransparentType::NONE:
9076 break;
9077 case TransparentType::Color:
9078 aTransparentColor = rObject.m_aBitmap.GetTransparentColor();
9079 break;
9080 case TransparentType::Bitmap:
9081 bWriteMask = true;
9082 break;
9086 else
9088 if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 || ! rObject.m_aBitmap.IsAlpha() )
9090 aBitmap = getExportBitmap(rObject.m_aBitmap.GetMask());
9091 aBitmap.Convert( BmpConversion::N1BitThreshold );
9092 SAL_WARN_IF( aBitmap.GetBitCount() != 1, "vcl.pdfwriter", "mask conversion failed" );
9094 else if( aBitmap.GetBitCount() != 8 )
9096 aBitmap = getExportBitmap(rObject.m_aBitmap.GetAlpha().GetBitmap());
9097 aBitmap.Convert( BmpConversion::N8BitGreys );
9098 SAL_WARN_IF( aBitmap.GetBitCount() != 8, "vcl.pdfwriter", "alpha mask conversion failed" );
9102 Bitmap::ScopedReadAccess pAccess(aBitmap);
9104 bool bTrueColor;
9105 sal_Int32 nBitsPerComponent;
9106 switch( aBitmap.GetBitCount() )
9108 case 1:
9109 case 2:
9110 case 4:
9111 case 8:
9112 bTrueColor = false;
9113 nBitsPerComponent = aBitmap.GetBitCount();
9114 break;
9115 default:
9116 bTrueColor = true;
9117 nBitsPerComponent = 8;
9118 break;
9121 sal_Int32 nStreamLengthObject = createObject();
9122 sal_Int32 nMaskObject = 0;
9124 if (g_bDebugDisableCompression)
9126 emitComment( "PDFWriterImpl::writeBitmapObject" );
9128 OStringBuffer aLine(1024);
9129 aLine.append( rObject.m_nObject );
9130 aLine.append( " 0 obj\n"
9131 "<</Type/XObject/Subtype/Image/Width " );
9132 aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Width()) );
9133 aLine.append( "/Height " );
9134 aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Height()) );
9135 aLine.append( "/BitsPerComponent " );
9136 aLine.append( nBitsPerComponent );
9137 aLine.append( "/Length " );
9138 aLine.append( nStreamLengthObject );
9139 aLine.append( " 0 R\n" );
9140 if (!g_bDebugDisableCompression)
9142 if( nBitsPerComponent != 1 )
9144 aLine.append( "/Filter/FlateDecode" );
9146 else
9148 aLine.append( "/Filter/CCITTFaxDecode/DecodeParms<</K -1/BlackIs1 true/Columns " );
9149 aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Width()) );
9150 aLine.append( ">>\n" );
9153 if( ! bMask )
9155 aLine.append( "/ColorSpace" );
9156 if( bTrueColor )
9157 aLine.append( "/DeviceRGB\n" );
9158 else if( aBitmap.HasGreyPalette() )
9160 aLine.append( "/DeviceGray\n" );
9161 if( aBitmap.GetBitCount() == 1 )
9163 // #i47395# 1 bit bitmaps occasionally have an inverted grey palette
9164 sal_uInt16 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_BLACK ) );
9165 assert( nBlackIndex == 0 || nBlackIndex == 1);
9166 sal_uInt16 nWhiteIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_WHITE ) );
9167 if( pAccess->GetPalette()[nBlackIndex] == BitmapColor( COL_BLACK ) &&
9168 pAccess->GetPalette()[nWhiteIndex] == BitmapColor( COL_WHITE ) )
9170 // It is black and white
9171 if( nBlackIndex == 1 )
9172 aLine.append( "/Decode[1 0]\n" );
9174 else
9176 // It is two levels of grey
9177 aLine.append( "/Decode[" );
9178 assert( pAccess->GetPalette()[0].GetRed() == pAccess->GetPalette()[0].GetGreen() &&
9179 pAccess->GetPalette()[0].GetRed() == pAccess->GetPalette()[0].GetBlue() &&
9180 pAccess->GetPalette()[1].GetRed() == pAccess->GetPalette()[1].GetGreen() &&
9181 pAccess->GetPalette()[1].GetRed() == pAccess->GetPalette()[1].GetBlue() );
9182 aLine.append( pAccess->GetPalette()[0].GetRed() / 255.0 );
9183 aLine.append( " " );
9184 aLine.append( pAccess->GetPalette()[1].GetRed() / 255.0 );
9185 aLine.append( "]\n" );
9189 else
9191 aLine.append( "[ /Indexed/DeviceRGB " );
9192 aLine.append( static_cast<sal_Int32>(pAccess->GetPaletteEntryCount()-1) );
9193 aLine.append( "\n<" );
9194 if( m_aContext.Encryption.Encrypt() )
9196 enableStringEncryption( rObject.m_nObject );
9197 //check encryption buffer size
9198 m_vEncryptionBuffer.resize(pAccess->GetPaletteEntryCount()*3);
9199 int nChar = 0;
9200 //fill the encryption buffer
9201 for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
9203 const BitmapColor& rColor = pAccess->GetPaletteColor( i );
9204 m_vEncryptionBuffer[nChar++] = rColor.GetRed();
9205 m_vEncryptionBuffer[nChar++] = rColor.GetGreen();
9206 m_vEncryptionBuffer[nChar++] = rColor.GetBlue();
9208 //encrypt the colorspace lookup table
9209 rtl_cipher_encodeARCFOUR( m_aCipher, m_vEncryptionBuffer.data(), nChar, m_vEncryptionBuffer.data(), nChar );
9210 //now queue the data for output
9211 nChar = 0;
9212 for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
9214 appendHex(m_vEncryptionBuffer[nChar++], aLine );
9215 appendHex(m_vEncryptionBuffer[nChar++], aLine );
9216 appendHex(m_vEncryptionBuffer[nChar++], aLine );
9219 else //no encryption requested (PDF/A-1a program flow drops here)
9221 for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
9223 const BitmapColor& rColor = pAccess->GetPaletteColor( i );
9224 appendHex( rColor.GetRed(), aLine );
9225 appendHex( rColor.GetGreen(), aLine );
9226 appendHex( rColor.GetBlue(), aLine );
9229 aLine.append( ">\n]\n" );
9232 else
9234 if( aBitmap.GetBitCount() == 1 )
9236 aLine.append( "/ImageMask true\n" );
9237 sal_Int32 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_BLACK ) );
9238 SAL_WARN_IF( nBlackIndex != 0 && nBlackIndex != 1, "vcl.pdfwriter", "wrong black index" );
9239 if( nBlackIndex )
9240 aLine.append( "/Decode[ 1 0 ]\n" );
9241 else
9242 aLine.append( "/Decode[ 0 1 ]\n" );
9244 else if( aBitmap.GetBitCount() == 8 )
9246 aLine.append( "/ColorSpace/DeviceGray\n"
9247 "/Decode [ 1 0 ]\n" );
9251 if( ! bMask && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2 && !m_bIsPDF_A1 )
9253 if( bWriteMask )
9255 nMaskObject = createObject();
9256 if( rObject.m_aBitmap.IsAlpha() && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
9257 aLine.append( "/SMask " );
9258 else
9259 aLine.append( "/Mask " );
9260 aLine.append( nMaskObject );
9261 aLine.append( " 0 R\n" );
9263 else if( aTransparentColor != COL_TRANSPARENT )
9265 aLine.append( "/Mask[ " );
9266 if( bTrueColor )
9268 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetRed()) );
9269 aLine.append( ' ' );
9270 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetRed()) );
9271 aLine.append( ' ' );
9272 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetGreen()) );
9273 aLine.append( ' ' );
9274 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetGreen()) );
9275 aLine.append( ' ' );
9276 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetBlue()) );
9277 aLine.append( ' ' );
9278 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetBlue()) );
9280 else
9282 sal_Int32 nIndex = pAccess->GetBestPaletteIndex( BitmapColor( aTransparentColor ) );
9283 aLine.append( nIndex );
9285 aLine.append( " ]\n" );
9288 else if( m_bIsPDF_A1 && (bWriteMask || aTransparentColor != COL_TRANSPARENT) )
9289 m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
9291 aLine.append( ">>\n"
9292 "stream\n" );
9293 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
9294 sal_uInt64 nStartPos = 0;
9295 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nStartPos)) );
9297 checkAndEnableStreamEncryption( rObject.m_nObject );
9298 if (!g_bDebugDisableCompression && nBitsPerComponent == 1)
9300 writeG4Stream(pAccess.get());
9302 else
9304 beginCompression();
9305 if( ! bTrueColor || pAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb )
9307 //With PDF bitmaps, each row is padded to a BYTE boundary (multiple of 8 bits).
9308 const int nScanLineBytes = ((pAccess->GetBitCount() * pAccess->Width()) + 7U) / 8U;
9310 for( long i = 0; i < pAccess->Height(); i++ )
9312 CHECK_RETURN( writeBuffer( pAccess->GetScanline( i ), nScanLineBytes ) );
9315 else
9317 const int nScanLineBytes = pAccess->Width()*3;
9318 std::unique_ptr<sal_uInt8[]> xCol(new sal_uInt8[nScanLineBytes]);
9319 for( long y = 0; y < pAccess->Height(); y++ )
9321 for( long x = 0; x < pAccess->Width(); x++ )
9323 BitmapColor aColor = pAccess->GetColor( y, x );
9324 xCol[3*x+0] = aColor.GetRed();
9325 xCol[3*x+1] = aColor.GetGreen();
9326 xCol[3*x+2] = aColor.GetBlue();
9328 CHECK_RETURN(writeBuffer(xCol.get(), nScanLineBytes));
9331 endCompression();
9333 disableStreamEncryption();
9335 sal_uInt64 nEndPos = 0;
9336 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nEndPos)) );
9337 aLine.setLength( 0 );
9338 aLine.append( "\nendstream\nendobj\n\n" );
9339 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
9340 CHECK_RETURN( updateObject( nStreamLengthObject ) );
9341 aLine.setLength( 0 );
9342 aLine.append( nStreamLengthObject );
9343 aLine.append( " 0 obj\n" );
9344 aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
9345 aLine.append( "\nendobj\n\n" );
9346 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
9348 if( nMaskObject )
9350 BitmapEmit aEmit;
9351 aEmit.m_nObject = nMaskObject;
9352 aEmit.m_aBitmap = rObject.m_aBitmap;
9353 return writeBitmapObject( aEmit, true );
9356 writeReferenceXObject(rObject.m_aReferenceXObject);
9358 return true;
9361 void PDFWriterImpl::createEmbeddedFile(const Graphic& rGraphic, ReferenceXObjectEmit& rEmit, sal_Int32 nBitmapObject)
9363 // The bitmap object is always a valid identifier, even if the graphic has
9364 // no pdf data.
9365 rEmit.m_nBitmapObject = nBitmapObject;
9367 if (!rGraphic.hasPdfData())
9368 return;
9370 if (m_aContext.UseReferenceXObject)
9372 // Store the original PDF data as an embedded file.
9373 m_aEmbeddedFiles.emplace_back();
9374 m_aEmbeddedFiles.back().m_nObject = createObject();
9375 m_aEmbeddedFiles.back().m_pData = rGraphic.getPdfData();
9377 rEmit.m_nEmbeddedObject = m_aEmbeddedFiles.back().m_nObject;
9379 else
9380 rEmit.m_aPDFData = *rGraphic.getPdfData();
9382 rEmit.m_nFormObject = createObject();
9383 rEmit.m_aPixelSize = rGraphic.GetPrefSize();
9386 void PDFWriterImpl::drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const Size& rSizePixel, const tools::Rectangle& rTargetArea, const Bitmap& rMask, const Graphic& rGraphic )
9388 MARK( "drawJPGBitmap" );
9390 OStringBuffer aLine( 80 );
9391 updateGraphicsState();
9393 // #i40055# sanity check
9394 if( ! (rTargetArea.GetWidth() && rTargetArea.GetHeight() ) )
9395 return;
9396 if( ! (rSizePixel.Width() && rSizePixel.Height()) )
9397 return;
9399 rDCTData.Seek( 0 );
9400 if( bIsTrueColor && m_aContext.ColorMode == PDFWriter::DrawGreyscale )
9402 // need to convert to grayscale;
9403 // load stream to bitmap and draw the bitmap instead
9404 Graphic aGraphic;
9405 GraphicConverter::Import( rDCTData, aGraphic, ConvertDataFormat::JPG );
9406 if( !!rMask && rMask.GetSizePixel() == aGraphic.GetSizePixel() )
9408 Bitmap aBmp( aGraphic.GetBitmapEx().GetBitmap() );
9409 BitmapEx aBmpEx( aBmp, rMask );
9410 drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aBmpEx );
9412 else
9413 drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aGraphic.GetBitmapEx() );
9414 return;
9417 std::unique_ptr<SvMemoryStream> pStream(new SvMemoryStream);
9418 pStream->WriteStream( rDCTData );
9419 pStream->Seek( STREAM_SEEK_TO_END );
9421 BitmapID aID;
9422 aID.m_aPixelSize = rSizePixel;
9423 aID.m_nSize = pStream->Tell();
9424 pStream->Seek( STREAM_SEEK_TO_BEGIN );
9425 aID.m_nChecksum = vcl_get_checksum( 0, pStream->GetData(), aID.m_nSize );
9426 if( ! rMask.IsEmpty() )
9427 aID.m_nMaskChecksum = rMask.GetChecksum();
9429 std::vector< JPGEmit >::const_iterator it = std::find_if(m_aJPGs.begin(), m_aJPGs.end(),
9430 [&](const JPGEmit& arg) { return aID == arg.m_aID; });
9431 if( it == m_aJPGs.end() )
9433 m_aJPGs.emplace( m_aJPGs.begin() );
9434 JPGEmit& rEmit = m_aJPGs.front();
9435 if (!rGraphic.hasPdfData() || m_aContext.UseReferenceXObject)
9436 rEmit.m_nObject = createObject();
9437 rEmit.m_aID = aID;
9438 rEmit.m_pStream = std::move( pStream );
9439 rEmit.m_bTrueColor = bIsTrueColor;
9440 if( !! rMask && rMask.GetSizePixel() == rSizePixel )
9441 rEmit.m_aMask = rMask;
9442 createEmbeddedFile(rGraphic, rEmit.m_aReferenceXObject, rEmit.m_nObject);
9444 it = m_aJPGs.begin();
9447 aLine.append( "q " );
9448 sal_Int32 nCheckWidth = 0;
9449 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetWidth()), aLine, false, &nCheckWidth );
9450 aLine.append( " 0 0 " );
9451 sal_Int32 nCheckHeight = 0;
9452 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetHeight()), aLine, true, &nCheckHeight );
9453 aLine.append( ' ' );
9454 m_aPages.back().appendPoint( rTargetArea.BottomLeft(), aLine );
9455 aLine.append( " cm\n/Im" );
9456 sal_Int32 nObject = it->m_aReferenceXObject.getObject();
9457 aLine.append(nObject);
9458 aLine.append( " Do Q\n" );
9459 if( nCheckWidth == 0 || nCheckHeight == 0 )
9461 // #i97512# avoid invalid current matrix
9462 aLine.setLength( 0 );
9463 aLine.append( "\n%jpeg image /Im" );
9464 aLine.append( it->m_nObject );
9465 aLine.append( " scaled to zero size, omitted\n" );
9467 writeBuffer( aLine.getStr(), aLine.getLength() );
9469 OString aObjName = "Im" + OString::number(nObject);
9470 pushResource( ResXObject, aObjName, nObject );
9474 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEmit& rBitmap, const Color& rFillColor )
9476 OStringBuffer aLine( 80 );
9477 updateGraphicsState();
9479 aLine.append( "q " );
9480 if( rFillColor != COL_TRANSPARENT )
9482 appendNonStrokingColor( rFillColor, aLine );
9483 aLine.append( ' ' );
9485 sal_Int32 nCheckWidth = 0;
9486 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Width()), aLine, false, &nCheckWidth );
9487 aLine.append( " 0 0 " );
9488 sal_Int32 nCheckHeight = 0;
9489 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Height()), aLine, true, &nCheckHeight );
9490 aLine.append( ' ' );
9491 m_aPages.back().appendPoint( rDestPoint + Point( 0, rDestSize.Height()-1 ), aLine );
9492 aLine.append( " cm\n/Im" );
9493 sal_Int32 nObject = rBitmap.m_aReferenceXObject.getObject();
9494 aLine.append(nObject);
9495 aLine.append( " Do Q\n" );
9496 if( nCheckWidth == 0 || nCheckHeight == 0 )
9498 // #i97512# avoid invalid current matrix
9499 aLine.setLength( 0 );
9500 aLine.append( "\n%bitmap image /Im" );
9501 aLine.append( rBitmap.m_nObject );
9502 aLine.append( " scaled to zero size, omitted\n" );
9504 writeBuffer( aLine.getStr(), aLine.getLength() );
9507 const PDFWriterImpl::BitmapEmit& PDFWriterImpl::createBitmapEmit( const BitmapEx& i_rBitmap, const Graphic& rGraphic )
9509 BitmapEx aBitmap( i_rBitmap );
9510 if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
9512 BmpConversion eConv = BmpConversion::N8BitGreys;
9513 int nDepth = aBitmap.GetBitmap().GetBitCount();
9514 if( nDepth <= 4 )
9515 eConv = BmpConversion::N4BitGreys;
9516 if( nDepth > 1 )
9517 aBitmap.Convert( eConv );
9519 BitmapID aID;
9520 aID.m_aPixelSize = aBitmap.GetSizePixel();
9521 aID.m_nSize = aBitmap.GetBitCount();
9522 aID.m_nChecksum = aBitmap.GetBitmap().GetChecksum();
9523 aID.m_nMaskChecksum = 0;
9524 if( aBitmap.IsAlpha() )
9525 aID.m_nMaskChecksum = aBitmap.GetAlpha().GetChecksum();
9526 else
9528 Bitmap aMask = aBitmap.GetMask();
9529 if( ! aMask.IsEmpty() )
9530 aID.m_nMaskChecksum = aMask.GetChecksum();
9532 std::list< BitmapEmit >::const_iterator it = std::find_if(m_aBitmaps.begin(), m_aBitmaps.end(),
9533 [&](const BitmapEmit& arg) { return aID == arg.m_aID; });
9534 if( it == m_aBitmaps.end() )
9536 m_aBitmaps.push_front( BitmapEmit() );
9537 m_aBitmaps.front().m_aID = aID;
9538 m_aBitmaps.front().m_aBitmap = aBitmap;
9539 if (!rGraphic.hasPdfData() || m_aContext.UseReferenceXObject)
9540 m_aBitmaps.front().m_nObject = createObject();
9541 createEmbeddedFile(rGraphic, m_aBitmaps.front().m_aReferenceXObject, m_aBitmaps.front().m_nObject);
9542 it = m_aBitmaps.begin();
9545 sal_Int32 nObject = it->m_aReferenceXObject.getObject();
9546 OString aObjName = "Im" + OString::number(nObject);
9547 pushResource( ResXObject, aObjName, nObject );
9549 return *it;
9552 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap, const Graphic& rGraphic )
9554 MARK( "drawBitmap (Bitmap)" );
9556 // #i40055# sanity check
9557 if( ! (rDestSize.Width() && rDestSize.Height()) )
9558 return;
9560 const BitmapEmit& rEmit = createBitmapEmit( BitmapEx( rBitmap ), rGraphic );
9561 drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
9564 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEx& rBitmap )
9566 MARK( "drawBitmap (BitmapEx)" );
9568 // #i40055# sanity check
9569 if( ! (rDestSize.Width() && rDestSize.Height()) )
9570 return;
9572 const BitmapEmit& rEmit = createBitmapEmit( rBitmap, Graphic() );
9573 drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
9576 sal_Int32 PDFWriterImpl::createGradient( const Gradient& rGradient, const Size& rSize )
9578 Size aPtSize( lcl_convert( m_aGraphicsStack.front().m_aMapMode,
9579 MapMode( MapUnit::MapPoint ),
9580 this,
9581 rSize ) );
9582 // check if we already have this gradient
9583 // rounding to point will generally lose some pixels
9584 // round up to point boundary
9585 aPtSize.AdjustWidth( 1 );
9586 aPtSize.AdjustHeight( 1 );
9587 std::list< GradientEmit >::const_iterator it = std::find_if(m_aGradients.begin(), m_aGradients.end(),
9588 [&](const GradientEmit& arg) { return ((rGradient == arg.m_aGradient) && (aPtSize == arg.m_aSize) ); });
9590 if( it == m_aGradients.end() )
9592 m_aGradients.push_front( GradientEmit() );
9593 m_aGradients.front().m_aGradient = rGradient;
9594 m_aGradients.front().m_nObject = createObject();
9595 m_aGradients.front().m_aSize = aPtSize;
9596 it = m_aGradients.begin();
9599 OStringBuffer aObjName( 16 );
9600 aObjName.append( 'P' );
9601 aObjName.append( it->m_nObject );
9602 pushResource( ResShading, aObjName.makeStringAndClear(), it->m_nObject );
9604 return it->m_nObject;
9607 void PDFWriterImpl::drawGradient( const tools::Rectangle& rRect, const Gradient& rGradient )
9609 MARK( "drawGradient (Rectangle)" );
9611 if( m_aContext.Version == PDFWriter::PDFVersion::PDF_1_2 )
9613 drawRectangle( rRect );
9614 return;
9617 sal_Int32 nGradient = createGradient( rGradient, rRect.GetSize() );
9619 Point aTranslate( rRect.BottomLeft() );
9620 aTranslate += Point( 0, 1 );
9622 updateGraphicsState();
9624 OStringBuffer aLine( 80 );
9625 aLine.append( "q 1 0 0 1 " );
9626 m_aPages.back().appendPoint( aTranslate, aLine );
9627 aLine.append( " cm " );
9628 // if a stroke is appended reset the clip region before stroke
9629 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
9630 aLine.append( "q " );
9631 aLine.append( "0 0 " );
9632 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
9633 aLine.append( ' ' );
9634 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
9635 aLine.append( " re W n\n" );
9637 aLine.append( "/P" );
9638 aLine.append( nGradient );
9639 aLine.append( " sh " );
9640 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
9642 aLine.append( "Q 0 0 " );
9643 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
9644 aLine.append( ' ' );
9645 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
9646 aLine.append( " re S " );
9648 aLine.append( "Q\n" );
9649 writeBuffer( aLine.getStr(), aLine.getLength() );
9652 void PDFWriterImpl::drawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch )
9654 MARK( "drawHatch" );
9656 updateGraphicsState();
9658 if( rPolyPoly.Count() )
9660 tools::PolyPolygon aPolyPoly( rPolyPoly );
9662 aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME );
9663 push( PushFlags::LINECOLOR );
9664 setLineColor( rHatch.GetColor() );
9665 DrawHatch( aPolyPoly, rHatch, false );
9666 pop();
9670 void PDFWriterImpl::drawWallpaper( const tools::Rectangle& rRect, const Wallpaper& rWall )
9672 MARK( "drawWallpaper" );
9674 bool bDrawColor = false;
9675 bool bDrawGradient = false;
9676 bool bDrawBitmap = false;
9678 BitmapEx aBitmap;
9679 Point aBmpPos = rRect.TopLeft();
9680 Size aBmpSize;
9681 if( rWall.IsBitmap() )
9683 aBitmap = rWall.GetBitmap();
9684 aBmpSize = lcl_convert( aBitmap.GetPrefMapMode(),
9685 getMapMode(),
9686 this,
9687 aBitmap.GetPrefSize() );
9688 tools::Rectangle aRect( rRect );
9689 if( rWall.IsRect() )
9691 aRect = rWall.GetRect();
9692 aBmpPos = aRect.TopLeft();
9693 aBmpSize = aRect.GetSize();
9695 if( rWall.GetStyle() != WallpaperStyle::Scale )
9697 if( rWall.GetStyle() != WallpaperStyle::Tile )
9699 bDrawBitmap = true;
9700 if( rWall.IsGradient() )
9701 bDrawGradient = true;
9702 else
9703 bDrawColor = true;
9704 switch( rWall.GetStyle() )
9706 case WallpaperStyle::TopLeft:
9707 break;
9708 case WallpaperStyle::Top:
9709 aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
9710 break;
9711 case WallpaperStyle::Left:
9712 aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
9713 break;
9714 case WallpaperStyle::TopRight:
9715 aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
9716 break;
9717 case WallpaperStyle::Center:
9718 aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
9719 aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
9720 break;
9721 case WallpaperStyle::Right:
9722 aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
9723 aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
9724 break;
9725 case WallpaperStyle::BottomLeft:
9726 aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
9727 break;
9728 case WallpaperStyle::Bottom:
9729 aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
9730 aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
9731 break;
9732 case WallpaperStyle::BottomRight:
9733 aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
9734 aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
9735 break;
9736 default: ;
9739 else
9741 // push the bitmap
9742 const BitmapEmit& rEmit = createBitmapEmit( aBitmap, Graphic() );
9744 // convert to page coordinates; this needs to be done here
9745 // since the emit does not know the page anymore
9746 tools::Rectangle aConvertRect( aBmpPos, aBmpSize );
9747 m_aPages.back().convertRect( aConvertRect );
9749 OString aImageName = "Im" + OString::number( rEmit.m_nObject );
9751 // push the pattern
9752 OStringBuffer aTilingStream( 32 );
9753 appendFixedInt( aConvertRect.GetWidth(), aTilingStream );
9754 aTilingStream.append( " 0 0 " );
9755 appendFixedInt( aConvertRect.GetHeight(), aTilingStream );
9756 aTilingStream.append( " 0 0 cm\n/" );
9757 aTilingStream.append( aImageName );
9758 aTilingStream.append( " Do\n" );
9760 m_aTilings.emplace_back( );
9761 m_aTilings.back().m_nObject = createObject();
9762 m_aTilings.back().m_aRectangle = tools::Rectangle( Point( 0, 0 ), aConvertRect.GetSize() );
9763 m_aTilings.back().m_pTilingStream.reset(new SvMemoryStream());
9764 m_aTilings.back().m_pTilingStream->WriteBytes(
9765 aTilingStream.getStr(), aTilingStream.getLength() );
9766 // phase the tiling so wallpaper begins on upper left
9767 if ((aConvertRect.GetWidth() == 0) || (aConvertRect.GetHeight() == 0))
9768 throw o3tl::divide_by_zero();
9769 m_aTilings.back().m_aTransform.matrix[2] = double(aConvertRect.Left() % aConvertRect.GetWidth()) / fDivisor;
9770 m_aTilings.back().m_aTransform.matrix[5] = double(aConvertRect.Top() % aConvertRect.GetHeight()) / fDivisor;
9771 m_aTilings.back().m_aResources.m_aXObjects[aImageName] = rEmit.m_nObject;
9773 updateGraphicsState();
9775 OStringBuffer aObjName( 16 );
9776 aObjName.append( 'P' );
9777 aObjName.append( m_aTilings.back().m_nObject );
9778 OString aPatternName( aObjName.makeStringAndClear() );
9779 pushResource( ResPattern, aPatternName, m_aTilings.back().m_nObject );
9781 // fill a rRect with the pattern
9782 OStringBuffer aLine( 100 );
9783 aLine.append( "q /Pattern cs /" );
9784 aLine.append( aPatternName );
9785 aLine.append( " scn " );
9786 m_aPages.back().appendRect( rRect, aLine );
9787 aLine.append( " f Q\n" );
9788 writeBuffer( aLine.getStr(), aLine.getLength() );
9791 else
9793 aBmpPos = aRect.TopLeft();
9794 aBmpSize = aRect.GetSize();
9795 bDrawBitmap = true;
9798 if( aBitmap.IsTransparent() )
9800 if( rWall.IsGradient() )
9801 bDrawGradient = true;
9802 else
9803 bDrawColor = true;
9806 else if( rWall.IsGradient() )
9807 bDrawGradient = true;
9808 else
9809 bDrawColor = true;
9811 if( bDrawGradient )
9813 drawGradient( rRect, rWall.GetGradient() );
9815 if( bDrawColor )
9817 Color aOldLineColor = m_aGraphicsStack.front().m_aLineColor;
9818 Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
9819 setLineColor( COL_TRANSPARENT );
9820 setFillColor( rWall.GetColor() );
9821 drawRectangle( rRect );
9822 setLineColor( aOldLineColor );
9823 setFillColor( aOldFillColor );
9825 if( bDrawBitmap )
9827 // set temporary clip region since aBmpPos and aBmpSize
9828 // may be outside rRect
9829 OStringBuffer aLine( 20 );
9830 aLine.append( "q " );
9831 m_aPages.back().appendRect( rRect, aLine );
9832 aLine.append( " W n\n" );
9833 writeBuffer( aLine.getStr(), aLine.getLength() );
9834 drawBitmap( aBmpPos, aBmpSize, aBitmap );
9835 writeBuffer( "Q\n", 2 );
9839 void PDFWriterImpl::updateGraphicsState(Mode const mode)
9841 OStringBuffer aLine( 256 );
9842 GraphicsState& rNewState = m_aGraphicsStack.front();
9843 // first set clip region since it might invalidate everything else
9845 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::ClipRegion )
9847 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::ClipRegion;
9849 if( m_aCurrentPDFState.m_bClipRegion != rNewState.m_bClipRegion ||
9850 ( rNewState.m_bClipRegion && m_aCurrentPDFState.m_aClipRegion != rNewState.m_aClipRegion ) )
9852 if( m_aCurrentPDFState.m_bClipRegion )
9854 aLine.append( "Q " );
9855 // invalidate everything but the clip region
9856 m_aCurrentPDFState = GraphicsState();
9857 rNewState.m_nUpdateFlags = ~GraphicsStateUpdateFlags::ClipRegion;
9859 if( rNewState.m_bClipRegion )
9861 // clip region is always stored in private PDF mapmode
9862 MapMode aNewMapMode = rNewState.m_aMapMode;
9863 rNewState.m_aMapMode = m_aMapMode;
9864 SetMapMode( rNewState.m_aMapMode );
9865 m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
9867 aLine.append("q ");
9868 if ( rNewState.m_aClipRegion.count() )
9870 m_aPages.back().appendPolyPolygon( rNewState.m_aClipRegion, aLine );
9871 aLine.append( "W* n\n" );
9874 rNewState.m_aMapMode = aNewMapMode;
9875 SetMapMode( rNewState.m_aMapMode );
9876 m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
9881 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::MapMode )
9883 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::MapMode;
9884 SetMapMode( rNewState.m_aMapMode );
9887 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::Font )
9889 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::Font;
9890 SetFont( rNewState.m_aFont );
9893 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LayoutMode )
9895 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LayoutMode;
9896 SetLayoutMode( rNewState.m_nLayoutMode );
9899 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::DigitLanguage )
9901 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::DigitLanguage;
9902 SetDigitLanguage( rNewState.m_aDigitLanguage );
9905 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LineColor )
9907 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LineColor;
9908 if( m_aCurrentPDFState.m_aLineColor != rNewState.m_aLineColor &&
9909 rNewState.m_aLineColor != COL_TRANSPARENT )
9911 appendStrokingColor( rNewState.m_aLineColor, aLine );
9912 aLine.append( "\n" );
9916 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::FillColor )
9918 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::FillColor;
9919 if( m_aCurrentPDFState.m_aFillColor != rNewState.m_aFillColor &&
9920 rNewState.m_aFillColor != COL_TRANSPARENT )
9922 appendNonStrokingColor( rNewState.m_aFillColor, aLine );
9923 aLine.append( "\n" );
9927 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::TransparentPercent )
9929 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::TransparentPercent;
9930 if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
9932 // TODO: switch extended graphicsstate
9936 // everything is up to date now
9937 m_aCurrentPDFState = m_aGraphicsStack.front();
9938 if ((mode != NOWRITE) && !aLine.isEmpty())
9939 writeBuffer( aLine.getStr(), aLine.getLength() );
9942 /* #i47544# imitate OutputDevice behaviour:
9943 * if a font with a nontransparent color is set, it overwrites the current
9944 * text color. OTOH setting the text color will overwrite the color of the font.
9946 void PDFWriterImpl::setFont( const vcl::Font& rFont )
9948 Color aColor = rFont.GetColor();
9949 if( aColor == COL_TRANSPARENT )
9950 aColor = m_aGraphicsStack.front().m_aFont.GetColor();
9951 m_aGraphicsStack.front().m_aFont = rFont;
9952 m_aGraphicsStack.front().m_aFont.SetColor( aColor );
9953 m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font;
9956 void PDFWriterImpl::push( PushFlags nFlags )
9958 OSL_ENSURE( !m_aGraphicsStack.empty(), "invalid graphics stack" );
9959 m_aGraphicsStack.push_front( m_aGraphicsStack.front() );
9960 m_aGraphicsStack.front().m_nFlags = nFlags;
9963 void PDFWriterImpl::pop()
9965 OSL_ENSURE( m_aGraphicsStack.size() > 1, "pop without push" );
9966 if( m_aGraphicsStack.size() < 2 )
9967 return;
9969 GraphicsState aState = m_aGraphicsStack.front();
9970 m_aGraphicsStack.pop_front();
9971 GraphicsState& rOld = m_aGraphicsStack.front();
9973 // move those parameters back that were not pushed
9974 // in the first place
9975 if( ! (aState.m_nFlags & PushFlags::LINECOLOR) )
9976 setLineColor( aState.m_aLineColor );
9977 if( ! (aState.m_nFlags & PushFlags::FILLCOLOR) )
9978 setFillColor( aState.m_aFillColor );
9979 if( ! (aState.m_nFlags & PushFlags::FONT) )
9980 setFont( aState.m_aFont );
9981 if( ! (aState.m_nFlags & PushFlags::TEXTCOLOR) )
9982 setTextColor( aState.m_aFont.GetColor() );
9983 if( ! (aState.m_nFlags & PushFlags::MAPMODE) )
9984 setMapMode( aState.m_aMapMode );
9985 if( ! (aState.m_nFlags & PushFlags::CLIPREGION) )
9987 // do not use setClipRegion here
9988 // it would convert again assuming the current mapmode
9989 rOld.m_aClipRegion = aState.m_aClipRegion;
9990 rOld.m_bClipRegion = aState.m_bClipRegion;
9992 if( ! (aState.m_nFlags & PushFlags::TEXTLINECOLOR ) )
9993 setTextLineColor( aState.m_aTextLineColor );
9994 if( ! (aState.m_nFlags & PushFlags::OVERLINECOLOR ) )
9995 setOverlineColor( aState.m_aOverlineColor );
9996 if( ! (aState.m_nFlags & PushFlags::TEXTALIGN ) )
9997 setTextAlign( aState.m_aFont.GetAlignment() );
9998 if( ! (aState.m_nFlags & PushFlags::TEXTFILLCOLOR) )
9999 setTextFillColor( aState.m_aFont.GetFillColor() );
10000 if( ! (aState.m_nFlags & PushFlags::REFPOINT) )
10002 // what ?
10004 // invalidate graphics state
10005 m_aGraphicsStack.front().m_nUpdateFlags = GraphicsStateUpdateFlags::All;
10008 void PDFWriterImpl::setMapMode( const MapMode& rMapMode )
10010 m_aGraphicsStack.front().m_aMapMode = rMapMode;
10011 SetMapMode( rMapMode );
10012 m_aCurrentPDFState.m_aMapMode = rMapMode;
10015 void PDFWriterImpl::setClipRegion( const basegfx::B2DPolyPolygon& rRegion )
10017 basegfx::B2DPolyPolygon aRegion = LogicToPixel( rRegion, m_aGraphicsStack.front().m_aMapMode );
10018 aRegion = PixelToLogic( aRegion, m_aMapMode );
10019 m_aGraphicsStack.front().m_aClipRegion = aRegion;
10020 m_aGraphicsStack.front().m_bClipRegion = true;
10021 m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10024 void PDFWriterImpl::moveClipRegion( sal_Int32 nX, sal_Int32 nY )
10026 if( m_aGraphicsStack.front().m_bClipRegion && m_aGraphicsStack.front().m_aClipRegion.count() )
10028 Point aPoint( lcl_convert( m_aGraphicsStack.front().m_aMapMode,
10029 m_aMapMode,
10030 this,
10031 Point( nX, nY ) ) );
10032 aPoint -= lcl_convert( m_aGraphicsStack.front().m_aMapMode,
10033 m_aMapMode,
10034 this,
10035 Point() );
10036 basegfx::B2DHomMatrix aMat;
10037 aMat.translate( aPoint.X(), aPoint.Y() );
10038 m_aGraphicsStack.front().m_aClipRegion.transform( aMat );
10039 m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10043 void PDFWriterImpl::intersectClipRegion( const tools::Rectangle& rRect )
10045 basegfx::B2DPolyPolygon aRect( basegfx::utils::createPolygonFromRect(
10046 vcl::unotools::b2DRectangleFromRectangle(rRect) ) );
10047 intersectClipRegion( aRect );
10050 void PDFWriterImpl::intersectClipRegion( const basegfx::B2DPolyPolygon& rRegion )
10052 basegfx::B2DPolyPolygon aRegion( LogicToPixel( rRegion, m_aGraphicsStack.front().m_aMapMode ) );
10053 aRegion = PixelToLogic( aRegion, m_aMapMode );
10054 m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10055 if( m_aGraphicsStack.front().m_bClipRegion )
10057 basegfx::B2DPolyPolygon aOld( basegfx::utils::prepareForPolygonOperation( m_aGraphicsStack.front().m_aClipRegion ) );
10058 aRegion = basegfx::utils::prepareForPolygonOperation( aRegion );
10059 m_aGraphicsStack.front().m_aClipRegion = basegfx::utils::solvePolygonOperationAnd( aOld, aRegion );
10061 else
10063 m_aGraphicsStack.front().m_aClipRegion = aRegion;
10064 m_aGraphicsStack.front().m_bClipRegion = true;
10068 void PDFWriterImpl::createNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr )
10070 if( nPageNr < 0 )
10071 nPageNr = m_nCurrentPage;
10073 if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
10074 return;
10076 m_aNotes.emplace_back( );
10077 m_aNotes.back().m_nObject = createObject();
10078 m_aNotes.back().m_aContents = rNote;
10079 m_aNotes.back().m_aRect = rRect;
10080 // convert to default user space now, since the mapmode may change
10081 m_aPages[nPageNr].convertRect( m_aNotes.back().m_aRect );
10083 // insert note to page's annotation list
10084 m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aNotes.back().m_nObject );
10087 sal_Int32 PDFWriterImpl::createLink( const tools::Rectangle& rRect, sal_Int32 nPageNr )
10089 if( nPageNr < 0 )
10090 nPageNr = m_nCurrentPage;
10092 if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
10093 return -1;
10095 sal_Int32 nRet = m_aLinks.size();
10097 m_aLinks.emplace_back( );
10098 m_aLinks.back().m_nObject = createObject();
10099 m_aLinks.back().m_nPage = nPageNr;
10100 m_aLinks.back().m_aRect = rRect;
10101 // convert to default user space now, since the mapmode may change
10102 m_aPages[nPageNr].convertRect( m_aLinks.back().m_aRect );
10104 // insert link to page's annotation list
10105 m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aLinks.back().m_nObject );
10107 return nRet;
10110 sal_Int32 PDFWriterImpl::createScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr)
10112 if (nPageNr < 0)
10113 nPageNr = m_nCurrentPage;
10115 if (nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()))
10116 return -1;
10118 sal_Int32 nRet = m_aScreens.size();
10120 m_aScreens.emplace_back();
10121 m_aScreens.back().m_nObject = createObject();
10122 m_aScreens.back().m_nPage = nPageNr;
10123 m_aScreens.back().m_aRect = rRect;
10124 // Convert to default user space now, since the mapmode may change.
10125 m_aPages[nPageNr].convertRect(m_aScreens.back().m_aRect);
10127 // Insert link to page's annotation list.
10128 m_aPages[nPageNr].m_aAnnotations.push_back(m_aScreens.back().m_nObject);
10130 return nRet;
10133 sal_Int32 PDFWriterImpl::createNamedDest( const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10135 if( nPageNr < 0 )
10136 nPageNr = m_nCurrentPage;
10138 if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
10139 return -1;
10141 sal_Int32 nRet = m_aNamedDests.size();
10143 m_aNamedDests.emplace_back( );
10144 m_aNamedDests.back().m_aDestName = sDestName;
10145 m_aNamedDests.back().m_nPage = nPageNr;
10146 m_aNamedDests.back().m_eType = eType;
10147 m_aNamedDests.back().m_aRect = rRect;
10148 // convert to default user space now, since the mapmode may change
10149 m_aPages[nPageNr].convertRect( m_aNamedDests.back().m_aRect );
10151 return nRet;
10154 sal_Int32 PDFWriterImpl::createDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10156 if( nPageNr < 0 )
10157 nPageNr = m_nCurrentPage;
10159 if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
10160 return -1;
10162 sal_Int32 nRet = m_aDests.size();
10164 m_aDests.emplace_back( );
10165 m_aDests.back().m_nPage = nPageNr;
10166 m_aDests.back().m_eType = eType;
10167 m_aDests.back().m_aRect = rRect;
10168 // convert to default user space now, since the mapmode may change
10169 m_aPages[nPageNr].convertRect( m_aDests.back().m_aRect );
10171 return nRet;
10174 sal_Int32 PDFWriterImpl::registerDestReference( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10176 return m_aDestinationIdTranslation[ nDestId ] = createDest( rRect, nPageNr, eType );
10179 void PDFWriterImpl::setLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
10181 if( nLinkId < 0 || nLinkId >= static_cast<sal_Int32>(m_aLinks.size()) )
10182 return;
10183 if( nDestId < 0 || nDestId >= static_cast<sal_Int32>(m_aDests.size()) )
10184 return;
10186 m_aLinks[ nLinkId ].m_nDest = nDestId;
10189 void PDFWriterImpl::setLinkURL( sal_Int32 nLinkId, const OUString& rURL )
10191 if( nLinkId < 0 || nLinkId >= static_cast<sal_Int32>(m_aLinks.size()) )
10192 return;
10194 m_aLinks[ nLinkId ].m_nDest = -1;
10196 using namespace ::com::sun::star;
10198 if (!m_xTrans.is())
10200 uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
10201 m_xTrans = util::URLTransformer::create(xContext);
10204 util::URL aURL;
10205 aURL.Complete = rURL;
10207 m_xTrans->parseStrict( aURL );
10209 m_aLinks[ nLinkId ].m_aURL = aURL.Complete;
10212 void PDFWriterImpl::setScreenURL(sal_Int32 nScreenId, const OUString& rURL)
10214 if (nScreenId < 0 || nScreenId >= static_cast<sal_Int32>(m_aScreens.size()))
10215 return;
10217 m_aScreens[nScreenId].m_aURL = rURL;
10220 void PDFWriterImpl::setScreenStream(sal_Int32 nScreenId, const OUString& rURL)
10222 if (nScreenId < 0 || nScreenId >= static_cast<sal_Int32>(m_aScreens.size()))
10223 return;
10225 m_aScreens[nScreenId].m_aTempFileURL = rURL;
10226 m_aScreens[nScreenId].m_nTempFileObject = createObject();
10229 void PDFWriterImpl::setLinkPropertyId( sal_Int32 nLinkId, sal_Int32 nPropertyId )
10231 m_aLinkPropertyMap[ nPropertyId ] = nLinkId;
10234 sal_Int32 PDFWriterImpl::createOutlineItem( sal_Int32 nParent, const OUString& rText, sal_Int32 nDestID )
10236 // create new item
10237 sal_Int32 nNewItem = m_aOutline.size();
10238 m_aOutline.emplace_back( );
10240 // set item attributes
10241 setOutlineItemParent( nNewItem, nParent );
10242 setOutlineItemText( nNewItem, rText );
10243 setOutlineItemDest( nNewItem, nDestID );
10245 return nNewItem;
10248 void PDFWriterImpl::setOutlineItemParent( sal_Int32 nItem, sal_Int32 nNewParent )
10250 if( nItem < 1 || nItem >= static_cast<sal_Int32>(m_aOutline.size()) )
10251 return;
10253 if( nNewParent < 0 || nNewParent >= static_cast<sal_Int32>(m_aOutline.size()) || nNewParent == nItem )
10255 nNewParent = 0;
10257 // insert item to new parent's list of children
10258 m_aOutline[ nNewParent ].m_aChildren.push_back( nItem );
10261 void PDFWriterImpl::setOutlineItemText( sal_Int32 nItem, const OUString& rText )
10263 if( nItem < 1 || nItem >= static_cast<sal_Int32>(m_aOutline.size()) )
10264 return;
10266 m_aOutline[ nItem ].m_aTitle = psp::WhitespaceToSpace( rText );
10269 void PDFWriterImpl::setOutlineItemDest( sal_Int32 nItem, sal_Int32 nDestID )
10271 if( nItem < 1 || nItem >= static_cast<sal_Int32>(m_aOutline.size()) ) // item does not exist
10272 return;
10273 if( nDestID < 0 || nDestID >= static_cast<sal_Int32>(m_aDests.size()) ) // dest does not exist
10274 return;
10275 m_aOutline[nItem].m_nDestID = nDestID;
10278 const sal_Char* PDFWriterImpl::getStructureTag( PDFWriter::StructElement eType )
10280 static std::map< PDFWriter::StructElement, const char* > aTagStrings;
10281 if( aTagStrings.empty() )
10283 aTagStrings[ PDFWriter::NonStructElement] = "NonStruct";
10284 aTagStrings[ PDFWriter::Document ] = "Document";
10285 aTagStrings[ PDFWriter::Part ] = "Part";
10286 aTagStrings[ PDFWriter::Article ] = "Art";
10287 aTagStrings[ PDFWriter::Section ] = "Sect";
10288 aTagStrings[ PDFWriter::Division ] = "Div";
10289 aTagStrings[ PDFWriter::BlockQuote ] = "BlockQuote";
10290 aTagStrings[ PDFWriter::Caption ] = "Caption";
10291 aTagStrings[ PDFWriter::TOC ] = "TOC";
10292 aTagStrings[ PDFWriter::TOCI ] = "TOCI";
10293 aTagStrings[ PDFWriter::Index ] = "Index";
10294 aTagStrings[ PDFWriter::Paragraph ] = "P";
10295 aTagStrings[ PDFWriter::Heading ] = "H";
10296 aTagStrings[ PDFWriter::H1 ] = "H1";
10297 aTagStrings[ PDFWriter::H2 ] = "H2";
10298 aTagStrings[ PDFWriter::H3 ] = "H3";
10299 aTagStrings[ PDFWriter::H4 ] = "H4";
10300 aTagStrings[ PDFWriter::H5 ] = "H5";
10301 aTagStrings[ PDFWriter::H6 ] = "H6";
10302 aTagStrings[ PDFWriter::List ] = "L";
10303 aTagStrings[ PDFWriter::ListItem ] = "LI";
10304 aTagStrings[ PDFWriter::LILabel ] = "Lbl";
10305 aTagStrings[ PDFWriter::LIBody ] = "LBody";
10306 aTagStrings[ PDFWriter::Table ] = "Table";
10307 aTagStrings[ PDFWriter::TableRow ] = "TR";
10308 aTagStrings[ PDFWriter::TableHeader ] = "TH";
10309 aTagStrings[ PDFWriter::TableData ] = "TD";
10310 aTagStrings[ PDFWriter::Span ] = "Span";
10311 aTagStrings[ PDFWriter::Quote ] = "Quote";
10312 aTagStrings[ PDFWriter::Note ] = "Note";
10313 aTagStrings[ PDFWriter::Reference ] = "Reference";
10314 aTagStrings[ PDFWriter::BibEntry ] = "BibEntry";
10315 aTagStrings[ PDFWriter::Code ] = "Code";
10316 aTagStrings[ PDFWriter::Link ] = "Link";
10317 aTagStrings[ PDFWriter::Figure ] = "Figure";
10318 aTagStrings[ PDFWriter::Formula ] = "Formula";
10319 aTagStrings[ PDFWriter::Form ] = "Form";
10322 std::map< PDFWriter::StructElement, const char* >::const_iterator it = aTagStrings.find( eType );
10324 return it != aTagStrings.end() ? it->second : "Div";
10327 void PDFWriterImpl::beginStructureElementMCSeq()
10329 if( m_bEmitStructure &&
10330 m_nCurrentStructElement > 0 && // StructTreeRoot
10331 ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
10334 PDFStructureElement& rEle = m_aStructure[ m_nCurrentStructElement ];
10335 OStringBuffer aLine( 128 );
10336 sal_Int32 nMCID = m_aPages[ m_nCurrentPage ].m_aMCIDParents.size();
10337 aLine.append( "/" );
10338 if( !rEle.m_aAlias.isEmpty() )
10339 aLine.append( rEle.m_aAlias );
10340 else
10341 aLine.append( getStructureTag( rEle.m_eType ) );
10342 aLine.append( "<</MCID " );
10343 aLine.append( nMCID );
10344 aLine.append( ">>BDC\n" );
10345 writeBuffer( aLine.getStr(), aLine.getLength() );
10347 // update the element's content list
10348 SAL_INFO("vcl.pdfwriter", "beginning marked content id " << nMCID << " on page object "
10349 << m_aPages[ m_nCurrentPage ].m_nPageObject << ", structure first page = "
10350 << rEle.m_nFirstPageObject);
10351 rEle.m_aKids.emplace_back( nMCID, m_aPages[m_nCurrentPage].m_nPageObject );
10352 // update the page's mcid parent list
10353 m_aPages[ m_nCurrentPage ].m_aMCIDParents.push_back( rEle.m_nObject );
10354 // mark element MC sequence as open
10355 rEle.m_bOpenMCSeq = true;
10357 // handle artifacts
10358 else if( ! m_bEmitStructure && m_aContext.Tagged &&
10359 m_nCurrentStructElement > 0 &&
10360 m_aStructure[ m_nCurrentStructElement ].m_eType == PDFWriter::NonStructElement &&
10361 ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
10364 OString aLine = "/Artifact BMC\n";
10365 writeBuffer( aLine.getStr(), aLine.getLength() );
10366 // mark element MC sequence as open
10367 m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = true;
10371 void PDFWriterImpl::endStructureElementMCSeq()
10373 if( m_nCurrentStructElement > 0 && // StructTreeRoot
10374 ( m_bEmitStructure || m_aStructure[ m_nCurrentStructElement ].m_eType == PDFWriter::NonStructElement ) &&
10375 m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // must have an opened MC sequence
10378 writeBuffer( "EMC\n", 4 );
10379 m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = false;
10383 bool PDFWriterImpl::checkEmitStructure()
10385 bool bEmit = false;
10386 if( m_aContext.Tagged )
10388 bEmit = true;
10389 sal_Int32 nEle = m_nCurrentStructElement;
10390 while( nEle > 0 && nEle < sal_Int32(m_aStructure.size()) )
10392 if( m_aStructure[ nEle ].m_eType == PDFWriter::NonStructElement )
10394 bEmit = false;
10395 break;
10397 nEle = m_aStructure[ nEle ].m_nParentElement;
10400 return bEmit;
10403 sal_Int32 PDFWriterImpl::beginStructureElement( PDFWriter::StructElement eType, const OUString& rAlias )
10405 if( m_nCurrentPage < 0 )
10406 return -1;
10408 if( ! m_aContext.Tagged )
10409 return -1;
10411 // close eventual current MC sequence
10412 endStructureElementMCSeq();
10414 if( m_nCurrentStructElement == 0 &&
10415 eType != PDFWriter::Document && eType != PDFWriter::NonStructElement )
10417 // struct tree root hit, but not beginning document
10418 // this might happen with setCurrentStructureElement
10419 // silently insert structure into document again if one properly exists
10420 if( ! m_aStructure[ 0 ].m_aChildren.empty() )
10422 const std::list< sal_Int32 >& rRootChildren = m_aStructure[0].m_aChildren;
10423 auto it = std::find_if(rRootChildren.begin(), rRootChildren.end(),
10424 [&](sal_Int32 nElement) { return m_aStructure[ nElement ].m_eType == PDFWriter::Document; });
10425 if( it != rRootChildren.end() )
10427 m_nCurrentStructElement = *it;
10428 SAL_WARN( "vcl.pdfwriter", "Structure element inserted to StructTreeRoot that is not a document" );
10430 else {
10431 OSL_FAIL( "document structure in disorder !" );
10434 else {
10435 OSL_FAIL( "PDF document structure MUST be contained in a Document element" );
10439 sal_Int32 nNewId = sal_Int32(m_aStructure.size());
10440 m_aStructure.emplace_back( );
10441 PDFStructureElement& rEle = m_aStructure.back();
10442 rEle.m_eType = eType;
10443 rEle.m_nOwnElement = nNewId;
10444 rEle.m_nParentElement = m_nCurrentStructElement;
10445 rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject;
10446 m_aStructure[ m_nCurrentStructElement ].m_aChildren.push_back( nNewId );
10447 m_nCurrentStructElement = nNewId;
10449 // handle alias names
10450 if( !rAlias.isEmpty() && eType != PDFWriter::NonStructElement )
10452 OStringBuffer aNameBuf( rAlias.getLength() );
10453 appendName( rAlias, aNameBuf );
10454 OString aAliasName( aNameBuf.makeStringAndClear() );
10455 rEle.m_aAlias = aAliasName;
10456 m_aRoleMap[ aAliasName ] = getStructureTag( eType );
10459 if (g_bDebugDisableCompression)
10461 OStringBuffer aLine( "beginStructureElement " );
10462 aLine.append( m_nCurrentStructElement );
10463 aLine.append( ": " );
10464 aLine.append( getStructureTag( eType ) );
10465 if( !rEle.m_aAlias.isEmpty() )
10467 aLine.append( " aliased as \"" );
10468 aLine.append( rEle.m_aAlias );
10469 aLine.append( '\"' );
10471 emitComment( aLine.getStr() );
10474 // check whether to emit structure henceforth
10475 m_bEmitStructure = checkEmitStructure();
10477 if( m_bEmitStructure ) // don't create nonexistent objects
10479 rEle.m_nObject = createObject();
10480 // update parent's kids list
10481 m_aStructure[ rEle.m_nParentElement ].m_aKids.emplace_back(rEle.m_nObject);
10483 return nNewId;
10486 void PDFWriterImpl::endStructureElement()
10488 if( m_nCurrentPage < 0 )
10489 return;
10491 if( ! m_aContext.Tagged )
10492 return;
10494 if( m_nCurrentStructElement == 0 )
10496 // hit the struct tree root, that means there is an endStructureElement
10497 // without corresponding beginStructureElement
10498 return;
10501 // end the marked content sequence
10502 endStructureElementMCSeq();
10504 OStringBuffer aLine;
10505 if (g_bDebugDisableCompression)
10507 aLine.append( "endStructureElement " );
10508 aLine.append( m_nCurrentStructElement );
10509 aLine.append( ": " );
10510 aLine.append( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) );
10511 if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
10513 aLine.append( " aliased as \"" );
10514 aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
10515 aLine.append( '\"' );
10519 // "end" the structure element, the parent becomes current element
10520 m_nCurrentStructElement = m_aStructure[ m_nCurrentStructElement ].m_nParentElement;
10522 // check whether to emit structure henceforth
10523 m_bEmitStructure = checkEmitStructure();
10525 if (g_bDebugDisableCompression && m_bEmitStructure)
10527 emitComment( aLine.getStr() );
10532 * This function adds an internal structure list container to overcome the 8191 elements array limitation
10533 * in kids element emission.
10534 * Recursive function
10537 void PDFWriterImpl::addInternalStructureContainer( PDFStructureElement& rEle )
10539 if( rEle.m_eType == PDFWriter::NonStructElement &&
10540 rEle.m_nOwnElement != rEle.m_nParentElement )
10541 return;
10543 for (auto const& child : rEle.m_aChildren)
10545 if( child > 0 && child < sal_Int32(m_aStructure.size()) )
10547 PDFStructureElement& rChild = m_aStructure[ child ];
10548 if( rChild.m_eType != PDFWriter::NonStructElement )
10550 //triggered when a child of the rEle element is found
10551 if( rChild.m_nParentElement == rEle.m_nOwnElement )
10552 addInternalStructureContainer( rChild );//examine the child
10553 else
10555 OSL_FAIL( "PDFWriterImpl::addInternalStructureContainer: invalid child structure element" );
10556 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure element with id " << child );
10560 else
10562 OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
10563 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure id " << child );
10567 if( rEle.m_nOwnElement != rEle.m_nParentElement )
10569 if( !rEle.m_aKids.empty() )
10571 if( rEle.m_aKids.size() > ncMaxPDFArraySize ) {
10572 //then we need to add the containers for the kids elements
10573 // a list to be used for the new kid element
10574 std::list< PDFStructureElementKid > aNewKids;
10575 std::list< sal_Int32 > aNewChildren;
10577 // add Div in RoleMap, in case no one else did (TODO: is it needed? Is it dangerous?)
10578 OString aAliasName( "Div" );
10579 m_aRoleMap[ aAliasName ] = getStructureTag( PDFWriter::Division );
10581 while( rEle.m_aKids.size() > ncMaxPDFArraySize )
10583 sal_Int32 nCurrentStructElement = rEle.m_nOwnElement;
10584 sal_Int32 nNewId = sal_Int32(m_aStructure.size());
10585 m_aStructure.emplace_back( );
10586 PDFStructureElement& rEleNew = m_aStructure.back();
10587 rEleNew.m_aAlias = aAliasName;
10588 rEleNew.m_eType = PDFWriter::Division; // a new Div type container
10589 rEleNew.m_nOwnElement = nNewId;
10590 rEleNew.m_nParentElement = nCurrentStructElement;
10591 //inherit the same page as the first child to be reparented
10592 rEleNew.m_nFirstPageObject = m_aStructure[ rEle.m_aChildren.front() ].m_nFirstPageObject;
10593 rEleNew.m_nObject = createObject();//assign a PDF object number
10594 //add the object to the kid list of the parent
10595 aNewKids.emplace_back( rEleNew.m_nObject );
10596 aNewChildren.push_back( nNewId );
10598 std::list< sal_Int32 >::iterator aChildEndIt( rEle.m_aChildren.begin() );
10599 std::list< PDFStructureElementKid >::iterator aKidEndIt( rEle.m_aKids.begin() );
10600 advance( aChildEndIt, ncMaxPDFArraySize );
10601 advance( aKidEndIt, ncMaxPDFArraySize );
10603 rEleNew.m_aKids.splice( rEleNew.m_aKids.begin(),
10604 rEle.m_aKids,
10605 rEle.m_aKids.begin(),
10606 aKidEndIt );
10607 rEleNew.m_aChildren.splice( rEleNew.m_aChildren.begin(),
10608 rEle.m_aChildren,
10609 rEle.m_aChildren.begin(),
10610 aChildEndIt );
10611 // set the kid's new parent
10612 for (auto const& child : rEleNew.m_aChildren)
10614 m_aStructure[ child ].m_nParentElement = nNewId;
10617 //finally add the new kids resulting from the container added
10618 rEle.m_aKids.insert( rEle.m_aKids.begin(), aNewKids.begin(), aNewKids.end() );
10619 rEle.m_aChildren.insert( rEle.m_aChildren.begin(), aNewChildren.begin(), aNewChildren.end() );
10625 bool PDFWriterImpl::setCurrentStructureElement( sal_Int32 nEle )
10627 bool bSuccess = false;
10629 if( m_aContext.Tagged && nEle >= 0 && nEle < sal_Int32(m_aStructure.size()) )
10631 // end eventual previous marked content sequence
10632 endStructureElementMCSeq();
10634 m_nCurrentStructElement = nEle;
10635 m_bEmitStructure = checkEmitStructure();
10636 if (g_bDebugDisableCompression)
10638 OStringBuffer aLine( "setCurrentStructureElement " );
10639 aLine.append( m_nCurrentStructElement );
10640 aLine.append( ": " );
10641 aLine.append( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) );
10642 if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
10644 aLine.append( " aliased as \"" );
10645 aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
10646 aLine.append( '\"' );
10648 if( ! m_bEmitStructure )
10649 aLine.append( " (inside NonStruct)" );
10650 emitComment( aLine.getStr() );
10652 bSuccess = true;
10655 return bSuccess;
10658 bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr, enum PDFWriter::StructAttributeValue eVal )
10660 if( !m_aContext.Tagged )
10661 return false;
10663 bool bInsert = false;
10664 if( m_nCurrentStructElement > 0 && m_bEmitStructure )
10666 PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
10667 switch( eAttr )
10669 case PDFWriter::Placement:
10670 if( eVal == PDFWriter::Block ||
10671 eVal == PDFWriter::Inline ||
10672 eVal == PDFWriter::Before ||
10673 eVal == PDFWriter::Start ||
10674 eVal == PDFWriter::End )
10675 bInsert = true;
10676 break;
10677 case PDFWriter::WritingMode:
10678 if( eVal == PDFWriter::LrTb ||
10679 eVal == PDFWriter::RlTb ||
10680 eVal == PDFWriter::TbRl )
10682 bInsert = true;
10684 break;
10685 case PDFWriter::TextAlign:
10686 if( eVal == PDFWriter::Start ||
10687 eVal == PDFWriter::Center ||
10688 eVal == PDFWriter::End ||
10689 eVal == PDFWriter::Justify )
10691 if( eType == PDFWriter::Paragraph ||
10692 eType == PDFWriter::Heading ||
10693 eType == PDFWriter::H1 ||
10694 eType == PDFWriter::H2 ||
10695 eType == PDFWriter::H3 ||
10696 eType == PDFWriter::H4 ||
10697 eType == PDFWriter::H5 ||
10698 eType == PDFWriter::H6 ||
10699 eType == PDFWriter::List ||
10700 eType == PDFWriter::ListItem ||
10701 eType == PDFWriter::LILabel ||
10702 eType == PDFWriter::LIBody ||
10703 eType == PDFWriter::Table ||
10704 eType == PDFWriter::TableRow ||
10705 eType == PDFWriter::TableHeader ||
10706 eType == PDFWriter::TableData )
10708 bInsert = true;
10711 break;
10712 case PDFWriter::Width:
10713 case PDFWriter::Height:
10714 if( eVal == PDFWriter::Auto )
10716 if( eType == PDFWriter::Figure ||
10717 eType == PDFWriter::Formula ||
10718 eType == PDFWriter::Form ||
10719 eType == PDFWriter::Table ||
10720 eType == PDFWriter::TableHeader ||
10721 eType == PDFWriter::TableData )
10723 bInsert = true;
10726 break;
10727 case PDFWriter::BlockAlign:
10728 if( eVal == PDFWriter::Before ||
10729 eVal == PDFWriter::Middle ||
10730 eVal == PDFWriter::After ||
10731 eVal == PDFWriter::Justify )
10733 if( eType == PDFWriter::TableHeader ||
10734 eType == PDFWriter::TableData )
10736 bInsert = true;
10739 break;
10740 case PDFWriter::InlineAlign:
10741 if( eVal == PDFWriter::Start ||
10742 eVal == PDFWriter::Center ||
10743 eVal == PDFWriter::End )
10745 if( eType == PDFWriter::TableHeader ||
10746 eType == PDFWriter::TableData )
10748 bInsert = true;
10751 break;
10752 case PDFWriter::LineHeight:
10753 if( eVal == PDFWriter::Normal ||
10754 eVal == PDFWriter::Auto )
10756 // only for ILSE and BLSE
10757 if( eType == PDFWriter::Paragraph ||
10758 eType == PDFWriter::Heading ||
10759 eType == PDFWriter::H1 ||
10760 eType == PDFWriter::H2 ||
10761 eType == PDFWriter::H3 ||
10762 eType == PDFWriter::H4 ||
10763 eType == PDFWriter::H5 ||
10764 eType == PDFWriter::H6 ||
10765 eType == PDFWriter::List ||
10766 eType == PDFWriter::ListItem ||
10767 eType == PDFWriter::LILabel ||
10768 eType == PDFWriter::LIBody ||
10769 eType == PDFWriter::Table ||
10770 eType == PDFWriter::TableRow ||
10771 eType == PDFWriter::TableHeader ||
10772 eType == PDFWriter::TableData ||
10773 eType == PDFWriter::Span ||
10774 eType == PDFWriter::Quote ||
10775 eType == PDFWriter::Note ||
10776 eType == PDFWriter::Reference ||
10777 eType == PDFWriter::BibEntry ||
10778 eType == PDFWriter::Code ||
10779 eType == PDFWriter::Link )
10781 bInsert = true;
10784 break;
10785 case PDFWriter::TextDecorationType:
10786 if( eVal == PDFWriter::NONE ||
10787 eVal == PDFWriter::Underline ||
10788 eVal == PDFWriter::Overline ||
10789 eVal == PDFWriter::LineThrough )
10791 // only for ILSE and BLSE
10792 if( eType == PDFWriter::Paragraph ||
10793 eType == PDFWriter::Heading ||
10794 eType == PDFWriter::H1 ||
10795 eType == PDFWriter::H2 ||
10796 eType == PDFWriter::H3 ||
10797 eType == PDFWriter::H4 ||
10798 eType == PDFWriter::H5 ||
10799 eType == PDFWriter::H6 ||
10800 eType == PDFWriter::List ||
10801 eType == PDFWriter::ListItem ||
10802 eType == PDFWriter::LILabel ||
10803 eType == PDFWriter::LIBody ||
10804 eType == PDFWriter::Table ||
10805 eType == PDFWriter::TableRow ||
10806 eType == PDFWriter::TableHeader ||
10807 eType == PDFWriter::TableData ||
10808 eType == PDFWriter::Span ||
10809 eType == PDFWriter::Quote ||
10810 eType == PDFWriter::Note ||
10811 eType == PDFWriter::Reference ||
10812 eType == PDFWriter::BibEntry ||
10813 eType == PDFWriter::Code ||
10814 eType == PDFWriter::Link )
10816 bInsert = true;
10819 break;
10820 case PDFWriter::ListNumbering:
10821 if( eVal == PDFWriter::NONE ||
10822 eVal == PDFWriter::Disc ||
10823 eVal == PDFWriter::Circle ||
10824 eVal == PDFWriter::Square ||
10825 eVal == PDFWriter::Decimal ||
10826 eVal == PDFWriter::UpperRoman ||
10827 eVal == PDFWriter::LowerRoman ||
10828 eVal == PDFWriter::UpperAlpha ||
10829 eVal == PDFWriter::LowerAlpha )
10831 if( eType == PDFWriter::List )
10832 bInsert = true;
10834 break;
10835 default: break;
10839 if( bInsert )
10840 m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( eVal );
10841 else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
10842 SAL_INFO("vcl.pdfwriter",
10843 "rejecting setStructureAttribute( " << getAttributeTag( eAttr )
10844 << ", " << getAttributeValueTag( eVal )
10845 << " ) on " << getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType )
10846 << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias
10847 << ") element");
10849 return bInsert;
10852 bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttribute eAttr, sal_Int32 nValue )
10854 if( ! m_aContext.Tagged )
10855 return false;
10857 bool bInsert = false;
10858 if( m_nCurrentStructElement > 0 && m_bEmitStructure )
10860 if( eAttr == PDFWriter::Language )
10862 m_aStructure[ m_nCurrentStructElement ].m_aLocale = LanguageTag( LanguageType(nValue) ).getLocale();
10863 return true;
10866 PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
10867 switch( eAttr )
10869 case PDFWriter::SpaceBefore:
10870 case PDFWriter::SpaceAfter:
10871 case PDFWriter::StartIndent:
10872 case PDFWriter::EndIndent:
10873 // just for BLSE
10874 if( eType == PDFWriter::Paragraph ||
10875 eType == PDFWriter::Heading ||
10876 eType == PDFWriter::H1 ||
10877 eType == PDFWriter::H2 ||
10878 eType == PDFWriter::H3 ||
10879 eType == PDFWriter::H4 ||
10880 eType == PDFWriter::H5 ||
10881 eType == PDFWriter::H6 ||
10882 eType == PDFWriter::List ||
10883 eType == PDFWriter::ListItem ||
10884 eType == PDFWriter::LILabel ||
10885 eType == PDFWriter::LIBody ||
10886 eType == PDFWriter::Table ||
10887 eType == PDFWriter::TableRow ||
10888 eType == PDFWriter::TableHeader ||
10889 eType == PDFWriter::TableData )
10891 bInsert = true;
10893 break;
10894 case PDFWriter::TextIndent:
10895 // paragraph like BLSE and additional elements
10896 if( eType == PDFWriter::Paragraph ||
10897 eType == PDFWriter::Heading ||
10898 eType == PDFWriter::H1 ||
10899 eType == PDFWriter::H2 ||
10900 eType == PDFWriter::H3 ||
10901 eType == PDFWriter::H4 ||
10902 eType == PDFWriter::H5 ||
10903 eType == PDFWriter::H6 ||
10904 eType == PDFWriter::LILabel ||
10905 eType == PDFWriter::LIBody ||
10906 eType == PDFWriter::TableHeader ||
10907 eType == PDFWriter::TableData )
10909 bInsert = true;
10911 break;
10912 case PDFWriter::Width:
10913 case PDFWriter::Height:
10914 if( eType == PDFWriter::Figure ||
10915 eType == PDFWriter::Formula ||
10916 eType == PDFWriter::Form ||
10917 eType == PDFWriter::Table ||
10918 eType == PDFWriter::TableHeader ||
10919 eType == PDFWriter::TableData )
10921 bInsert = true;
10923 break;
10924 case PDFWriter::LineHeight:
10925 case PDFWriter::BaselineShift:
10926 // only for ILSE and BLSE
10927 if( eType == PDFWriter::Paragraph ||
10928 eType == PDFWriter::Heading ||
10929 eType == PDFWriter::H1 ||
10930 eType == PDFWriter::H2 ||
10931 eType == PDFWriter::H3 ||
10932 eType == PDFWriter::H4 ||
10933 eType == PDFWriter::H5 ||
10934 eType == PDFWriter::H6 ||
10935 eType == PDFWriter::List ||
10936 eType == PDFWriter::ListItem ||
10937 eType == PDFWriter::LILabel ||
10938 eType == PDFWriter::LIBody ||
10939 eType == PDFWriter::Table ||
10940 eType == PDFWriter::TableRow ||
10941 eType == PDFWriter::TableHeader ||
10942 eType == PDFWriter::TableData ||
10943 eType == PDFWriter::Span ||
10944 eType == PDFWriter::Quote ||
10945 eType == PDFWriter::Note ||
10946 eType == PDFWriter::Reference ||
10947 eType == PDFWriter::BibEntry ||
10948 eType == PDFWriter::Code ||
10949 eType == PDFWriter::Link )
10951 bInsert = true;
10953 break;
10954 case PDFWriter::RowSpan:
10955 case PDFWriter::ColSpan:
10956 // only for table cells
10957 if( eType == PDFWriter::TableHeader ||
10958 eType == PDFWriter::TableData )
10960 bInsert = true;
10962 break;
10963 case PDFWriter::LinkAnnotation:
10964 if( eType == PDFWriter::Link )
10965 bInsert = true;
10966 break;
10967 default: break;
10971 if( bInsert )
10972 m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( nValue );
10973 else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
10974 SAL_INFO("vcl.pdfwriter",
10975 "rejecting setStructureAttributeNumerical( " << getAttributeTag( eAttr )
10976 << ", " << static_cast<int>(nValue)
10977 << " ) on " << getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType )
10978 << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias
10979 << ") element");
10981 return bInsert;
10984 void PDFWriterImpl::setStructureBoundingBox( const tools::Rectangle& rRect )
10986 sal_Int32 nPageNr = m_nCurrentPage;
10987 if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) || !m_aContext.Tagged )
10988 return;
10990 if( m_nCurrentStructElement > 0 && m_bEmitStructure )
10992 PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
10993 if( eType == PDFWriter::Figure ||
10994 eType == PDFWriter::Formula ||
10995 eType == PDFWriter::Form ||
10996 eType == PDFWriter::Table )
10998 m_aStructure[ m_nCurrentStructElement ].m_aBBox = rRect;
10999 // convert to default user space now, since the mapmode may change
11000 m_aPages[nPageNr].convertRect( m_aStructure[ m_nCurrentStructElement ].m_aBBox );
11005 void PDFWriterImpl::setActualText( const OUString& rText )
11007 if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
11009 m_aStructure[ m_nCurrentStructElement ].m_aActualText = rText;
11013 void PDFWriterImpl::setAlternateText( const OUString& rText )
11015 if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
11017 m_aStructure[ m_nCurrentStructElement ].m_aAltText = rText;
11021 void PDFWriterImpl::setPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr )
11023 if( nPageNr < 0 )
11024 nPageNr = m_nCurrentPage;
11026 if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
11027 return;
11029 m_aPages[ nPageNr ].m_eTransition = eType;
11030 m_aPages[ nPageNr ].m_nTransTime = nMilliSec;
11033 void PDFWriterImpl::ensureUniqueRadioOnValues()
11035 // loop over radio groups
11036 for (auto const& group : m_aRadioGroupWidgets)
11038 PDFWidget& rGroupWidget = m_aWidgets[ group.second ];
11039 // check whether all kids have a unique OnValue
11040 std::unordered_map< OUString, sal_Int32 > aOnValues;
11041 bool bIsUnique = true;
11042 for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11044 const OUString& rVal = m_aWidgets[nKidIndex].m_aOnValue;
11045 SAL_INFO("vcl.pdfwriter", "OnValue: " << rVal);
11046 if( aOnValues.find( rVal ) == aOnValues.end() )
11048 aOnValues[ rVal ] = 1;
11050 else
11052 bIsUnique = false;
11053 break;
11056 if( ! bIsUnique )
11058 SAL_INFO("vcl.pdfwriter", "enforcing unique OnValues" );
11059 // make unique by using ascending OnValues
11060 int nKid = 0;
11061 for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11063 PDFWidget& rKid = m_aWidgets[nKidIndex];
11064 rKid.m_aOnValue = OUString::number( nKid+1 );
11065 if( rKid.m_aValue != "Off" )
11066 rKid.m_aValue = rKid.m_aOnValue;
11067 ++nKid;
11070 // finally move the "Yes" appearance to the OnValue appearance
11071 for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11073 PDFWidget& rKid = m_aWidgets[nKidIndex];
11074 PDFAppearanceMap::iterator app_it = rKid.m_aAppearances.find( "N" );
11075 if( app_it != rKid.m_aAppearances.end() )
11077 PDFAppearanceStreams::iterator stream_it = app_it->second.find( "Yes" );
11078 if( stream_it != app_it->second.end() )
11080 SvMemoryStream* pStream = stream_it->second;
11081 app_it->second.erase( stream_it );
11082 OStringBuffer aBuf( rKid.m_aOnValue.getLength()*2 );
11083 appendName( rKid.m_aOnValue, aBuf );
11084 (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
11086 else
11087 SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Yes\" stream" );
11089 // update selected radio button
11090 if( rKid.m_aValue != "Off" )
11092 rGroupWidget.m_aValue = rKid.m_aValue;
11098 sal_Int32 PDFWriterImpl::findRadioGroupWidget( const PDFWriter::RadioButtonWidget& rBtn )
11100 sal_Int32 nRadioGroupWidget = -1;
11102 std::map< sal_Int32, sal_Int32 >::const_iterator it = m_aRadioGroupWidgets.find( rBtn.RadioGroup );
11104 if( it == m_aRadioGroupWidgets.end() )
11106 m_aRadioGroupWidgets[ rBtn.RadioGroup ] = nRadioGroupWidget =
11107 sal_Int32(m_aWidgets.size());
11109 // new group, insert the radiobutton
11110 m_aWidgets.emplace_back( );
11111 m_aWidgets.back().m_nObject = createObject();
11112 m_aWidgets.back().m_nPage = m_nCurrentPage;
11113 m_aWidgets.back().m_eType = PDFWriter::RadioButton;
11114 m_aWidgets.back().m_nRadioGroup = rBtn.RadioGroup;
11115 m_aWidgets.back().m_nFlags |= 0x0000C000; // NoToggleToOff and Radio bits
11117 createWidgetFieldName( sal_Int32(m_aWidgets.size()-1), rBtn );
11119 else
11120 nRadioGroupWidget = it->second;
11122 return nRadioGroupWidget;
11125 sal_Int32 PDFWriterImpl::createControl( const PDFWriter::AnyWidget& rControl, sal_Int32 nPageNr )
11127 if( nPageNr < 0 )
11128 nPageNr = m_nCurrentPage;
11130 if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
11131 return -1;
11133 bool sigHidden(true);
11134 sal_Int32 nNewWidget = m_aWidgets.size();
11135 m_aWidgets.emplace_back( );
11137 m_aWidgets.back().m_nObject = createObject();
11138 m_aWidgets.back().m_aRect = rControl.Location;
11139 m_aWidgets.back().m_nPage = nPageNr;
11140 m_aWidgets.back().m_eType = rControl.getType();
11142 sal_Int32 nRadioGroupWidget = -1;
11143 // for unknown reasons the radio buttons of a radio group must not have a
11144 // field name, else the buttons are in fact check boxes -
11145 // that is multiple buttons of the radio group can be selected
11146 if( rControl.getType() == PDFWriter::RadioButton )
11147 nRadioGroupWidget = findRadioGroupWidget( static_cast<const PDFWriter::RadioButtonWidget&>(rControl) );
11148 else
11150 createWidgetFieldName( nNewWidget, rControl );
11153 // caution: m_aWidgets must not be changed after here or rNewWidget may be invalid
11154 PDFWidget& rNewWidget = m_aWidgets[nNewWidget];
11155 rNewWidget.m_aDescription = rControl.Description;
11156 rNewWidget.m_aText = rControl.Text;
11157 rNewWidget.m_nTextStyle = rControl.TextStyle &
11158 ( DrawTextFlags::Left | DrawTextFlags::Center | DrawTextFlags::Right | DrawTextFlags::Top |
11159 DrawTextFlags::VCenter | DrawTextFlags::Bottom |
11160 DrawTextFlags::MultiLine | DrawTextFlags::WordBreak );
11161 rNewWidget.m_nTabOrder = rControl.TabOrder;
11163 // various properties are set via the flags (/Ff) property of the field dict
11164 if( rControl.ReadOnly )
11165 rNewWidget.m_nFlags |= 1;
11166 if( rControl.getType() == PDFWriter::PushButton )
11168 const PDFWriter::PushButtonWidget& rBtn = static_cast<const PDFWriter::PushButtonWidget&>(rControl);
11169 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11170 rNewWidget.m_nTextStyle =
11171 DrawTextFlags::Center | DrawTextFlags::VCenter |
11172 DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11174 rNewWidget.m_nFlags |= 0x00010000;
11175 if( !rBtn.URL.isEmpty() )
11176 rNewWidget.m_aListEntries.push_back( rBtn.URL );
11177 rNewWidget.m_bSubmit = rBtn.Submit;
11178 rNewWidget.m_bSubmitGet = rBtn.SubmitGet;
11179 rNewWidget.m_nDest = rBtn.Dest;
11180 createDefaultPushButtonAppearance( rNewWidget, rBtn );
11182 else if( rControl.getType() == PDFWriter::RadioButton )
11184 const PDFWriter::RadioButtonWidget& rBtn = static_cast<const PDFWriter::RadioButtonWidget&>(rControl);
11185 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11186 rNewWidget.m_nTextStyle =
11187 DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11188 /* PDF sees a RadioButton group as one radio button with
11189 * children which are in turn check boxes
11191 * so we need to create a radio button on demand for a new group
11192 * and insert a checkbox for each RadioButtonWidget as its child
11194 rNewWidget.m_eType = PDFWriter::CheckBox;
11195 rNewWidget.m_nRadioGroup = rBtn.RadioGroup;
11197 SAL_WARN_IF( nRadioGroupWidget < 0 || nRadioGroupWidget >= static_cast<sal_Int32>(m_aWidgets.size()), "vcl.pdfwriter", "no radio group parent" );
11199 PDFWidget& rRadioButton = m_aWidgets[nRadioGroupWidget];
11200 rRadioButton.m_aKids.push_back( rNewWidget.m_nObject );
11201 rRadioButton.m_aKidsIndex.push_back( nNewWidget );
11202 rNewWidget.m_nParent = rRadioButton.m_nObject;
11204 rNewWidget.m_aValue = "Off";
11205 rNewWidget.m_aOnValue = rBtn.OnValue;
11206 if( rRadioButton.m_aValue.isEmpty() && rBtn.Selected )
11208 rNewWidget.m_aValue = rNewWidget.m_aOnValue;
11209 rRadioButton.m_aValue = rNewWidget.m_aOnValue;
11211 createDefaultRadioButtonAppearance( rNewWidget, rBtn );
11213 // union rect of radio group
11214 tools::Rectangle aRect = rNewWidget.m_aRect;
11215 m_aPages[ nPageNr ].convertRect( aRect );
11216 rRadioButton.m_aRect.Union( aRect );
11218 else if( rControl.getType() == PDFWriter::CheckBox )
11220 const PDFWriter::CheckBoxWidget& rBox = static_cast<const PDFWriter::CheckBoxWidget&>(rControl);
11221 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11222 rNewWidget.m_nTextStyle =
11223 DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11225 rNewWidget.m_aValue = rBox.Checked ? OUStringLiteral("Yes") : OUStringLiteral("Off" );
11226 // create default appearance before m_aRect gets transformed
11227 createDefaultCheckBoxAppearance( rNewWidget, rBox );
11229 else if( rControl.getType() == PDFWriter::ListBox )
11231 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11232 rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
11234 const PDFWriter::ListBoxWidget& rLstBox = static_cast<const PDFWriter::ListBoxWidget&>(rControl);
11235 rNewWidget.m_aListEntries = rLstBox.Entries;
11236 rNewWidget.m_aSelectedEntries = rLstBox.SelectedEntries;
11237 rNewWidget.m_aValue = rLstBox.Text;
11238 if( rLstBox.DropDown )
11239 rNewWidget.m_nFlags |= 0x00020000;
11240 if( rLstBox.MultiSelect && !rLstBox.DropDown && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
11241 rNewWidget.m_nFlags |= 0x00200000;
11243 createDefaultListBoxAppearance( rNewWidget, rLstBox );
11245 else if( rControl.getType() == PDFWriter::ComboBox )
11247 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11248 rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
11250 const PDFWriter::ComboBoxWidget& rBox = static_cast<const PDFWriter::ComboBoxWidget&>(rControl);
11251 rNewWidget.m_aValue = rBox.Text;
11252 rNewWidget.m_aListEntries = rBox.Entries;
11253 rNewWidget.m_nFlags |= 0x00060000; // combo and edit flag
11255 PDFWriter::ListBoxWidget aLBox;
11256 aLBox.Name = rBox.Name;
11257 aLBox.Description = rBox.Description;
11258 aLBox.Text = rBox.Text;
11259 aLBox.TextStyle = rBox.TextStyle;
11260 aLBox.ReadOnly = rBox.ReadOnly;
11261 aLBox.Border = rBox.Border;
11262 aLBox.BorderColor = rBox.BorderColor;
11263 aLBox.Background = rBox.Background;
11264 aLBox.BackgroundColor = rBox.BackgroundColor;
11265 aLBox.TextFont = rBox.TextFont;
11266 aLBox.TextColor = rBox.TextColor;
11267 aLBox.DropDown = true;
11268 aLBox.MultiSelect = false;
11269 aLBox.Entries = rBox.Entries;
11271 createDefaultListBoxAppearance( rNewWidget, aLBox );
11273 else if( rControl.getType() == PDFWriter::Edit )
11275 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11276 rNewWidget.m_nTextStyle = DrawTextFlags::Left | DrawTextFlags::VCenter;
11278 const PDFWriter::EditWidget& rEdit = static_cast<const PDFWriter::EditWidget&>(rControl);
11279 if( rEdit.MultiLine )
11281 rNewWidget.m_nFlags |= 0x00001000;
11282 rNewWidget.m_nTextStyle |= DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11284 if( rEdit.Password )
11285 rNewWidget.m_nFlags |= 0x00002000;
11286 if( rEdit.FileSelect && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
11287 rNewWidget.m_nFlags |= 0x00100000;
11288 rNewWidget.m_nMaxLen = rEdit.MaxLen;
11289 rNewWidget.m_aValue = rEdit.Text;
11291 createDefaultEditAppearance( rNewWidget, rEdit );
11293 #if HAVE_FEATURE_NSS
11294 else if( rControl.getType() == PDFWriter::Signature)
11296 sigHidden = true;
11298 rNewWidget.m_aRect = tools::Rectangle(0, 0, 0, 0);
11300 m_nSignatureObject = createObject();
11301 rNewWidget.m_aValue = OUString::number( m_nSignatureObject );
11302 rNewWidget.m_aValue += " 0 R";
11303 // let's add a fake appearance
11304 rNewWidget.m_aAppearances[ "N" ][ "Standard" ] = new SvMemoryStream();
11306 #endif
11308 // if control is a hidden signature, do not convert coordinates since we
11309 // need /Rect [ 0 0 0 0 ]
11310 if ( ! ( ( rControl.getType() == PDFWriter::Signature ) && sigHidden ) )
11312 // convert to default user space now, since the mapmode may change
11313 // note: create default appearances before m_aRect gets transformed
11314 m_aPages[ nPageNr ].convertRect( rNewWidget.m_aRect );
11317 // insert widget to page's annotation list
11318 m_aPages[ nPageNr ].m_aAnnotations.push_back( rNewWidget.m_nObject );
11320 return nNewWidget;
11323 void PDFWriterImpl::addStream( const OUString& rMimeType, PDFOutputStream* pStream )
11325 if( pStream )
11327 m_aAdditionalStreams.emplace_back( );
11328 PDFAddStream& rStream = m_aAdditionalStreams.back();
11329 rStream.m_aMimeType = !rMimeType.isEmpty()
11330 ? rMimeType
11331 : OUString( "application/octet-stream" );
11332 rStream.m_pStream = pStream;
11333 rStream.m_bCompress = false;
11337 void PDFWriterImpl::MARK( const char* pString )
11339 beginStructureElementMCSeq();
11340 if (g_bDebugDisableCompression)
11341 emitComment( pString );
11344 sal_Int32 PDFWriterImpl::ReferenceXObjectEmit::getObject() const
11346 if (m_nFormObject > 0)
11347 return m_nFormObject;
11348 else
11349 return m_nBitmapObject;
11352 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */