LibreOffice Module vcl (master) 1
pdfwriter_impl.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20#include <sal/config.h>
21#include <config_crypto.h>
22
23#include <sal/types.h>
24
25#include <math.h>
26#include <algorithm>
27#include <string_view>
28
29#include <lcms2.h>
30
37#include <memory>
38#include <com/sun/star/io/XOutputStream.hpp>
39#include <com/sun/star/util/URL.hpp>
40#include <com/sun/star/util/URLTransformer.hpp>
42#include <comphelper/string.hxx>
45#include <o3tl/numeric.hxx>
46#include <o3tl/safeint.hxx>
47#include <o3tl/temporary.hxx>
48#include <officecfg/Office/Common.hxx>
49#include <osl/file.hxx>
50#include <osl/thread.h>
51#include <rtl/digest.h>
52#include <rtl/uri.hxx>
53#include <rtl/ustrbuf.hxx>
54#include <sal/log.hxx>
55#include <svl/urihelper.hxx>
56#include <tools/fract.hxx>
57#include <tools/stream.hxx>
58#include <tools/helpers.hxx>
59#include <tools/urlobj.hxx>
60#include <tools/zcodec.hxx>
61#include <svl/cryptosign.hxx>
62#include <vcl/bitmapex.hxx>
63#include <vcl/canvastools.hxx>
64#include <vcl/cvtgrf.hxx>
65#include <vcl/fontcharmap.hxx>
67#include <vcl/lineinfo.hxx>
68#include <vcl/metric.hxx>
69#include <vcl/mnemonic.hxx>
70#include <vcl/settings.hxx>
71#include <strhelper.hxx>
72#include <vcl/svapp.hxx>
73#include <vcl/virdev.hxx>
76#include <comphelper/hash.hxx>
77
78#include <svdata.hxx>
80#include <fontsubset.hxx>
81#include <font/EmphasisMark.hxx>
83#include <salgdi.hxx>
84#include <textlayout.hxx>
85#include <textlineinfo.hxx>
86#include <impglyphitem.hxx>
87#include <pdf/XmpMetadata.hxx>
88#include <pdf/objectcopier.hxx>
90#include <pdf/PdfConfig.hxx>
92
93#include <config_eot.h>
94
95#if ENABLE_LIBEOT
96#include <libeot/libeot.h>
97#endif
98
99using namespace::com::sun::star;
100
101static bool g_bDebugDisableCompression = getenv("VCL_DEBUG_DISABLE_PDFCOMPRESSION");
102
103namespace
104{
105
106constexpr sal_Int32 nLog10Divisor = 3;
107constexpr double fDivisor = 1000.0;
108
109constexpr double pixelToPoint(double px)
110{
111 return px / fDivisor;
112}
113
114constexpr sal_Int32 pointToPixel(double pt)
115{
116 return sal_Int32(pt * fDivisor);
117}
118
119void appendHex(sal_Int8 nInt, OStringBuffer& rBuffer)
120{
121 static const char pHexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7',
122 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
123 rBuffer.append( pHexDigits[ (nInt >> 4) & 15 ] );
124 rBuffer.append( pHexDigits[ nInt & 15 ] );
125}
126
127void appendName( std::u16string_view rStr, OStringBuffer& rBuffer )
128{
129// FIXME i59651 add a check for max length of 127 chars? Per PDF spec 1.4, appendix C.1
130// I guess than when reading the #xx sequence it will count for a single character.
131 OString aStr( OUStringToOString( rStr, RTL_TEXTENCODING_UTF8 ) );
132 int nLen = aStr.getLength();
133 for( int i = 0; i < nLen; i++ )
134 {
135 /* #i16920# PDF recommendation: output UTF8, any byte
136 * outside the interval [33(=ASCII'!');126(=ASCII'~')]
137 * should be escaped hexadecimal
138 * for the sake of ghostscript which also reads PDF
139 * but has a narrower acceptance rate we only pass
140 * alphanumerics and '-' literally.
141 */
142 if( (aStr[i] >= 'A' && aStr[i] <= 'Z' ) ||
143 (aStr[i] >= 'a' && aStr[i] <= 'z' ) ||
144 (aStr[i] >= '0' && aStr[i] <= '9' ) ||
145 aStr[i] == '-' )
146 {
147 rBuffer.append( aStr[i] );
148 }
149 else
150 {
151 rBuffer.append( '#' );
152 appendHex( static_cast<sal_Int8>(aStr[i]), rBuffer );
153 }
154 }
155}
156
157void appendName( const char* pStr, OStringBuffer& rBuffer )
158{
159 // FIXME i59651 see above
160 while( pStr && *pStr )
161 {
162 if( (*pStr >= 'A' && *pStr <= 'Z' ) ||
163 (*pStr >= 'a' && *pStr <= 'z' ) ||
164 (*pStr >= '0' && *pStr <= '9' ) ||
165 *pStr == '-' )
166 {
167 rBuffer.append( *pStr );
168 }
169 else
170 {
171 rBuffer.append( '#' );
172 appendHex( static_cast<sal_Int8>(*pStr), rBuffer );
173 }
174 pStr++;
175 }
176}
177
178//used only to emit encoded passwords
179void appendLiteralString( const char* pStr, sal_Int32 nLength, OStringBuffer& rBuffer )
180{
181 while( nLength )
182 {
183 switch( *pStr )
184 {
185 case '\n' :
186 rBuffer.append( "\\n" );
187 break;
188 case '\r' :
189 rBuffer.append( "\\r" );
190 break;
191 case '\t' :
192 rBuffer.append( "\\t" );
193 break;
194 case '\b' :
195 rBuffer.append( "\\b" );
196 break;
197 case '\f' :
198 rBuffer.append( "\\f" );
199 break;
200 case '(' :
201 case ')' :
202 case '\\' :
203 rBuffer.append( "\\" );
204 rBuffer.append( static_cast<char>(*pStr) );
205 break;
206 default:
207 rBuffer.append( static_cast<char>(*pStr) );
208 break;
209 }
210 pStr++;
211 nLength--;
212 }
213}
214
215/*
216 * Convert a string before using it.
217 *
218 * This string conversion function is needed because the destination name
219 * in a PDF file seen through an Internet browser should be
220 * specially crafted, in order to be used directly by the browser.
221 * In this way the fragment part of a hyperlink to a PDF file (e.g. something
222 * as 'test1/test2/a-file.pdf\#thefragment) will be (hopefully) interpreted by the
223 * PDF reader (currently only Adobe Reader plug-in seems to be working that way) called
224 * from inside the Internet browser as: 'open the file test1/test2/a-file.pdf
225 * and go to named destination thefragment using default zoom'.
226 * The conversion is needed because in case of a fragment in the form: Slide%201
227 * (meaning Slide 1) as it is converted obeying the Inet rules, it will become Slide25201
228 * using this conversion, in both the generated named destinations, fragment and GoToR
229 * destination.
230 *
231 * The names for destinations are name objects and so they don't need to be encrypted
232 * even though they expose the content of PDF file (e.g. guessing the PDF content from the
233 * destination name).
234 *
235 * Further limitation: it is advisable to use standard ASCII characters for
236 * OOo bookmarks.
237*/
238void appendDestinationName( const OUString& rString, OStringBuffer& rBuffer )
239{
240 const sal_Unicode* pStr = rString.getStr();
241 sal_Int32 nLen = rString.getLength();
242 for( int i = 0; i < nLen; i++ )
243 {
244 sal_Unicode aChar = pStr[i];
245 if( (aChar >= '0' && aChar <= '9' ) ||
246 (aChar >= 'a' && aChar <= 'z' ) ||
247 (aChar >= 'A' && aChar <= 'Z' ) ||
248 aChar == '-' )
249 {
250 rBuffer.append(static_cast<char>(aChar));
251 }
252 else
253 {
254 sal_Int8 aValueHigh = sal_Int8(aChar >> 8);
255 if(aValueHigh > 0)
256 appendHex( aValueHigh, rBuffer );
257 appendHex( static_cast<sal_Int8>(aChar & 255 ), rBuffer );
258 }
259 }
260}
261
262} // end anonymous namespace
263
264namespace vcl
265{
267{
268 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
269 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A
270};
271
272namespace
273{
274
275template < class GEOMETRY >
276GEOMETRY lcl_convert( const MapMode& _rSource, const MapMode& _rDest, OutputDevice* _pPixelConversion, const GEOMETRY& _rObject )
277{
278 GEOMETRY aPoint;
279 if ( MapUnit::MapPixel == _rSource.GetMapUnit() )
280 {
281 aPoint = _pPixelConversion->PixelToLogic( _rObject, _rDest );
282 }
283 else
284 {
285 aPoint = OutputDevice::LogicToLogic( _rObject, _rSource, _rDest );
286 }
287 return aPoint;
288}
289
290} // end anonymous namespace
291
292void PDFWriter::AppendUnicodeTextString(const OUString& rString, OStringBuffer& rBuffer)
293{
294 rBuffer.append( "FEFF" );
295 const sal_Unicode* pStr = rString.getStr();
296 sal_Int32 nLen = rString.getLength();
297 for( int i = 0; i < nLen; i++ )
298 {
299 sal_Unicode aChar = pStr[i];
300 appendHex( static_cast<sal_Int8>(aChar >> 8), rBuffer );
301 appendHex( static_cast<sal_Int8>(aChar & 255 ), rBuffer );
302 }
303}
304
305void PDFWriterImpl::createWidgetFieldName( sal_Int32 i_nWidgetIndex, const PDFWriter::AnyWidget& i_rControl )
306{
307 /* #i80258# previously we use appendName here
308 however we need a slightly different coding scheme than the normal
309 name encoding for field names
310 */
311 const OUString& rName = (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2) ? i_rControl.Name : i_rControl.Text;
312 OString aStr( OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ) );
313 int nLen = aStr.getLength();
314
315 OStringBuffer aBuffer( rName.getLength()+64 );
316 for( int i = 0; i < nLen; i++ )
317 {
318 /* #i16920# PDF recommendation: output UTF8, any byte
319 * outside the interval [32(=ASCII' ');126(=ASCII'~')]
320 * should be escaped hexadecimal
321 */
322 if( aStr[i] >= 32 && aStr[i] <= 126 )
323 aBuffer.append( aStr[i] );
324 else
325 {
326 aBuffer.append( '#' );
327 appendHex( static_cast<sal_Int8>(aStr[i]), aBuffer );
328 }
329 }
330
331 OString aFullName( aBuffer.makeStringAndClear() );
332
333 /* #i82785# create hierarchical fields down to the for each dot in i_rName */
334 sal_Int32 nTokenIndex = 0, nLastTokenIndex = 0;
335 OString aPartialName;
336 OString aDomain;
337 do
338 {
339 nLastTokenIndex = nTokenIndex;
340 aPartialName = aFullName.getToken( 0, '.', nTokenIndex );
341 if( nTokenIndex != -1 )
342 {
343 // find or create a hierarchical field
344 // first find the fully qualified name up to this field
345 aDomain = aFullName.copy( 0, nTokenIndex-1 );
346 std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
347 if( it == m_aFieldNameMap.end() )
348 {
349 // create new hierarchy field
350 sal_Int32 nNewWidget = m_aWidgets.size();
351 m_aWidgets.emplace_back( );
352 m_aWidgets[nNewWidget].m_nObject = createObject();
353 m_aWidgets[nNewWidget].m_eType = PDFWriter::Hierarchy;
354 m_aWidgets[nNewWidget].m_aName = aPartialName;
355 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
356 m_aFieldNameMap[aDomain] = nNewWidget;
357 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
358 if( nLastTokenIndex > 0 )
359 {
360 // this field is not a root field and
361 // needs to be inserted to its parent
362 OString aParentDomain( aDomain.copy( 0, nLastTokenIndex-1 ) );
363 it = m_aFieldNameMap.find( aParentDomain );
364 OSL_ENSURE( it != m_aFieldNameMap.end(), "field name not found" );
365 if( it != m_aFieldNameMap.end() )
366 {
367 OSL_ENSURE( it->second < sal_Int32(m_aWidgets.size()), "invalid field number entry" );
368 if( it->second < sal_Int32(m_aWidgets.size()) )
369 {
370 PDFWidget& rParentField( m_aWidgets[it->second] );
371 rParentField.m_aKids.push_back( m_aWidgets[nNewWidget].m_nObject );
372 rParentField.m_aKidsIndex.push_back( nNewWidget );
373 m_aWidgets[nNewWidget].m_nParent = rParentField.m_nObject;
374 }
375 }
376 }
377 }
378 else if( m_aWidgets[it->second].m_eType != PDFWriter::Hierarchy )
379 {
380 // this is invalid, someone tries to have a terminal field as parent
381 // example: a button with the name foo.bar exists and
382 // another button is named foo.bar.no
383 // workaround: put the second terminal field as much up in the hierarchy as
384 // necessary to have a non-terminal field as parent (or none at all)
385 // since it->second already is terminal, we just need to use its parent
386 aDomain.clear();
387 aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
388 if( nLastTokenIndex > 0 )
389 {
390 aDomain = aFullName.copy( 0, nLastTokenIndex-1 );
391 aFullName = aDomain + "." + aPartialName;
392 }
393 else
394 aFullName = aPartialName;
395 break;
396 }
397 }
398 } while( nTokenIndex != -1 );
399
400 // insert widget into its hierarchy field
401 if( !aDomain.isEmpty() )
402 {
403 std::unordered_map< OString, sal_Int32 >::const_iterator it = m_aFieldNameMap.find( aDomain );
404 if( it != m_aFieldNameMap.end() )
405 {
406 OSL_ENSURE( it->second >= 0 && o3tl::make_unsigned(it->second) < m_aWidgets.size(), "invalid field index" );
407 if( it->second >= 0 && o3tl::make_unsigned(it->second) < m_aWidgets.size() )
408 {
409 m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[it->second].m_nObject;
410 m_aWidgets[it->second].m_aKids.push_back( m_aWidgets[i_nWidgetIndex].m_nObject);
411 m_aWidgets[it->second].m_aKidsIndex.push_back( i_nWidgetIndex );
412 }
413 }
414 }
415
416 if( aPartialName.isEmpty() )
417 {
418 // how funny, an empty field name
419 if( i_rControl.getType() == PDFWriter::RadioButton )
420 {
421 aPartialName = "RadioGroup" +
422 OString::number( static_cast<const PDFWriter::RadioButtonWidget&>(i_rControl).RadioGroup );
423 }
424 else
425 aPartialName = OString( "Widget" );
426 }
427
429 {
430 std::unordered_map<OString, sal_Int32>::iterator it = m_aFieldNameMap.find( aFullName );
431
432 if( it != m_aFieldNameMap.end() ) // not unique
433 {
434 std::unordered_map< OString, sal_Int32 >::const_iterator check_it;
435 OString aTry;
436 sal_Int32 nTry = 2;
437 do
438 {
439 OStringBuffer aUnique( aFullName.getLength() + 16 );
440 aUnique.append( aFullName );
441 aUnique.append( '_' );
442 aUnique.append( nTry++ );
443 aTry = aUnique.makeStringAndClear();
444 check_it = m_aFieldNameMap.find( aTry );
445 } while( check_it != m_aFieldNameMap.end() );
446 aFullName = aTry;
447 m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
448 aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
449 }
450 else
451 m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
452 }
453
454 // finally
455 m_aWidgets[i_nWidgetIndex].m_aName = aPartialName;
456}
457
458namespace
459{
460
461void appendFixedInt( sal_Int32 nValue, OStringBuffer& rBuffer )
462{
463 if( nValue < 0 )
464 {
465 rBuffer.append( '-' );
466 nValue = -nValue;
467 }
468 sal_Int32 nFactor = 1, nDiv = nLog10Divisor;
469 while( nDiv-- )
470 nFactor *= 10;
471
472 sal_Int32 nInt = nValue / nFactor;
473 rBuffer.append( nInt );
474 if (nFactor > 1 && nValue % nFactor)
475 {
476 rBuffer.append( '.' );
477 do
478 {
479 nFactor /= 10;
480 rBuffer.append((nValue / nFactor) % 10);
481 }
482 while (nFactor > 1 && nValue % nFactor); // omit trailing zeros
483 }
484}
485
486// appends a double. PDF does not accept exponential format, only fixed point
487void appendDouble( double fValue, OStringBuffer& rBuffer, sal_Int32 nPrecision = 10 )
488{
489 bool bNeg = false;
490 if( fValue < 0.0 )
491 {
492 bNeg = true;
493 fValue=-fValue;
494 }
495
496 sal_Int64 nInt = static_cast<sal_Int64>(fValue);
497 fValue -= static_cast<double>(nInt);
498 // optimizing hardware may lead to a value of 1.0 after the subtraction
499 if( rtl::math::approxEqual(fValue, 1.0) || log10( 1.0-fValue ) <= -nPrecision )
500 {
501 nInt++;
502 fValue = 0.0;
503 }
504 sal_Int64 nFrac = 0;
505 if( fValue )
506 {
507 fValue *= pow( 10.0, static_cast<double>(nPrecision) );
508 nFrac = static_cast<sal_Int64>(fValue);
509 }
510 if( bNeg && ( nInt || nFrac ) )
511 rBuffer.append( '-' );
512 rBuffer.append( nInt );
513 if( !nFrac )
514 return;
515
516 int i;
517 rBuffer.append( '.' );
518 sal_Int64 nBound = static_cast<sal_Int64>(pow( 10.0, nPrecision - 1.0 )+0.5);
519 for ( i = 0; ( i < nPrecision ) && nFrac; i++ )
520 {
521 sal_Int64 nNumb = nFrac / nBound;
522 nFrac -= nNumb * nBound;
523 rBuffer.append( nNumb );
524 nBound /= 10;
525 }
526}
527
528void appendColor( const Color& rColor, OStringBuffer& rBuffer, bool bConvertToGrey )
529{
530
531 if( rColor == COL_TRANSPARENT )
532 return;
533
534 if( bConvertToGrey )
535 {
536 sal_uInt8 cByte = rColor.GetLuminance();
537 appendDouble( static_cast<double>(cByte) / 255.0, rBuffer );
538 }
539 else
540 {
541 appendDouble( static_cast<double>(rColor.GetRed()) / 255.0, rBuffer );
542 rBuffer.append( ' ' );
543 appendDouble( static_cast<double>(rColor.GetGreen()) / 255.0, rBuffer );
544 rBuffer.append( ' ' );
545 appendDouble( static_cast<double>(rColor.GetBlue()) / 255.0, rBuffer );
546 }
547}
548
549} // end anonymous namespace
550
551void PDFWriterImpl::appendStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
552{
553 if( rColor != COL_TRANSPARENT )
554 {
556 appendColor( rColor, rBuffer, bGrey );
557 rBuffer.append( bGrey ? " G" : " RG" );
558 }
559}
560
561void PDFWriterImpl::appendNonStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
562{
563 if( rColor != COL_TRANSPARENT )
564 {
566 appendColor( rColor, rBuffer, bGrey );
567 rBuffer.append( bGrey ? " g" : " rg" );
568 }
569}
570
571namespace
572{
573
574void appendPdfTimeDate(OStringBuffer & rBuffer,
575 sal_Int16 year, sal_uInt16 month, sal_uInt16 day, sal_uInt16 hours, sal_uInt16 minutes, sal_uInt16 seconds, sal_Int32 tzDelta)
576{
577 rBuffer.append("D:");
578 rBuffer.append(char('0' + ((year / 1000) % 10)));
579 rBuffer.append(char('0' + ((year / 100) % 10)));
580 rBuffer.append(char('0' + ((year / 10) % 10)));
581 rBuffer.append(char('0' + (year % 10)));
582 rBuffer.append(char('0' + ((month / 10) % 10)));
583 rBuffer.append(char('0' + (month % 10)));
584 rBuffer.append(char('0' + ((day / 10) % 10)));
585 rBuffer.append(char('0' + (day % 10)));
586 rBuffer.append(char('0' + ((hours / 10) % 10)));
587 rBuffer.append(char('0' + (hours % 10)));
588 rBuffer.append(char('0' + ((minutes / 10) % 10)));
589 rBuffer.append(char('0' + (minutes % 10)));
590 rBuffer.append(char('0' + ((seconds / 10) % 10)));
591 rBuffer.append(char('0' + (seconds % 10)));
592
593 if (tzDelta == 0)
594 {
595 rBuffer.append("Z");
596 }
597 else
598 {
599 if (tzDelta > 0 )
600 rBuffer.append("+");
601 else
602 {
603 rBuffer.append("-");
604 tzDelta = -tzDelta;
605 }
606
607 rBuffer.append(char('0' + ((tzDelta / 36000) % 10)));
608 rBuffer.append(char('0' + ((tzDelta / 3600) % 10)));
609 rBuffer.append("'");
610 rBuffer.append(char('0' + ((tzDelta / 600) % 6)));
611 rBuffer.append(char('0' + ((tzDelta / 60) % 10)));
612 }
613}
614
615} // end namespace
616
617PDFPage::PDFPage( PDFWriterImpl* pWriter, double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
618 :
619 m_pWriter( pWriter ),
620 m_nPageWidth( nPageWidth ),
621 m_nPageHeight( nPageHeight ),
622 m_nUserUnit( 1 ),
623 m_eOrientation( eOrientation ),
624 m_nPageObject( 0 ), // invalid object number
625 m_nStreamLengthObject( 0 ),
626 m_nBeginStreamPos( 0 ),
627 m_eTransition( PDFWriter::PageTransition::Regular ),
628 m_nTransTime( 0 )
629{
630 // object ref must be only ever updated in emit()
631 m_nPageObject = m_pWriter->createObject();
632
633 switch (m_pWriter->m_aContext.Version)
634 {
636 m_nUserUnit = std::ceil(std::max(nPageWidth, nPageHeight) / 14400.0);
637 break;
638 default:
639 // 1.2 -> 1.5
640 break;
641 }
642}
643
645{
647 {
648 m_pWriter->emitComment("PDFWriterImpl::PDFPage::beginStream, +");
649 }
650 m_aStreamObjects.push_back(m_pWriter->createObject());
651 if( ! m_pWriter->updateObject( m_aStreamObjects.back() ) )
652 return;
653
654 m_nStreamLengthObject = m_pWriter->createObject();
655 // write content stream header
656 OStringBuffer aLine;
657 aLine.append( m_aStreamObjects.back() );
658 aLine.append( " 0 obj\n<</Length " );
659 aLine.append( m_nStreamLengthObject );
660 aLine.append( " 0 R" );
662 aLine.append( "/Filter/FlateDecode" );
663 aLine.append( ">>\nstream\n" );
664 if( ! m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() ) )
665 return;
666 if (osl::File::E_None != m_pWriter->m_aFile.getPos(m_nBeginStreamPos))
667 {
668 m_pWriter->m_aFile.close();
669 m_pWriter->m_bOpen = false;
670 }
672 m_pWriter->beginCompression();
673 m_pWriter->checkAndEnableStreamEncryption( m_aStreamObjects.back() );
674}
675
677{
679 m_pWriter->endCompression();
680 sal_uInt64 nEndStreamPos;
681 if (osl::File::E_None != m_pWriter->m_aFile.getPos(nEndStreamPos))
682 {
683 m_pWriter->m_aFile.close();
684 m_pWriter->m_bOpen = false;
685 return;
686 }
687 m_pWriter->disableStreamEncryption();
688 if( ! m_pWriter->writeBuffer( "\nendstream\nendobj\n\n", 19 ) )
689 return;
690 // emit stream length object
691 if( ! m_pWriter->updateObject( m_nStreamLengthObject ) )
692 return;
693 OString aLine =
694 OString::number( m_nStreamLengthObject ) +
695 " 0 obj\n" +
696 OString::number( static_cast<sal_Int64>(nEndStreamPos-m_nBeginStreamPos) ) +
697 "\nendobj\n\n";
698 m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() );
699}
700
701bool PDFPage::emit(sal_Int32 nParentObject )
702{
703 m_pWriter->MARK("PDFPage::emit");
704 // emit page object
705 if( ! m_pWriter->updateObject( m_nPageObject ) )
706 return false;
707 OStringBuffer aLine;
708
709 aLine.append( m_nPageObject );
710 aLine.append( " 0 obj\n"
711 "<</Type/Page/Parent " );
712 aLine.append( nParentObject );
713 aLine.append( " 0 R" );
714 aLine.append( "/Resources " );
715 aLine.append( m_pWriter->getResourceDictObj() );
716 aLine.append( " 0 R" );
718 {
719 aLine.append( "/MediaBox[0 0 " );
720 aLine.append(m_nPageWidth / m_nUserUnit);
721 aLine.append( ' ' );
722 aLine.append(m_nPageHeight / m_nUserUnit);
723 aLine.append( "]" );
724 if (m_nUserUnit > 1)
725 {
726 aLine.append("\n/UserUnit ");
727 aLine.append(m_nUserUnit);
728 }
729 }
730 switch( m_eOrientation )
731 {
732 case PDFWriter::Orientation::Portrait: aLine.append( "/Rotate 0\n" );break;
734 }
735 int nAnnots = m_aAnnotations.size();
736 if( nAnnots > 0 )
737 {
738 aLine.append( "/Annots[\n" );
739 for( int i = 0; i < nAnnots; i++ )
740 {
741 aLine.append( m_aAnnotations[i] );
742 aLine.append( " 0 R" );
743 aLine.append( ((i+1)%15) ? " " : "\n" );
744 }
745 aLine.append( "]\n" );
746 }
747 if( !m_aMCIDParents.empty() )
748 {
749 OStringBuffer aStructParents( 1024 );
750 aStructParents.append( "[ " );
751 int nParents = m_aMCIDParents.size();
752 for( int i = 0; i < nParents; i++ )
753 {
754 aStructParents.append( m_aMCIDParents[i] );
755 aStructParents.append( " 0 R" );
756 aStructParents.append( ((i%10) == 9) ? "\n" : " " );
757 }
758 aStructParents.append( "]" );
759 m_pWriter->m_aStructParentTree.push_back( aStructParents.makeStringAndClear() );
760
761 aLine.append( "/StructParents " );
762 aLine.append( sal_Int32(m_pWriter->m_aStructParentTree.size()-1) );
763 aLine.append( "\n" );
764 }
766 {
767 // transition duration
768 aLine.append( "/Trans<</D " );
769 appendDouble( static_cast<double>(m_nTransTime)/1000.0, aLine, 3 );
770 aLine.append( "\n" );
771 const char *pStyle = nullptr, *pDm = nullptr, *pM = nullptr, *pDi = nullptr;
772 switch( m_eTransition )
773 {
775 pStyle = "Split"; pDm = "H"; pM = "I"; break;
777 pStyle = "Split"; pDm = "H"; pM = "O"; break;
779 pStyle = "Split"; pDm = "V"; pM = "I"; break;
781 pStyle = "Split"; pDm = "V"; pM = "O"; break;
783 pStyle = "Blinds"; pDm = "H"; break;
785 pStyle = "Blinds"; pDm = "V"; break;
787 pStyle = "Box"; pM = "I"; break;
789 pStyle = "Box"; pM = "O"; break;
791 pStyle = "Wipe"; pDi = "0"; break;
793 pStyle = "Wipe"; pDi = "90"; break;
795 pStyle = "Wipe"; pDi = "180"; break;
797 pStyle = "Wipe"; pDi = "270"; break;
799 pStyle = "Dissolve"; break;
801 break;
802 }
803 // transition style
804 if( pStyle )
805 {
806 aLine.append( "/S/" );
807 aLine.append( pStyle );
808 aLine.append( "\n" );
809 }
810 if( pDm )
811 {
812 aLine.append( "/Dm/" );
813 aLine.append( pDm );
814 aLine.append( "\n" );
815 }
816 if( pM )
817 {
818 aLine.append( "/M/" );
819 aLine.append( pM );
820 aLine.append( "\n" );
821 }
822 if( pDi )
823 {
824 aLine.append( "/Di " );
825 aLine.append( pDi );
826 aLine.append( "\n" );
827 }
828 aLine.append( ">>\n" );
829 }
830
831 aLine.append( "/Contents" );
832 unsigned int nStreamObjects = m_aStreamObjects.size();
833 if( nStreamObjects > 1 )
834 aLine.append( '[' );
835 for(sal_Int32 i : m_aStreamObjects)
836 {
837 aLine.append( ' ' );
838 aLine.append( i );
839 aLine.append( " 0 R" );
840 }
841 if( nStreamObjects > 1 )
842 aLine.append( ']' );
843 aLine.append( ">>\nendobj\n\n" );
844 return m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() );
845}
846
847void PDFPage::appendPoint( const Point& rPoint, OStringBuffer& rBuffer ) const
848{
849 Point aPoint( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
850 m_pWriter->m_aMapMode,
851 m_pWriter,
852 rPoint ) );
853
854 sal_Int32 nValue = aPoint.X();
855
856 appendFixedInt( nValue, rBuffer );
857
858 rBuffer.append( ' ' );
859
860 nValue = pointToPixel(getHeight()) - aPoint.Y();
861
862 appendFixedInt( nValue, rBuffer );
863}
864
865void PDFPage::appendPixelPoint( const basegfx::B2DPoint& rPoint, OStringBuffer& rBuffer ) const
866{
867 double fValue = pixelToPoint(rPoint.getX());
868
869 appendDouble( fValue, rBuffer, nLog10Divisor );
870 rBuffer.append( ' ' );
871 fValue = getHeight() - pixelToPoint(rPoint.getY());
872 appendDouble( fValue, rBuffer, nLog10Divisor );
873}
874
875void PDFPage::appendRect( const tools::Rectangle& rRect, OStringBuffer& rBuffer ) const
876{
877 appendPoint( rRect.BottomLeft() + Point( 0, 1 ), rBuffer );
878 rBuffer.append( ' ' );
879 appendMappedLength( static_cast<sal_Int32>(rRect.GetWidth()), rBuffer, false );
880 rBuffer.append( ' ' );
881 appendMappedLength( static_cast<sal_Int32>(rRect.GetHeight()), rBuffer );
882 rBuffer.append( " re" );
883}
884
886{
887 Point aLL = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
888 m_pWriter->m_aMapMode,
889 m_pWriter,
890 rRect.BottomLeft() + Point( 0, 1 )
891 );
892 Size aSize = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
893 m_pWriter->m_aMapMode,
894 m_pWriter,
895 rRect.GetSize() );
896 rRect.SetLeft( aLL.X() );
897 rRect.SetRight( aLL.X() + aSize.Width() );
898 rRect.SetTop( pointToPixel(getHeight()) - aLL.Y() );
899 rRect.SetBottom( rRect.Top() + aSize.Height() );
900}
901
902void PDFPage::appendPolygon( const tools::Polygon& rPoly, OStringBuffer& rBuffer, bool bClose ) const
903{
904 sal_uInt16 nPoints = rPoly.GetSize();
905 /*
906 * #108582# applications do weird things
907 */
908 sal_uInt32 nBufLen = rBuffer.getLength();
909 if( nPoints <= 0 )
910 return;
911
912 const PolyFlags* pFlagArray = rPoly.GetConstFlagAry();
913 appendPoint( rPoly[0], rBuffer );
914 rBuffer.append( " m\n" );
915 for( sal_uInt16 i = 1; i < nPoints; i++ )
916 {
917 if( pFlagArray && pFlagArray[i] == PolyFlags::Control && nPoints-i > 2 )
918 {
919 // bezier
920 SAL_WARN_IF( pFlagArray[i+1] != PolyFlags::Control || pFlagArray[i+2] == PolyFlags::Control, "vcl.pdfwriter", "unexpected sequence of control points" );
921 appendPoint( rPoly[i], rBuffer );
922 rBuffer.append( " " );
923 appendPoint( rPoly[i+1], rBuffer );
924 rBuffer.append( " " );
925 appendPoint( rPoly[i+2], rBuffer );
926 rBuffer.append( " c" );
927 i += 2; // add additionally consumed points
928 }
929 else
930 {
931 // line
932 appendPoint( rPoly[i], rBuffer );
933 rBuffer.append( " l" );
934 }
935 if( (rBuffer.getLength() - nBufLen) > 65 )
936 {
937 rBuffer.append( "\n" );
938 nBufLen = rBuffer.getLength();
939 }
940 else
941 rBuffer.append( " " );
942 }
943 if( bClose )
944 rBuffer.append( "h\n" );
945}
946
947void PDFPage::appendPolygon( const basegfx::B2DPolygon& rPoly, OStringBuffer& rBuffer ) const
948{
949 basegfx::B2DPolygon aPoly( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
950 m_pWriter->m_aMapMode,
951 m_pWriter,
952 rPoly ) );
953
954 if( basegfx::utils::isRectangle( aPoly ) )
955 {
956 basegfx::B2DRange aRange( aPoly.getB2DRange() );
957 basegfx::B2DPoint aBL( aRange.getMinX(), aRange.getMaxY() );
958 appendPixelPoint( aBL, rBuffer );
959 rBuffer.append( ' ' );
960 appendMappedLength( aRange.getWidth(), rBuffer, false, nLog10Divisor );
961 rBuffer.append( ' ' );
962 appendMappedLength( aRange.getHeight(), rBuffer, true, nLog10Divisor );
963 rBuffer.append( " re\n" );
964 return;
965 }
966 sal_uInt32 nPoints = aPoly.count();
967 if( nPoints <= 0 )
968 return;
969
970 sal_uInt32 nBufLen = rBuffer.getLength();
971 basegfx::B2DPoint aLastPoint( aPoly.getB2DPoint( 0 ) );
972 appendPixelPoint( aLastPoint, rBuffer );
973 rBuffer.append( " m\n" );
974 for( sal_uInt32 i = 1; i <= nPoints; i++ )
975 {
976 if( i != nPoints || aPoly.isClosed() )
977 {
978 sal_uInt32 nCurPoint = i % nPoints;
979 sal_uInt32 nLastPoint = i-1;
980 basegfx::B2DPoint aPoint( aPoly.getB2DPoint( nCurPoint ) );
981 if( aPoly.isNextControlPointUsed( nLastPoint ) &&
983 {
984 appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer );
985 rBuffer.append( ' ' );
987 rBuffer.append( ' ' );
988 appendPixelPoint( aPoint, rBuffer );
989 rBuffer.append( " c" );
990 }
991 else if( aPoly.isNextControlPointUsed( nLastPoint ) )
992 {
993 appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer );
994 rBuffer.append( ' ' );
995 appendPixelPoint( aPoint, rBuffer );
996 rBuffer.append( " y" );
997 }
998 else if( aPoly.isPrevControlPointUsed( nCurPoint ) )
999 {
1000 appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer );
1001 rBuffer.append( ' ' );
1002 appendPixelPoint( aPoint, rBuffer );
1003 rBuffer.append( " v" );
1004 }
1005 else
1006 {
1007 appendPixelPoint( aPoint, rBuffer );
1008 rBuffer.append( " l" );
1009 }
1010 if( (rBuffer.getLength() - nBufLen) > 65 )
1011 {
1012 rBuffer.append( "\n" );
1013 nBufLen = rBuffer.getLength();
1014 }
1015 else
1016 rBuffer.append( " " );
1017 }
1018 }
1019 rBuffer.append( "h\n" );
1020}
1021
1022void PDFPage::appendPolyPolygon( const tools::PolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
1023{
1024 sal_uInt16 nPolygons = rPolyPoly.Count();
1025 for( sal_uInt16 n = 0; n < nPolygons; n++ )
1026 appendPolygon( rPolyPoly[n], rBuffer );
1027}
1028
1029void PDFPage::appendPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
1030{
1031 for(auto const& rPolygon : rPolyPoly)
1032 appendPolygon( rPolygon, rBuffer );
1033}
1034
1035void PDFPage::appendMappedLength( sal_Int32 nLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32* pOutLength ) const
1036{
1037 sal_Int32 nValue = nLength;
1038 if ( nLength < 0 )
1039 {
1040 rBuffer.append( '-' );
1041 nValue = -nLength;
1042 }
1043 Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1044 m_pWriter->m_aMapMode,
1045 m_pWriter,
1046 Size( nValue, nValue ) ) );
1047 nValue = bVertical ? aSize.Height() : aSize.Width();
1048 if( pOutLength )
1049 *pOutLength = ((nLength < 0 ) ? -nValue : nValue);
1050
1051 appendFixedInt( nValue, rBuffer );
1052}
1053
1054void PDFPage::appendMappedLength( double fLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32 nPrecision ) const
1055{
1056 Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
1057 m_pWriter->m_aMapMode,
1058 m_pWriter,
1059 Size( 1000, 1000 ) ) );
1060 fLength *= pixelToPoint(static_cast<double>(bVertical ? aSize.Height() : aSize.Width()) / 1000.0);
1061 appendDouble( fLength, rBuffer, nPrecision );
1062}
1063
1064bool PDFPage::appendLineInfo( const LineInfo& rInfo, OStringBuffer& rBuffer ) const
1065{
1066 if(LineStyle::Dash == rInfo.GetStyle() && rInfo.GetDashLen() != rInfo.GetDotLen())
1067 {
1068 // dashed and non-degraded case, check for implementation limits of dash array
1069 // in PDF reader apps (e.g. acroread)
1070 if(2 * (rInfo.GetDashCount() + rInfo.GetDotCount()) > 10)
1071 {
1072 return false;
1073 }
1074 }
1075
1076 if(basegfx::B2DLineJoin::NONE != rInfo.GetLineJoin())
1077 {
1078 // LineJoin used, ExtLineInfo required
1079 return false;
1080 }
1081
1082 if(css::drawing::LineCap_BUTT != rInfo.GetLineCap())
1083 {
1084 // LineCap used, ExtLineInfo required
1085 return false;
1086 }
1087
1088 if( rInfo.GetStyle() == LineStyle::Dash )
1089 {
1090 rBuffer.append( "[ " );
1091 if( rInfo.GetDashLen() == rInfo.GetDotLen() ) // degraded case
1092 {
1093 appendMappedLength( rInfo.GetDashLen(), rBuffer );
1094 rBuffer.append( ' ' );
1095 appendMappedLength( rInfo.GetDistance(), rBuffer );
1096 rBuffer.append( ' ' );
1097 }
1098 else
1099 {
1100 for( int n = 0; n < rInfo.GetDashCount(); n++ )
1101 {
1102 appendMappedLength( rInfo.GetDashLen(), rBuffer );
1103 rBuffer.append( ' ' );
1104 appendMappedLength( rInfo.GetDistance(), rBuffer );
1105 rBuffer.append( ' ' );
1106 }
1107 for( int m = 0; m < rInfo.GetDotCount(); m++ )
1108 {
1109 appendMappedLength( rInfo.GetDotLen(), rBuffer );
1110 rBuffer.append( ' ' );
1111 appendMappedLength( rInfo.GetDistance(), rBuffer );
1112 rBuffer.append( ' ' );
1113 }
1114 }
1115 rBuffer.append( "] 0 d\n" );
1116 }
1117
1118 if( rInfo.GetWidth() > 1 )
1119 {
1120 appendMappedLength( rInfo.GetWidth(), rBuffer );
1121 rBuffer.append( " w\n" );
1122 }
1123 else if( rInfo.GetWidth() == 0 )
1124 {
1125 // "pixel" line
1126 appendDouble( 72.0/double(m_pWriter->GetDPIX()), rBuffer );
1127 rBuffer.append( " w\n" );
1128 }
1129
1130 return true;
1131}
1132
1133void PDFPage::appendWaveLine( sal_Int32 nWidth, sal_Int32 nY, sal_Int32 nDelta, OStringBuffer& rBuffer ) const
1134{
1135 if( nWidth <= 0 )
1136 return;
1137 if( nDelta < 1 )
1138 nDelta = 1;
1139
1140 rBuffer.append( "0 " );
1141 appendMappedLength( nY, rBuffer );
1142 rBuffer.append( " m\n" );
1143 for( sal_Int32 n = 0; n < nWidth; )
1144 {
1145 n += nDelta;
1146 appendMappedLength( n, rBuffer, false );
1147 rBuffer.append( ' ' );
1148 appendMappedLength( nDelta+nY, rBuffer );
1149 rBuffer.append( ' ' );
1150 n += nDelta;
1151 appendMappedLength( n, rBuffer, false );
1152 rBuffer.append( ' ' );
1153 appendMappedLength( nY, rBuffer );
1154 rBuffer.append( " v " );
1155 if( n < nWidth )
1156 {
1157 n += nDelta;
1158 appendMappedLength( n, rBuffer, false );
1159 rBuffer.append( ' ' );
1160 appendMappedLength( nY-nDelta, rBuffer );
1161 rBuffer.append( ' ' );
1162 n += nDelta;
1163 appendMappedLength( n, rBuffer, false );
1164 rBuffer.append( ' ' );
1165 appendMappedLength( nY, rBuffer );
1166 rBuffer.append( " v\n" );
1167 }
1168 }
1169 rBuffer.append( "S\n" );
1170}
1171
1172void PDFPage::appendMatrix3(Matrix3 const & rMatrix, OStringBuffer& rBuffer)
1173{
1174 appendDouble(rMatrix.get(0), rBuffer);
1175 rBuffer.append(' ');
1176 appendDouble(rMatrix.get(1), rBuffer);
1177 rBuffer.append(' ');
1178 appendDouble(rMatrix.get(2), rBuffer);
1179 rBuffer.append(' ');
1180 appendDouble(rMatrix.get(3), rBuffer);
1181 rBuffer.append(' ');
1182 appendPoint(Point(tools::Long(rMatrix.get(4)), tools::Long(rMatrix.get(5))), rBuffer);
1183}
1184
1186{
1188
1189 if (m_nUserUnit > 1)
1190 {
1191 fRet /= m_nUserUnit;
1192 }
1193
1194 return fRet;
1195}
1196
1198 const css::uno::Reference< css::beans::XMaterialHolder >& xEnc,
1199 PDFWriter& i_rOuterFace)
1201 m_aMapMode( MapUnit::MapPoint, Point(), Fraction( 1, pointToPixel(1) ), Fraction( 1, pointToPixel(1) ) ),
1202 m_nCurrentStructElement( 0 ),
1203 m_bEmitStructure( true ),
1204 m_nNextFID( 1 ),
1205 m_aPDFBmpCache(
1206 officecfg::Office::Common::VCL::PDFExportImageCacheSize::get() ),
1207 m_nCurrentPage( -1 ),
1208 m_nCatalogObject(0),
1209 m_nSignatureObject( -1 ),
1210 m_nSignatureContentOffset( 0 ),
1211 m_nSignatureLastByteRangeNoOffset( 0 ),
1212 m_nResourceDict( -1 ),
1213 m_nFontDictObject( -1 ),
1214 m_aContext(rContext),
1215 m_aFile(m_aContext.URL),
1216 m_bOpen(false),
1217 m_DocDigest(::comphelper::HashType::MD5),
1218 m_aCipher( nullptr ),
1219 m_nKeyLength(0),
1220 m_nRC4KeyLength(0),
1221 m_bEncryptThisStream( false ),
1222 m_nAccessPermissions(0),
1223 m_bIsPDF_A1( false ),
1224 m_bIsPDF_A2( false ),
1225 m_bIsPDF_UA( false ),
1226 m_bIsPDF_A3( false ),
1227 m_rOuterFace( i_rOuterFace )
1228{
1229 m_aStructure.emplace_back( );
1230 m_aStructure[0].m_nOwnElement = 0;
1231 m_aStructure[0].m_nParentElement = 0;
1232
1233 Font aFont;
1234 aFont.SetFamilyName( "Times" );
1235 aFont.SetFontSize( Size( 0, 12 ) );
1236
1237 GraphicsState aState;
1238 aState.m_aMapMode = m_aMapMode;
1239 aState.m_aFont = aFont;
1240 m_aGraphicsStack.push_front( aState );
1241
1242 osl::File::RC aError = m_aFile.open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create);
1243 if (aError != osl::File::E_None)
1244 {
1245 if (aError == osl::File::E_EXIST)
1246 {
1247 aError = m_aFile.open(osl_File_OpenFlag_Write);
1248 if (aError == osl::File::E_None)
1249 aError = m_aFile.setSize(0);
1250 }
1251 }
1252 if (aError != osl::File::E_None)
1253 return;
1254
1255 m_bOpen = true;
1256
1257 // setup DocInfo
1258 setupDocInfo();
1259
1260 /* prepare the cypher engine, can be done in CTOR, free in DTOR */
1261 m_aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream );
1262
1263 if( xEnc.is() )
1264 prepareEncryption( xEnc );
1265
1267 {
1268 // sanity check
1272 )
1273 {
1274 // the field lengths are invalid ? This was not setup by initEncryption.
1275 // do not encrypt after all
1278 OSL_ENSURE( false, "encryption data failed sanity check, encryption disabled" );
1279 }
1280 else // setup key lengths
1282 }
1283
1284 // write header
1285 OStringBuffer aBuffer( 20 );
1286 aBuffer.append( "%PDF-" );
1287 switch( m_aContext.Version )
1288 {
1289 case PDFWriter::PDFVersion::PDF_1_2: aBuffer.append( "1.2" );break;
1290 case PDFWriter::PDFVersion::PDF_1_3: aBuffer.append( "1.3" );break;
1292 case PDFWriter::PDFVersion::PDF_1_4: aBuffer.append( "1.4" );break;
1293 case PDFWriter::PDFVersion::PDF_1_5: aBuffer.append( "1.5" );break;
1294 default:
1295 case PDFWriter::PDFVersion::PDF_1_6: aBuffer.append( "1.6" );break;
1296 }
1297 // append something binary as comment (suggested in PDF Reference)
1298 aBuffer.append( "\n%\303\244\303\274\303\266\303\237\n" );
1299 if( !writeBuffer( aBuffer.getStr(), aBuffer.getLength() ) )
1300 {
1301 m_aFile.close();
1302 m_bOpen = false;
1303 return;
1304 }
1305
1306 // insert outline root
1307 m_aOutline.emplace_back( );
1308
1310 if( m_bIsPDF_A1 )
1311 m_aContext.Version = PDFWriter::PDFVersion::PDF_1_4; //meaning we need PDF 1.4, PDF/A flavour
1312
1314 if( m_bIsPDF_A2 )
1315 m_aContext.Version = PDFWriter::PDFVersion::PDF_1_6; //we could even use 1.7 features
1316
1318 if( m_bIsPDF_A3 )
1319 m_aContext.Version = PDFWriter::PDFVersion::PDF_1_6; //we could even use 1.7 features
1320
1322 {
1323 m_bIsPDF_UA = true;
1324 m_aContext.Tagged = true;
1325 }
1326
1327 if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 )
1329 else
1331
1332 SetOutputSizePixel( Size( 640, 480 ) );
1333 SetMapMode(MapMode(MapUnit::MapMM));
1334}
1335
1337{
1338 disposeOnce();
1339}
1340
1342{
1343 if( m_aCipher )
1344 rtl_cipher_destroyARCFOUR( m_aCipher );
1345 m_aPages.clear();
1347}
1348
1350{
1351 const ImplSVData* pSVData = ImplGetSVData();
1352
1354 || mxFontCache == pSVData->maGDIData.mxScreenFontCache )
1355 {
1356 const_cast<vcl::PDFWriterImpl&>(*this).ImplUpdateFontData();
1357 }
1358
1360}
1361
1363{
1364 std::vector< sal_uInt8 > aId;
1369}
1370
1372{
1373 OStringBuffer aRet;
1374
1375 TimeValue aTVal, aGMT;
1376 oslDateTime aDT;
1377 osl_getSystemTime(&aGMT);
1378 osl_getLocalTimeFromSystemTime(&aGMT, &aTVal);
1379 osl_getDateTimeFromTimeValue(&aTVal, &aDT);
1380
1381 sal_Int32 nDelta = aTVal.Seconds-aGMT.Seconds;
1382
1383 appendPdfTimeDate(aRet, aDT.Year, aDT.Month, aDT.Day, aDT.Hours, aDT.Minutes, aDT.Seconds, nDelta);
1384
1385 aRet.append("'");
1386 return aRet.makeStringAndClear();
1387}
1388
1389void PDFWriterImpl::computeDocumentIdentifier( std::vector< sal_uInt8 >& o_rIdentifier,
1390 const vcl::PDFWriter::PDFDocInfo& i_rDocInfo,
1391 const OString& i_rCString1,
1392 OString& o_rCString2
1393 )
1394{
1395 o_rIdentifier.clear();
1396
1397 //build the document id
1398 OString aInfoValuesOut;
1399 OStringBuffer aID( 1024 );
1400 if( !i_rDocInfo.Title.isEmpty() )
1402 if( !i_rDocInfo.Author.isEmpty() )
1404 if( !i_rDocInfo.Subject.isEmpty() )
1406 if( !i_rDocInfo.Keywords.isEmpty() )
1408 if( !i_rDocInfo.Creator.isEmpty() )
1410 if( !i_rDocInfo.Producer.isEmpty() )
1412
1413 TimeValue aTVal, aGMT;
1414 oslDateTime aDT;
1415 osl_getSystemTime( &aGMT );
1416 osl_getLocalTimeFromSystemTime( &aGMT, &aTVal );
1417 osl_getDateTimeFromTimeValue( &aTVal, &aDT );
1418 OStringBuffer aCreationMetaDateString(64);
1419
1420 // i59651: we fill the Metadata date string as well, if PDF/A is requested
1421 // according to ISO 19005-1:2005 6.7.3 the date is corrected for
1422 // local time zone offset UTC only, whereas Acrobat 8 seems
1423 // to use the localtime notation only
1424 // according to a recommendation in XMP Specification (Jan 2004, page 75)
1425 // the Acrobat way seems the right approach
1426 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Year/1000)%10)) );
1427 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Year/100)%10)) );
1428 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Year/10)%10)) );
1429 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Year)%10)) );
1430 aCreationMetaDateString.append( "-" );
1431 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Month/10)%10)) );
1432 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Month)%10)) );
1433 aCreationMetaDateString.append( "-" );
1434 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Day/10)%10)) );
1435 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Day)%10)) );
1436 aCreationMetaDateString.append( "T" );
1437 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Hours/10)%10)) );
1438 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Hours)%10)) );
1439 aCreationMetaDateString.append( ":" );
1440 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Minutes/10)%10)) );
1441 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Minutes)%10)) );
1442 aCreationMetaDateString.append( ":" );
1443 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Seconds/10)%10)) );
1444 aCreationMetaDateString.append( static_cast<char>('0' + ((aDT.Seconds)%10)) );
1445
1446 sal_uInt32 nDelta = 0;
1447 if( aGMT.Seconds > aTVal.Seconds )
1448 {
1449 nDelta = aGMT.Seconds-aTVal.Seconds;
1450 aCreationMetaDateString.append( "-" );
1451 }
1452 else if( aGMT.Seconds < aTVal.Seconds )
1453 {
1454 nDelta = aTVal.Seconds-aGMT.Seconds;
1455 aCreationMetaDateString.append( "+" );
1456 }
1457 else
1458 {
1459 aCreationMetaDateString.append( "Z" );
1460
1461 }
1462 if( nDelta )
1463 {
1464 aCreationMetaDateString.append( static_cast<char>('0' + ((nDelta/36000)%10)) );
1465 aCreationMetaDateString.append( static_cast<char>('0' + ((nDelta/3600)%10)) );
1466 aCreationMetaDateString.append( ":" );
1467 aCreationMetaDateString.append( static_cast<char>('0' + ((nDelta/600)%6)) );
1468 aCreationMetaDateString.append( static_cast<char>('0' + ((nDelta/60)%10)) );
1469 }
1470 aID.append( i_rCString1.getStr(), i_rCString1.getLength() );
1471
1472 aInfoValuesOut = aID.makeStringAndClear();
1473 o_rCString2 = aCreationMetaDateString.makeStringAndClear();
1474
1475 ::comphelper::Hash aDigest(::comphelper::HashType::MD5);
1476 aDigest.update(reinterpret_cast<unsigned char const*>(&aGMT), sizeof(aGMT));
1477 aDigest.update(reinterpret_cast<unsigned char const*>(aInfoValuesOut.getStr()), aInfoValuesOut.getLength());
1478 //the binary form of the doc id is needed for encryption stuff
1479 o_rIdentifier = aDigest.finalize();
1480}
1481
1482/* i12626 methods */
1483/*
1484check if the Unicode string must be encrypted or not, perform the requested task,
1485append the string as unicode hex, encrypted if needed
1486 */
1487inline void PDFWriterImpl::appendUnicodeTextStringEncrypt( const OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
1488{
1489 rOutBuffer.append( "<" );
1491 {
1492 const sal_Unicode* pStr = rInString.getStr();
1493 sal_Int32 nLen = rInString.getLength();
1494 //prepare a unicode string, encrypt it
1495 enableStringEncryption( nInObjectNumber );
1496 sal_uInt8 *pCopy = m_vEncryptionBuffer.data();
1497 sal_Int32 nChars = 2 + (nLen * 2);
1498 m_vEncryptionBuffer.resize(nChars);
1499 *pCopy++ = 0xFE;
1500 *pCopy++ = 0xFF;
1501 // we need to prepare a byte stream from the unicode string buffer
1502 for( int i = 0; i < nLen; i++ )
1503 {
1504 sal_Unicode aUnChar = pStr[i];
1505 *pCopy++ = static_cast<sal_uInt8>( aUnChar >> 8 );
1506 *pCopy++ = static_cast<sal_uInt8>( aUnChar & 255 );
1507 }
1508 //encrypt in place
1509 rtl_cipher_encodeARCFOUR( m_aCipher, m_vEncryptionBuffer.data(), nChars, m_vEncryptionBuffer.data(), nChars );
1510 //now append, hexadecimal (appendHex), the encrypted result
1511 for(int i = 0; i < nChars; i++)
1512 appendHex( m_vEncryptionBuffer[i], rOutBuffer );
1513 }
1514 else
1515 PDFWriter::AppendUnicodeTextString(rInString, rOutBuffer);
1516 rOutBuffer.append( ">" );
1517}
1518
1519inline void PDFWriterImpl::appendLiteralStringEncrypt( std::string_view rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
1520{
1521 rOutBuffer.append( "(" );
1522 sal_Int32 nChars = rInString.size();
1523 //check for encryption, if ok, encrypt the string, then convert with appndLiteralString
1525 {
1526 m_vEncryptionBuffer.resize(nChars);
1527 //encrypt the string in a buffer, then append it
1528 enableStringEncryption( nInObjectNumber );
1529 rtl_cipher_encodeARCFOUR( m_aCipher, rInString.data(), nChars, m_vEncryptionBuffer.data(), nChars );
1530 appendLiteralString( reinterpret_cast<char*>(m_vEncryptionBuffer.data()), nChars, rOutBuffer );
1531 }
1532 else
1533 appendLiteralString( rInString.data(), nChars , rOutBuffer );
1534 rOutBuffer.append( ")" );
1535}
1536
1537void PDFWriterImpl::appendLiteralStringEncrypt( std::u16string_view rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer, rtl_TextEncoding nEnc )
1538{
1539 OString aBufferString( OUStringToOString( rInString, nEnc ) );
1540 sal_Int32 nLen = aBufferString.getLength();
1541 OStringBuffer aBuf( nLen );
1542 const char* pT = aBufferString.getStr();
1543
1544 for( sal_Int32 i = 0; i < nLen; i++, pT++ )
1545 {
1546 if( (*pT & 0x80) == 0 )
1547 aBuf.append( *pT );
1548 else
1549 {
1550 aBuf.append( '<' );
1551 appendHex( *pT, aBuf );
1552 aBuf.append( '>' );
1553 }
1554 }
1555 aBufferString = aBuf.makeStringAndClear();
1556 appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer);
1557}
1558
1559/* end i12626 methods */
1560
1561void PDFWriterImpl::emitComment( const char* pComment )
1562{
1563 OString aLine = OString::Concat("% ") + pComment + "\n";
1564 writeBuffer( aLine.getStr(), aLine.getLength() );
1565}
1566
1568{
1570 {
1571 sal_uInt64 nEndPos = pStream->TellEnd();
1572 pStream->Seek( STREAM_SEEK_TO_BEGIN );
1573 ZCodec aCodec( 0x4000, 0x4000 );
1574 SvMemoryStream aStream;
1575 aCodec.BeginCompression();
1576 aCodec.Write( aStream, static_cast<const sal_uInt8*>(pStream->GetData()), nEndPos );
1577 aCodec.EndCompression();
1578 nEndPos = aStream.Tell();
1579 pStream->Seek( STREAM_SEEK_TO_BEGIN );
1580 aStream.Seek( STREAM_SEEK_TO_BEGIN );
1581 pStream->SetStreamSize( nEndPos );
1582 pStream->WriteBytes( aStream.GetData(), nEndPos );
1583 return true;
1584 }
1585 else
1586 return false;
1587}
1588
1590{
1592 {
1593 m_pCodec = std::make_unique<ZCodec>( 0x4000, 0x4000 );
1594 m_pMemStream = std::make_unique<SvMemoryStream>();
1595 m_pCodec->BeginCompression();
1596 }
1597}
1598
1600{
1602 {
1603 m_pCodec->EndCompression();
1604 m_pCodec.reset();
1605 sal_uInt64 nLen = m_pMemStream->Tell();
1606 m_pMemStream->Seek( 0 );
1607 writeBuffer( m_pMemStream->GetData(), nLen );
1608 m_pMemStream.reset();
1609 }
1610}
1611
1612bool PDFWriterImpl::writeBuffer( const void* pBuffer, sal_uInt64 nBytes )
1613{
1614 if( ! m_bOpen ) // we are already down the drain
1615 return false;
1616
1617 if( ! nBytes ) // huh ?
1618 return true;
1619
1620 if( !m_aOutputStreams.empty() )
1621 {
1622 m_aOutputStreams.front().m_pStream->Seek( STREAM_SEEK_TO_END );
1623 m_aOutputStreams.front().m_pStream->WriteBytes(
1624 pBuffer, sal::static_int_cast<std::size_t>(nBytes));
1625 return true;
1626 }
1627
1628 sal_uInt64 nWritten;
1629 if( m_pCodec )
1630 {
1631 m_pCodec->Write( *m_pMemStream, static_cast<const sal_uInt8*>(pBuffer), static_cast<sal_uLong>(nBytes) );
1632 nWritten = nBytes;
1633 }
1634 else
1635 {
1636 bool buffOK = true;
1638 {
1639 /* implement the encryption part of the PDF spec encryption algorithm 3.1 */
1640 m_vEncryptionBuffer.resize(nBytes);
1641 if( buffOK )
1642 rtl_cipher_encodeARCFOUR( m_aCipher,
1643 pBuffer, static_cast<sal_Size>(nBytes),
1644 m_vEncryptionBuffer.data(), static_cast<sal_Size>(nBytes) );
1645 }
1646
1647 const void* pWriteBuffer = ( m_bEncryptThisStream && buffOK ) ? m_vEncryptionBuffer.data() : pBuffer;
1648 m_DocDigest.update(static_cast<unsigned char const*>(pWriteBuffer), static_cast<sal_uInt32>(nBytes));
1649
1650 if (m_aFile.write(pWriteBuffer, nBytes, nWritten) != osl::File::E_None)
1651 nWritten = 0;
1652
1653 if( nWritten != nBytes )
1654 {
1655 m_aFile.close();
1656 m_bOpen = false;
1657 }
1658 }
1659
1660 return nWritten == nBytes;
1661}
1662
1663void PDFWriterImpl::newPage( double nPageWidth, double nPageHeight, PDFWriter::Orientation eOrientation )
1664{
1665 endPage();
1666 m_nCurrentPage = m_aPages.size();
1667 m_aPages.emplace_back(this, nPageWidth, nPageHeight, eOrientation );
1668
1669 sal_Int32 nUserUnit = m_aPages.back().m_nUserUnit;
1670 if (nUserUnit > 1)
1671 {
1672 m_aMapMode = MapMode(MapUnit::MapPoint, Point(), Fraction(nUserUnit, pointToPixel(1)),
1673 Fraction(nUserUnit, pointToPixel(1)));
1674 }
1675
1676 m_aPages.back().beginStream();
1677
1678 // setup global graphics state
1679 // linewidth is "1 pixel" by default
1680 OStringBuffer aBuf( 16 );
1681 appendDouble( 72.0/double(GetDPIX()), aBuf );
1682 aBuf.append( " w\n" );
1683 writeBuffer( aBuf.getStr(), aBuf.getLength() );
1684}
1685
1687{
1688 if( m_aPages.empty() )
1689 return;
1690
1691 // close eventual MC sequence
1693
1694 // sanity check
1695 if( !m_aOutputStreams.empty() )
1696 {
1697 OSL_FAIL( "redirection across pages !!!" );
1698 m_aOutputStreams.clear(); // leak !
1700 }
1701
1702 m_aGraphicsStack.clear();
1703 m_aGraphicsStack.emplace_back( );
1704
1705 // this should pop the PDF graphics stack if necessary
1707
1708 m_aPages.back().endStream();
1709
1710 // reset the default font
1711 Font aFont;
1712 aFont.SetFamilyName( "Times" );
1713 aFont.SetFontSize( Size( 0, 12 ) );
1714
1716 m_aGraphicsStack.front().m_aFont = aFont;
1717
1718 for (auto & bitmap : m_aBitmaps)
1719 {
1720 if( ! bitmap.m_aBitmap.IsEmpty() )
1721 {
1722 writeBitmapObject(bitmap);
1723 bitmap.m_aBitmap = BitmapEx();
1724 }
1725 }
1726 for (auto & jpeg : m_aJPGs)
1727 {
1728 if( jpeg.m_pStream )
1729 {
1730 writeJPG( jpeg );
1731 jpeg.m_pStream.reset();
1732 jpeg.m_aAlphaMask = AlphaMask();
1733 }
1734 }
1735 for (auto & item : m_aTransparentObjects)
1736 {
1737 if( item.m_pContentStream )
1738 {
1740 item.m_pContentStream.reset();
1741 }
1742 }
1743
1744}
1745
1747{
1748 m_aObjects.push_back( ~0U );
1749 return m_aObjects.size();
1750}
1751
1753{
1754 if( ! m_bOpen )
1755 return false;
1756
1757 sal_uInt64 nOffset = ~0U;
1758 osl::File::RC aError = m_aFile.getPos(nOffset);
1759 SAL_WARN_IF( aError != osl::File::E_None, "vcl.pdfwriter", "could not register object" );
1760 if (aError != osl::File::E_None)
1761 {
1762 m_aFile.close();
1763 m_bOpen = false;
1764 }
1765 m_aObjects[ n-1 ] = nOffset;
1766 return aError == osl::File::E_None;
1767}
1768
1769#define CHECK_RETURN( x ) if( !(x) ) return 0
1770#define CHECK_RETURN2( x ) if( !(x) ) return
1771
1772sal_Int32 PDFWriterImpl::emitStructParentTree( sal_Int32 nObject )
1773{
1774 if( nObject > 0 )
1775 {
1776 OStringBuffer aLine( 1024 );
1777
1778 aLine.append( nObject );
1779 aLine.append( " 0 obj\n"
1780 "<</Nums[\n" );
1781 sal_Int32 nTreeItems = m_aStructParentTree.size();
1782 for( sal_Int32 n = 0; n < nTreeItems; n++ )
1783 {
1784 aLine.append( n );
1785 aLine.append( ' ' );
1786 aLine.append( m_aStructParentTree[n] );
1787 aLine.append( "\n" );
1788 }
1789 aLine.append( "]>>\nendobj\n\n" );
1790 CHECK_RETURN( updateObject( nObject ) );
1791 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
1792 }
1793 return nObject;
1794}
1795
1797{
1798 static std::map< PDFWriter::StructAttribute, const char* > aAttributeStrings;
1799 // fill maps once
1800 if( aAttributeStrings.empty() )
1801 {
1802 aAttributeStrings[ PDFWriter::Placement ] = "Placement";
1803 aAttributeStrings[ PDFWriter::WritingMode ] = "WritingMode";
1804 aAttributeStrings[ PDFWriter::SpaceBefore ] = "SpaceBefore";
1805 aAttributeStrings[ PDFWriter::SpaceAfter ] = "SpaceAfter";
1806 aAttributeStrings[ PDFWriter::StartIndent ] = "StartIndent";
1807 aAttributeStrings[ PDFWriter::EndIndent ] = "EndIndent";
1808 aAttributeStrings[ PDFWriter::TextIndent ] = "TextIndent";
1809 aAttributeStrings[ PDFWriter::TextAlign ] = "TextAlign";
1810 aAttributeStrings[ PDFWriter::Width ] = "Width";
1811 aAttributeStrings[ PDFWriter::Height ] = "Height";
1812 aAttributeStrings[ PDFWriter::BlockAlign ] = "BlockAlign";
1813 aAttributeStrings[ PDFWriter::InlineAlign ] = "InlineAlign";
1814 aAttributeStrings[ PDFWriter::LineHeight ] = "LineHeight";
1815 aAttributeStrings[ PDFWriter::BaselineShift ] = "BaselineShift";
1816 aAttributeStrings[ PDFWriter::TextDecorationType ] = "TextDecorationType";
1817 aAttributeStrings[ PDFWriter::ListNumbering ] = "ListNumbering";
1818 aAttributeStrings[ PDFWriter::RowSpan ] = "RowSpan";
1819 aAttributeStrings[ PDFWriter::ColSpan ] = "ColSpan";
1820 aAttributeStrings[ PDFWriter::LinkAnnotation ] = "LinkAnnotation";
1821 }
1822
1823 std::map< PDFWriter::StructAttribute, const char* >::const_iterator it =
1824 aAttributeStrings.find( eAttr );
1825
1826 if( it == aAttributeStrings.end() )
1827 SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttribute " << eAttr);
1828
1829 return it != aAttributeStrings.end() ? it->second : "";
1830}
1831
1833{
1834 static std::map< PDFWriter::StructAttributeValue, const char* > aValueStrings;
1835
1836 if( aValueStrings.empty() )
1837 {
1838 aValueStrings[ PDFWriter::NONE ] = "None";
1839 aValueStrings[ PDFWriter::Block ] = "Block";
1840 aValueStrings[ PDFWriter::Inline ] = "Inline";
1841 aValueStrings[ PDFWriter::Before ] = "Before";
1842 aValueStrings[ PDFWriter::After ] = "After";
1843 aValueStrings[ PDFWriter::Start ] = "Start";
1844 aValueStrings[ PDFWriter::End ] = "End";
1845 aValueStrings[ PDFWriter::LrTb ] = "LrTb";
1846 aValueStrings[ PDFWriter::RlTb ] = "RlTb";
1847 aValueStrings[ PDFWriter::TbRl ] = "TbRl";
1848 aValueStrings[ PDFWriter::Center ] = "Center";
1849 aValueStrings[ PDFWriter::Justify ] = "Justify";
1850 aValueStrings[ PDFWriter::Auto ] = "Auto";
1851 aValueStrings[ PDFWriter::Middle ] = "Middle";
1852 aValueStrings[ PDFWriter::Normal ] = "Normal";
1853 aValueStrings[ PDFWriter::Underline ] = "Underline";
1854 aValueStrings[ PDFWriter::Overline ] = "Overline";
1855 aValueStrings[ PDFWriter::LineThrough ] = "LineThrough";
1856 aValueStrings[ PDFWriter::Disc ] = "Disc";
1857 aValueStrings[ PDFWriter::Circle ] = "Circle";
1858 aValueStrings[ PDFWriter::Square ] = "Square";
1859 aValueStrings[ PDFWriter::Decimal ] = "Decimal";
1860 aValueStrings[ PDFWriter::UpperRoman ] = "UpperRoman";
1861 aValueStrings[ PDFWriter::LowerRoman ] = "LowerRoman";
1862 aValueStrings[ PDFWriter::UpperAlpha ] = "UpperAlpha";
1863 aValueStrings[ PDFWriter::LowerAlpha ] = "LowerAlpha";
1864 }
1865
1866 std::map< PDFWriter::StructAttributeValue, const char* >::const_iterator it =
1867 aValueStrings.find( eVal );
1868
1869 if( it == aValueStrings.end() )
1870 SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttributeValue " << eVal);
1871
1872 return it != aValueStrings.end() ? it->second : "";
1873}
1874
1875static void appendStructureAttributeLine( PDFWriter::StructAttribute i_eAttr, const PDFStructureAttribute& i_rVal, OStringBuffer& o_rLine, bool i_bIsFixedInt )
1876{
1877 o_rLine.append( "/" );
1878 o_rLine.append( PDFWriterImpl::getAttributeTag( i_eAttr ) );
1879
1880 if( i_rVal.eValue != PDFWriter::Invalid )
1881 {
1882 o_rLine.append( "/" );
1883 o_rLine.append( PDFWriterImpl::getAttributeValueTag( i_rVal.eValue ) );
1884 }
1885 else
1886 {
1887 // numerical value
1888 o_rLine.append( " " );
1889 if( i_bIsFixedInt )
1890 appendFixedInt( i_rVal.nValue, o_rLine );
1891 else
1892 o_rLine.append( i_rVal.nValue );
1893 }
1894 o_rLine.append( "\n" );
1895}
1896
1898{
1899 // create layout, list and table attribute sets
1900 OStringBuffer aLayout(256), aList(64), aTable(64);
1901 for (auto const& attribute : i_rEle.m_aAttributes)
1902 {
1903 if( attribute.first == PDFWriter::ListNumbering )
1904 appendStructureAttributeLine( attribute.first, attribute.second, aList, true );
1905 else if( attribute.first == PDFWriter::RowSpan ||
1906 attribute.first == PDFWriter::ColSpan )
1907 appendStructureAttributeLine( attribute.first, attribute.second, aTable, false );
1908 else if( attribute.first == PDFWriter::LinkAnnotation )
1909 {
1910 sal_Int32 nLink = attribute.second.nValue;
1911 std::map< sal_Int32, sal_Int32 >::const_iterator link_it =
1912 m_aLinkPropertyMap.find( nLink );
1913 if( link_it != m_aLinkPropertyMap.end() )
1914 nLink = link_it->second;
1915 if( nLink >= 0 && o3tl::make_unsigned(nLink) < m_aLinks.size() )
1916 {
1917 // update struct parent of link
1918 OString aStructParentEntry =
1919 OString::number( i_rEle.m_nObject ) +
1920 " 0 R";
1921 m_aStructParentTree.push_back( aStructParentEntry );
1922 m_aLinks[ nLink ].m_nStructParent = m_aStructParentTree.size()-1;
1923
1924 sal_Int32 nRefObject = createObject();
1925 if (updateObject(nRefObject))
1926 {
1927 OString aRef =
1928 OString::number( nRefObject ) +
1929 " 0 obj\n"
1930 "<</Type/OBJR/Obj " +
1931 OString::number( m_aLinks[ nLink ].m_nObject ) +
1932 " 0 R>>\n"
1933 "endobj\n\n";
1934 writeBuffer( aRef.getStr(), aRef.getLength() );
1935 }
1936
1937 i_rEle.m_aKids.emplace_back( nRefObject );
1938 }
1939 else
1940 {
1941 OSL_FAIL( "unresolved link id for Link structure" );
1942 SAL_INFO("vcl.pdfwriter", "unresolved link id " << nLink << " for Link structure");
1944 {
1945 OString aLine = "unresolved link id " +
1946 OString::number( nLink ) +
1947 " for Link structure";
1948 emitComment( aLine.getStr() );
1949 }
1950 }
1951 }
1952 else
1953 appendStructureAttributeLine( attribute.first, attribute.second, aLayout, true );
1954 }
1955 if( ! i_rEle.m_aBBox.IsEmpty() )
1956 {
1957 aLayout.append( "/BBox[" );
1958 appendFixedInt( i_rEle.m_aBBox.Left(), aLayout );
1959 aLayout.append( " " );
1960 appendFixedInt( i_rEle.m_aBBox.Top(), aLayout );
1961 aLayout.append( " " );
1962 appendFixedInt( i_rEle.m_aBBox.Right(), aLayout );
1963 aLayout.append( " " );
1964 appendFixedInt( i_rEle.m_aBBox.Bottom(), aLayout );
1965 aLayout.append( "]\n" );
1966 }
1967
1968 std::vector< sal_Int32 > aAttribObjects;
1969 if( !aLayout.isEmpty() )
1970 {
1971 aAttribObjects.push_back( createObject() );
1972 if (updateObject( aAttribObjects.back() ))
1973 {
1974 OStringBuffer aObj( 64 );
1975 aObj.append( aAttribObjects.back() );
1976 aObj.append( " 0 obj\n"
1977 "<</O/Layout\n" );
1978 aLayout.append( ">>\nendobj\n\n" );
1979 writeBuffer( aObj.getStr(), aObj.getLength() );
1980 writeBuffer( aLayout.getStr(), aLayout.getLength() );
1981 }
1982 }
1983 if( !aList.isEmpty() )
1984 {
1985 aAttribObjects.push_back( createObject() );
1986 if (updateObject( aAttribObjects.back() ))
1987 {
1988 OStringBuffer aObj( 64 );
1989 aObj.append( aAttribObjects.back() );
1990 aObj.append( " 0 obj\n"
1991 "<</O/List\n" );
1992 aList.append( ">>\nendobj\n\n" );
1993 writeBuffer( aObj.getStr(), aObj.getLength() );
1994 writeBuffer( aList.getStr(), aList.getLength() );
1995 }
1996 }
1997 if( !aTable.isEmpty() )
1998 {
1999 aAttribObjects.push_back( createObject() );
2000 if (updateObject( aAttribObjects.back() ))
2001 {
2002 OStringBuffer aObj( 64 );
2003 aObj.append( aAttribObjects.back() );
2004 aObj.append( " 0 obj\n"
2005 "<</O/Table\n" );
2006 aTable.append( ">>\nendobj\n\n" );
2007 writeBuffer( aObj.getStr(), aObj.getLength() );
2008 writeBuffer( aTable.getStr(), aTable.getLength() );
2009 }
2010 }
2011
2012 OStringBuffer aRet( 64 );
2013 if( aAttribObjects.size() > 1 )
2014 aRet.append( " [" );
2015 for (auto const& attrib : aAttribObjects)
2016 {
2017 aRet.append( " " );
2018 aRet.append( attrib );
2019 aRet.append( " 0 R" );
2020 }
2021 if( aAttribObjects.size() > 1 )
2022 aRet.append( " ]" );
2023 return aRet.makeStringAndClear();
2024}
2025
2027{
2028 if(
2029 // do not emit NonStruct and its children
2031 rEle.m_nOwnElement != rEle.m_nParentElement // but of course emit the struct tree root
2032 )
2033 return 0;
2034
2035 for (auto const& child : rEle.m_aChildren)
2036 {
2037 if( child > 0 && o3tl::make_unsigned(child) < m_aStructure.size() )
2038 {
2039 PDFStructureElement& rChild = m_aStructure[ child ];
2040 if( rChild.m_eType != PDFWriter::NonStructElement )
2041 {
2042 if( rChild.m_nParentElement == rEle.m_nOwnElement )
2043 emitStructure( rChild );
2044 else
2045 {
2046 OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure element" );
2047 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure element with id " << child);
2048 }
2049 }
2050 }
2051 else
2052 {
2053 OSL_FAIL( "PDFWriterImpl::emitStructure: invalid child structure id" );
2054 SAL_INFO("vcl.pdfwriter", "PDFWriterImpl::emitStructure: invalid child structure id " << child);
2055 }
2056 }
2057
2058 OStringBuffer aLine( 512 );
2059 aLine.append( rEle.m_nObject );
2060 aLine.append( " 0 obj\n"
2061 "<</Type" );
2062 sal_Int32 nParentTree = -1;
2063 if( rEle.m_nOwnElement == rEle.m_nParentElement )
2064 {
2065 nParentTree = createObject();
2066 CHECK_RETURN( nParentTree );
2067 aLine.append( "/StructTreeRoot\n" );
2068 aLine.append( "/ParentTree " );
2069 aLine.append( nParentTree );
2070 aLine.append( " 0 R\n" );
2071 if( ! m_aRoleMap.empty() )
2072 {
2073 aLine.append( "/RoleMap<<" );
2074 for (auto const& role : m_aRoleMap)
2075 {
2076 aLine.append( '/' );
2077 aLine.append(role.first);
2078 aLine.append( '/' );
2079 aLine.append( role.second );
2080 aLine.append( '\n' );
2081 }
2082 aLine.append( ">>\n" );
2083 }
2084 }
2085 else
2086 {
2087 aLine.append( "/StructElem\n"
2088 "/S/" );
2089 if( !rEle.m_aAlias.isEmpty() )
2090 aLine.append( rEle.m_aAlias );
2091 else
2092 aLine.append( getStructureTag( rEle.m_eType ) );
2093 aLine.append( "\n"
2094 "/P " );
2095 aLine.append( m_aStructure[ rEle.m_nParentElement ].m_nObject );
2096 aLine.append( " 0 R\n"
2097 "/Pg " );
2098 aLine.append( rEle.m_nFirstPageObject );
2099 aLine.append( " 0 R\n" );
2100 if( !rEle.m_aActualText.isEmpty() )
2101 {
2102 aLine.append( "/ActualText" );
2104 aLine.append( "\n" );
2105 }
2106 if( !rEle.m_aAltText.isEmpty() )
2107 {
2108 aLine.append( "/Alt" );
2110 aLine.append( "\n" );
2111 }
2112 }
2113 if( (! rEle.m_aBBox.IsEmpty()) || (! rEle.m_aAttributes.empty()) )
2114 {
2115 OString aAttribs = emitStructureAttributes( rEle );
2116 if( !aAttribs.isEmpty() )
2117 {
2118 aLine.append( "/A" );
2119 aLine.append( aAttribs );
2120 aLine.append( "\n" );
2121 }
2122 }
2123 if( !rEle.m_aLocale.Language.isEmpty() )
2124 {
2125 /* PDF allows only RFC 3066, which is only partly BCP 47 and does not
2126 * include script tags and others.
2127 * http://pdf.editme.com/pdfua-naturalLanguageSpecification
2128 * http://partners.adobe.com/public/developer/en/pdf/PDFReference16.pdf#page=886
2129 * https://www.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf#M13.9.19332.1Heading.97.Natural.Language.Specification
2130 * */
2131 LanguageTag aLanguageTag( rEle.m_aLocale);
2132 OUString aLanguage, aScript, aCountry;
2133 aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, aCountry);
2134 if (!aLanguage.isEmpty())
2135 {
2136 OUStringBuffer aLocBuf( 16 );
2137 aLocBuf.append( aLanguage );
2138 if( !aCountry.isEmpty() )
2139 {
2140 aLocBuf.append( '-' );
2141 aLocBuf.append( aCountry );
2142 }
2143 aLine.append( "/Lang" );
2144 appendLiteralStringEncrypt( aLocBuf, rEle.m_nObject, aLine );
2145 aLine.append( "\n" );
2146 }
2147 }
2148 if( ! rEle.m_aKids.empty() )
2149 {
2150 unsigned int i = 0;
2151 aLine.append( "/K[" );
2152 for (auto const& kid : rEle.m_aKids)
2153 {
2154 if( kid.nMCID == -1 )
2155 {
2156 aLine.append( kid.nObject );
2157 aLine.append( " 0 R" );
2158 aLine.append( ( (i & 15) == 15 ) ? "\n" : " " );
2159 }
2160 else
2161 {
2162 if( kid.nObject == rEle.m_nFirstPageObject )
2163 {
2164 aLine.append( kid.nMCID );
2165 aLine.append( " " );
2166 }
2167 else
2168 {
2169 aLine.append( "<</Type/MCR/Pg " );
2170 aLine.append( kid.nObject );
2171 aLine.append( " 0 R /MCID " );
2172 aLine.append( kid.nMCID );
2173 aLine.append( ">>\n" );
2174 }
2175 }
2176 ++i;
2177 }
2178 aLine.append( "]\n" );
2179 }
2180 aLine.append( ">>\nendobj\n\n" );
2181
2183 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2184
2185 CHECK_RETURN( emitStructParentTree( nParentTree ) );
2186
2187 return rEle.m_nObject;
2188}
2189
2191{
2192 for (auto const& gradient : m_aGradients)
2193 {
2194 if ( !writeGradientFunction( gradient ) ) return false;
2195 }
2196 return true;
2197}
2198
2200{
2201 OStringBuffer aTilingObj( 1024 );
2202
2203 for (auto & tiling : m_aTilings)
2204 {
2205 SAL_WARN_IF( !tiling.m_pTilingStream, "vcl.pdfwriter", "tiling without stream" );
2206 if( ! tiling.m_pTilingStream )
2207 continue;
2208
2209 aTilingObj.setLength( 0 );
2210
2212 {
2213 emitComment( "PDFWriterImpl::emitTilings" );
2214 }
2215
2216 sal_Int32 nX = static_cast<sal_Int32>(tiling.m_aRectangle.Left());
2217 sal_Int32 nY = static_cast<sal_Int32>(tiling.m_aRectangle.Top());
2218 sal_Int32 nW = static_cast<sal_Int32>(tiling.m_aRectangle.GetWidth());
2219 sal_Int32 nH = static_cast<sal_Int32>(tiling.m_aRectangle.GetHeight());
2220 if( tiling.m_aCellSize.Width() == 0 )
2221 tiling.m_aCellSize.setWidth( nW );
2222 if( tiling.m_aCellSize.Height() == 0 )
2223 tiling.m_aCellSize.setHeight( nH );
2224
2225 bool bDeflate = compressStream( tiling.m_pTilingStream.get() );
2226 sal_uInt64 const nTilingStreamSize = tiling.m_pTilingStream->TellEnd();
2227 tiling.m_pTilingStream->Seek( STREAM_SEEK_TO_BEGIN );
2228
2229 // write pattern object
2230 aTilingObj.append( tiling.m_nObject );
2231 aTilingObj.append( " 0 obj\n" );
2232 aTilingObj.append( "<</Type/Pattern/PatternType 1\n"
2233 "/PaintType 1\n"
2234 "/TilingType 2\n"
2235 "/BBox[" );
2236 appendFixedInt( nX, aTilingObj );
2237 aTilingObj.append( ' ' );
2238 appendFixedInt( nY, aTilingObj );
2239 aTilingObj.append( ' ' );
2240 appendFixedInt( nX+nW, aTilingObj );
2241 aTilingObj.append( ' ' );
2242 appendFixedInt( nY+nH, aTilingObj );
2243 aTilingObj.append( "]\n"
2244 "/XStep " );
2245 appendFixedInt( tiling.m_aCellSize.Width(), aTilingObj );
2246 aTilingObj.append( "\n"
2247 "/YStep " );
2248 appendFixedInt( tiling.m_aCellSize.Height(), aTilingObj );
2249 aTilingObj.append( "\n" );
2250 if( tiling.m_aTransform.matrix[0] != 1.0 ||
2251 tiling.m_aTransform.matrix[1] != 0.0 ||
2252 tiling.m_aTransform.matrix[3] != 0.0 ||
2253 tiling.m_aTransform.matrix[4] != 1.0 ||
2254 tiling.m_aTransform.matrix[2] != 0.0 ||
2255 tiling.m_aTransform.matrix[5] != 0.0 )
2256 {
2257 aTilingObj.append( "/Matrix [" );
2258 // TODO: scaling, mirroring on y, etc
2259 appendDouble( tiling.m_aTransform.matrix[0], aTilingObj );
2260 aTilingObj.append( ' ' );
2261 appendDouble( tiling.m_aTransform.matrix[1], aTilingObj );
2262 aTilingObj.append( ' ' );
2263 appendDouble( tiling.m_aTransform.matrix[3], aTilingObj );
2264 aTilingObj.append( ' ' );
2265 appendDouble( tiling.m_aTransform.matrix[4], aTilingObj );
2266 aTilingObj.append( ' ' );
2267 appendDouble( tiling.m_aTransform.matrix[2], aTilingObj );
2268 aTilingObj.append( ' ' );
2269 appendDouble( tiling.m_aTransform.matrix[5], aTilingObj );
2270 aTilingObj.append( "]\n" );
2271 }
2272 aTilingObj.append( "/Resources" );
2273 tiling.m_aResources.append( aTilingObj, getFontDictObject() );
2274 if( bDeflate )
2275 aTilingObj.append( "/Filter/FlateDecode" );
2276 aTilingObj.append( "/Length " );
2277 aTilingObj.append( static_cast<sal_Int32>(nTilingStreamSize) );
2278 aTilingObj.append( ">>\nstream\n" );
2279 if ( !updateObject( tiling.m_nObject ) ) return false;
2280 if ( !writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) ) return false;
2281 checkAndEnableStreamEncryption( tiling.m_nObject );
2282 bool written = writeBuffer( tiling.m_pTilingStream->GetData(), nTilingStreamSize );
2283 tiling.m_pTilingStream.reset();
2284 if( !written )
2285 return false;
2287 aTilingObj.setLength( 0 );
2288 aTilingObj.append( "\nendstream\nendobj\n\n" );
2289 if ( !writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) ) return false;
2290 }
2291 return true;
2292}
2293
2294sal_Int32 PDFWriterImpl::emitBuildinFont(const pdf::BuildinFontFace* pFD, sal_Int32 nFontObject)
2295{
2296 if( !pFD )
2297 return 0;
2298 const pdf::BuildinFont& rBuildinFont = pFD->GetBuildinFont();
2299
2300 OStringBuffer aLine( 1024 );
2301
2302 if( nFontObject <= 0 )
2303 nFontObject = createObject();
2304 CHECK_RETURN( updateObject( nFontObject ) );
2305 aLine.append( nFontObject );
2306 aLine.append( " 0 obj\n"
2307 "<</Type/Font/Subtype/Type1/BaseFont/" );
2308 appendName( rBuildinFont.m_pPSName, aLine );
2309 aLine.append( "\n" );
2310 if( rBuildinFont.m_eCharSet == RTL_TEXTENCODING_MS_1252 )
2311 aLine.append( "/Encoding/WinAnsiEncoding\n" );
2312 aLine.append( ">>\nendobj\n\n" );
2313 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2314 return nFontObject;
2315}
2316
2317namespace
2318{
2319// Translate units from TT to PS (standard 1/1000)
2320int XUnits(int nUPEM, int n) { return (n * 1000) / nUPEM; }
2321}
2322
2323std::map< sal_Int32, sal_Int32 > PDFWriterImpl::emitSystemFont( const vcl::font::PhysicalFontFace* pFont, EmbedFont const & rEmbed )
2324{
2325 std::map< sal_Int32, sal_Int32 > aRet;
2326
2328 emitComment("PDFWriterImpl::emitSystemFont");
2329
2330 FontSubsetInfo aInfo;
2331 // fill in dummy values
2332 aInfo.m_nAscent = 1000;
2333 aInfo.m_nDescent = 200;
2334 aInfo.m_nCapHeight = 1000;
2335 aInfo.m_aFontBBox = tools::Rectangle( Point( -200, -200 ), Size( 1700, 1700 ) );
2336 aInfo.m_aPSName = pFont->GetFamilyName();
2337
2338 sal_Int32 pWidths[256] = { 0 };
2339 const LogicalFontInstance* pFontInstance = rEmbed.m_pFontInstance;
2340 auto nUPEM = pFont->UnitsPerEm();
2341 for( sal_Ucs c = 32; c < 256; c++ )
2342 {
2343 sal_GlyphId nGlyph = pFontInstance->GetGlyphIndex(c);
2344 pWidths[c] = XUnits(nUPEM, pFontInstance->GetGlyphWidth(nGlyph, false, false));
2345 }
2346
2347 // We are interested only in filling aInfo
2348 sal_GlyphId aGlyphIds[] = { 0 };
2349 sal_uInt8 pEncoding[] = { 0 };
2350 std::vector<sal_uInt8> aBuffer;
2351 pFont->CreateFontSubset(aBuffer, aGlyphIds, pEncoding, 1, aInfo);
2352
2353 // write font descriptor
2354 sal_Int32 nFontDescriptor = emitFontDescriptor( pFont, aInfo, 0, 0 );
2355 if( nFontDescriptor )
2356 {
2357 // write font object
2358 sal_Int32 nObject = createObject();
2359 if( updateObject( nObject ) )
2360 {
2361 OStringBuffer aLine( 1024 );
2362 aLine.append( nObject );
2363 aLine.append( " 0 obj\n"
2364 "<</Type/Font/Subtype/TrueType" );
2365 aLine.append( "/BaseFont/" );
2366 appendName( aInfo.m_aPSName, aLine );
2367 aLine.append( "\n" );
2368 if( !pFont->IsSymbolFont() )
2369 aLine.append( "/Encoding/WinAnsiEncoding\n" );
2370 aLine.append( "/FirstChar 32 /LastChar 255\n"
2371 "/Widths[" );
2372 for( int i = 32; i < 256; i++ )
2373 {
2374 aLine.append( pWidths[i] );
2375 aLine.append( ((i&15) == 15) ? "\n" : " " );
2376 }
2377 aLine.append( "]\n"
2378 "/FontDescriptor " );
2379 aLine.append( nFontDescriptor );
2380 aLine.append( " 0 R>>\n"
2381 "endobj\n\n" );
2382 writeBuffer( aLine.getStr(), aLine.getLength() );
2383
2384 aRet[ rEmbed.m_nNormalFontID ] = nObject;
2385 }
2386 }
2387
2388 return aRet;
2389}
2390
2391namespace
2392{
2393uint32_t fillSubsetArrays(const FontEmit& rSubset, sal_GlyphId* pGlyphIds, sal_Int32* pWidths,
2394 sal_uInt8* pEncoding, sal_Int32* pEncToUnicodeIndex,
2395 sal_Int32* pCodeUnitsPerGlyph, std::vector<sal_Ucs>& rCodeUnits,
2396 sal_Int32& nToUnicodeStream)
2397{
2398 rCodeUnits.reserve(256);
2399
2400 // if it gets used then it will appear in s_subset.m_aMapping, otherwise 0 is fine
2401 pWidths[0] = 0;
2402
2403 uint32_t nGlyphs = 1;
2404 for (auto const& item : rSubset.m_aMapping)
2405 {
2406 sal_uInt8 nEnc = item.second.getGlyphId();
2407
2408 SAL_WARN_IF(pGlyphIds[nEnc] != 0 || pEncoding[nEnc] != 0, "vcl.pdfwriter",
2409 "duplicate glyph");
2410 SAL_WARN_IF(nEnc > rSubset.m_aMapping.size(), "vcl.pdfwriter", "invalid glyph encoding");
2411
2412 pGlyphIds[nEnc] = item.first;
2413 pEncoding[nEnc] = nEnc;
2414 pEncToUnicodeIndex[nEnc] = static_cast<sal_Int32>(rCodeUnits.size());
2415 pCodeUnitsPerGlyph[nEnc] = item.second.countCodes();
2416 pWidths[nEnc] = item.second.getGlyphWidth();
2417 for (sal_Int32 n = 0; n < pCodeUnitsPerGlyph[nEnc]; n++)
2418 rCodeUnits.push_back(item.second.getCode(n));
2419 if (item.second.getCode(0))
2420 nToUnicodeStream = 1;
2421 if (nGlyphs < 256)
2422 nGlyphs++;
2423 else
2424 OSL_FAIL("too many glyphs for subset");
2425 }
2426
2427 return nGlyphs;
2428}
2429}
2430
2432 const FontSubset& rType3Font,
2433 std::map<sal_Int32, sal_Int32>& rFontIDToObject)
2434{
2436 emitComment("PDFWriterImpl::emitType3Font");
2437
2438 const auto& aPalette = pFace->GetColorPalette(0);
2439
2440 FontSubsetInfo aSubsetInfo;
2441 sal_GlyphId pTempGlyphIds[] = { 0 };
2442 sal_uInt8 pTempEncoding[] = { 0 };
2443 std::vector<sal_uInt8> aBuffer;
2444 pFace->CreateFontSubset(aBuffer, pTempGlyphIds, pTempEncoding, 1, aSubsetInfo);
2445
2446 for (auto& rSubset : rType3Font.m_aSubsets)
2447 {
2448 sal_GlyphId pGlyphIds[256] = {};
2449 sal_Int32 pWidths[256];
2450 sal_uInt8 pEncoding[256] = {};
2451 sal_Int32 pEncToUnicodeIndex[256] = {};
2452 sal_Int32 pCodeUnitsPerGlyph[256] = {};
2453 std::vector<sal_Ucs> aCodeUnits;
2454 sal_Int32 nToUnicodeStream = 0;
2455
2456 // fill arrays and prepare encoding index map
2457 auto nGlyphs = fillSubsetArrays(rSubset, pGlyphIds, pWidths, pEncoding, pEncToUnicodeIndex,
2458 pCodeUnitsPerGlyph, aCodeUnits, nToUnicodeStream);
2459
2460 // write font descriptor
2461 sal_Int32 nFontDescriptor = 0;
2463 nFontDescriptor = emitFontDescriptor(pFace, aSubsetInfo, 0, 0);
2464
2465 if (nToUnicodeStream)
2466 nToUnicodeStream = createToUnicodeCMap(pEncoding, aCodeUnits.data(), pCodeUnitsPerGlyph,
2467 pEncToUnicodeIndex, nGlyphs);
2468
2469 // write font object
2470 sal_Int32 nFontObject = createObject();
2471 if (!updateObject(nFontObject))
2472 return false;
2473
2474 OStringBuffer aLine(1024);
2475 aLine.append(nFontObject);
2476 aLine.append(" 0 obj\n"
2477 "<</Type/Font/Subtype/Type3\n");
2478
2479 aLine.append("/FontBBox[");
2480 // note: Top and Bottom are reversed in VCL and PDF rectangles
2481 aLine.append(OString::number(aSubsetInfo.m_aFontBBox.Left()));
2482 aLine.append(' ');
2483 aLine.append(OString::number(aSubsetInfo.m_aFontBBox.Top()));
2484 aLine.append(' ');
2485 aLine.append(OString::number(aSubsetInfo.m_aFontBBox.Right()));
2486 aLine.append(' ');
2487 aLine.append(OString::number(aSubsetInfo.m_aFontBBox.Bottom() + 1));
2488 aLine.append("]\n");
2489
2490 auto nScale = 1. / pFace->UnitsPerEm();
2491 aLine.append("/FontMatrix[");
2492 aLine.append(nScale);
2493 aLine.append(" 0 0 ");
2494 aLine.append(nScale);
2495 aLine.append(" 0 0]\n");
2496
2497 sal_Int32 pGlyphStreams[256] = {};
2498 aLine.append("/CharProcs<<\n");
2499 for (auto i = 1u; i < nGlyphs; i++)
2500 {
2501 auto nStream = createObject();
2502 aLine.append("/");
2503 aLine.append(pFace->GetGlyphName(pGlyphIds[i], true));
2504 aLine.append(" ");
2505 aLine.append(nStream);
2506 aLine.append(" 0 R\n");
2507 pGlyphStreams[i] = nStream;
2508 }
2509 aLine.append(">>\n");
2510
2511 aLine.append("/Encoding<</Type/Encoding/Differences[1");
2512 for (auto i = 1u; i < nGlyphs; i++)
2513 aLine.append(" /" + pFace->GetGlyphName(pGlyphIds[i], true));
2514 aLine.append("]>>\n");
2515
2516 aLine.append("/FirstChar 0\n"
2517 "/LastChar ");
2518 aLine.append(OString::number(nGlyphs));
2519 aLine.append("\n");
2520
2521 aLine.append("/Widths[");
2522 for (auto i = 0u; i < nGlyphs; i++)
2523 {
2524 aLine.append(pWidths[i]);
2525 aLine.append(" ");
2526 }
2527 aLine.append("]\n");
2528
2530 {
2531 aLine.append("/FontDescriptor ");
2532 aLine.append(nFontDescriptor);
2533 aLine.append(" 0 R\n");
2534 }
2535
2536 auto nResources = createObject();
2537 aLine.append("/Resources ");
2538 aLine.append(nResources);
2539 aLine.append(" 0 R\n");
2540
2541 if (nToUnicodeStream)
2542 {
2543 aLine.append("/ToUnicode ");
2544 aLine.append(nToUnicodeStream);
2545 aLine.append(" 0 R\n");
2546 }
2547
2548 aLine.append(">>\n"
2549 "endobj\n\n");
2550
2551 if (!writeBuffer(aLine.getStr(), aLine.getLength()))
2552 return false;
2553
2554 std::set<sal_Int32> aUsedFonts;
2555 std::list<BitmapEmit> aUsedBitmaps;
2556 std::map<sal_uInt8, sal_Int32> aUsedAlpha;
2557 ResourceDict aResourceDict;
2558 std::list<StreamRedirect> aOutputStreams;
2559
2560 for (auto i = 1u; i < nGlyphs; i++)
2561 {
2562 auto nStream = pGlyphStreams[i];
2563 if (!updateObject(nStream))
2564 return false;
2565 OStringBuffer aContents(1024);
2566 aContents.append(pWidths[i]);
2567 aContents.append(" 0 d0\n");
2568
2569 const auto& rGlyph = rSubset.m_aMapping.find(pGlyphIds[i])->second;
2570 const auto& rLayers = rGlyph.getColorLayers();
2571 for (const auto& rLayer : rLayers)
2572 {
2573 aUsedFonts.insert(rLayer.m_nFontID);
2574
2575 aContents.append("q ");
2576 // 0xFFFF is a special value means foreground color.
2577 if (rLayer.m_nColorIndex != 0xFFFF)
2578 {
2579 auto aColor(aPalette[rLayer.m_nColorIndex]);
2580 appendNonStrokingColor(aColor, aContents);
2581 aContents.append(" ");
2582 if (aColor.GetAlpha() != 0xFF
2584 {
2585 auto nAlpha = aColor.GetAlpha();
2586 OStringBuffer aName(16);
2587 aName.append("GS");
2588 appendHex(nAlpha, aName);
2589
2590 aContents.append("/" + aName + " gs ");
2591
2592 if (aUsedAlpha.find(nAlpha) == aUsedAlpha.end())
2593 {
2594 auto nObject = createObject();
2595 aUsedAlpha[nAlpha] = nObject;
2596 pushResource(ResourceKind::ExtGState, aName.makeStringAndClear(),
2597 nObject, aResourceDict, aOutputStreams);
2598 }
2599 }
2600 }
2601 aContents.append("BT ");
2602 aContents.append("/F" + OString::number(rLayer.m_nFontID) + " ");
2603 aContents.append(OString::number(pFace->UnitsPerEm()) + " Tf ");
2604 aContents.append("<");
2605 appendHex(rLayer.m_nSubsetGlyphID, aContents);
2606 aContents.append(">Tj ");
2607 aContents.append("ET ");
2608 aContents.append("Q\n");
2609 }
2610
2611 tools::Rectangle aRect;
2612 const auto& rBitmapData = rGlyph.getColorBitmap(aRect);
2613 if (!rBitmapData.empty())
2614 {
2615 SvMemoryStream aStream(const_cast<uint8_t*>(rBitmapData.data()), rBitmapData.size(),
2616 StreamMode::READ);
2617 vcl::PngImageReader aReader(aStream);
2618 auto aBitmapEmit = createBitmapEmit(std::move(aReader.read()), Graphic(),
2619 aUsedBitmaps, aResourceDict, aOutputStreams);
2620
2621 auto nObject = aBitmapEmit.m_aReferenceXObject.getObject();
2622 aContents.append("q ");
2623 aContents.append(aRect.GetWidth());
2624 aContents.append(" 0 0 ");
2625 aContents.append(aRect.GetHeight());
2626 aContents.append(" ");
2627 aContents.append(aRect.getX());
2628 aContents.append(" ");
2629 aContents.append(aRect.getY());
2630 aContents.append(" cm ");
2631 aContents.append("/Im");
2632 aContents.append(nObject);
2633 aContents.append(" Do Q\n");
2634 }
2635
2636 aLine.setLength(0);
2637 aLine.append(nStream);
2638 aLine.append(" 0 obj\n<</Length ");
2639 aLine.append(aContents.getLength());
2640 aLine.append(">>\nstream\n");
2641 if (!writeBuffer(aLine.getStr(), aLine.getLength()))
2642 return false;
2643 if (!writeBuffer(aContents.getStr(), aContents.getLength()))
2644 return false;
2645 aLine.setLength(0);
2646 aLine.append("endstream\nendobj\n\n");
2647 if (!writeBuffer(aLine.getStr(), aLine.getLength()))
2648 return false;
2649 }
2650
2651 // write font dict
2652 auto nFontDict = createObject();
2653 aLine.setLength(0);
2654 aLine.append(nFontDict);
2655 aLine.append(" 0 obj\n<<");
2656 for (auto nFontID : aUsedFonts)
2657 {
2658 aLine.append("/F");
2659 aLine.append(nFontID);
2660 aLine.append(" ");
2661 aLine.append(rFontIDToObject[nFontID]);
2662 aLine.append(" 0 R");
2663 }
2664 aLine.append(">>\nendobj\n\n");
2665 if (!updateObject(nFontDict))
2666 return false;
2667 if (!writeBuffer(aLine.getStr(), aLine.getLength()))
2668 return false;
2669
2670 // write ExtGState objects
2671 if (!aUsedAlpha.empty())
2672 {
2673 for (const auto & [ nAlpha, nObject ] : aUsedAlpha)
2674 {
2675 aLine.setLength(0);
2676 aLine.append(nObject);
2677 aLine.append(" 0 obj\n<<");
2678 if (m_bIsPDF_A1)
2679 {
2680 aLine.append("/CA 1.0/ca 1.0");
2682 }
2683 else
2684 {
2685 aLine.append("/CA ");
2686 appendDouble(nAlpha / 255., aLine);
2687 aLine.append("/ca ");
2688 appendDouble(nAlpha / 255., aLine);
2689 }
2690 aLine.append(">>\nendobj\n\n");
2691 if (!updateObject(nObject))
2692 return false;
2693 if (!writeBuffer(aLine.getStr(), aLine.getLength()))
2694 return false;
2695 }
2696 }
2697
2698 // write bitmap objects
2699 for (auto& aBitmap : aUsedBitmaps)
2700 writeBitmapObject(aBitmap);
2701
2702 // write resources dict
2703 aLine.setLength(0);
2704 aLine.append(nResources);
2705 aLine.append(" 0 obj\n");
2706 aResourceDict.append(aLine, nFontDict);
2707 aLine.append("endobj\n\n");
2708 if (!updateObject(nResources))
2709 return false;
2710 if (!writeBuffer(aLine.getStr(), aLine.getLength()))
2711 return false;
2712
2713 rFontIDToObject[rSubset.m_nFontID] = nFontObject;
2714 }
2715
2716 return true;
2717}
2718
2719typedef int ThreeInts[3];
2720static bool getPfbSegmentLengths( const unsigned char* pFontBytes, int nByteLen,
2721 ThreeInts& rSegmentLengths )
2722{
2723 if( !pFontBytes || (nByteLen < 0) )
2724 return false;
2725 const unsigned char* pPtr = pFontBytes;
2726 const unsigned char* pEnd = pFontBytes + nByteLen;
2727
2728 for(int & rSegmentLength : rSegmentLengths) {
2729 // read segment1 header
2730 if( pPtr+6 >= pEnd )
2731 return false;
2732 if( (pPtr[0] != 0x80) || (pPtr[1] >= 0x03) )
2733 return false;
2734 const int nLen = (pPtr[5]<<24) + (pPtr[4]<<16) + (pPtr[3]<<8) + pPtr[2];
2735 if( nLen <= 0)
2736 return false;
2737 rSegmentLength = nLen;
2738 pPtr += nLen + 6;
2739 }
2740
2741 // read segment-end header
2742 if( pPtr+2 >= pEnd )
2743 return false;
2744 if( (pPtr[0] != 0x80) || (pPtr[1] != 0x03) )
2745 return false;
2746
2747 return true;
2748}
2749
2750static void appendSubsetName( int nSubsetID, std::u16string_view rPSName, OStringBuffer& rBuffer )
2751{
2752 if( nSubsetID )
2753 {
2754 for( int i = 0; i < 6; i++ )
2755 {
2756 int nOffset = nSubsetID % 26;
2757 nSubsetID /= 26;
2758 rBuffer.append( static_cast<char>('A'+nOffset) );
2759 }
2760 rBuffer.append( '+' );
2761 }
2762 appendName( rPSName, rBuffer );
2763}
2764
2766 const sal_Ucs* pCodeUnits,
2767 const sal_Int32* pCodeUnitsPerGlyph,
2768 const sal_Int32* pEncToUnicodeIndex,
2769 uint32_t nGlyphs )
2770{
2771 int nMapped = 0;
2772 for (auto n = 0u; n < nGlyphs; ++n)
2773 if( pCodeUnits[pEncToUnicodeIndex[n]] && pCodeUnitsPerGlyph[n] )
2774 nMapped++;
2775
2776 if( nMapped == 0 )
2777 return 0;
2778
2779 sal_Int32 nStream = createObject();
2780 CHECK_RETURN( updateObject( nStream ) );
2781
2782 OStringBuffer aContents( 1024 );
2783 aContents.append(
2784 "/CIDInit/ProcSet findresource begin\n"
2785 "12 dict begin\n"
2786 "begincmap\n"
2787 "/CIDSystemInfo<<\n"
2788 "/Registry (Adobe)\n"
2789 "/Ordering (UCS)\n"
2790 "/Supplement 0\n"
2791 ">> def\n"
2792 "/CMapName/Adobe-Identity-UCS def\n"
2793 "/CMapType 2 def\n"
2794 "1 begincodespacerange\n"
2795 "<00> <FF>\n"
2796 "endcodespacerange\n"
2797 );
2798 int nCount = 0;
2799 for (auto n = 0u; n < nGlyphs; ++n)
2800 {
2801 if( pCodeUnits[pEncToUnicodeIndex[n]] && pCodeUnitsPerGlyph[n] )
2802 {
2803 if( (nCount % 100) == 0 )
2804 {
2805 if( nCount )
2806 aContents.append( "endbfchar\n" );
2807 aContents.append( static_cast<sal_Int32>(std::min(nMapped-nCount, 100)) );
2808 aContents.append( " beginbfchar\n" );
2809 }
2810 aContents.append( '<' );
2811 appendHex( static_cast<sal_Int8>(pEncoding[n]), aContents );
2812 aContents.append( "> <" );
2813 // TODO: handle code points>U+FFFF
2814 sal_Int32 nIndex = pEncToUnicodeIndex[n];
2815 for( sal_Int32 j = 0; j < pCodeUnitsPerGlyph[n]; j++ )
2816 {
2817 appendHex( static_cast<sal_Int8>(pCodeUnits[nIndex + j] / 256), aContents );
2818 appendHex( static_cast<sal_Int8>(pCodeUnits[nIndex + j] & 255), aContents );
2819 }
2820 aContents.append( ">\n" );
2821 nCount++;
2822 }
2823 }
2824 aContents.append( "endbfchar\n"
2825 "endcmap\n"
2826 "CMapName currentdict /CMap defineresource pop\n"
2827 "end\n"
2828 "end\n" );
2829 SvMemoryStream aStream;
2831 {
2832 ZCodec aCodec( 0x4000, 0x4000 );
2833 aCodec.BeginCompression();
2834 aCodec.Write( aStream, reinterpret_cast<const sal_uInt8*>(aContents.getStr()), aContents.getLength() );
2835 aCodec.EndCompression();
2836 }
2837
2839 {
2840 emitComment( "PDFWriterImpl::createToUnicodeCMap" );
2841 }
2842 OStringBuffer aLine( 40 );
2843
2844 aLine.append( nStream );
2845 aLine.append( " 0 obj\n<</Length " );
2846 sal_Int32 nLen = 0;
2848 {
2849 nLen = static_cast<sal_Int32>(aStream.Tell());
2850 aStream.Seek( 0 );
2851 aLine.append( nLen );
2852 aLine.append( "/Filter/FlateDecode" );
2853 }
2854 else
2855 aLine.append( aContents.getLength() );
2856 aLine.append( ">>\nstream\n" );
2857 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2860 {
2861 CHECK_RETURN( writeBuffer( aStream.GetData(), nLen ) );
2862 }
2863 else
2864 {
2865 CHECK_RETURN( writeBuffer( aContents.getStr(), aContents.getLength() ) );
2866 }
2868 aLine.setLength( 0 );
2869 aLine.append( "\nendstream\n"
2870 "endobj\n\n" );
2871 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2872 return nStream;
2873}
2874
2875sal_Int32 PDFWriterImpl::emitFontDescriptor( const vcl::font::PhysicalFontFace* pFont, FontSubsetInfo const & rInfo, sal_Int32 nSubsetID, sal_Int32 nFontStream )
2876{
2877 OStringBuffer aLine( 1024 );
2878 // get font flags, see PDF reference 1.4 p. 358
2879 // possibly characters outside Adobe standard encoding
2880 // so set Symbolic flag
2881 sal_Int32 nFontFlags = (1<<2);
2882 if( pFont->GetItalic() == ITALIC_NORMAL || pFont->GetItalic() == ITALIC_OBLIQUE )
2883 nFontFlags |= (1 << 6);
2884 if( pFont->GetPitch() == PITCH_FIXED )
2885 nFontFlags |= 1;
2886 if( pFont->GetFamilyType() == FAMILY_SCRIPT )
2887 nFontFlags |= (1 << 3);
2888 else if( pFont->GetFamilyType() == FAMILY_ROMAN )
2889 nFontFlags |= (1 << 1);
2890
2891 sal_Int32 nFontDescriptor = createObject();
2892 CHECK_RETURN( updateObject( nFontDescriptor ) );
2893 aLine.setLength( 0 );
2894 aLine.append( nFontDescriptor );
2895 aLine.append( " 0 obj\n"
2896 "<</Type/FontDescriptor/FontName/" );
2897 appendSubsetName( nSubsetID, rInfo.m_aPSName, aLine );
2898 aLine.append( "\n"
2899 "/Flags " );
2900 aLine.append( nFontFlags );
2901 aLine.append( "\n"
2902 "/FontBBox[" );
2903 // note: Top and Bottom are reversed in VCL and PDF rectangles
2904 aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.Left()) );
2905 aLine.append( ' ' );
2906 aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.Top()) );
2907 aLine.append( ' ' );
2908 aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.Right()) );
2909 aLine.append( ' ' );
2910 aLine.append( static_cast<sal_Int32>(rInfo.m_aFontBBox.Bottom()+1) );
2911 aLine.append( "]/ItalicAngle " );
2912 if( pFont->GetItalic() == ITALIC_OBLIQUE || pFont->GetItalic() == ITALIC_NORMAL )
2913 aLine.append( "-30" );
2914 else
2915 aLine.append( "0" );
2916 aLine.append( "\n"
2917 "/Ascent " );
2918 aLine.append( static_cast<sal_Int32>(rInfo.m_nAscent) );
2919 aLine.append( "\n"
2920 "/Descent " );
2921 aLine.append( static_cast<sal_Int32>(-rInfo.m_nDescent) );
2922 aLine.append( "\n"
2923 "/CapHeight " );
2924 aLine.append( static_cast<sal_Int32>(rInfo.m_nCapHeight) );
2925 // According to PDF reference 1.4 StemV is required
2926 // seems a tad strange to me, but well ...
2927 aLine.append( "\n"
2928 "/StemV 80\n" );
2929 if( nFontStream )
2930 {
2931 aLine.append( "/FontFile" );
2932 switch( rInfo.m_nFontType )
2933 {
2934 case FontType::SFNT_TTF:
2935 aLine.append( '2' );
2936 break;
2940 break;
2941 default:
2942 OSL_FAIL( "unknown fonttype in PDF font descriptor" );
2943 return 0;
2944 }
2945 aLine.append( ' ' );
2946 aLine.append( nFontStream );
2947 aLine.append( " 0 R\n" );
2948 }
2949 aLine.append( ">>\n"
2950 "endobj\n\n" );
2951 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
2952
2953 return nFontDescriptor;
2954}
2955
2956void PDFWriterImpl::appendBuildinFontsToDict( OStringBuffer& rDict ) const
2957{
2958 for (auto const& item : m_aBuildinFontToObjectMap)
2959 {
2960 rDict.append( pdf::BuildinFontFace::Get(item.first).getNameObject() );
2961 rDict.append( ' ' );
2962 rDict.append( item.second );
2963 rDict.append( " 0 R" );
2964 }
2965}
2966
2968{
2969 OStringBuffer aLine( 1024 );
2970
2971 std::map< sal_Int32, sal_Int32 > aFontIDToObject;
2972
2973 for (const auto & subset : m_aSubsets)
2974 {
2975 for (auto & s_subset :subset.second.m_aSubsets)
2976 {
2977 sal_GlyphId pGlyphIds[ 256 ] = {};
2978 sal_Int32 pWidths[ 256 ];
2979 sal_uInt8 pEncoding[ 256 ] = {};
2980 sal_Int32 pEncToUnicodeIndex[ 256 ] = {};
2981 sal_Int32 pCodeUnitsPerGlyph[ 256 ] = {};
2982 std::vector<sal_Ucs> aCodeUnits;
2983 sal_Int32 nToUnicodeStream = 0;
2984
2985 // fill arrays and prepare encoding index map
2986 auto nGlyphs = fillSubsetArrays(s_subset, pGlyphIds, pWidths, pEncoding, pEncToUnicodeIndex,
2987 pCodeUnitsPerGlyph, aCodeUnits, nToUnicodeStream);
2988
2989 std::vector<sal_uInt8> aBuffer;
2990 FontSubsetInfo aSubsetInfo;
2991 const auto* pFace = subset.first;
2992 if (pFace->CreateFontSubset(aBuffer, pGlyphIds, pEncoding, nGlyphs, aSubsetInfo))
2993 {
2994 // create font stream
2996 {
2997 emitComment( "PDFWriterImpl::emitFonts" );
2998 }
2999 sal_Int32 nFontStream = createObject();
3000 sal_Int32 nStreamLengthObject = createObject();
3001 if ( !updateObject( nFontStream ) ) return false;
3002 aLine.setLength( 0 );
3003 aLine.append( nFontStream );
3004 aLine.append( " 0 obj\n"
3005 "<</Length " );
3006 aLine.append( nStreamLengthObject );
3008 aLine.append( " 0 R"
3009 "/Filter/FlateDecode"
3010 "/Length1 " );
3011 else
3012 aLine.append( " 0 R"
3013 "/Length1 " );
3014
3015 sal_uInt64 nStartPos = 0;
3016 if( aSubsetInfo.m_nFontType == FontType::SFNT_TTF )
3017 {
3018 aLine.append( static_cast<sal_Int32>(aBuffer.size()) );
3019
3020 aLine.append( ">>\n"
3021 "stream\n" );
3022 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
3023 if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
3024
3025 // copy font file
3027 checkAndEnableStreamEncryption( nFontStream );
3028 if (!writeBuffer(aBuffer.data(), aBuffer.size()))
3029 return false;
3030 }
3031 else if( aSubsetInfo.m_nFontType & FontType::CFF_FONT)
3032 {
3033 // TODO: implement
3034 OSL_FAIL( "PDFWriterImpl does not support CFF-font subsets yet!" );
3035 }
3036 else if( aSubsetInfo.m_nFontType & FontType::TYPE1_PFB) // TODO: also support PFA?
3037 {
3038 // get the PFB-segment lengths
3039 ThreeInts aSegmentLengths = {0,0,0};
3040 getPfbSegmentLengths(aBuffer.data(), static_cast<int>(aBuffer.size()), aSegmentLengths);
3041 // the lengths below are mandatory for PDF-exported Type1 fonts
3042 // because the PFB segment headers get stripped! WhyOhWhy.
3043 aLine.append( static_cast<sal_Int32>(aSegmentLengths[0]) );
3044 aLine.append( "/Length2 " );
3045 aLine.append( static_cast<sal_Int32>(aSegmentLengths[1]) );
3046 aLine.append( "/Length3 " );
3047 aLine.append( static_cast<sal_Int32>(aSegmentLengths[2]) );
3048
3049 aLine.append( ">>\n"
3050 "stream\n" );
3051 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
3052 if ( osl::File::E_None != m_aFile.getPos(nStartPos) ) return false;
3053
3054 // emit PFB-sections without section headers
3056 checkAndEnableStreamEncryption( nFontStream );
3057 if ( !writeBuffer( &aBuffer[6], aSegmentLengths[0] ) ) return false;
3058 if ( !writeBuffer( &aBuffer[12] + aSegmentLengths[0], aSegmentLengths[1] ) ) return false;
3059 if ( !writeBuffer( &aBuffer[18] + aSegmentLengths[0] + aSegmentLengths[1], aSegmentLengths[2] ) ) return false;
3060 }
3061 else
3062 {
3063 SAL_INFO("vcl.pdfwriter", "PDF: CreateFontSubset result in not yet supported format=" << static_cast<int>(aSubsetInfo.m_nFontType));
3064 aLine.append( "0 >>\nstream\n" );
3065 }
3066
3069
3070 sal_uInt64 nEndPos = 0;
3071 if ( osl::File::E_None != m_aFile.getPos(nEndPos) ) return false;
3072 // end the stream
3073 aLine.setLength( 0 );
3074 aLine.append( "\nendstream\nendobj\n\n" );
3075 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
3076
3077 // emit stream length object
3078 if ( !updateObject( nStreamLengthObject ) ) return false;
3079 aLine.setLength( 0 );
3080 aLine.append( nStreamLengthObject );
3081 aLine.append( " 0 obj\n" );
3082 aLine.append( static_cast<sal_Int64>(nEndPos-nStartPos) );
3083 aLine.append( "\nendobj\n\n" );
3084 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
3085
3086 // write font descriptor
3087 sal_Int32 nFontDescriptor = emitFontDescriptor( subset.first, aSubsetInfo, s_subset.m_nFontID, nFontStream );
3088
3089 if( nToUnicodeStream )
3090 nToUnicodeStream = createToUnicodeCMap( pEncoding, aCodeUnits.data(), pCodeUnitsPerGlyph, pEncToUnicodeIndex, nGlyphs );
3091
3092 sal_Int32 nFontObject = createObject();
3093 if ( !updateObject( nFontObject ) ) return false;
3094 aLine.setLength( 0 );
3095 aLine.append( nFontObject );
3096
3097 aLine.append( " 0 obj\n" );
3098 aLine.append( (aSubsetInfo.m_nFontType & FontType::ANY_TYPE1) ?
3099 "<</Type/Font/Subtype/Type1/BaseFont/" :
3100 "<</Type/Font/Subtype/TrueType/BaseFont/" );
3101 appendSubsetName( s_subset.m_nFontID, aSubsetInfo.m_aPSName, aLine );
3102 aLine.append( "\n"
3103 "/FirstChar 0\n"
3104 "/LastChar " );
3105 aLine.append( static_cast<sal_Int32>(nGlyphs-1) );
3106 aLine.append( "\n"
3107 "/Widths[" );
3108 for (auto i = 0u; i < nGlyphs; i++)
3109 {
3110 aLine.append( pWidths[ i ] );
3111 aLine.append( ((i & 15) == 15) ? "\n" : " " );
3112 }
3113 aLine.append( "]\n"
3114 "/FontDescriptor " );
3115 aLine.append( nFontDescriptor );
3116 aLine.append( " 0 R\n" );
3117 if( nToUnicodeStream )
3118 {
3119 aLine.append( "/ToUnicode " );
3120 aLine.append( nToUnicodeStream );
3121 aLine.append( " 0 R\n" );
3122 }
3123 aLine.append( ">>\n"
3124 "endobj\n\n" );
3125 if ( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) return false;
3126
3127 aFontIDToObject[ s_subset.m_nFontID ] = nFontObject;
3128 }
3129 else
3130 {
3131 OStringBuffer aErrorComment( 256 );
3132 aErrorComment.append( "CreateFontSubset failed for font \"" );
3133 aErrorComment.append( OUStringToOString( pFace->GetFamilyName(), RTL_TEXTENCODING_UTF8 ) );
3134 aErrorComment.append( '\"' );
3135 if( pFace->GetItalic() == ITALIC_NORMAL )
3136 aErrorComment.append( " italic" );
3137 else if( pFace->GetItalic() == ITALIC_OBLIQUE )
3138 aErrorComment.append( " oblique" );
3139 aErrorComment.append( " weight=" );
3140 aErrorComment.append( sal_Int32(pFace->GetWeight()) );
3141 emitComment( aErrorComment.getStr() );
3142 }
3143 }
3144 }
3145
3146 // emit system fonts
3147 for (auto const& systemFont : m_aSystemFonts)
3148 {
3149 std::map< sal_Int32, sal_Int32 > aObjects = emitSystemFont( systemFont.first, systemFont.second );
3150 for (auto const& item : aObjects)
3151 {
3152 if ( !item.second ) return false;
3153 aFontIDToObject[ item.first ] = item.second;
3154 }
3155 }
3156
3157 // emit Type3 fonts
3158 for (auto const& it : m_aType3Fonts)
3159 {
3160 if (!emitType3Font(it.first, it.second, aFontIDToObject))
3161 return false;
3162 }
3163
3164 OStringBuffer aFontDict( 1024 );
3165 aFontDict.append( getFontDictObject() );
3166 aFontDict.append( " 0 obj\n"
3167 "<<" );
3168 int ni = 0;
3169 for (auto const& itemMap : aFontIDToObject)
3170 {
3171 aFontDict.append( "/F" );
3172 aFontDict.append( itemMap.first );
3173 aFontDict.append( ' ' );
3174 aFontDict.append( itemMap.second );
3175 aFontDict.append( " 0 R" );
3176 if( ((++ni) & 7) == 0 )
3177 aFontDict.append( '\n' );
3178 }
3179 // emit builtin font for widget appearances / variable text
3180 for (auto & item : m_aBuildinFontToObjectMap)
3181 {
3183 item.second = emitBuildinFont( aData.get(), item.second );
3184 }
3185
3186 appendBuildinFontsToDict(aFontDict);
3187 aFontDict.append( "\n>>\nendobj\n\n" );
3188
3189 if ( !updateObject( getFontDictObject() ) ) return false;
3190 if ( !writeBuffer( aFontDict.getStr(), aFontDict.getLength() ) ) return false;
3191 return true;
3192}
3193
3195{
3196 // emit shadings
3197 if( ! m_aGradients.empty() )
3199 // emit tilings
3200 if( ! m_aTilings.empty() )
3202
3203 // emit font dict
3205
3206 // emit Resource dict
3207 OStringBuffer aLine( 512 );
3208 sal_Int32 nResourceDict = getResourceDictObj();
3209 CHECK_RETURN( updateObject( nResourceDict ) );
3210 aLine.setLength( 0 );
3211 aLine.append( nResourceDict );
3212 aLine.append( " 0 obj\n" );
3214 aLine.append( "endobj\n\n" );
3215 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3216 return nResourceDict;
3217}
3218
3219sal_Int32 PDFWriterImpl::updateOutlineItemCount( std::vector< sal_Int32 >& rCounts,
3220 sal_Int32 nItemLevel,
3221 sal_Int32 nCurrentItemId )
3222{
3223 /* The /Count number of an item is
3224 positive: the number of visible subitems
3225 negative: the negative number of subitems that will become visible if
3226 the item gets opened
3227 see PDF ref 1.4 p 478
3228 */
3229
3230 sal_Int32 nCount = 0;
3231
3232 if( m_aContext.OpenBookmarkLevels < 0 || // all levels are visible
3233 m_aContext.OpenBookmarkLevels >= nItemLevel // this level is visible
3234 )
3235 {
3236 PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
3237 sal_Int32 nChildren = rItem.m_aChildren.size();
3238 for( sal_Int32 i = 0; i < nChildren; i++ )
3239 nCount += updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
3240 rCounts[nCurrentItemId] = nCount;
3241 // return 1 (this item) + visible sub items
3242 if( nCount < 0 )
3243 nCount = 0;
3244 nCount++;
3245 }
3246 else
3247 {
3248 // this bookmark level is invisible
3249 PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ];
3250 sal_Int32 nChildren = rItem.m_aChildren.size();
3251 rCounts[ nCurrentItemId ] = -sal_Int32(rItem.m_aChildren.size());
3252 for( sal_Int32 i = 0; i < nChildren; i++ )
3253 updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] );
3254 nCount = -1;
3255 }
3256
3257 return nCount;
3258}
3259
3261{
3262 int i, nItems = m_aOutline.size();
3263
3264 // do we have an outline at all ?
3265 if( nItems < 2 )
3266 return 0;
3267
3268 // reserve object numbers for all outline items
3269 for( i = 0; i < nItems; ++i )
3270 m_aOutline[i].m_nObject = createObject();
3271
3272 // update all parent, next and prev object ids
3273 for( i = 0; i < nItems; ++i )
3274 {
3275 PDFOutlineEntry& rItem = m_aOutline[i];
3276 int nChildren = rItem.m_aChildren.size();
3277
3278 if( nChildren )
3279 {
3280 for( int n = 0; n < nChildren; ++n )
3281 {
3282 PDFOutlineEntry& rChild = m_aOutline[ rItem.m_aChildren[n] ];
3283
3284 rChild.m_nParentObject = rItem.m_nObject;
3285 rChild.m_nPrevObject = (n > 0) ? m_aOutline[ rItem.m_aChildren[n-1] ].m_nObject : 0;
3286 rChild.m_nNextObject = (n < nChildren-1) ? m_aOutline[ rItem.m_aChildren[n+1] ].m_nObject : 0;
3287 }
3288
3289 }
3290 }
3291
3292 // calculate Count entries for all items
3293 std::vector< sal_Int32 > aCounts( nItems );
3294 updateOutlineItemCount( aCounts, 0, 0 );
3295
3296 // emit hierarchy
3297 for( i = 0; i < nItems; ++i )
3298 {
3299 PDFOutlineEntry& rItem = m_aOutline[i];
3300 OStringBuffer aLine( 1024 );
3301
3303 aLine.append( rItem.m_nObject );
3304 aLine.append( " 0 obj\n" );
3305 aLine.append( "<<" );
3306 // number of visible children (all levels)
3307 if( i > 0 || aCounts[0] > 0 )
3308 {
3309 aLine.append( "/Count " );
3310 aLine.append( aCounts[i] );
3311 }
3312 if( ! rItem.m_aChildren.empty() )
3313 {
3314 // children list: First, Last
3315 aLine.append( "/First " );
3316 aLine.append( m_aOutline[rItem.m_aChildren.front()].m_nObject );
3317 aLine.append( " 0 R/Last " );
3318 aLine.append( m_aOutline[rItem.m_aChildren.back()].m_nObject );
3319 aLine.append( " 0 R\n" );
3320 }
3321 if( i > 0 )
3322 {
3323 // Title, Dest, Parent, Prev, Next
3324 aLine.append( "/Title" );
3325 appendUnicodeTextStringEncrypt( rItem.m_aTitle, rItem.m_nObject, aLine );
3326 aLine.append( "\n" );
3327 // Dest is not required
3328 if( rItem.m_nDestID >= 0 && o3tl::make_unsigned(rItem.m_nDestID) < m_aDests.size() )
3329 {
3330 aLine.append( "/Dest" );
3331 appendDest( rItem.m_nDestID, aLine );
3332 }
3333 aLine.append( "/Parent " );
3334 aLine.append( rItem.m_nParentObject );
3335 aLine.append( " 0 R" );
3336 if( rItem.m_nPrevObject )
3337 {
3338 aLine.append( "/Prev " );
3339 aLine.append( rItem.m_nPrevObject );
3340 aLine.append( " 0 R" );
3341 }
3342 if( rItem.m_nNextObject )
3343 {
3344 aLine.append( "/Next " );
3345 aLine.append( rItem.m_nNextObject );
3346 aLine.append( " 0 R" );
3347 }
3348 }
3349 aLine.append( ">>\nendobj\n\n" );
3350 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3351 }
3352
3353 return m_aOutline[0].m_nObject;
3354}
3355
3356#undef CHECK_RETURN
3357#define CHECK_RETURN( x ) if( !x ) return false
3358
3359bool PDFWriterImpl::appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer )
3360{
3361 if( nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size() )
3362 {
3363 SAL_INFO("vcl.pdfwriter", "ERROR: invalid dest " << static_cast<int>(nDestID) << " requested");
3364 return false;
3365 }
3366
3367 const PDFDest& rDest = m_aDests[ nDestID ];
3368 const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ];
3369
3370 rBuffer.append( '[' );
3371 rBuffer.append( rDestPage.m_nPageObject );
3372 rBuffer.append( " 0 R" );
3373
3374 switch( rDest.m_eType )
3375 {
3377 default:
3378 rBuffer.append( "/XYZ " );
3379 appendFixedInt( rDest.m_aRect.Left(), rBuffer );
3380 rBuffer.append( ' ' );
3381 appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
3382 rBuffer.append( " 0" );
3383 break;
3385 rBuffer.append( "/FitR " );
3386 appendFixedInt( rDest.m_aRect.Left(), rBuffer );
3387 rBuffer.append( ' ' );
3388 appendFixedInt( rDest.m_aRect.Top(), rBuffer );
3389 rBuffer.append( ' ' );
3390 appendFixedInt( rDest.m_aRect.Right(), rBuffer );
3391 rBuffer.append( ' ' );
3392 appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
3393 break;
3394 }
3395 rBuffer.append( ']' );
3396
3397 return true;
3398}
3399
3401{
3402 int nAnnots = m_aScreens.size();
3403 for (int i = 0; i < nAnnots; i++)
3404 {
3405 const PDFScreen& rScreen = m_aScreens[i];
3406
3407 OStringBuffer aLine;
3408 bool bEmbed = false;
3409 if (!rScreen.m_aTempFileURL.isEmpty())
3410 {
3411 bEmbed = true;
3412 if (!updateObject(rScreen.m_nTempFileObject))
3413 continue;
3414
3415 SvFileStream aFileStream(rScreen.m_aTempFileURL, StreamMode::READ);
3416 SvMemoryStream aMemoryStream;
3417 aMemoryStream.WriteStream(aFileStream);
3418
3419 aLine.append(rScreen.m_nTempFileObject);
3420 aLine.append(" 0 obj\n");
3421 aLine.append("<< /Type /EmbeddedFile /Length ");
3422 aLine.append(static_cast<sal_Int64>(aMemoryStream.GetSize()));
3423 aLine.append(" >>\nstream\n");
3424 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
3425 aLine.setLength(0);
3426
3427 CHECK_RETURN(writeBuffer(aMemoryStream.GetData(), aMemoryStream.GetSize()));
3428
3429 aLine.append("\nendstream\nendobj\n\n");
3430 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
3431 aLine.setLength(0);
3432 }
3433
3434 if (!updateObject(rScreen.m_nObject))
3435 continue;
3436
3437 // Annot dictionary.
3438 aLine.append(rScreen.m_nObject);
3439 aLine.append(" 0 obj\n");
3440 aLine.append("<</Type/Annot");
3441 aLine.append("/Subtype/Screen/Rect[");
3442 appendFixedInt(rScreen.m_aRect.Left(), aLine);
3443 aLine.append(' ');
3444 appendFixedInt(rScreen.m_aRect.Top(), aLine);
3445 aLine.append(' ');
3446 appendFixedInt(rScreen.m_aRect.Right(), aLine);
3447 aLine.append(' ');
3448 appendFixedInt(rScreen.m_aRect.Bottom(), aLine);
3449 aLine.append("]");
3450
3451 // Action dictionary.
3452 aLine.append("/A<</Type/Action /S/Rendition /AN ");
3453 aLine.append(rScreen.m_nObject);
3454 aLine.append(" 0 R ");
3455
3456 // Rendition dictionary.
3457 aLine.append("/R<</Type/Rendition /S/MR ");
3458
3459 // MediaClip dictionary.
3460 aLine.append("/C<</Type/MediaClip /S/MCD ");
3461 if (bEmbed)
3462 {
3463 aLine.append("/D << /Type /Filespec /F (<embedded file>) /EF << /F ");
3464 aLine.append(rScreen.m_nTempFileObject);
3465 aLine.append(" 0 R >> >>");
3466 }
3467 else
3468 {
3469 // Linked.
3470 aLine.append("/D << /Type /Filespec /FS /URL /F ");
3471 appendLiteralStringEncrypt(rScreen.m_aURL, rScreen.m_nObject, aLine, osl_getThreadTextEncoding());
3472 aLine.append(" >>");
3473 }
3474 // Allow playing the video via a tempfile.
3475 aLine.append("/P <</TF (TEMPACCESS)>>");
3476 // Until the real MIME type (instead of application/vnd.sun.star.media) is available here.
3477 aLine.append("/CT (video/mp4)");
3478 aLine.append(">>");
3479
3480 // End Rendition dictionary by requesting play/pause/stop controls.
3481 aLine.append("/P<</BE<</C true >>>>");
3482 aLine.append(">>");
3483
3484 // End Action dictionary.
3485 aLine.append("/OP 0 >>");
3486
3487 // End Annot dictionary.
3488 aLine.append("/P ");
3489 aLine.append(m_aPages[rScreen.m_nPage].m_nPageObject);
3490 aLine.append(" 0 R\n>>\nendobj\n\n");
3491 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
3492 }
3493
3494 return true;
3495}
3496
3498{
3499 MARK("PDFWriterImpl::emitLinkAnnotations");
3500 int nAnnots = m_aLinks.size();
3501 for( int i = 0; i < nAnnots; i++ )
3502 {
3503 const PDFLink& rLink = m_aLinks[i];
3504 if( ! updateObject( rLink.m_nObject ) )
3505 continue;
3506
3507 OStringBuffer aLine( 1024 );
3508 aLine.append( rLink.m_nObject );
3509 aLine.append( " 0 obj\n" );
3510// i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
3511// see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
3512 aLine.append( "<</Type/Annot" );
3514 aLine.append( "/F 4" );
3515 aLine.append( "/Subtype/Link/Border[0 0 0]/Rect[" );
3516
3517 appendFixedInt( rLink.m_aRect.Left()-7, aLine );//the +7 to have a better shape of the border rectangle
3518 aLine.append( ' ' );
3519 appendFixedInt( rLink.m_aRect.Top(), aLine );
3520 aLine.append( ' ' );
3521 appendFixedInt( rLink.m_aRect.Right()+7, aLine );//the +7 to have a better shape of the border rectangle
3522 aLine.append( ' ' );
3523 appendFixedInt( rLink.m_aRect.Bottom(), aLine );
3524 aLine.append( "]" );
3525 if( rLink.m_nDest >= 0 )
3526 {
3527 aLine.append( "/Dest" );
3528 appendDest( rLink.m_nDest, aLine );
3529 }
3530 else
3531 {
3532/*
3533destination is external to the document, so
3534we check in the following sequence:
3535
3536 if target type is neither .pdf, nor .od[tpgs], then
3537 check if relative or absolute and act accordingly (use URI or 'launch application' as requested)
3538 end processing
3539 else if target is .od[tpgs]: then
3540 if conversion of type from od[tpgs] to pdf is requested, convert it and this becomes the new target file
3541 processing continue
3542
3543 if (new)target is .pdf : then
3544 if GotToR is requested, then
3545 convert the target in GoToR where the fragment of the URI is
3546 considered the named destination in the target file, set relative or absolute as requested
3547 else strip the fragment from URL and then set URI or 'launch application' as requested
3548*/
3549
3550// FIXME: check if the decode mechanisms for URL processing throughout this implementation
3551// are the correct one!!
3552
3553// extract target file type
3554 auto url(URIHelper::resolveIdnaHost(rLink.m_aURL));
3555
3556 INetURLObject aDocumentURL( m_aContext.BaseURL );
3558 bool bSetGoToRMode = false;
3559 bool bTargetHasPDFExtension = false;
3560 INetProtocol eTargetProtocol = aTargetURL.GetProtocol();
3561 bool bIsUNCPath = false;
3562 bool bUnparsedURI = false;
3563
3564 // check if the protocol is a known one, or if there is no protocol at all (on target only)
3565 // if there is no protocol, make the target relative to the current document directory
3566 // getting the needed URL information from the current document path
3567 if( eTargetProtocol == INetProtocol::NotValid )
3568 {
3569 if( url.getLength() > 4 && url.startsWith("\\\\\\\\"))
3570 {
3571 bIsUNCPath = true;
3572 }
3573 else
3574 {
3575 INetURLObject aNewURL(rtl::Uri::convertRelToAbs(m_aContext.BaseURL, url));
3576 aTargetURL = aNewURL; //reassign the new target URL
3577
3578 //recompute the target protocol, with the new URL
3579 //normal URL processing resumes
3580 eTargetProtocol = aTargetURL.GetProtocol();
3581
3582 bUnparsedURI = eTargetProtocol == INetProtocol::NotValid;
3583 }
3584 }
3585
3586 OUString aFileExtension = aTargetURL.GetFileExtension();
3587
3588 // Check if the URL ends in '/': if yes it's a directory,
3589 // it will be forced to a URI link.
3590 // possibly a malformed URI, leave it as it is, force as URI
3591 if( aTargetURL.hasFinalSlash() )
3593
3594 if( !aFileExtension.isEmpty() )
3595 {
3597 {
3598 bool bChangeFileExtensionToPDF = false;
3599 //examine the file type (.odm .odt. .odp, odg, ods)
3600 if( aFileExtension.equalsIgnoreAsciiCase( "odm" ) )
3601 bChangeFileExtensionToPDF = true;
3602 if( aFileExtension.equalsIgnoreAsciiCase( "odt" ) )
3603 bChangeFileExtensionToPDF = true;
3604 else if( aFileExtension.equalsIgnoreAsciiCase( "odp" ) )
3605 bChangeFileExtensionToPDF = true;
3606 else if( aFileExtension.equalsIgnoreAsciiCase( "odg" ) )
3607 bChangeFileExtensionToPDF = true;
3608 else if( aFileExtension.equalsIgnoreAsciiCase( "ods" ) )
3609 bChangeFileExtensionToPDF = true;
3610 if( bChangeFileExtensionToPDF )
3611 aTargetURL.setExtension(u"pdf" );
3612 }
3613 //check if extension is pdf, see if GoToR should be forced
3614 bTargetHasPDFExtension = aTargetURL.GetFileExtension().equalsIgnoreAsciiCase( "pdf" );
3615 if( m_aContext.ForcePDFAction && bTargetHasPDFExtension )
3616 bSetGoToRMode = true;
3617 }
3618 //prepare the URL, if relative or not
3619 INetProtocol eBaseProtocol = aDocumentURL.GetProtocol();
3620 //queue the string common to all types of actions
3621 aLine.append( "/A<</Type/Action/S");
3622 if( bIsUNCPath ) // handle Win UNC paths
3623 {
3624 aLine.append( "/Launch/Win<</F" );
3625 // INetURLObject is not good with UNC paths, use original path
3626 appendLiteralStringEncrypt( url, rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
3627 aLine.append( ">>" );
3628 }
3629 else
3630 {
3631 bool bSetRelative = false;
3632 bool bFileSpec = false;
3633 //check if relative file link is requested and if the protocol is 'file://'
3634 if( m_aContext.RelFsys && eBaseProtocol == eTargetProtocol && eTargetProtocol == INetProtocol::File )
3635 bSetRelative = true;
3636
3637 OUString aFragment = aTargetURL.GetMark( INetURLObject::DecodeMechanism::NONE /*DecodeMechanism::WithCharset*/ ); //fragment as is,
3638 if( !bSetGoToRMode )
3639 {
3641 {
3642 default:
3645 aLine.append( "/URI/URI" );
3646 break;
3648 // now:
3649 // if a launch action is requested and the hyperlink target has a fragment
3650 // and the target file does not have a pdf extension, or it's not a 'file:://'
3651 // protocol then force the uri action on it
3652 // This code will permit the correct opening of application on web pages,
3653 // the one that normally have fragments (but I may be wrong...)
3654 // and will force the use of URI when the protocol is not file:
3655 if( (!aFragment.isEmpty() && !bTargetHasPDFExtension) ||
3656 eTargetProtocol != INetProtocol::File )
3657 {
3658 aLine.append( "/URI/URI" );
3659 }
3660 else
3661 {
3662 aLine.append( "/Launch/F" );
3663 bFileSpec = true;
3664 }
3665 break;
3666 }
3667 }
3668
3669 //fragment are encoded in the same way as in the named destination processing
3670 if( bSetGoToRMode )
3671 {
3672 //add the fragment
3673 OUString aURLNoMark = aTargetURL.GetURLNoMark( INetURLObject::DecodeMechanism::WithCharset );
3674 aLine.append("/GoToR");
3675 aLine.append("/F");
3679 aURLNoMark, rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
3680 if( !aFragment.isEmpty() )
3681 {
3682 aLine.append("/D/");
3683 appendDestinationName( aFragment , aLine );
3684 }
3685 }
3686 else
3687 {
3688 // change the fragment to accommodate the bookmark (only if the file extension
3689 // is PDF and the requested action is of the correct type)
3691 bTargetHasPDFExtension && !aFragment.isEmpty() )
3692 {
3693 OStringBuffer aLineLoc( 1024 );
3694 appendDestinationName( aFragment , aLineLoc );
3695 //substitute the fragment
3696 aTargetURL.SetMark( OStringToOUString(aLineLoc, RTL_TEXTENCODING_ASCII_US) );
3697 }
3698 OUString aURL = bUnparsedURI ? url :
3704 ) :
3705 aURL , rLink.m_nObject, aLine, osl_getThreadTextEncoding() );
3706 }
3707 }
3708 aLine.append( ">>\n" );
3709 }
3710 if( rLink.m_nStructParent > 0 )
3711 {
3712 aLine.append( "/StructParent " );
3713 aLine.append( rLink.m_nStructParent );
3714 }
3715 aLine.append( ">>\nendobj\n\n" );
3716 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
3717 }
3718
3719 return true;
3720}
3721
3722namespace
3723{
3724
3725void appendAnnotationRect(tools::Rectangle const & rRectangle, OStringBuffer & aLine)
3726{
3727 aLine.append("/Rect[");
3728 appendFixedInt(rRectangle.Left(), aLine);
3729 aLine.append(' ');
3730 appendFixedInt(rRectangle.Top(), aLine);
3731 aLine.append(' ');
3732 appendFixedInt(rRectangle.Right(), aLine);
3733 aLine.append(' ');
3734 appendFixedInt(rRectangle.Bottom(), aLine);
3735 aLine.append("] ");
3736}
3737
3738void appendObjectID(sal_Int32 nObjectID, OStringBuffer & aLine)
3739{
3740 aLine.append(nObjectID);
3741 aLine.append(" 0 obj\n");
3742}
3743
3744void appendObjectReference(sal_Int32 nObjectID, OStringBuffer & aLine)
3745{
3746 aLine.append(nObjectID);
3747 aLine.append(" 0 R ");
3748}
3749
3750} // end anonymous namespace
3751
3752void PDFWriterImpl::emitTextAnnotationLine(OStringBuffer & aLine, PDFNoteEntry const & rNote)
3753{
3754 appendObjectID(rNote.m_nObject, aLine);
3755
3756 aLine.append("<</Type /Annot /Subtype /Text ");
3757
3758// i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should'
3759// see PDF 8.4.2 and ISO 19005-1:2005 6.5.3
3761 aLine.append("/F 4 ");
3762
3763 appendAnnotationRect(rNote.m_aRect, aLine);
3764
3765 aLine.append("/Popup ");
3766 appendObjectReference(rNote.m_aPopUpAnnotation.m_nObject, aLine);
3767
3768 auto & rDateTime = rNote.m_aContents.maModificationDate;
3769
3770 aLine.append("/M (");
3771 appendPdfTimeDate(aLine, rDateTime.Year, rDateTime.Month, rDateTime.Day, rDateTime.Hours, rDateTime.Minutes, rDateTime.Seconds, 0);
3772 aLine.append(") ");
3773
3774 // contents of the note (type text string)
3775 aLine.append("/Contents ");
3777 aLine.append("\n");
3778
3779 // optional title
3780 if (!rNote.m_aContents.Title.isEmpty())
3781 {
3782 aLine.append("/T ");
3784 aLine.append("\n");
3785 }
3786 aLine.append(">>\n");
3787 aLine.append("endobj\n\n");
3788}
3789
3790void PDFWriterImpl::emitPopupAnnotationLine(OStringBuffer & aLine, PDFPopupAnnotation const & rPopUp)
3791{
3792 appendObjectID(rPopUp.m_nObject, aLine);
3793 aLine.append("<</Type /Annot /Subtype /Popup ");
3794 aLine.append("/Parent ");
3795 appendObjectReference(rPopUp.m_nParentObject, aLine);
3796 aLine.append(">>\n");
3797 aLine.append("endobj\n\n");
3798}
3799
3801{
3802 // emit note annotations
3803 int nAnnots = m_aNotes.size();
3804 for( int i = 0; i < nAnnots; i++ )
3805 {
3806 const PDFNoteEntry& rNote = m_aNotes[i];
3807 const PDFPopupAnnotation& rPopUp = rNote.m_aPopUpAnnotation;
3808
3809 {
3810 if (!updateObject(rNote.m_nObject))
3811 return false;
3812
3813 OStringBuffer aLine(1024);
3814
3815 emitTextAnnotationLine(aLine, rNote);
3816
3817 if (!writeBuffer(aLine.getStr(), aLine.getLength()))
3818 return false;
3819 }
3820
3821 {
3822
3823 if (!updateObject(rPopUp.m_nObject))
3824 return false;
3825
3826 OStringBuffer aLine(1024);
3827
3828 emitPopupAnnotationLine(aLine, rPopUp);
3829
3830 if (!writeBuffer(aLine.getStr(), aLine.getLength()))
3831 return false;
3832 }
3833 }
3834 return true;
3835}
3836
3837Font PDFWriterImpl::replaceFont( const vcl::Font& rControlFont, const vcl::Font& rAppSetFont )
3838{
3839 bool bAdjustSize = false;
3840
3841 Font aFont( rControlFont );
3842 if( aFont.GetFamilyName().isEmpty() )
3843 {
3844 aFont = rAppSetFont;
3845 if( rControlFont.GetFontHeight() )
3846 aFont.SetFontSize( Size( 0, rControlFont.GetFontHeight() ) );
3847 else
3848 bAdjustSize = true;
3849 if( rControlFont.GetItalic() != ITALIC_DONTKNOW )
3850 aFont.SetItalic( rControlFont.GetItalic() );
3851 if( rControlFont.GetWeight() != WEIGHT_DONTKNOW )
3852 aFont.SetWeight( rControlFont.GetWeight() );
3853 }
3854 else if( ! aFont.GetFontHeight() )
3855 {
3856 aFont.SetFontSize( rAppSetFont.GetFontSize() );
3857 bAdjustSize = true;
3858 }
3859 if( bAdjustSize )
3860 {
3861 Size aFontSize = aFont.GetFontSize();
3863 aFontSize = OutputDevice::LogicToLogic( aFontSize, pDefDev->GetMapMode(), getMapMode() );
3864 aFont.SetFontSize( aFontSize );
3865 }
3866 return aFont;
3867}
3868
3870{
3871 sal_Int32 nBest = 4; // default to Helvetica
3872
3873 if (rFont.GetFamilyType() == FAMILY_ROMAN)
3874 {
3875 // Serif: default to Times-Roman.
3876 nBest = 8;
3877 }
3878
3879 OUString aFontName( rFont.GetFamilyName() );
3880 aFontName = aFontName.toAsciiLowerCase();
3881
3882 if( aFontName.indexOf( "times" ) != -1 )
3883 nBest = 8;
3884 else if( aFontName.indexOf( "courier" ) != -1 )
3885 nBest = 0;
3886 else if( aFontName.indexOf( "dingbats" ) != -1 )
3887 nBest = 13;
3888 else if( aFontName.indexOf( "symbol" ) != -1 )
3889 nBest = 12;
3890 if( nBest < 12 )
3891 {
3892 if( rFont.GetItalic() == ITALIC_OBLIQUE || rFont.GetItalic() == ITALIC_NORMAL )
3893 nBest += 1;
3894 if( rFont.GetWeight() > WEIGHT_MEDIUM )
3895 nBest += 2;
3896 }
3897
3898 if( m_aBuildinFontToObjectMap.find( nBest ) == m_aBuildinFontToObjectMap.end() )
3900
3901 return nBest;
3902}
3903
3904static const Color& replaceColor( const Color& rCol1, const Color& rCol2 )
3905{
3906 return (rCol1 == COL_TRANSPARENT) ? rCol2 : rCol1;
3907}
3908
3910{
3912
3913 // save graphics state
3915
3916 // transform relative to control's coordinates since an
3917 // appearance stream is a form XObject
3918 // this relies on the m_aRect member of rButton NOT already being transformed
3919 // to default user space
3920 if( rWidget.Background || rWidget.Border )
3921 {
3922 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetLightColor() ) : COL_TRANSPARENT );
3924 drawRectangle( rWidget.Location );
3925 }
3926 // prepare font to use
3927 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetPushButtonFont() );
3928 setFont( aFont );
3929 setTextColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ) );
3930
3931 drawText( rButton.m_aRect, rButton.m_aText, rButton.m_nTextStyle );
3932
3933 // create DA string while local mapmode is still in place
3934 // (that is before endRedirect())
3935 OStringBuffer aDA( 256 );
3936 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ), aDA );
3937 Font aDummyFont( "Helvetica", aFont.GetFontSize() );
3938 sal_Int32 nDummyBuildin = getBestBuildinFont( aDummyFont );
3939 aDA.append( ' ' );
3940 aDA.append(pdf::BuildinFontFace::Get(nDummyBuildin).getNameObject());
3941 aDA.append( ' ' );
3942 m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
3943 aDA.append( " Tf" );
3944 rButton.m_aDAString = aDA.makeStringAndClear();
3945
3946 pop();
3947
3948 rButton.m_aAppearances[ "N" ][ "Standard" ] = new SvMemoryStream();
3949
3950 /* seems like a bad hack but at least works in both AR5 and 6:
3951 we draw the button ourselves and tell AR
3952 the button would be totally transparent with no text
3953
3954 One would expect that simply setting a normal appearance
3955 should suffice, but no, as soon as the user actually presses
3956 the button and an action is tied to it (gasp! a button that
3957 does something) the appearance gets replaced by some crap that AR
3958 creates on the fly even if no DA or MK is given. On AR6 at least
3959 the DA and MK work as expected, but on AR5 this creates a region
3960 filled with the background color but nor text. Urgh.
3961 */
3962 rButton.m_aMKDict = "/BC [] /BG [] /CA";
3963 rButton.m_aMKDictCAString = "";
3964}
3965
3967 const PDFWriter::AnyWidget& rWidget,
3968 const StyleSettings& rSettings )
3969{
3970 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetFieldFont() );
3971
3972 if( rWidget.Background || rWidget.Border )
3973 {
3974 if( rWidget.Border && rWidget.BorderColor == COL_TRANSPARENT )
3975 {
3976 sal_Int32 nDelta = GetDPIX() / 500;
3977 if( nDelta < 1 )
3978 nDelta = 1;
3980 tools::Rectangle aRect = rIntern.m_aRect;
3981 setFillColor( rSettings.GetLightBorderColor() );
3982 drawRectangle( aRect );
3983 aRect.AdjustLeft(nDelta ); aRect.AdjustTop(nDelta );
3984 aRect.AdjustRight( -nDelta ); aRect.AdjustBottom( -nDelta );
3985 setFillColor( rSettings.GetFieldColor() );
3986 drawRectangle( aRect );
3987 setFillColor( rSettings.GetLightColor() );
3988 drawRectangle( tools::Rectangle( Point( aRect.Left(), aRect.Bottom()-nDelta ), aRect.BottomRight() ) );
3989 drawRectangle( tools::Rectangle( Point( aRect.Right()-nDelta, aRect.Top() ), aRect.BottomRight() ) );
3990 setFillColor( rSettings.GetDarkShadowColor() );
3991 drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Left()+nDelta, aRect.Bottom() ) ) );
3992 drawRectangle( tools::Rectangle( aRect.TopLeft(), Point( aRect.Right(), aRect.Top()+nDelta ) ) );
3993 }
3994 else
3995 {
3996 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetShadowColor() ) : COL_TRANSPARENT );
3998 drawRectangle( rIntern.m_aRect );
3999 }
4000
4001 if( rWidget.Border )
4002 {
4003 // adjust edit area accounting for border
4004 sal_Int32 nDelta = aFont.GetFontHeight()/4;
4005 if( nDelta < 1 )
4006 nDelta = 1;
4007 rIntern.m_aRect.AdjustLeft(nDelta );
4008 rIntern.m_aRect.AdjustTop(nDelta );
4009 rIntern.m_aRect.AdjustRight( -nDelta );
4010 rIntern.m_aRect.AdjustBottom( -nDelta );
4011 }
4012 }
4013 return aFont;
4014}
4015
4017{
4019 SvMemoryStream* pEditStream = new SvMemoryStream( 1024, 1024 );
4020
4022
4023 // prepare font to use, draw field border
4024 Font aFont = drawFieldBorder( rEdit, rWidget, rSettings );
4025 // Get the built-in font which is closest to aFont.
4026 sal_Int32 nBest = getBestBuildinFont(aFont);
4027
4028 // prepare DA string
4029 OStringBuffer aDA( 32 );
4030 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
4031 aDA.append( ' ' );
4032 aDA.append(pdf::BuildinFontFace::Get(nBest).getNameObject());
4033
4034 OStringBuffer aDR( 32 );
4035 aDR.append( "/Font " );
4036 aDR.append( getFontDictObject() );
4037 aDR.append( " 0 R" );
4038 rEdit.m_aDRDict = aDR.makeStringAndClear();
4039 aDA.append( ' ' );
4040 m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
4041 aDA.append( " Tf" );
4042
4043 /* create an empty appearance stream, let the viewer create
4044 the appearance at runtime. This is because AR5 seems to
4045 paint the widget appearance always, and a dynamically created
4046 appearance on top of it. AR6 is well behaved in that regard, so
4047 that behaviour seems to be a bug. Anyway this empty appearance
4048 relies on /NeedAppearances in the AcroForm dictionary set to "true"
4049 */
4050 beginRedirect( pEditStream, rEdit.m_aRect );
4051 OString aAppearance = "/Tx BMC\nEMC\n";
4052 writeBuffer( aAppearance.getStr(), aAppearance.getLength() );
4053
4054 endRedirect();
4055 pop();
4056
4057 rEdit.m_aAppearances[ "N" ][ "Standard" ] = pEditStream;
4058
4059 rEdit.m_aDAString = aDA.makeStringAndClear();
4060}
4061
4063{
4065 SvMemoryStream* pListBoxStream = new SvMemoryStream( 1024, 1024 );
4066
4068
4069 // prepare font to use, draw field border
4070 Font aFont = drawFieldBorder( rBox, rWidget, rSettings );
4071 sal_Int32 nBest = getSystemFont( aFont );
4072
4073 beginRedirect( pListBoxStream, rBox.m_aRect );
4074
4076 setFillColor( replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) );
4077 drawRectangle( rBox.m_aRect );
4078
4079 // empty appearance, see createDefaultEditAppearance for reference
4080 OString aAppearance = "/Tx BMC\nEMC\n";
4081 writeBuffer( aAppearance.getStr(), aAppearance.getLength() );
4082
4083 endRedirect();
4084 pop();
4085
4086 rBox.m_aAppearances[ "N" ][ "Standard" ] = pListBoxStream;
4087
4088 // prepare DA string
4089 OStringBuffer aDA( 256 );
4090 // prepare DA string
4091 appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
4092 aDA.append( ' ' );
4093 aDA.append( "/F" );
4094 aDA.append( nBest );
4095
4096 OStringBuffer aDR( 32 );
4097 aDR.append( "/Font " );
4098 aDR.append( getFontDictObject() );
4099 aDR.append( " 0 R" );
4100 rBox.m_aDRDict = aDR.makeStringAndClear();
4101 aDA.append( ' ' );
4102 m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetFontHeight() ), aDA );
4103 aDA.append( " Tf" );
4104 rBox.m_aDAString = aDA.makeStringAndClear();
4105}
4106
4108{
4110
4111 // save graphics state
4113
4114 if( rWidget.Background || rWidget.Border )
4115 {
4116 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
4118 drawRectangle( rBox.m_aRect );
4119 }
4120
4121 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
4122 setFont( aFont );
4123 Size aFontSize = aFont.GetFontSize();
4124 if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
4125 aFontSize.setHeight( rBox.m_aRect.GetHeight() );
4126 sal_Int32 nDelta = aFontSize.Height()/10;
4127 if( nDelta < 1 )
4128 nDelta = 1;
4129
4130 tools::Rectangle aCheckRect, aTextRect;
4131 {
4132 aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
4133 aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
4134 aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
4135 aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
4136
4137 // #i74206# handle small controls without text area
4138 while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
4139 {
4140 aCheckRect.AdjustRight( -nDelta );
4141 aCheckRect.AdjustTop(nDelta/2 );
4142 aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
4143 }
4144
4145 aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
4146 aTextRect.SetTop( rBox.m_aRect.Top() );
4147 aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
4148 aTextRect.SetBottom( rBox.m_aRect.Bottom() );
4149 }
4152 OStringBuffer aLW( 32 );
4153 aLW.append( "q " );
4154 m_aPages[m_nCurrentPage].appendMappedLength( nDelta, aLW );
4155 aLW.append( " w " );
4156 writeBuffer( aLW.getStr(), aLW.getLength() );
4157 drawRectangle( aCheckRect );
4158 writeBuffer( " Q\n", 3 );
4159 setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4160 drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
4161
4162 pop();
4163
4164 OStringBuffer aDA( 256 );
4165
4166 // tdf#93853 don't rely on Zapf (or any other 'standard' font)
4167 // being present, but our own OpenSymbol - N.B. PDF/A for good
4168 // reasons require even the standard PS fonts to be embedded!
4169 Push();
4170 SetFont( Font( OUString( "OpenSymbol" ), aFont.GetFontSize() ) );
4171 const LogicalFontInstance* pFontInstance = GetFontInstance();
4172 const vcl::font::PhysicalFontFace* pDevFont = pFontInstance->GetFontFace();
4173 Pop();
4174
4175 // make sure OpenSymbol is embedded, and includes our checkmark
4176 const sal_Unicode cMark=0x2713;
4177 const auto nGlyphId = pFontInstance->GetGlyphIndex(cMark);
4178 const auto nGlyphWidth = pFontInstance->GetGlyphWidth(nGlyphId, false, false);
4179
4180 sal_uInt8 nMappedGlyph;
4181 sal_Int32 nMappedFontObject;
4182 registerGlyph(nGlyphId, pDevFont, { cMark }, nGlyphWidth, nMappedGlyph, nMappedFontObject, pDevFont->IsColorFont());
4183
4185 aDA.append( ' ' );
4186 aDA.append( "/F" );
4187 aDA.append( nMappedFontObject );
4188 aDA.append( " 0 Tf" );
4189
4190 OStringBuffer aDR( 32 );
4191 aDR.append( "/Font " );
4192 aDR.append( getFontDictObject() );
4193 aDR.append( " 0 R" );
4194 rBox.m_aDRDict = aDR.makeStringAndClear();
4195 rBox.m_aDAString = aDA.makeStringAndClear();
4196 rBox.m_aMKDict = "/CA";
4197 rBox.m_aMKDictCAString = "8";
4198 rBox.m_aRect = aCheckRect;
4199
4200 // create appearance streams
4201 sal_Int32 nCharXOffset = 1000 - 787; // metrics from OpenSymbol
4202 nCharXOffset *= aCheckRect.GetHeight();
4203 nCharXOffset /= 2000;
4204 sal_Int32 nCharYOffset = 1000 - (820-143); // metrics from Zapf
4205 nCharYOffset *= aCheckRect.GetHeight();
4206 nCharYOffset /= 2000;
4207
4208 // write 'checked' appearance stream
4209 SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
4210 beginRedirect( pCheckStream, aCheckRect );
4211 aDA.append( "/Tx BMC\nq BT\n" );
4213 aDA.append( ' ' );
4214 aDA.append( "/F" );
4215 aDA.append( nMappedFontObject );
4216 aDA.append( ' ' );
4217 m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
4218 aDA.append( " Tf\n" );
4219 m_aPages[ m_nCurrentPage ].appendMappedLength( nCharXOffset, aDA );
4220 aDA.append( " " );
4221 m_aPages[ m_nCurrentPage ].appendMappedLength( nCharYOffset, aDA );
4222 aDA.append( " Td <" );
4223 appendHex( nMappedGlyph, aDA );
4224 aDA.append( "> Tj\nET\nQ\nEMC\n" );
4225 writeBuffer( aDA.getStr(), aDA.getLength() );
4226 endRedirect();
4227 rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream;
4228
4229 // write 'unchecked' appearance stream
4230 SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
4231 beginRedirect( pUncheckStream, aCheckRect );
4232 writeBuffer( "/Tx BMC\nEMC\n", 12 );
4233 endRedirect();
4234 rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream;
4235}
4236
4238{
4240
4241 // save graphics state
4243
4244 if( rWidget.Background || rWidget.Border )
4245 {
4246 setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : COL_TRANSPARENT );
4248 drawRectangle( rBox.m_aRect );
4249 }
4250
4251 Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
4252 setFont( aFont );
4253 Size aFontSize = aFont.GetFontSize();
4254 if( aFontSize.Height() > rBox.m_aRect.GetHeight() )
4255 aFontSize.setHeight( rBox.m_aRect.GetHeight() );
4256 sal_Int32 nDelta = aFontSize.Height()/10;
4257 if( nDelta < 1 )
4258 nDelta = 1;
4259
4260 tools::Rectangle aCheckRect, aTextRect;
4261 {
4262 aCheckRect.SetLeft( rBox.m_aRect.Left() + nDelta );
4263 aCheckRect.SetTop( rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2 );
4264 aCheckRect.SetRight( aCheckRect.Left() + aFontSize.Height() );
4265 aCheckRect.SetBottom( aCheckRect.Top() + aFontSize.Height() );
4266
4267 // #i74206# handle small controls without text area
4268 while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta )
4269 {
4270 aCheckRect.AdjustRight( -nDelta );
4271 aCheckRect.AdjustTop(nDelta/2 );
4272 aCheckRect.AdjustBottom( -(nDelta - (nDelta/2)) );
4273 }
4274
4275 aTextRect.SetLeft( rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta );
4276 aTextRect.SetTop( rBox.m_aRect.Top() );
4277 aTextRect.SetRight( aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta );
4278 aTextRect.SetBottom( rBox.m_aRect.Bottom() );
4279 }
4282 OStringBuffer aLW( 32 );
4283 aLW.append( "q " );
4284 m_aPages[ m_nCurrentPage ].appendMappedLength( nDelta, aLW );
4285 aLW.append( " w " );
4286 writeBuffer( aLW.getStr(), aLW.getLength() );
4287 drawEllipse( aCheckRect );
4288 writeBuffer( " Q\n", 3 );
4289 setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4290 drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
4291
4292 pop();
4293
4294 //to encrypt this (el)
4295 rBox.m_aMKDict = "/CA";
4296 //after this assignment, to m_aMKDic cannot be added anything
4297 rBox.m_aMKDictCAString = "l";
4298
4299 rBox.m_aRect = aCheckRect;
4300
4301 // create appearance streams
4303 SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
4304
4305 beginRedirect( pCheckStream, aCheckRect );
4306 OStringBuffer aDA( 256 );
4307 aDA.append( "/Tx BMC\nq BT\n" );
4309 aDA.append( ' ' );
4310 m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
4311 aDA.append( " 0 0 Td\nET\nQ\n" );
4312 writeBuffer( aDA.getStr(), aDA.getLength() );
4313 setFillColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
4315 aCheckRect.AdjustLeft(3*nDelta );
4316 aCheckRect.AdjustTop(3*nDelta );
4317 aCheckRect.AdjustBottom( -(3*nDelta) );
4318 aCheckRect.AdjustRight( -(3*nDelta) );
4319 drawEllipse( aCheckRect );
4320 writeBuffer( "\nEMC\n", 5 );
4321 endRedirect();
4322
4323 pop();
4324 rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream;
4325
4326 SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
4327 beginRedirect( pUncheckStream, aCheckRect );
4328 writeBuffer( "/Tx BMC\nEMC\n", 12 );
4329 endRedirect();
4330 rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream;
4331}
4332
4333bool PDFWriterImpl::emitAppearances( PDFWidget& rWidget, OStringBuffer& rAnnotDict )
4334{
4335 // TODO: check and insert default streams
4336 OString aStandardAppearance;
4337 switch( rWidget.m_eType )
4338 {
4340 aStandardAppearance = OUStringToOString( rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US );
4341 break;
4342 default:
4343 break;
4344 }
4345
4346 if( !rWidget.m_aAppearances.empty() )
4347 {
4348 rAnnotDict.append( "/AP<<\n" );
4349 for (auto & dict_item : rWidget.m_aAppearances)
4350 {
4351 rAnnotDict.append( "/" );
4352 rAnnotDict.append( dict_item.first );
4353 bool bUseSubDict = (dict_item.second.size() > 1);
4354
4355 // PDF/A requires sub-dicts for /FT/Btn objects (clause
4356 // 6.3.3)
4358 {
4359 if( rWidget.m_eType == PDFWriter::RadioButton ||
4360 rWidget.m_eType == PDFWriter::CheckBox ||
4361 rWidget.m_eType == PDFWriter::PushButton )
4362 {
4363 bUseSubDict = true;
4364 }
4365 }
4366
4367 rAnnotDict.append( bUseSubDict ? "<<" : " " );
4368
4369 for (auto const& stream_item : dict_item.second)
4370 {
4371 SvMemoryStream* pAppearanceStream = stream_item.second;
4372 dict_item.second[ stream_item.first ] = nullptr;
4373
4374 bool bDeflate = compressStream( pAppearanceStream );
4375
4376 sal_Int64 nStreamLen = pAppearanceStream->TellEnd();
4377 pAppearanceStream->Seek( STREAM_SEEK_TO_BEGIN );
4378 sal_Int32 nObject = createObject();
4379 CHECK_RETURN( updateObject( nObject ) );
4381 {
4382 emitComment( "PDFWriterImpl::emitAppearances" );
4383 }
4384 OStringBuffer aLine;
4385 aLine.append( nObject );
4386
4387 aLine.append( " 0 obj\n"
4388 "<</Type/XObject\n"
4389 "/Subtype/Form\n"
4390 "/BBox[0 0 " );
4391 appendFixedInt( rWidget.m_aRect.GetWidth()-1, aLine );
4392 aLine.append( " " );
4393 appendFixedInt( rWidget.m_aRect.GetHeight()-1, aLine );
4394 aLine.append( "]\n"
4395 "/Resources " );
4396 aLine.append( getResourceDictObj() );
4397 aLine.append( " 0 R\n"
4398 "/Length " );
4399 aLine.append( nStreamLen );
4400 aLine.append( "\n" );
4401 if( bDeflate )
4402 aLine.append( "/Filter/FlateDecode\n" );
4403 aLine.append( ">>\nstream\n" );
4404 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
4406 CHECK_RETURN( writeBuffer( pAppearanceStream->GetData(), nStreamLen ) );
4408 CHECK_RETURN( writeBuffer( "\nendstream\nendobj\n\n", 19 ) );
4409
4410 if( bUseSubDict )
4411 {
4412 rAnnotDict.append( " /" );
4413 rAnnotDict.append( stream_item.first );
4414 rAnnotDict.append( " " );
4415 }
4416 rAnnotDict.append( nObject );
4417 rAnnotDict.append( " 0 R" );
4418
4419 delete pAppearanceStream;
4420 }
4421
4422 rAnnotDict.append( bUseSubDict ? ">>\n" : "\n" );
4423 }
4424 rAnnotDict.append( ">>\n" );
4425 if( !aStandardAppearance.isEmpty() )
4426 {
4427 rAnnotDict.append( "/AS /" );
4428 rAnnotDict.append( aStandardAppearance );
4429 rAnnotDict.append( "\n" );
4430 }
4431 }
4432
4433 return true;
4434}
4435
4437{
4439
4440 int nAnnots = m_aWidgets.size();
4441 for( int a = 0; a < nAnnots; a++ )
4442 {
4443 PDFWidget& rWidget = m_aWidgets[a];
4444
4445 if( rWidget.m_eType == PDFWriter::CheckBox )
4446 {
4447 if ( !rWidget.m_aOnValue.isEmpty() )
4448 {
4449 auto app_it = rWidget.m_aAppearances.find( "N" );
4450 if( app_it != rWidget.m_aAppearances.end() )
4451 {
4452 auto stream_it = app_it->second.find( "Yes" );
4453 if( stream_it != app_it->second.end() )
4454 {
4455 SvMemoryStream* pStream = stream_it->second;
4456 app_it->second.erase( stream_it );
4457 OStringBuffer aBuf( rWidget.m_aOnValue.getLength()*2 );
4458 appendName( rWidget.m_aOnValue, aBuf );
4459 (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
4460 }
4461 else
4462 SAL_INFO("vcl.pdfwriter", "error: CheckBox without \"Yes\" stream" );
4463 }
4464 }
4465
4466 if ( !rWidget.m_aOffValue.isEmpty() )
4467 {
4468 auto app_it = rWidget.m_aAppearances.find( "N" );
4469 if( app_it != rWidget.m_aAppearances.end() )
4470 {
4471 auto stream_it = app_it->second.find( "Off" );
4472 if( stream_it != app_it->second.end() )
4473 {
4474 SvMemoryStream* pStream = stream_it->second;
4475 app_it->second.erase( stream_it );
4476 OStringBuffer aBuf( rWidget.m_aOffValue.getLength()*2 );
4477 appendName( rWidget.m_aOffValue, aBuf );
4478 (app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
4479 }
4480 else
4481 SAL_INFO("vcl.pdfwriter", "error: CheckBox without \"Off\" stream" );
4482 }
4483 }
4484 }
4485
4486 OStringBuffer aLine( 1024 );
4487 OStringBuffer aValue( 256 );
4488 aLine.append( rWidget.m_nObject );
4489 aLine.append( " 0 obj\n"
4490 "<<" );
4491 if( rWidget.m_eType != PDFWriter::Hierarchy )
4492 {
4493 // emit widget annotation only for terminal fields
4494 if( rWidget.m_aKids.empty() )
4495 {
4496 int iRectMargin;
4497
4498 aLine.append( "/Type/Annot/Subtype/Widget/F " );
4499
4500 if (rWidget.m_eType == PDFWriter::Signature)
4501 {
4502 aLine.append( "132\n" ); // Print & Locked
4503 iRectMargin = 0;
4504 }
4505 else
4506 {
4507 aLine.append( "4\n" );
4508 iRectMargin = 1;
4509 }
4510
4511 aLine.append("/Rect[" );
4512 appendFixedInt( rWidget.m_aRect.Left()-iRectMargin, aLine );
4513 aLine.append( ' ' );
4514 appendFixedInt( rWidget.m_aRect.Top()+iRectMargin, aLine );
4515 aLine.append( ' ' );
4516 appendFixedInt( rWidget.m_aRect.Right()+iRectMargin, aLine );
4517 aLine.append( ' ' );
4518 appendFixedInt( rWidget.m_aRect.Bottom()-iRectMargin, aLine );
4519 aLine.append( "]\n" );
4520 }
4521 aLine.append( "/FT/" );
4522 switch( rWidget.m_eType )
4523 {
4526 // for radio buttons only the RadioButton field, not the
4527 // CheckBox children should have a value, else acrobat reader
4528 // does not always check the right button
4529 // of course real check boxes (not belonging to a radio group)
4530 // need their values, too
4531 if( rWidget.m_eType == PDFWriter::RadioButton || rWidget.m_nRadioGroup < 0 )
4532 {
4533 aValue.append( "/" );
4534 // check for radio group with all buttons unpressed
4535 if( rWidget.m_aValue.isEmpty() )
4536 aValue.append( "Off" );
4537 else
4538 appendName( rWidget.m_aValue, aValue );
4539 }
4540 [[fallthrough]];
4542 aLine.append( "Btn" );
4543 break;
4544 case PDFWriter::ListBox:
4545 if( rWidget.m_nFlags & 0x200000 ) // multiselect
4546 {
4547 aValue.append( "[" );
4548 for( size_t i = 0; i < rWidget.m_aSelectedEntries.size(); i++ )
4549 {
4550 sal_Int32 nEntry = rWidget.m_aSelectedEntries[i];
4551 if( nEntry >= 0
4552 && o3tl::make_unsigned(nEntry) < rWidget.m_aListEntries.size() )
4553 appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ nEntry ], rWidget.m_nObject, aValue );
4554 }
4555 aValue.append( "]" );
4556 }
4557 else if( !rWidget.m_aSelectedEntries.empty() &&
4558 rWidget.m_aSelectedEntries[0] >= 0 &&
4559 o3tl::make_unsigned(rWidget.m_aSelectedEntries[0]) < rWidget.m_aListEntries.size() )
4560 {
4561 appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ rWidget.m_aSelectedEntries[0] ], rWidget.m_nObject, aValue );
4562 }
4563 else
4564 appendUnicodeTextStringEncrypt( OUString(), rWidget.m_nObject, aValue );
4565 aLine.append( "Ch" );
4566 break;
4568 appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue );
4569 aLine.append( "Ch" );
4570 break;
4571 case PDFWriter::Edit:
4572 aLine.append( "Tx" );
4573 appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue );
4574 break;
4576 aLine.append( "Sig" );
4577 aValue.append(OUStringToOString(rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US));
4578 break;
4579 case PDFWriter::Hierarchy: // make the compiler happy
4580 break;
4581 }
4582 aLine.append( "\n" );
4583 aLine.append( "/P " );
4584 aLine.append( m_aPages[ rWidget.m_nPage ].m_nPageObject );
4585 aLine.append( " 0 R\n" );
4586 }
4587 if( rWidget.m_nParent )
4588 {
4589 aLine.append( "/Parent " );
4590 aLine.append( rWidget.m_nParent );
4591 aLine.append( " 0 R\n" );
4592 }
4593 if( !rWidget.m_aKids.empty() )
4594 {
4595 aLine.append( "/Kids[" );
4596 for( size_t i = 0; i < rWidget.m_aKids.size(); i++ )
4597 {
4598 aLine.append( rWidget.m_aKids[i] );
4599 aLine.append( " 0 R" );
4600 aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
4601 }
4602 aLine.append( "]\n" );
4603 }
4604 if( !rWidget.m_aName.isEmpty() )
4605 {
4606 aLine.append( "/T" );
4607 appendLiteralStringEncrypt( rWidget.m_aName, rWidget.m_nObject, aLine );
4608 aLine.append( "\n" );
4609 }
4611 {
4612 // the alternate field name should be unicode able since it is
4613 // supposed to be used in UI
4614 aLine.append( "/TU" );
4615 appendUnicodeTextStringEncrypt( rWidget.m_aDescription, rWidget.m_nObject, aLine );
4616 aLine.append( "\n" );
4617 }
4618
4619 if( rWidget.m_nFlags )
4620 {
4621 aLine.append( "/Ff " );
4622 aLine.append( rWidget.m_nFlags );
4623 aLine.append( "\n" );
4624 }
4625 if( !aValue.isEmpty() )
4626 {
4627 OString aVal = aValue.makeStringAndClear();
4628 aLine.append( "/V " );
4629 aLine.append( aVal );
4630 aLine.append( "\n"
4631 "/DV " );
4632 aLine.append( aVal );
4633 aLine.append( "\n" );
4634 }
4635 if( rWidget.m_eType == PDFWriter::ListBox || rWidget.m_eType == PDFWriter::ComboBox )
4636 {
4637 sal_Int32 nTI = -1;
4638 aLine.append( "/Opt[\n" );
4639 sal_Int32 i = 0;
4640 for (auto const& entry : rWidget.m_aListEntries)
4641 {
4642 appendUnicodeTextStringEncrypt( entry, rWidget.m_nObject, aLine );
4643 aLine.append( "\n" );
4644 if( entry == rWidget.m_aValue )
4645 nTI = i;
4646 ++i;
4647 }
4648 aLine.append( "]\n" );
4649 if( nTI > 0 )
4650 {
4651 aLine.append( "/TI " );
4652 aLine.append( nTI );
4653 aLine.append( "\n" );
4654 if( rWidget.m_nFlags & 0x200000 ) // Multiselect
4655 {
4656 aLine.append( "/I [" );
4657 aLine.append( nTI );
4658 aLine.append( "]\n" );
4659 }
4660 }
4661 }
4662 if( rWidget.m_eType == PDFWriter::Edit )
4663 {
4664 if ( rWidget.m_nMaxLen > 0 )
4665 {
4666 aLine.append( "/MaxLen " );
4667 aLine.append( rWidget.m_nMaxLen );
4668 aLine.append( "\n" );
4669 }
4670
4671 if ( rWidget.m_nFormat == PDFWriter::Number )
4672 {
4673 OString aHexText;
4674
4675 if ( !rWidget.m_aCurrencySymbol.isEmpty() )
4676 {
4677 // Get the hexadecimal code
4678 sal_UCS4 cChar = rWidget.m_aCurrencySymbol.iterateCodePoints(&o3tl::temporary(sal_Int32(1)), -1);
4679 aHexText = "\\\\u" + OString::number(cChar, 16);
4680 }
4681
4682 aLine.append("/AA<<\n");
4683 aLine.append("/F<</JS(AFNumber_Format\\(");
4684 aLine.append(OString::number(rWidget.m_nDecimalAccuracy));
4685 aLine.append(", 0, 0, 0, \"");
4686 aLine.append( aHexText );
4687 aLine.append("\",");
4688 aLine.append(OString::boolean(rWidget.m_bPrependCurrencySymbol));
4689 aLine.append("\\);)");
4690 aLine.append("/S/JavaScript>>\n");
4691 aLine.append("/K<</JS(AFNumber_Keystroke\\(");
4692 aLine.append(OString::number(rWidget.m_nDecimalAccuracy));
4693 aLine.append(", 0, 0, 0, \"");
4694 aLine.append( aHexText );
4695 aLine.append("\",");
4696 aLine.append(OString::boolean(rWidget.m_bPrependCurrencySymbol));
4697 aLine.append("\\);)");
4698 aLine.append("/S/JavaScript>>\n");
4699 aLine.append(">>\n");
4700 }
4701 else if ( rWidget.m_nFormat == PDFWriter::Time )
4702 {
4703 aLine.append("/AA<<\n");
4704 aLine.append("/F<</JS(AFTime_FormatEx\\(\"");
4705 aLine.append(OUStringToOString(rWidget.m_aTimeFormat, RTL_TEXTENCODING_ASCII_US));
4706 aLine.append("\"\\);)");
4707 aLine.append("/S/JavaScript>>\n");
4708 aLine.append("/K<</JS(AFTime_KeystrokeEx\\(\"");
4709 aLine.append(OUStringToOString(rWidget.m_aTimeFormat, RTL_TEXTENCODING_ASCII_US));
4710 aLine.append("\"\\);)");
4711 aLine.append("/S/JavaScript>>\n");
4712 aLine.append(">>\n");
4713 }
4714 else if ( rWidget.m_nFormat == PDFWriter::Date )
4715 {
4716 aLine.append("/AA<<\n");
4717 aLine.append("/F<</JS(AFDate_FormatEx\\(\"");
4718 aLine.append(OUStringToOString(rWidget.m_aDateFormat, RTL_TEXTENCODING_ASCII_US));
4719 aLine.append("\"\\);)");
4720 aLine.append("/S/JavaScript>>\n");
4721 aLine.append("/K<</JS(AFDate_KeystrokeEx\\(\"");
4722 aLine.append(OUStringToOString(rWidget.m_aDateFormat, RTL_TEXTENCODING_ASCII_US));
4723 aLine.append("\"\\);)");
4724 aLine.append("/S/JavaScript>>\n");
4725 aLine.append(">>\n");
4726 }
4727 }
4728 if( rWidget.m_eType == PDFWriter::PushButton )
4729 {
4730 if(!m_bIsPDF_A1)
4731 {
4732 OStringBuffer aDest;
4733 if( rWidget.m_nDest != -1 && appendDest( m_aDestinationIdTranslation[ rWidget.m_nDest ], aDest ) )
4734 {
4735 aLine.append( "/AA<</D<</Type/Action/S/GoTo/D " );
4736 aLine.append( aDest );
4737 aLine.append( ">>>>\n" );
4738 }
4739 else if( rWidget.m_aListEntries.empty() )
4740 {
4741 if( !m_bIsPDF_A2 && !m_bIsPDF_A3 )
4742 {
4743 // create a reset form action
4744 aLine.append( "/AA<</D<</Type/Action/S/ResetForm>>>>\n" );
4745 }
4746 }
4747 else if( rWidget.m_bSubmit )
4748 {
4749 // create a submit form action
4750 aLine.append( "/AA<</D<</Type/Action/S/SubmitForm/F" );
4751 appendLiteralStringEncrypt( rWidget.m_aListEntries.front(), rWidget.m_nObject, aLine, osl_getThreadTextEncoding() );
4752 aLine.append( "/Flags " );
4753
4754 sal_Int32 nFlags = 0;
4755 switch( m_aContext.SubmitFormat )
4756 {
4757 case PDFWriter::HTML:
4758 nFlags |= 4;
4759 break;
4760 case PDFWriter::XML:
4762 nFlags |= 32;
4763 break;
4764 case PDFWriter::PDF:
4766 nFlags |= 256;
4767 break;
4768 case PDFWriter::FDF:
4769 default:
4770 break;
4771 }
4772 if( rWidget.m_bSubmitGet )
4773 nFlags |= 8;
4774 aLine.append( nFlags );
4775 aLine.append( ">>>>\n" );
4776 }
4777 else
4778 {
4779 // create a URI action
4780 aLine.append( "/AA<</D<</Type/Action/S/URI/URI(" );
4781 aLine.append( OUStringToOString( rWidget.m_aListEntries.front(), RTL_TEXTENCODING_ASCII_US ) );
4782 aLine.append( ")>>>>\n" );
4783 }
4784 }
4785 else
4787 }
4788 if( !rWidget.m_aDAString.isEmpty() )
4789 {
4790 if( !rWidget.m_aDRDict.isEmpty() )
4791 {
4792 aLine.append( "/DR<<" );
4793 aLine.append( rWidget.m_aDRDict );
4794 aLine.append( ">>\n" );
4795 }
4796 else
4797 {
4798 aLine.append( "/DR<</Font<<" );
4799 appendBuildinFontsToDict( aLine );
4800 aLine.append( ">>>>\n" );
4801 }
4802 aLine.append( "/DA" );
4803 appendLiteralStringEncrypt( rWidget.m_aDAString, rWidget.m_nObject, aLine );
4804 aLine.append( "\n" );
4805 if( rWidget.m_nTextStyle & DrawTextFlags::Center )
4806 aLine.append( "/Q 1\n" );
4807 else if( rWidget.m_nTextStyle & DrawTextFlags::Right )
4808 aLine.append( "/Q 2\n" );
4809 }
4810 // appearance characteristics for terminal fields
4811 // which are supposed to have an appearance constructed
4812 // by the viewer application
4813 if( !rWidget.m_aMKDict.isEmpty() )
4814 {
4815 aLine.append( "/MK<<" );
4816 aLine.append( rWidget.m_aMKDict );
4817 //add the CA string, encrypting it
4819 aLine.append( ">>\n" );
4820 }
4821
4822 CHECK_RETURN( emitAppearances( rWidget, aLine ) );
4823
4824 aLine.append( ">>\n"
4825 "endobj\n\n" );
4826 CHECK_RETURN( updateObject( rWidget.m_nObject ) );
4827 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
4828 }
4829 return true;
4830}
4831
4833{
4834 if( m_aPages.empty() )
4835 return false;
4836
4841
4842 return true;
4843}
4844
4846{
4847 for (const auto& rEmbeddedFile : m_aEmbeddedFiles)
4848 {
4849 if (!updateObject(rEmbeddedFile.m_nObject))
4850 continue;
4851
4852 OStringBuffer aLine;
4853 aLine.append(rEmbeddedFile.m_nObject);
4854 aLine.append(" 0 obj\n");
4855 aLine.append("<< /Type /EmbeddedFile /Length ");
4856 aLine.append(static_cast<sal_Int64>(rEmbeddedFile.m_aDataContainer.getSize()));
4857 aLine.append(" >>\nstream\n");
4858 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
4859 aLine.setLength(0);
4860
4861 CHECK_RETURN(writeBuffer(rEmbeddedFile.m_aDataContainer.getData(), rEmbeddedFile.m_aDataContainer.getSize()));
4862
4863 aLine.append("\nendstream\nendobj\n\n");
4864 CHECK_RETURN(writeBuffer(aLine.getStr(), aLine.getLength()));
4865 }
4866 return true;
4867}
4868
4869#undef CHECK_RETURN
4870#define CHECK_RETURN( x ) if( !x ) return false
4871
4873{
4874 // build page tree
4875 // currently there is only one node that contains all leaves
4876
4877 // first create a page tree node id
4878 sal_Int32 nTreeNode = createObject();
4879
4880 // emit global resource dictionary (page emit needs it)
4882
4883 // emit all pages
4884 for (auto & page : m_aPages)
4885 if( ! page.emit( nTreeNode ) )
4886 return false;
4887
4888 sal_Int32 nNamedDestinationsDictionary = emitNamedDestinations();
4889
4890 sal_Int32 nOutlineDict = emitOutline();
4891
4892 // emit Output intent
4893 sal_Int32 nOutputIntentObject = emitOutputIntent();
4894
4895 // emit metadata
4896 sal_Int32 nMetadataObject = emitDocumentMetadata();
4897
4898 sal_Int32 nStructureDict = 0;
4899 if(m_aStructure.size() > 1)
4900 {
4901 // check if dummy structure containers are needed
4903 nStructureDict = m_aStructure[0].m_nObject = createObject();
4905 }
4906
4907 // adjust tree node file offset
4908 if( ! updateObject( nTreeNode ) )
4909 return false;
4910
4911 // emit tree node
4912 OStringBuffer aLine( 2048 );
4913 aLine.append( nTreeNode );
4914 aLine.append( " 0 obj\n" );
4915 aLine.append( "<</Type/Pages\n" );
4916 aLine.append( "/Resources " );
4917 aLine.append( getResourceDictObj() );
4918 aLine.append( " 0 R\n" );
4919
4920 double nMediaBoxWidth = 0;
4921 double nMediaBoxHeight = 0;
4922 sal_Int32 nUserUnit = 1;
4923 if( m_aPages.empty() ) // sanity check, this should not happen
4924 {
4925 nMediaBoxWidth = g_nInheritedPageWidth;
4926 nMediaBoxHeight = g_nInheritedPageHeight;
4927 }
4928 else
4929 {
4930 for (auto const& page : m_aPages)
4931 {
4932 if( page.m_nPageWidth > nMediaBoxWidth )
4933 {
4934 nMediaBoxWidth = page.m_nPageWidth;
4935 nUserUnit = page.m_nUserUnit;
4936 }
4937 if( page.m_nPageHeight > nMediaBoxHeight )
4938 {
4939 nMediaBoxHeight = page.m_nPageHeight;
4940 nUserUnit = page.m_nUserUnit;
4941 }
4942 }
4943 }
4944 aLine.append( "/MediaBox[ 0 0 " );
4945 aLine.append(nMediaBoxWidth / nUserUnit);
4946 aLine.append( ' ' );
4947 aLine.append(nMediaBoxHeight / nUserUnit);
4948 aLine.append(" ]\n");
4949 if (nUserUnit > 1)
4950 {
4951 aLine.append("/UserUnit ");
4952 aLine.append(nUserUnit);
4953 aLine.append("\n");
4954 }
4955 aLine.append("/Kids[ ");
4956 unsigned int i = 0;
4957 for (const auto & page : m_aPages)
4958 {
4959 aLine.append( page.m_nPageObject );
4960 aLine.append( " 0 R" );
4961 aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
4962 ++i;
4963 }
4964 aLine.append( "]\n"
4965 "/Count " );
4966 aLine.append( static_cast<sal_Int32>(m_aPages.size()) );
4967 aLine.append( ">>\n"
4968 "endobj\n\n" );
4969 CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
4970
4971 // emit annotation objects
4974
4975 // emit Catalog
4978 return false;
4979 aLine.setLength( 0 );
4980 aLine.append( m_nCatalogObject );
4981 aLine.append( " 0 obj\n"
4982 "<</Type/Catalog/Pages " );
4983 aLine.append( nTreeNode );
4984 aLine.append( " 0 R\n" );
4985
4986 // check if there are named destinations to emit (root must be inside the catalog)
4987 if( nNamedDestinationsDictionary )
4988 {
4989 aLine.append("/Dests ");
4990 aLine.append( nNamedDestinationsDictionary );
4991 aLine.append( " 0 R\n" );
4992 }
4993
4995 switch( m_aContext.PageLayout )
4996 {
4997 default :
4999 aLine.append( "/PageLayout/SinglePage\n" );
5000 break;
5002 aLine.append( "/PageLayout/OneColumn\n" );
5003 break;
5005 // the flag m_aContext.FirstPageLeft below is used to set the page on the left side
5006 aLine.append( "/PageLayout/TwoColumnRight\n" );//odd page on the right side
5007 break;
5008 }
5010 switch( m_aContext.PDFDocumentMode )
5011 {
5012 default :
5013 aLine.append( "/PageMode/UseNone\n" );
5014 break;
5016 aLine.append( "/PageMode/UseOutlines\n" ); //document is opened with outline pane open
5017 break;
5019 aLine.append( "/PageMode/UseThumbs\n" ); //document is opened with thumbnails pane open
5020 break;
5021 }
5023 aLine.append( "/PageMode/FullScreen\n" ); //document is opened full screen
5024
5025 OStringBuffer aInitPageRef;
5027 {
5028 aInitPageRef.append( m_aPages[m_aContext.InitialPage].m_nPageObject );
5029 aInitPageRef.append( " 0 R" );
5030 }
5031 else
5032 aInitPageRef.append( "0" );
5033
5035 {
5036 case PDFWriter::ActionDefault : //do nothing, this is the Acrobat default
5037 default:
5038 if( aInitPageRef.getLength() > 1 )
5039 {
5040 aLine.append( "/OpenAction[" );
5041 aLine.append( aInitPageRef );
5042 aLine.append( " /XYZ null null 0]\n" );
5043 }
5044 break;
5046 aLine.append( "/OpenAction[" );
5047 aLine.append( aInitPageRef );
5048 aLine.append( " /Fit]\n" ); //Open fit page
5049 break;
5050 case PDFWriter::FitWidth :
5051 aLine.append( "/OpenAction[" );
5052 aLine.append( aInitPageRef );
5053 aLine.append( " /FitH " );
5054 aLine.append( g_nInheritedPageHeight );//Open fit width
5055 aLine.append( "]\n" );
5056 break;
5058 aLine.append( "/OpenAction[" );
5059 aLine.append( aInitPageRef );
5060 aLine.append( " /FitBH " );
5061 aLine.append( g_nInheritedPageHeight );//Open fit visible
5062 aLine.append( "]\n" );
5063 break;