Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / vcl / source / gdi / pdfwriter_impl.cxx
bloba63a2152e6b73d88e66dd68c1be21a5705eade94
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 VirtualDevice::dispose();
1424 void PDFWriterImpl::setupDocInfo()
1426 std::vector< sal_uInt8 > aId;
1427 m_aCreationDateString = PDFWriter::GetDateTime();
1428 computeDocumentIdentifier( aId, m_aContext.DocumentInfo, m_aCreationDateString, m_aCreationMetaDateString );
1429 if( m_aContext.Encryption.DocumentIdentifier.empty() )
1430 m_aContext.Encryption.DocumentIdentifier = aId;
1433 OString PDFWriter::GetDateTime()
1435 OStringBuffer aRet;
1437 TimeValue aTVal, aGMT;
1438 oslDateTime aDT;
1439 osl_getSystemTime(&aGMT);
1440 osl_getLocalTimeFromSystemTime(&aGMT, &aTVal);
1441 osl_getDateTimeFromTimeValue(&aTVal, &aDT);
1442 aRet.append("D:");
1443 aRet.append(static_cast<sal_Char>('0' + ((aDT.Year / 1000) % 10)));
1444 aRet.append(static_cast<sal_Char>('0' + ((aDT.Year / 100) % 10)));
1445 aRet.append(static_cast<sal_Char>('0' + ((aDT.Year / 10) % 10)));
1446 aRet.append(static_cast<sal_Char>('0' + (aDT.Year % 10)));
1447 aRet.append(static_cast<sal_Char>('0' + ((aDT.Month / 10) % 10)));
1448 aRet.append(static_cast<sal_Char>('0' + (aDT.Month % 10)));
1449 aRet.append(static_cast<sal_Char>('0' + ((aDT.Day / 10) % 10)));
1450 aRet.append(static_cast<sal_Char>('0' + (aDT.Day % 10)));
1451 aRet.append(static_cast<sal_Char>('0' + ((aDT.Hours / 10) % 10)));
1452 aRet.append(static_cast<sal_Char>('0' + (aDT.Hours % 10)));
1453 aRet.append(static_cast<sal_Char>('0' + ((aDT.Minutes / 10) % 10)));
1454 aRet.append(static_cast<sal_Char>('0' + (aDT.Minutes % 10)));
1455 aRet.append(static_cast<sal_Char>('0' + ((aDT.Seconds / 10) % 10)));
1456 aRet.append(static_cast<sal_Char>('0' + (aDT.Seconds % 10)));
1458 sal_uInt32 nDelta = 0;
1459 if (aGMT.Seconds > aTVal.Seconds)
1461 aRet.append("-");
1462 nDelta = aGMT.Seconds-aTVal.Seconds;
1464 else if (aGMT.Seconds < aTVal.Seconds)
1466 aRet.append("+");
1467 nDelta = aTVal.Seconds-aGMT.Seconds;
1469 else
1470 aRet.append("Z");
1472 if (nDelta)
1474 aRet.append(static_cast<sal_Char>('0' + ((nDelta / 36000) % 10)));
1475 aRet.append(static_cast<sal_Char>('0' + ((nDelta / 3600) % 10)));
1476 aRet.append("'");
1477 aRet.append(static_cast<sal_Char>('0' + ((nDelta / 600) % 6)));
1478 aRet.append(static_cast<sal_Char>('0' + ((nDelta / 60) % 10)));
1480 aRet.append( "'" );
1482 return aRet.makeStringAndClear();
1485 void PDFWriterImpl::computeDocumentIdentifier( std::vector< sal_uInt8 >& o_rIdentifier,
1486 const vcl::PDFWriter::PDFDocInfo& i_rDocInfo,
1487 const OString& i_rCString1,
1488 OString& o_rCString2
1491 o_rIdentifier.clear();
1493 //build the document id
1494 OString aInfoValuesOut;
1495 OStringBuffer aID( 1024 );
1496 if( !i_rDocInfo.Title.isEmpty() )
1497 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Title, aID);
1498 if( !i_rDocInfo.Author.isEmpty() )
1499 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Author, aID);
1500 if( !i_rDocInfo.Subject.isEmpty() )
1501 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Subject, aID);
1502 if( !i_rDocInfo.Keywords.isEmpty() )
1503 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Keywords, aID);
1504 if( !i_rDocInfo.Creator.isEmpty() )
1505 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Creator, aID);
1506 if( !i_rDocInfo.Producer.isEmpty() )
1507 PDFWriter::AppendUnicodeTextString(i_rDocInfo.Producer, aID);
1509 TimeValue aTVal, aGMT;
1510 oslDateTime aDT;
1511 osl_getSystemTime( &aGMT );
1512 osl_getLocalTimeFromSystemTime( &aGMT, &aTVal );
1513 osl_getDateTimeFromTimeValue( &aTVal, &aDT );
1514 OStringBuffer aCreationMetaDateString(64);
1516 // i59651: we fill the Metadata date string as well, if PDF/A is requested
1517 // according to ISO 19005-1:2005 6.7.3 the date is corrected for
1518 // local time zone offset UTC only, whereas Acrobat 8 seems
1519 // to use the localtime notation only
1520 // according to a recommendation in XMP Specification (Jan 2004, page 75)
1521 // the Acrobat way seems the right approach
1522 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Year/1000)%10)) );
1523 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Year/100)%10)) );
1524 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Year/10)%10)) );
1525 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Year)%10)) );
1526 aCreationMetaDateString.append( "-" );
1527 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Month/10)%10)) );
1528 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Month)%10)) );
1529 aCreationMetaDateString.append( "-" );
1530 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Day/10)%10)) );
1531 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Day)%10)) );
1532 aCreationMetaDateString.append( "T" );
1533 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Hours/10)%10)) );
1534 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Hours)%10)) );
1535 aCreationMetaDateString.append( ":" );
1536 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Minutes/10)%10)) );
1537 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Minutes)%10)) );
1538 aCreationMetaDateString.append( ":" );
1539 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Seconds/10)%10)) );
1540 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((aDT.Seconds)%10)) );
1542 sal_uInt32 nDelta = 0;
1543 if( aGMT.Seconds > aTVal.Seconds )
1545 nDelta = aGMT.Seconds-aTVal.Seconds;
1546 aCreationMetaDateString.append( "-" );
1548 else if( aGMT.Seconds < aTVal.Seconds )
1550 nDelta = aTVal.Seconds-aGMT.Seconds;
1551 aCreationMetaDateString.append( "+" );
1553 else
1555 aCreationMetaDateString.append( "Z" );
1558 if( nDelta )
1560 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((nDelta/36000)%10)) );
1561 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((nDelta/3600)%10)) );
1562 aCreationMetaDateString.append( ":" );
1563 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((nDelta/600)%6)) );
1564 aCreationMetaDateString.append( static_cast<sal_Char>('0' + ((nDelta/60)%10)) );
1566 aID.append( i_rCString1.getStr(), i_rCString1.getLength() );
1568 aInfoValuesOut = aID.makeStringAndClear();
1569 o_rCString2 = aCreationMetaDateString.makeStringAndClear();
1571 ::comphelper::Hash aDigest(::comphelper::HashType::MD5);
1572 aDigest.update(reinterpret_cast<unsigned char const*>(&aGMT), sizeof(aGMT));
1573 aDigest.update(reinterpret_cast<unsigned char const*>(aInfoValuesOut.getStr()), aInfoValuesOut.getLength());
1574 //the binary form of the doc id is needed for encryption stuff
1575 o_rIdentifier = aDigest.finalize();
1578 /* i12626 methods */
1580 check if the Unicode string must be encrypted or not, perform the requested task,
1581 append the string as unicode hex, encrypted if needed
1583 inline void PDFWriterImpl::appendUnicodeTextStringEncrypt( const OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
1585 rOutBuffer.append( "<" );
1586 if( m_aContext.Encryption.Encrypt() )
1588 const sal_Unicode* pStr = rInString.getStr();
1589 sal_Int32 nLen = rInString.getLength();
1590 //prepare a unicode string, encrypt it
1591 enableStringEncryption( nInObjectNumber );
1592 sal_uInt8 *pCopy = m_vEncryptionBuffer.data();
1593 sal_Int32 nChars = 2 + (nLen * 2);
1594 m_vEncryptionBuffer.resize(nChars);
1595 *pCopy++ = 0xFE;
1596 *pCopy++ = 0xFF;
1597 // we need to prepare a byte stream from the unicode string buffer
1598 for( int i = 0; i < nLen; i++ )
1600 sal_Unicode aUnChar = pStr[i];
1601 *pCopy++ = static_cast<sal_uInt8>( aUnChar >> 8 );
1602 *pCopy++ = static_cast<sal_uInt8>( aUnChar & 255 );
1604 //encrypt in place
1605 rtl_cipher_encodeARCFOUR( m_aCipher, m_vEncryptionBuffer.data(), nChars, m_vEncryptionBuffer.data(), nChars );
1606 //now append, hexadecimal (appendHex), the encrypted result
1607 for(int i = 0; i < nChars; i++)
1608 appendHex( m_vEncryptionBuffer[i], rOutBuffer );
1610 else
1611 PDFWriter::AppendUnicodeTextString(rInString, rOutBuffer);
1612 rOutBuffer.append( ">" );
1615 inline void PDFWriterImpl::appendLiteralStringEncrypt( OStringBuffer const & rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
1617 rOutBuffer.append( "(" );
1618 sal_Int32 nChars = rInString.getLength();
1619 //check for encryption, if ok, encrypt the string, then convert with appndLiteralString
1620 if( m_aContext.Encryption.Encrypt() )
1622 m_vEncryptionBuffer.resize(nChars);
1623 //encrypt the string in a buffer, then append it
1624 enableStringEncryption( nInObjectNumber );
1625 rtl_cipher_encodeARCFOUR( m_aCipher, rInString.getStr(), nChars, m_vEncryptionBuffer.data(), nChars );
1626 appendLiteralString( reinterpret_cast<sal_Char*>(m_vEncryptionBuffer.data()), nChars, rOutBuffer );
1628 else
1629 appendLiteralString( rInString.getStr(), nChars , rOutBuffer );
1630 rOutBuffer.append( ")" );
1633 inline void PDFWriterImpl::appendLiteralStringEncrypt( const OString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
1635 OStringBuffer aBufferString( rInString );
1636 appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer);
1639 void PDFWriterImpl::appendLiteralStringEncrypt( const OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer, rtl_TextEncoding nEnc )
1641 OString aBufferString( OUStringToOString( rInString, nEnc ) );
1642 sal_Int32 nLen = aBufferString.getLength();
1643 OStringBuffer aBuf( nLen );
1644 const sal_Char* pT = aBufferString.getStr();
1646 for( sal_Int32 i = 0; i < nLen; i++, pT++ )
1648 if( (*pT & 0x80) == 0 )
1649 aBuf.append( *pT );
1650 else
1652 aBuf.append( '<' );
1653 appendHex( *pT, aBuf );
1654 aBuf.append( '>' );
1657 aBufferString = aBuf.makeStringAndClear();
1658 appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer);
1661 /* end i12626 methods */
1663 void PDFWriterImpl::emitComment( const char* pComment )
1665 OString aLine = "% " + rtl::OStringView(pComment) + "\n";
1666 writeBuffer( aLine.getStr(), aLine.getLength() );
1669 bool PDFWriterImpl::compressStream( SvMemoryStream* pStream )
1671 if (!g_bDebugDisableCompression)
1673 sal_uLong nEndPos = pStream->TellEnd();
1674 pStream->Seek( STREAM_SEEK_TO_BEGIN );
1675 ZCodec aCodec( 0x4000, 0x4000 );
1676 SvMemoryStream aStream;
1677 aCodec.BeginCompression();
1678 aCodec.Write( aStream, static_cast<const sal_uInt8*>(pStream->GetData()), nEndPos );
1679 aCodec.EndCompression();
1680 nEndPos = aStream.Tell();
1681 pStream->Seek( STREAM_SEEK_TO_BEGIN );
1682 aStream.Seek( STREAM_SEEK_TO_BEGIN );
1683 pStream->SetStreamSize( nEndPos );
1684 pStream->WriteBytes( aStream.GetData(), nEndPos );
1685 return true;
1687 else
1688 return false;
1691 void PDFWriterImpl::beginCompression()
1693 if (!g_bDebugDisableCompression)
1695 m_pCodec = std::make_unique<ZCodec>( 0x4000, 0x4000 );
1696 m_pMemStream = std::make_unique<SvMemoryStream>();
1697 m_pCodec->BeginCompression();
1701 void PDFWriterImpl::endCompression()
1703 if (!g_bDebugDisableCompression && m_pCodec)
1705 m_pCodec->EndCompression();
1706 m_pCodec.reset();
1707 sal_uInt64 nLen = m_pMemStream->Tell();
1708 m_pMemStream->Seek( 0 );
1709 writeBuffer( m_pMemStream->GetData(), nLen );
1710 m_pMemStream.reset();
1714 bool PDFWriterImpl::writeBuffer( const void* pBuffer, sal_uInt64 nBytes )
1716 if( ! m_bOpen ) // we are already down the drain
1717 return false;
1719 if( ! nBytes ) // huh ?
1720 return true;
1722 if( !m_aOutputStreams.empty() )
1724 m_aOutputStreams.front().m_pStream->Seek( STREAM_SEEK_TO_END );
1725 m_aOutputStreams.front().m_pStream->WriteBytes(
1726 pBuffer, sal::static_int_cast<std::size_t>(nBytes));
1727 return true;
1730 sal_uInt64 nWritten;
1731 if( m_pCodec )
1733 m_pCodec->Write( *m_pMemStream, static_cast<const sal_uInt8*>(pBuffer), static_cast<sal_uLong>(nBytes) );
1734 nWritten = nBytes;
1736 else
1738 bool buffOK = true;
1739 if( m_bEncryptThisStream )
1741 /* implement the encryption part of the PDF spec encryption algorithm 3.1 */
1742 m_vEncryptionBuffer.resize(nBytes);
1743 if( buffOK )
1744 rtl_cipher_encodeARCFOUR( m_aCipher,
1745 pBuffer, static_cast<sal_Size>(nBytes),
1746 m_vEncryptionBuffer.data(), static_cast<sal_Size>(nBytes) );
1749 const void* pWriteBuffer = ( m_bEncryptThisStream && buffOK ) ? m_vEncryptionBuffer.data() : pBuffer;
1750 m_DocDigest.update(static_cast<unsigned char const*>(pWriteBuffer), static_cast<sal_uInt32>(nBytes));
1752 if (m_aFile.write(pWriteBuffer, nBytes, nWritten) != osl::File::E_None)
1753 nWritten = 0;
1755 if( nWritten != nBytes )
1757 m_aFile.close();
1758 m_bOpen = false;
1762 return nWritten == nBytes;
1765 void PDFWriterImpl::newPage( double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
1767 endPage();
1768 m_nCurrentPage = m_aPages.size();
1769 m_aPages.emplace_back(this, nPageWidth, nPageHeight, eOrientation );
1770 m_aPages.back().beginStream();
1772 // setup global graphics state
1773 // linewidth is "1 pixel" by default
1774 OStringBuffer aBuf( 16 );
1775 appendDouble( 72.0/double(GetDPIX()), aBuf );
1776 aBuf.append( " w\n" );
1777 writeBuffer( aBuf.getStr(), aBuf.getLength() );
1780 void PDFWriterImpl::endPage()
1782 if( m_aPages.empty() )
1783 return;
1785 // close eventual MC sequence
1786 endStructureElementMCSeq();
1788 // sanity check
1789 if( !m_aOutputStreams.empty() )
1791 OSL_FAIL( "redirection across pages !!!" );
1792 m_aOutputStreams.clear(); // leak !
1793 m_aMapMode.SetOrigin( Point() );
1796 m_aGraphicsStack.clear();
1797 m_aGraphicsStack.emplace_back( );
1799 // this should pop the PDF graphics stack if necessary
1800 updateGraphicsState();
1802 m_aPages.back().endStream();
1804 // reset the default font
1805 Font aFont;
1806 aFont.SetFamilyName( "Times" );
1807 aFont.SetFontSize( Size( 0, 12 ) );
1809 m_aCurrentPDFState = m_aGraphicsStack.front();
1810 m_aGraphicsStack.front().m_aFont = aFont;
1812 for (auto & bitmap : m_aBitmaps)
1814 if( ! bitmap.m_aBitmap.IsEmpty() )
1816 writeBitmapObject(bitmap);
1817 bitmap.m_aBitmap = BitmapEx();
1820 for (auto & jpeg : m_aJPGs)
1822 if( jpeg.m_pStream )
1824 writeJPG( jpeg );
1825 jpeg.m_pStream.reset();
1826 jpeg.m_aMask = Bitmap();
1829 for (auto & item : m_aTransparentObjects)
1831 if( item.m_pContentStream )
1833 writeTransparentObject(item);
1834 item.m_pContentStream.reset();
1840 sal_Int32 PDFWriterImpl::createObject()
1842 m_aObjects.push_back( ~0U );
1843 return m_aObjects.size();
1846 bool PDFWriterImpl::updateObject( sal_Int32 n )
1848 if( ! m_bOpen )
1849 return false;
1851 sal_uInt64 nOffset = ~0U;
1852 osl::File::RC aError = m_aFile.getPos(nOffset);
1853 SAL_WARN_IF( aError != osl::File::E_None, "vcl.pdfwriter", "could not register object" );
1854 if (aError != osl::File::E_None)
1856 m_aFile.close();
1857 m_bOpen = false;
1859 m_aObjects[ n-1 ] = nOffset;
1860 return aError == osl::File::E_None;
1863 #define CHECK_RETURN( x ) if( !(x) ) return 0
1864 #define CHECK_RETURN2( x ) if( !(x) ) return
1866 sal_Int32 PDFWriterImpl::emitStructParentTree( sal_Int32 nObject )
1868 if( nObject > 0 )
1870 OStringBuffer aLine( 1024 );
1872 aLine.append( nObject );
1873 aLine.append( " 0 obj\n"
1874 "<</Nums[\n" );
1875 sal_Int32 nTreeItems = m_aStructParentTree.size();
1876 for( sal_Int32 n = 0; n < nTreeItems; n++ )
1878 aLine.append( n );
1879 aLine.append( ' ' );
1880 aLine.append( m_aStructParentTree[n] );
1881 aLine.append( "\n" );
1883 aLine.append( "]>>\nendobj\n\n" );
1884 CHECK_RETURN( updateObject( nObject ) );
1885 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
1887 return nObject;
1890 const sal_Char* PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr )
1892 static std::map< PDFWriter::StructAttribute, const char* > aAttributeStrings;
1893 // fill maps once
1894 if( aAttributeStrings.empty() )
1896 aAttributeStrings[ PDFWriter::Placement ] = "Placement";
1897 aAttributeStrings[ PDFWriter::WritingMode ] = "WritingMode";
1898 aAttributeStrings[ PDFWriter::SpaceBefore ] = "SpaceBefore";
1899 aAttributeStrings[ PDFWriter::SpaceAfter ] = "SpaceAfter";
1900 aAttributeStrings[ PDFWriter::StartIndent ] = "StartIndent";
1901 aAttributeStrings[ PDFWriter::EndIndent ] = "EndIndent";
1902 aAttributeStrings[ PDFWriter::TextIndent ] = "TextIndent";
1903 aAttributeStrings[ PDFWriter::TextAlign ] = "TextAlign";
1904 aAttributeStrings[ PDFWriter::Width ] = "Width";
1905 aAttributeStrings[ PDFWriter::Height ] = "Height";
1906 aAttributeStrings[ PDFWriter::BlockAlign ] = "BlockAlign";
1907 aAttributeStrings[ PDFWriter::InlineAlign ] = "InlineAlign";
1908 aAttributeStrings[ PDFWriter::LineHeight ] = "LineHeight";
1909 aAttributeStrings[ PDFWriter::BaselineShift ] = "BaselineShift";
1910 aAttributeStrings[ PDFWriter::TextDecorationType ] = "TextDecorationType";
1911 aAttributeStrings[ PDFWriter::ListNumbering ] = "ListNumbering";
1912 aAttributeStrings[ PDFWriter::RowSpan ] = "RowSpan";
1913 aAttributeStrings[ PDFWriter::ColSpan ] = "ColSpan";
1914 aAttributeStrings[ PDFWriter::LinkAnnotation ] = "LinkAnnotation";
1917 std::map< PDFWriter::StructAttribute, const char* >::const_iterator it =
1918 aAttributeStrings.find( eAttr );
1920 if( it == aAttributeStrings.end() )
1921 SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttribute " << eAttr);
1923 return it != aAttributeStrings.end() ? it->second : "";
1926 const sal_Char* PDFWriterImpl::getAttributeValueTag( PDFWriter::StructAttributeValue eVal )
1928 static std::map< PDFWriter::StructAttributeValue, const char* > aValueStrings;
1930 if( aValueStrings.empty() )
1932 aValueStrings[ PDFWriter::NONE ] = "None";
1933 aValueStrings[ PDFWriter::Block ] = "Block";
1934 aValueStrings[ PDFWriter::Inline ] = "Inline";
1935 aValueStrings[ PDFWriter::Before ] = "Before";
1936 aValueStrings[ PDFWriter::After ] = "After";
1937 aValueStrings[ PDFWriter::Start ] = "Start";
1938 aValueStrings[ PDFWriter::End ] = "End";
1939 aValueStrings[ PDFWriter::LrTb ] = "LrTb";
1940 aValueStrings[ PDFWriter::RlTb ] = "RlTb";
1941 aValueStrings[ PDFWriter::TbRl ] = "TbRl";
1942 aValueStrings[ PDFWriter::Center ] = "Center";
1943 aValueStrings[ PDFWriter::Justify ] = "Justify";
1944 aValueStrings[ PDFWriter::Auto ] = "Auto";
1945 aValueStrings[ PDFWriter::Middle ] = "Middle";
1946 aValueStrings[ PDFWriter::Normal ] = "Normal";
1947 aValueStrings[ PDFWriter::Underline ] = "Underline";
1948 aValueStrings[ PDFWriter::Overline ] = "Overline";
1949 aValueStrings[ PDFWriter::LineThrough ] = "LineThrough";
1950 aValueStrings[ PDFWriter::Disc ] = "Disc";
1951 aValueStrings[ PDFWriter::Circle ] = "Circle";
1952 aValueStrings[ PDFWriter::Square ] = "Square";
1953 aValueStrings[ PDFWriter::Decimal ] = "Decimal";
1954 aValueStrings[ PDFWriter::UpperRoman ] = "UpperRoman";
1955 aValueStrings[ PDFWriter::LowerRoman ] = "LowerRoman";
1956 aValueStrings[ PDFWriter::UpperAlpha ] = "UpperAlpha";
1957 aValueStrings[ PDFWriter::LowerAlpha ] = "LowerAlpha";
1960 std::map< PDFWriter::StructAttributeValue, const char* >::const_iterator it =
1961 aValueStrings.find( eVal );
1963 if( it == aValueStrings.end() )
1964 SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttributeValue " << eVal);
1966 return it != aValueStrings.end() ? it->second : "";
1969 static void appendStructureAttributeLine( PDFWriter::StructAttribute i_eAttr, const PDFWriterImpl::PDFStructureAttribute& i_rVal, OStringBuffer& o_rLine, bool i_bIsFixedInt )
1971 o_rLine.append( "/" );
1972 o_rLine.append( PDFWriterImpl::getAttributeTag( i_eAttr ) );
1974 if( i_rVal.eValue != PDFWriter::Invalid )
1976 o_rLine.append( "/" );
1977 o_rLine.append( PDFWriterImpl::getAttributeValueTag( i_rVal.eValue ) );
1979 else
1981 // numerical value
1982 o_rLine.append( " " );
1983 if( i_bIsFixedInt )
1984 appendFixedInt( i_rVal.nValue, o_rLine );
1985 else
1986 o_rLine.append( i_rVal.nValue );
1988 o_rLine.append( "\n" );
1991 OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle )
1993 // create layout, list and table attribute sets
1994 OStringBuffer aLayout(256), aList(64), aTable(64);
1995 for (auto const& attribute : i_rEle.m_aAttributes)
1997 if( attribute.first == PDFWriter::ListNumbering )
1998 appendStructureAttributeLine( attribute.first, attribute.second, aList, true );
1999 else if( attribute.first == PDFWriter::RowSpan ||
2000 attribute.first == PDFWriter::ColSpan )
2001 appendStructureAttributeLine( attribute.first, attribute.second, aTable, false );
2002 else if( attribute.first == PDFWriter::LinkAnnotation )
2004 sal_Int32 nLink = attribute.second.nValue;
2005 std::map< sal_Int32, sal_Int32 >::const_iterator link_it =
2006 m_aLinkPropertyMap.find( nLink );
2007 if( link_it != m_aLinkPropertyMap.end() )
2008 nLink = link_it->second;
2009 if( nLink >= 0 && nLink < static_cast<sal_Int32>(m_aLinks.size()) )
2011 // update struct parent of link
2012 OString aStructParentEntry =
2013 OString::number( i_rEle.m_nObject ) +
2014 " 0 R";
2015 m_aStructParentTree.push_back( aStructParentEntry );
2016 m_aLinks[ nLink ].m_nStructParent = m_aStructParentTree.size()-1;
2018 sal_Int32 nRefObject = createObject();
2019 if (updateObject(nRefObject))
2021 OString aRef =
2022 OString::number( nRefObject ) +
2023 " 0 obj\n"
2024 "<</Type/OBJR/Obj " +
2025 OString::number( m_aLinks[ nLink ].m_nObject ) +
2026 " 0 R>>\n"
2027 "endobj\n\n";
2028 writeBuffer( aRef.getStr(), aRef.getLength() );
2031 i_rEle.m_aKids.emplace_back( nRefObject );
2033 else
2035 OSL_FAIL( "unresolved link id for Link structure" );
2036 SAL_INFO("vcl.pdfwriter", "unresolved link id " << nLink << " for Link structure");
2037 if (g_bDebugDisableCompression)
2039 OString aLine = "unresolved link id " +
2040 OString::number( nLink ) +
2041 " for Link structure";
2042 emitComment( aLine.getStr() );
2046 else
2047 appendStructureAttributeLine( attribute.first, attribute.second, aLayout, true );
2049 if( ! i_rEle.m_aBBox.IsEmpty() )
2051 aLayout.append( "/BBox[" );
2052 appendFixedInt( i_rEle.m_aBBox.Left(), aLayout );
2053 aLayout.append( " " );
2054 appendFixedInt( i_rEle.m_aBBox.Top(), aLayout );
2055 aLayout.append( " " );
2056 appendFixedInt( i_rEle.m_aBBox.Right(), aLayout );
2057 aLayout.append( " " );
2058 appendFixedInt( i_rEle.m_aBBox.Bottom(), aLayout );
2059 aLayout.append( "]\n" );
2062 std::vector< sal_Int32 > aAttribObjects;
2063 if( !aLayout.isEmpty() )
2065 aAttribObjects.push_back( createObject() );
2066 if (updateObject( aAttribObjects.back() ))
2068 OStringBuffer aObj( 64 );
2069 aObj.append( aAttribObjects.back() );
2070 aObj.append( " 0 obj\n"
2071 "<</O/Layout\n" );
2072 aLayout.append( ">>\nendobj\n\n" );
2073 writeBuffer( aObj.getStr(), aObj.getLength() );
2074 writeBuffer( aLayout.getStr(), aLayout.getLength() );
2077 if( !aList.isEmpty() )
2079 aAttribObjects.push_back( createObject() );
2080 if (updateObject( aAttribObjects.back() ))
2082 OStringBuffer aObj( 64 );
2083 aObj.append( aAttribObjects.back() );
2084 aObj.append( " 0 obj\n"
2085 "<</O/List\n" );
2086 aList.append( ">>\nendobj\n\n" );
2087 writeBuffer( aObj.getStr(), aObj.getLength() );
2088 writeBuffer( aList.getStr(), aList.getLength() );
2091 if( !aTable.isEmpty() )
2093 aAttribObjects.push_back( createObject() );
2094 if (updateObject( aAttribObjects.back() ))
2096 OStringBuffer aObj( 64 );
2097 aObj.append( aAttribObjects.back() );
2098 aObj.append( " 0 obj\n"
2099 "<</O/Table\n" );
2100 aTable.append( ">>\nendobj\n\n" );
2101 writeBuffer( aObj.getStr(), aObj.getLength() );
2102 writeBuffer( aTable.getStr(), aTable.getLength() );
2106 OStringBuffer aRet( 64 );
2107 if( aAttribObjects.size() > 1 )
2108 aRet.append( " [" );
2109 for (auto const& attrib : aAttribObjects)
2111 aRet.append( " " );
2112 aRet.append( attrib );
2113 aRet.append( " 0 R" );
2115 if( aAttribObjects.size() > 1 )
2116 aRet.append( " ]" );
2117 return aRet.makeStringAndClear();
2120 sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle )
2123 // do not emit NonStruct and its children
2124 rEle.m_eType == PDFWriter::NonStructElement &&
2125 rEle.m_nOwnElement != rEle.m_nParentElement // but of course emit the struct tree root
2127 return 0;
2129 for (auto const& child : rEle.m_aChildren)
2131 if( child > 0 && child < sal_Int32(m_aStructure.size()) )
2133 PDFStructureElement& rChild = m_aStructure[ child ];
2134 if( rChild.m_eType != PDFWriter::NonStructElement )
2136 if( rChild.m_nParentElement == rEle.m_nOwnElement )
2137 emitStructure( rChild );
2138 else
2140 OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure element" );
2141 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure element with id " << child);
2145 else
2147 OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
2148 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure id " << child);
2152 OStringBuffer aLine( 512 );
2153 aLine.append( rEle.m_nObject );
2154 aLine.append( " 0 obj\n"
2155 "<</Type" );
2156 sal_Int32 nParentTree = -1;
2157 if( rEle.m_nOwnElement == rEle.m_nParentElement )
2159 nParentTree = createObject();
2160 CHECK_RETURN( nParentTree );
2161 aLine.append( "/StructTreeRoot\n" );
2162 aLine.append( "/ParentTree " );
2163 aLine.append( nParentTree );
2164 aLine.append( " 0 R\n" );
2165 if( ! m_aRoleMap.empty() )
2167 aLine.append( "/RoleMap<<" );
2168 for (auto const& role : m_aRoleMap)
2170 aLine.append( '/' );
2171 aLine.append(role.first);
2172 aLine.append( '/' );
2173 aLine.append( role.second );
2174 aLine.append( '\n' );
2176 aLine.append( ">>\n" );
2179 else
2181 aLine.append( "/StructElem\n"
2182 "/S/" );
2183 if( !rEle.m_aAlias.isEmpty() )
2184 aLine.append( rEle.m_aAlias );
2185 else
2186 aLine.append( getStructureTag( rEle.m_eType ) );
2187 aLine.append( "\n"
2188 "/P " );
2189 aLine.append( m_aStructure[ rEle.m_nParentElement ].m_nObject );
2190 aLine.append( " 0 R\n"
2191 "/Pg " );
2192 aLine.append( rEle.m_nFirstPageObject );
2193 aLine.append( " 0 R\n" );
2194 if( !rEle.m_aActualText.isEmpty() )
2196 aLine.append( "/ActualText" );
2197 appendUnicodeTextStringEncrypt( rEle.m_aActualText, rEle.m_nObject, aLine );
2198 aLine.append( "\n" );
2200 if( !rEle.m_aAltText.isEmpty() )
2202 aLine.append( "/Alt" );
2203 appendUnicodeTextStringEncrypt( rEle.m_aAltText, rEle.m_nObject, aLine );
2204 aLine.append( "\n" );
2207 if( (! rEle.m_aBBox.IsEmpty()) || (! rEle.m_aAttributes.empty()) )
2209 OString aAttribs = emitStructureAttributes( rEle );
2210 if( !aAttribs.isEmpty() )
2212 aLine.append( "/A" );
2213 aLine.append( aAttribs );
2214 aLine.append( "\n" );
2217 if( !rEle.m_aLocale.Language.isEmpty() )
2219 /* PDF allows only RFC 3066, which is only partly BCP 47 and does not
2220 * include script tags and others.
2221 * http://pdf.editme.com/pdfua-naturalLanguageSpecification
2222 * http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf#page=886
2223 * https://www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf#M13.9.19332.1Heading.97.Natural.Language.Specification
2224 * */
2225 LanguageTag aLanguageTag( rEle.m_aLocale);
2226 OUString aLanguage, aScript, aCountry;
2227 aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
2228 if (!aLanguage.isEmpty())
2230 OUStringBuffer aLocBuf( 16 );
2231 aLocBuf.append( aLanguage );
2232 if( !aCountry.isEmpty() )
2234 aLocBuf.append( '-' );
2235 aLocBuf.append( aCountry );
2237 aLine.append( "/Lang" );
2238 appendLiteralStringEncrypt( aLocBuf.makeStringAndClear(), rEle.m_nObject, aLine );
2239 aLine.append( "\n" );
2242 if( ! rEle.m_aKids.empty() )
2244 unsigned int i = 0;
2245 aLine.append( "/K[" );
2246 for (auto const& kid : rEle.m_aKids)
2248 if( kid.nMCID == -1 )
2250 aLine.append( kid.nObject );
2251 aLine.append( " 0 R" );
2252 aLine.append( ( (i & 15) == 15 ) ? "\n" : " " );
2254 else
2256 if( kid.nObject == rEle.m_nFirstPageObject )
2258 aLine.append( kid.nMCID );
2259 aLine.append( " " );
2261 else
2263 aLine.append( "<</Type/MCR/Pg " );
2264 aLine.append( kid.nObject );
2265 aLine.append( " 0 R /MCID " );
2266 aLine.append( kid.nMCID );
2267 aLine.append( ">>\n" );
2270 ++i;
2272 aLine.append( "]\n" );
2274 aLine.append( ">>\nendobj\n\n" );
2276 CHECK_RETURN( updateObject( rEle.m_nObject ) );
2277 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2279 CHECK_RETURN( emitStructParentTree( nParentTree ) );
2281 return rEle.m_nObject;
2284 bool PDFWriterImpl::emitGradients()
2286 for (auto const& gradient : m_aGradients)
2288 if ( !writeGradientFunction( gradient ) ) return false;
2290 return true;
2293 bool PDFWriterImpl::emitTilings()
2295 OStringBuffer aTilingObj( 1024 );
2297 for (auto & tiling : m_aTilings)
2299 SAL_WARN_IF( !tiling.m_pTilingStream, "vcl.pdfwriter", "tiling without stream" );
2300 if( ! tiling.m_pTilingStream )
2301 continue;
2303 aTilingObj.setLength( 0 );
2305 if (g_bDebugDisableCompression)
2307 emitComment( "PDFWriterImpl::emitTilings" );
2310 sal_Int32 nX = static_cast<sal_Int32>(tiling.m_aRectangle.Left());
2311 sal_Int32 nY = static_cast<sal_Int32>(tiling.m_aRectangle.Top());
2312 sal_Int32 nW = static_cast<sal_Int32>(tiling.m_aRectangle.GetWidth());
2313 sal_Int32 nH = static_cast<sal_Int32>(tiling.m_aRectangle.GetHeight());
2314 if( tiling.m_aCellSize.Width() == 0 )
2315 tiling.m_aCellSize.setWidth( nW );
2316 if( tiling.m_aCellSize.Height() == 0 )
2317 tiling.m_aCellSize.setHeight( nH );
2319 bool bDeflate = compressStream( tiling.m_pTilingStream.get() );
2320 sal_uInt64 const nTilingStreamSize = tiling.m_pTilingStream->TellEnd();
2321 tiling.m_pTilingStream->Seek( STREAM_SEEK_TO_BEGIN );
2323 // write pattern object
2324 aTilingObj.append( tiling.m_nObject );
2325 aTilingObj.append( " 0 obj\n" );
2326 aTilingObj.append( "<</Type/Pattern/PatternType 1\n"
2327 "/PaintType 1\n"
2328 "/TilingType 2\n"
2329 "/BBox[" );
2330 appendFixedInt( nX, aTilingObj );
2331 aTilingObj.append( ' ' );
2332 appendFixedInt( nY, aTilingObj );
2333 aTilingObj.append( ' ' );
2334 appendFixedInt( nX+nW, aTilingObj );
2335 aTilingObj.append( ' ' );
2336 appendFixedInt( nY+nH, aTilingObj );
2337 aTilingObj.append( "]\n"
2338 "/XStep " );
2339 appendFixedInt( tiling.m_aCellSize.Width(), aTilingObj );
2340 aTilingObj.append( "\n"
2341 "/YStep " );
2342 appendFixedInt( tiling.m_aCellSize.Height(), aTilingObj );
2343 aTilingObj.append( "\n" );
2344 if( tiling.m_aTransform.matrix[0] != 1.0 ||
2345 tiling.m_aTransform.matrix[1] != 0.0 ||
2346 tiling.m_aTransform.matrix[3] != 0.0 ||
2347 tiling.m_aTransform.matrix[4] != 1.0 ||
2348 tiling.m_aTransform.matrix[2] != 0.0 ||
2349 tiling.m_aTransform.matrix[5] != 0.0 )
2351 aTilingObj.append( "/Matrix [" );
2352 // TODO: scaling, mirroring on y, etc
2353 appendDouble( tiling.m_aTransform.matrix[0], aTilingObj );
2354 aTilingObj.append( ' ' );
2355 appendDouble( tiling.m_aTransform.matrix[1], aTilingObj );
2356 aTilingObj.append( ' ' );
2357 appendDouble( tiling.m_aTransform.matrix[3], aTilingObj );
2358 aTilingObj.append( ' ' );
2359 appendDouble( tiling.m_aTransform.matrix[4], aTilingObj );
2360 aTilingObj.append( ' ' );
2361 appendDouble( tiling.m_aTransform.matrix[2], aTilingObj );
2362 aTilingObj.append( ' ' );
2363 appendDouble( tiling.m_aTransform.matrix[5], aTilingObj );
2364 aTilingObj.append( "]\n" );
2366 aTilingObj.append( "/Resources" );
2367 tiling.m_aResources.append( aTilingObj, getFontDictObject() );
2368 if( bDeflate )
2369 aTilingObj.append( "/Filter/FlateDecode" );
2370 aTilingObj.append( "/Length " );
2371 aTilingObj.append( static_cast<sal_Int32>(nTilingStreamSize) );
2372 aTilingObj.append( ">>\nstream\n" );
2373 if ( !updateObject( tiling.m_nObject ) ) return false;
2374 if ( !writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) ) return false;
2375 checkAndEnableStreamEncryption( tiling.m_nObject );
2376 bool written = writeBuffer( tiling.m_pTilingStream->GetData(), nTilingStreamSize );
2377 tiling.m_pTilingStream.reset();
2378 if( !written )
2379 return false;
2380 disableStreamEncryption();
2381 aTilingObj.setLength( 0 );
2382 aTilingObj.append( "\nendstream\nendobj\n\n" );
2383 if ( !writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) ) return false;
2385 return true;
2388 sal_Int32 PDFWriterImpl::emitBuildinFont(const pdf::BuildinFontFace* pFD, sal_Int32 nFontObject)
2390 if( !pFD )
2391 return 0;
2392 const pdf::BuildinFont& rBuildinFont = pFD->GetBuildinFont();
2394 OStringBuffer aLine( 1024 );
2396 if( nFontObject <= 0 )
2397 nFontObject = createObject();
2398 CHECK_RETURN( updateObject( nFontObject ) );
2399 aLine.append( nFontObject );
2400 aLine.append( " 0 obj\n"
2401 "<</Type/Font/Subtype/Type1/BaseFont/" );
2402 appendName( rBuildinFont.m_pPSName, aLine );
2403 aLine.append( "\n" );
2404 if( rBuildinFont.m_eCharSet == RTL_TEXTENCODING_MS_1252 )
2405 aLine.append( "/Encoding/WinAnsiEncoding\n" );
2406 aLine.append( ">>\nendobj\n\n" );
2407 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2408 return nFontObject;
2411 std::map< sal_Int32, sal_Int32 > PDFWriterImpl::emitSystemFont( const PhysicalFontFace* pFont, EmbedFont const & rEmbed )
2413 std::map< sal_Int32, sal_Int32 > aRet;
2415 sal_Int32 nFontDescriptor = 0;
2416 OString aSubType( "/Type1" );
2417 FontSubsetInfo aInfo;
2418 // fill in dummy values
2419 aInfo.m_nAscent = 1000;
2420 aInfo.m_nDescent = 200;
2421 aInfo.m_nCapHeight = 1000;
2422 aInfo.m_aFontBBox = tools::Rectangle( Point( -200, -200 ), Size( 1700, 1700 ) );
2423 aInfo.m_aPSName = pFont->GetFamilyName();
2424 sal_Int32 pWidths[256] = {};
2426 SalGraphics *pGraphics = GetGraphics();
2428 assert(pGraphics);
2430 aSubType = OString( "/TrueType" );
2431 std::vector< sal_Int32 > aGlyphWidths;
2432 Ucs2UIntMap aUnicodeMap;
2433 pGraphics->GetGlyphWidths( pFont, false, aGlyphWidths, aUnicodeMap );
2435 OUString aTmpName;
2436 osl_createTempFile( nullptr, nullptr, &aTmpName.pData );
2437 sal_GlyphId aGlyphIds[ 256 ] = {};
2438 sal_uInt8 pEncoding[ 256 ] = {};
2439 sal_Int32 pDuWidths[ 256 ] = {};
2441 for( sal_Ucs c = 32; c < 256; c++ )
2443 pEncoding[c] = c;
2444 aGlyphIds[c] = 0;
2445 if( aUnicodeMap.find( c ) != aUnicodeMap.end() )
2446 pWidths[ c ] = aGlyphWidths[ aUnicodeMap[ c ] ];
2448 //TODO: surely this is utterly broken because aGlyphIds is just all zeros, if we
2449 //had the right glyphids here then I imagine we could replace pDuWidths with
2450 //pWidths and remove pWidths assignment above. i.e. start with the glyph ids
2451 //and map those to unicode rather than try and reverse map them ?
2452 pGraphics->CreateFontSubset( aTmpName, pFont, aGlyphIds, pEncoding, pDuWidths, 256, aInfo );
2453 osl_removeFile( aTmpName.pData );
2455 // write font descriptor
2456 nFontDescriptor = emitFontDescriptor( pFont, aInfo, 0, 0 );
2457 if( nFontDescriptor )
2459 // write font object
2460 sal_Int32 nObject = createObject();
2461 if( updateObject( nObject ) )
2463 OStringBuffer aLine( 1024 );
2464 aLine.append( nObject );
2465 aLine.append( " 0 obj\n"
2466 "<</Type/Font/Subtype" );
2467 aLine.append( aSubType );
2468 aLine.append( "/BaseFont/" );
2469 appendName( aInfo.m_aPSName, aLine );
2470 aLine.append( "\n" );
2471 if( !pFont->IsSymbolFont() )
2472 aLine.append( "/Encoding/WinAnsiEncoding\n" );
2473 aLine.append( "/FirstChar 32 /LastChar 255\n"
2474 "/Widths[" );
2475 for( int i = 32; i < 256; i++ )
2477 aLine.append( pWidths[i] );
2478 aLine.append( ((i&15) == 15) ? "\n" : " " );
2480 aLine.append( "]\n"
2481 "/FontDescriptor " );
2482 aLine.append( nFontDescriptor );
2483 aLine.append( " 0 R>>\n"
2484 "endobj\n\n" );
2485 writeBuffer( aLine.getStr(), aLine.getLength() );
2487 aRet[ rEmbed.m_nNormalFontID ] = nObject;
2491 return aRet;
2494 typedef int ThreeInts[3];
2495 static bool getPfbSegmentLengths( const unsigned char* pFontBytes, int nByteLen,
2496 ThreeInts& rSegmentLengths )
2498 if( !pFontBytes || (nByteLen < 0) )
2499 return false;
2500 const unsigned char* pPtr = pFontBytes;
2501 const unsigned char* pEnd = pFontBytes + nByteLen;
2503 for(int & rSegmentLength : rSegmentLengths) {
2504 // read segment1 header
2505 if( pPtr+6 >= pEnd )
2506 return false;
2507 if( (pPtr[0] != 0x80) || (pPtr[1] >= 0x03) )
2508 return false;
2509 const int nLen = (pPtr[5]<<24) + (pPtr[4]<<16) + (pPtr[3]<<8) + pPtr[2];
2510 if( nLen <= 0)
2511 return false;
2512 rSegmentLength = nLen;
2513 pPtr += nLen + 6;
2516 // read segment-end header
2517 if( pPtr+2 >= pEnd )
2518 return false;
2519 if( (pPtr[0] != 0x80) || (pPtr[1] != 0x03) )
2520 return false;
2522 return true;
2525 static void appendSubsetName( int nSubsetID, const OUString& rPSName, OStringBuffer& rBuffer )
2527 if( nSubsetID )
2529 for( int i = 0; i < 6; i++ )
2531 int nOffset = nSubsetID % 26;
2532 nSubsetID /= 26;
2533 rBuffer.append( static_cast<sal_Char>('A'+nOffset) );
2535 rBuffer.append( '+' );
2537 appendName( rPSName, rBuffer );
2540 sal_Int32 PDFWriterImpl::createToUnicodeCMap( sal_uInt8 const * pEncoding,
2541 const sal_Ucs* pCodeUnits,
2542 const sal_Int32* pCodeUnitsPerGlyph,
2543 const sal_Int32* pEncToUnicodeIndex,
2544 int nGlyphs )
2546 int nMapped = 0;
2547 for (int n = 0; n < nGlyphs; ++n)
2548 if( pCodeUnits[pEncToUnicodeIndex[n]] && pCodeUnitsPerGlyph[n] )
2549 nMapped++;
2551 if( nMapped == 0 )
2552 return 0;
2554 sal_Int32 nStream = createObject();
2555 CHECK_RETURN( updateObject( nStream ) );
2557 OStringBuffer aContents( 1024 );
2558 aContents.append(
2559 "/CIDInit/ProcSet findresource begin\n"
2560 "12 dict begin\n"
2561 "begincmap\n"
2562 "/CIDSystemInfo<<\n"
2563 "/Registry (Adobe)\n"
2564 "/Ordering (UCS)\n"
2565 "/Supplement 0\n"
2566 ">> def\n"
2567 "/CMapName/Adobe-Identity-UCS def\n"
2568 "/CMapType 2 def\n"
2569 "1 begincodespacerange\n"
2570 "<00> <FF>\n"
2571 "endcodespacerange\n"
2573 int nCount = 0;
2574 for (int n = 0; n < nGlyphs; ++n)
2576 if( pCodeUnits[pEncToUnicodeIndex[n]] && pCodeUnitsPerGlyph[n] )
2578 if( (nCount % 100) == 0 )
2580 if( nCount )
2581 aContents.append( "endbfchar\n" );
2582 aContents.append( static_cast<sal_Int32>(std::min(nMapped-nCount, 100)) );
2583 aContents.append( " beginbfchar\n" );
2585 aContents.append( '<' );
2586 appendHex( static_cast<sal_Int8>(pEncoding[n]), aContents );
2587 aContents.append( "> <" );
2588 // TODO: handle code points>U+FFFF
2589 sal_Int32 nIndex = pEncToUnicodeIndex[n];
2590 for( sal_Int32 j = 0; j < pCodeUnitsPerGlyph[n]; j++ )
2592 appendHex( static_cast<sal_Int8>(pCodeUnits[nIndex + j] / 256), aContents );
2593 appendHex( static_cast<sal_Int8>(pCodeUnits[nIndex + j] & 255), aContents );
2595 aContents.append( ">\n" );
2596 nCount++;
2599 aContents.append( "endbfchar\n"
2600 "endcmap\n"
2601 "CMapName currentdict /CMap defineresource pop\n"
2602 "end\n"
2603 "end\n" );
2604 SvMemoryStream aStream;
2605 if (!g_bDebugDisableCompression)
2607 ZCodec aCodec( 0x4000, 0x4000 );
2608 aCodec.BeginCompression();
2609 aCodec.Write( aStream, reinterpret_cast<const sal_uInt8*>(aContents.getStr()), aContents.getLength() );
2610 aCodec.EndCompression();
2613 if (g_bDebugDisableCompression)
2615 emitComment( "PDFWriterImpl::createToUnicodeCMap" );
2617 OStringBuffer aLine( 40 );
2619 aLine.append( nStream );
2620 aLine.append( " 0 obj\n<</Length " );
2621 sal_Int32 nLen = 0;
2622 if (!g_bDebugDisableCompression)
2624 nLen = static_cast<sal_Int32>(aStream.Tell());
2625 aStream.Seek( 0 );
2626 aLine.append( nLen );
2627 aLine.append( "/Filter/FlateDecode" );
2629 else
2630 aLine.append( aContents.getLength() );
2631 aLine.append( ">>\nstream\n" );
2632 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2633 checkAndEnableStreamEncryption( nStream );
2634 if (!g_bDebugDisableCompression)
2636 CHECK_RETURN( writeBuffer( aStream.GetData(), nLen ) );
2638 else
2640 CHECK_RETURN( writeBuffer( aContents.getStr(), aContents.getLength() ) );
2642 disableStreamEncryption();
2643 aLine.setLength( 0 );
2644 aLine.append( "\nendstream\n"
2645 "endobj\n\n" );
2646 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2647 return nStream;
2650 sal_Int32 PDFWriterImpl::emitFontDescriptor( const PhysicalFontFace* pFont, FontSubsetInfo const & rInfo, sal_Int32 nSubsetID, sal_Int32 nFontStream )
2652 OStringBuffer aLine( 1024 );
2653 // get font flags, see PDF reference 1.4 p. 358
2654 // possibly characters outside Adobe standard encoding
2655 // so set Symbolic flag
2656 sal_Int32 nFontFlags = (1<<2);
2657 if( pFont->GetItalic() == ITALIC_NORMAL || pFont->GetItalic() == ITALIC_OBLIQUE )
2658 nFontFlags |= (1 << 6);
2659 if( pFont->GetPitch() == PITCH_FIXED )
2660 nFontFlags |= 1;
2661 if( pFont->GetFamilyType() == FAMILY_SCRIPT )
2662 nFontFlags |= (1 << 3);
2663 else if( pFont->GetFamilyType() == FAMILY_ROMAN )
2664 nFontFlags |= (1 << 1);
2666 sal_Int32 nFontDescriptor = createObject();
2667 CHECK_RETURN( updateObject( nFontDescriptor ) );
2668 aLine.setLength( 0 );
2669 aLine.append( nFontDescriptor );
2670 aLine.append( " 0 obj\n"
2671 "<</Type/FontDescriptor/FontName/" );
2672 appendSubsetName( nSubsetID, rInfo.m_aPSName, aLine );
2673 aLine.append( "\n"
2674 "/Flags " );
2675 aLine.append( nFontFlags );
2676 aLine.append( "\n"
2677 "/FontBBox[" );
2678 // note: Top and Bottom are reversed in VCL and PDF rectangles
2679 aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.TopLeft().X()) );
2680 aLine.append( ' ' );
2681 aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.TopLeft().Y()) );
2682 aLine.append( ' ' );
2683 aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.BottomRight().X()) );
2684 aLine.append( ' ' );
2685 aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.BottomRight().Y()+1) );
2686 aLine.append( "]/ItalicAngle " );
2687 if( pFont->GetItalic() == ITALIC_OBLIQUE || pFont->GetItalic() == ITALIC_NORMAL )
2688 aLine.append( "-30" );
2689 else
2690 aLine.append( "0" );
2691 aLine.append( "\n"
2692 "/Ascent " );
2693 aLine.append( static_cast<sal_Int32>(rInfo.m_nAscent) );
2694 aLine.append( "\n"
2695 "/Descent " );
2696 aLine.append( static_cast<sal_Int32>(-rInfo.m_nDescent) );
2697 aLine.append( "\n"
2698 "/CapHeight " );
2699 aLine.append( static_cast<sal_Int32>(rInfo.m_nCapHeight) );
2700 // According to PDF reference 1.4 StemV is required
2701 // seems a tad strange to me, but well ...
2702 aLine.append( "\n"
2703 "/StemV 80\n" );
2704 if( nFontStream )
2706 aLine.append( "/FontFile" );
2707 switch( rInfo.m_nFontType )
2709 case FontType::SFNT_TTF:
2710 aLine.append( '2' );
2711 break;
2712 case FontType::TYPE1_PFA:
2713 case FontType::TYPE1_PFB:
2714 case FontType::ANY_TYPE1:
2715 break;
2716 default:
2717 OSL_FAIL( "unknown fonttype in PDF font descriptor" );
2718 return 0;
2720 aLine.append( ' ' );
2721 aLine.append( nFontStream );
2722 aLine.append( " 0 R\n" );
2724 aLine.append( ">>\n"
2725 "endobj\n\n" );
2726 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2728 return nFontDescriptor;
2731 void PDFWriterImpl::appendBuildinFontsToDict( OStringBuffer& rDict ) const
2733 for (auto const& item : m_aBuildinFontToObjectMap)
2735 rDict.append( pdf::BuildinFontFace::Get(item.first).getNameObject() );
2736 rDict.append( ' ' );
2737 rDict.append( item.second );
2738 rDict.append( " 0 R" );
2742 bool PDFWriterImpl::emitFonts()
2744 SalGraphics *pGraphics = GetGraphics();
2746 if (!pGraphics)
2747 return false;
2749 OStringBuffer aLine( 1024 );
2751 std::map< sal_Int32, sal_Int32 > aFontIDToObject;
2753 OUString aTmpName;
2754 osl_createTempFile( nullptr, nullptr, &aTmpName.pData );
2755 for (const auto & subset : m_aSubsets)
2757 for (auto & s_subset :subset.second.m_aSubsets)
2759 sal_GlyphId aGlyphIds[ 256 ] = {};
2760 sal_Int32 pWidths[ 256 ];
2761 sal_uInt8 pEncoding[ 256 ] = {};
2762 sal_Int32 pEncToUnicodeIndex[ 256 ] = {};
2763 sal_Int32 pCodeUnitsPerGlyph[ 256 ] = {};
2764 std::vector<sal_Ucs> aCodeUnits;
2765 aCodeUnits.reserve( 256 );
2766 int nGlyphs = 1;
2767 // fill arrays and prepare encoding index map
2768 sal_Int32 nToUnicodeStream = 0;
2770 for (auto const& item : s_subset.m_aMapping)
2772 sal_uInt8 nEnc = item.second.getGlyphId();
2774 SAL_WARN_IF( aGlyphIds[nEnc] != 0 || pEncoding[nEnc] != 0, "vcl.pdfwriter", "duplicate glyph" );
2775 SAL_WARN_IF( nEnc > s_subset.m_aMapping.size(), "vcl.pdfwriter", "invalid glyph encoding" );
2777 aGlyphIds[ nEnc ] = item.first;
2778 pEncoding[ nEnc ] = nEnc;
2779 pEncToUnicodeIndex[ nEnc ] = static_cast<sal_Int32>(aCodeUnits.size());
2780 pCodeUnitsPerGlyph[ nEnc ] = item.second.countCodes();
2781 for( sal_Int32 n = 0; n < pCodeUnitsPerGlyph[ nEnc ]; n++ )
2782 aCodeUnits.push_back( item.second.getCode( n ) );
2783 if( item.second.getCode(0) )
2784 nToUnicodeStream = 1;
2785 if( nGlyphs < 256 )
2786 nGlyphs++;
2787 else
2789 OSL_FAIL( "too many glyphs for subset" );
2792 FontSubsetInfo aSubsetInfo;
2793 if( pGraphics->CreateFontSubset( aTmpName, subset.first, aGlyphIds, pEncoding, pWidths, nGlyphs, aSubsetInfo ) )
2795 // create font stream
2796 osl::File aFontFile(aTmpName);
2797 if (osl::File::E_None != aFontFile.open(osl_File_OpenFlag_Read)) return false;
2798 // get file size
2799 sal_uInt64 nLength1;
2800 if ( osl::File::E_None != aFontFile.setPos(osl_Pos_End, 0) ) return false;
2801 if ( osl::File::E_None != aFontFile.getPos(nLength1) ) return false;
2802 if ( osl::File::E_None != aFontFile.setPos(osl_Pos_Absolut, 0) ) return false;
2804 if (g_bDebugDisableCompression)
2806 emitComment( "PDFWriterImpl::emitFonts" );
2808 sal_Int32 nFontStream = createObject();
2809 sal_Int32 nStreamLengthObject = createObject();
2810 if ( !updateObject( nFontStream ) ) return false;
2811 aLine.setLength( 0 );
2812 aLine.append( nFontStream );
2813 aLine.append( " 0 obj\n"
2814 "<</Length " );
2815 aLine.append( nStreamLengthObject );
2816 if (!g_bDebugDisableCompression)
2817 aLine.append( " 0 R"
2818 "/Filter/FlateDecode"
2819 "/Length1 " );
2820 else
2821 aLine.append( " 0 R"
2822 "/Length1 " );
2824 sal_uInt64 nStartPos = 0;
2825 if( aSubsetInfo.m_nFontType == FontType::SFNT_TTF )
2827 aLine.append( static_cast<sal_Int32>(nLength1) );
2829 aLine.append( ">>\n"
2830 "stream\n" );
2831 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
2832 if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
2834 // copy font file
2835 beginCompression();
2836 checkAndEnableStreamEncryption( nFontStream );
2837 sal_Bool bEOF = false;
2840 char buf[8192];
2841 sal_uInt64 nRead;
2842 if ( osl::File::E_None != aFontFile.read(buf, sizeof(buf), nRead) ) return false;
2843 if ( !writeBuffer( buf, nRead ) ) return false;
2844 if ( osl::File::E_None != aFontFile.isEndOfFile(&bEOF) ) return false;
2845 } while( ! bEOF );
2847 else if( aSubsetInfo.m_nFontType & FontType::CFF_FONT)
2849 // TODO: implement
2850 OSL_FAIL( "PDFWriterImpl does not support CFF-font subsets yet!" );
2852 else if( aSubsetInfo.m_nFontType & FontType::TYPE1_PFB) // TODO: also support PFA?
2854 std::unique_ptr<unsigned char[]> xBuffer(new unsigned char[nLength1]);
2856 sal_uInt64 nBytesRead = 0;
2857 if ( osl::File::E_None != aFontFile.read(xBuffer.get(), nLength1, nBytesRead) ) return false;
2858 SAL_WARN_IF( nBytesRead!=nLength1, "vcl.pdfwriter", "PDF-FontSubset read incomplete!" );
2859 if ( osl::File::E_None != aFontFile.setPos(osl_Pos_Absolut, 0) ) return false;
2860 // get the PFB-segment lengths
2861 ThreeInts aSegmentLengths = {0,0,0};
2862 getPfbSegmentLengths(xBuffer.get(), static_cast<int>(nBytesRead), aSegmentLengths);
2863 // the lengths below are mandatory for PDF-exported Type1 fonts
2864 // because the PFB segment headers get stripped! WhyOhWhy.
2865 aLine.append( static_cast<sal_Int32>(aSegmentLengths[0]) );
2866 aLine.append( "/Length2 " );
2867 aLine.append( static_cast<sal_Int32>(aSegmentLengths[1]) );
2868 aLine.append( "/Length3 " );
2869 aLine.append( static_cast<sal_Int32>(aSegmentLengths[2]) );
2871 aLine.append( ">>\n"
2872 "stream\n" );
2873 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
2874 if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
2876 // emit PFB-sections without section headers
2877 beginCompression();
2878 checkAndEnableStreamEncryption( nFontStream );
2879 if ( !writeBuffer( &xBuffer[6], aSegmentLengths[0] ) ) return false;
2880 if ( !writeBuffer( &xBuffer[12] + aSegmentLengths[0], aSegmentLengths[1] ) ) return false;
2881 if ( !writeBuffer( &xBuffer[18] + aSegmentLengths[0] + aSegmentLengths[1], aSegmentLengths[2] ) ) return false;
2883 else
2885 SAL_INFO("vcl.pdfwriter", "PDF: CreateFontSubset result in not yet supported format=" << static_cast<int>(aSubsetInfo.m_nFontType));
2886 aLine.append( "0 >>\nstream\n" );
2889 endCompression();
2890 disableStreamEncryption();
2891 // close the file
2892 aFontFile.close();
2894 sal_uInt64 nEndPos = 0;
2895 if ( osl::File::E_None != m_aFile.getPos(nEndPos) ) return false;
2896 // end the stream
2897 aLine.setLength( 0 );
2898 aLine.append( "\nendstream\nendobj\n\n" );
2899 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
2901 // emit stream length object
2902 if ( !updateObject( nStreamLengthObject ) ) return false;
2903 aLine.setLength( 0 );
2904 aLine.append( nStreamLengthObject );
2905 aLine.append( " 0 obj\n" );
2906 aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
2907 aLine.append( "\nendobj\n\n" );
2908 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
2910 // write font descriptor
2911 sal_Int32 nFontDescriptor = emitFontDescriptor( subset.first, aSubsetInfo, s_subset.m_nFontID, nFontStream );
2913 if( nToUnicodeStream )
2914 nToUnicodeStream = createToUnicodeCMap( pEncoding, aCodeUnits.data(), pCodeUnitsPerGlyph, pEncToUnicodeIndex, nGlyphs );
2916 sal_Int32 nFontObject = createObject();
2917 if ( !updateObject( nFontObject ) ) return false;
2918 aLine.setLength( 0 );
2919 aLine.append( nFontObject );
2921 aLine.append( " 0 obj\n" );
2922 aLine.append( (aSubsetInfo.m_nFontType & FontType::ANY_TYPE1) ?
2923 "<</Type/Font/Subtype/Type1/BaseFont/" :
2924 "<</Type/Font/Subtype/TrueType/BaseFont/" );
2925 appendSubsetName( s_subset.m_nFontID, aSubsetInfo.m_aPSName, aLine );
2926 aLine.append( "\n"
2927 "/FirstChar 0\n"
2928 "/LastChar " );
2929 aLine.append( static_cast<sal_Int32>(nGlyphs-1) );
2930 aLine.append( "\n"
2931 "/Widths[" );
2932 for( int i = 0; i < nGlyphs; i++ )
2934 aLine.append( pWidths[ i ] );
2935 aLine.append( ((i & 15) == 15) ? "\n" : " " );
2937 aLine.append( "]\n"
2938 "/FontDescriptor " );
2939 aLine.append( nFontDescriptor );
2940 aLine.append( " 0 R\n" );
2941 if( nToUnicodeStream )
2943 aLine.append( "/ToUnicode " );
2944 aLine.append( nToUnicodeStream );
2945 aLine.append( " 0 R\n" );
2947 aLine.append( ">>\n"
2948 "endobj\n\n" );
2949 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
2951 aFontIDToObject[ s_subset.m_nFontID ] = nFontObject;
2953 else
2955 const PhysicalFontFace* pFont = subset.first;
2956 OStringBuffer aErrorComment( 256 );
2957 aErrorComment.append( "CreateFontSubset failed for font \"" );
2958 aErrorComment.append( OUStringToOString( pFont->GetFamilyName(), RTL_TEXTENCODING_UTF8 ) );
2959 aErrorComment.append( '\"' );
2960 if( pFont->GetItalic() == ITALIC_NORMAL )
2961 aErrorComment.append( " italic" );
2962 else if( pFont->GetItalic() == ITALIC_OBLIQUE )
2963 aErrorComment.append( " oblique" );
2964 aErrorComment.append( " weight=" );
2965 aErrorComment.append( sal_Int32(pFont->GetWeight()) );
2966 emitComment( aErrorComment.getStr() );
2970 osl_removeFile( aTmpName.pData );
2972 // emit system fonts
2973 for (auto const& systemFont : m_aSystemFonts)
2975 std::map< sal_Int32, sal_Int32 > aObjects = emitSystemFont( systemFont.first, systemFont.second );
2976 for (auto const& item : aObjects)
2978 if ( !item.second ) return false;
2979 aFontIDToObject[ item.first ] = item.second;
2983 OStringBuffer aFontDict( 1024 );
2984 aFontDict.append( getFontDictObject() );
2985 aFontDict.append( " 0 obj\n"
2986 "<<" );
2987 int ni = 0;
2988 for (auto const& itemMap : aFontIDToObject)
2990 aFontDict.append( "/F" );
2991 aFontDict.append( itemMap.first );
2992 aFontDict.append( ' ' );
2993 aFontDict.append( itemMap.second );
2994 aFontDict.append( " 0 R" );
2995 if( ((++ni) & 7) == 0 )
2996 aFontDict.append( '\n' );
2998 // emit builtin font for widget appearances / variable text
2999 for (auto & item : m_aBuildinFontToObjectMap)
3001 rtl::Reference<pdf::BuildinFontFace> aData(new pdf::BuildinFontFace(item.first));
3002 item.second = emitBuildinFont( aData.get(), item.second );
3005 appendBuildinFontsToDict(aFontDict);
3006 aFontDict.append( "\n>>\nendobj\n\n" );
3008 if ( !updateObject( getFontDictObject() ) ) return false;
3009 if ( !writeBuffer( aFontDict.getStr(), aFontDict.getLength() ) ) return false;
3010 return true;
3013 sal_Int32 PDFWriterImpl::emitResources()
3015 // emit shadings
3016 if( ! m_aGradients.empty() )
3017 CHECK_RETURN( emitGradients() );
3018 // emit tilings
3019 if( ! m_aTilings.empty() )
3020 CHECK_RETURN( emitTilings() );
3022 // emit font dict
3023 CHECK_RETURN( emitFonts() );
3025 // emit Resource dict
3026 OStringBuffer aLine( 512 );
3027 sal_Int32 nResourceDict = getResourceDictObj();
3028 CHECK_RETURN( updateObject( nResourceDict ) );
3029 aLine.setLength( 0 );
3030 aLine.append( nResourceDict );
3031 aLine.append( " 0 obj\n" );
3032 m_aGlobalResourceDict.append( aLine, getFontDictObject() );
3033 aLine.append( "endobj\n\n" );
3034 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3035 return nResourceDict;
3038 sal_Int32 PDFWriterImpl::updateOutlineItemCount( std::vector< sal_Int32 >& rCounts,
3039 sal_Int32 nItemLevel,
3040 sal_Int32 nCurrentItemId )
3042 /* The /Count number of an item is
3043 positive: the number of visible subitems
3044 negative: the negative number of subitems that will become visible if
3045 the item gets opened
3046 see PDF ref 1.4 p 478
3049 sal_Int32 nCount = 0;
3051 if( m_aContext.OpenBookmarkLevels < 0 || // all levels are visible
3052 m_aContext.OpenBookmarkLevels >= nItemLevel // this level is visible
3055 PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
3056 sal_Int32 nChildren = rItem.m_aChildren.size();
3057 for( sal_Int32 i = 0; i < nChildren; i++ )
3058 nCount += updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
3059 rCounts[nCurrentItemId] = nCount;
3060 // return 1 (this item) + visible sub items
3061 if( nCount < 0 )
3062 nCount = 0;
3063 nCount++;
3065 else
3067 // this bookmark level is invisible
3068 PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
3069 sal_Int32 nChildren = rItem.m_aChildren.size();
3070 rCounts[ nCurrentItemId ] = -sal_Int32(rItem.m_aChildren.size());
3071 for( sal_Int32 i = 0; i < nChildren; i++ )
3072 updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
3073 nCount = -1;
3076 return nCount;
3079 sal_Int32 PDFWriterImpl::emitOutline()
3081 int i, nItems = m_aOutline.size();
3083 // do we have an outline at all ?
3084 if( nItems < 2 )
3085 return 0;
3087 // reserve object numbers for all outline items
3088 for( i = 0; i < nItems; ++i )
3089 m_aOutline[i].m_nObject = createObject();
3091 // update all parent, next and prev object ids
3092 for( i = 0; i < nItems; ++i )
3094 PDFOutlineEntry& rItem = m_aOutline[i];
3095 int nChildren = rItem.m_aChildren.size();
3097 if( nChildren )
3099 for( int n = 0; n < nChildren; ++n )
3101 PDFOutlineEntry& rChild = m_aOutline[ rItem.m_aChildren[n] ];
3103 rChild.m_nParentObject = rItem.m_nObject;
3104 rChild.m_nPrevObject = (n > 0) ? m_aOutline[ rItem.m_aChildren[n-1] ].m_nObject : 0;
3105 rChild.m_nNextObject = (n < nChildren-1) ? m_aOutline[ rItem.m_aChildren[n+1] ].m_nObject : 0;
3111 // calculate Count entries for all items
3112 std::vector< sal_Int32 > aCounts( nItems );
3113 updateOutlineItemCount( aCounts, 0, 0 );
3115 // emit hierarchy
3116 for( i = 0; i < nItems; ++i )
3118 PDFOutlineEntry& rItem = m_aOutline[i];
3119 OStringBuffer aLine( 1024 );
3121 CHECK_RETURN( updateObject( rItem.m_nObject ) );
3122 aLine.append( rItem.m_nObject );
3123 aLine.append( " 0 obj\n" );
3124 aLine.append( "<<" );
3125 // number of visible children (all levels)
3126 if( i > 0 || aCounts[0] > 0 )
3128 aLine.append( "/Count " );
3129 aLine.append( aCounts[i] );
3131 if( ! rItem.m_aChildren.empty() )
3133 // children list: First, Last
3134 aLine.append( "/First " );
3135 aLine.append( m_aOutline[rItem.m_aChildren.front()].m_nObject );
3136 aLine.append( " 0 R/Last " );
3137 aLine.append( m_aOutline[rItem.m_aChildren.back()].m_nObject );
3138 aLine.append( " 0 R\n" );
3140 if( i > 0 )
3142 // Title, Dest, Parent, Prev, Next
3143 aLine.append( "/Title" );
3144 appendUnicodeTextStringEncrypt( rItem.m_aTitle, rItem.m_nObject, aLine );
3145 aLine.append( "\n" );
3146 // Dest is not required
3147 if( rItem.m_nDestID >= 0 && rItem.m_nDestID < static_cast<sal_Int32>(m_aDests.size()) )
3149 aLine.append( "/Dest" );
3150 appendDest( rItem.m_nDestID, aLine );
3152 aLine.append( "/Parent " );
3153 aLine.append( rItem.m_nParentObject );
3154 aLine.append( " 0 R" );
3155 if( rItem.m_nPrevObject )
3157 aLine.append( "/Prev " );
3158 aLine.append( rItem.m_nPrevObject );
3159 aLine.append( " 0 R" );
3161 if( rItem.m_nNextObject )
3163 aLine.append( "/Next " );
3164 aLine.append( rItem.m_nNextObject );
3165 aLine.append( " 0 R" );
3168 aLine.append( ">>\nendobj\n\n" );
3169 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3172 return m_aOutline[0].m_nObject;
3175 #undef CHECK_RETURN
3176 #define CHECK_RETURN( x ) if( !x ) return false
3178 bool PDFWriterImpl::appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer )
3180 if( nDestID < 0 || nDestID >= static_cast<sal_Int32>(m_aDests.size()) )
3182 SAL_INFO("vcl.pdfwriter", "ERROR: invalid dest " << static_cast<int>(nDestID) << " requested");
3183 return false;
3186 const PDFDest& rDest = m_aDests[ nDestID ];
3187 const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ];
3189 rBuffer.append( '[' );
3190 rBuffer.append( rDestPage.m_nPageObject );
3191 rBuffer.append( " 0 R" );
3193 switch( rDest.m_eType )
3195 case PDFWriter::DestAreaType::XYZ:
3196 default:
3197 rBuffer.append( "/XYZ " );
3198 appendFixedInt( rDest.m_aRect.Left(), rBuffer );
3199 rBuffer.append( ' ' );
3200 appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
3201 rBuffer.append( " 0" );
3202 break;
3203 case PDFWriter::DestAreaType::FitRectangle:
3204 rBuffer.append( "/FitR " );
3205 appendFixedInt( rDest.m_aRect.Left(), rBuffer );
3206 rBuffer.append( ' ' );
3207 appendFixedInt( rDest.m_aRect.Top(), rBuffer );
3208 rBuffer.append( ' ' );
3209 appendFixedInt( rDest.m_aRect.Right(), rBuffer );
3210 rBuffer.append( ' ' );
3211 appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
3212 break;
3214 rBuffer.append( ']' );
3216 return true;
3219 bool PDFWriterImpl::emitScreenAnnotations()
3221 int nAnnots = m_aScreens.size();
3222 for (int i = 0; i < nAnnots; i++)
3224 const PDFScreen& rScreen = m_aScreens[i];
3226 OStringBuffer aLine;
3227 bool bEmbed = false;
3228 if (!rScreen.m_aTempFileURL.isEmpty())
3230 bEmbed = true;
3231 if (!updateObject(rScreen.m_nTempFileObject))
3232 continue;
3234 SvFileStream aFileStream(rScreen.m_aTempFileURL, StreamMode::READ);
3235 SvMemoryStream aMemoryStream;
3236 aMemoryStream.WriteStream(aFileStream);
3238 aLine.append(rScreen.m_nTempFileObject);
3239 aLine.append(" 0 obj\n");
3240 aLine.append("<< /Type /EmbeddedFile /Length ");
3241 aLine.append(static_cast<sal_Int64>(aMemoryStream.GetSize()));
3242 aLine.append(" >>\nstream\n");
3243 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
3244 aLine.setLength(0);
3246 CHECK_RETURN(writeBuffer(aMemoryStream.GetData(), aMemoryStream.GetSize()));
3248 aLine.append("\nendstream\nendobj\n\n");
3249 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
3250 aLine.setLength(0);
3253 if (!updateObject(rScreen.m_nObject))
3254 continue;
3256 // Annot dictionary.
3257 aLine.append(rScreen.m_nObject);
3258 aLine.append(" 0 obj\n");
3259 aLine.append("<</Type/Annot");
3260 aLine.append("/Subtype/Screen/Rect[");
3261 appendFixedInt(rScreen.m_aRect.Left(), aLine);
3262 aLine.append(' ');
3263 appendFixedInt(rScreen.m_aRect.Top(), aLine);
3264 aLine.append(' ');
3265 appendFixedInt(rScreen.m_aRect.Right(), aLine);
3266 aLine.append(' ');
3267 appendFixedInt(rScreen.m_aRect.Bottom(), aLine);
3268 aLine.append("]");
3270 // Action dictionary.
3271 aLine.append("/A<</Type/Action /S/Rendition /AN ");
3272 aLine.append(rScreen.m_nObject);
3273 aLine.append(" 0 R ");
3275 // Rendition dictionary.
3276 aLine.append("/R<</Type/Rendition /S/MR ");
3278 // MediaClip dictionary.
3279 aLine.append("/C<</Type/MediaClip /S/MCD ");
3280 if (bEmbed)
3282 aLine.append("/D << /Type /Filespec /F (<embedded file>) /EF << /F ");
3283 aLine.append(rScreen.m_nTempFileObject);
3284 aLine.append(" 0 R >> >>");
3286 else
3288 // Linked.
3289 aLine.append("/D << /Type /Filespec /FS /URL /F ");
3290 appendLiteralStringEncrypt(rScreen.m_aURL, rScreen.m_nObject, aLine, osl_getThreadTextEncoding());
3291 aLine.append(" >>");
3293 // Allow playing the video via a tempfile.
3294 aLine.append("/P <</TF (TEMPACCESS)>>");
3295 // Until the real MIME type (instead of application/vnd.sun.star.media) is available here.
3296 aLine.append("/CT (video/mp4)");
3297 aLine.append(">>");
3299 // End Rendition dictionary by requesting play/pause/stop controls.
3300 aLine.append("/P<</BE<</C true >>>>");
3301 aLine.append(">>");
3303 // End Action dictionary.
3304 aLine.append("/OP 0 >>");
3306 // End Annot dictionary.
3307 aLine.append("/P ");
3308 aLine.append(m_aPages[rScreen.m_nPage].m_nPageObject);
3309 aLine.append(" 0 R\n>>\nendobj\n\n");
3310 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
3313 return true;
3316 bool PDFWriterImpl::emitLinkAnnotations()
3318 int nAnnots = m_aLinks.size();
3319 for( int i = 0; i < nAnnots; i++ )
3321 const PDFLink& rLink = m_aLinks[i];
3322 if( ! updateObject( rLink.m_nObject ) )
3323 continue;
3325 OStringBuffer aLine( 1024 );
3326 aLine.append( rLink.m_nObject );
3327 aLine.append( " 0 obj\n" );
3328 // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
3329 // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
3330 aLine.append( "<</Type/Annot" );
3331 if( m_bIsPDF_A1 || m_bIsPDF_A2 )
3332 aLine.append( "/F 4" );
3333 aLine.append( "/Subtype/Link/Border[0 0 0]/Rect[" );
3335 appendFixedInt( rLink.m_aRect.Left()-7, aLine );//the +7 to have a better shape of the border rectangle
3336 aLine.append( ' ' );
3337 appendFixedInt( rLink.m_aRect.Top(), aLine );
3338 aLine.append( ' ' );
3339 appendFixedInt( rLink.m_aRect.Right()+7, aLine );//the +7 to have a better shape of the border rectangle
3340 aLine.append( ' ' );
3341 appendFixedInt( rLink.m_aRect.Bottom(), aLine );
3342 aLine.append( "]" );
3343 if( rLink.m_nDest >= 0 )
3345 aLine.append( "/Dest" );
3346 appendDest( rLink.m_nDest, aLine );
3348 else
3351 destination is external to the document, so
3352 we check in the following sequence:
3354 if target type is neither .pdf, nor .od[tpgs], then
3355 check if relative or absolute and act accordingly (use URI or 'launch application' as requested)
3356 end processing
3357 else if target is .od[tpgs]: then
3358 if conversion of type from od[tpgs] to pdf is requested, convert it and this becomes the new target file
3359 processing continue
3361 if (new)target is .pdf : then
3362 if GotToR is requested, then
3363 convert the target in GoToR where the fragment of the URI is
3364 considered the named destination in the target file, set relative or absolute as requested
3365 else strip the fragment from URL and then set URI or 'launch application' as requested
3368 // FIXME: check if the decode mechanisms for URL processing throughout this implementation
3369 // are the correct one!!
3371 // extract target file type
3372 auto url(URIHelper::resolveIdnaHost(rLink.m_aURL));
3374 INetURLObject aDocumentURL( m_aContext.BaseURL );
3375 INetURLObject aTargetURL( url );
3376 bool bSetGoToRMode = false;
3377 bool bTargetHasPDFExtension = false;
3378 INetProtocol eTargetProtocol = aTargetURL.GetProtocol();
3379 bool bIsUNCPath = false;
3381 // check if the protocol is a known one, or if there is no protocol at all (on target only)
3382 // if there is no protocol, make the target relative to the current document directory
3383 // getting the needed URL information from the current document path
3384 if( eTargetProtocol == INetProtocol::NotValid )
3386 if( url.getLength() > 4 && url.startsWith("\\\\\\\\"))
3388 bIsUNCPath = true;
3390 else
3392 INetURLObject aNewBase( aDocumentURL );//duplicate document URL
3393 aNewBase.removeSegment(); //remove last segment from it, obtaining the base URL of the
3394 //target document
3395 aNewBase.insertName( url );
3396 aTargetURL = aNewBase;//reassign the new target URL
3397 //recompute the target protocol, with the new URL
3398 //normal URL processing resumes
3399 eTargetProtocol = aTargetURL.GetProtocol();
3403 OUString aFileExtension = aTargetURL.GetFileExtension();
3405 // Check if the URL ends in '/': if yes it's a directory,
3406 // it will be forced to a URI link.
3407 // possibly a malformed URI, leave it as it is, force as URI
3408 if( aTargetURL.hasFinalSlash() )
3409 m_aContext.DefaultLinkAction = PDFWriter::URIAction;
3411 if( !aFileExtension.isEmpty() )
3413 if( m_aContext.ConvertOOoTargetToPDFTarget )
3415 bool bChangeFileExtensionToPDF = false;
3416 //examine the file type (.odm .odt. .odp, odg, ods)
3417 if( aFileExtension.equalsIgnoreAsciiCase( "odm" ) )
3418 bChangeFileExtensionToPDF = true;
3419 if( aFileExtension.equalsIgnoreAsciiCase( "odt" ) )
3420 bChangeFileExtensionToPDF = true;
3421 else if( aFileExtension.equalsIgnoreAsciiCase( "odp" ) )
3422 bChangeFileExtensionToPDF = true;
3423 else if( aFileExtension.equalsIgnoreAsciiCase( "odg" ) )
3424 bChangeFileExtensionToPDF = true;
3425 else if( aFileExtension.equalsIgnoreAsciiCase( "ods" ) )
3426 bChangeFileExtensionToPDF = true;
3427 if( bChangeFileExtensionToPDF )
3428 aTargetURL.setExtension("pdf" );
3430 //check if extension is pdf, see if GoToR should be forced
3431 bTargetHasPDFExtension = aTargetURL.GetFileExtension().equalsIgnoreAsciiCase( "pdf" );
3432 if( m_aContext.ForcePDFAction && bTargetHasPDFExtension )
3433 bSetGoToRMode = true;
3435 //prepare the URL, if relative or not
3436 INetProtocol eBaseProtocol = aDocumentURL.GetProtocol();
3437 //queue the string common to all types of actions
3438 aLine.append( "/A<</Type/Action/S");
3439 if( bIsUNCPath ) // handle Win UNC paths
3441 aLine.append( "/Launch/Win<</F" );
3442 // INetURLObject is not good with UNC paths, use original path
3443 appendLiteralStringEncrypt( url, rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
3444 aLine.append( ">>" );
3446 else
3448 bool bSetRelative = false;
3449 bool bFileSpec = false;
3450 //check if relative file link is requested and if the protocol is 'file://'
3451 if( m_aContext.RelFsys && eBaseProtocol == eTargetProtocol && eTargetProtocol == INetProtocol::File )
3452 bSetRelative = true;
3454 OUString aFragment = aTargetURL.GetMark( INetURLObject::DecodeMechanism::NONE /*DecodeMechanism::WithCharset*/ ); //fragment as is,
3455 if( !bSetGoToRMode )
3457 switch( m_aContext.DefaultLinkAction )
3459 default:
3460 case PDFWriter::URIAction :
3461 case PDFWriter::URIActionDestination :
3462 aLine.append( "/URI/URI" );
3463 break;
3464 case PDFWriter::LaunchAction:
3465 // now:
3466 // if a launch action is requested and the hyperlink target has a fragment
3467 // and the target file does not have a pdf extension, or it's not a 'file:://'
3468 // protocol then force the uri action on it
3469 // This code will permit the correct opening of application on web pages,
3470 // the one that normally have fragments (but I may be wrong...)
3471 // and will force the use of URI when the protocol is not file:
3472 if( (!aFragment.isEmpty() && !bTargetHasPDFExtension) ||
3473 eTargetProtocol != INetProtocol::File )
3475 aLine.append( "/URI/URI" );
3477 else
3479 aLine.append( "/Launch/F" );
3480 bFileSpec = true;
3482 break;
3486 //fragment are encoded in the same way as in the named destination processing
3487 if( bSetGoToRMode )
3489 //add the fragment
3490 OUString aURLNoMark = aTargetURL.GetURLNoMark( INetURLObject::DecodeMechanism::WithCharset );
3491 aLine.append("/GoToR");
3492 aLine.append("/F");
3493 appendLiteralStringEncrypt( bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURLNoMark,
3494 INetURLObject::EncodeMechanism::WasEncoded,
3495 INetURLObject::DecodeMechanism::WithCharset ) :
3496 aURLNoMark, rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
3497 if( !aFragment.isEmpty() )
3499 aLine.append("/D/");
3500 appendDestinationName( aFragment , aLine );
3503 else
3505 // change the fragment to accommodate the bookmark (only if the file extension
3506 // is PDF and the requested action is of the correct type)
3507 if(m_aContext.DefaultLinkAction == PDFWriter::URIActionDestination &&
3508 bTargetHasPDFExtension && !aFragment.isEmpty() )
3510 OStringBuffer aLineLoc( 1024 );
3511 appendDestinationName( aFragment , aLineLoc );
3512 //substitute the fragment
3513 aTargetURL.SetMark( OStringToOUString(aLineLoc.makeStringAndClear(), RTL_TEXTENCODING_ASCII_US) );
3515 OUString aURL = aTargetURL.GetMainURL( bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE );
3516 appendLiteralStringEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURL,
3517 INetURLObject::EncodeMechanism::WasEncoded,
3518 bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE
3520 aURL , rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
3523 aLine.append( ">>\n" );
3525 if( rLink.m_nStructParent > 0 )
3527 aLine.append( "/StructParent " );
3528 aLine.append( rLink.m_nStructParent );
3530 aLine.append( ">>\nendobj\n\n" );
3531 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3534 return true;
3537 bool PDFWriterImpl::emitNoteAnnotations()
3539 // emit note annotations
3540 int nAnnots = m_aNotes.size();
3541 for( int i = 0; i < nAnnots; i++ )
3543 const PDFNoteEntry& rNote = m_aNotes[i];
3544 if( ! updateObject( rNote.m_nObject ) )
3545 return false;
3547 OStringBuffer aLine( 1024 );
3548 aLine.append( rNote.m_nObject );
3549 aLine.append( " 0 obj\n" );
3550 // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
3551 // see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
3552 aLine.append( "<</Type/Annot" );
3553 if( m_bIsPDF_A1 || m_bIsPDF_A2 )
3554 aLine.append( "/F 4" );
3555 aLine.append( "/Subtype/Text/Rect[" );
3557 appendFixedInt( rNote.m_aRect.Left(), aLine );
3558 aLine.append( ' ' );
3559 appendFixedInt( rNote.m_aRect.Top(), aLine );
3560 aLine.append( ' ' );
3561 appendFixedInt( rNote.m_aRect.Right(), aLine );
3562 aLine.append( ' ' );
3563 appendFixedInt( rNote.m_aRect.Bottom(), aLine );
3564 aLine.append( "]" );
3566 // contents of the note (type text string)
3567 aLine.append( "/Contents\n" );
3568 appendUnicodeTextStringEncrypt( rNote.m_aContents.Contents, rNote.m_nObject, aLine );
3569 aLine.append( "\n" );
3571 // optional title
3572 if( !rNote.m_aContents.Title.isEmpty() )
3574 aLine.append( "/T" );
3575 appendUnicodeTextStringEncrypt( rNote.m_aContents.Title, rNote.m_nObject, aLine );
3576 aLine.append( "\n" );
3579 aLine.append( ">>\nendobj\n\n" );
3580 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3582 return true;
3585 Font PDFWriterImpl::replaceFont( const vcl::Font& rControlFont, const vcl::Font& rAppSetFont )
3587 bool bAdjustSize = false;
3589 Font aFont( rControlFont );
3590 if( aFont.GetFamilyName().isEmpty() )
3592 aFont = rAppSetFont;
3593 if( rControlFont.GetFontHeight() )
3594 aFont.SetFontSize( Size( 0, rControlFont.GetFontHeight() ) );
3595 else
3596 bAdjustSize = true;
3597 if( rControlFont.GetItalic() != ITALIC_DONTKNOW )
3598 aFont.SetItalic( rControlFont.GetItalic() );
3599 if( rControlFont.GetWeight() != WEIGHT_DONTKNOW )
3600 aFont.SetWeight( rControlFont.GetWeight() );
3602 else if( ! aFont.GetFontHeight() )
3604 aFont.SetFontSize( rAppSetFont.GetFontSize() );
3605 bAdjustSize = true;
3607 if( bAdjustSize )
3609 Size aFontSize = aFont.GetFontSize();
3610 OutputDevice* pDefDev = Application::GetDefaultDevice();
3611 aFontSize = OutputDevice::LogicToLogic( aFontSize, pDefDev->GetMapMode(), getMapMode() );
3612 aFont.SetFontSize( aFontSize );
3614 return aFont;
3617 sal_Int32 PDFWriterImpl::getBestBuildinFont( const vcl::Font& rFont )
3619 sal_Int32 nBest = 4; // default to Helvetica
3620 OUString aFontName( rFont.GetFamilyName() );
3621 aFontName = aFontName.toAsciiLowerCase();
3623 if( aFontName.indexOf( "times" ) != -1 )
3624 nBest = 8;
3625 else if( aFontName.indexOf( "courier" ) != -1 )
3626 nBest = 0;
3627 else if( aFontName.indexOf( "dingbats" ) != -1 )
3628 nBest = 13;
3629 else if( aFontName.indexOf( "symbol" ) != -1 )
3630 nBest = 12;
3631 if( nBest < 12 )
3633 if( rFont.GetItalic() == ITALIC_OBLIQUE || rFont.GetItalic() == ITALIC_NORMAL )
3634 nBest += 1;
3635 if( rFont.GetWeight() > WEIGHT_MEDIUM )
3636 nBest += 2;
3639 if( m_aBuildinFontToObjectMap.find( nBest ) == m_aBuildinFontToObjectMap.end() )
3640 m_aBuildinFontToObjectMap[ nBest ] = createObject();
3642 return nBest;
3645 static const Color& replaceColor( const Color& rCol1, const Color& rCol2 )
3647 return (rCol1 == COL_TRANSPARENT) ? rCol2 : rCol1;
3650 void PDFWriterImpl::createDefaultPushButtonAppearance( PDFWidget& rButton, const PDFWriter::PushButtonWidget& rWidget )
3652 const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
3654 // save graphics state
3655 push( PushFlags::ALL );
3657 // transform relative to control's coordinates since an
3658 // appearance stream is a form XObject
3659 // this relies on the m_aRect member of rButton NOT already being transformed
3660 // to default user space
3661 if( rWidget.Background || rWidget.Border )
3663 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetLightColor() ) : COL_TRANSPARENT );
3664 setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetDialogColor() ) : COL_TRANSPARENT );
3665 drawRectangle( rWidget.Location );
3667 // prepare font to use
3668 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetPushButtonFont() );
3669 setFont( aFont );
3670 setTextColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ) );
3672 drawText( rButton.m_aRect, rButton.m_aText, rButton.m_nTextStyle );
3674 // create DA string while local mapmode is still in place
3675 // (that is before endRedirect())
3676 OStringBuffer aDA( 256 );
3677 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ), aDA );
3678 Font aDummyFont( "Helvetica", aFont.GetFontSize() );
3679 sal_Int32 nDummyBuildin = getBestBuildinFont( aDummyFont );
3680 aDA.append( ' ' );
3681 aDA.append(pdf::BuildinFontFace::Get(nDummyBuildin).getNameObject());
3682 aDA.append( ' ' );
3683 m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
3684 aDA.append( " Tf" );
3685 rButton.m_aDAString = aDA.makeStringAndClear();
3687 pop();
3689 rButton.m_aAppearances[ "N" ][ "Standard" ] = new SvMemoryStream();
3691 /* seems like a bad hack but at least works in both AR5 and 6:
3692 we draw the button ourselves and tell AR
3693 the button would be totally transparent with no text
3695 One would expect that simply setting a normal appearance
3696 should suffice, but no, as soon as the user actually presses
3697 the button and an action is tied to it (gasp! a button that
3698 does something) the appearance gets replaced by some crap that AR
3699 creates on the fly even if no DA or MK is given. On AR6 at least
3700 the DA and MK work as expected, but on AR5 this creates a region
3701 filled with the background color but nor text. Urgh.
3703 rButton.m_aMKDict = "/BC [] /BG [] /CA";
3704 rButton.m_aMKDictCAString = "";
3707 Font PDFWriterImpl::drawFieldBorder( PDFWidget& rIntern,
3708 const PDFWriter::AnyWidget& rWidget,
3709 const StyleSettings& rSettings )
3711 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetFieldFont() );
3713 if( rWidget.Background || rWidget.Border )
3715 if( rWidget.Border && rWidget.BorderColor == COL_TRANSPARENT )
3717 sal_Int32 nDelta = GetDPIX() / 500;
3718 if( nDelta < 1 )
3719 nDelta = 1;
3720 setLineColor( COL_TRANSPARENT );
3721 tools::Rectangle aRect = rIntern.m_aRect;
3722 setFillColor( rSettings.GetLightBorderColor() );
3723 drawRectangle( aRect );
3724 aRect.AdjustLeft(nDelta ); aRect.AdjustTop(nDelta );
3725 aRect.AdjustRight( -nDelta ); aRect.AdjustBottom( -nDelta );
3726 setFillColor( rSettings.GetFieldColor() );
3727 drawRectangle( aRect );
3728 setFillColor( rSettings.GetLightColor() );
3729 drawRectangle( tools::Rectangle( Point( aRect.Left(), aRect.Bottom()-nDelta ), aRect.BottomRight() ) );
3730 drawRectangle( tools::Rectangle( Point( aRect.Right()-nDelta, aRect.Top() ), aRect.BottomRight() ) );
3731 setFillColor( rSettings.GetDarkShadowColor() );
3732 drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Left()+nDelta, aRect.Bottom() ) ) );
3733 drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Right(), aRect.Top()+nDelta ) ) );
3735 else
3737 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetShadowColor() ) : COL_TRANSPARENT );
3738 setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
3739 drawRectangle( rIntern.m_aRect );
3742 if( rWidget.Border )
3744 // adjust edit area accounting for border
3745 sal_Int32 nDelta = aFont.GetFontHeight()/4;
3746 if( nDelta < 1 )
3747 nDelta = 1;
3748 rIntern.m_aRect.AdjustLeft(nDelta );
3749 rIntern.m_aRect.AdjustTop(nDelta );
3750 rIntern.m_aRect.AdjustRight( -nDelta );
3751 rIntern.m_aRect.AdjustBottom( -nDelta );
3754 return aFont;
3757 void PDFWriterImpl::createDefaultEditAppearance( PDFWidget& rEdit, const PDFWriter::EditWidget& rWidget )
3759 const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
3760 SvMemoryStream* pEditStream = new SvMemoryStream( 1024, 1024 );
3762 push( PushFlags::ALL );
3764 // prepare font to use, draw field border
3765 Font aFont = drawFieldBorder( rEdit, rWidget, rSettings );
3766 sal_Int32 nBest = getSystemFont( aFont );
3768 // prepare DA string
3769 OStringBuffer aDA( 32 );
3770 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
3771 aDA.append( ' ' );
3772 aDA.append( "/F" );
3773 aDA.append( nBest );
3775 OStringBuffer aDR( 32 );
3776 aDR.append( "/Font " );
3777 aDR.append( getFontDictObject() );
3778 aDR.append( " 0 R" );
3779 rEdit.m_aDRDict = aDR.makeStringAndClear();
3780 aDA.append( ' ' );
3781 m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
3782 aDA.append( " Tf" );
3784 /* create an empty appearance stream, let the viewer create
3785 the appearance at runtime. This is because AR5 seems to
3786 paint the widget appearance always, and a dynamically created
3787 appearance on top of it. AR6 is well behaved in that regard, so
3788 that behaviour seems to be a bug. Anyway this empty appearance
3789 relies on /NeedAppearances in the AcroForm dictionary set to "true"
3791 beginRedirect( pEditStream, rEdit.m_aRect );
3792 OString aAppearance = "/Tx BMC\nEMC\n";
3793 writeBuffer( aAppearance.getStr(), aAppearance.getLength() );
3795 endRedirect();
3796 pop();
3798 rEdit.m_aAppearances[ "N" ][ "Standard" ] = pEditStream;
3800 rEdit.m_aDAString = aDA.makeStringAndClear();
3803 void PDFWriterImpl::createDefaultListBoxAppearance( PDFWidget& rBox, const PDFWriter::ListBoxWidget& rWidget )
3805 const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
3806 SvMemoryStream* pListBoxStream = new SvMemoryStream( 1024, 1024 );
3808 push( PushFlags::ALL );
3810 // prepare font to use, draw field border
3811 Font aFont = drawFieldBorder( rBox, rWidget, rSettings );
3812 sal_Int32 nBest = getSystemFont( aFont );
3814 beginRedirect( pListBoxStream, rBox.m_aRect );
3816 setLineColor( COL_TRANSPARENT );
3817 setFillColor( replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) );
3818 drawRectangle( rBox.m_aRect );
3820 // empty appearance, see createDefaultEditAppearance for reference
3821 OString aAppearance = "/Tx BMC\nEMC\n";
3822 writeBuffer( aAppearance.getStr(), aAppearance.getLength() );
3824 endRedirect();
3825 pop();
3827 rBox.m_aAppearances[ "N" ][ "Standard" ] = pListBoxStream;
3829 // prepare DA string
3830 OStringBuffer aDA( 256 );
3831 // prepare DA string
3832 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
3833 aDA.append( ' ' );
3834 aDA.append( "/F" );
3835 aDA.append( nBest );
3837 OStringBuffer aDR( 32 );
3838 aDR.append( "/Font " );
3839 aDR.append( getFontDictObject() );
3840 aDR.append( " 0 R" );
3841 rBox.m_aDRDict = aDR.makeStringAndClear();
3842 aDA.append( ' ' );
3843 m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
3844 aDA.append( " Tf" );
3845 rBox.m_aDAString = aDA.makeStringAndClear();
3848 void PDFWriterImpl::createDefaultCheckBoxAppearance( PDFWidget& rBox, const PDFWriter::CheckBoxWidget& rWidget )
3850 const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
3852 // save graphics state
3853 push( PushFlags::ALL );
3855 if( rWidget.Background || rWidget.Border )
3857 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
3858 setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
3859 drawRectangle( rBox.m_aRect );
3862 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
3863 setFont( aFont );
3864 Size aFontSize = aFont.GetFontSize();
3865 if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
3866 aFontSize.setHeight( rBox.m_aRect.GetHeight() );
3867 sal_Int32 nDelta = aFontSize.Height()/10;
3868 if( nDelta < 1 )
3869 nDelta = 1;
3871 tools::Rectangle aCheckRect, aTextRect;
3873 aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
3874 aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
3875 aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
3876 aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
3878 // #i74206# handle small controls without text area
3879 while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
3881 aCheckRect.AdjustRight( -nDelta );
3882 aCheckRect.AdjustTop(nDelta/2 );
3883 aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
3886 aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
3887 aTextRect.SetTop( rBox.m_aRect.Top() );
3888 aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
3889 aTextRect.SetBottom( rBox.m_aRect.Bottom() );
3891 setLineColor( COL_BLACK );
3892 setFillColor( COL_TRANSPARENT );
3893 OStringBuffer aLW( 32 );
3894 aLW.append( "q " );
3895 m_aPages[m_nCurrentPage].appendMappedLength( nDelta, aLW );
3896 aLW.append( " w " );
3897 writeBuffer( aLW.getStr(), aLW.getLength() );
3898 drawRectangle( aCheckRect );
3899 writeBuffer( " Q\n", 3 );
3900 setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
3901 drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
3903 pop();
3905 OStringBuffer aDA( 256 );
3907 // tdf#93853 don't rely on Zapf (or any other 'standard' font)
3908 // being present, but our own OpenSymbol - N.B. PDF/A for good
3909 // reasons require even the standard PS fonts to be embedded!
3910 Push();
3911 SetFont( Font( OUString( "OpenSymbol" ), aFont.GetFontSize() ) );
3912 FontCharMapRef pMap;
3913 GetFontCharMap(pMap);
3914 const LogicalFontInstance* pFontInstance = GetFontInstance();
3915 const PhysicalFontFace* pDevFont = pFontInstance->GetFontFace();
3916 Pop();
3918 // make sure OpenSymbol is embedded, and includes our checkmark
3919 const sal_Unicode cMark=0x2713;
3920 const GlyphItem aItem(0, 0, pMap->GetGlyphIndex(cMark),
3921 Point(), GlyphItemFlags::NONE, 0, 0,
3922 const_cast<LogicalFontInstance*>(pFontInstance));
3923 const std::vector<sal_Ucs> aCodeUnits={ cMark };
3924 sal_uInt8 nMappedGlyph;
3925 sal_Int32 nMappedFontObject;
3926 registerGlyph(&aItem, pDevFont, aCodeUnits, nMappedGlyph, nMappedFontObject);
3928 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
3929 aDA.append( ' ' );
3930 aDA.append( "/F" );
3931 aDA.append( nMappedFontObject );
3932 aDA.append( " 0 Tf" );
3934 OStringBuffer aDR( 32 );
3935 aDR.append( "/Font " );
3936 aDR.append( getFontDictObject() );
3937 aDR.append( " 0 R" );
3938 rBox.m_aDRDict = aDR.makeStringAndClear();
3939 rBox.m_aDAString = aDA.makeStringAndClear();
3940 rBox.m_aMKDict = "/CA";
3941 rBox.m_aMKDictCAString = "8";
3942 rBox.m_aRect = aCheckRect;
3944 // create appearance streams
3945 sal_Int32 nCharXOffset = 1000 - 787; // metrics from OpenSymbol
3946 nCharXOffset *= aCheckRect.GetHeight();
3947 nCharXOffset /= 2000;
3948 sal_Int32 nCharYOffset = 1000 - (820-143); // metrics from Zapf
3949 nCharYOffset *= aCheckRect.GetHeight();
3950 nCharYOffset /= 2000;
3952 // write 'checked' appearance stream
3953 SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
3954 beginRedirect( pCheckStream, aCheckRect );
3955 aDA.append( "/Tx BMC\nq BT\n" );
3956 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
3957 aDA.append( ' ' );
3958 aDA.append( "/F" );
3959 aDA.append( nMappedFontObject );
3960 aDA.append( ' ' );
3961 m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
3962 aDA.append( " Tf\n" );
3963 m_aPages[ m_nCurrentPage ].appendMappedLength( nCharXOffset, aDA );
3964 aDA.append( " " );
3965 m_aPages[ m_nCurrentPage ].appendMappedLength( nCharYOffset, aDA );
3966 aDA.append( " Td <" );
3967 appendHex( nMappedGlyph, aDA );
3968 aDA.append( "> Tj\nET\nQ\nEMC\n" );
3969 writeBuffer( aDA.getStr(), aDA.getLength() );
3970 endRedirect();
3971 rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream;
3973 // write 'unchecked' appearance stream
3974 SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
3975 beginRedirect( pUncheckStream, aCheckRect );
3976 writeBuffer( "/Tx BMC\nEMC\n", 12 );
3977 endRedirect();
3978 rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream;
3981 void PDFWriterImpl::createDefaultRadioButtonAppearance( PDFWidget& rBox, const PDFWriter::RadioButtonWidget& rWidget )
3983 const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
3985 // save graphics state
3986 push( PushFlags::ALL );
3988 if( rWidget.Background || rWidget.Border )
3990 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
3991 setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : COL_TRANSPARENT );
3992 drawRectangle( rBox.m_aRect );
3995 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
3996 setFont( aFont );
3997 Size aFontSize = aFont.GetFontSize();
3998 if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
3999 aFontSize.setHeight( rBox.m_aRect.GetHeight() );
4000 sal_Int32 nDelta = aFontSize.Height()/10;
4001 if( nDelta < 1 )
4002 nDelta = 1;
4004 tools::Rectangle aCheckRect, aTextRect;
4006 aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
4007 aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
4008 aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
4009 aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
4011 // #i74206# handle small controls without text area
4012 while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
4014 aCheckRect.AdjustRight( -nDelta );
4015 aCheckRect.AdjustTop(nDelta/2 );
4016 aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
4019 aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
4020 aTextRect.SetTop( rBox.m_aRect.Top() );
4021 aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
4022 aTextRect.SetBottom( rBox.m_aRect.Bottom() );
4024 setLineColor( COL_BLACK );
4025 setFillColor( COL_TRANSPARENT );
4026 OStringBuffer aLW( 32 );
4027 aLW.append( "q " );
4028 m_aPages[ m_nCurrentPage ].appendMappedLength( nDelta, aLW );
4029 aLW.append( " w " );
4030 writeBuffer( aLW.getStr(), aLW.getLength() );
4031 drawEllipse( aCheckRect );
4032 writeBuffer( " Q\n", 3 );
4033 setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4034 drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
4036 pop();
4038 OStringBuffer aDA( 256 );
4039 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
4040 rBox.m_aDAString = aDA.makeStringAndClear();
4041 //to encrypt this (el)
4042 rBox.m_aMKDict = "/CA";
4043 //after this assignment, to m_aMKDic cannot be added anything
4044 rBox.m_aMKDictCAString = "l";
4046 rBox.m_aRect = aCheckRect;
4048 // create appearance streams
4049 push( PushFlags::ALL);
4050 SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
4052 beginRedirect( pCheckStream, aCheckRect );
4053 aDA.append( "/Tx BMC\nq BT\n" );
4054 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
4055 aDA.append( ' ' );
4056 m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
4057 aDA.append( " 0 0 Td\nET\nQ\n" );
4058 writeBuffer( aDA.getStr(), aDA.getLength() );
4059 setFillColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4060 setLineColor( COL_TRANSPARENT );
4061 aCheckRect.AdjustLeft(3*nDelta );
4062 aCheckRect.AdjustTop(3*nDelta );
4063 aCheckRect.AdjustBottom( -(3*nDelta) );
4064 aCheckRect.AdjustRight( -(3*nDelta) );
4065 drawEllipse( aCheckRect );
4066 writeBuffer( "\nEMC\n", 5 );
4067 endRedirect();
4069 pop();
4070 rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream;
4072 SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
4073 beginRedirect( pUncheckStream, aCheckRect );
4074 writeBuffer( "/Tx BMC\nEMC\n", 12 );
4075 endRedirect();
4076 rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream;
4079 bool PDFWriterImpl::emitAppearances( PDFWidget& rWidget, OStringBuffer& rAnnotDict )
4081 // TODO: check and insert default streams
4082 OString aStandardAppearance;
4083 switch( rWidget.m_eType )
4085 case PDFWriter::CheckBox:
4086 aStandardAppearance = OUStringToOString( rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US );
4087 break;
4088 default:
4089 break;
4092 if( !rWidget.m_aAppearances.empty() )
4094 rAnnotDict.append( "/AP<<\n" );
4095 for (auto & dict_item : rWidget.m_aAppearances)
4097 rAnnotDict.append( "/" );
4098 rAnnotDict.append( dict_item.first );
4099 bool bUseSubDict = (dict_item.second.size() > 1);
4101 // PDF/A requires sub-dicts for /FT/Btn objects (clause
4102 // 6.3.3)
4103 if( m_bIsPDF_A1 || m_bIsPDF_A2 )
4105 if( rWidget.m_eType == PDFWriter::RadioButton ||
4106 rWidget.m_eType == PDFWriter::CheckBox ||
4107 rWidget.m_eType == PDFWriter::PushButton )
4109 bUseSubDict = true;
4113 rAnnotDict.append( bUseSubDict ? "<<" : " " );
4115 for (auto const& stream_item : dict_item.second)
4117 SvMemoryStream* pApppearanceStream = stream_item.second;
4118 dict_item.second[ stream_item.first ] = nullptr;
4120 bool bDeflate = compressStream( pApppearanceStream );
4122 sal_Int64 nStreamLen = pApppearanceStream->TellEnd();
4123 pApppearanceStream->Seek( STREAM_SEEK_TO_BEGIN );
4124 sal_Int32 nObject = createObject();
4125 CHECK_RETURN( updateObject( nObject ) );
4126 if (g_bDebugDisableCompression)
4128 emitComment( "PDFWriterImpl::emitAppearances" );
4130 OStringBuffer aLine;
4131 aLine.append( nObject );
4133 aLine.append( " 0 obj\n"
4134 "<</Type/XObject\n"
4135 "/Subtype/Form\n"
4136 "/BBox[0 0 " );
4137 appendFixedInt( rWidget.m_aRect.GetWidth()-1, aLine );
4138 aLine.append( " " );
4139 appendFixedInt( rWidget.m_aRect.GetHeight()-1, aLine );
4140 aLine.append( "]\n"
4141 "/Resources " );
4142 aLine.append( getResourceDictObj() );
4143 aLine.append( " 0 R\n"
4144 "/Length " );
4145 aLine.append( nStreamLen );
4146 aLine.append( "\n" );
4147 if( bDeflate )
4148 aLine.append( "/Filter/FlateDecode\n" );
4149 aLine.append( ">>\nstream\n" );
4150 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
4151 checkAndEnableStreamEncryption( nObject );
4152 CHECK_RETURN( writeBuffer( pApppearanceStream->GetData(), nStreamLen ) );
4153 disableStreamEncryption();
4154 CHECK_RETURN( writeBuffer( "\nendstream\nendobj\n\n", 19 ) );
4156 if( bUseSubDict )
4158 rAnnotDict.append( " /" );
4159 rAnnotDict.append( stream_item.first );
4160 rAnnotDict.append( " " );
4162 rAnnotDict.append( nObject );
4163 rAnnotDict.append( " 0 R" );
4165 delete pApppearanceStream;
4168 rAnnotDict.append( bUseSubDict ? ">>\n" : "\n" );
4170 rAnnotDict.append( ">>\n" );
4171 if( !aStandardAppearance.isEmpty() )
4173 rAnnotDict.append( "/AS /" );
4174 rAnnotDict.append( aStandardAppearance );
4175 rAnnotDict.append( "\n" );
4179 return true;
4182 bool PDFWriterImpl::emitWidgetAnnotations()
4184 ensureUniqueRadioOnValues();
4186 int nAnnots = m_aWidgets.size();
4187 for( int a = 0; a < nAnnots; a++ )
4189 PDFWidget& rWidget = m_aWidgets[a];
4191 OStringBuffer aLine( 1024 );
4192 OStringBuffer aValue( 256 );
4193 aLine.append( rWidget.m_nObject );
4194 aLine.append( " 0 obj\n"
4195 "<<" );
4196 if( rWidget.m_eType != PDFWriter::Hierarchy )
4198 // emit widget annotation only for terminal fields
4199 if( rWidget.m_aKids.empty() )
4201 int iRectMargin;
4203 aLine.append( "/Type/Annot/Subtype/Widget/F " );
4205 if (rWidget.m_eType == PDFWriter::Signature)
4207 aLine.append( "132\n" ); // Print & Locked
4208 iRectMargin = 0;
4210 else
4212 aLine.append( "4\n" );
4213 iRectMargin = 1;
4216 aLine.append("/Rect[" );
4217 appendFixedInt( rWidget.m_aRect.Left()-iRectMargin, aLine );
4218 aLine.append( ' ' );
4219 appendFixedInt( rWidget.m_aRect.Top()+iRectMargin, aLine );
4220 aLine.append( ' ' );
4221 appendFixedInt( rWidget.m_aRect.Right()+iRectMargin, aLine );
4222 aLine.append( ' ' );
4223 appendFixedInt( rWidget.m_aRect.Bottom()-iRectMargin, aLine );
4224 aLine.append( "]\n" );
4226 aLine.append( "/FT/" );
4227 switch( rWidget.m_eType )
4229 case PDFWriter::RadioButton:
4230 case PDFWriter::CheckBox:
4231 // for radio buttons only the RadioButton field, not the
4232 // CheckBox children should have a value, else acrobat reader
4233 // does not always check the right button
4234 // of course real check boxes (not belonging to a radio group)
4235 // need their values, too
4236 if( rWidget.m_eType == PDFWriter::RadioButton || rWidget.m_nRadioGroup < 0 )
4238 aValue.append( "/" );
4239 // check for radio group with all buttons unpressed
4240 if( rWidget.m_aValue.isEmpty() )
4241 aValue.append( "Off" );
4242 else
4243 appendName( rWidget.m_aValue, aValue );
4245 [[fallthrough]];
4246 case PDFWriter::PushButton:
4247 aLine.append( "Btn" );
4248 break;
4249 case PDFWriter::ListBox:
4250 if( rWidget.m_nFlags & 0x200000 ) // multiselect
4252 aValue.append( "[" );
4253 for( size_t i = 0; i < rWidget.m_aSelectedEntries.size(); i++ )
4255 sal_Int32 nEntry = rWidget.m_aSelectedEntries[i];
4256 if( nEntry >= 0 && nEntry < sal_Int32(rWidget.m_aListEntries.size()) )
4257 appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ nEntry ], rWidget.m_nObject, aValue );
4259 aValue.append( "]" );
4261 else if( !rWidget.m_aSelectedEntries.empty() &&
4262 rWidget.m_aSelectedEntries[0] >= 0 &&
4263 rWidget.m_aSelectedEntries[0] < sal_Int32(rWidget.m_aListEntries.size()) )
4265 appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ rWidget.m_aSelectedEntries[0] ], rWidget.m_nObject, aValue );
4267 else
4268 appendUnicodeTextStringEncrypt( OUString(), rWidget.m_nObject, aValue );
4269 aLine.append( "Ch" );
4270 break;
4271 case PDFWriter::ComboBox:
4272 appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue );
4273 aLine.append( "Ch" );
4274 break;
4275 case PDFWriter::Edit:
4276 aLine.append( "Tx" );
4277 appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue );
4278 break;
4279 case PDFWriter::Signature:
4280 aLine.append( "Sig" );
4281 aValue.append(OUStringToOString(rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US));
4282 break;
4283 case PDFWriter::Hierarchy: // make the compiler happy
4284 break;
4286 aLine.append( "\n" );
4287 aLine.append( "/P " );
4288 aLine.append( m_aPages[ rWidget.m_nPage ].m_nPageObject );
4289 aLine.append( " 0 R\n" );
4291 if( rWidget.m_nParent )
4293 aLine.append( "/Parent " );
4294 aLine.append( rWidget.m_nParent );
4295 aLine.append( " 0 R\n" );
4297 if( !rWidget.m_aKids.empty() )
4299 aLine.append( "/Kids[" );
4300 for( size_t i = 0; i < rWidget.m_aKids.size(); i++ )
4302 aLine.append( rWidget.m_aKids[i] );
4303 aLine.append( " 0 R" );
4304 aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
4306 aLine.append( "]\n" );
4308 if( !rWidget.m_aName.isEmpty() )
4310 aLine.append( "/T" );
4311 appendLiteralStringEncrypt( rWidget.m_aName, rWidget.m_nObject, aLine );
4312 aLine.append( "\n" );
4314 if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2 && !rWidget.m_aDescription.isEmpty() )
4316 // the alternate field name should be unicode able since it is
4317 // supposed to be used in UI
4318 aLine.append( "/TU" );
4319 appendUnicodeTextStringEncrypt( rWidget.m_aDescription, rWidget.m_nObject, aLine );
4320 aLine.append( "\n" );
4323 if( rWidget.m_nFlags )
4325 aLine.append( "/Ff " );
4326 aLine.append( rWidget.m_nFlags );
4327 aLine.append( "\n" );
4329 if( !aValue.isEmpty() )
4331 OString aVal = aValue.makeStringAndClear();
4332 aLine.append( "/V " );
4333 aLine.append( aVal );
4334 aLine.append( "\n"
4335 "/DV " );
4336 aLine.append( aVal );
4337 aLine.append( "\n" );
4339 if( rWidget.m_eType == PDFWriter::ListBox || rWidget.m_eType == PDFWriter::ComboBox )
4341 sal_Int32 nTI = -1;
4342 aLine.append( "/Opt[\n" );
4343 sal_Int32 i = 0;
4344 for (auto const& entry : rWidget.m_aListEntries)
4346 appendUnicodeTextStringEncrypt( entry, rWidget.m_nObject, aLine );
4347 aLine.append( "\n" );
4348 if( entry == rWidget.m_aValue )
4349 nTI = i;
4350 ++i;
4352 aLine.append( "]\n" );
4353 if( nTI > 0 )
4355 aLine.append( "/TI " );
4356 aLine.append( nTI );
4357 aLine.append( "\n" );
4358 if( rWidget.m_nFlags & 0x200000 ) // Multiselect
4360 aLine.append( "/I [" );
4361 aLine.append( nTI );
4362 aLine.append( "]\n" );
4366 if( rWidget.m_eType == PDFWriter::Edit && rWidget.m_nMaxLen > 0 )
4368 aLine.append( "/MaxLen " );
4369 aLine.append( rWidget.m_nMaxLen );
4370 aLine.append( "\n" );
4372 if( rWidget.m_eType == PDFWriter::PushButton )
4374 if(!m_bIsPDF_A1)
4376 OStringBuffer aDest;
4377 if( rWidget.m_nDest != -1 && appendDest( m_aDestinationIdTranslation[ rWidget.m_nDest ], aDest ) )
4379 aLine.append( "/AA<</D<</Type/Action/S/GoTo/D " );
4380 aLine.append( aDest.makeStringAndClear() );
4381 aLine.append( ">>>>\n" );
4383 else if( rWidget.m_aListEntries.empty() )
4385 if( !m_bIsPDF_A2 )
4387 // create a reset form action
4388 aLine.append( "/AA<</D<</Type/Action/S/ResetForm>>>>\n" );
4391 else if( rWidget.m_bSubmit )
4393 // create a submit form action
4394 aLine.append( "/AA<</D<</Type/Action/S/SubmitForm/F" );
4395 appendLiteralStringEncrypt( rWidget.m_aListEntries.front(), rWidget.m_nObject, aLine, osl_getThreadTextEncoding() );
4396 aLine.append( "/Flags " );
4398 sal_Int32 nFlags = 0;
4399 switch( m_aContext.SubmitFormat )
4401 case PDFWriter::HTML:
4402 nFlags |= 4;
4403 break;
4404 case PDFWriter::XML:
4405 if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
4406 nFlags |= 32;
4407 break;
4408 case PDFWriter::PDF:
4409 if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
4410 nFlags |= 256;
4411 break;
4412 case PDFWriter::FDF:
4413 default:
4414 break;
4416 if( rWidget.m_bSubmitGet )
4417 nFlags |= 8;
4418 aLine.append( nFlags );
4419 aLine.append( ">>>>\n" );
4421 else
4423 // create a URI action
4424 aLine.append( "/AA<</D<</Type/Action/S/URI/URI(" );
4425 aLine.append( OUStringToOString( rWidget.m_aListEntries.front(), RTL_TEXTENCODING_ASCII_US ) );
4426 aLine.append( ")>>>>\n" );
4429 else
4430 m_aErrors.insert( PDFWriter::Warning_FormAction_Omitted_PDFA );
4432 if( !rWidget.m_aDAString.isEmpty() )
4434 if( !rWidget.m_aDRDict.isEmpty() )
4436 aLine.append( "/DR<<" );
4437 aLine.append( rWidget.m_aDRDict );
4438 aLine.append( ">>\n" );
4440 else
4442 aLine.append( "/DR<</Font<<" );
4443 appendBuildinFontsToDict( aLine );
4444 aLine.append( ">>>>\n" );
4446 aLine.append( "/DA" );
4447 appendLiteralStringEncrypt( rWidget.m_aDAString, rWidget.m_nObject, aLine );
4448 aLine.append( "\n" );
4449 if( rWidget.m_nTextStyle & DrawTextFlags::Center )
4450 aLine.append( "/Q 1\n" );
4451 else if( rWidget.m_nTextStyle & DrawTextFlags::Right )
4452 aLine.append( "/Q 2\n" );
4454 // appearance characteristics for terminal fields
4455 // which are supposed to have an appearance constructed
4456 // by the viewer application
4457 if( !rWidget.m_aMKDict.isEmpty() )
4459 aLine.append( "/MK<<" );
4460 aLine.append( rWidget.m_aMKDict );
4461 //add the CA string, encrypting it
4462 appendLiteralStringEncrypt(rWidget.m_aMKDictCAString, rWidget.m_nObject, aLine);
4463 aLine.append( ">>\n" );
4466 CHECK_RETURN( emitAppearances( rWidget, aLine ) );
4468 aLine.append( ">>\n"
4469 "endobj\n\n" );
4470 CHECK_RETURN( updateObject( rWidget.m_nObject ) );
4471 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
4473 return true;
4476 bool PDFWriterImpl::emitAnnotations()
4478 if( m_aPages.empty() )
4479 return false;
4481 CHECK_RETURN( emitLinkAnnotations() );
4482 CHECK_RETURN(emitScreenAnnotations());
4483 CHECK_RETURN( emitNoteAnnotations() );
4484 CHECK_RETURN( emitWidgetAnnotations() );
4486 return true;
4489 bool PDFWriterImpl::emitEmbeddedFiles()
4491 for (auto& rEmbeddedFile : m_aEmbeddedFiles)
4493 if (!updateObject(rEmbeddedFile.m_nObject))
4494 continue;
4496 OStringBuffer aLine;
4497 aLine.append(rEmbeddedFile.m_nObject);
4498 aLine.append(" 0 obj\n");
4499 aLine.append("<< /Type /EmbeddedFile /Length ");
4500 aLine.append(static_cast<sal_Int64>(rEmbeddedFile.m_pData->size()));
4501 aLine.append(" >>\nstream\n");
4502 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
4503 aLine.setLength(0);
4505 CHECK_RETURN(writeBuffer(rEmbeddedFile.m_pData->data(), rEmbeddedFile.m_pData->size()));
4507 aLine.append("\nendstream\nendobj\n\n");
4508 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
4510 return true;
4513 #undef CHECK_RETURN
4514 #define CHECK_RETURN( x ) if( !x ) return false
4516 bool PDFWriterImpl::emitCatalog()
4518 // build page tree
4519 // currently there is only one node that contains all leaves
4521 // first create a page tree node id
4522 sal_Int32 nTreeNode = createObject();
4524 // emit global resource dictionary (page emit needs it)
4525 CHECK_RETURN( emitResources() );
4527 // emit all pages
4528 for (auto & page : m_aPages)
4529 if( ! page.emit( nTreeNode ) )
4530 return false;
4532 sal_Int32 nNamedDestinationsDictionary = emitNamedDestinations();
4534 sal_Int32 nOutlineDict = emitOutline();
4536 // emit Output intent
4537 sal_Int32 nOutputIntentObject = emitOutputIntent();
4539 // emit metadata
4540 sal_Int32 nMetadataObject = emitDocumentMetadata();
4542 sal_Int32 nStructureDict = 0;
4543 if(m_aStructure.size() > 1)
4545 // check if dummy structure containers are needed
4546 addInternalStructureContainer(m_aStructure[0]);
4547 nStructureDict = m_aStructure[0].m_nObject = createObject();
4548 emitStructure( m_aStructure[ 0 ] );
4551 // adjust tree node file offset
4552 if( ! updateObject( nTreeNode ) )
4553 return false;
4555 // emit tree node
4556 OStringBuffer aLine( 2048 );
4557 aLine.append( nTreeNode );
4558 aLine.append( " 0 obj\n" );
4559 aLine.append( "<</Type/Pages\n" );
4560 aLine.append( "/Resources " );
4561 aLine.append( getResourceDictObj() );
4562 aLine.append( " 0 R\n" );
4564 sal_Int32 nMediaBoxWidth = 0;
4565 sal_Int32 nMediaBoxHeight = 0;
4566 if( m_aPages.empty() ) // sanity check, this should not happen
4568 nMediaBoxWidth = g_nInheritedPageWidth;
4569 nMediaBoxHeight = g_nInheritedPageHeight;
4571 else
4573 for (auto const& page : m_aPages)
4575 if( page.m_nPageWidth > nMediaBoxWidth )
4576 nMediaBoxWidth = page.m_nPageWidth;
4577 if( page.m_nPageHeight > nMediaBoxHeight )
4578 nMediaBoxHeight = page.m_nPageHeight;
4581 aLine.append( "/MediaBox[ 0 0 " );
4582 aLine.append( nMediaBoxWidth );
4583 aLine.append( ' ' );
4584 aLine.append( nMediaBoxHeight );
4585 aLine.append( " ]\n"
4586 "/Kids[ " );
4587 unsigned int i = 0;
4588 for (const auto & page : m_aPages)
4590 aLine.append( page.m_nPageObject );
4591 aLine.append( " 0 R" );
4592 aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
4593 ++i;
4595 aLine.append( "]\n"
4596 "/Count " );
4597 aLine.append( static_cast<sal_Int32>(m_aPages.size()) );
4598 aLine.append( ">>\n"
4599 "endobj\n\n" );
4600 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
4602 // emit annotation objects
4603 CHECK_RETURN( emitAnnotations() );
4604 CHECK_RETURN( emitEmbeddedFiles() );
4606 // emit Catalog
4607 m_nCatalogObject = createObject();
4608 if( ! updateObject( m_nCatalogObject ) )
4609 return false;
4610 aLine.setLength( 0 );
4611 aLine.append( m_nCatalogObject );
4612 aLine.append( " 0 obj\n"
4613 "<</Type/Catalog/Pages " );
4614 aLine.append( nTreeNode );
4615 aLine.append( " 0 R\n" );
4617 // check if there are named destinations to emit (root must be inside the catalog)
4618 if( nNamedDestinationsDictionary )
4620 aLine.append("/Dests ");
4621 aLine.append( nNamedDestinationsDictionary );
4622 aLine.append( " 0 R\n" );
4625 if( m_aContext.PageLayout != PDFWriter::DefaultLayout )
4626 switch( m_aContext.PageLayout )
4628 default :
4629 case PDFWriter::SinglePage :
4630 aLine.append( "/PageLayout/SinglePage\n" );
4631 break;
4632 case PDFWriter::Continuous :
4633 aLine.append( "/PageLayout/OneColumn\n" );
4634 break;
4635 case PDFWriter::ContinuousFacing :
4636 // the flag m_aContext.FirstPageLeft below is used to set the page on the left side
4637 aLine.append( "/PageLayout/TwoColumnRight\n" );//odd page on the right side
4638 break;
4640 if( m_aContext.PDFDocumentMode != PDFWriter::ModeDefault && !m_aContext.OpenInFullScreenMode )
4641 switch( m_aContext.PDFDocumentMode )
4643 default :
4644 aLine.append( "/PageMode/UseNone\n" );
4645 break;
4646 case PDFWriter::UseOutlines :
4647 aLine.append( "/PageMode/UseOutlines\n" ); //document is opened with outline pane open
4648 break;
4649 case PDFWriter::UseThumbs :
4650 aLine.append( "/PageMode/UseThumbs\n" ); //document is opened with thumbnails pane open
4651 break;
4653 else if( m_aContext.OpenInFullScreenMode )
4654 aLine.append( "/PageMode/FullScreen\n" ); //document is opened full screen
4656 OStringBuffer aInitPageRef;
4657 if( m_aContext.InitialPage >= 0 && m_aContext.InitialPage < static_cast<sal_Int32>(m_aPages.size()) )
4659 aInitPageRef.append( m_aPages[m_aContext.InitialPage].m_nPageObject );
4660 aInitPageRef.append( " 0 R" );
4662 else
4663 aInitPageRef.append( "0" );
4665 switch( m_aContext.PDFDocumentAction )
4667 case PDFWriter::ActionDefault : //do nothing, this is the Acrobat default
4668 default:
4669 if( aInitPageRef.getLength() > 1 )
4671 aLine.append( "/OpenAction[" );
4672 aLine.append( aInitPageRef.makeStringAndClear() );
4673 aLine.append( " /XYZ null null 0]\n" );
4675 break;
4676 case PDFWriter::FitInWindow :
4677 aLine.append( "/OpenAction[" );
4678 aLine.append( aInitPageRef.makeStringAndClear() );
4679 aLine.append( " /Fit]\n" ); //Open fit page
4680 break;
4681 case PDFWriter::FitWidth :
4682 aLine.append( "/OpenAction[" );
4683 aLine.append( aInitPageRef.makeStringAndClear() );
4684 aLine.append( " /FitH " );
4685 aLine.append( g_nInheritedPageHeight );//Open fit width
4686 aLine.append( "]\n" );
4687 break;
4688 case PDFWriter::FitVisible :
4689 aLine.append( "/OpenAction[" );
4690 aLine.append( aInitPageRef.makeStringAndClear() );
4691 aLine.append( " /FitBH " );
4692 aLine.append( g_nInheritedPageHeight );//Open fit visible
4693 aLine.append( "]\n" );
4694 break;
4695 case PDFWriter::ActionZoom :
4696 aLine.append( "/OpenAction[" );
4697 aLine.append( aInitPageRef.makeStringAndClear() );
4698 aLine.append( " /XYZ null null " );
4699 if( m_aContext.Zoom >= 50 && m_aContext.Zoom <= 1600 )
4700 aLine.append( static_cast<double>(m_aContext.Zoom)/100.0 );
4701 else
4702 aLine.append( "0" );
4703 aLine.append( "]\n" );
4704 break;
4707 // viewer preferences, if we had some, then emit
4708 if( m_aContext.HideViewerToolbar ||
4709 ( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 && !m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle ) ||
4710 m_aContext.HideViewerMenubar ||
4711 m_aContext.HideViewerWindowControls || m_aContext.FitWindow ||
4712 m_aContext.CenterWindow || (m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing ) ||
4713 m_aContext.OpenInFullScreenMode )
4715 aLine.append( "/ViewerPreferences<<" );
4716 if( m_aContext.HideViewerToolbar )
4717 aLine.append( "/HideToolbar true\n" );
4718 if( m_aContext.HideViewerMenubar )
4719 aLine.append( "/HideMenubar true\n" );
4720 if( m_aContext.HideViewerWindowControls )
4721 aLine.append( "/HideWindowUI true\n" );
4722 if( m_aContext.FitWindow )
4723 aLine.append( "/FitWindow true\n" );
4724 if( m_aContext.CenterWindow )
4725 aLine.append( "/CenterWindow true\n" );
4726 if( m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 && !m_aContext.DocumentInfo.Title.isEmpty() && m_aContext.DisplayPDFDocumentTitle )
4727 aLine.append( "/DisplayDocTitle true\n" );
4728 if( m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing )
4729 aLine.append( "/Direction/R2L\n" );
4730 if( m_aContext.OpenInFullScreenMode )
4731 switch( m_aContext.PDFDocumentMode )
4733 default :
4734 case PDFWriter::ModeDefault :
4735 aLine.append( "/NonFullScreenPageMode/UseNone\n" );
4736 break;
4737 case PDFWriter::UseOutlines :
4738 aLine.append( "/NonFullScreenPageMode/UseOutlines\n" );
4739 break;
4740 case PDFWriter::UseThumbs :
4741 aLine.append( "/NonFullScreenPageMode/UseThumbs\n" );
4742 break;
4744 aLine.append( ">>\n" );
4747 if( nOutlineDict )
4749 aLine.append( "/Outlines " );
4750 aLine.append( nOutlineDict );
4751 aLine.append( " 0 R\n" );
4753 if( nStructureDict )
4755 aLine.append( "/StructTreeRoot " );
4756 aLine.append( nStructureDict );
4757 aLine.append( " 0 R\n" );
4759 if( !m_aContext.DocumentLocale.Language.isEmpty() )
4761 /* PDF allows only RFC 3066, see above in emitStructure(). */
4762 LanguageTag aLanguageTag( m_aContext.DocumentLocale);
4763 OUString aLanguage, aScript, aCountry;
4764 aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
4765 if (!aLanguage.isEmpty())
4767 OUStringBuffer aLocBuf( 16 );
4768 aLocBuf.append( aLanguage );
4769 if( !aCountry.isEmpty() )
4771 aLocBuf.append( '-' );
4772 aLocBuf.append( aCountry );
4774 aLine.append( "/Lang" );
4775 appendLiteralStringEncrypt( aLocBuf.makeStringAndClear(), m_nCatalogObject, aLine );
4776 aLine.append( "\n" );
4779 if( m_aContext.Tagged && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
4781 aLine.append( "/MarkInfo<</Marked true>>\n" );
4783 if( !m_aWidgets.empty() )
4785 aLine.append( "/AcroForm<</Fields[\n" );
4786 int nWidgets = m_aWidgets.size();
4787 int nOut = 0;
4788 for( int j = 0; j < nWidgets; j++ )
4790 // output only root fields
4791 if( m_aWidgets[j].m_nParent < 1 )
4793 aLine.append( m_aWidgets[j].m_nObject );
4794 aLine.append( (nOut++ % 5)==4 ? " 0 R\n" : " 0 R " );
4797 aLine.append( "\n]" );
4799 #if HAVE_FEATURE_NSS
4800 if (m_nSignatureObject != -1)
4801 aLine.append( "/SigFlags 3");
4802 #endif
4804 aLine.append( "/DR " );
4805 aLine.append( getResourceDictObj() );
4806 aLine.append( " 0 R" );
4807 // NeedAppearances must not be used if PDF is signed
4808 if( m_bIsPDF_A1 || m_bIsPDF_A2
4809 #if HAVE_FEATURE_NSS
4810 || ( m_nSignatureObject != -1 )
4811 #endif
4813 aLine.append( ">>\n" );
4814 else
4815 aLine.append( "/NeedAppearances true>>\n" );
4818 //check if there is a Metadata object
4819 if( nOutputIntentObject )
4821 aLine.append("/OutputIntents[");
4822 aLine.append( nOutputIntentObject );
4823 aLine.append( " 0 R]" );
4826 if( nMetadataObject )
4828 aLine.append("/Metadata ");
4829 aLine.append( nMetadataObject );
4830 aLine.append( " 0 R" );
4833 aLine.append( ">>\n"
4834 "endobj\n\n" );
4835 return writeBuffer( aLine.getStr(), aLine.getLength() );
4838 #if HAVE_FEATURE_NSS
4840 bool PDFWriterImpl::emitSignature()
4842 if( !updateObject( m_nSignatureObject ) )
4843 return false;
4845 OStringBuffer aLine( 0x5000 );
4846 aLine.append( m_nSignatureObject );
4847 aLine.append( " 0 obj\n" );
4848 aLine.append("<</Contents <" );
4850 sal_uInt64 nOffset = ~0U;
4851 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nOffset) ) );
4853 m_nSignatureContentOffset = nOffset + aLine.getLength();
4855 // reserve some space for the PKCS#7 object
4856 OStringBuffer aContentFiller( MAX_SIGNATURE_CONTENT_LENGTH );
4857 comphelper::string::padToLength(aContentFiller, MAX_SIGNATURE_CONTENT_LENGTH, '0');
4858 aLine.append( aContentFiller.makeStringAndClear() );
4859 aLine.append( ">\n/Type/Sig/SubFilter/adbe.pkcs7.detached");
4861 if( !m_aContext.DocumentInfo.Author.isEmpty() )
4863 aLine.append( "/Name" );
4864 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Author, m_nSignatureObject, aLine );
4867 aLine.append( " /M ");
4868 appendLiteralStringEncrypt( m_aCreationDateString, m_nSignatureObject, aLine );
4870 aLine.append( " /ByteRange [ 0 ");
4871 aLine.append( m_nSignatureContentOffset - 1 );
4872 aLine.append( " " );
4873 aLine.append( m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1 );
4874 aLine.append( " " );
4876 m_nSignatureLastByteRangeNoOffset = nOffset + aLine.getLength();
4878 // mark the last ByteRange no and add some space. Now, we don't know
4879 // how many bytes we need for this ByteRange value
4880 // The real value will be overwritten in the finalizeSignature method
4881 OStringBuffer aByteRangeFiller( 100 );
4882 comphelper::string::padToLength(aByteRangeFiller, 100, ' ');
4883 aLine.append( aByteRangeFiller.makeStringAndClear() );
4884 aLine.append(" /Filter/Adobe.PPKMS");
4886 //emit reason, location and contactinfo
4887 if ( !m_aContext.SignReason.isEmpty() )
4889 aLine.append("/Reason");
4890 appendUnicodeTextStringEncrypt( m_aContext.SignReason, m_nSignatureObject, aLine );
4893 if ( !m_aContext.SignLocation.isEmpty() )
4895 aLine.append("/Location");
4896 appendUnicodeTextStringEncrypt( m_aContext.SignLocation, m_nSignatureObject, aLine );
4899 if ( !m_aContext.SignContact.isEmpty() )
4901 aLine.append("/ContactInfo");
4902 appendUnicodeTextStringEncrypt( m_aContext.SignContact, m_nSignatureObject, aLine );
4905 aLine.append(" >>\nendobj\n\n" );
4907 return writeBuffer( aLine.getStr(), aLine.getLength() );
4910 bool PDFWriterImpl::finalizeSignature()
4912 if (!m_aContext.SignCertificate.is())
4913 return false;
4915 // 1- calculate last ByteRange value
4916 sal_uInt64 nOffset = ~0U;
4917 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nOffset) ) );
4919 sal_Int64 nLastByteRangeNo = nOffset - (m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1);
4921 // 2- overwrite the value to the m_nSignatureLastByteRangeNoOffset position
4922 sal_uInt64 nWritten = 0;
4923 CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureLastByteRangeNoOffset) ) );
4924 OString aByteRangeNo = OString::number( nLastByteRangeNo ) + " ]";
4926 if (m_aFile.write(aByteRangeNo.getStr(), aByteRangeNo.getLength(), nWritten) != osl::File::E_None)
4928 CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset)) );
4929 return false;
4932 // 3- create the PKCS#7 object using NSS
4934 // Prepare buffer and calculate PDF file digest
4935 CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, 0)) );
4937 std::unique_ptr<char[]> buffer1(new char[m_nSignatureContentOffset + 1]);
4938 sal_uInt64 bytesRead1;
4940 //FIXME: Check if hash is calculated from the correct byterange
4941 if (osl::File::E_None != m_aFile.read(buffer1.get(), m_nSignatureContentOffset - 1 , bytesRead1) ||
4942 bytesRead1 != static_cast<sal_uInt64>(m_nSignatureContentOffset) - 1)
4944 SAL_WARN("vcl.pdfwriter", "First buffer read failed");
4945 return false;
4948 std::unique_ptr<char[]> buffer2(new char[nLastByteRangeNo + 1]);
4949 sal_uInt64 bytesRead2;
4951 if (osl::File::E_None != m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1) ||
4952 osl::File::E_None != m_aFile.read(buffer2.get(), nLastByteRangeNo, bytesRead2) ||
4953 bytesRead2 != static_cast<sal_uInt64>(nLastByteRangeNo))
4955 SAL_WARN("vcl.pdfwriter", "Second buffer read failed");
4956 return false;
4959 OStringBuffer aCMSHexBuffer;
4960 svl::crypto::Signing aSigning(m_aContext.SignCertificate);
4961 aSigning.AddDataRange(buffer1.get(), bytesRead1);
4962 aSigning.AddDataRange(buffer2.get(), bytesRead2);
4963 aSigning.SetSignTSA(m_aContext.SignTSA);
4964 aSigning.SetSignPassword(m_aContext.SignPassword);
4965 if (!aSigning.Sign(aCMSHexBuffer))
4967 SAL_WARN("vcl.pdfwriter", "PDFWriter::Sign() failed");
4968 return false;
4971 assert(aCMSHexBuffer.getLength() <= MAX_SIGNATURE_CONTENT_LENGTH);
4973 // Set file pointer to the m_nSignatureContentOffset, we're ready to overwrite PKCS7 object
4974 nWritten = 0;
4975 CHECK_RETURN( (osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, m_nSignatureContentOffset)) );
4976 m_aFile.write(aCMSHexBuffer.getStr(), aCMSHexBuffer.getLength(), nWritten);
4978 return osl::File::E_None == m_aFile.setPos(osl_Pos_Absolut, nOffset);
4981 #endif //HAVE_FEATURE_NSS
4983 sal_Int32 PDFWriterImpl::emitInfoDict( )
4985 sal_Int32 nObject = createObject();
4987 if( updateObject( nObject ) )
4989 OStringBuffer aLine( 1024 );
4990 aLine.append( nObject );
4991 aLine.append( " 0 obj\n"
4992 "<<" );
4993 if( !m_aContext.DocumentInfo.Title.isEmpty() )
4995 aLine.append( "/Title" );
4996 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Title, nObject, aLine );
4997 aLine.append( "\n" );
4999 if( !m_aContext.DocumentInfo.Author.isEmpty() )
5001 aLine.append( "/Author" );
5002 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Author, nObject, aLine );
5003 aLine.append( "\n" );
5005 if( !m_aContext.DocumentInfo.Subject.isEmpty() )
5007 aLine.append( "/Subject" );
5008 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Subject, nObject, aLine );
5009 aLine.append( "\n" );
5011 if( !m_aContext.DocumentInfo.Keywords.isEmpty() )
5013 aLine.append( "/Keywords" );
5014 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Keywords, nObject, aLine );
5015 aLine.append( "\n" );
5017 if( !m_aContext.DocumentInfo.Creator.isEmpty() )
5019 aLine.append( "/Creator" );
5020 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Creator, nObject, aLine );
5021 aLine.append( "\n" );
5023 if( !m_aContext.DocumentInfo.Producer.isEmpty() )
5025 aLine.append( "/Producer" );
5026 appendUnicodeTextStringEncrypt( m_aContext.DocumentInfo.Producer, nObject, aLine );
5027 aLine.append( "\n" );
5030 aLine.append( "/CreationDate" );
5031 appendLiteralStringEncrypt( m_aCreationDateString, nObject, aLine );
5032 aLine.append( ">>\nendobj\n\n" );
5033 if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
5034 nObject = 0;
5036 else
5037 nObject = 0;
5039 return nObject;
5042 // Part of this function may be shared with method appendDest.
5043 sal_Int32 PDFWriterImpl::emitNamedDestinations()
5045 sal_Int32 nCount = m_aNamedDests.size();
5046 if( nCount <= 0 )
5047 return 0;//define internal error
5049 //get the object number for all the destinations
5050 sal_Int32 nObject = createObject();
5052 if( updateObject( nObject ) )
5054 //emit the dictionary
5055 OStringBuffer aLine( 1024 );
5056 aLine.append( nObject );
5057 aLine.append( " 0 obj\n"
5058 "<<" );
5060 sal_Int32 nDestID;
5061 for( nDestID = 0; nDestID < nCount; nDestID++ )
5063 const PDFNamedDest& rDest = m_aNamedDests[ nDestID ];
5064 // In order to correctly function both under an Internet browser and
5065 // directly with a reader (provided the reader has the feature) we
5066 // need to set the name of the destination the same way it will be encoded
5067 // in an Internet link
5068 INetURLObject aLocalURL( "http://ahost.ax" ); //dummy location, won't be used
5069 aLocalURL.SetMark( rDest.m_aDestName );
5071 const OUString aName = aLocalURL.GetMark( INetURLObject::DecodeMechanism::NONE ); //same coding as
5072 // in link creation ( see PDFWriterImpl::emitLinkAnnotations )
5073 const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ];
5075 aLine.append( '/' );
5076 appendDestinationName( aName, aLine ); // this conversion must be done when forming the link to target ( see in emitCatalog )
5077 aLine.append( '[' ); // the '[' can be emitted immediately, because the appendDestinationName function
5078 //maps the preceding character properly
5079 aLine.append( rDestPage.m_nPageObject );
5080 aLine.append( " 0 R" );
5082 switch( rDest.m_eType )
5084 case PDFWriter::DestAreaType::XYZ:
5085 default:
5086 aLine.append( "/XYZ " );
5087 appendFixedInt( rDest.m_aRect.Left(), aLine );
5088 aLine.append( ' ' );
5089 appendFixedInt( rDest.m_aRect.Bottom(), aLine );
5090 aLine.append( " 0" );
5091 break;
5092 case PDFWriter::DestAreaType::FitRectangle:
5093 aLine.append( "/FitR " );
5094 appendFixedInt( rDest.m_aRect.Left(), aLine );
5095 aLine.append( ' ' );
5096 appendFixedInt( rDest.m_aRect.Top(), aLine );
5097 aLine.append( ' ' );
5098 appendFixedInt( rDest.m_aRect.Right(), aLine );
5099 aLine.append( ' ' );
5100 appendFixedInt( rDest.m_aRect.Bottom(), aLine );
5101 break;
5103 aLine.append( "]\n" );
5106 //close
5107 aLine.append( ">>\nendobj\n\n" );
5108 if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
5109 nObject = 0;
5111 else
5112 nObject = 0;
5114 return nObject;
5117 // emits the output intent dictionary
5118 sal_Int32 PDFWriterImpl::emitOutputIntent()
5120 if( !m_bIsPDF_A1 && !m_bIsPDF_A2 )
5121 return 0;
5123 //emit the sRGB standard profile, in ICC format, in a stream, per IEC61966-2.1
5125 OStringBuffer aLine( 1024 );
5126 sal_Int32 nICCObject = createObject();
5127 sal_Int32 nStreamLengthObject = createObject();
5129 aLine.append( nICCObject );
5130 // sRGB has 3 colors, hence /N 3 below (PDF 1.4 table 4.16)
5131 aLine.append( " 0 obj\n<</N 3/Length " );
5132 aLine.append( nStreamLengthObject );
5133 aLine.append( " 0 R" );
5134 if (!g_bDebugDisableCompression)
5135 aLine.append( "/Filter/FlateDecode" );
5136 aLine.append( ">>\nstream\n" );
5137 if ( !updateObject( nICCObject ) ) return 0;
5138 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0;
5139 //get file position
5140 sal_uInt64 nBeginStreamPos = 0;
5141 if (osl::File::E_None != m_aFile.getPos(nBeginStreamPos))
5142 return 0;
5143 beginCompression();
5144 checkAndEnableStreamEncryption( nICCObject );
5145 cmsHPROFILE hProfile = cmsCreate_sRGBProfile();
5146 //force ICC profile version 2.1
5147 cmsSetProfileVersion(hProfile, 2.1);
5148 cmsUInt32Number nBytesNeeded = 0;
5149 cmsSaveProfileToMem(hProfile, nullptr, &nBytesNeeded);
5150 if (!nBytesNeeded)
5151 return 0;
5152 std::vector<unsigned char> aBuffer(nBytesNeeded);
5153 cmsSaveProfileToMem(hProfile, aBuffer.data(), &nBytesNeeded);
5154 cmsCloseProfile(hProfile);
5155 bool written = writeBuffer( aBuffer.data(), static_cast<sal_Int32>(aBuffer.size()) );
5156 disableStreamEncryption();
5157 endCompression();
5159 sal_uInt64 nEndStreamPos = 0;
5160 if (m_aFile.getPos(nEndStreamPos) != osl::File::E_None)
5161 return 0;
5163 if( !written )
5164 return 0;
5165 if( ! writeBuffer( "\nendstream\nendobj\n\n", 19 ) )
5166 return 0 ;
5167 aLine.setLength( 0 );
5169 //emit the stream length object
5170 if ( !updateObject( nStreamLengthObject ) ) return 0;
5171 aLine.setLength( 0 );
5172 aLine.append( nStreamLengthObject );
5173 aLine.append( " 0 obj\n" );
5174 aLine.append( static_cast<sal_Int64>(nEndStreamPos-nBeginStreamPos) );
5175 aLine.append( "\nendobj\n\n" );
5176 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0;
5177 aLine.setLength( 0 );
5179 //emit the OutputIntent dictionary
5180 sal_Int32 nOIObject = createObject();
5181 if ( !updateObject( nOIObject ) ) return 0;
5182 aLine.append( nOIObject );
5183 aLine.append( " 0 obj\n"
5184 "<</Type/OutputIntent/S/GTS_PDFA1/OutputConditionIdentifier");
5186 OUString const aComment( "sRGB IEC61966-2.1" );
5187 appendLiteralStringEncrypt( aComment ,nOIObject, aLine );
5188 aLine.append("/DestOutputProfile ");
5189 aLine.append( nICCObject );
5190 aLine.append( " 0 R>>\nendobj\n\n" );
5191 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return 0;
5193 return nOIObject;
5196 // formats the string for the XML stream
5197 static void escapeStringXML( const OUString& rStr, OUString &rValue)
5199 const sal_Unicode* pUni = rStr.getStr();
5200 int nLen = rStr.getLength();
5201 for( ; nLen; nLen--, pUni++ )
5203 switch( *pUni )
5205 case u'&':
5206 rValue += "&amp;";
5207 break;
5208 case u'<':
5209 rValue += "&lt;";
5210 break;
5211 case u'>':
5212 rValue += "&gt;";
5213 break;
5214 case u'\'':
5215 rValue += "&apos;";
5216 break;
5217 case u'"':
5218 rValue += "&quot;";
5219 break;
5220 default:
5221 rValue += OUStringChar( *pUni );
5222 break;
5227 // emits the document metadata
5228 sal_Int32 PDFWriterImpl::emitDocumentMetadata()
5230 if( !m_bIsPDF_A1 && !m_bIsPDF_A2 )
5231 return 0;
5233 //get the object number for all the destinations
5234 sal_Int32 nObject = createObject();
5236 if( updateObject( nObject ) )
5238 // the following string are written in UTF-8 unicode
5239 OStringBuffer aMetadataStream( 8192 );
5241 aMetadataStream.append( "<?xpacket begin=\"" );
5242 // these lines write Unicode "zero width non-breaking space character" (U+FEFF)
5243 // (aka byte-order mark ) used as a byte-order marker.
5244 aMetadataStream.append( OUStringToOString( OUString( u'\xFEFF' ), RTL_TEXTENCODING_UTF8 ) );
5245 aMetadataStream.append( "\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n" );
5246 aMetadataStream.append( "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\">\n" );
5247 aMetadataStream.append( " <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n" );
5248 //PDF/A part ( ISO 19005-1:2005 - 6.7.11 )
5249 aMetadataStream.append( " <rdf:Description rdf:about=\"\"\n" );
5250 aMetadataStream.append( " xmlns:pdfaid=\"http://www.aiim.org/pdfa/ns/id/\">\n" );
5251 if( m_bIsPDF_A2 )
5253 aMetadataStream.append( " <pdfaid:part>2</pdfaid:part>\n" );
5254 aMetadataStream.append( " <pdfaid:conformance>B</pdfaid:conformance>\n" );
5256 else
5258 aMetadataStream.append( " <pdfaid:part>1</pdfaid:part>\n" );
5259 aMetadataStream.append( " <pdfaid:conformance>A</pdfaid:conformance>\n" );
5261 aMetadataStream.append( " </rdf:Description>\n" );
5262 //... Dublin Core properties go here
5263 if( !m_aContext.DocumentInfo.Title.isEmpty() ||
5264 !m_aContext.DocumentInfo.Author.isEmpty() ||
5265 !m_aContext.DocumentInfo.Subject.isEmpty() )
5267 aMetadataStream.append( " <rdf:Description rdf:about=\"\"\n" );
5268 aMetadataStream.append( " xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n" );
5269 if( !m_aContext.DocumentInfo.Title.isEmpty() )
5271 // this is according to PDF/A-1, technical corrigendum 1 (2007-04-01)
5272 aMetadataStream.append( " <dc:title>\n" );
5273 aMetadataStream.append( " <rdf:Alt>\n" );
5274 aMetadataStream.append( " <rdf:li xml:lang=\"x-default\">" );
5275 OUString aTitle;
5276 escapeStringXML( m_aContext.DocumentInfo.Title, aTitle );
5277 aMetadataStream.append( OUStringToOString( aTitle, RTL_TEXTENCODING_UTF8 ) );
5278 aMetadataStream.append( "</rdf:li>\n" );
5279 aMetadataStream.append( " </rdf:Alt>\n" );
5280 aMetadataStream.append( " </dc:title>\n" );
5282 if( !m_aContext.DocumentInfo.Author.isEmpty() )
5284 aMetadataStream.append( " <dc:creator>\n" );
5285 aMetadataStream.append( " <rdf:Seq>\n" );
5286 aMetadataStream.append( " <rdf:li>" );
5287 OUString aAuthor;
5288 escapeStringXML( m_aContext.DocumentInfo.Author, aAuthor );
5289 aMetadataStream.append( OUStringToOString( aAuthor , RTL_TEXTENCODING_UTF8 ) );
5290 aMetadataStream.append( "</rdf:li>\n" );
5291 aMetadataStream.append( " </rdf:Seq>\n" );
5292 aMetadataStream.append( " </dc:creator>\n" );
5294 if( !m_aContext.DocumentInfo.Subject.isEmpty() )
5296 // this is according to PDF/A-1, technical corrigendum 1 (2007-04-01)
5297 aMetadataStream.append( " <dc:description>\n" );
5298 aMetadataStream.append( " <rdf:Alt>\n" );
5299 aMetadataStream.append( " <rdf:li xml:lang=\"x-default\">" );
5300 OUString aSubject;
5301 escapeStringXML( m_aContext.DocumentInfo.Subject, aSubject );
5302 aMetadataStream.append( OUStringToOString( aSubject , RTL_TEXTENCODING_UTF8 ) );
5303 aMetadataStream.append( "</rdf:li>\n" );
5304 aMetadataStream.append( " </rdf:Alt>\n" );
5305 aMetadataStream.append( " </dc:description>\n" );
5307 aMetadataStream.append( " </rdf:Description>\n" );
5310 //... PDF properties go here
5311 if( !m_aContext.DocumentInfo.Producer.isEmpty() ||
5312 !m_aContext.DocumentInfo.Keywords.isEmpty() )
5314 aMetadataStream.append( " <rdf:Description rdf:about=\"\"\n" );
5315 aMetadataStream.append( " xmlns:pdf=\"http://ns.adobe.com/pdf/1.3/\">\n" );
5316 if( !m_aContext.DocumentInfo.Producer.isEmpty() )
5318 aMetadataStream.append( " <pdf:Producer>" );
5319 OUString aProducer;
5320 escapeStringXML( m_aContext.DocumentInfo.Producer, aProducer );
5321 aMetadataStream.append( OUStringToOString( aProducer , RTL_TEXTENCODING_UTF8 ) );
5322 aMetadataStream.append( "</pdf:Producer>\n" );
5324 if( !m_aContext.DocumentInfo.Keywords.isEmpty() )
5326 aMetadataStream.append( " <pdf:Keywords>" );
5327 OUString aKeywords;
5328 escapeStringXML( m_aContext.DocumentInfo.Keywords, aKeywords );
5329 aMetadataStream.append( OUStringToOString( aKeywords , RTL_TEXTENCODING_UTF8 ) );
5330 aMetadataStream.append( "</pdf:Keywords>\n" );
5332 aMetadataStream.append( " </rdf:Description>\n" );
5335 aMetadataStream.append( " <rdf:Description rdf:about=\"\"\n" );
5336 aMetadataStream.append( " xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\">\n" );
5337 if( !m_aContext.DocumentInfo.Creator.isEmpty() )
5339 aMetadataStream.append( " <xmp:CreatorTool>" );
5340 OUString aCreator;
5341 escapeStringXML( m_aContext.DocumentInfo.Creator, aCreator );
5342 aMetadataStream.append( OUStringToOString( aCreator , RTL_TEXTENCODING_UTF8 ) );
5343 aMetadataStream.append( "</xmp:CreatorTool>\n" );
5345 //creation date
5346 aMetadataStream.append( " <xmp:CreateDate>" );
5347 aMetadataStream.append( m_aCreationMetaDateString );
5348 aMetadataStream.append( "</xmp:CreateDate>\n" );
5350 aMetadataStream.append( " </rdf:Description>\n" );
5351 aMetadataStream.append( " </rdf:RDF>\n" );
5352 aMetadataStream.append( "</x:xmpmeta>\n" );
5354 //add the padding
5355 for( sal_Int32 nSpaces = 1; nSpaces <= 2100; nSpaces++ )
5357 aMetadataStream.append( " " );
5358 if( nSpaces % 100 == 0 )
5359 aMetadataStream.append( "\n" );
5362 aMetadataStream.append( "<?xpacket end=\"w\"?>\n" );
5364 OStringBuffer aMetadataObj( 1024 );
5366 aMetadataObj.append( nObject );
5367 aMetadataObj.append( " 0 obj\n" );
5369 aMetadataObj.append( "<</Type/Metadata/Subtype/XML/Length " );
5371 aMetadataObj.append( aMetadataStream.getLength() );
5372 aMetadataObj.append( ">>\nstream\n" );
5373 if ( !writeBuffer( aMetadataObj.getStr(), aMetadataObj.getLength() ) )
5374 return 0;
5375 //emit the stream
5376 if ( !writeBuffer( aMetadataStream.getStr(), aMetadataStream.getLength() ) )
5377 return 0;
5379 aMetadataObj.setLength( 0 );
5380 aMetadataObj.append( "\nendstream\nendobj\n\n" );
5381 if( ! writeBuffer( aMetadataObj.getStr(), aMetadataObj.getLength() ) )
5382 nObject = 0;
5384 else
5385 nObject = 0;
5387 return nObject;
5390 bool PDFWriterImpl::emitTrailer()
5392 // emit doc info
5393 sal_Int32 nDocInfoObject = emitInfoDict( );
5395 sal_Int32 nSecObject = 0;
5397 if( m_aContext.Encryption.Encrypt() )
5399 //emit the security information
5400 //must be emitted as indirect dictionary object, since
5401 //Acrobat Reader 5 works only with this kind of implementation
5402 nSecObject = createObject();
5404 if( updateObject( nSecObject ) )
5406 OStringBuffer aLineS( 1024 );
5407 aLineS.append( nSecObject );
5408 aLineS.append( " 0 obj\n"
5409 "<</Filter/Standard/V " );
5410 // check the version
5411 aLineS.append( "2/Length 128/R 3" );
5413 // emit the owner password, must not be encrypted
5414 aLineS.append( "/O(" );
5415 appendLiteralString( reinterpret_cast<char*>(m_aContext.Encryption.OValue.data()), sal_Int32(m_aContext.Encryption.OValue.size()), aLineS );
5416 aLineS.append( ")/U(" );
5417 appendLiteralString( reinterpret_cast<char*>(m_aContext.Encryption.UValue.data()), sal_Int32(m_aContext.Encryption.UValue.size()), aLineS );
5418 aLineS.append( ")/P " );// the permission set
5419 aLineS.append( m_nAccessPermissions );
5420 aLineS.append( ">>\nendobj\n\n" );
5421 if( !writeBuffer( aLineS.getStr(), aLineS.getLength() ) )
5422 nSecObject = 0;
5424 else
5425 nSecObject = 0;
5427 // emit xref table
5428 // remember start
5429 sal_uInt64 nXRefOffset = 0;
5430 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nXRefOffset )) );
5431 CHECK_RETURN( writeBuffer( "xref\n", 5 ) );
5433 sal_Int32 nObjects = m_aObjects.size();
5434 OStringBuffer aLine;
5435 aLine.append( "0 " );
5436 aLine.append( static_cast<sal_Int32>(nObjects+1) );
5437 aLine.append( "\n" );
5438 aLine.append( "0000000000 65535 f \n" );
5439 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
5441 for( sal_Int32 i = 0; i < nObjects; i++ )
5443 aLine.setLength( 0 );
5444 OString aOffset = OString::number( m_aObjects[i] );
5445 for( sal_Int32 j = 0; j < (10-aOffset.getLength()); j++ )
5446 aLine.append( '0' );
5447 aLine.append( aOffset );
5448 aLine.append( " 00000 n \n" );
5449 SAL_WARN_IF( aLine.getLength() != 20, "vcl.pdfwriter", "invalid xref entry" );
5450 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
5453 // prepare document checksum
5454 OStringBuffer aDocChecksum( 2*RTL_DIGEST_LENGTH_MD5+1 );
5455 ::std::vector<unsigned char> const nMD5Sum(m_DocDigest.finalize());
5456 for (sal_uInt8 i : nMD5Sum)
5457 appendHex( i, aDocChecksum );
5458 // document id set in setDocInfo method
5459 // emit trailer
5460 aLine.setLength( 0 );
5461 aLine.append( "trailer\n"
5462 "<</Size " );
5463 aLine.append( static_cast<sal_Int32>(nObjects+1) );
5464 aLine.append( "/Root " );
5465 aLine.append( m_nCatalogObject );
5466 aLine.append( " 0 R\n" );
5467 if( nSecObject )
5469 aLine.append( "/Encrypt ");
5470 aLine.append( nSecObject );
5471 aLine.append( " 0 R\n" );
5473 if( nDocInfoObject )
5475 aLine.append( "/Info " );
5476 aLine.append( nDocInfoObject );
5477 aLine.append( " 0 R\n" );
5479 if( ! m_aContext.Encryption.DocumentIdentifier.empty() )
5481 aLine.append( "/ID [ <" );
5482 for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
5484 appendHex( sal_Int8(item), aLine );
5486 aLine.append( ">\n"
5487 "<" );
5488 for (auto const& item : m_aContext.Encryption.DocumentIdentifier)
5490 appendHex( sal_Int8(item), aLine );
5492 aLine.append( "> ]\n" );
5494 if( !aDocChecksum.isEmpty() )
5496 aLine.append( "/DocChecksum /" );
5497 aLine.append( aDocChecksum.makeStringAndClear() );
5498 aLine.append( "\n" );
5500 if( !m_aAdditionalStreams.empty() )
5502 aLine.append( "/AdditionalStreams [" );
5503 for(const PDFAddStream & rAdditionalStream : m_aAdditionalStreams)
5505 aLine.append( "/" );
5506 appendName( rAdditionalStream.m_aMimeType, aLine );
5507 aLine.append( " " );
5508 aLine.append( rAdditionalStream.m_nStreamObject );
5509 aLine.append( " 0 R\n" );
5511 aLine.append( "]\n" );
5513 aLine.append( ">>\n"
5514 "startxref\n" );
5515 aLine.append( static_cast<sal_Int64>(nXRefOffset) );
5516 aLine.append( "\n"
5517 "%%EOF\n" );
5518 return writeBuffer( aLine.getStr(), aLine.getLength() );
5521 struct AnnotationSortEntry
5523 sal_Int32 nTabOrder;
5524 sal_Int32 nObject;
5525 sal_Int32 nWidgetIndex;
5527 AnnotationSortEntry( sal_Int32 nTab, sal_Int32 nObj, sal_Int32 nI ) :
5528 nTabOrder( nTab ),
5529 nObject( nObj ),
5530 nWidgetIndex( nI )
5534 struct AnnotSortContainer
5536 std::set< sal_Int32 > aObjects;
5537 std::vector< AnnotationSortEntry > aSortedAnnots;
5540 struct AnnotSorterLess
5542 std::vector< PDFWriterImpl::PDFWidget >& m_rWidgets;
5544 explicit AnnotSorterLess( std::vector< PDFWriterImpl::PDFWidget >& rWidgets ) : m_rWidgets( rWidgets ) {}
5546 bool operator()( const AnnotationSortEntry& rLeft, const AnnotationSortEntry& rRight )
5548 if( rLeft.nTabOrder < rRight.nTabOrder )
5549 return true;
5550 if( rRight.nTabOrder < rLeft.nTabOrder )
5551 return false;
5552 if( rLeft.nWidgetIndex < 0 && rRight.nWidgetIndex < 0 )
5553 return false;
5554 if( rRight.nWidgetIndex < 0 )
5555 return true;
5556 if( rLeft.nWidgetIndex < 0 )
5557 return false;
5558 // remember: widget rects are in PDF coordinates, so they are ordered down up
5559 if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() >
5560 m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() )
5561 return true;
5562 if( m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() >
5563 m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() )
5564 return false;
5565 if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Left() <
5566 m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Left() )
5567 return true;
5568 return false;
5572 void PDFWriterImpl::sortWidgets()
5574 // sort widget annotations on each page as per their
5575 // TabOrder attribute
5576 std::unordered_map< sal_Int32, AnnotSortContainer > sorted;
5577 int nWidgets = m_aWidgets.size();
5578 for( int nW = 0; nW < nWidgets; nW++ )
5580 const PDFWidget& rWidget = m_aWidgets[nW];
5581 if( rWidget.m_nPage >= 0 )
5583 AnnotSortContainer& rCont = sorted[ rWidget.m_nPage ];
5584 // optimize vector allocation
5585 if( rCont.aSortedAnnots.empty() )
5586 rCont.aSortedAnnots.reserve( m_aPages[ rWidget.m_nPage ].m_aAnnotations.size() );
5587 // insert widget to tab sorter
5588 // RadioButtons are not page annotations, only their individual check boxes are
5589 if( rWidget.m_eType != PDFWriter::RadioButton )
5591 rCont.aObjects.insert( rWidget.m_nObject );
5592 rCont.aSortedAnnots.emplace_back( rWidget.m_nTabOrder, rWidget.m_nObject, nW );
5596 for (auto & item : sorted)
5598 // append entries for non widget annotations
5599 PDFPage& rPage = m_aPages[ item.first ];
5600 unsigned int nAnnots = rPage.m_aAnnotations.size();
5601 for( unsigned int nA = 0; nA < nAnnots; nA++ )
5602 if( item.second.aObjects.find( rPage.m_aAnnotations[nA] ) == item.second.aObjects.end())
5603 item.second.aSortedAnnots.emplace_back( 10000, rPage.m_aAnnotations[nA], -1 );
5605 AnnotSorterLess aLess( m_aWidgets );
5606 std::stable_sort( item.second.aSortedAnnots.begin(), item.second.aSortedAnnots.end(), aLess );
5607 // sanity check
5608 if( item.second.aSortedAnnots.size() == nAnnots)
5610 for( unsigned int nA = 0; nA < nAnnots; nA++ )
5611 rPage.m_aAnnotations[nA] = item.second.aSortedAnnots[nA].nObject;
5613 else
5615 SAL_WARN( "vcl.pdfwriter", "wrong number of sorted annotations" );
5616 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::sortWidgets(): wrong number of sorted assertions "
5617 "on page nr " << item.first << ", " <<
5618 static_cast<long int>(item.second.aSortedAnnots.size()) << " sorted and " <<
5619 static_cast<long int>(nAnnots) << " unsorted");
5623 // FIXME: implement tab order in structure tree for PDF 1.5
5626 namespace vcl {
5627 class PDFStreamIf :
5628 public cppu::WeakImplHelper< css::io::XOutputStream >
5630 VclPtr<PDFWriterImpl> m_pWriter;
5631 bool m_bWrite;
5632 public:
5633 explicit PDFStreamIf( PDFWriterImpl* pWriter ) : m_pWriter( pWriter ), m_bWrite( true ) {}
5635 virtual void SAL_CALL writeBytes( const css::uno::Sequence< sal_Int8 >& aData ) override;
5636 virtual void SAL_CALL flush() override;
5637 virtual void SAL_CALL closeOutput() override;
5641 void SAL_CALL PDFStreamIf::writeBytes( const css::uno::Sequence< sal_Int8 >& aData )
5643 if( m_bWrite && aData.hasElements() )
5645 sal_Int32 nBytes = aData.getLength();
5646 m_pWriter->writeBuffer( aData.getConstArray(), nBytes );
5650 void SAL_CALL PDFStreamIf::flush()
5654 void SAL_CALL PDFStreamIf::closeOutput()
5656 m_bWrite = false;
5659 bool PDFWriterImpl::emitAdditionalStreams()
5661 unsigned int nStreams = m_aAdditionalStreams.size();
5662 for( unsigned int i = 0; i < nStreams; i++ )
5664 PDFAddStream& rStream = m_aAdditionalStreams[i];
5665 rStream.m_nStreamObject = createObject();
5666 sal_Int32 nSizeObject = createObject();
5668 if( ! updateObject( rStream.m_nStreamObject ) )
5669 return false;
5671 OStringBuffer aLine;
5672 aLine.append( rStream.m_nStreamObject );
5673 aLine.append( " 0 obj\n<</Length " );
5674 aLine.append( nSizeObject );
5675 aLine.append( " 0 R" );
5676 if( rStream.m_bCompress )
5677 aLine.append( "/Filter/FlateDecode" );
5678 aLine.append( ">>\nstream\n" );
5679 if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
5680 return false;
5681 sal_uInt64 nBeginStreamPos = 0, nEndStreamPos = 0;
5682 if( osl::File::E_None != m_aFile.getPos(nBeginStreamPos) )
5684 m_aFile.close();
5685 m_bOpen = false;
5687 if( rStream.m_bCompress )
5688 beginCompression();
5690 checkAndEnableStreamEncryption( rStream.m_nStreamObject );
5691 css::uno::Reference< css::io::XOutputStream > xStream( new PDFStreamIf( this ) );
5692 assert(rStream.m_pStream);
5693 if (!rStream.m_pStream)
5694 return false;
5695 rStream.m_pStream->write( xStream );
5696 xStream.clear();
5697 delete rStream.m_pStream;
5698 rStream.m_pStream = nullptr;
5699 disableStreamEncryption();
5701 if( rStream.m_bCompress )
5702 endCompression();
5704 if (osl::File::E_None != m_aFile.getPos(nEndStreamPos))
5706 m_aFile.close();
5707 m_bOpen = false;
5708 return false;
5710 if( ! writeBuffer( "\nendstream\nendobj\n\n", 19 ) )
5711 return false ;
5712 // emit stream length object
5713 if( ! updateObject( nSizeObject ) )
5714 return false;
5715 aLine.setLength( 0 );
5716 aLine.append( nSizeObject );
5717 aLine.append( " 0 obj\n" );
5718 aLine.append( static_cast<sal_Int64>(nEndStreamPos-nBeginStreamPos) );
5719 aLine.append( "\nendobj\n\n" );
5720 if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
5721 return false;
5723 return true;
5726 bool PDFWriterImpl::emit()
5728 endPage();
5730 // resort structure tree and annotations if necessary
5731 // needed for widget tab order
5732 sortWidgets();
5734 #if HAVE_FEATURE_NSS
5735 if( m_aContext.SignPDF )
5737 // sign the document
5738 PDFWriter::SignatureWidget aSignature;
5739 aSignature.Name = "Signature1";
5740 createControl( aSignature, 0 );
5742 #endif
5744 // emit additional streams
5745 CHECK_RETURN( emitAdditionalStreams() );
5747 // emit catalog
5748 CHECK_RETURN( emitCatalog() );
5750 #if HAVE_FEATURE_NSS
5751 if (m_nSignatureObject != -1) // if document is signed, emit sigdict
5753 if( !emitSignature() )
5755 m_aErrors.insert( PDFWriter::Error_Signature_Failed );
5756 return false;
5759 #endif
5761 // emit trailer
5762 CHECK_RETURN( emitTrailer() );
5764 #if HAVE_FEATURE_NSS
5765 if (m_nSignatureObject != -1) // finalize the signature
5767 if( !finalizeSignature() )
5769 m_aErrors.insert( PDFWriter::Error_Signature_Failed );
5770 return false;
5773 #endif
5775 m_aFile.close();
5776 m_bOpen = false;
5778 return true;
5782 sal_Int32 PDFWriterImpl::getSystemFont( const vcl::Font& i_rFont )
5784 Push();
5786 SetFont( i_rFont );
5788 const PhysicalFontFace* pDevFont = GetFontInstance()->GetFontFace();
5789 sal_Int32 nFontID = 0;
5790 FontEmbedData::iterator it = m_aSystemFonts.find( pDevFont );
5791 if( it != m_aSystemFonts.end() )
5792 nFontID = it->second.m_nNormalFontID;
5793 else
5795 nFontID = m_nNextFID++;
5796 m_aSystemFonts[ pDevFont ] = EmbedFont();
5797 m_aSystemFonts[ pDevFont ].m_nNormalFontID = nFontID;
5800 Pop();
5801 return nFontID;
5804 void PDFWriterImpl::registerGlyph(const GlyphItem* pGlyph,
5805 const PhysicalFontFace* pFont,
5806 const std::vector<sal_Ucs>& rCodeUnits,
5807 sal_uInt8& nMappedGlyph,
5808 sal_Int32& nMappedFontObject)
5810 const int nFontGlyphId = pGlyph->glyphId();
5811 FontSubset& rSubset = m_aSubsets[ pFont ];
5812 // search for font specific glyphID
5813 FontMapping::iterator it = rSubset.m_aMapping.find( nFontGlyphId );
5814 if( it != rSubset.m_aMapping.end() )
5816 nMappedFontObject = it->second.m_nFontID;
5817 nMappedGlyph = it->second.m_nSubsetGlyphID;
5819 else
5821 // create new subset if necessary
5822 if( rSubset.m_aSubsets.empty()
5823 || (rSubset.m_aSubsets.back().m_aMapping.size() > 254) )
5825 rSubset.m_aSubsets.emplace_back( m_nNextFID++ );
5828 // copy font id
5829 nMappedFontObject = rSubset.m_aSubsets.back().m_nFontID;
5830 // create new glyph in subset
5831 sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(rSubset.m_aSubsets.back().m_aMapping.size()+1);
5832 nMappedGlyph = nNewId;
5834 // add new glyph to emitted font subset
5835 GlyphEmit& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[ nFontGlyphId ];
5836 rNewGlyphEmit.setGlyphId( nNewId );
5837 for (const auto nCode : rCodeUnits)
5838 rNewGlyphEmit.addCode(nCode);
5840 // add new glyph to font mapping
5841 Glyph& rNewGlyph = rSubset.m_aMapping[ nFontGlyphId ];
5842 rNewGlyph.m_nFontID = nMappedFontObject;
5843 rNewGlyph.m_nSubsetGlyphID = nNewId;
5847 void PDFWriterImpl::drawRelief( SalLayout& rLayout, const OUString& rText, bool bTextLines )
5849 push( PushFlags::ALL );
5851 FontRelief eRelief = m_aCurrentPDFState.m_aFont.GetRelief();
5853 Color aTextColor = m_aCurrentPDFState.m_aFont.GetColor();
5854 Color aTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
5855 Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
5856 Color aReliefColor( COL_LIGHTGRAY );
5857 if( aTextColor == COL_BLACK )
5858 aTextColor = COL_WHITE;
5859 if( aTextLineColor == COL_BLACK )
5860 aTextLineColor = COL_WHITE;
5861 if( aOverlineColor == COL_BLACK )
5862 aOverlineColor = COL_WHITE;
5863 // coverity[copy_paste_error : FALSE] - aReliefColor depending on aTextColor is correct
5864 if( aTextColor == COL_WHITE )
5865 aReliefColor = COL_BLACK;
5867 Font aSetFont = m_aCurrentPDFState.m_aFont;
5868 aSetFont.SetRelief( FontRelief::NONE );
5869 aSetFont.SetShadow( false );
5871 aSetFont.SetColor( aReliefColor );
5872 setTextLineColor( aReliefColor );
5873 setOverlineColor( aReliefColor );
5874 setFont( aSetFont );
5875 long nOff = 1 + GetDPIX()/300;
5876 if( eRelief == FontRelief::Engraved )
5877 nOff = -nOff;
5879 rLayout.DrawOffset() += Point( nOff, nOff );
5880 updateGraphicsState();
5881 drawLayout( rLayout, rText, bTextLines );
5883 rLayout.DrawOffset() -= Point( nOff, nOff );
5884 setTextLineColor( aTextLineColor );
5885 setOverlineColor( aOverlineColor );
5886 aSetFont.SetColor( aTextColor );
5887 setFont( aSetFont );
5888 updateGraphicsState();
5889 drawLayout( rLayout, rText, bTextLines );
5891 // clean up the mess
5892 pop();
5895 void PDFWriterImpl::drawShadow( SalLayout& rLayout, const OUString& rText, bool bTextLines )
5897 Font aSaveFont = m_aCurrentPDFState.m_aFont;
5898 Color aSaveTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
5899 Color aSaveOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
5901 Font& rFont = m_aCurrentPDFState.m_aFont;
5902 if( rFont.GetColor() == COL_BLACK || rFont.GetColor().GetLuminance() < 8 )
5903 rFont.SetColor( COL_LIGHTGRAY );
5904 else
5905 rFont.SetColor( COL_BLACK );
5906 rFont.SetShadow( false );
5907 rFont.SetOutline( false );
5908 setFont( rFont );
5909 setTextLineColor( rFont.GetColor() );
5910 setOverlineColor( rFont.GetColor() );
5911 updateGraphicsState();
5913 long nOff = 1 + ((GetFontInstance()->mnLineHeight-24)/24);
5914 if( rFont.IsOutline() )
5915 nOff++;
5916 rLayout.DrawBase() += Point( nOff, nOff );
5917 drawLayout( rLayout, rText, bTextLines );
5918 rLayout.DrawBase() -= Point( nOff, nOff );
5920 setFont( aSaveFont );
5921 setTextLineColor( aSaveTextLineColor );
5922 setOverlineColor( aSaveOverlineColor );
5923 updateGraphicsState();
5926 void PDFWriterImpl::drawVerticalGlyphs(
5927 const std::vector<PDFWriterImpl::PDFGlyph>& rGlyphs,
5928 OStringBuffer& rLine,
5929 const Point& rAlignOffset,
5930 const Matrix3& rRotScale,
5931 double fAngle,
5932 double fXScale,
5933 double fSkew,
5934 sal_Int32 nFontHeight )
5936 long nXOffset = 0;
5937 Point aCurPos( rGlyphs[0].m_aPos );
5938 aCurPos = PixelToLogic( aCurPos );
5939 aCurPos += rAlignOffset;
5940 for( size_t i = 0; i < rGlyphs.size(); i++ )
5942 // have to emit each glyph on its own
5943 double fDeltaAngle = 0.0;
5944 double fYScale = 1.0;
5945 double fTempXScale = fXScale;
5946 double fSkewB = fSkew;
5947 double fSkewA = 0.0;
5949 Point aDeltaPos;
5950 if (rGlyphs[i].m_pGlyph->IsVertical())
5952 fDeltaAngle = M_PI/2.0;
5953 aDeltaPos.setX( GetFontMetric().GetAscent() );
5954 aDeltaPos.setY( static_cast<int>(static_cast<double>(GetFontMetric().GetDescent()) * fXScale) );
5955 fYScale = fXScale;
5956 fTempXScale = 1.0;
5957 fSkewA = -fSkewB;
5958 fSkewB = 0.0;
5960 aDeltaPos += PixelToLogic( Point( static_cast<int>(static_cast<double>(nXOffset)/fXScale), 0 ) ) - PixelToLogic( Point() );
5961 if( i < rGlyphs.size()-1 )
5962 // #i120627# the text on the Y axis is reversed when export ppt file to PDF format
5964 long nOffsetX = rGlyphs[i+1].m_aPos.X() - rGlyphs[i].m_aPos.X();
5965 long nOffsetY = rGlyphs[i+1].m_aPos.Y() - rGlyphs[i].m_aPos.Y();
5966 nXOffset += static_cast<int>(sqrt(double(nOffsetX*nOffsetX + nOffsetY*nOffsetY)));
5968 if (!rGlyphs[i].m_pGlyph->glyphId())
5969 continue;
5971 aDeltaPos = rRotScale.transform( aDeltaPos );
5973 Matrix3 aMat;
5974 if( fSkewB != 0.0 || fSkewA != 0.0 )
5975 aMat.skew( fSkewA, fSkewB );
5976 aMat.scale( fTempXScale, fYScale );
5977 aMat.rotate( fAngle+fDeltaAngle );
5978 aMat.translate( aCurPos.X()+aDeltaPos.X(), aCurPos.Y()+aDeltaPos.Y() );
5979 aMat.append( m_aPages.back(), rLine );
5980 rLine.append( " Tm" );
5981 if( i == 0 || rGlyphs[i-1].m_nMappedFontId != rGlyphs[i].m_nMappedFontId )
5983 rLine.append( " /F" );
5984 rLine.append( rGlyphs[i].m_nMappedFontId );
5985 rLine.append( ' ' );
5986 m_aPages.back().appendMappedLength( nFontHeight, rLine );
5987 rLine.append( " Tf" );
5989 rLine.append( "<" );
5990 appendHex( rGlyphs[i].m_nMappedGlyphId, rLine );
5991 rLine.append( ">Tj\n" );
5995 void PDFWriterImpl::drawHorizontalGlyphs(
5996 const std::vector<PDFWriterImpl::PDFGlyph>& rGlyphs,
5997 OStringBuffer& rLine,
5998 const Point& rAlignOffset,
5999 bool bFirst,
6000 double fAngle,
6001 double fXScale,
6002 double fSkew,
6003 sal_Int32 nFontHeight,
6004 sal_Int32 nPixelFontHeight
6007 // horizontal (= normal) case
6009 // fill in run end indices
6010 // end is marked by index of the first glyph of the next run
6011 // a run is marked by same mapped font id and same Y position
6012 std::vector< sal_uInt32 > aRunEnds;
6013 aRunEnds.reserve( rGlyphs.size() );
6014 for( size_t i = 1; i < rGlyphs.size(); i++ )
6016 if( rGlyphs[i].m_nMappedFontId != rGlyphs[i-1].m_nMappedFontId ||
6017 rGlyphs[i].m_aPos.Y() != rGlyphs[i-1].m_aPos.Y() )
6019 aRunEnds.push_back(i);
6022 // last run ends at last glyph
6023 aRunEnds.push_back( rGlyphs.size() );
6025 // loop over runs of the same font
6026 sal_uInt32 nBeginRun = 0;
6027 for( size_t nRun = 0; nRun < aRunEnds.size(); nRun++ )
6029 // setup text matrix
6030 Point aCurPos = rGlyphs[nBeginRun].m_aPos;
6031 // back transformation to current coordinate system
6032 aCurPos = PixelToLogic( aCurPos );
6033 aCurPos += rAlignOffset;
6034 // the first run can be set with "Td" operator
6035 // subsequent use of that operator would move
6036 // the textline matrix relative to what was set before
6037 // making use of that would drive us into rounding issues
6038 Matrix3 aMat;
6039 if( bFirst && nRun == 0 && fAngle == 0.0 && fXScale == 1.0 && fSkew == 0.0 )
6041 m_aPages.back().appendPoint( aCurPos, rLine );
6042 rLine.append( " Td " );
6044 else
6046 if( fSkew != 0.0 )
6047 aMat.skew( 0.0, fSkew );
6048 aMat.scale( fXScale, 1.0 );
6049 aMat.rotate( fAngle );
6050 aMat.translate( aCurPos.X(), aCurPos.Y() );
6051 aMat.append( m_aPages.back(), rLine );
6052 rLine.append( " Tm\n" );
6054 // set up correct font
6055 rLine.append( "/F" );
6056 rLine.append( rGlyphs[nBeginRun].m_nMappedFontId );
6057 rLine.append( ' ' );
6058 m_aPages.back().appendMappedLength( nFontHeight, rLine );
6059 rLine.append( " Tf" );
6061 // output glyphs using Tj or TJ
6062 OStringBuffer aKernedLine( 256 ), aUnkernedLine( 256 );
6063 aKernedLine.append( "[<" );
6064 aUnkernedLine.append( '<' );
6065 appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aKernedLine );
6066 appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aUnkernedLine );
6068 aMat.invert();
6069 bool bNeedKern = false;
6070 for( sal_uInt32 nPos = nBeginRun+1; nPos < aRunEnds[nRun]; nPos++ )
6072 appendHex( rGlyphs[nPos].m_nMappedGlyphId, aUnkernedLine );
6073 // check if default glyph positioning is sufficient
6074 const Point aThisPos = aMat.transform( rGlyphs[nPos].m_aPos );
6075 const Point aPrevPos = aMat.transform( rGlyphs[nPos-1].m_aPos );
6076 double fAdvance = aThisPos.X() - aPrevPos.X();
6077 fAdvance *= 1000.0 / nPixelFontHeight;
6078 const double fAdjustment = rGlyphs[nPos-1].m_nNativeWidth - fAdvance + 0.5;
6079 SAL_WARN_IF(
6080 fAdjustment < SAL_MIN_INT32 || fAdjustment > SAL_MAX_INT32, "vcl.pdfwriter",
6081 "adjustment " << fAdjustment << " outside 32-bit int");
6082 const sal_Int32 nAdjustment = static_cast<sal_Int32>(
6083 std::clamp(fAdjustment, double(SAL_MIN_INT32), double(SAL_MAX_INT32)));
6084 if( nAdjustment != 0 )
6086 // apply individual glyph positioning
6087 bNeedKern = true;
6088 aKernedLine.append( ">" );
6089 aKernedLine.append( nAdjustment );
6090 aKernedLine.append( "<" );
6092 appendHex( rGlyphs[nPos].m_nMappedGlyphId, aKernedLine );
6094 aKernedLine.append( ">]TJ\n" );
6095 aUnkernedLine.append( ">Tj\n" );
6096 rLine.append(
6097 (bNeedKern ? aKernedLine : aUnkernedLine).makeStringAndClear() );
6099 // set beginning of next run
6100 nBeginRun = aRunEnds[nRun];
6104 void PDFWriterImpl::drawLayout( SalLayout& rLayout, const OUString& rText, bool bTextLines )
6106 // relief takes precedence over shadow (see outdev3.cxx)
6107 if( m_aCurrentPDFState.m_aFont.GetRelief() != FontRelief::NONE )
6109 drawRelief( rLayout, rText, bTextLines );
6110 return;
6112 else if( m_aCurrentPDFState.m_aFont.IsShadow() )
6113 drawShadow( rLayout, rText, bTextLines );
6115 OStringBuffer aLine( 512 );
6117 const int nMaxGlyphs = 256;
6119 std::vector<sal_Ucs> aCodeUnits;
6120 bool bVertical = m_aCurrentPDFState.m_aFont.IsVertical();
6121 int nIndex = 0;
6122 double fXScale = 1.0;
6123 double fSkew = 0.0;
6124 sal_Int32 nPixelFontHeight = GetFontInstance()->GetFontSelectPattern().mnHeight;
6125 TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
6127 // transform font height back to current units
6128 // note: the layout calculates in outdevs device pixel !!
6129 sal_Int32 nFontHeight = ImplDevicePixelToLogicHeight( nPixelFontHeight );
6130 if( m_aCurrentPDFState.m_aFont.GetAverageFontWidth() )
6132 Font aFont( m_aCurrentPDFState.m_aFont );
6133 aFont.SetAverageFontWidth( 0 );
6134 FontMetric aMetric = GetFontMetric( aFont );
6135 if( aMetric.GetAverageFontWidth() != m_aCurrentPDFState.m_aFont.GetAverageFontWidth() )
6137 fXScale =
6138 static_cast<double>(m_aCurrentPDFState.m_aFont.GetAverageFontWidth()) /
6139 static_cast<double>(aMetric.GetAverageFontWidth());
6143 // perform artificial italics if necessary
6144 if( ( m_aCurrentPDFState.m_aFont.GetItalic() == ITALIC_NORMAL ||
6145 m_aCurrentPDFState.m_aFont.GetItalic() == ITALIC_OBLIQUE ) &&
6146 !( GetFontInstance()->GetFontFace()->GetItalic() == ITALIC_NORMAL ||
6147 GetFontInstance()->GetFontFace()->GetItalic() == ITALIC_OBLIQUE )
6150 fSkew = M_PI/12.0;
6153 // if the mapmode is distorted we need to adjust for that also
6154 if( m_aCurrentPDFState.m_aMapMode.GetScaleX() != m_aCurrentPDFState.m_aMapMode.GetScaleY() )
6156 fXScale *= double(m_aCurrentPDFState.m_aMapMode.GetScaleX()) / double(m_aCurrentPDFState.m_aMapMode.GetScaleY());
6159 int nAngle = m_aCurrentPDFState.m_aFont.GetOrientation();
6160 // normalize angles
6161 while( nAngle < 0 )
6162 nAngle += 3600;
6163 nAngle = nAngle % 3600;
6164 double fAngle = static_cast<double>(nAngle) * M_PI / 1800.0;
6166 Matrix3 aRotScale;
6167 aRotScale.scale( fXScale, 1.0 );
6168 if( fAngle != 0.0 )
6169 aRotScale.rotate( -fAngle );
6171 bool bPop = false;
6172 bool bABold = false;
6173 // artificial bold necessary ?
6174 if( GetFontInstance()->GetFontFace()->GetWeight() <= WEIGHT_MEDIUM &&
6175 GetFontInstance()->GetFontSelectPattern().GetWeight() > WEIGHT_MEDIUM )
6177 aLine.append("q ");
6178 bPop = true;
6179 bABold = true;
6181 // setup text colors (if necessary)
6182 Color aStrokeColor( COL_TRANSPARENT );
6183 Color aNonStrokeColor( COL_TRANSPARENT );
6185 if( m_aCurrentPDFState.m_aFont.IsOutline() )
6187 aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6188 aNonStrokeColor = COL_WHITE;
6190 else
6191 aNonStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6192 if( bABold )
6193 aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
6195 if( aStrokeColor != COL_TRANSPARENT && aStrokeColor != m_aCurrentPDFState.m_aLineColor )
6197 if( ! bPop )
6198 aLine.append( "q " );
6199 bPop = true;
6200 appendStrokingColor( aStrokeColor, aLine );
6201 aLine.append( "\n" );
6203 if( aNonStrokeColor != COL_TRANSPARENT && aNonStrokeColor != m_aCurrentPDFState.m_aFillColor )
6205 if( ! bPop )
6206 aLine.append( "q " );
6207 bPop = true;
6208 appendNonStrokingColor( aNonStrokeColor, aLine );
6209 aLine.append( "\n" );
6212 // begin text object
6213 aLine.append( "BT\n" );
6214 // outline attribute ?
6215 if( m_aCurrentPDFState.m_aFont.IsOutline() || bABold )
6217 // set correct text mode, set stroke width
6218 aLine.append( "2 Tr " ); // fill, then stroke
6220 if( m_aCurrentPDFState.m_aFont.IsOutline() )
6222 // unclear what to do in case of outline and artificial bold
6223 // for the time being outline wins
6224 aLine.append( "0.25 w \n" );
6226 else
6228 double fW = static_cast<double>(m_aCurrentPDFState.m_aFont.GetFontHeight()) / 30.0;
6229 m_aPages.back().appendMappedLength( fW, aLine );
6230 aLine.append ( " w\n" );
6234 FontMetric aRefDevFontMetric = GetFontMetric();
6235 const PhysicalFontFace* pDevFont = GetFontInstance()->GetFontFace();
6236 const GlyphItem* pGlyph = nullptr;
6237 const PhysicalFontFace* pFallbackFont = nullptr;
6239 // collect the glyphs into a single array
6240 std::vector< PDFGlyph > aGlyphs;
6241 aGlyphs.reserve( nMaxGlyphs );
6242 // first get all the glyphs and register them; coordinates still in Pixel
6243 Point aPos;
6244 while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pFallbackFont))
6246 const auto* pFont = pFallbackFont ? pFallbackFont : pDevFont;
6248 aCodeUnits.clear();
6250 // tdf#66597, tdf#115117
6252 // Here is how we embed textual content in PDF files, to allow for
6253 // better text extraction for complex and typography-rich text.
6255 // * If there is many to one or many to many mapping, use an
6256 // ActualText span embedding the original string, since ToUnicode
6257 // can’t handle these.
6258 // * If the one glyph is used for several Unicode code points, also
6259 // use ActualText since ToUnicode can map each glyph in the font
6260 // only once.
6261 // * Limit ActualText to single cluster at a time, since using it
6262 // for whole words or sentences breaks text selection and
6263 // highlighting in PDF viewers (there will be no way to tell
6264 // which glyphs belong to which characters).
6265 // * Keep generating (now) redundant ToUnicode entries for
6266 // compatibility with old tools not supporting ActualText.
6268 assert(pGlyph->charCount() >= 0);
6269 for (int n = 0; n < pGlyph->charCount(); n++)
6270 aCodeUnits.push_back(rText[pGlyph->charPos() + n]);
6272 bool bUseActualText = false;
6274 // If this is a start of complex cluster, use ActualText.
6275 if (pGlyph->IsClusterStart())
6276 bUseActualText = true;
6278 // Or part of a complex cluster, will be handled by the ActualText
6279 // of its cluster start.
6280 if (pGlyph->IsInCluster())
6281 assert(aCodeUnits.empty());
6283 // A glyph can’t have more than one ToUnicode entry, use ActualText
6284 // instead.
6285 if (!aCodeUnits.empty() && !bUseActualText)
6287 for (const auto& rSubset : m_aSubsets[pFont].m_aSubsets)
6289 const auto& it = rSubset.m_aMapping.find(pGlyph->glyphId());
6290 if (it != rSubset.m_aMapping.cend() && it->second.codes() != aCodeUnits)
6292 bUseActualText = true;
6293 aCodeUnits.clear();
6298 assert(!aCodeUnits.empty() || bUseActualText || pGlyph->IsInCluster());
6300 sal_uInt8 nMappedGlyph;
6301 sal_Int32 nMappedFontObject;
6302 registerGlyph(pGlyph, pFont, aCodeUnits, nMappedGlyph, nMappedFontObject);
6304 sal_Int32 nGlyphWidth = 0;
6305 SalGraphics *pGraphics = GetGraphics();
6306 if (pGraphics)
6307 nGlyphWidth = m_aFontCache.getGlyphWidth(pFont,
6308 pGlyph->glyphId(),
6309 pGlyph->IsVertical(),
6310 pGraphics);
6312 int nCharPos = -1;
6313 if (bUseActualText || pGlyph->IsInCluster())
6314 nCharPos = pGlyph->charPos();
6316 aGlyphs.emplace_back(aPos,
6317 pGlyph,
6318 nGlyphWidth,
6319 nMappedFontObject,
6320 nMappedGlyph,
6321 nCharPos);
6324 // Avoid fill color when map mode is in pixels, the below code assumes
6325 // logic map mode.
6326 bool bPixel = m_aCurrentPDFState.m_aMapMode.GetMapUnit() == MapUnit::MapPixel;
6327 if (m_aCurrentPDFState.m_aFont.GetFillColor() != COL_TRANSPARENT && !bPixel)
6329 // PDF doesn't have a text fill color, so draw a rectangle before
6330 // drawing the actual text.
6331 push(PushFlags::FILLCOLOR | PushFlags::LINECOLOR);
6332 setFillColor(m_aCurrentPDFState.m_aFont.GetFillColor());
6333 // Avoid border around the rectangle for Writer shape text.
6334 setLineColor(COL_TRANSPARENT);
6336 // The rectangle is the bounding box of the text, but also includes
6337 // ascent / descent to match the on-screen rendering.
6338 tools::Rectangle aRectangle;
6339 // This is the top left of the text without ascent / descent.
6340 aRectangle.SetPos(PixelToLogic(rLayout.GetDrawPosition()));
6341 aRectangle.setY(aRectangle.getY() - aRefDevFontMetric.GetAscent());
6342 aRectangle.SetSize(PixelToLogic(Size(rLayout.GetTextWidth(), 0)));
6343 // This includes ascent / descent.
6344 aRectangle.setHeight(aRefDevFontMetric.GetLineHeight());
6346 const LogicalFontInstance* pFontInstance = GetFontInstance();
6347 if (pFontInstance->mnOrientation)
6349 // Adapt rectangle for rotated text.
6350 tools::Polygon aPolygon(aRectangle);
6351 aPolygon.Rotate(PixelToLogic(rLayout.GetDrawPosition()), pFontInstance->mnOrientation);
6352 drawPolygon(aPolygon);
6354 else
6355 drawRectangle(aRectangle);
6357 pop();
6360 Point aAlignOffset;
6361 if ( eAlign == ALIGN_BOTTOM )
6362 aAlignOffset.AdjustY( -(aRefDevFontMetric.GetDescent()) );
6363 else if ( eAlign == ALIGN_TOP )
6364 aAlignOffset.AdjustY(aRefDevFontMetric.GetAscent() );
6365 if( aAlignOffset.X() || aAlignOffset.Y() )
6366 aAlignOffset = aRotScale.transform( aAlignOffset );
6368 /* #159153# do not emit an empty glyph vector; this can happen if e.g. the original
6369 string contained only one of the UTF16 BOMs
6371 if( ! aGlyphs.empty() )
6373 size_t nStart = 0;
6374 size_t nEnd = 0;
6375 while (nStart < aGlyphs.size())
6377 while (nEnd < aGlyphs.size() && aGlyphs[nEnd].m_nCharPos == aGlyphs[nStart].m_nCharPos)
6378 nEnd++;
6380 std::vector<PDFGlyph> aRun(aGlyphs.begin() + nStart, aGlyphs.begin() + nEnd);
6382 int nCharPos, nCharCount;
6383 if (!aRun.front().m_pGlyph->IsRTLGlyph())
6385 nCharPos = aRun.front().m_nCharPos;
6386 nCharCount = aRun.front().m_pGlyph->charCount();
6388 else
6390 nCharPos = aRun.back().m_nCharPos;
6391 nCharCount = aRun.back().m_pGlyph->charCount();
6394 if (nCharPos >= 0 && nCharCount)
6396 aLine.append("/Span<</ActualText<FEFF");
6397 for (int i = 0; i < nCharCount; i++)
6399 sal_Unicode aChar = rText[nCharPos + i];
6400 appendHex(static_cast<sal_Int8>(aChar >> 8), aLine);
6401 appendHex(static_cast<sal_Int8>(aChar & 255), aLine);
6403 aLine.append( ">>>\nBDC\n" );
6406 if (bVertical)
6407 drawVerticalGlyphs(aRun, aLine, aAlignOffset, aRotScale, fAngle, fXScale, fSkew, nFontHeight);
6408 else
6409 drawHorizontalGlyphs(aRun, aLine, aAlignOffset, nStart == 0, fAngle, fXScale, fSkew, nFontHeight, nPixelFontHeight);
6411 if (nCharPos >= 0 && nCharCount)
6412 aLine.append( "EMC\n" );
6414 nStart = nEnd;
6418 // end textobject
6419 aLine.append( "ET\n" );
6420 if( bPop )
6421 aLine.append( "Q\n" );
6423 writeBuffer( aLine.getStr(), aLine.getLength() );
6425 // draw eventual textlines
6426 FontStrikeout eStrikeout = m_aCurrentPDFState.m_aFont.GetStrikeout();
6427 FontLineStyle eUnderline = m_aCurrentPDFState.m_aFont.GetUnderline();
6428 FontLineStyle eOverline = m_aCurrentPDFState.m_aFont.GetOverline();
6429 if( bTextLines &&
6431 ( eUnderline != LINESTYLE_NONE && eUnderline != LINESTYLE_DONTKNOW ) ||
6432 ( eOverline != LINESTYLE_NONE && eOverline != LINESTYLE_DONTKNOW ) ||
6433 ( eStrikeout != STRIKEOUT_NONE && eStrikeout != STRIKEOUT_DONTKNOW )
6437 bool bUnderlineAbove = m_aCurrentPDFState.m_aFont.IsUnderlineAbove();
6438 if( m_aCurrentPDFState.m_aFont.IsWordLineMode() )
6440 Point aStartPt;
6441 sal_Int32 nWidth = 0;
6442 nIndex = 0;
6443 while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex))
6445 if (!pGlyph->IsSpacing())
6447 if( !nWidth )
6448 aStartPt = aPos;
6450 nWidth += pGlyph->m_nNewWidth;
6452 else if( nWidth > 0 )
6454 drawTextLine( PixelToLogic( aStartPt ),
6455 ImplDevicePixelToLogicWidth( nWidth ),
6456 eStrikeout, eUnderline, eOverline, bUnderlineAbove );
6457 nWidth = 0;
6461 if( nWidth > 0 )
6463 drawTextLine( PixelToLogic( aStartPt ),
6464 ImplDevicePixelToLogicWidth( nWidth ),
6465 eStrikeout, eUnderline, eOverline, bUnderlineAbove );
6468 else
6470 Point aStartPt = rLayout.GetDrawPosition();
6471 int nWidth = rLayout.GetTextWidth() / rLayout.GetUnitsPerPixel();
6472 drawTextLine( PixelToLogic( aStartPt ),
6473 ImplDevicePixelToLogicWidth( nWidth ),
6474 eStrikeout, eUnderline, eOverline, bUnderlineAbove );
6478 // write eventual emphasis marks
6479 if( !(m_aCurrentPDFState.m_aFont.GetEmphasisMark() & FontEmphasisMark::Style) )
6480 return;
6482 tools::PolyPolygon aEmphPoly;
6483 tools::Rectangle aEmphRect1;
6484 tools::Rectangle aEmphRect2;
6485 long nEmphYOff;
6486 long nEmphWidth;
6487 long nEmphHeight;
6488 bool bEmphPolyLine;
6489 FontEmphasisMark nEmphMark;
6491 push( PushFlags::ALL );
6493 aLine.setLength( 0 );
6494 aLine.append( "q\n" );
6496 nEmphMark = OutputDevice::ImplGetEmphasisMarkStyle( m_aCurrentPDFState.m_aFont );
6497 if ( nEmphMark & FontEmphasisMark::PosBelow )
6498 nEmphHeight = GetEmphasisDescent();
6499 else
6500 nEmphHeight = GetEmphasisAscent();
6501 ImplGetEmphasisMark( aEmphPoly,
6502 bEmphPolyLine,
6503 aEmphRect1,
6504 aEmphRect2,
6505 nEmphYOff,
6506 nEmphWidth,
6507 nEmphMark,
6508 ImplDevicePixelToLogicWidth(nEmphHeight) );
6509 if ( bEmphPolyLine )
6511 setLineColor( m_aCurrentPDFState.m_aFont.GetColor() );
6512 setFillColor( COL_TRANSPARENT );
6514 else
6516 setFillColor( m_aCurrentPDFState.m_aFont.GetColor() );
6517 setLineColor( COL_TRANSPARENT );
6519 writeBuffer( aLine.getStr(), aLine.getLength() );
6521 Point aOffset(0,0);
6523 if ( nEmphMark & FontEmphasisMark::PosBelow )
6524 aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetDescent() + nEmphYOff );
6525 else
6526 aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetAscent() + nEmphYOff) );
6528 long nEmphWidth2 = nEmphWidth / 2;
6529 long nEmphHeight2 = nEmphHeight / 2;
6530 aOffset += Point( nEmphWidth2, nEmphHeight2 );
6532 if ( eAlign == ALIGN_BOTTOM )
6533 aOffset.AdjustY( -(GetFontInstance()->mxFontMetric->GetDescent()) );
6534 else if ( eAlign == ALIGN_TOP )
6535 aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetAscent() );
6537 nIndex = 0;
6538 while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex))
6540 if (pGlyph->IsSpacing())
6542 Point aAdjOffset = aOffset;
6543 aAdjOffset.AdjustX((pGlyph->m_nNewWidth - nEmphWidth) / 2 );
6544 aAdjOffset = aRotScale.transform( aAdjOffset );
6546 aAdjOffset -= Point( nEmphWidth2, nEmphHeight2 );
6548 aPos += aAdjOffset;
6549 aPos = PixelToLogic( aPos );
6550 drawEmphasisMark( aPos.X(), aPos.Y(),
6551 aEmphPoly, bEmphPolyLine,
6552 aEmphRect1, aEmphRect2 );
6556 writeBuffer( "Q\n", 2 );
6557 pop();
6561 void PDFWriterImpl::drawEmphasisMark( long nX, long nY,
6562 const tools::PolyPolygon& rPolyPoly, bool bPolyLine,
6563 const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 )
6565 // TODO: pass nWidth as width of this mark
6566 // long nWidth = 0;
6568 if ( rPolyPoly.Count() )
6570 if ( bPolyLine )
6572 tools::Polygon aPoly = rPolyPoly.GetObject( 0 );
6573 aPoly.Move( nX, nY );
6574 drawPolyLine( aPoly );
6576 else
6578 tools::PolyPolygon aPolyPoly = rPolyPoly;
6579 aPolyPoly.Move( nX, nY );
6580 drawPolyPolygon( aPolyPoly );
6584 if ( !rRect1.IsEmpty() )
6586 tools::Rectangle aRect( Point( nX+rRect1.Left(),
6587 nY+rRect1.Top() ), rRect1.GetSize() );
6588 drawRectangle( aRect );
6591 if ( !rRect2.IsEmpty() )
6593 tools::Rectangle aRect( Point( nX+rRect2.Left(),
6594 nY+rRect2.Top() ), rRect2.GetSize() );
6596 drawRectangle( aRect );
6600 void PDFWriterImpl::drawText( const Point& rPos, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen, bool bTextLines )
6602 MARK( "drawText" );
6604 updateGraphicsState();
6606 // get a layout from the OutputDevice's SalGraphics
6607 // this also enforces font substitution and sets the font on SalGraphics
6608 std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos );
6609 if( pLayout )
6611 drawLayout( *pLayout, rText, bTextLines );
6615 void PDFWriterImpl::drawTextArray( const Point& rPos, const OUString& rText, const long* pDXArray, sal_Int32 nIndex, sal_Int32 nLen )
6617 MARK( "drawText with array" );
6619 updateGraphicsState();
6621 // get a layout from the OutputDevice's SalGraphics
6622 // this also enforces font substitution and sets the font on SalGraphics
6623 std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, 0, pDXArray );
6624 if( pLayout )
6626 drawLayout( *pLayout, rText, true );
6630 void PDFWriterImpl::drawStretchText( const Point& rPos, sal_uLong nWidth, const OUString& rText, sal_Int32 nIndex, sal_Int32 nLen )
6632 MARK( "drawStretchText" );
6634 updateGraphicsState();
6636 // get a layout from the OutputDevice's SalGraphics
6637 // this also enforces font substitution and sets the font on SalGraphics
6638 std::unique_ptr<SalLayout> pLayout = ImplLayout( rText, nIndex, nLen, rPos, nWidth );
6639 if( pLayout )
6641 drawLayout( *pLayout, rText, true );
6645 void PDFWriterImpl::drawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle )
6647 long nWidth = rRect.GetWidth();
6648 long nHeight = rRect.GetHeight();
6650 if ( nWidth <= 0 || nHeight <= 0 )
6651 return;
6653 MARK( "drawText with rectangle" );
6655 updateGraphicsState();
6657 // clip with rectangle
6658 OStringBuffer aLine;
6659 aLine.append( "q " );
6660 m_aPages.back().appendRect( rRect, aLine );
6661 aLine.append( " W* n\n" );
6662 writeBuffer( aLine.getStr(), aLine.getLength() );
6664 // if disabled text is needed, put in here
6666 Point aPos = rRect.TopLeft();
6668 long nTextHeight = GetTextHeight();
6669 sal_Int32 nMnemonicPos = -1;
6671 OUString aStr = rOrigStr;
6672 if ( nStyle & DrawTextFlags::Mnemonic )
6673 aStr = OutputDevice::GetNonMnemonicString( aStr, nMnemonicPos );
6675 // multiline text
6676 if ( nStyle & DrawTextFlags::MultiLine )
6678 OUString aLastLine;
6679 ImplMultiTextLineInfo aMultiLineInfo;
6680 ImplTextLineInfo* pLineInfo;
6681 sal_Int32 i;
6682 sal_Int32 nLines;
6683 sal_Int32 nFormatLines;
6685 if ( nTextHeight )
6687 vcl::DefaultTextLayout aLayout( *this );
6688 OutputDevice::ImplGetTextLines( aMultiLineInfo, nWidth, aStr, nStyle, aLayout );
6689 nLines = nHeight/nTextHeight;
6690 nFormatLines = aMultiLineInfo.Count();
6691 if ( !nLines )
6692 nLines = 1;
6693 if ( nFormatLines > nLines )
6695 if ( nStyle & DrawTextFlags::EndEllipsis )
6697 // handle last line
6698 nFormatLines = nLines-1;
6700 pLineInfo = aMultiLineInfo.GetLine( nFormatLines );
6701 aLastLine = convertLineEnd(aStr.copy(pLineInfo->GetIndex()), LINEEND_LF);
6702 // replace line feed by space
6703 aLastLine = aLastLine.replace('\n', ' ');
6704 aLastLine = GetEllipsisString( aLastLine, nWidth, nStyle );
6705 nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom);
6706 nStyle |= DrawTextFlags::Top;
6710 // vertical alignment
6711 if ( nStyle & DrawTextFlags::Bottom )
6712 aPos.AdjustY(nHeight-(nFormatLines*nTextHeight) );
6713 else if ( nStyle & DrawTextFlags::VCenter )
6714 aPos.AdjustY((nHeight-(nFormatLines*nTextHeight))/2 );
6716 // draw all lines excluding the last
6717 for ( i = 0; i < nFormatLines; i++ )
6719 pLineInfo = aMultiLineInfo.GetLine( i );
6720 if ( nStyle & DrawTextFlags::Right )
6721 aPos.AdjustX(nWidth-pLineInfo->GetWidth() );
6722 else if ( nStyle & DrawTextFlags::Center )
6723 aPos.AdjustX((nWidth-pLineInfo->GetWidth())/2 );
6724 sal_Int32 nIndex = pLineInfo->GetIndex();
6725 sal_Int32 nLineLen = pLineInfo->GetLen();
6726 drawText( aPos, aStr, nIndex, nLineLen );
6727 // mnemonics should not appear in documents,
6728 // if the need arises, put them in here
6729 aPos.AdjustY(nTextHeight );
6730 aPos.setX( rRect.Left() );
6733 // output last line left adjusted since it was shortened
6734 if (!aLastLine.isEmpty())
6735 drawText( aPos, aLastLine, 0, aLastLine.getLength() );
6738 else
6740 long nTextWidth = GetTextWidth( aStr );
6742 // Evt. Text kuerzen
6743 if ( nTextWidth > nWidth )
6745 if ( nStyle & (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis) )
6747 aStr = GetEllipsisString( aStr, nWidth, nStyle );
6748 nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right);
6749 nStyle |= DrawTextFlags::Left;
6750 nTextWidth = GetTextWidth( aStr );
6754 // vertical alignment
6755 if ( nStyle & DrawTextFlags::Right )
6756 aPos.AdjustX(nWidth-nTextWidth );
6757 else if ( nStyle & DrawTextFlags::Center )
6758 aPos.AdjustX((nWidth-nTextWidth)/2 );
6760 if ( nStyle & DrawTextFlags::Bottom )
6761 aPos.AdjustY(nHeight-nTextHeight );
6762 else if ( nStyle & DrawTextFlags::VCenter )
6763 aPos.AdjustY((nHeight-nTextHeight)/2 );
6765 // mnemonics should be inserted here if the need arises
6767 // draw the actual text
6768 drawText( aPos, aStr, 0, aStr.getLength() );
6771 // reset clip region to original value
6772 aLine.setLength( 0 );
6773 aLine.append( "Q\n" );
6774 writeBuffer( aLine.getStr(), aLine.getLength() );
6777 void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop )
6779 MARK( "drawLine" );
6781 updateGraphicsState();
6783 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
6784 return;
6786 OStringBuffer aLine;
6787 m_aPages.back().appendPoint( rStart, aLine );
6788 aLine.append( " m " );
6789 m_aPages.back().appendPoint( rStop, aLine );
6790 aLine.append( " l S\n" );
6792 writeBuffer( aLine.getStr(), aLine.getLength() );
6795 void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo )
6797 MARK( "drawLine with LineInfo" );
6798 updateGraphicsState();
6800 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
6801 return;
6803 if( rInfo.GetStyle() == LineStyle::Solid && rInfo.GetWidth() < 2 )
6805 drawLine( rStart, rStop );
6806 return;
6809 OStringBuffer aLine;
6811 aLine.append( "q " );
6812 if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
6814 m_aPages.back().appendPoint( rStart, aLine );
6815 aLine.append( " m " );
6816 m_aPages.back().appendPoint( rStop, aLine );
6817 aLine.append( " l S Q\n" );
6819 writeBuffer( aLine.getStr(), aLine.getLength() );
6821 else
6823 PDFWriter::ExtLineInfo aInfo;
6824 convertLineInfoToExtLineInfo( rInfo, aInfo );
6825 Point aPolyPoints[2] = { rStart, rStop };
6826 tools::Polygon aPoly( 2, aPolyPoints );
6827 drawPolyLine( aPoly, aInfo );
6831 #define HCONV( x ) ImplDevicePixelToLogicHeight( x )
6833 void PDFWriterImpl::drawWaveTextLine( OStringBuffer& aLine, long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
6835 // note: units in pFontInstance are ref device pixel
6836 const LogicalFontInstance* pFontInstance = GetFontInstance();
6837 long nLineHeight = 0;
6838 long nLinePos = 0;
6840 appendStrokingColor( aColor, aLine );
6841 aLine.append( "\n" );
6843 if ( bIsAbove )
6845 if ( !pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() )
6846 ImplInitAboveTextLineSize();
6847 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize() );
6848 nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveWavelineUnderlineOffset() );
6850 else
6852 if ( !pFontInstance->mxFontMetric->GetWavelineUnderlineSize() )
6853 ImplInitTextLineSize();
6854 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineSize() );
6855 nLinePos = HCONV( pFontInstance->mxFontMetric->GetWavelineUnderlineOffset() );
6857 if ( (eTextLine == LINESTYLE_SMALLWAVE) && (nLineHeight > 3) )
6858 nLineHeight = 3;
6860 long nLineWidth = GetDPIX()/450;
6861 if ( ! nLineWidth )
6862 nLineWidth = 1;
6864 if ( eTextLine == LINESTYLE_BOLDWAVE )
6865 nLineWidth = 3*nLineWidth;
6867 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineWidth), aLine );
6868 aLine.append( " w " );
6870 if ( eTextLine == LINESTYLE_DOUBLEWAVE )
6872 long nOrgLineHeight = nLineHeight;
6873 nLineHeight /= 3;
6874 if ( nLineHeight < 2 )
6876 if ( nOrgLineHeight > 1 )
6877 nLineHeight = 2;
6878 else
6879 nLineHeight = 1;
6881 long nLineDY = nOrgLineHeight-(nLineHeight*2);
6882 if ( nLineDY < nLineWidth )
6883 nLineDY = nLineWidth;
6884 long nLineDY2 = nLineDY/2;
6885 if ( !nLineDY2 )
6886 nLineDY2 = 1;
6888 nLinePos -= nLineWidth-nLineDY2;
6890 m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
6892 nLinePos += nLineWidth+nLineDY;
6893 m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
6895 else
6897 if ( eTextLine != LINESTYLE_BOLDWAVE )
6898 nLinePos -= nLineWidth/2;
6899 m_aPages.back().appendWaveLine( nWidth, -nLinePos, nLineHeight, aLine );
6903 void PDFWriterImpl::drawStraightTextLine( OStringBuffer& aLine, long nWidth, FontLineStyle eTextLine, Color aColor, bool bIsAbove )
6905 // note: units in pFontInstance are ref device pixel
6906 const LogicalFontInstance* pFontInstance = GetFontInstance();
6907 long nLineHeight = 0;
6908 long nLinePos = 0;
6909 long nLinePos2 = 0;
6911 if ( eTextLine > LINESTYLE_BOLDWAVE )
6912 eTextLine = LINESTYLE_SINGLE;
6914 switch ( eTextLine )
6916 case LINESTYLE_SINGLE:
6917 case LINESTYLE_DOTTED:
6918 case LINESTYLE_DASH:
6919 case LINESTYLE_LONGDASH:
6920 case LINESTYLE_DASHDOT:
6921 case LINESTYLE_DASHDOTDOT:
6922 if ( bIsAbove )
6924 if ( !pFontInstance->mxFontMetric->GetAboveUnderlineSize() )
6925 ImplInitAboveTextLineSize();
6926 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveUnderlineSize() );
6927 nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveUnderlineOffset() );
6929 else
6931 if ( !pFontInstance->mxFontMetric->GetUnderlineSize() )
6932 ImplInitTextLineSize();
6933 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetUnderlineSize() );
6934 nLinePos = HCONV( pFontInstance->mxFontMetric->GetUnderlineOffset() );
6936 break;
6937 case LINESTYLE_BOLD:
6938 case LINESTYLE_BOLDDOTTED:
6939 case LINESTYLE_BOLDDASH:
6940 case LINESTYLE_BOLDLONGDASH:
6941 case LINESTYLE_BOLDDASHDOT:
6942 case LINESTYLE_BOLDDASHDOTDOT:
6943 if ( bIsAbove )
6945 if ( !pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() )
6946 ImplInitAboveTextLineSize();
6947 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize() );
6948 nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset() );
6950 else
6952 if ( !pFontInstance->mxFontMetric->GetBoldUnderlineSize() )
6953 ImplInitTextLineSize();
6954 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetBoldUnderlineSize() );
6955 nLinePos = HCONV( pFontInstance->mxFontMetric->GetBoldUnderlineOffset() );
6956 nLinePos += nLineHeight/2;
6958 break;
6959 case LINESTYLE_DOUBLE:
6960 if ( bIsAbove )
6962 if ( !pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() )
6963 ImplInitAboveTextLineSize();
6964 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize() );
6965 nLinePos = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1() );
6966 nLinePos2 = HCONV( pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2() );
6968 else
6970 if ( !pFontInstance->mxFontMetric->GetDoubleUnderlineSize() )
6971 ImplInitTextLineSize();
6972 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineSize() );
6973 nLinePos = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1() );
6974 nLinePos2 = HCONV( pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2() );
6976 break;
6977 default:
6978 break;
6981 if ( !nLineHeight )
6982 return;
6984 // outline attribute ?
6985 if (m_aCurrentPDFState.m_aFont.IsOutline() && eTextLine == LINESTYLE_SINGLE)
6987 appendStrokingColor(aColor, aLine); // stroke with text color
6988 aLine.append( " " );
6989 appendNonStrokingColor(COL_WHITE, aLine); // fill with white
6990 aLine.append( "\n" );
6991 aLine.append( "0.25 w \n" ); // same line thickness as in drawLayout
6993 // draw rectangle instead
6994 aLine.append( "0 " );
6995 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos * 1.5), aLine );
6996 aLine.append( " " );
6997 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
6998 aLine.append( ' ' );
6999 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7000 aLine.append( " re h B\n" );
7001 return;
7004 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7005 aLine.append( " w " );
7006 appendStrokingColor( aColor, aLine );
7007 aLine.append( "\n" );
7009 switch ( eTextLine )
7011 case LINESTYLE_DOTTED:
7012 case LINESTYLE_BOLDDOTTED:
7013 aLine.append( "[ " );
7014 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7015 aLine.append( " ] 0 d\n" );
7016 break;
7017 case LINESTYLE_DASH:
7018 case LINESTYLE_LONGDASH:
7019 case LINESTYLE_BOLDDASH:
7020 case LINESTYLE_BOLDLONGDASH:
7022 sal_Int32 nDashLength = 4*nLineHeight;
7023 sal_Int32 nVoidLength = 2*nLineHeight;
7024 if ( ( eTextLine == LINESTYLE_LONGDASH ) || ( eTextLine == LINESTYLE_BOLDLONGDASH ) )
7025 nDashLength = 8*nLineHeight;
7027 aLine.append( "[ " );
7028 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7029 aLine.append( ' ' );
7030 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7031 aLine.append( " ] 0 d\n" );
7033 break;
7034 case LINESTYLE_DASHDOT:
7035 case LINESTYLE_BOLDDASHDOT:
7037 sal_Int32 nDashLength = 4*nLineHeight;
7038 sal_Int32 nVoidLength = 2*nLineHeight;
7039 aLine.append( "[ " );
7040 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7041 aLine.append( ' ' );
7042 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7043 aLine.append( ' ' );
7044 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7045 aLine.append( ' ' );
7046 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7047 aLine.append( " ] 0 d\n" );
7049 break;
7050 case LINESTYLE_DASHDOTDOT:
7051 case LINESTYLE_BOLDDASHDOTDOT:
7053 sal_Int32 nDashLength = 4*nLineHeight;
7054 sal_Int32 nVoidLength = 2*nLineHeight;
7055 aLine.append( "[ " );
7056 m_aPages.back().appendMappedLength( nDashLength, aLine, false );
7057 aLine.append( ' ' );
7058 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7059 aLine.append( ' ' );
7060 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7061 aLine.append( ' ' );
7062 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7063 aLine.append( ' ' );
7064 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine, false );
7065 aLine.append( ' ' );
7066 m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
7067 aLine.append( " ] 0 d\n" );
7069 break;
7070 default:
7071 break;
7074 aLine.append( "0 " );
7075 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7076 aLine.append( " m " );
7077 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
7078 aLine.append( ' ' );
7079 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7080 aLine.append( " l S\n" );
7081 if ( eTextLine == LINESTYLE_DOUBLE )
7083 aLine.append( "0 " );
7084 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7085 aLine.append( " m " );
7086 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine, false );
7087 aLine.append( ' ' );
7088 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7089 aLine.append( " l S\n" );
7094 void PDFWriterImpl::drawStrikeoutLine( OStringBuffer& aLine, long nWidth, FontStrikeout eStrikeout, Color aColor )
7096 // note: units in pFontInstance are ref device pixel
7097 const LogicalFontInstance* pFontInstance = GetFontInstance();
7098 long nLineHeight = 0;
7099 long nLinePos = 0;
7100 long nLinePos2 = 0;
7102 if ( eStrikeout > STRIKEOUT_X )
7103 eStrikeout = STRIKEOUT_SINGLE;
7105 switch ( eStrikeout )
7107 case STRIKEOUT_SINGLE:
7108 if ( !pFontInstance->mxFontMetric->GetStrikeoutSize() )
7109 ImplInitTextLineSize();
7110 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetStrikeoutSize() );
7111 nLinePos = HCONV( pFontInstance->mxFontMetric->GetStrikeoutOffset() );
7112 break;
7113 case STRIKEOUT_BOLD:
7114 if ( !pFontInstance->mxFontMetric->GetBoldStrikeoutSize() )
7115 ImplInitTextLineSize();
7116 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetBoldStrikeoutSize() );
7117 nLinePos = HCONV( pFontInstance->mxFontMetric->GetBoldStrikeoutOffset() );
7118 break;
7119 case STRIKEOUT_DOUBLE:
7120 if ( !pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() )
7121 ImplInitTextLineSize();
7122 nLineHeight = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutSize() );
7123 nLinePos = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1() );
7124 nLinePos2 = HCONV( pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2() );
7125 break;
7126 default:
7127 break;
7130 if ( !nLineHeight )
7131 return;
7133 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nLineHeight), aLine );
7134 aLine.append( " w " );
7135 appendStrokingColor( aColor, aLine );
7136 aLine.append( "\n" );
7138 aLine.append( "0 " );
7139 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7140 aLine.append( " m " );
7141 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
7142 aLine.append( ' ' );
7143 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos), aLine );
7144 aLine.append( " l S\n" );
7146 if ( eStrikeout == STRIKEOUT_DOUBLE )
7148 aLine.append( "0 " );
7149 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7150 aLine.append( " m " );
7151 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(nWidth), aLine );
7152 aLine.append( ' ' );
7153 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(-nLinePos2-nLineHeight), aLine );
7154 aLine.append( " l S\n" );
7159 void PDFWriterImpl::drawStrikeoutChar( const Point& rPos, long nWidth, FontStrikeout eStrikeout )
7161 //See qadevOOo/testdocs/StrikeThrough.odt for examples if you need
7162 //to tweak this
7164 OUString aStrikeoutChar = eStrikeout == STRIKEOUT_SLASH ? OUString( "/" ) : OUString( "X" );
7165 OUString aStrikeout = aStrikeoutChar;
7166 while( GetTextWidth( aStrikeout ) < nWidth )
7167 aStrikeout += aStrikeout;
7169 // do not get broader than nWidth modulo 1 character
7170 while( GetTextWidth( aStrikeout ) >= nWidth )
7171 aStrikeout = aStrikeout.replaceAt( 0, 1, "" );
7172 aStrikeout += aStrikeoutChar;
7173 bool bShadow = m_aCurrentPDFState.m_aFont.IsShadow();
7174 if ( bShadow )
7176 Font aFont = m_aCurrentPDFState.m_aFont;
7177 aFont.SetShadow( false );
7178 setFont( aFont );
7179 updateGraphicsState();
7182 // strikeout string is left aligned non-CTL text
7183 ComplexTextLayoutFlags nOrigTLM = GetLayoutMode();
7184 SetLayoutMode(ComplexTextLayoutFlags::BiDiStrong);
7186 push( PushFlags::CLIPREGION );
7187 FontMetric aRefDevFontMetric = GetFontMetric();
7188 tools::Rectangle aRect;
7189 aRect.SetLeft( rPos.X() );
7190 aRect.SetRight( aRect.Left()+nWidth );
7191 aRect.SetBottom( rPos.Y()+aRefDevFontMetric.GetDescent() );
7192 aRect.SetTop( rPos.Y()-aRefDevFontMetric.GetAscent() );
7194 const LogicalFontInstance* pFontInstance = GetFontInstance();
7195 if (pFontInstance->mnOrientation)
7197 tools::Polygon aPoly( aRect );
7198 aPoly.Rotate( rPos, pFontInstance->mnOrientation);
7199 aRect = aPoly.GetBoundRect();
7202 intersectClipRegion( aRect );
7203 drawText( rPos, aStrikeout, 0, aStrikeout.getLength(), false );
7204 pop();
7206 SetLayoutMode( nOrigTLM );
7208 if ( bShadow )
7210 Font aFont = m_aCurrentPDFState.m_aFont;
7211 aFont.SetShadow( true );
7212 setFont( aFont );
7213 updateGraphicsState();
7217 void PDFWriterImpl::drawTextLine( const Point& rPos, long nWidth, FontStrikeout eStrikeout, FontLineStyle eUnderline, FontLineStyle eOverline, bool bUnderlineAbove )
7219 if ( !nWidth ||
7220 ( ((eStrikeout == STRIKEOUT_NONE)||(eStrikeout == STRIKEOUT_DONTKNOW)) &&
7221 ((eUnderline == LINESTYLE_NONE)||(eUnderline == LINESTYLE_DONTKNOW)) &&
7222 ((eOverline == LINESTYLE_NONE)||(eOverline == LINESTYLE_DONTKNOW)) ) )
7223 return;
7225 MARK( "drawTextLine" );
7226 updateGraphicsState();
7228 // note: units in pFontInstance are ref device pixel
7229 const LogicalFontInstance* pFontInstance = GetFontInstance();
7230 Color aUnderlineColor = m_aCurrentPDFState.m_aTextLineColor;
7231 Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor;
7232 Color aStrikeoutColor = m_aCurrentPDFState.m_aFont.GetColor();
7233 bool bStrikeoutDone = false;
7234 bool bUnderlineDone = false;
7235 bool bOverlineDone = false;
7237 if ( (eStrikeout == STRIKEOUT_SLASH) || (eStrikeout == STRIKEOUT_X) )
7239 drawStrikeoutChar( rPos, nWidth, eStrikeout );
7240 bStrikeoutDone = true;
7243 Point aPos( rPos );
7244 TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlignment();
7245 if( eAlign == ALIGN_TOP )
7246 aPos.AdjustY(HCONV( pFontInstance->mxFontMetric->GetAscent() ));
7247 else if( eAlign == ALIGN_BOTTOM )
7248 aPos.AdjustY( -HCONV( pFontInstance->mxFontMetric->GetDescent() ) );
7250 OStringBuffer aLine( 512 );
7251 // save GS
7252 aLine.append( "q " );
7254 // rotate and translate matrix
7255 double fAngle = static_cast<double>(m_aCurrentPDFState.m_aFont.GetOrientation()) * M_PI / 1800.0;
7256 Matrix3 aMat;
7257 aMat.rotate( fAngle );
7258 aMat.translate( aPos.X(), aPos.Y() );
7259 aMat.append( m_aPages.back(), aLine );
7260 aLine.append( " cm\n" );
7262 if ( aUnderlineColor.GetTransparency() != 0 )
7263 aUnderlineColor = aStrikeoutColor;
7265 if ( (eUnderline == LINESTYLE_SMALLWAVE) ||
7266 (eUnderline == LINESTYLE_WAVE) ||
7267 (eUnderline == LINESTYLE_DOUBLEWAVE) ||
7268 (eUnderline == LINESTYLE_BOLDWAVE) )
7270 drawWaveTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
7271 bUnderlineDone = true;
7274 if ( (eOverline == LINESTYLE_SMALLWAVE) ||
7275 (eOverline == LINESTYLE_WAVE) ||
7276 (eOverline == LINESTYLE_DOUBLEWAVE) ||
7277 (eOverline == LINESTYLE_BOLDWAVE) )
7279 drawWaveTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
7280 bOverlineDone = true;
7283 if ( !bUnderlineDone )
7285 drawStraightTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
7288 if ( !bOverlineDone )
7290 drawStraightTextLine( aLine, nWidth, eOverline, aOverlineColor, true );
7293 if ( !bStrikeoutDone )
7295 drawStrikeoutLine( aLine, nWidth, eStrikeout, aStrikeoutColor );
7298 aLine.append( "Q\n" );
7299 writeBuffer( aLine.getStr(), aLine.getLength() );
7302 void PDFWriterImpl::drawPolygon( const tools::Polygon& rPoly )
7304 MARK( "drawPolygon" );
7306 updateGraphicsState();
7308 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7309 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7310 return;
7312 int nPoints = rPoly.GetSize();
7313 OStringBuffer aLine( 20 * nPoints );
7314 m_aPages.back().appendPolygon( rPoly, aLine );
7315 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7316 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7317 aLine.append( "B*\n" );
7318 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7319 aLine.append( "S\n" );
7320 else
7321 aLine.append( "f*\n" );
7323 writeBuffer( aLine.getStr(), aLine.getLength() );
7326 void PDFWriterImpl::drawPolyPolygon( const tools::PolyPolygon& rPolyPoly )
7328 MARK( "drawPolyPolygon" );
7330 updateGraphicsState();
7332 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7333 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7334 return;
7336 int nPolygons = rPolyPoly.Count();
7338 OStringBuffer aLine( 40 * nPolygons );
7339 m_aPages.back().appendPolyPolygon( rPolyPoly, aLine );
7340 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7341 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7342 aLine.append( "B*\n" );
7343 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7344 aLine.append( "S\n" );
7345 else
7346 aLine.append( "f*\n" );
7348 writeBuffer( aLine.getStr(), aLine.getLength() );
7351 void PDFWriterImpl::drawTransparent( const tools::PolyPolygon& rPolyPoly, sal_uInt32 nTransparentPercent )
7353 SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
7354 nTransparentPercent = nTransparentPercent % 100;
7356 MARK( "drawTransparent" );
7358 updateGraphicsState();
7360 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7361 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7362 return;
7364 if( m_bIsPDF_A1 || m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
7366 m_aErrors.insert( m_bIsPDF_A1 ?
7367 PDFWriter::Warning_Transparency_Omitted_PDFA :
7368 PDFWriter::Warning_Transparency_Omitted_PDF13 );
7370 drawPolyPolygon( rPolyPoly );
7371 return;
7374 // create XObject
7375 m_aTransparentObjects.emplace_back( );
7376 // FIXME: polygons with beziers may yield incorrect bound rect
7377 m_aTransparentObjects.back().m_aBoundRect = rPolyPoly.GetBoundRect();
7378 // convert rectangle to default user space
7379 m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
7380 m_aTransparentObjects.back().m_nObject = createObject();
7381 m_aTransparentObjects.back().m_nExtGStateObject = createObject();
7382 m_aTransparentObjects.back().m_fAlpha = static_cast<double>(100-nTransparentPercent) / 100.0;
7383 m_aTransparentObjects.back().m_pContentStream.reset(new SvMemoryStream( 256, 256 ));
7384 // create XObject's content stream
7385 OStringBuffer aContent( 256 );
7386 m_aPages.back().appendPolyPolygon( rPolyPoly, aContent );
7387 if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT &&
7388 m_aCurrentPDFState.m_aFillColor != COL_TRANSPARENT )
7389 aContent.append( " B*\n" );
7390 else if( m_aCurrentPDFState.m_aLineColor != COL_TRANSPARENT )
7391 aContent.append( " S\n" );
7392 else
7393 aContent.append( " f*\n" );
7394 m_aTransparentObjects.back().m_pContentStream->WriteBytes(
7395 aContent.getStr(), aContent.getLength() );
7397 OStringBuffer aObjName( 16 );
7398 aObjName.append( "Tr" );
7399 aObjName.append( m_aTransparentObjects.back().m_nObject );
7400 OString aTrName( aObjName.makeStringAndClear() );
7401 aObjName.append( "EGS" );
7402 aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
7403 OString aExtName( aObjName.makeStringAndClear() );
7405 OString aLine =
7406 // insert XObject
7407 "q /" +
7408 aExtName +
7409 " gs /" +
7410 aTrName +
7411 " Do Q\n";
7412 writeBuffer( aLine.getStr(), aLine.getLength() );
7414 pushResource( ResXObject, aTrName, m_aTransparentObjects.back().m_nObject );
7415 pushResource( ResExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
7418 void PDFWriterImpl::pushResource( ResourceKind eKind, const OString& rResource, sal_Int32 nObject )
7420 if( nObject >= 0 )
7422 switch( eKind )
7424 case ResXObject:
7425 m_aGlobalResourceDict.m_aXObjects[ rResource ] = nObject;
7426 if( ! m_aOutputStreams.empty() )
7427 m_aOutputStreams.front().m_aResourceDict.m_aXObjects[ rResource ] = nObject;
7428 break;
7429 case ResExtGState:
7430 m_aGlobalResourceDict.m_aExtGStates[ rResource ] = nObject;
7431 if( ! m_aOutputStreams.empty() )
7432 m_aOutputStreams.front().m_aResourceDict.m_aExtGStates[ rResource ] = nObject;
7433 break;
7434 case ResShading:
7435 m_aGlobalResourceDict.m_aShadings[ rResource ] = nObject;
7436 if( ! m_aOutputStreams.empty() )
7437 m_aOutputStreams.front().m_aResourceDict.m_aShadings[ rResource ] = nObject;
7438 break;
7439 case ResPattern:
7440 m_aGlobalResourceDict.m_aPatterns[ rResource ] = nObject;
7441 if( ! m_aOutputStreams.empty() )
7442 m_aOutputStreams.front().m_aResourceDict.m_aPatterns[ rResource ] = nObject;
7443 break;
7448 void PDFWriterImpl::beginRedirect( SvStream* pStream, const tools::Rectangle& rTargetRect )
7450 push( PushFlags::ALL );
7452 // force reemitting clip region inside the new stream, and
7453 // prevent emitting an unbalanced "Q" at the start
7454 clearClipRegion();
7455 // this is needed to point m_aCurrentPDFState at the pushed state
7456 // ... but it's pointless to actually write into the "outer" stream here!
7457 updateGraphicsState(NOWRITE);
7459 m_aOutputStreams.push_front( StreamRedirect() );
7460 m_aOutputStreams.front().m_pStream = pStream;
7461 m_aOutputStreams.front().m_aMapMode = m_aMapMode;
7463 if( !rTargetRect.IsEmpty() )
7465 m_aOutputStreams.front().m_aTargetRect =
7466 lcl_convert( m_aGraphicsStack.front().m_aMapMode,
7467 m_aMapMode,
7468 this,
7469 rTargetRect );
7470 Point aDelta = m_aOutputStreams.front().m_aTargetRect.BottomLeft();
7471 long nPageHeight = pointToPixel(m_aPages[m_nCurrentPage].getHeight());
7472 aDelta.setY( -(nPageHeight - m_aOutputStreams.front().m_aTargetRect.Bottom()) );
7473 m_aMapMode.SetOrigin( m_aMapMode.GetOrigin() + aDelta );
7476 // setup graphics state for independent object stream
7478 // force reemitting colors
7479 m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
7480 m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
7483 SvStream* PDFWriterImpl::endRedirect()
7485 SvStream* pStream = nullptr;
7486 if( ! m_aOutputStreams.empty() )
7488 pStream = m_aOutputStreams.front().m_pStream;
7489 m_aMapMode = m_aOutputStreams.front().m_aMapMode;
7490 m_aOutputStreams.pop_front();
7493 pop();
7495 m_aCurrentPDFState.m_aLineColor = COL_TRANSPARENT;
7496 m_aCurrentPDFState.m_aFillColor = COL_TRANSPARENT;
7498 // needed after pop() to set m_aCurrentPDFState
7499 updateGraphicsState(NOWRITE);
7501 return pStream;
7504 void PDFWriterImpl::beginTransparencyGroup()
7506 updateGraphicsState();
7507 if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
7508 beginRedirect( new SvMemoryStream( 1024, 1024 ), tools::Rectangle() );
7511 void PDFWriterImpl::endTransparencyGroup( const tools::Rectangle& rBoundingBox, sal_uInt32 nTransparentPercent )
7513 SAL_WARN_IF( nTransparentPercent > 100, "vcl.pdfwriter", "invalid alpha value" );
7514 nTransparentPercent = nTransparentPercent % 100;
7516 if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
7517 return;
7519 // create XObject
7520 m_aTransparentObjects.emplace_back( );
7521 m_aTransparentObjects.back().m_aBoundRect = rBoundingBox;
7522 // convert rectangle to default user space
7523 m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
7524 m_aTransparentObjects.back().m_nObject = createObject();
7525 m_aTransparentObjects.back().m_fAlpha = static_cast<double>(100-nTransparentPercent) / 100.0;
7526 // get XObject's content stream
7527 m_aTransparentObjects.back().m_pContentStream.reset( static_cast<SvMemoryStream*>(endRedirect()) );
7528 m_aTransparentObjects.back().m_nExtGStateObject = createObject();
7530 OStringBuffer aObjName( 16 );
7531 aObjName.append( "Tr" );
7532 aObjName.append( m_aTransparentObjects.back().m_nObject );
7533 OString aTrName( aObjName.makeStringAndClear() );
7534 aObjName.append( "EGS" );
7535 aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject );
7536 OString aExtName( aObjName.makeStringAndClear() );
7538 OString aLine =
7539 // insert XObject
7540 "q /" +
7541 aExtName +
7542 " gs /" +
7543 aTrName +
7544 " Do Q\n";
7545 writeBuffer( aLine.getStr(), aLine.getLength() );
7547 pushResource( ResXObject, aTrName, m_aTransparentObjects.back().m_nObject );
7548 pushResource( ResExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject );
7552 void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect )
7554 MARK( "drawRectangle" );
7556 updateGraphicsState();
7558 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7559 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7560 return;
7562 OStringBuffer aLine( 40 );
7563 m_aPages.back().appendRect( rRect, aLine );
7565 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7566 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7567 aLine.append( " B*\n" );
7568 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7569 aLine.append( " S\n" );
7570 else
7571 aLine.append( " f*\n" );
7573 writeBuffer( aLine.getStr(), aLine.getLength() );
7576 void PDFWriterImpl::drawRectangle( const tools::Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound )
7578 MARK( "drawRectangle with rounded edges" );
7580 if( !nHorzRound && !nVertRound )
7581 drawRectangle( rRect );
7583 updateGraphicsState();
7585 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7586 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7587 return;
7589 if( nHorzRound > static_cast<sal_uInt32>(rRect.GetWidth())/2 )
7590 nHorzRound = rRect.GetWidth()/2;
7591 if( nVertRound > static_cast<sal_uInt32>(rRect.GetHeight())/2 )
7592 nVertRound = rRect.GetHeight()/2;
7594 Point aPoints[16];
7595 const double kappa = 0.5522847498;
7596 const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(nHorzRound))+0.5);
7597 const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(nVertRound))+0.5);
7599 aPoints[1] = Point( rRect.TopLeft().X() + nHorzRound, rRect.TopLeft().Y() );
7600 aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() );
7601 aPoints[2] = Point( rRect.TopRight().X()+1 - nHorzRound, aPoints[1].Y() );
7602 aPoints[3] = Point( aPoints[2].X()+kx, aPoints[2].Y() );
7604 aPoints[5] = Point( rRect.TopRight().X()+1, rRect.TopRight().Y()+nVertRound );
7605 aPoints[4] = Point( aPoints[5].X(), aPoints[5].Y()-ky );
7606 aPoints[6] = Point( aPoints[5].X(), rRect.BottomRight().Y()+1 - nVertRound );
7607 aPoints[7] = Point( aPoints[6].X(), aPoints[6].Y()+ky );
7609 aPoints[9] = Point( rRect.BottomRight().X()+1-nHorzRound, rRect.BottomRight().Y()+1 );
7610 aPoints[8] = Point( aPoints[9].X()+kx, aPoints[9].Y() );
7611 aPoints[10] = Point( rRect.BottomLeft().X() + nHorzRound, aPoints[9].Y() );
7612 aPoints[11] = Point( aPoints[10].X()-kx, aPoints[10].Y() );
7614 aPoints[13] = Point( rRect.BottomLeft().X(), rRect.BottomLeft().Y()+1-nVertRound );
7615 aPoints[12] = Point( aPoints[13].X(), aPoints[13].Y()+ky );
7616 aPoints[14] = Point( rRect.TopLeft().X(), rRect.TopLeft().Y()+nVertRound );
7617 aPoints[15] = Point( aPoints[14].X(), aPoints[14].Y()-ky );
7619 OStringBuffer aLine( 80 );
7620 m_aPages.back().appendPoint( aPoints[1], aLine );
7621 aLine.append( " m " );
7622 m_aPages.back().appendPoint( aPoints[2], aLine );
7623 aLine.append( " l " );
7624 m_aPages.back().appendPoint( aPoints[3], aLine );
7625 aLine.append( ' ' );
7626 m_aPages.back().appendPoint( aPoints[4], aLine );
7627 aLine.append( ' ' );
7628 m_aPages.back().appendPoint( aPoints[5], aLine );
7629 aLine.append( " c\n" );
7630 m_aPages.back().appendPoint( aPoints[6], aLine );
7631 aLine.append( " l " );
7632 m_aPages.back().appendPoint( aPoints[7], aLine );
7633 aLine.append( ' ' );
7634 m_aPages.back().appendPoint( aPoints[8], aLine );
7635 aLine.append( ' ' );
7636 m_aPages.back().appendPoint( aPoints[9], aLine );
7637 aLine.append( " c\n" );
7638 m_aPages.back().appendPoint( aPoints[10], aLine );
7639 aLine.append( " l " );
7640 m_aPages.back().appendPoint( aPoints[11], aLine );
7641 aLine.append( ' ' );
7642 m_aPages.back().appendPoint( aPoints[12], aLine );
7643 aLine.append( ' ' );
7644 m_aPages.back().appendPoint( aPoints[13], aLine );
7645 aLine.append( " c\n" );
7646 m_aPages.back().appendPoint( aPoints[14], aLine );
7647 aLine.append( " l " );
7648 m_aPages.back().appendPoint( aPoints[15], aLine );
7649 aLine.append( ' ' );
7650 m_aPages.back().appendPoint( aPoints[0], aLine );
7651 aLine.append( ' ' );
7652 m_aPages.back().appendPoint( aPoints[1], aLine );
7653 aLine.append( " c " );
7655 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7656 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7657 aLine.append( "b*\n" );
7658 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7659 aLine.append( "s\n" );
7660 else
7661 aLine.append( "f*\n" );
7663 writeBuffer( aLine.getStr(), aLine.getLength() );
7666 void PDFWriterImpl::drawEllipse( const tools::Rectangle& rRect )
7668 MARK( "drawEllipse" );
7670 updateGraphicsState();
7672 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7673 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7674 return;
7676 Point aPoints[12];
7677 const double kappa = 0.5522847498;
7678 const sal_uInt32 kx = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetWidth())/2.0)+0.5);
7679 const sal_uInt32 ky = static_cast<sal_uInt32>((kappa*static_cast<double>(rRect.GetHeight())/2.0)+0.5);
7681 aPoints[1] = Point( rRect.TopLeft().X() + rRect.GetWidth()/2, rRect.TopLeft().Y() );
7682 aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() );
7683 aPoints[2] = Point( aPoints[1].X() + kx, aPoints[1].Y() );
7685 aPoints[4] = Point( rRect.TopRight().X()+1, rRect.TopRight().Y() + rRect.GetHeight()/2 );
7686 aPoints[3] = Point( aPoints[4].X(), aPoints[4].Y() - ky );
7687 aPoints[5] = Point( aPoints[4].X(), aPoints[4].Y() + ky );
7689 aPoints[7] = Point( rRect.BottomLeft().X() + rRect.GetWidth()/2, rRect.BottomLeft().Y()+1 );
7690 aPoints[6] = Point( aPoints[7].X() + kx, aPoints[7].Y() );
7691 aPoints[8] = Point( aPoints[7].X() - kx, aPoints[7].Y() );
7693 aPoints[10] = Point( rRect.TopLeft().X(), rRect.TopLeft().Y() + rRect.GetHeight()/2 );
7694 aPoints[9] = Point( aPoints[10].X(), aPoints[10].Y() + ky );
7695 aPoints[11] = Point( aPoints[10].X(), aPoints[10].Y() - ky );
7697 OStringBuffer aLine( 80 );
7698 m_aPages.back().appendPoint( aPoints[1], aLine );
7699 aLine.append( " m " );
7700 m_aPages.back().appendPoint( aPoints[2], aLine );
7701 aLine.append( ' ' );
7702 m_aPages.back().appendPoint( aPoints[3], aLine );
7703 aLine.append( ' ' );
7704 m_aPages.back().appendPoint( aPoints[4], aLine );
7705 aLine.append( " c\n" );
7706 m_aPages.back().appendPoint( aPoints[5], aLine );
7707 aLine.append( ' ' );
7708 m_aPages.back().appendPoint( aPoints[6], aLine );
7709 aLine.append( ' ' );
7710 m_aPages.back().appendPoint( aPoints[7], aLine );
7711 aLine.append( " c\n" );
7712 m_aPages.back().appendPoint( aPoints[8], aLine );
7713 aLine.append( ' ' );
7714 m_aPages.back().appendPoint( aPoints[9], aLine );
7715 aLine.append( ' ' );
7716 m_aPages.back().appendPoint( aPoints[10], aLine );
7717 aLine.append( " c\n" );
7718 m_aPages.back().appendPoint( aPoints[11], aLine );
7719 aLine.append( ' ' );
7720 m_aPages.back().appendPoint( aPoints[0], aLine );
7721 aLine.append( ' ' );
7722 m_aPages.back().appendPoint( aPoints[1], aLine );
7723 aLine.append( " c " );
7725 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7726 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7727 aLine.append( "b*\n" );
7728 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7729 aLine.append( "s\n" );
7730 else
7731 aLine.append( "f*\n" );
7733 writeBuffer( aLine.getStr(), aLine.getLength() );
7736 static double calcAngle( const tools::Rectangle& rRect, const Point& rPoint )
7738 Point aOrigin((rRect.Left()+rRect.Right()+1)/2,
7739 (rRect.Top()+rRect.Bottom()+1)/2);
7740 Point aPoint = rPoint - aOrigin;
7742 double fX = static_cast<double>(aPoint.X());
7743 double fY = static_cast<double>(-aPoint.Y());
7745 if ((rRect.GetHeight() == 0) || (rRect.GetWidth() == 0))
7746 throw o3tl::divide_by_zero();
7748 if( rRect.GetWidth() > rRect.GetHeight() )
7749 fY = fY*(static_cast<double>(rRect.GetWidth())/static_cast<double>(rRect.GetHeight()));
7750 else if( rRect.GetHeight() > rRect.GetWidth() )
7751 fX = fX*(static_cast<double>(rRect.GetHeight())/static_cast<double>(rRect.GetWidth()));
7752 return atan2( fY, fX );
7755 void PDFWriterImpl::drawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rStop, bool bWithPie, bool bWithChord )
7757 MARK( "drawArc" );
7759 updateGraphicsState();
7761 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT &&
7762 m_aGraphicsStack.front().m_aFillColor == COL_TRANSPARENT )
7763 return;
7765 // calculate start and stop angles
7766 const double fStartAngle = calcAngle( rRect, rStart );
7767 double fStopAngle = calcAngle( rRect, rStop );
7768 while( fStopAngle < fStartAngle )
7769 fStopAngle += 2.0*M_PI;
7770 const int nFragments = static_cast<int>((fStopAngle-fStartAngle)/(M_PI/2.0))+1;
7771 const double fFragmentDelta = (fStopAngle-fStartAngle)/static_cast<double>(nFragments);
7772 const double kappa = fabs( 4.0 * (1.0-cos(fFragmentDelta/2.0))/sin(fFragmentDelta/2.0) / 3.0);
7773 const double halfWidth = static_cast<double>(rRect.GetWidth())/2.0;
7774 const double halfHeight = static_cast<double>(rRect.GetHeight())/2.0;
7776 const Point aCenter( (rRect.Left()+rRect.Right()+1)/2,
7777 (rRect.Top()+rRect.Bottom()+1)/2 );
7779 OStringBuffer aLine( 30*nFragments );
7780 Point aPoint( static_cast<int>(halfWidth * cos(fStartAngle) ),
7781 -static_cast<int>(halfHeight * sin(fStartAngle) ) );
7782 aPoint += aCenter;
7783 m_aPages.back().appendPoint( aPoint, aLine );
7784 aLine.append( " m " );
7785 if( !basegfx::fTools::equal(fStartAngle, fStopAngle) )
7787 for( int i = 0; i < nFragments; i++ )
7789 const double fStartFragment = fStartAngle + static_cast<double>(i)*fFragmentDelta;
7790 const double fStopFragment = fStartFragment + fFragmentDelta;
7791 aPoint = Point( static_cast<int>(halfWidth * (cos(fStartFragment) - kappa*sin(fStartFragment) ) ),
7792 -static_cast<int>(halfHeight * (sin(fStartFragment) + kappa*cos(fStartFragment) ) ) );
7793 aPoint += aCenter;
7794 m_aPages.back().appendPoint( aPoint, aLine );
7795 aLine.append( ' ' );
7797 aPoint = Point( static_cast<int>(halfWidth * (cos(fStopFragment) + kappa*sin(fStopFragment) ) ),
7798 -static_cast<int>(halfHeight * (sin(fStopFragment) - kappa*cos(fStopFragment) ) ) );
7799 aPoint += aCenter;
7800 m_aPages.back().appendPoint( aPoint, aLine );
7801 aLine.append( ' ' );
7803 aPoint = Point( static_cast<int>(halfWidth * cos(fStopFragment) ),
7804 -static_cast<int>(halfHeight * sin(fStopFragment) ) );
7805 aPoint += aCenter;
7806 m_aPages.back().appendPoint( aPoint, aLine );
7807 aLine.append( " c\n" );
7810 if( bWithChord || bWithPie )
7812 if( bWithPie )
7814 m_aPages.back().appendPoint( aCenter, aLine );
7815 aLine.append( " l " );
7817 aLine.append( "h " );
7819 if( ! bWithChord && ! bWithPie )
7820 aLine.append( "S\n" );
7821 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT &&
7822 m_aGraphicsStack.front().m_aFillColor != COL_TRANSPARENT )
7823 aLine.append( "B*\n" );
7824 else if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
7825 aLine.append( "S\n" );
7826 else
7827 aLine.append( "f*\n" );
7829 writeBuffer( aLine.getStr(), aLine.getLength() );
7832 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly )
7834 MARK( "drawPolyLine" );
7836 sal_uInt16 nPoints = rPoly.GetSize();
7837 if( nPoints < 2 )
7838 return;
7840 updateGraphicsState();
7842 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
7843 return;
7845 OStringBuffer aLine( 20 * nPoints );
7846 m_aPages.back().appendPolygon( rPoly, aLine, rPoly[0] == rPoly[nPoints-1] );
7847 aLine.append( "S\n" );
7849 writeBuffer( aLine.getStr(), aLine.getLength() );
7852 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const LineInfo& rInfo )
7854 MARK( "drawPolyLine with LineInfo" );
7856 updateGraphicsState();
7858 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
7859 return;
7861 OStringBuffer aLine;
7862 aLine.append( "q " );
7863 if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
7865 writeBuffer( aLine.getStr(), aLine.getLength() );
7866 drawPolyLine( rPoly );
7867 writeBuffer( "Q\n", 2 );
7869 else
7871 PDFWriter::ExtLineInfo aInfo;
7872 convertLineInfoToExtLineInfo( rInfo, aInfo );
7873 drawPolyLine( rPoly, aInfo );
7877 void PDFWriterImpl::convertLineInfoToExtLineInfo( const LineInfo& rIn, PDFWriter::ExtLineInfo& rOut )
7879 SAL_WARN_IF( rIn.GetStyle() != LineStyle::Dash, "vcl.pdfwriter", "invalid conversion" );
7880 rOut.m_fLineWidth = rIn.GetWidth();
7881 rOut.m_fTransparency = 0.0;
7882 rOut.m_eCap = PDFWriter::capButt;
7883 rOut.m_eJoin = PDFWriter::joinMiter;
7884 rOut.m_fMiterLimit = 10;
7885 rOut.m_aDashArray.clear();
7887 // add DashDot to DashArray
7888 const int nDashes = rIn.GetDashCount();
7889 const int nDashLen = rIn.GetDashLen();
7890 const int nDistance = rIn.GetDistance();
7892 for( int n = 0; n < nDashes; n++ )
7894 rOut.m_aDashArray.push_back( nDashLen );
7895 rOut.m_aDashArray.push_back( nDistance );
7897 const int nDots = rIn.GetDotCount();
7898 const int nDotLen = rIn.GetDotLen();
7900 for( int n = 0; n < nDots; n++ )
7902 rOut.m_aDashArray.push_back( nDotLen );
7903 rOut.m_aDashArray.push_back( nDistance );
7906 // add LineJoin
7907 switch(rIn.GetLineJoin())
7909 case basegfx::B2DLineJoin::Bevel :
7911 rOut.m_eJoin = PDFWriter::joinBevel;
7912 break;
7914 // Pdf has no 'none' lineJoin, default is miter
7915 case basegfx::B2DLineJoin::NONE :
7916 case basegfx::B2DLineJoin::Miter :
7918 rOut.m_eJoin = PDFWriter::joinMiter;
7919 break;
7921 case basegfx::B2DLineJoin::Round :
7923 rOut.m_eJoin = PDFWriter::joinRound;
7924 break;
7928 // add LineCap
7929 switch(rIn.GetLineCap())
7931 default: /* css::drawing::LineCap_BUTT */
7933 rOut.m_eCap = PDFWriter::capButt;
7934 break;
7936 case css::drawing::LineCap_ROUND:
7938 rOut.m_eCap = PDFWriter::capRound;
7939 break;
7941 case css::drawing::LineCap_SQUARE:
7943 rOut.m_eCap = PDFWriter::capSquare;
7944 break;
7949 void PDFWriterImpl::drawPolyLine( const tools::Polygon& rPoly, const PDFWriter::ExtLineInfo& rInfo )
7951 MARK( "drawPolyLine with ExtLineInfo" );
7953 updateGraphicsState();
7955 if( m_aGraphicsStack.front().m_aLineColor == COL_TRANSPARENT )
7956 return;
7958 if( rInfo.m_fTransparency >= 1.0 )
7959 return;
7961 if( rInfo.m_fTransparency != 0.0 )
7962 beginTransparencyGroup();
7964 OStringBuffer aLine;
7965 aLine.append( "q " );
7966 m_aPages.back().appendMappedLength( rInfo.m_fLineWidth, aLine );
7967 aLine.append( " w" );
7968 if( rInfo.m_aDashArray.size() < 10 ) // implementation limit of acrobat reader
7970 switch( rInfo.m_eCap )
7972 default:
7973 case PDFWriter::capButt: aLine.append( " 0 J" );break;
7974 case PDFWriter::capRound: aLine.append( " 1 J" );break;
7975 case PDFWriter::capSquare: aLine.append( " 2 J" );break;
7977 switch( rInfo.m_eJoin )
7979 default:
7980 case PDFWriter::joinMiter:
7982 double fLimit = rInfo.m_fMiterLimit;
7983 if( rInfo.m_fLineWidth < rInfo.m_fMiterLimit )
7984 fLimit = fLimit / rInfo.m_fLineWidth;
7985 if( fLimit < 1.0 )
7986 fLimit = 1.0;
7987 aLine.append( " 0 j " );
7988 appendDouble( fLimit, aLine );
7989 aLine.append( " M" );
7991 break;
7992 case PDFWriter::joinRound: aLine.append( " 1 j" );break;
7993 case PDFWriter::joinBevel: aLine.append( " 2 j" );break;
7995 if( !rInfo.m_aDashArray.empty() )
7997 aLine.append( " [ " );
7998 for (auto const& dash : rInfo.m_aDashArray)
8000 m_aPages.back().appendMappedLength( dash, aLine );
8001 aLine.append( ' ' );
8003 aLine.append( "] 0 d" );
8005 aLine.append( "\n" );
8006 writeBuffer( aLine.getStr(), aLine.getLength() );
8007 drawPolyLine( rPoly );
8009 else
8011 basegfx::B2DPolygon aPoly(rPoly.getB2DPolygon());
8012 basegfx::B2DPolyPolygon aPolyPoly;
8014 basegfx::utils::applyLineDashing(aPoly, rInfo.m_aDashArray, &aPolyPoly);
8016 // Old applyLineDashing subdivided the polygon. New one will create bezier curve segments.
8017 // To mimic old behaviour, apply subdivide here. If beziers shall be written (better quality)
8018 // this line needs to be removed and the loop below adapted accordingly
8019 aPolyPoly = basegfx::utils::adaptiveSubdivideByAngle(aPolyPoly);
8021 const sal_uInt32 nPolygonCount(aPolyPoly.count());
8023 for( sal_uInt32 nPoly = 0; nPoly < nPolygonCount; nPoly++ )
8025 aLine.append( (nPoly != 0 && (nPoly & 7) == 0) ? "\n" : " " );
8026 aPoly = aPolyPoly.getB2DPolygon( nPoly );
8027 const sal_uInt32 nPointCount(aPoly.count());
8029 if(nPointCount)
8031 const sal_uInt32 nEdgeCount(aPoly.isClosed() ? nPointCount : nPointCount - 1);
8032 basegfx::B2DPoint aCurrent(aPoly.getB2DPoint(0));
8034 for(sal_uInt32 a(0); a < nEdgeCount; a++)
8036 if( a > 0 )
8037 aLine.append( " " );
8038 const sal_uInt32 nNextIndex((a + 1) % nPointCount);
8039 const basegfx::B2DPoint aNext(aPoly.getB2DPoint(nNextIndex));
8041 m_aPages.back().appendPoint( Point( FRound(aCurrent.getX()),
8042 FRound(aCurrent.getY()) ),
8043 aLine );
8044 aLine.append( " m " );
8045 m_aPages.back().appendPoint( Point( FRound(aNext.getX()),
8046 FRound(aNext.getY()) ),
8047 aLine );
8048 aLine.append( " l" );
8050 // prepare next edge
8051 aCurrent = aNext;
8055 aLine.append( " S " );
8056 writeBuffer( aLine.getStr(), aLine.getLength() );
8058 writeBuffer( "Q\n", 2 );
8060 if( rInfo.m_fTransparency != 0.0 )
8062 // FIXME: actually this may be incorrect with bezier polygons
8063 tools::Rectangle aBoundRect( rPoly.GetBoundRect() );
8064 // avoid clipping with thick lines
8065 if( rInfo.m_fLineWidth > 0.0 )
8067 sal_Int32 nLW = sal_Int32(rInfo.m_fLineWidth);
8068 aBoundRect.AdjustTop( -nLW );
8069 aBoundRect.AdjustLeft( -nLW );
8070 aBoundRect.AdjustRight(nLW );
8071 aBoundRect.AdjustBottom(nLW );
8073 endTransparencyGroup( aBoundRect, static_cast<sal_uInt16>(100.0*rInfo.m_fTransparency) );
8077 void PDFWriterImpl::drawPixel( const Point& rPoint, const Color& rColor )
8079 MARK( "drawPixel" );
8081 Color aColor = ( rColor == COL_TRANSPARENT ? m_aGraphicsStack.front().m_aLineColor : rColor );
8083 if( aColor == COL_TRANSPARENT )
8084 return;
8086 // pixels are drawn in line color, so have to set
8087 // the nonstroking color to line color
8088 Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
8089 setFillColor( aColor );
8091 updateGraphicsState();
8093 OStringBuffer aLine( 20 );
8094 m_aPages.back().appendPoint( rPoint, aLine );
8095 aLine.append( ' ' );
8096 appendDouble( 1.0/double(GetDPIX()), aLine );
8097 aLine.append( ' ' );
8098 appendDouble( 1.0/double(GetDPIY()), aLine );
8099 aLine.append( " re f\n" );
8100 writeBuffer( aLine.getStr(), aLine.getLength() );
8102 setFillColor( aOldFillColor );
8105 void PDFWriterImpl::writeTransparentObject( TransparencyEmit& rObject )
8107 CHECK_RETURN2( updateObject( rObject.m_nObject ) );
8109 bool bFlateFilter = compressStream( rObject.m_pContentStream.get() );
8110 sal_uLong nSize = rObject.m_pContentStream->TellEnd();
8111 rObject.m_pContentStream->Seek( STREAM_SEEK_TO_BEGIN );
8112 if (g_bDebugDisableCompression)
8114 emitComment( "PDFWriterImpl::writeTransparentObject" );
8116 OStringBuffer aLine( 512 );
8117 CHECK_RETURN2( updateObject( rObject.m_nObject ) );
8118 aLine.append( rObject.m_nObject );
8119 aLine.append( " 0 obj\n"
8120 "<</Type/XObject\n"
8121 "/Subtype/Form\n"
8122 "/BBox[ " );
8123 appendFixedInt( rObject.m_aBoundRect.Left(), aLine );
8124 aLine.append( ' ' );
8125 appendFixedInt( rObject.m_aBoundRect.Top(), aLine );
8126 aLine.append( ' ' );
8127 appendFixedInt( rObject.m_aBoundRect.Right(), aLine );
8128 aLine.append( ' ' );
8129 appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aLine );
8130 aLine.append( " ]\n" );
8131 if( ! rObject.m_pSoftMaskStream )
8133 if( ! m_bIsPDF_A1 )
8135 // 7.8.3 Resource dicts are required for content streams
8136 aLine.append( "/Resources " );
8137 aLine.append( getResourceDictObj() );
8138 aLine.append( " 0 R\n" );
8140 aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/K true>>\n" );
8144 aLine.append( "/Length " );
8145 aLine.append( static_cast<sal_Int32>(nSize) );
8146 aLine.append( "\n" );
8147 if( bFlateFilter )
8148 aLine.append( "/Filter/FlateDecode\n" );
8149 aLine.append( ">>\n"
8150 "stream\n" );
8151 CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8152 checkAndEnableStreamEncryption( rObject.m_nObject );
8153 CHECK_RETURN2( writeBuffer( rObject.m_pContentStream->GetData(), nSize ) );
8154 disableStreamEncryption();
8155 aLine.setLength( 0 );
8156 aLine.append( "\n"
8157 "endstream\n"
8158 "endobj\n\n" );
8159 CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8161 // write ExtGState dict for this XObject
8162 aLine.setLength( 0 );
8163 aLine.append( rObject.m_nExtGStateObject );
8164 aLine.append( " 0 obj\n"
8165 "<<" );
8166 if( ! rObject.m_pSoftMaskStream )
8168 if( m_bIsPDF_A1 )
8170 aLine.append( "/CA 1.0/ca 1.0" );
8171 m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
8173 else
8175 aLine.append( "/CA " );
8176 appendDouble( rObject.m_fAlpha, aLine );
8177 aLine.append( "\n"
8178 " /ca " );
8179 appendDouble( rObject.m_fAlpha, aLine );
8181 aLine.append( "\n" );
8183 else
8185 if( m_bIsPDF_A1 )
8187 aLine.append( "/SMask/None" );
8188 m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
8190 else
8192 sal_Int32 nMaskSize = static_cast<sal_Int32>(rObject.m_pSoftMaskStream->TellEnd());
8193 rObject.m_pSoftMaskStream->Seek( STREAM_SEEK_TO_BEGIN );
8194 sal_Int32 nMaskObject = createObject();
8195 aLine.append( "/SMask<</Type/Mask/S/Luminosity/G " );
8196 aLine.append( nMaskObject );
8197 aLine.append( " 0 R>>\n" );
8199 OStringBuffer aMask;
8200 aMask.append( nMaskObject );
8201 aMask.append( " 0 obj\n"
8202 "<</Type/XObject\n"
8203 "/Subtype/Form\n"
8204 "/BBox[" );
8205 appendFixedInt( rObject.m_aBoundRect.Left(), aMask );
8206 aMask.append( ' ' );
8207 appendFixedInt( rObject.m_aBoundRect.Top(), aMask );
8208 aMask.append( ' ' );
8209 appendFixedInt( rObject.m_aBoundRect.Right(), aMask );
8210 aMask.append( ' ' );
8211 appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aMask );
8212 aMask.append( "]\n" );
8214 // 7.8.3 Resource dicts are required for content streams
8215 aMask.append( "/Resources " );
8216 aMask.append( getResourceDictObj() );
8217 aMask.append( " 0 R\n" );
8219 aMask.append( "/Group<</S/Transparency/CS/DeviceRGB>>\n" );
8220 aMask.append( "/Length " );
8221 aMask.append( nMaskSize );
8222 aMask.append( ">>\n"
8223 "stream\n" );
8224 CHECK_RETURN2( updateObject( nMaskObject ) );
8225 checkAndEnableStreamEncryption( nMaskObject );
8226 CHECK_RETURN2( writeBuffer( aMask.getStr(), aMask.getLength() ) );
8227 CHECK_RETURN2( writeBuffer( rObject.m_pSoftMaskStream->GetData(), nMaskSize ) );
8228 disableStreamEncryption();
8229 aMask.setLength( 0 );
8230 aMask.append( "\nendstream\n"
8231 "endobj\n\n" );
8232 CHECK_RETURN2( writeBuffer( aMask.getStr(), aMask.getLength() ) );
8235 aLine.append( ">>\n"
8236 "endobj\n\n" );
8237 CHECK_RETURN2( updateObject( rObject.m_nExtGStateObject ) );
8238 CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8241 bool PDFWriterImpl::writeGradientFunction( GradientEmit const & rObject )
8243 // LO internal gradient -> PDF shading type:
8244 // * GradientStyle::Linear: axial shading, using sampled-function with 2 samples
8245 // [t=0:colorStart, t=1:colorEnd]
8246 // * GradientStyle::Axial: axial shading, using sampled-function with 3 samples
8247 // [t=0:colorEnd, t=0.5:colorStart, t=1:colorEnd]
8248 // * other styles: function shading with aSize.Width() * aSize.Height() samples
8249 sal_Int32 nFunctionObject = createObject();
8250 CHECK_RETURN( updateObject( nFunctionObject ) );
8252 ScopedVclPtrInstance< VirtualDevice > aDev;
8253 aDev->SetOutputSizePixel( rObject.m_aSize );
8254 aDev->SetMapMode( MapMode( MapUnit::MapPixel ) );
8255 if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
8256 aDev->SetDrawMode( aDev->GetDrawMode() |
8257 ( DrawModeFlags::GrayLine | DrawModeFlags::GrayFill | DrawModeFlags::GrayText |
8258 DrawModeFlags::GrayBitmap | DrawModeFlags::GrayGradient ) );
8259 aDev->DrawGradient( tools::Rectangle( Point( 0, 0 ), rObject.m_aSize ), rObject.m_aGradient );
8261 Bitmap aSample = aDev->GetBitmap( Point( 0, 0 ), rObject.m_aSize );
8262 Bitmap::ScopedReadAccess pAccess(aSample);
8264 Size aSize = aSample.GetSizePixel();
8266 sal_Int32 nStreamLengthObject = createObject();
8267 if (g_bDebugDisableCompression)
8269 emitComment( "PDFWriterImpl::writeGradientFunction" );
8271 OStringBuffer aLine( 120 );
8272 aLine.append( nFunctionObject );
8273 aLine.append( " 0 obj\n"
8274 "<</FunctionType 0\n");
8275 switch (rObject.m_aGradient.GetStyle())
8277 case GradientStyle::Linear:
8278 case GradientStyle::Axial:
8279 aLine.append("/Domain[ 0 1]\n");
8280 break;
8281 default:
8282 aLine.append("/Domain[ 0 1 0 1]\n");
8284 aLine.append("/Size[ " );
8285 switch (rObject.m_aGradient.GetStyle())
8287 case GradientStyle::Linear:
8288 aLine.append('2');
8289 break;
8290 case GradientStyle::Axial:
8291 aLine.append('3');
8292 break;
8293 default:
8294 aLine.append( static_cast<sal_Int32>(aSize.Width()) );
8295 aLine.append( ' ' );
8296 aLine.append( static_cast<sal_Int32>(aSize.Height()) );
8298 aLine.append( " ]\n"
8299 "/BitsPerSample 8\n"
8300 "/Range[ 0 1 0 1 0 1 ]\n"
8301 "/Order 3\n"
8302 "/Length " );
8303 aLine.append( nStreamLengthObject );
8304 if (!g_bDebugDisableCompression)
8305 aLine.append( " 0 R\n"
8306 "/Filter/FlateDecode"
8307 ">>\n"
8308 "stream\n" );
8309 else
8310 aLine.append( " 0 R\n"
8311 ">>\n"
8312 "stream\n" );
8313 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8315 sal_uInt64 nStartStreamPos = 0;
8316 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nStartStreamPos)) );
8318 checkAndEnableStreamEncryption( nFunctionObject );
8319 beginCompression();
8320 sal_uInt8 aCol[3];
8321 switch (rObject.m_aGradient.GetStyle())
8323 case GradientStyle::Axial:
8324 aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
8325 aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
8326 aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
8327 CHECK_RETURN( writeBuffer( aCol, 3 ) );
8328 [[fallthrough]];
8329 case GradientStyle::Linear:
8331 aCol[0] = rObject.m_aGradient.GetStartColor().GetRed();
8332 aCol[1] = rObject.m_aGradient.GetStartColor().GetGreen();
8333 aCol[2] = rObject.m_aGradient.GetStartColor().GetBlue();
8334 CHECK_RETURN( writeBuffer( aCol, 3 ) );
8336 aCol[0] = rObject.m_aGradient.GetEndColor().GetRed();
8337 aCol[1] = rObject.m_aGradient.GetEndColor().GetGreen();
8338 aCol[2] = rObject.m_aGradient.GetEndColor().GetBlue();
8339 CHECK_RETURN( writeBuffer( aCol, 3 ) );
8340 break;
8342 default:
8343 for( int y = aSize.Height()-1; y >= 0; y-- )
8345 for( long x = 0; x < aSize.Width(); x++ )
8347 BitmapColor aColor = pAccess->GetColor( y, x );
8348 aCol[0] = aColor.GetRed();
8349 aCol[1] = aColor.GetGreen();
8350 aCol[2] = aColor.GetBlue();
8351 CHECK_RETURN( writeBuffer( aCol, 3 ) );
8355 endCompression();
8356 disableStreamEncryption();
8358 sal_uInt64 nEndStreamPos = 0;
8359 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nEndStreamPos)) );
8361 aLine.setLength( 0 );
8362 aLine.append( "\nendstream\nendobj\n\n" );
8363 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8365 // write stream length
8366 CHECK_RETURN( updateObject( nStreamLengthObject ) );
8367 aLine.setLength( 0 );
8368 aLine.append( nStreamLengthObject );
8369 aLine.append( " 0 obj\n" );
8370 aLine.append( static_cast<sal_Int64>(nEndStreamPos-nStartStreamPos) );
8371 aLine.append( "\nendobj\n\n" );
8372 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8374 CHECK_RETURN( updateObject( rObject.m_nObject ) );
8375 aLine.setLength( 0 );
8376 aLine.append( rObject.m_nObject );
8377 aLine.append( " 0 obj\n");
8378 switch (rObject.m_aGradient.GetStyle())
8380 case GradientStyle::Linear:
8381 case GradientStyle::Axial:
8382 aLine.append("<</ShadingType 2\n");
8383 break;
8384 default:
8385 aLine.append("<</ShadingType 1\n");
8387 aLine.append("/ColorSpace/DeviceRGB\n"
8388 "/AntiAlias true\n");
8390 // Determination of shading axis
8391 // See: OutputDevice::ImplDrawLinearGradient for reference
8392 tools::Rectangle aRect;
8393 aRect.SetLeft(0);
8394 aRect.SetTop(0);
8395 aRect.SetRight( aSize.Width() );
8396 aRect.SetBottom( aSize.Height() );
8398 tools::Rectangle aBoundRect;
8399 Point aCenter;
8400 sal_uInt16 nAngle = rObject.m_aGradient.GetAngle() % 3600;
8401 rObject.m_aGradient.GetBoundRect( aRect, aBoundRect, aCenter );
8403 const bool bLinear = (rObject.m_aGradient.GetStyle() == GradientStyle::Linear);
8404 double fBorder = aBoundRect.GetHeight() * rObject.m_aGradient.GetBorder() / 100.0;
8405 if ( !bLinear )
8407 fBorder /= 2.0;
8410 aBoundRect.AdjustBottom( -fBorder );
8411 if (!bLinear)
8413 aBoundRect.AdjustTop(fBorder );
8416 switch (rObject.m_aGradient.GetStyle())
8418 case GradientStyle::Linear:
8419 case GradientStyle::Axial:
8421 aLine.append("/Domain[ 0 1 ]\n"
8422 "/Coords[ " );
8423 tools::Polygon aPoly( 2 );
8424 aPoly[0] = aBoundRect.BottomCenter();
8425 aPoly[1] = aBoundRect.TopCenter();
8426 aPoly.Rotate( aCenter, 3600 - nAngle );
8428 aLine.append( static_cast<sal_Int32>(aPoly[0].X()) );
8429 aLine.append( " " );
8430 aLine.append( static_cast<sal_Int32>(aPoly[0].Y()) );
8431 aLine.append( " " );
8432 aLine.append( static_cast<sal_Int32>(aPoly[1].X()));
8433 aLine.append( " ");
8434 aLine.append( static_cast<sal_Int32>(aPoly[1].Y()));
8435 aLine.append( " ]\n");
8436 aLine.append("/Extend [true true]\n");
8437 break;
8439 default:
8440 aLine.append("/Domain[ 0 1 0 1 ]\n"
8441 "/Matrix[ " );
8442 aLine.append( static_cast<sal_Int32>(aSize.Width()) );
8443 aLine.append( " 0 0 " );
8444 aLine.append( static_cast<sal_Int32>(aSize.Height()) );
8445 aLine.append( " 0 0 ]\n");
8447 aLine.append("/Function " );
8448 aLine.append( nFunctionObject );
8449 aLine.append( " 0 R\n"
8450 ">>\n"
8451 "endobj\n\n" );
8452 return writeBuffer( aLine.getStr(), aLine.getLength() );
8455 void PDFWriterImpl::writeJPG( JPGEmit& rObject )
8457 if (!rObject.m_aReferenceXObject.m_aPDFData.empty() && !m_aContext.UseReferenceXObject)
8459 writeReferenceXObject(rObject.m_aReferenceXObject);
8460 return;
8463 CHECK_RETURN2( rObject.m_pStream );
8464 CHECK_RETURN2( updateObject( rObject.m_nObject ) );
8466 sal_Int32 nLength = rObject.m_pStream->TellEnd();
8467 rObject.m_pStream->Seek( STREAM_SEEK_TO_BEGIN );
8469 sal_Int32 nMaskObject = 0;
8470 if( !!rObject.m_aMask )
8472 if( rObject.m_aMask.GetBitCount() == 1 ||
8473 ( rObject.m_aMask.GetBitCount() == 8 && m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 && !m_bIsPDF_A1 )
8476 nMaskObject = createObject();
8478 else if( m_bIsPDF_A1 )
8479 m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
8480 else if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 )
8481 m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDF13 );
8484 if (g_bDebugDisableCompression)
8486 emitComment( "PDFWriterImpl::writeJPG" );
8489 OStringBuffer aLine(200);
8490 aLine.append( rObject.m_nObject );
8491 aLine.append( " 0 obj\n"
8492 "<</Type/XObject/Subtype/Image/Width " );
8493 aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Width()) );
8494 aLine.append( " /Height " );
8495 aLine.append( static_cast<sal_Int32>(rObject.m_aID.m_aPixelSize.Height()) );
8496 aLine.append( " /BitsPerComponent 8 " );
8497 if( rObject.m_bTrueColor )
8498 aLine.append( "/ColorSpace/DeviceRGB" );
8499 else
8500 aLine.append( "/ColorSpace/DeviceGray" );
8501 aLine.append( "/Filter/DCTDecode/Length " );
8502 aLine.append( nLength );
8503 if( nMaskObject )
8505 aLine.append( rObject.m_aMask.GetBitCount() == 1 ? " /Mask " : " /SMask " );
8506 aLine.append( nMaskObject );
8507 aLine.append( " 0 R " );
8509 aLine.append( ">>\nstream\n" );
8510 CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8512 checkAndEnableStreamEncryption( rObject.m_nObject );
8513 CHECK_RETURN2( writeBuffer( rObject.m_pStream->GetData(), nLength ) );
8514 disableStreamEncryption();
8516 aLine.setLength( 0 );
8517 aLine.append( "\nendstream\nendobj\n\n" );
8518 CHECK_RETURN2( writeBuffer( aLine.getStr(), aLine.getLength() ) );
8520 if( nMaskObject )
8522 BitmapEmit aEmit;
8523 aEmit.m_nObject = nMaskObject;
8524 if( rObject.m_aMask.GetBitCount() == 1 )
8525 aEmit.m_aBitmap = BitmapEx( rObject.m_aMask, rObject.m_aMask );
8526 else if( rObject.m_aMask.GetBitCount() == 8 )
8527 aEmit.m_aBitmap = BitmapEx( rObject.m_aMask, AlphaMask( rObject.m_aMask ) );
8528 writeBitmapObject( aEmit, true );
8531 writeReferenceXObject(rObject.m_aReferenceXObject);
8534 sal_Int32 PDFWriterImpl::copyExternalResource(SvMemoryStream& rDocBuffer, filter::PDFObjectElement& rObject, std::map<sal_Int32, sal_Int32>& rCopiedResources)
8536 auto it = rCopiedResources.find(rObject.GetObjectValue());
8537 if (it != rCopiedResources.end())
8538 // This resource was already copied once, nothing to do.
8539 return it->second;
8541 sal_Int32 nObject = createObject();
8542 // Remember what is the ID of this object in our output.
8543 rCopiedResources[rObject.GetObjectValue()] = nObject;
8544 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::copyExternalResource: " << rObject.GetObjectValue() << " -> " << nObject);
8546 OStringBuffer aLine;
8547 aLine.append(nObject);
8548 aLine.append(" 0 obj\n");
8549 if (rObject.GetDictionary())
8551 aLine.append("<<");
8553 // Complex case: can't copy the dictionary byte array as is, as it may contain references.
8554 bool bDone = false;
8555 sal_uInt64 nCopyStart = 0;
8556 for (auto pReference : rObject.GetDictionaryReferences())
8558 if (pReference)
8560 filter::PDFObjectElement* pReferenced = pReference->LookupObject();
8561 if (pReferenced)
8563 // Copy the referenced object.
8564 sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced, rCopiedResources);
8566 sal_uInt64 nReferenceStart = pReference->GetObjectElement().GetLocation();
8567 sal_uInt64 nReferenceEnd = pReference->GetOffset();
8568 sal_uInt64 nOffset = 0;
8569 if (nCopyStart == 0)
8570 // Dict start -> reference start.
8571 nOffset = rObject.GetDictionaryOffset();
8572 else
8573 // Previous reference end -> reference start.
8574 nOffset = nCopyStart;
8575 aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nOffset, nReferenceStart - nOffset);
8576 // Write the updated reference.
8577 aLine.append(" ");
8578 aLine.append(nRef);
8579 aLine.append(" 0 R");
8580 // Start copying here next time.
8581 nCopyStart = nReferenceEnd;
8583 bDone = true;
8588 if (bDone)
8590 // Copy the last part here, in the complex case.
8591 sal_uInt64 nDictEnd = rObject.GetDictionaryOffset() + rObject.GetDictionaryLength();
8592 const sal_Int32 nLen = nDictEnd - nCopyStart;
8593 if (nLen < 0)
8594 SAL_WARN("vcl.pdfwriter", "copyExternalResource() failed");
8595 else
8596 aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nCopyStart, nLen);
8598 else
8599 // Can copy it as-is.
8600 aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + rObject.GetDictionaryOffset(), rObject.GetDictionaryLength());
8602 aLine.append(">>\n");
8605 if (filter::PDFStreamElement* pStream = rObject.GetStream())
8607 aLine.append("stream\n");
8608 SvMemoryStream& rStream = pStream->GetMemory();
8609 aLine.append(static_cast<const sal_Char*>(rStream.GetData()), rStream.GetSize());
8610 aLine.append("\nendstream\n");
8613 if (filter::PDFArrayElement* pArray = rObject.GetArray())
8615 aLine.append("[");
8617 const std::vector<filter::PDFElement*>& rElements = pArray->GetElements();
8618 bool bDone = false;
8619 // Complex case: can't copy the array byte array as is, as it may contain references.
8620 sal_uInt64 nCopyStart = 0;
8621 for (const auto pElement : rElements)
8623 auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
8624 if (pReference)
8626 filter::PDFObjectElement* pReferenced = pReference->LookupObject();
8627 if (pReferenced)
8629 // Copy the referenced object.
8630 sal_Int32 nRef = copyExternalResource(rDocBuffer, *pReferenced, rCopiedResources);
8632 sal_uInt64 nReferenceStart = pReference->GetObjectElement().GetLocation();
8633 sal_uInt64 nReferenceEnd = pReference->GetOffset();
8634 sal_uInt64 nOffset = 0;
8635 if (nCopyStart == 0)
8636 // Array start -> reference start.
8637 nOffset = rObject.GetArrayOffset();
8638 else
8639 // Previous reference end -> reference start.
8640 nOffset = nCopyStart;
8641 aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nOffset, nReferenceStart - nOffset);
8643 // Write the updated reference.
8644 aLine.append(" ");
8645 aLine.append(nRef);
8646 aLine.append(" 0 R");
8647 // Start copying here next time.
8648 nCopyStart = nReferenceEnd;
8650 bDone = true;
8655 if (bDone)
8657 // Copy the last part here, in the complex case.
8658 sal_uInt64 nArrEnd = rObject.GetArrayOffset() + rObject.GetArrayLength();
8659 const sal_Int32 nLen = nArrEnd - nCopyStart;
8660 if (nLen < 0)
8661 SAL_WARN("vcl.pdfwriter", "copyExternalResource() failed");
8662 else
8663 aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + nCopyStart, nLen);
8665 else
8666 // Can copy it as-is.
8667 aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + rObject.GetArrayOffset(), rObject.GetArrayLength());
8669 aLine.append("]\n");
8672 // If the object has a number element outside a dictionary or array, copy that.
8673 if (filter::PDFNumberElement* pNumber = rObject.GetNumberElement())
8675 aLine.append(static_cast<const sal_Char*>(rDocBuffer.GetData()) + pNumber->GetLocation(), pNumber->GetLength());
8676 aLine.append("\n");
8680 aLine.append("endobj\n\n");
8682 // We have the whole object, now write it to the output.
8683 if (!updateObject(nObject))
8684 return -1;
8685 if (!writeBuffer(aLine.getStr(), aLine.getLength()))
8686 return -1;
8688 return nObject;
8691 OString PDFWriterImpl::copyExternalResources(filter::PDFObjectElement& rPage, const OString& rKind, std::map<sal_Int32, sal_Int32>& rCopiedResources)
8693 // A name - object ID map, IDs as they appear in our output, not the
8694 // original ones.
8695 std::map<OString, sal_Int32> aRet;
8697 // Get the rKind subset of the resource dictionary.
8698 std::map<OString, filter::PDFElement*> aItems;
8699 if (auto pResources = dynamic_cast<filter::PDFDictionaryElement*>(rPage.Lookup("Resources")))
8701 // Resources is a direct dictionary.
8702 filter::PDFElement* pLookup = pResources->LookupElement(rKind);
8703 if (auto pDictionary = dynamic_cast<filter::PDFDictionaryElement*>(pLookup))
8705 // rKind is an inline dictionary.
8706 aItems = pDictionary->GetItems();
8708 else if (auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pLookup))
8710 // rKind refers to a dictionary.
8711 filter::PDFObjectElement* pReferenced = pReference->LookupObject();
8712 if (!pReferenced)
8714 return OString();
8717 aItems = pReferenced->GetDictionaryItems();
8720 else if (filter::PDFObjectElement* pPageResources = rPage.LookupObject("Resources"))
8722 // Resources is an indirect object.
8723 filter::PDFElement* pValue = pPageResources->Lookup(rKind);
8724 if (auto pDictionary = dynamic_cast<filter::PDFDictionaryElement*>(pValue))
8725 // Kind is a direct dictionary.
8726 aItems = pDictionary->GetItems();
8727 else if (filter::PDFObjectElement* pObject = pPageResources->LookupObject(rKind))
8728 // Kind is an indirect object.
8729 aItems = pObject->GetDictionaryItems();
8731 if (aItems.empty())
8732 return OString();
8734 SvMemoryStream& rDocBuffer = rPage.GetDocument().GetEditBuffer();
8736 for (const auto& rItem : aItems)
8738 // For each item copy it over to our output then insert it into aRet.
8739 auto pReference = dynamic_cast<filter::PDFReferenceElement*>(rItem.second);
8740 if (!pReference)
8741 continue;
8743 filter::PDFObjectElement* pValue = pReference->LookupObject();
8744 if (!pValue)
8745 continue;
8747 // Then copying over an object copy its dictionary and its stream.
8748 sal_Int32 nObject = copyExternalResource(rDocBuffer, *pValue, rCopiedResources);
8749 aRet[rItem.first] = nObject;
8752 // Build the dictionary entry string.
8753 OStringBuffer sRet("/" + rKind + "<<");
8754 for (const auto& rPair : aRet)
8756 sRet.append("/").append(rPair.first).append(" ").append(OString::number(rPair.second)).append(" 0 R");
8758 sRet.append(">>");
8760 return sRet.makeStringAndClear();
8763 void PDFWriterImpl::writeReferenceXObject(ReferenceXObjectEmit& rEmit)
8765 if (rEmit.m_nFormObject <= 0)
8766 return;
8768 // Count /Matrix and /BBox.
8769 // vcl::ImportPDF() works with 96 DPI so use the same values here, too.
8770 sal_Int32 nOldDPIX = GetDPIX();
8771 SetDPIX(96);
8772 sal_Int32 nOldDPIY = GetDPIY();
8773 SetDPIY(96);
8774 Size aSize = PixelToLogic(rEmit.m_aPixelSize, MapMode(m_aMapMode.GetMapUnit()));
8775 SetDPIX(nOldDPIX);
8776 SetDPIY(nOldDPIY);
8777 double fScaleX = 1.0 / aSize.Width();
8778 double fScaleY = 1.0 / aSize.Height();
8780 sal_Int32 nWrappedFormObject = 0;
8781 if (!m_aContext.UseReferenceXObject)
8783 // Parse the PDF data, we need that to write the PDF dictionary of our
8784 // object.
8785 SvMemoryStream aPDFStream;
8786 aPDFStream.WriteBytes(rEmit.m_aPDFData.data(), rEmit.m_aPDFData.size());
8787 aPDFStream.Seek(0);
8788 filter::PDFDocument aPDFDocument;
8789 if (!aPDFDocument.Read(aPDFStream))
8791 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: reading the PDF document failed");
8792 return;
8794 std::vector<filter::PDFObjectElement*> aPages = aPDFDocument.GetPages();
8795 if (aPages.empty())
8797 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no pages");
8798 return;
8801 filter::PDFObjectElement* pPage = aPages[0];
8802 if (!pPage)
8804 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no page");
8805 return;
8808 std::vector<filter::PDFObjectElement*> aContentStreams;
8809 if (filter::PDFObjectElement* pContentStream = pPage->LookupObject("Contents"))
8810 aContentStreams.push_back(pContentStream);
8811 else if (auto pArray = dynamic_cast<filter::PDFArrayElement*>(pPage->Lookup("Contents")))
8813 for (const auto pElement : pArray->GetElements())
8815 auto pReference = dynamic_cast<filter::PDFReferenceElement*>(pElement);
8816 if (!pReference)
8817 continue;
8819 filter::PDFObjectElement* pObject = pReference->LookupObject();
8820 if (!pObject)
8821 continue;
8823 aContentStreams.push_back(pObject);
8827 if (aContentStreams.empty())
8829 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: no content stream");
8830 return;
8833 // Maps from source object id (PDF image) to target object id (export result).
8834 std::map<sal_Int32, sal_Int32> aCopiedResources;
8836 nWrappedFormObject = createObject();
8837 // Write the form XObject wrapped below. This is a separate object from
8838 // the wrapper, this way there is no need to alter the stream contents.
8840 OStringBuffer aLine;
8841 aLine.append(nWrappedFormObject);
8842 aLine.append(" 0 obj\n");
8843 aLine.append("<< /Type /XObject");
8844 aLine.append(" /Subtype /Form");
8846 long nWidth = aSize.Width();
8847 long nHeight = aSize.Height();
8848 if (auto pRotate = dynamic_cast<filter::PDFNumberElement*>(pPage->Lookup("Rotate")))
8850 // The original page was rotated, then construct a transformation matrix which does the
8851 // same with our form object.
8852 if (rtl::math::approxEqual(pRotate->GetValue(), 90))
8854 std::swap(nWidth, nHeight);
8855 basegfx::B2DHomMatrix aMat;
8856 aMat.rotate(basegfx::deg2rad(pRotate->GetValue()));
8857 // Rotate around the origo (bottom left corner) counter-clockwise, then translate
8858 // horizontally to effectively keep the bottom left corner unchanged.
8859 aLine.append(" /Matrix [ ");
8860 aLine.append(aMat.get(0, 0));
8861 aLine.append(" ");
8862 aLine.append(aMat.get(0, 1));
8863 aLine.append(" ");
8864 aLine.append(aMat.get(1, 0));
8865 aLine.append(" ");
8866 aLine.append(aMat.get(1, 1));
8867 aLine.append(" 0 ");
8868 aLine.append(nWidth);
8869 aLine.append(" ] ");
8873 aLine.append(" /Resources <<");
8874 static const std::initializer_list<OString> aKeys =
8876 "ColorSpace",
8877 "ExtGState",
8878 "Font",
8879 "XObject",
8880 "Shading"
8882 for (const auto& rKey : aKeys)
8883 aLine.append(copyExternalResources(*pPage, rKey, aCopiedResources));
8884 aLine.append(">>");
8885 aLine.append(" /BBox [ 0 0 ");
8886 aLine.append(nWidth);
8887 aLine.append(" ");
8888 aLine.append(nHeight);
8889 aLine.append(" ]");
8891 if (!g_bDebugDisableCompression)
8892 aLine.append(" /Filter/FlateDecode");
8893 aLine.append(" /Length ");
8895 SvMemoryStream aStream;
8896 for (auto pContent : aContentStreams)
8898 filter::PDFStreamElement* pPageStream = pContent->GetStream();
8899 if (!pPageStream)
8901 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: contents has no stream");
8902 continue;
8905 SvMemoryStream& rPageStream = pPageStream->GetMemory();
8907 auto pFilter = dynamic_cast<filter::PDFNameElement*>(pContent->Lookup("Filter"));
8908 if (pFilter)
8910 if (pFilter->GetValue() != "FlateDecode")
8911 continue;
8913 SvMemoryStream aMemoryStream;
8914 ZCodec aZCodec;
8915 rPageStream.Seek(0);
8916 aZCodec.BeginCompression();
8917 aZCodec.Decompress(rPageStream, aMemoryStream);
8918 if (!aZCodec.EndCompression())
8920 SAL_WARN("vcl.pdfwriter", "PDFWriterImpl::writeReferenceXObject: decompression failed");
8921 continue;
8924 aStream.WriteBytes(aMemoryStream.GetData(), aMemoryStream.GetSize());
8926 else
8927 aStream.WriteBytes(rPageStream.GetData(), rPageStream.GetSize());
8930 compressStream(&aStream);
8931 sal_Int32 nLength = aStream.Tell();
8932 aLine.append(nLength);
8934 aLine.append(">>\nstream\n");
8935 // Copy the original page streams to the form XObject stream.
8936 aLine.append(static_cast<const sal_Char*>(aStream.GetData()), aStream.GetSize());
8937 aLine.append("\nendstream\nendobj\n\n");
8938 if (!updateObject(nWrappedFormObject))
8939 return;
8940 if (!writeBuffer(aLine.getStr(), aLine.getLength()))
8941 return;
8944 OStringBuffer aLine;
8945 if (!updateObject(rEmit.m_nFormObject))
8946 return;
8948 // Now have all the info to write the form XObject.
8949 aLine.append(rEmit.m_nFormObject);
8950 aLine.append(" 0 obj\n");
8951 aLine.append("<< /Type /XObject");
8952 aLine.append(" /Subtype /Form");
8953 aLine.append(" /Resources << /XObject<<");
8955 sal_Int32 nObject = m_aContext.UseReferenceXObject ? rEmit.m_nBitmapObject : nWrappedFormObject;
8956 aLine.append(" /Im");
8957 aLine.append(nObject);
8958 aLine.append(" ");
8959 aLine.append(nObject);
8960 aLine.append(" 0 R");
8962 aLine.append(">> >>");
8963 aLine.append(" /Matrix [ ");
8964 appendDouble(fScaleX, aLine);
8965 aLine.append(" 0 0 ");
8966 appendDouble(fScaleY, aLine);
8967 aLine.append(" 0 0 ]");
8968 aLine.append(" /BBox [ 0 0 ");
8969 aLine.append(aSize.Width());
8970 aLine.append(" ");
8971 aLine.append(aSize.Height());
8972 aLine.append(" ]\n");
8974 if (m_aContext.UseReferenceXObject && rEmit.m_nEmbeddedObject > 0)
8976 // Write the reference dictionary.
8977 aLine.append("/Ref<< /F << /Type /Filespec /F (<embedded file>) /EF << /F ");
8978 aLine.append(rEmit.m_nEmbeddedObject);
8979 aLine.append(" 0 R >> >> /Page 0 >>\n");
8982 aLine.append("/Length ");
8984 OStringBuffer aStream;
8985 aStream.append("q ");
8986 if (m_aContext.UseReferenceXObject)
8988 // Reference XObject markup is used, just refer to the fallback bitmap
8989 // here.
8990 aStream.append(aSize.Width());
8991 aStream.append(" 0 0 ");
8992 aStream.append(aSize.Height());
8993 aStream.append(" 0 0 cm\n");
8994 aStream.append("/Im");
8995 aStream.append(rEmit.m_nBitmapObject);
8996 aStream.append(" Do\n");
8998 else
9000 // Reset line width to the default.
9001 aStream.append(" 1 w\n");
9003 // No reference XObject, draw the form XObject containing the original
9004 // page streams.
9005 aStream.append("/Im");
9006 aStream.append(nWrappedFormObject);
9007 aStream.append(" Do\n");
9009 aStream.append("Q");
9010 aLine.append(aStream.getLength());
9012 aLine.append(">>\nstream\n");
9013 aLine.append(aStream.getStr());
9014 aLine.append("\nendstream\nendobj\n\n");
9015 CHECK_RETURN2(writeBuffer(aLine.getStr(), aLine.getLength()));
9018 namespace
9020 unsigned char reverseByte(unsigned char b)
9022 b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
9023 b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
9024 b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
9025 return b;
9028 //tdf#103051 convert any N1BitLsbPal to N1BitMsbPal
9029 Bitmap getExportBitmap(const Bitmap &rBitmap)
9031 Bitmap::ScopedReadAccess pAccess(const_cast<Bitmap&>(rBitmap));
9032 const ScanlineFormat eFormat = pAccess->GetScanlineFormat();
9033 if (eFormat != ScanlineFormat::N1BitLsbPal)
9034 return rBitmap;
9035 Bitmap aNewBmp(rBitmap);
9036 BitmapScopedWriteAccess xWriteAcc(aNewBmp);
9037 const int nScanLineBytes = (pAccess->Width() + 7U) / 8U;
9038 for (long nY = 0L; nY < xWriteAcc->Height(); ++nY)
9040 Scanline pBitSwap = xWriteAcc->GetScanline(nY);
9041 for (int x = 0; x < nScanLineBytes; ++x)
9042 pBitSwap[x] = reverseByte(pBitSwap[x]);
9044 return aNewBmp;
9048 bool PDFWriterImpl::writeBitmapObject( BitmapEmit& rObject, bool bMask )
9050 if (!rObject.m_aReferenceXObject.m_aPDFData.empty() && !m_aContext.UseReferenceXObject)
9052 writeReferenceXObject(rObject.m_aReferenceXObject);
9053 return true;
9056 CHECK_RETURN( updateObject( rObject.m_nObject ) );
9058 Bitmap aBitmap;
9059 Color aTransparentColor( COL_TRANSPARENT );
9060 bool bWriteMask = false;
9061 if( ! bMask )
9063 aBitmap = getExportBitmap(rObject.m_aBitmap.GetBitmap());
9064 if( rObject.m_aBitmap.IsAlpha() )
9066 if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
9067 bWriteMask = true;
9068 // else draw without alpha channel
9070 else
9072 switch( rObject.m_aBitmap.GetTransparentType() )
9074 case TransparentType::NONE:
9075 break;
9076 case TransparentType::Color:
9077 aTransparentColor = rObject.m_aBitmap.GetTransparentColor();
9078 break;
9079 case TransparentType::Bitmap:
9080 bWriteMask = true;
9081 break;
9085 else
9087 if( m_aContext.Version < PDFWriter::PDFVersion::PDF_1_4 || ! rObject.m_aBitmap.IsAlpha() )
9089 aBitmap = getExportBitmap(rObject.m_aBitmap.GetMask());
9090 aBitmap.Convert( BmpConversion::N1BitThreshold );
9091 SAL_WARN_IF( aBitmap.GetBitCount() != 1, "vcl.pdfwriter", "mask conversion failed" );
9093 else if( aBitmap.GetBitCount() != 8 )
9095 aBitmap = getExportBitmap(rObject.m_aBitmap.GetAlpha().GetBitmap());
9096 aBitmap.Convert( BmpConversion::N8BitGreys );
9097 SAL_WARN_IF( aBitmap.GetBitCount() != 8, "vcl.pdfwriter", "alpha mask conversion failed" );
9101 Bitmap::ScopedReadAccess pAccess(aBitmap);
9103 bool bTrueColor;
9104 sal_Int32 nBitsPerComponent;
9105 switch( aBitmap.GetBitCount() )
9107 case 1:
9108 case 2:
9109 case 4:
9110 case 8:
9111 bTrueColor = false;
9112 nBitsPerComponent = aBitmap.GetBitCount();
9113 break;
9114 default:
9115 bTrueColor = true;
9116 nBitsPerComponent = 8;
9117 break;
9120 sal_Int32 nStreamLengthObject = createObject();
9121 sal_Int32 nMaskObject = 0;
9123 if (g_bDebugDisableCompression)
9125 emitComment( "PDFWriterImpl::writeBitmapObject" );
9127 OStringBuffer aLine(1024);
9128 aLine.append( rObject.m_nObject );
9129 aLine.append( " 0 obj\n"
9130 "<</Type/XObject/Subtype/Image/Width " );
9131 aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Width()) );
9132 aLine.append( "/Height " );
9133 aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Height()) );
9134 aLine.append( "/BitsPerComponent " );
9135 aLine.append( nBitsPerComponent );
9136 aLine.append( "/Length " );
9137 aLine.append( nStreamLengthObject );
9138 aLine.append( " 0 R\n" );
9139 if (!g_bDebugDisableCompression)
9141 if( nBitsPerComponent != 1 )
9143 aLine.append( "/Filter/FlateDecode" );
9145 else
9147 aLine.append( "/Filter/CCITTFaxDecode/DecodeParms<</K -1/BlackIs1 true/Columns " );
9148 aLine.append( static_cast<sal_Int32>(aBitmap.GetSizePixel().Width()) );
9149 aLine.append( ">>\n" );
9152 if( ! bMask )
9154 aLine.append( "/ColorSpace" );
9155 if( bTrueColor )
9156 aLine.append( "/DeviceRGB\n" );
9157 else if( aBitmap.HasGreyPalette() )
9159 aLine.append( "/DeviceGray\n" );
9160 if( aBitmap.GetBitCount() == 1 )
9162 // #i47395# 1 bit bitmaps occasionally have an inverted grey palette
9163 sal_uInt16 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_BLACK ) );
9164 assert( nBlackIndex == 0 || nBlackIndex == 1);
9165 sal_uInt16 nWhiteIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_WHITE ) );
9166 if( pAccess->GetPalette()[nBlackIndex] == BitmapColor( COL_BLACK ) &&
9167 pAccess->GetPalette()[nWhiteIndex] == BitmapColor( COL_WHITE ) )
9169 // It is black and white
9170 if( nBlackIndex == 1 )
9171 aLine.append( "/Decode[1 0]\n" );
9173 else
9175 // It is two levels of grey
9176 aLine.append( "/Decode[" );
9177 assert( pAccess->GetPalette()[0].GetRed() == pAccess->GetPalette()[0].GetGreen() &&
9178 pAccess->GetPalette()[0].GetRed() == pAccess->GetPalette()[0].GetBlue() &&
9179 pAccess->GetPalette()[1].GetRed() == pAccess->GetPalette()[1].GetGreen() &&
9180 pAccess->GetPalette()[1].GetRed() == pAccess->GetPalette()[1].GetBlue() );
9181 aLine.append( pAccess->GetPalette()[0].GetRed() / 255.0 );
9182 aLine.append( " " );
9183 aLine.append( pAccess->GetPalette()[1].GetRed() / 255.0 );
9184 aLine.append( "]\n" );
9188 else
9190 aLine.append( "[ /Indexed/DeviceRGB " );
9191 aLine.append( static_cast<sal_Int32>(pAccess->GetPaletteEntryCount()-1) );
9192 aLine.append( "\n<" );
9193 if( m_aContext.Encryption.Encrypt() )
9195 enableStringEncryption( rObject.m_nObject );
9196 //check encryption buffer size
9197 m_vEncryptionBuffer.resize(pAccess->GetPaletteEntryCount()*3);
9198 int nChar = 0;
9199 //fill the encryption buffer
9200 for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
9202 const BitmapColor& rColor = pAccess->GetPaletteColor( i );
9203 m_vEncryptionBuffer[nChar++] = rColor.GetRed();
9204 m_vEncryptionBuffer[nChar++] = rColor.GetGreen();
9205 m_vEncryptionBuffer[nChar++] = rColor.GetBlue();
9207 //encrypt the colorspace lookup table
9208 rtl_cipher_encodeARCFOUR( m_aCipher, m_vEncryptionBuffer.data(), nChar, m_vEncryptionBuffer.data(), nChar );
9209 //now queue the data for output
9210 nChar = 0;
9211 for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
9213 appendHex(m_vEncryptionBuffer[nChar++], aLine );
9214 appendHex(m_vEncryptionBuffer[nChar++], aLine );
9215 appendHex(m_vEncryptionBuffer[nChar++], aLine );
9218 else //no encryption requested (PDF/A-1a program flow drops here)
9220 for( sal_uInt16 i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
9222 const BitmapColor& rColor = pAccess->GetPaletteColor( i );
9223 appendHex( rColor.GetRed(), aLine );
9224 appendHex( rColor.GetGreen(), aLine );
9225 appendHex( rColor.GetBlue(), aLine );
9228 aLine.append( ">\n]\n" );
9231 else
9233 if( aBitmap.GetBitCount() == 1 )
9235 aLine.append( "/ImageMask true\n" );
9236 sal_Int32 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( COL_BLACK ) );
9237 SAL_WARN_IF( nBlackIndex != 0 && nBlackIndex != 1, "vcl.pdfwriter", "wrong black index" );
9238 if( nBlackIndex )
9239 aLine.append( "/Decode[ 1 0 ]\n" );
9240 else
9241 aLine.append( "/Decode[ 0 1 ]\n" );
9243 else if( aBitmap.GetBitCount() == 8 )
9245 aLine.append( "/ColorSpace/DeviceGray\n"
9246 "/Decode [ 1 0 ]\n" );
9250 if( ! bMask && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2 && !m_bIsPDF_A1 )
9252 if( bWriteMask )
9254 nMaskObject = createObject();
9255 if( rObject.m_aBitmap.IsAlpha() && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
9256 aLine.append( "/SMask " );
9257 else
9258 aLine.append( "/Mask " );
9259 aLine.append( nMaskObject );
9260 aLine.append( " 0 R\n" );
9262 else if( aTransparentColor != COL_TRANSPARENT )
9264 aLine.append( "/Mask[ " );
9265 if( bTrueColor )
9267 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetRed()) );
9268 aLine.append( ' ' );
9269 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetRed()) );
9270 aLine.append( ' ' );
9271 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetGreen()) );
9272 aLine.append( ' ' );
9273 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetGreen()) );
9274 aLine.append( ' ' );
9275 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetBlue()) );
9276 aLine.append( ' ' );
9277 aLine.append( static_cast<sal_Int32>(aTransparentColor.GetBlue()) );
9279 else
9281 sal_Int32 nIndex = pAccess->GetBestPaletteIndex( BitmapColor( aTransparentColor ) );
9282 aLine.append( nIndex );
9284 aLine.append( " ]\n" );
9287 else if( m_bIsPDF_A1 && (bWriteMask || aTransparentColor != COL_TRANSPARENT) )
9288 m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA );
9290 aLine.append( ">>\n"
9291 "stream\n" );
9292 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
9293 sal_uInt64 nStartPos = 0;
9294 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nStartPos)) );
9296 checkAndEnableStreamEncryption( rObject.m_nObject );
9297 if (!g_bDebugDisableCompression && nBitsPerComponent == 1)
9299 writeG4Stream(pAccess.get());
9301 else
9303 beginCompression();
9304 if( ! bTrueColor || pAccess->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb )
9306 //With PDF bitmaps, each row is padded to a BYTE boundary (multiple of 8 bits).
9307 const int nScanLineBytes = ((pAccess->GetBitCount() * pAccess->Width()) + 7U) / 8U;
9309 for( long i = 0; i < pAccess->Height(); i++ )
9311 CHECK_RETURN( writeBuffer( pAccess->GetScanline( i ), nScanLineBytes ) );
9314 else
9316 const int nScanLineBytes = pAccess->Width()*3;
9317 std::unique_ptr<sal_uInt8[]> xCol(new sal_uInt8[nScanLineBytes]);
9318 for( long y = 0; y < pAccess->Height(); y++ )
9320 for( long x = 0; x < pAccess->Width(); x++ )
9322 BitmapColor aColor = pAccess->GetColor( y, x );
9323 xCol[3*x+0] = aColor.GetRed();
9324 xCol[3*x+1] = aColor.GetGreen();
9325 xCol[3*x+2] = aColor.GetBlue();
9327 CHECK_RETURN(writeBuffer(xCol.get(), nScanLineBytes));
9330 endCompression();
9332 disableStreamEncryption();
9334 sal_uInt64 nEndPos = 0;
9335 CHECK_RETURN( (osl::File::E_None == m_aFile.getPos(nEndPos)) );
9336 aLine.setLength( 0 );
9337 aLine.append( "\nendstream\nendobj\n\n" );
9338 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
9339 CHECK_RETURN( updateObject( nStreamLengthObject ) );
9340 aLine.setLength( 0 );
9341 aLine.append( nStreamLengthObject );
9342 aLine.append( " 0 obj\n" );
9343 aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
9344 aLine.append( "\nendobj\n\n" );
9345 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
9347 if( nMaskObject )
9349 BitmapEmit aEmit;
9350 aEmit.m_nObject = nMaskObject;
9351 aEmit.m_aBitmap = rObject.m_aBitmap;
9352 return writeBitmapObject( aEmit, true );
9355 writeReferenceXObject(rObject.m_aReferenceXObject);
9357 return true;
9360 void PDFWriterImpl::createEmbeddedFile(const Graphic& rGraphic, ReferenceXObjectEmit& rEmit, sal_Int32 nBitmapObject)
9362 // The bitmap object is always a valid identifier, even if the graphic has
9363 // no pdf data.
9364 rEmit.m_nBitmapObject = nBitmapObject;
9366 if (!rGraphic.hasPdfData())
9367 return;
9369 if (m_aContext.UseReferenceXObject)
9371 // Store the original PDF data as an embedded file.
9372 m_aEmbeddedFiles.emplace_back();
9373 m_aEmbeddedFiles.back().m_nObject = createObject();
9374 m_aEmbeddedFiles.back().m_pData = rGraphic.getPdfData();
9376 rEmit.m_nEmbeddedObject = m_aEmbeddedFiles.back().m_nObject;
9378 else
9379 rEmit.m_aPDFData = *rGraphic.getPdfData();
9381 rEmit.m_nFormObject = createObject();
9382 rEmit.m_aPixelSize = rGraphic.GetPrefSize();
9385 void PDFWriterImpl::drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const Size& rSizePixel, const tools::Rectangle& rTargetArea, const Bitmap& rMask, const Graphic& rGraphic )
9387 MARK( "drawJPGBitmap" );
9389 OStringBuffer aLine( 80 );
9390 updateGraphicsState();
9392 // #i40055# sanity check
9393 if( ! (rTargetArea.GetWidth() && rTargetArea.GetHeight() ) )
9394 return;
9395 if( ! (rSizePixel.Width() && rSizePixel.Height()) )
9396 return;
9398 rDCTData.Seek( 0 );
9399 if( bIsTrueColor && m_aContext.ColorMode == PDFWriter::DrawGreyscale )
9401 // need to convert to grayscale;
9402 // load stream to bitmap and draw the bitmap instead
9403 Graphic aGraphic;
9404 GraphicConverter::Import( rDCTData, aGraphic, ConvertDataFormat::JPG );
9405 if( !!rMask && rMask.GetSizePixel() == aGraphic.GetSizePixel() )
9407 Bitmap aBmp( aGraphic.GetBitmapEx().GetBitmap() );
9408 BitmapEx aBmpEx( aBmp, rMask );
9409 drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aBmpEx );
9411 else
9412 drawBitmap( rTargetArea.TopLeft(), rTargetArea.GetSize(), aGraphic.GetBitmapEx() );
9413 return;
9416 std::unique_ptr<SvMemoryStream> pStream(new SvMemoryStream);
9417 pStream->WriteStream( rDCTData );
9418 pStream->Seek( STREAM_SEEK_TO_END );
9420 BitmapID aID;
9421 aID.m_aPixelSize = rSizePixel;
9422 aID.m_nSize = pStream->Tell();
9423 pStream->Seek( STREAM_SEEK_TO_BEGIN );
9424 aID.m_nChecksum = vcl_get_checksum( 0, pStream->GetData(), aID.m_nSize );
9425 if( ! rMask.IsEmpty() )
9426 aID.m_nMaskChecksum = rMask.GetChecksum();
9428 std::vector< JPGEmit >::const_iterator it = std::find_if(m_aJPGs.begin(), m_aJPGs.end(),
9429 [&](const JPGEmit& arg) { return aID == arg.m_aID; });
9430 if( it == m_aJPGs.end() )
9432 m_aJPGs.emplace( m_aJPGs.begin() );
9433 JPGEmit& rEmit = m_aJPGs.front();
9434 if (!rGraphic.hasPdfData() || m_aContext.UseReferenceXObject)
9435 rEmit.m_nObject = createObject();
9436 rEmit.m_aID = aID;
9437 rEmit.m_pStream = std::move( pStream );
9438 rEmit.m_bTrueColor = bIsTrueColor;
9439 if( !! rMask && rMask.GetSizePixel() == rSizePixel )
9440 rEmit.m_aMask = rMask;
9441 createEmbeddedFile(rGraphic, rEmit.m_aReferenceXObject, rEmit.m_nObject);
9443 it = m_aJPGs.begin();
9446 aLine.append( "q " );
9447 sal_Int32 nCheckWidth = 0;
9448 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetWidth()), aLine, false, &nCheckWidth );
9449 aLine.append( " 0 0 " );
9450 sal_Int32 nCheckHeight = 0;
9451 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rTargetArea.GetHeight()), aLine, true, &nCheckHeight );
9452 aLine.append( ' ' );
9453 m_aPages.back().appendPoint( rTargetArea.BottomLeft(), aLine );
9454 aLine.append( " cm\n/Im" );
9455 sal_Int32 nObject = it->m_aReferenceXObject.getObject();
9456 aLine.append(nObject);
9457 aLine.append( " Do Q\n" );
9458 if( nCheckWidth == 0 || nCheckHeight == 0 )
9460 // #i97512# avoid invalid current matrix
9461 aLine.setLength( 0 );
9462 aLine.append( "\n%jpeg image /Im" );
9463 aLine.append( it->m_nObject );
9464 aLine.append( " scaled to zero size, omitted\n" );
9466 writeBuffer( aLine.getStr(), aLine.getLength() );
9468 OString aObjName = "Im" + OString::number(nObject);
9469 pushResource( ResXObject, aObjName, nObject );
9473 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEmit& rBitmap, const Color& rFillColor )
9475 OStringBuffer aLine( 80 );
9476 updateGraphicsState();
9478 aLine.append( "q " );
9479 if( rFillColor != COL_TRANSPARENT )
9481 appendNonStrokingColor( rFillColor, aLine );
9482 aLine.append( ' ' );
9484 sal_Int32 nCheckWidth = 0;
9485 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Width()), aLine, false, &nCheckWidth );
9486 aLine.append( " 0 0 " );
9487 sal_Int32 nCheckHeight = 0;
9488 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rDestSize.Height()), aLine, true, &nCheckHeight );
9489 aLine.append( ' ' );
9490 m_aPages.back().appendPoint( rDestPoint + Point( 0, rDestSize.Height()-1 ), aLine );
9491 aLine.append( " cm\n/Im" );
9492 sal_Int32 nObject = rBitmap.m_aReferenceXObject.getObject();
9493 aLine.append(nObject);
9494 aLine.append( " Do Q\n" );
9495 if( nCheckWidth == 0 || nCheckHeight == 0 )
9497 // #i97512# avoid invalid current matrix
9498 aLine.setLength( 0 );
9499 aLine.append( "\n%bitmap image /Im" );
9500 aLine.append( rBitmap.m_nObject );
9501 aLine.append( " scaled to zero size, omitted\n" );
9503 writeBuffer( aLine.getStr(), aLine.getLength() );
9506 const PDFWriterImpl::BitmapEmit& PDFWriterImpl::createBitmapEmit( const BitmapEx& i_rBitmap, const Graphic& rGraphic )
9508 BitmapEx aBitmap( i_rBitmap );
9509 if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
9511 BmpConversion eConv = BmpConversion::N8BitGreys;
9512 int nDepth = aBitmap.GetBitmap().GetBitCount();
9513 if( nDepth <= 4 )
9514 eConv = BmpConversion::N4BitGreys;
9515 if( nDepth > 1 )
9516 aBitmap.Convert( eConv );
9518 BitmapID aID;
9519 aID.m_aPixelSize = aBitmap.GetSizePixel();
9520 aID.m_nSize = aBitmap.GetBitCount();
9521 aID.m_nChecksum = aBitmap.GetBitmap().GetChecksum();
9522 aID.m_nMaskChecksum = 0;
9523 if( aBitmap.IsAlpha() )
9524 aID.m_nMaskChecksum = aBitmap.GetAlpha().GetChecksum();
9525 else
9527 Bitmap aMask = aBitmap.GetMask();
9528 if( ! aMask.IsEmpty() )
9529 aID.m_nMaskChecksum = aMask.GetChecksum();
9531 std::list< BitmapEmit >::const_iterator it = std::find_if(m_aBitmaps.begin(), m_aBitmaps.end(),
9532 [&](const BitmapEmit& arg) { return aID == arg.m_aID; });
9533 if( it == m_aBitmaps.end() )
9535 m_aBitmaps.push_front( BitmapEmit() );
9536 m_aBitmaps.front().m_aID = aID;
9537 m_aBitmaps.front().m_aBitmap = aBitmap;
9538 if (!rGraphic.hasPdfData() || m_aContext.UseReferenceXObject)
9539 m_aBitmaps.front().m_nObject = createObject();
9540 createEmbeddedFile(rGraphic, m_aBitmaps.front().m_aReferenceXObject, m_aBitmaps.front().m_nObject);
9541 it = m_aBitmaps.begin();
9544 sal_Int32 nObject = it->m_aReferenceXObject.getObject();
9545 OString aObjName = "Im" + OString::number(nObject);
9546 pushResource( ResXObject, aObjName, nObject );
9548 return *it;
9551 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap, const Graphic& rGraphic )
9553 MARK( "drawBitmap (Bitmap)" );
9555 // #i40055# sanity check
9556 if( ! (rDestSize.Width() && rDestSize.Height()) )
9557 return;
9559 const BitmapEmit& rEmit = createBitmapEmit( BitmapEx( rBitmap ), rGraphic );
9560 drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
9563 void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEx& rBitmap )
9565 MARK( "drawBitmap (BitmapEx)" );
9567 // #i40055# sanity check
9568 if( ! (rDestSize.Width() && rDestSize.Height()) )
9569 return;
9571 const BitmapEmit& rEmit = createBitmapEmit( rBitmap, Graphic() );
9572 drawBitmap( rDestPoint, rDestSize, rEmit, COL_TRANSPARENT );
9575 sal_Int32 PDFWriterImpl::createGradient( const Gradient& rGradient, const Size& rSize )
9577 Size aPtSize( lcl_convert( m_aGraphicsStack.front().m_aMapMode,
9578 MapMode( MapUnit::MapPoint ),
9579 this,
9580 rSize ) );
9581 // check if we already have this gradient
9582 // rounding to point will generally lose some pixels
9583 // round up to point boundary
9584 aPtSize.AdjustWidth( 1 );
9585 aPtSize.AdjustHeight( 1 );
9586 std::list< GradientEmit >::const_iterator it = std::find_if(m_aGradients.begin(), m_aGradients.end(),
9587 [&](const GradientEmit& arg) { return ((rGradient == arg.m_aGradient) && (aPtSize == arg.m_aSize) ); });
9589 if( it == m_aGradients.end() )
9591 m_aGradients.push_front( GradientEmit() );
9592 m_aGradients.front().m_aGradient = rGradient;
9593 m_aGradients.front().m_nObject = createObject();
9594 m_aGradients.front().m_aSize = aPtSize;
9595 it = m_aGradients.begin();
9598 OStringBuffer aObjName( 16 );
9599 aObjName.append( 'P' );
9600 aObjName.append( it->m_nObject );
9601 pushResource( ResShading, aObjName.makeStringAndClear(), it->m_nObject );
9603 return it->m_nObject;
9606 void PDFWriterImpl::drawGradient( const tools::Rectangle& rRect, const Gradient& rGradient )
9608 MARK( "drawGradient (Rectangle)" );
9610 if( m_aContext.Version == PDFWriter::PDFVersion::PDF_1_2 )
9612 drawRectangle( rRect );
9613 return;
9616 sal_Int32 nGradient = createGradient( rGradient, rRect.GetSize() );
9618 Point aTranslate( rRect.BottomLeft() );
9619 aTranslate += Point( 0, 1 );
9621 updateGraphicsState();
9623 OStringBuffer aLine( 80 );
9624 aLine.append( "q 1 0 0 1 " );
9625 m_aPages.back().appendPoint( aTranslate, aLine );
9626 aLine.append( " cm " );
9627 // if a stroke is appended reset the clip region before stroke
9628 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
9629 aLine.append( "q " );
9630 aLine.append( "0 0 " );
9631 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
9632 aLine.append( ' ' );
9633 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
9634 aLine.append( " re W n\n" );
9636 aLine.append( "/P" );
9637 aLine.append( nGradient );
9638 aLine.append( " sh " );
9639 if( m_aGraphicsStack.front().m_aLineColor != COL_TRANSPARENT )
9641 aLine.append( "Q 0 0 " );
9642 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), aLine, false );
9643 aLine.append( ' ' );
9644 m_aPages.back().appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), aLine );
9645 aLine.append( " re S " );
9647 aLine.append( "Q\n" );
9648 writeBuffer( aLine.getStr(), aLine.getLength() );
9651 void PDFWriterImpl::drawHatch( const tools::PolyPolygon& rPolyPoly, const Hatch& rHatch )
9653 MARK( "drawHatch" );
9655 updateGraphicsState();
9657 if( rPolyPoly.Count() )
9659 tools::PolyPolygon aPolyPoly( rPolyPoly );
9661 aPolyPoly.Optimize( PolyOptimizeFlags::NO_SAME );
9662 push( PushFlags::LINECOLOR );
9663 setLineColor( rHatch.GetColor() );
9664 DrawHatch( aPolyPoly, rHatch, false );
9665 pop();
9669 void PDFWriterImpl::drawWallpaper( const tools::Rectangle& rRect, const Wallpaper& rWall )
9671 MARK( "drawWallpaper" );
9673 bool bDrawColor = false;
9674 bool bDrawGradient = false;
9675 bool bDrawBitmap = false;
9677 BitmapEx aBitmap;
9678 Point aBmpPos = rRect.TopLeft();
9679 Size aBmpSize;
9680 if( rWall.IsBitmap() )
9682 aBitmap = rWall.GetBitmap();
9683 aBmpSize = lcl_convert( aBitmap.GetPrefMapMode(),
9684 getMapMode(),
9685 this,
9686 aBitmap.GetPrefSize() );
9687 tools::Rectangle aRect( rRect );
9688 if( rWall.IsRect() )
9690 aRect = rWall.GetRect();
9691 aBmpPos = aRect.TopLeft();
9692 aBmpSize = aRect.GetSize();
9694 if( rWall.GetStyle() != WallpaperStyle::Scale )
9696 if( rWall.GetStyle() != WallpaperStyle::Tile )
9698 bDrawBitmap = true;
9699 if( rWall.IsGradient() )
9700 bDrawGradient = true;
9701 else
9702 bDrawColor = true;
9703 switch( rWall.GetStyle() )
9705 case WallpaperStyle::TopLeft:
9706 break;
9707 case WallpaperStyle::Top:
9708 aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
9709 break;
9710 case WallpaperStyle::Left:
9711 aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
9712 break;
9713 case WallpaperStyle::TopRight:
9714 aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
9715 break;
9716 case WallpaperStyle::Center:
9717 aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
9718 aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
9719 break;
9720 case WallpaperStyle::Right:
9721 aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
9722 aBmpPos.AdjustY((aRect.GetHeight()-aBmpSize.Height())/2 );
9723 break;
9724 case WallpaperStyle::BottomLeft:
9725 aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
9726 break;
9727 case WallpaperStyle::Bottom:
9728 aBmpPos.AdjustX((aRect.GetWidth()-aBmpSize.Width())/2 );
9729 aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
9730 break;
9731 case WallpaperStyle::BottomRight:
9732 aBmpPos.AdjustX(aRect.GetWidth()-aBmpSize.Width() );
9733 aBmpPos.AdjustY(aRect.GetHeight()-aBmpSize.Height() );
9734 break;
9735 default: ;
9738 else
9740 // push the bitmap
9741 const BitmapEmit& rEmit = createBitmapEmit( aBitmap, Graphic() );
9743 // convert to page coordinates; this needs to be done here
9744 // since the emit does not know the page anymore
9745 tools::Rectangle aConvertRect( aBmpPos, aBmpSize );
9746 m_aPages.back().convertRect( aConvertRect );
9748 OString aImageName = "Im" + OString::number( rEmit.m_nObject );
9750 // push the pattern
9751 OStringBuffer aTilingStream( 32 );
9752 appendFixedInt( aConvertRect.GetWidth(), aTilingStream );
9753 aTilingStream.append( " 0 0 " );
9754 appendFixedInt( aConvertRect.GetHeight(), aTilingStream );
9755 aTilingStream.append( " 0 0 cm\n/" );
9756 aTilingStream.append( aImageName );
9757 aTilingStream.append( " Do\n" );
9759 m_aTilings.emplace_back( );
9760 m_aTilings.back().m_nObject = createObject();
9761 m_aTilings.back().m_aRectangle = tools::Rectangle( Point( 0, 0 ), aConvertRect.GetSize() );
9762 m_aTilings.back().m_pTilingStream.reset(new SvMemoryStream());
9763 m_aTilings.back().m_pTilingStream->WriteBytes(
9764 aTilingStream.getStr(), aTilingStream.getLength() );
9765 // phase the tiling so wallpaper begins on upper left
9766 if ((aConvertRect.GetWidth() == 0) || (aConvertRect.GetHeight() == 0))
9767 throw o3tl::divide_by_zero();
9768 m_aTilings.back().m_aTransform.matrix[2] = double(aConvertRect.Left() % aConvertRect.GetWidth()) / fDivisor;
9769 m_aTilings.back().m_aTransform.matrix[5] = double(aConvertRect.Top() % aConvertRect.GetHeight()) / fDivisor;
9770 m_aTilings.back().m_aResources.m_aXObjects[aImageName] = rEmit.m_nObject;
9772 updateGraphicsState();
9774 OStringBuffer aObjName( 16 );
9775 aObjName.append( 'P' );
9776 aObjName.append( m_aTilings.back().m_nObject );
9777 OString aPatternName( aObjName.makeStringAndClear() );
9778 pushResource( ResPattern, aPatternName, m_aTilings.back().m_nObject );
9780 // fill a rRect with the pattern
9781 OStringBuffer aLine( 100 );
9782 aLine.append( "q /Pattern cs /" );
9783 aLine.append( aPatternName );
9784 aLine.append( " scn " );
9785 m_aPages.back().appendRect( rRect, aLine );
9786 aLine.append( " f Q\n" );
9787 writeBuffer( aLine.getStr(), aLine.getLength() );
9790 else
9792 aBmpPos = aRect.TopLeft();
9793 aBmpSize = aRect.GetSize();
9794 bDrawBitmap = true;
9797 if( aBitmap.IsTransparent() )
9799 if( rWall.IsGradient() )
9800 bDrawGradient = true;
9801 else
9802 bDrawColor = true;
9805 else if( rWall.IsGradient() )
9806 bDrawGradient = true;
9807 else
9808 bDrawColor = true;
9810 if( bDrawGradient )
9812 drawGradient( rRect, rWall.GetGradient() );
9814 if( bDrawColor )
9816 Color aOldLineColor = m_aGraphicsStack.front().m_aLineColor;
9817 Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
9818 setLineColor( COL_TRANSPARENT );
9819 setFillColor( rWall.GetColor() );
9820 drawRectangle( rRect );
9821 setLineColor( aOldLineColor );
9822 setFillColor( aOldFillColor );
9824 if( bDrawBitmap )
9826 // set temporary clip region since aBmpPos and aBmpSize
9827 // may be outside rRect
9828 OStringBuffer aLine( 20 );
9829 aLine.append( "q " );
9830 m_aPages.back().appendRect( rRect, aLine );
9831 aLine.append( " W n\n" );
9832 writeBuffer( aLine.getStr(), aLine.getLength() );
9833 drawBitmap( aBmpPos, aBmpSize, aBitmap );
9834 writeBuffer( "Q\n", 2 );
9838 void PDFWriterImpl::updateGraphicsState(Mode const mode)
9840 OStringBuffer aLine( 256 );
9841 GraphicsState& rNewState = m_aGraphicsStack.front();
9842 // first set clip region since it might invalidate everything else
9844 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::ClipRegion )
9846 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::ClipRegion;
9848 if( m_aCurrentPDFState.m_bClipRegion != rNewState.m_bClipRegion ||
9849 ( rNewState.m_bClipRegion && m_aCurrentPDFState.m_aClipRegion != rNewState.m_aClipRegion ) )
9851 if( m_aCurrentPDFState.m_bClipRegion )
9853 aLine.append( "Q " );
9854 // invalidate everything but the clip region
9855 m_aCurrentPDFState = GraphicsState();
9856 rNewState.m_nUpdateFlags = ~GraphicsStateUpdateFlags::ClipRegion;
9858 if( rNewState.m_bClipRegion )
9860 // clip region is always stored in private PDF mapmode
9861 MapMode aNewMapMode = rNewState.m_aMapMode;
9862 rNewState.m_aMapMode = m_aMapMode;
9863 SetMapMode( rNewState.m_aMapMode );
9864 m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
9866 aLine.append("q ");
9867 if ( rNewState.m_aClipRegion.count() )
9869 m_aPages.back().appendPolyPolygon( rNewState.m_aClipRegion, aLine );
9870 aLine.append( "W* n\n" );
9873 rNewState.m_aMapMode = aNewMapMode;
9874 SetMapMode( rNewState.m_aMapMode );
9875 m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
9880 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::MapMode )
9882 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::MapMode;
9883 SetMapMode( rNewState.m_aMapMode );
9886 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::Font )
9888 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::Font;
9889 SetFont( rNewState.m_aFont );
9892 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LayoutMode )
9894 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LayoutMode;
9895 SetLayoutMode( rNewState.m_nLayoutMode );
9898 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::DigitLanguage )
9900 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::DigitLanguage;
9901 SetDigitLanguage( rNewState.m_aDigitLanguage );
9904 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::LineColor )
9906 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::LineColor;
9907 if( m_aCurrentPDFState.m_aLineColor != rNewState.m_aLineColor &&
9908 rNewState.m_aLineColor != COL_TRANSPARENT )
9910 appendStrokingColor( rNewState.m_aLineColor, aLine );
9911 aLine.append( "\n" );
9915 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::FillColor )
9917 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::FillColor;
9918 if( m_aCurrentPDFState.m_aFillColor != rNewState.m_aFillColor &&
9919 rNewState.m_aFillColor != COL_TRANSPARENT )
9921 appendNonStrokingColor( rNewState.m_aFillColor, aLine );
9922 aLine.append( "\n" );
9926 if( rNewState.m_nUpdateFlags & GraphicsStateUpdateFlags::TransparentPercent )
9928 rNewState.m_nUpdateFlags &= ~GraphicsStateUpdateFlags::TransparentPercent;
9929 if( m_aContext.Version >= PDFWriter::PDFVersion::PDF_1_4 )
9931 // TODO: switch extended graphicsstate
9935 // everything is up to date now
9936 m_aCurrentPDFState = m_aGraphicsStack.front();
9937 if ((mode != NOWRITE) && !aLine.isEmpty())
9938 writeBuffer( aLine.getStr(), aLine.getLength() );
9941 /* #i47544# imitate OutputDevice behaviour:
9942 * if a font with a nontransparent color is set, it overwrites the current
9943 * text color. OTOH setting the text color will overwrite the color of the font.
9945 void PDFWriterImpl::setFont( const vcl::Font& rFont )
9947 Color aColor = rFont.GetColor();
9948 if( aColor == COL_TRANSPARENT )
9949 aColor = m_aGraphicsStack.front().m_aFont.GetColor();
9950 m_aGraphicsStack.front().m_aFont = rFont;
9951 m_aGraphicsStack.front().m_aFont.SetColor( aColor );
9952 m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::Font;
9955 void PDFWriterImpl::push( PushFlags nFlags )
9957 OSL_ENSURE( !m_aGraphicsStack.empty(), "invalid graphics stack" );
9958 m_aGraphicsStack.push_front( m_aGraphicsStack.front() );
9959 m_aGraphicsStack.front().m_nFlags = nFlags;
9962 void PDFWriterImpl::pop()
9964 OSL_ENSURE( m_aGraphicsStack.size() > 1, "pop without push" );
9965 if( m_aGraphicsStack.size() < 2 )
9966 return;
9968 GraphicsState aState = m_aGraphicsStack.front();
9969 m_aGraphicsStack.pop_front();
9970 GraphicsState& rOld = m_aGraphicsStack.front();
9972 // move those parameters back that were not pushed
9973 // in the first place
9974 if( ! (aState.m_nFlags & PushFlags::LINECOLOR) )
9975 setLineColor( aState.m_aLineColor );
9976 if( ! (aState.m_nFlags & PushFlags::FILLCOLOR) )
9977 setFillColor( aState.m_aFillColor );
9978 if( ! (aState.m_nFlags & PushFlags::FONT) )
9979 setFont( aState.m_aFont );
9980 if( ! (aState.m_nFlags & PushFlags::TEXTCOLOR) )
9981 setTextColor( aState.m_aFont.GetColor() );
9982 if( ! (aState.m_nFlags & PushFlags::MAPMODE) )
9983 setMapMode( aState.m_aMapMode );
9984 if( ! (aState.m_nFlags & PushFlags::CLIPREGION) )
9986 // do not use setClipRegion here
9987 // it would convert again assuming the current mapmode
9988 rOld.m_aClipRegion = aState.m_aClipRegion;
9989 rOld.m_bClipRegion = aState.m_bClipRegion;
9991 if( ! (aState.m_nFlags & PushFlags::TEXTLINECOLOR ) )
9992 setTextLineColor( aState.m_aTextLineColor );
9993 if( ! (aState.m_nFlags & PushFlags::OVERLINECOLOR ) )
9994 setOverlineColor( aState.m_aOverlineColor );
9995 if( ! (aState.m_nFlags & PushFlags::TEXTALIGN ) )
9996 setTextAlign( aState.m_aFont.GetAlignment() );
9997 if( ! (aState.m_nFlags & PushFlags::TEXTFILLCOLOR) )
9998 setTextFillColor( aState.m_aFont.GetFillColor() );
9999 if( ! (aState.m_nFlags & PushFlags::REFPOINT) )
10001 // what ?
10003 // invalidate graphics state
10004 m_aGraphicsStack.front().m_nUpdateFlags = GraphicsStateUpdateFlags::All;
10007 void PDFWriterImpl::setMapMode( const MapMode& rMapMode )
10009 m_aGraphicsStack.front().m_aMapMode = rMapMode;
10010 SetMapMode( rMapMode );
10011 m_aCurrentPDFState.m_aMapMode = rMapMode;
10014 void PDFWriterImpl::setClipRegion( const basegfx::B2DPolyPolygon& rRegion )
10016 basegfx::B2DPolyPolygon aRegion = LogicToPixel( rRegion, m_aGraphicsStack.front().m_aMapMode );
10017 aRegion = PixelToLogic( aRegion, m_aMapMode );
10018 m_aGraphicsStack.front().m_aClipRegion = aRegion;
10019 m_aGraphicsStack.front().m_bClipRegion = true;
10020 m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10023 void PDFWriterImpl::moveClipRegion( sal_Int32 nX, sal_Int32 nY )
10025 if( m_aGraphicsStack.front().m_bClipRegion && m_aGraphicsStack.front().m_aClipRegion.count() )
10027 Point aPoint( lcl_convert( m_aGraphicsStack.front().m_aMapMode,
10028 m_aMapMode,
10029 this,
10030 Point( nX, nY ) ) );
10031 aPoint -= lcl_convert( m_aGraphicsStack.front().m_aMapMode,
10032 m_aMapMode,
10033 this,
10034 Point() );
10035 basegfx::B2DHomMatrix aMat;
10036 aMat.translate( aPoint.X(), aPoint.Y() );
10037 m_aGraphicsStack.front().m_aClipRegion.transform( aMat );
10038 m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10042 void PDFWriterImpl::intersectClipRegion( const tools::Rectangle& rRect )
10044 basegfx::B2DPolyPolygon aRect( basegfx::utils::createPolygonFromRect(
10045 vcl::unotools::b2DRectangleFromRectangle(rRect) ) );
10046 intersectClipRegion( aRect );
10049 void PDFWriterImpl::intersectClipRegion( const basegfx::B2DPolyPolygon& rRegion )
10051 basegfx::B2DPolyPolygon aRegion( LogicToPixel( rRegion, m_aGraphicsStack.front().m_aMapMode ) );
10052 aRegion = PixelToLogic( aRegion, m_aMapMode );
10053 m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsStateUpdateFlags::ClipRegion;
10054 if( m_aGraphicsStack.front().m_bClipRegion )
10056 basegfx::B2DPolyPolygon aOld( basegfx::utils::prepareForPolygonOperation( m_aGraphicsStack.front().m_aClipRegion ) );
10057 aRegion = basegfx::utils::prepareForPolygonOperation( aRegion );
10058 m_aGraphicsStack.front().m_aClipRegion = basegfx::utils::solvePolygonOperationAnd( aOld, aRegion );
10060 else
10062 m_aGraphicsStack.front().m_aClipRegion = aRegion;
10063 m_aGraphicsStack.front().m_bClipRegion = true;
10067 void PDFWriterImpl::createNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr )
10069 if( nPageNr < 0 )
10070 nPageNr = m_nCurrentPage;
10072 if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
10073 return;
10075 m_aNotes.emplace_back( );
10076 m_aNotes.back().m_nObject = createObject();
10077 m_aNotes.back().m_aContents = rNote;
10078 m_aNotes.back().m_aRect = rRect;
10079 // convert to default user space now, since the mapmode may change
10080 m_aPages[nPageNr].convertRect( m_aNotes.back().m_aRect );
10082 // insert note to page's annotation list
10083 m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aNotes.back().m_nObject );
10086 sal_Int32 PDFWriterImpl::createLink( const tools::Rectangle& rRect, sal_Int32 nPageNr )
10088 if( nPageNr < 0 )
10089 nPageNr = m_nCurrentPage;
10091 if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
10092 return -1;
10094 sal_Int32 nRet = m_aLinks.size();
10096 m_aLinks.emplace_back( );
10097 m_aLinks.back().m_nObject = createObject();
10098 m_aLinks.back().m_nPage = nPageNr;
10099 m_aLinks.back().m_aRect = rRect;
10100 // convert to default user space now, since the mapmode may change
10101 m_aPages[nPageNr].convertRect( m_aLinks.back().m_aRect );
10103 // insert link to page's annotation list
10104 m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aLinks.back().m_nObject );
10106 return nRet;
10109 sal_Int32 PDFWriterImpl::createScreen(const tools::Rectangle& rRect, sal_Int32 nPageNr)
10111 if (nPageNr < 0)
10112 nPageNr = m_nCurrentPage;
10114 if (nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()))
10115 return -1;
10117 sal_Int32 nRet = m_aScreens.size();
10119 m_aScreens.emplace_back();
10120 m_aScreens.back().m_nObject = createObject();
10121 m_aScreens.back().m_nPage = nPageNr;
10122 m_aScreens.back().m_aRect = rRect;
10123 // Convert to default user space now, since the mapmode may change.
10124 m_aPages[nPageNr].convertRect(m_aScreens.back().m_aRect);
10126 // Insert link to page's annotation list.
10127 m_aPages[nPageNr].m_aAnnotations.push_back(m_aScreens.back().m_nObject);
10129 return nRet;
10132 sal_Int32 PDFWriterImpl::createNamedDest( const OUString& sDestName, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10134 if( nPageNr < 0 )
10135 nPageNr = m_nCurrentPage;
10137 if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
10138 return -1;
10140 sal_Int32 nRet = m_aNamedDests.size();
10142 m_aNamedDests.emplace_back( );
10143 m_aNamedDests.back().m_aDestName = sDestName;
10144 m_aNamedDests.back().m_nPage = nPageNr;
10145 m_aNamedDests.back().m_eType = eType;
10146 m_aNamedDests.back().m_aRect = rRect;
10147 // convert to default user space now, since the mapmode may change
10148 m_aPages[nPageNr].convertRect( m_aNamedDests.back().m_aRect );
10150 return nRet;
10153 sal_Int32 PDFWriterImpl::createDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10155 if( nPageNr < 0 )
10156 nPageNr = m_nCurrentPage;
10158 if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
10159 return -1;
10161 sal_Int32 nRet = m_aDests.size();
10163 m_aDests.emplace_back( );
10164 m_aDests.back().m_nPage = nPageNr;
10165 m_aDests.back().m_eType = eType;
10166 m_aDests.back().m_aRect = rRect;
10167 // convert to default user space now, since the mapmode may change
10168 m_aPages[nPageNr].convertRect( m_aDests.back().m_aRect );
10170 return nRet;
10173 sal_Int32 PDFWriterImpl::registerDestReference( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
10175 return m_aDestinationIdTranslation[ nDestId ] = createDest( rRect, nPageNr, eType );
10178 void PDFWriterImpl::setLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
10180 if( nLinkId < 0 || nLinkId >= static_cast<sal_Int32>(m_aLinks.size()) )
10181 return;
10182 if( nDestId < 0 || nDestId >= static_cast<sal_Int32>(m_aDests.size()) )
10183 return;
10185 m_aLinks[ nLinkId ].m_nDest = nDestId;
10188 void PDFWriterImpl::setLinkURL( sal_Int32 nLinkId, const OUString& rURL )
10190 if( nLinkId < 0 || nLinkId >= static_cast<sal_Int32>(m_aLinks.size()) )
10191 return;
10193 m_aLinks[ nLinkId ].m_nDest = -1;
10195 using namespace ::com::sun::star;
10197 if (!m_xTrans.is())
10199 uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
10200 m_xTrans = util::URLTransformer::create(xContext);
10203 util::URL aURL;
10204 aURL.Complete = rURL;
10206 m_xTrans->parseStrict( aURL );
10208 m_aLinks[ nLinkId ].m_aURL = aURL.Complete;
10211 void PDFWriterImpl::setScreenURL(sal_Int32 nScreenId, const OUString& rURL)
10213 if (nScreenId < 0 || nScreenId >= static_cast<sal_Int32>(m_aScreens.size()))
10214 return;
10216 m_aScreens[nScreenId].m_aURL = rURL;
10219 void PDFWriterImpl::setScreenStream(sal_Int32 nScreenId, const OUString& rURL)
10221 if (nScreenId < 0 || nScreenId >= static_cast<sal_Int32>(m_aScreens.size()))
10222 return;
10224 m_aScreens[nScreenId].m_aTempFileURL = rURL;
10225 m_aScreens[nScreenId].m_nTempFileObject = createObject();
10228 void PDFWriterImpl::setLinkPropertyId( sal_Int32 nLinkId, sal_Int32 nPropertyId )
10230 m_aLinkPropertyMap[ nPropertyId ] = nLinkId;
10233 sal_Int32 PDFWriterImpl::createOutlineItem( sal_Int32 nParent, const OUString& rText, sal_Int32 nDestID )
10235 // create new item
10236 sal_Int32 nNewItem = m_aOutline.size();
10237 m_aOutline.emplace_back( );
10239 // set item attributes
10240 setOutlineItemParent( nNewItem, nParent );
10241 setOutlineItemText( nNewItem, rText );
10242 setOutlineItemDest( nNewItem, nDestID );
10244 return nNewItem;
10247 void PDFWriterImpl::setOutlineItemParent( sal_Int32 nItem, sal_Int32 nNewParent )
10249 if( nItem < 1 || nItem >= static_cast<sal_Int32>(m_aOutline.size()) )
10250 return;
10252 if( nNewParent < 0 || nNewParent >= static_cast<sal_Int32>(m_aOutline.size()) || nNewParent == nItem )
10254 nNewParent = 0;
10256 // insert item to new parent's list of children
10257 m_aOutline[ nNewParent ].m_aChildren.push_back( nItem );
10260 void PDFWriterImpl::setOutlineItemText( sal_Int32 nItem, const OUString& rText )
10262 if( nItem < 1 || nItem >= static_cast<sal_Int32>(m_aOutline.size()) )
10263 return;
10265 m_aOutline[ nItem ].m_aTitle = psp::WhitespaceToSpace( rText );
10268 void PDFWriterImpl::setOutlineItemDest( sal_Int32 nItem, sal_Int32 nDestID )
10270 if( nItem < 1 || nItem >= static_cast<sal_Int32>(m_aOutline.size()) ) // item does not exist
10271 return;
10272 if( nDestID < 0 || nDestID >= static_cast<sal_Int32>(m_aDests.size()) ) // dest does not exist
10273 return;
10274 m_aOutline[nItem].m_nDestID = nDestID;
10277 const sal_Char* PDFWriterImpl::getStructureTag( PDFWriter::StructElement eType )
10279 static std::map< PDFWriter::StructElement, const char* > aTagStrings;
10280 if( aTagStrings.empty() )
10282 aTagStrings[ PDFWriter::NonStructElement] = "NonStruct";
10283 aTagStrings[ PDFWriter::Document ] = "Document";
10284 aTagStrings[ PDFWriter::Part ] = "Part";
10285 aTagStrings[ PDFWriter::Article ] = "Art";
10286 aTagStrings[ PDFWriter::Section ] = "Sect";
10287 aTagStrings[ PDFWriter::Division ] = "Div";
10288 aTagStrings[ PDFWriter::BlockQuote ] = "BlockQuote";
10289 aTagStrings[ PDFWriter::Caption ] = "Caption";
10290 aTagStrings[ PDFWriter::TOC ] = "TOC";
10291 aTagStrings[ PDFWriter::TOCI ] = "TOCI";
10292 aTagStrings[ PDFWriter::Index ] = "Index";
10293 aTagStrings[ PDFWriter::Paragraph ] = "P";
10294 aTagStrings[ PDFWriter::Heading ] = "H";
10295 aTagStrings[ PDFWriter::H1 ] = "H1";
10296 aTagStrings[ PDFWriter::H2 ] = "H2";
10297 aTagStrings[ PDFWriter::H3 ] = "H3";
10298 aTagStrings[ PDFWriter::H4 ] = "H4";
10299 aTagStrings[ PDFWriter::H5 ] = "H5";
10300 aTagStrings[ PDFWriter::H6 ] = "H6";
10301 aTagStrings[ PDFWriter::List ] = "L";
10302 aTagStrings[ PDFWriter::ListItem ] = "LI";
10303 aTagStrings[ PDFWriter::LILabel ] = "Lbl";
10304 aTagStrings[ PDFWriter::LIBody ] = "LBody";
10305 aTagStrings[ PDFWriter::Table ] = "Table";
10306 aTagStrings[ PDFWriter::TableRow ] = "TR";
10307 aTagStrings[ PDFWriter::TableHeader ] = "TH";
10308 aTagStrings[ PDFWriter::TableData ] = "TD";
10309 aTagStrings[ PDFWriter::Span ] = "Span";
10310 aTagStrings[ PDFWriter::Quote ] = "Quote";
10311 aTagStrings[ PDFWriter::Note ] = "Note";
10312 aTagStrings[ PDFWriter::Reference ] = "Reference";
10313 aTagStrings[ PDFWriter::BibEntry ] = "BibEntry";
10314 aTagStrings[ PDFWriter::Code ] = "Code";
10315 aTagStrings[ PDFWriter::Link ] = "Link";
10316 aTagStrings[ PDFWriter::Figure ] = "Figure";
10317 aTagStrings[ PDFWriter::Formula ] = "Formula";
10318 aTagStrings[ PDFWriter::Form ] = "Form";
10321 std::map< PDFWriter::StructElement, const char* >::const_iterator it = aTagStrings.find( eType );
10323 return it != aTagStrings.end() ? it->second : "Div";
10326 void PDFWriterImpl::beginStructureElementMCSeq()
10328 if( m_bEmitStructure &&
10329 m_nCurrentStructElement > 0 && // StructTreeRoot
10330 ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
10333 PDFStructureElement& rEle = m_aStructure[ m_nCurrentStructElement ];
10334 OStringBuffer aLine( 128 );
10335 sal_Int32 nMCID = m_aPages[ m_nCurrentPage ].m_aMCIDParents.size();
10336 aLine.append( "/" );
10337 if( !rEle.m_aAlias.isEmpty() )
10338 aLine.append( rEle.m_aAlias );
10339 else
10340 aLine.append( getStructureTag( rEle.m_eType ) );
10341 aLine.append( "<</MCID " );
10342 aLine.append( nMCID );
10343 aLine.append( ">>BDC\n" );
10344 writeBuffer( aLine.getStr(), aLine.getLength() );
10346 // update the element's content list
10347 SAL_INFO("vcl.pdfwriter", "beginning marked content id " << nMCID << " on page object "
10348 << m_aPages[ m_nCurrentPage ].m_nPageObject << ", structure first page = "
10349 << rEle.m_nFirstPageObject);
10350 rEle.m_aKids.emplace_back( nMCID, m_aPages[m_nCurrentPage].m_nPageObject );
10351 // update the page's mcid parent list
10352 m_aPages[ m_nCurrentPage ].m_aMCIDParents.push_back( rEle.m_nObject );
10353 // mark element MC sequence as open
10354 rEle.m_bOpenMCSeq = true;
10356 // handle artifacts
10357 else if( ! m_bEmitStructure && m_aContext.Tagged &&
10358 m_nCurrentStructElement > 0 &&
10359 m_aStructure[ m_nCurrentStructElement ].m_eType == PDFWriter::NonStructElement &&
10360 ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
10363 OString aLine = "/Artifact BMC\n";
10364 writeBuffer( aLine.getStr(), aLine.getLength() );
10365 // mark element MC sequence as open
10366 m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = true;
10370 void PDFWriterImpl::endStructureElementMCSeq()
10372 if( m_nCurrentStructElement > 0 && // StructTreeRoot
10373 ( m_bEmitStructure || m_aStructure[ m_nCurrentStructElement ].m_eType == PDFWriter::NonStructElement ) &&
10374 m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // must have an opened MC sequence
10377 writeBuffer( "EMC\n", 4 );
10378 m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = false;
10382 bool PDFWriterImpl::checkEmitStructure()
10384 bool bEmit = false;
10385 if( m_aContext.Tagged )
10387 bEmit = true;
10388 sal_Int32 nEle = m_nCurrentStructElement;
10389 while( nEle > 0 && nEle < sal_Int32(m_aStructure.size()) )
10391 if( m_aStructure[ nEle ].m_eType == PDFWriter::NonStructElement )
10393 bEmit = false;
10394 break;
10396 nEle = m_aStructure[ nEle ].m_nParentElement;
10399 return bEmit;
10402 sal_Int32 PDFWriterImpl::beginStructureElement( PDFWriter::StructElement eType, const OUString& rAlias )
10404 if( m_nCurrentPage < 0 )
10405 return -1;
10407 if( ! m_aContext.Tagged )
10408 return -1;
10410 // close eventual current MC sequence
10411 endStructureElementMCSeq();
10413 if( m_nCurrentStructElement == 0 &&
10414 eType != PDFWriter::Document && eType != PDFWriter::NonStructElement )
10416 // struct tree root hit, but not beginning document
10417 // this might happen with setCurrentStructureElement
10418 // silently insert structure into document again if one properly exists
10419 if( ! m_aStructure[ 0 ].m_aChildren.empty() )
10421 const std::list< sal_Int32 >& rRootChildren = m_aStructure[0].m_aChildren;
10422 auto it = std::find_if(rRootChildren.begin(), rRootChildren.end(),
10423 [&](sal_Int32 nElement) { return m_aStructure[ nElement ].m_eType == PDFWriter::Document; });
10424 if( it != rRootChildren.end() )
10426 m_nCurrentStructElement = *it;
10427 SAL_WARN( "vcl.pdfwriter", "Structure element inserted to StructTreeRoot that is not a document" );
10429 else {
10430 OSL_FAIL( "document structure in disorder !" );
10433 else {
10434 OSL_FAIL( "PDF document structure MUST be contained in a Document element" );
10438 sal_Int32 nNewId = sal_Int32(m_aStructure.size());
10439 m_aStructure.emplace_back( );
10440 PDFStructureElement& rEle = m_aStructure.back();
10441 rEle.m_eType = eType;
10442 rEle.m_nOwnElement = nNewId;
10443 rEle.m_nParentElement = m_nCurrentStructElement;
10444 rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject;
10445 m_aStructure[ m_nCurrentStructElement ].m_aChildren.push_back( nNewId );
10446 m_nCurrentStructElement = nNewId;
10448 // handle alias names
10449 if( !rAlias.isEmpty() && eType != PDFWriter::NonStructElement )
10451 OStringBuffer aNameBuf( rAlias.getLength() );
10452 appendName( rAlias, aNameBuf );
10453 OString aAliasName( aNameBuf.makeStringAndClear() );
10454 rEle.m_aAlias = aAliasName;
10455 m_aRoleMap[ aAliasName ] = getStructureTag( eType );
10458 if (g_bDebugDisableCompression)
10460 OStringBuffer aLine( "beginStructureElement " );
10461 aLine.append( m_nCurrentStructElement );
10462 aLine.append( ": " );
10463 aLine.append( getStructureTag( eType ) );
10464 if( !rEle.m_aAlias.isEmpty() )
10466 aLine.append( " aliased as \"" );
10467 aLine.append( rEle.m_aAlias );
10468 aLine.append( '\"' );
10470 emitComment( aLine.getStr() );
10473 // check whether to emit structure henceforth
10474 m_bEmitStructure = checkEmitStructure();
10476 if( m_bEmitStructure ) // don't create nonexistent objects
10478 rEle.m_nObject = createObject();
10479 // update parent's kids list
10480 m_aStructure[ rEle.m_nParentElement ].m_aKids.emplace_back(rEle.m_nObject);
10482 return nNewId;
10485 void PDFWriterImpl::endStructureElement()
10487 if( m_nCurrentPage < 0 )
10488 return;
10490 if( ! m_aContext.Tagged )
10491 return;
10493 if( m_nCurrentStructElement == 0 )
10495 // hit the struct tree root, that means there is an endStructureElement
10496 // without corresponding beginStructureElement
10497 return;
10500 // end the marked content sequence
10501 endStructureElementMCSeq();
10503 OStringBuffer aLine;
10504 if (g_bDebugDisableCompression)
10506 aLine.append( "endStructureElement " );
10507 aLine.append( m_nCurrentStructElement );
10508 aLine.append( ": " );
10509 aLine.append( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) );
10510 if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
10512 aLine.append( " aliased as \"" );
10513 aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
10514 aLine.append( '\"' );
10518 // "end" the structure element, the parent becomes current element
10519 m_nCurrentStructElement = m_aStructure[ m_nCurrentStructElement ].m_nParentElement;
10521 // check whether to emit structure henceforth
10522 m_bEmitStructure = checkEmitStructure();
10524 if (g_bDebugDisableCompression && m_bEmitStructure)
10526 emitComment( aLine.getStr() );
10531 * This function adds an internal structure list container to overcome the 8191 elements array limitation
10532 * in kids element emission.
10533 * Recursive function
10536 void PDFWriterImpl::addInternalStructureContainer( PDFStructureElement& rEle )
10538 if( rEle.m_eType == PDFWriter::NonStructElement &&
10539 rEle.m_nOwnElement != rEle.m_nParentElement )
10540 return;
10542 for (auto const& child : rEle.m_aChildren)
10544 if( child > 0 && child < sal_Int32(m_aStructure.size()) )
10546 PDFStructureElement& rChild = m_aStructure[ child ];
10547 if( rChild.m_eType != PDFWriter::NonStructElement )
10549 //triggered when a child of the rEle element is found
10550 if( rChild.m_nParentElement == rEle.m_nOwnElement )
10551 addInternalStructureContainer( rChild );//examine the child
10552 else
10554 OSL_FAIL( "PDFWriterImpl::addInternalStructureContainer: invalid child structure element" );
10555 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure element with id " << child );
10559 else
10561 OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
10562 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::addInternalStructureContainer: invalid child structure id " << child );
10566 if( rEle.m_nOwnElement != rEle.m_nParentElement )
10568 if( !rEle.m_aKids.empty() )
10570 if( rEle.m_aKids.size() > ncMaxPDFArraySize ) {
10571 //then we need to add the containers for the kids elements
10572 // a list to be used for the new kid element
10573 std::list< PDFStructureElementKid > aNewKids;
10574 std::list< sal_Int32 > aNewChildren;
10576 // add Div in RoleMap, in case no one else did (TODO: is it needed? Is it dangerous?)
10577 OString aAliasName( "Div" );
10578 m_aRoleMap[ aAliasName ] = getStructureTag( PDFWriter::Division );
10580 while( rEle.m_aKids.size() > ncMaxPDFArraySize )
10582 sal_Int32 nCurrentStructElement = rEle.m_nOwnElement;
10583 sal_Int32 nNewId = sal_Int32(m_aStructure.size());
10584 m_aStructure.emplace_back( );
10585 PDFStructureElement& rEleNew = m_aStructure.back();
10586 rEleNew.m_aAlias = aAliasName;
10587 rEleNew.m_eType = PDFWriter::Division; // a new Div type container
10588 rEleNew.m_nOwnElement = nNewId;
10589 rEleNew.m_nParentElement = nCurrentStructElement;
10590 //inherit the same page as the first child to be reparented
10591 rEleNew.m_nFirstPageObject = m_aStructure[ rEle.m_aChildren.front() ].m_nFirstPageObject;
10592 rEleNew.m_nObject = createObject();//assign a PDF object number
10593 //add the object to the kid list of the parent
10594 aNewKids.emplace_back( rEleNew.m_nObject );
10595 aNewChildren.push_back( nNewId );
10597 std::list< sal_Int32 >::iterator aChildEndIt( rEle.m_aChildren.begin() );
10598 std::list< PDFStructureElementKid >::iterator aKidEndIt( rEle.m_aKids.begin() );
10599 advance( aChildEndIt, ncMaxPDFArraySize );
10600 advance( aKidEndIt, ncMaxPDFArraySize );
10602 rEleNew.m_aKids.splice( rEleNew.m_aKids.begin(),
10603 rEle.m_aKids,
10604 rEle.m_aKids.begin(),
10605 aKidEndIt );
10606 rEleNew.m_aChildren.splice( rEleNew.m_aChildren.begin(),
10607 rEle.m_aChildren,
10608 rEle.m_aChildren.begin(),
10609 aChildEndIt );
10610 // set the kid's new parent
10611 for (auto const& child : rEleNew.m_aChildren)
10613 m_aStructure[ child ].m_nParentElement = nNewId;
10616 //finally add the new kids resulting from the container added
10617 rEle.m_aKids.insert( rEle.m_aKids.begin(), aNewKids.begin(), aNewKids.end() );
10618 rEle.m_aChildren.insert( rEle.m_aChildren.begin(), aNewChildren.begin(), aNewChildren.end() );
10624 bool PDFWriterImpl::setCurrentStructureElement( sal_Int32 nEle )
10626 bool bSuccess = false;
10628 if( m_aContext.Tagged && nEle >= 0 && nEle < sal_Int32(m_aStructure.size()) )
10630 // end eventual previous marked content sequence
10631 endStructureElementMCSeq();
10633 m_nCurrentStructElement = nEle;
10634 m_bEmitStructure = checkEmitStructure();
10635 if (g_bDebugDisableCompression)
10637 OStringBuffer aLine( "setCurrentStructureElement " );
10638 aLine.append( m_nCurrentStructElement );
10639 aLine.append( ": " );
10640 aLine.append( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) );
10641 if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() )
10643 aLine.append( " aliased as \"" );
10644 aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias );
10645 aLine.append( '\"' );
10647 if( ! m_bEmitStructure )
10648 aLine.append( " (inside NonStruct)" );
10649 emitComment( aLine.getStr() );
10651 bSuccess = true;
10654 return bSuccess;
10657 bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr, enum PDFWriter::StructAttributeValue eVal )
10659 if( !m_aContext.Tagged )
10660 return false;
10662 bool bInsert = false;
10663 if( m_nCurrentStructElement > 0 && m_bEmitStructure )
10665 PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
10666 switch( eAttr )
10668 case PDFWriter::Placement:
10669 if( eVal == PDFWriter::Block ||
10670 eVal == PDFWriter::Inline ||
10671 eVal == PDFWriter::Before ||
10672 eVal == PDFWriter::Start ||
10673 eVal == PDFWriter::End )
10674 bInsert = true;
10675 break;
10676 case PDFWriter::WritingMode:
10677 if( eVal == PDFWriter::LrTb ||
10678 eVal == PDFWriter::RlTb ||
10679 eVal == PDFWriter::TbRl )
10681 bInsert = true;
10683 break;
10684 case PDFWriter::TextAlign:
10685 if( eVal == PDFWriter::Start ||
10686 eVal == PDFWriter::Center ||
10687 eVal == PDFWriter::End ||
10688 eVal == PDFWriter::Justify )
10690 if( eType == PDFWriter::Paragraph ||
10691 eType == PDFWriter::Heading ||
10692 eType == PDFWriter::H1 ||
10693 eType == PDFWriter::H2 ||
10694 eType == PDFWriter::H3 ||
10695 eType == PDFWriter::H4 ||
10696 eType == PDFWriter::H5 ||
10697 eType == PDFWriter::H6 ||
10698 eType == PDFWriter::List ||
10699 eType == PDFWriter::ListItem ||
10700 eType == PDFWriter::LILabel ||
10701 eType == PDFWriter::LIBody ||
10702 eType == PDFWriter::Table ||
10703 eType == PDFWriter::TableRow ||
10704 eType == PDFWriter::TableHeader ||
10705 eType == PDFWriter::TableData )
10707 bInsert = true;
10710 break;
10711 case PDFWriter::Width:
10712 case PDFWriter::Height:
10713 if( eVal == PDFWriter::Auto )
10715 if( eType == PDFWriter::Figure ||
10716 eType == PDFWriter::Formula ||
10717 eType == PDFWriter::Form ||
10718 eType == PDFWriter::Table ||
10719 eType == PDFWriter::TableHeader ||
10720 eType == PDFWriter::TableData )
10722 bInsert = true;
10725 break;
10726 case PDFWriter::BlockAlign:
10727 if( eVal == PDFWriter::Before ||
10728 eVal == PDFWriter::Middle ||
10729 eVal == PDFWriter::After ||
10730 eVal == PDFWriter::Justify )
10732 if( eType == PDFWriter::TableHeader ||
10733 eType == PDFWriter::TableData )
10735 bInsert = true;
10738 break;
10739 case PDFWriter::InlineAlign:
10740 if( eVal == PDFWriter::Start ||
10741 eVal == PDFWriter::Center ||
10742 eVal == PDFWriter::End )
10744 if( eType == PDFWriter::TableHeader ||
10745 eType == PDFWriter::TableData )
10747 bInsert = true;
10750 break;
10751 case PDFWriter::LineHeight:
10752 if( eVal == PDFWriter::Normal ||
10753 eVal == PDFWriter::Auto )
10755 // only for ILSE and BLSE
10756 if( eType == PDFWriter::Paragraph ||
10757 eType == PDFWriter::Heading ||
10758 eType == PDFWriter::H1 ||
10759 eType == PDFWriter::H2 ||
10760 eType == PDFWriter::H3 ||
10761 eType == PDFWriter::H4 ||
10762 eType == PDFWriter::H5 ||
10763 eType == PDFWriter::H6 ||
10764 eType == PDFWriter::List ||
10765 eType == PDFWriter::ListItem ||
10766 eType == PDFWriter::LILabel ||
10767 eType == PDFWriter::LIBody ||
10768 eType == PDFWriter::Table ||
10769 eType == PDFWriter::TableRow ||
10770 eType == PDFWriter::TableHeader ||
10771 eType == PDFWriter::TableData ||
10772 eType == PDFWriter::Span ||
10773 eType == PDFWriter::Quote ||
10774 eType == PDFWriter::Note ||
10775 eType == PDFWriter::Reference ||
10776 eType == PDFWriter::BibEntry ||
10777 eType == PDFWriter::Code ||
10778 eType == PDFWriter::Link )
10780 bInsert = true;
10783 break;
10784 case PDFWriter::TextDecorationType:
10785 if( eVal == PDFWriter::NONE ||
10786 eVal == PDFWriter::Underline ||
10787 eVal == PDFWriter::Overline ||
10788 eVal == PDFWriter::LineThrough )
10790 // only for ILSE and BLSE
10791 if( eType == PDFWriter::Paragraph ||
10792 eType == PDFWriter::Heading ||
10793 eType == PDFWriter::H1 ||
10794 eType == PDFWriter::H2 ||
10795 eType == PDFWriter::H3 ||
10796 eType == PDFWriter::H4 ||
10797 eType == PDFWriter::H5 ||
10798 eType == PDFWriter::H6 ||
10799 eType == PDFWriter::List ||
10800 eType == PDFWriter::ListItem ||
10801 eType == PDFWriter::LILabel ||
10802 eType == PDFWriter::LIBody ||
10803 eType == PDFWriter::Table ||
10804 eType == PDFWriter::TableRow ||
10805 eType == PDFWriter::TableHeader ||
10806 eType == PDFWriter::TableData ||
10807 eType == PDFWriter::Span ||
10808 eType == PDFWriter::Quote ||
10809 eType == PDFWriter::Note ||
10810 eType == PDFWriter::Reference ||
10811 eType == PDFWriter::BibEntry ||
10812 eType == PDFWriter::Code ||
10813 eType == PDFWriter::Link )
10815 bInsert = true;
10818 break;
10819 case PDFWriter::ListNumbering:
10820 if( eVal == PDFWriter::NONE ||
10821 eVal == PDFWriter::Disc ||
10822 eVal == PDFWriter::Circle ||
10823 eVal == PDFWriter::Square ||
10824 eVal == PDFWriter::Decimal ||
10825 eVal == PDFWriter::UpperRoman ||
10826 eVal == PDFWriter::LowerRoman ||
10827 eVal == PDFWriter::UpperAlpha ||
10828 eVal == PDFWriter::LowerAlpha )
10830 if( eType == PDFWriter::List )
10831 bInsert = true;
10833 break;
10834 default: break;
10838 if( bInsert )
10839 m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( eVal );
10840 else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
10841 SAL_INFO("vcl.pdfwriter",
10842 "rejecting setStructureAttribute( " << getAttributeTag( eAttr )
10843 << ", " << getAttributeValueTag( eVal )
10844 << " ) on " << getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType )
10845 << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias
10846 << ") element");
10848 return bInsert;
10851 bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttribute eAttr, sal_Int32 nValue )
10853 if( ! m_aContext.Tagged )
10854 return false;
10856 bool bInsert = false;
10857 if( m_nCurrentStructElement > 0 && m_bEmitStructure )
10859 if( eAttr == PDFWriter::Language )
10861 m_aStructure[ m_nCurrentStructElement ].m_aLocale = LanguageTag( LanguageType(nValue) ).getLocale();
10862 return true;
10865 PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
10866 switch( eAttr )
10868 case PDFWriter::SpaceBefore:
10869 case PDFWriter::SpaceAfter:
10870 case PDFWriter::StartIndent:
10871 case PDFWriter::EndIndent:
10872 // just for BLSE
10873 if( eType == PDFWriter::Paragraph ||
10874 eType == PDFWriter::Heading ||
10875 eType == PDFWriter::H1 ||
10876 eType == PDFWriter::H2 ||
10877 eType == PDFWriter::H3 ||
10878 eType == PDFWriter::H4 ||
10879 eType == PDFWriter::H5 ||
10880 eType == PDFWriter::H6 ||
10881 eType == PDFWriter::List ||
10882 eType == PDFWriter::ListItem ||
10883 eType == PDFWriter::LILabel ||
10884 eType == PDFWriter::LIBody ||
10885 eType == PDFWriter::Table ||
10886 eType == PDFWriter::TableRow ||
10887 eType == PDFWriter::TableHeader ||
10888 eType == PDFWriter::TableData )
10890 bInsert = true;
10892 break;
10893 case PDFWriter::TextIndent:
10894 // paragraph like BLSE and additional elements
10895 if( eType == PDFWriter::Paragraph ||
10896 eType == PDFWriter::Heading ||
10897 eType == PDFWriter::H1 ||
10898 eType == PDFWriter::H2 ||
10899 eType == PDFWriter::H3 ||
10900 eType == PDFWriter::H4 ||
10901 eType == PDFWriter::H5 ||
10902 eType == PDFWriter::H6 ||
10903 eType == PDFWriter::LILabel ||
10904 eType == PDFWriter::LIBody ||
10905 eType == PDFWriter::TableHeader ||
10906 eType == PDFWriter::TableData )
10908 bInsert = true;
10910 break;
10911 case PDFWriter::Width:
10912 case PDFWriter::Height:
10913 if( eType == PDFWriter::Figure ||
10914 eType == PDFWriter::Formula ||
10915 eType == PDFWriter::Form ||
10916 eType == PDFWriter::Table ||
10917 eType == PDFWriter::TableHeader ||
10918 eType == PDFWriter::TableData )
10920 bInsert = true;
10922 break;
10923 case PDFWriter::LineHeight:
10924 case PDFWriter::BaselineShift:
10925 // only for ILSE and BLSE
10926 if( eType == PDFWriter::Paragraph ||
10927 eType == PDFWriter::Heading ||
10928 eType == PDFWriter::H1 ||
10929 eType == PDFWriter::H2 ||
10930 eType == PDFWriter::H3 ||
10931 eType == PDFWriter::H4 ||
10932 eType == PDFWriter::H5 ||
10933 eType == PDFWriter::H6 ||
10934 eType == PDFWriter::List ||
10935 eType == PDFWriter::ListItem ||
10936 eType == PDFWriter::LILabel ||
10937 eType == PDFWriter::LIBody ||
10938 eType == PDFWriter::Table ||
10939 eType == PDFWriter::TableRow ||
10940 eType == PDFWriter::TableHeader ||
10941 eType == PDFWriter::TableData ||
10942 eType == PDFWriter::Span ||
10943 eType == PDFWriter::Quote ||
10944 eType == PDFWriter::Note ||
10945 eType == PDFWriter::Reference ||
10946 eType == PDFWriter::BibEntry ||
10947 eType == PDFWriter::Code ||
10948 eType == PDFWriter::Link )
10950 bInsert = true;
10952 break;
10953 case PDFWriter::RowSpan:
10954 case PDFWriter::ColSpan:
10955 // only for table cells
10956 if( eType == PDFWriter::TableHeader ||
10957 eType == PDFWriter::TableData )
10959 bInsert = true;
10961 break;
10962 case PDFWriter::LinkAnnotation:
10963 if( eType == PDFWriter::Link )
10964 bInsert = true;
10965 break;
10966 default: break;
10970 if( bInsert )
10971 m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( nValue );
10972 else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
10973 SAL_INFO("vcl.pdfwriter",
10974 "rejecting setStructureAttributeNumerical( " << getAttributeTag( eAttr )
10975 << ", " << static_cast<int>(nValue)
10976 << " ) on " << getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType )
10977 << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias
10978 << ") element");
10980 return bInsert;
10983 void PDFWriterImpl::setStructureBoundingBox( const tools::Rectangle& rRect )
10985 sal_Int32 nPageNr = m_nCurrentPage;
10986 if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) || !m_aContext.Tagged )
10987 return;
10989 if( m_nCurrentStructElement > 0 && m_bEmitStructure )
10991 PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
10992 if( eType == PDFWriter::Figure ||
10993 eType == PDFWriter::Formula ||
10994 eType == PDFWriter::Form ||
10995 eType == PDFWriter::Table )
10997 m_aStructure[ m_nCurrentStructElement ].m_aBBox = rRect;
10998 // convert to default user space now, since the mapmode may change
10999 m_aPages[nPageNr].convertRect( m_aStructure[ m_nCurrentStructElement ].m_aBBox );
11004 void PDFWriterImpl::setActualText( const OUString& rText )
11006 if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
11008 m_aStructure[ m_nCurrentStructElement ].m_aActualText = rText;
11012 void PDFWriterImpl::setAlternateText( const OUString& rText )
11014 if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
11016 m_aStructure[ m_nCurrentStructElement ].m_aAltText = rText;
11020 void PDFWriterImpl::setPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr )
11022 if( nPageNr < 0 )
11023 nPageNr = m_nCurrentPage;
11025 if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
11026 return;
11028 m_aPages[ nPageNr ].m_eTransition = eType;
11029 m_aPages[ nPageNr ].m_nTransTime = nMilliSec;
11032 void PDFWriterImpl::ensureUniqueRadioOnValues()
11034 // loop over radio groups
11035 for (auto const& group : m_aRadioGroupWidgets)
11037 PDFWidget& rGroupWidget = m_aWidgets[ group.second ];
11038 // check whether all kids have a unique OnValue
11039 std::unordered_map< OUString, sal_Int32 > aOnValues;
11040 bool bIsUnique = true;
11041 for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11043 const OUString& rVal = m_aWidgets[nKidIndex].m_aOnValue;
11044 SAL_INFO("vcl.pdfwriter", "OnValue: " << rVal);
11045 if( aOnValues.find( rVal ) == aOnValues.end() )
11047 aOnValues[ rVal ] = 1;
11049 else
11051 bIsUnique = false;
11052 break;
11055 if( ! bIsUnique )
11057 SAL_INFO("vcl.pdfwriter", "enforcing unique OnValues" );
11058 // make unique by using ascending OnValues
11059 int nKid = 0;
11060 for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11062 PDFWidget& rKid = m_aWidgets[nKidIndex];
11063 rKid.m_aOnValue = OUString::number( nKid+1 );
11064 if( rKid.m_aValue != "Off" )
11065 rKid.m_aValue = rKid.m_aOnValue;
11066 ++nKid;
11069 // finally move the "Yes" appearance to the OnValue appearance
11070 for (auto const& nKidIndex : rGroupWidget.m_aKidsIndex)
11072 PDFWidget& rKid = m_aWidgets[nKidIndex];
11073 PDFAppearanceMap::iterator app_it = rKid.m_aAppearances.find( "N" );
11074 if( app_it != rKid.m_aAppearances.end() )
11076 PDFAppearanceStreams::iterator stream_it = app_it->second.find( "Yes" );
11077 if( stream_it != app_it->second.end() )
11079 SvMemoryStream* pStream = stream_it->second;
11080 app_it->second.erase( stream_it );
11081 OStringBuffer aBuf( rKid.m_aOnValue.getLength()*2 );
11082 appendName( rKid.m_aOnValue, aBuf );
11083 (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
11085 else
11086 SAL_INFO("vcl.pdfwriter", "error: RadioButton without \"Yes\" stream" );
11088 // update selected radio button
11089 if( rKid.m_aValue != "Off" )
11091 rGroupWidget.m_aValue = rKid.m_aValue;
11097 sal_Int32 PDFWriterImpl::findRadioGroupWidget( const PDFWriter::RadioButtonWidget& rBtn )
11099 sal_Int32 nRadioGroupWidget = -1;
11101 std::map< sal_Int32, sal_Int32 >::const_iterator it = m_aRadioGroupWidgets.find( rBtn.RadioGroup );
11103 if( it == m_aRadioGroupWidgets.end() )
11105 m_aRadioGroupWidgets[ rBtn.RadioGroup ] = nRadioGroupWidget =
11106 sal_Int32(m_aWidgets.size());
11108 // new group, insert the radiobutton
11109 m_aWidgets.emplace_back( );
11110 m_aWidgets.back().m_nObject = createObject();
11111 m_aWidgets.back().m_nPage = m_nCurrentPage;
11112 m_aWidgets.back().m_eType = PDFWriter::RadioButton;
11113 m_aWidgets.back().m_nRadioGroup = rBtn.RadioGroup;
11114 m_aWidgets.back().m_nFlags |= 0x0000C000; // NoToggleToOff and Radio bits
11116 createWidgetFieldName( sal_Int32(m_aWidgets.size()-1), rBtn );
11118 else
11119 nRadioGroupWidget = it->second;
11121 return nRadioGroupWidget;
11124 sal_Int32 PDFWriterImpl::createControl( const PDFWriter::AnyWidget& rControl, sal_Int32 nPageNr )
11126 if( nPageNr < 0 )
11127 nPageNr = m_nCurrentPage;
11129 if( nPageNr < 0 || nPageNr >= static_cast<sal_Int32>(m_aPages.size()) )
11130 return -1;
11132 bool sigHidden(true);
11133 sal_Int32 nNewWidget = m_aWidgets.size();
11134 m_aWidgets.emplace_back( );
11136 m_aWidgets.back().m_nObject = createObject();
11137 m_aWidgets.back().m_aRect = rControl.Location;
11138 m_aWidgets.back().m_nPage = nPageNr;
11139 m_aWidgets.back().m_eType = rControl.getType();
11141 sal_Int32 nRadioGroupWidget = -1;
11142 // for unknown reasons the radio buttons of a radio group must not have a
11143 // field name, else the buttons are in fact check boxes -
11144 // that is multiple buttons of the radio group can be selected
11145 if( rControl.getType() == PDFWriter::RadioButton )
11146 nRadioGroupWidget = findRadioGroupWidget( static_cast<const PDFWriter::RadioButtonWidget&>(rControl) );
11147 else
11149 createWidgetFieldName( nNewWidget, rControl );
11152 // caution: m_aWidgets must not be changed after here or rNewWidget may be invalid
11153 PDFWidget& rNewWidget = m_aWidgets[nNewWidget];
11154 rNewWidget.m_aDescription = rControl.Description;
11155 rNewWidget.m_aText = rControl.Text;
11156 rNewWidget.m_nTextStyle = rControl.TextStyle &
11157 ( DrawTextFlags::Left | DrawTextFlags::Center | DrawTextFlags::Right | DrawTextFlags::Top |
11158 DrawTextFlags::VCenter | DrawTextFlags::Bottom |
11159 DrawTextFlags::MultiLine | DrawTextFlags::WordBreak );
11160 rNewWidget.m_nTabOrder = rControl.TabOrder;
11162 // various properties are set via the flags (/Ff) property of the field dict
11163 if( rControl.ReadOnly )
11164 rNewWidget.m_nFlags |= 1;
11165 if( rControl.getType() == PDFWriter::PushButton )
11167 const PDFWriter::PushButtonWidget& rBtn = static_cast<const PDFWriter::PushButtonWidget&>(rControl);
11168 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11169 rNewWidget.m_nTextStyle =
11170 DrawTextFlags::Center | DrawTextFlags::VCenter |
11171 DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11173 rNewWidget.m_nFlags |= 0x00010000;
11174 if( !rBtn.URL.isEmpty() )
11175 rNewWidget.m_aListEntries.push_back( rBtn.URL );
11176 rNewWidget.m_bSubmit = rBtn.Submit;
11177 rNewWidget.m_bSubmitGet = rBtn.SubmitGet;
11178 rNewWidget.m_nDest = rBtn.Dest;
11179 createDefaultPushButtonAppearance( rNewWidget, rBtn );
11181 else if( rControl.getType() == PDFWriter::RadioButton )
11183 const PDFWriter::RadioButtonWidget& rBtn = static_cast<const PDFWriter::RadioButtonWidget&>(rControl);
11184 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11185 rNewWidget.m_nTextStyle =
11186 DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11187 /* PDF sees a RadioButton group as one radio button with
11188 * children which are in turn check boxes
11190 * so we need to create a radio button on demand for a new group
11191 * and insert a checkbox for each RadioButtonWidget as its child
11193 rNewWidget.m_eType = PDFWriter::CheckBox;
11194 rNewWidget.m_nRadioGroup = rBtn.RadioGroup;
11196 SAL_WARN_IF( nRadioGroupWidget < 0 || nRadioGroupWidget >= static_cast<sal_Int32>(m_aWidgets.size()), "vcl.pdfwriter", "no radio group parent" );
11198 PDFWidget& rRadioButton = m_aWidgets[nRadioGroupWidget];
11199 rRadioButton.m_aKids.push_back( rNewWidget.m_nObject );
11200 rRadioButton.m_aKidsIndex.push_back( nNewWidget );
11201 rNewWidget.m_nParent = rRadioButton.m_nObject;
11203 rNewWidget.m_aValue = "Off";
11204 rNewWidget.m_aOnValue = rBtn.OnValue;
11205 if( rRadioButton.m_aValue.isEmpty() && rBtn.Selected )
11207 rNewWidget.m_aValue = rNewWidget.m_aOnValue;
11208 rRadioButton.m_aValue = rNewWidget.m_aOnValue;
11210 createDefaultRadioButtonAppearance( rNewWidget, rBtn );
11212 // union rect of radio group
11213 tools::Rectangle aRect = rNewWidget.m_aRect;
11214 m_aPages[ nPageNr ].convertRect( aRect );
11215 rRadioButton.m_aRect.Union( aRect );
11217 else if( rControl.getType() == PDFWriter::CheckBox )
11219 const PDFWriter::CheckBoxWidget& rBox = static_cast<const PDFWriter::CheckBoxWidget&>(rControl);
11220 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11221 rNewWidget.m_nTextStyle =
11222 DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11224 rNewWidget.m_aValue = rBox.Checked ? OUStringLiteral("Yes") : OUStringLiteral("Off" );
11225 // create default appearance before m_aRect gets transformed
11226 createDefaultCheckBoxAppearance( rNewWidget, rBox );
11228 else if( rControl.getType() == PDFWriter::ListBox )
11230 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11231 rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
11233 const PDFWriter::ListBoxWidget& rLstBox = static_cast<const PDFWriter::ListBoxWidget&>(rControl);
11234 rNewWidget.m_aListEntries = rLstBox.Entries;
11235 rNewWidget.m_aSelectedEntries = rLstBox.SelectedEntries;
11236 rNewWidget.m_aValue = rLstBox.Text;
11237 if( rLstBox.DropDown )
11238 rNewWidget.m_nFlags |= 0x00020000;
11239 if( rLstBox.MultiSelect && !rLstBox.DropDown && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
11240 rNewWidget.m_nFlags |= 0x00200000;
11242 createDefaultListBoxAppearance( rNewWidget, rLstBox );
11244 else if( rControl.getType() == PDFWriter::ComboBox )
11246 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11247 rNewWidget.m_nTextStyle = DrawTextFlags::VCenter;
11249 const PDFWriter::ComboBoxWidget& rBox = static_cast<const PDFWriter::ComboBoxWidget&>(rControl);
11250 rNewWidget.m_aValue = rBox.Text;
11251 rNewWidget.m_aListEntries = rBox.Entries;
11252 rNewWidget.m_nFlags |= 0x00060000; // combo and edit flag
11254 PDFWriter::ListBoxWidget aLBox;
11255 aLBox.Name = rBox.Name;
11256 aLBox.Description = rBox.Description;
11257 aLBox.Text = rBox.Text;
11258 aLBox.TextStyle = rBox.TextStyle;
11259 aLBox.ReadOnly = rBox.ReadOnly;
11260 aLBox.Border = rBox.Border;
11261 aLBox.BorderColor = rBox.BorderColor;
11262 aLBox.Background = rBox.Background;
11263 aLBox.BackgroundColor = rBox.BackgroundColor;
11264 aLBox.TextFont = rBox.TextFont;
11265 aLBox.TextColor = rBox.TextColor;
11266 aLBox.DropDown = true;
11267 aLBox.MultiSelect = false;
11268 aLBox.Entries = rBox.Entries;
11270 createDefaultListBoxAppearance( rNewWidget, aLBox );
11272 else if( rControl.getType() == PDFWriter::Edit )
11274 if( rNewWidget.m_nTextStyle == DrawTextFlags::NONE )
11275 rNewWidget.m_nTextStyle = DrawTextFlags::Left | DrawTextFlags::VCenter;
11277 const PDFWriter::EditWidget& rEdit = static_cast<const PDFWriter::EditWidget&>(rControl);
11278 if( rEdit.MultiLine )
11280 rNewWidget.m_nFlags |= 0x00001000;
11281 rNewWidget.m_nTextStyle |= DrawTextFlags::MultiLine | DrawTextFlags::WordBreak;
11283 if( rEdit.Password )
11284 rNewWidget.m_nFlags |= 0x00002000;
11285 if( rEdit.FileSelect && m_aContext.Version > PDFWriter::PDFVersion::PDF_1_3 )
11286 rNewWidget.m_nFlags |= 0x00100000;
11287 rNewWidget.m_nMaxLen = rEdit.MaxLen;
11288 rNewWidget.m_aValue = rEdit.Text;
11290 createDefaultEditAppearance( rNewWidget, rEdit );
11292 #if HAVE_FEATURE_NSS
11293 else if( rControl.getType() == PDFWriter::Signature)
11295 sigHidden = true;
11297 rNewWidget.m_aRect = tools::Rectangle(0, 0, 0, 0);
11299 m_nSignatureObject = createObject();
11300 rNewWidget.m_aValue = OUString::number( m_nSignatureObject );
11301 rNewWidget.m_aValue += " 0 R";
11302 // let's add a fake appearance
11303 rNewWidget.m_aAppearances[ "N" ][ "Standard" ] = new SvMemoryStream();
11305 #endif
11307 // if control is a hidden signature, do not convert coordinates since we
11308 // need /Rect [ 0 0 0 0 ]
11309 if ( ! ( ( rControl.getType() == PDFWriter::Signature ) && sigHidden ) )
11311 // convert to default user space now, since the mapmode may change
11312 // note: create default appearances before m_aRect gets transformed
11313 m_aPages[ nPageNr ].convertRect( rNewWidget.m_aRect );
11316 // insert widget to page's annotation list
11317 m_aPages[ nPageNr ].m_aAnnotations.push_back( rNewWidget.m_nObject );
11319 return nNewWidget;
11322 void PDFWriterImpl::addStream( const OUString& rMimeType, PDFOutputStream* pStream )
11324 if( pStream )
11326 m_aAdditionalStreams.emplace_back( );
11327 PDFAddStream& rStream = m_aAdditionalStreams.back();
11328 rStream.m_aMimeType = !rMimeType.isEmpty()
11329 ? rMimeType
11330 : OUString( "application/octet-stream" );
11331 rStream.m_pStream = pStream;
11332 rStream.m_bCompress = false;
11336 void PDFWriterImpl::MARK( const char* pString )
11338 beginStructureElementMCSeq();
11339 if (g_bDebugDisableCompression)
11340 emitComment( pString );
11343 sal_Int32 PDFWriterImpl::ReferenceXObjectEmit::getObject() const
11345 if (m_nFormObject > 0)
11346 return m_nFormObject;
11347 else
11348 return m_nBitmapObject;
11351 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */