Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
13296 lines (11871 sloc) 487 KB
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <config_features.h>
#include <sal/types.h>
#include <math.h>
#include <algorithm>
#if defined __GNUC__ && __cplusplus > 201402L
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wregister"
#endif
#include <lcms2.h>
#if defined __GNUC__ && __cplusplus > 201402L
#pragma GCC diagnostic pop
#endif
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <memory>
#include <com/sun/star/lang/XMultiServiceFactory.hpp>
#include <com/sun/star/util/URL.hpp>
#include <com/sun/star/util/URLTransformer.hpp>
#include <comphelper/processfactory.hxx>
#include <comphelper/random.hxx>
#include <comphelper/string.hxx>
#include <cppuhelper/implbase.hxx>
#include <i18nlangtag/languagetag.hxx>
#include <o3tl/numeric.hxx>
#include <o3tl/make_unique.hxx>
#include <osl/file.hxx>
#include <osl/thread.h>
#include <rtl/crc.h>
#include <rtl/digest.h>
#include <rtl/ustrbuf.hxx>
#include <svl/urihelper.hxx>
#include <tools/debug.hxx>
#include <tools/fract.hxx>
#include <tools/stream.hxx>
#include <tools/urlobj.hxx>
#include <tools/zcodec.hxx>
#include <vcl/bitmapex.hxx>
#include <vcl/bitmapaccess.hxx>
#include <vcl/cvtgrf.hxx>
#include <vcl/image.hxx>
#include <vcl/lineinfo.hxx>
#include <vcl/metric.hxx>
#include <vcl/settings.hxx>
#include <vcl/strhelper.hxx>
#include <vcl/svapp.hxx>
#include <vcl/virdev.hxx>
#include "fontsubset.hxx"
#include "outdev.h"
#include "PhysicalFontFace.hxx"
#include "salgdi.hxx"
#include "sallayout.hxx"
#include "textlayout.hxx"
#include "textlineinfo.hxx"
#include "pdfwriter_impl.hxx"
#if !defined(ANDROID) && HAVE_FEATURE_NSS && !defined(_WIN32)
// NSS headers for PDF signing
#include "nss.h"
#include "cert.h"
#include "hasht.h"
#include "secerr.h"
#include "sechash.h"
#include "cms.h"
#include "cmst.h"
// We use curl for RFC3161 time stamp requests
#include <curl/curl.h>
#endif
#ifdef _WIN32
// WinCrypt headers for PDF signing
// Note: this uses Windows 7 APIs and requires the relevant data types;
// the functions that don't exist in WinXP must be looked up at runtime!
#undef _WIN32_WINNT
#define _WIN32_WINNT _WIN32_WINNT_WIN7
#include <prewin.h>
#include <wincrypt.h>
#include <postwin.h>
#include <comphelper/windowserrorstring.hxx>
#endif
#include <config_eot.h>
#include <config_features.h>
#if ENABLE_EOT
#include <libeot/libeot.h>
#endif
using namespace vcl;
using namespace::com::sun::star;
static bool g_bDebugDisableCompression = getenv("VCL_DEBUG_DISABLE_PDFCOMPRESSION");
#if !defined(ANDROID) && HAVE_FEATURE_NSS
// Is this length truly the maximum possible, or just a number that
// seemed large enough when the author tested this (with some type of
// certificates)? I suspect the latter.
// Used to be 0x4000 = 16384, but a sample signed PDF (produced by
// some other software) provided by the customer has a signature
// content that is 30000 bytes. The SampleSignedPDFDocument.pdf from
// Adobe has one that is 21942 bytes. So let's be careful. Pity this
// can't be dynamic, at least not without restructuring the code. Also
// note that the checks in the code for this being too small
// apparently are broken, if this overflows you end up with an invalid
// PDF. Need to fix that.
#define MAX_SIGNATURE_CONTENT_LENGTH 50000
#endif
#ifdef DO_TEST_PDF
class PDFTestOutputStream : public PDFOutputStream
{
public:
virtual ~PDFTestOutputStream();
virtual void write( const css::uno::Reference< css::io::XOutputStream >& xStream );
};
PDFTestOutputStream::~PDFTestOutputStream()
{
}
void PDFTestOutputStream::write( const css::uno::Reference< css::io::XOutputStream >& xStream )
{
OString aStr( "lalala\ntest\ntest\ntest" );
css::uno::Sequence< sal_Int8 > aData( aStr.getLength() );
memcpy( aData.getArray(), aStr.getStr(), aStr.getLength() );
xStream->writeBytes( aData );
}
// this test code cannot be used to test PDF/A-1 because it forces
// control item (widgets) to bypass the structure controlling
// the embedding of such elements in actual run
void doTestCode()
{
static const char* pHome = getenv( "HOME" );
OUString aTestFile( "file://" );
aTestFile += OUString( pHome, strlen( pHome ), RTL_TEXTENCODING_MS_1252 );
aTestFile += "/pdf_export_test.pdf";
PDFWriter::PDFWriterContext aContext;
aContext.URL = aTestFile;
aContext.Version = PDFWriter::PDF_1_4;
aContext.Tagged = true;
aContext.InitialPage = 2;
aContext.DocumentInfo.Title = "PDF export test document";
aContext.DocumentInfo.Producer = "VCL";
aContext.SignPDF = true;
aContext.SignLocation = "Burdur";
aContext.SignReason = "Some valid reason to sign";
aContext.SignContact = "signer@example.com";
css::uno::Reference< css::beans::XMaterialHolder > xEnc;
PDFWriter aWriter( aContext, xEnc );
aWriter.NewPage( 595, 842 );
aWriter.BeginStructureElement( PDFWriter::Document );
// set duration of 3 sec for first page
aWriter.SetAutoAdvanceTime( 3 );
aWriter.SetMapMode( MapMode( MapUnit::Map100thMM ) );
aWriter.SetFillColor( Color( COL_LIGHTRED ) );
aWriter.SetLineColor( Color( COL_LIGHTGREEN ) );
aWriter.DrawRect( Rectangle( Point( 2000, 200 ), Size( 8000, 3000 ) ), 5000, 2000 );
aWriter.SetFont( Font( OUString( "Times" ), Size( 0, 500 ) ) );
aWriter.SetTextColor( Color( COL_BLACK ) );
aWriter.SetLineColor( Color( COL_BLACK ) );
aWriter.SetFillColor( Color( COL_LIGHTBLUE ) );
Rectangle aRect( Point( 5000, 5000 ), Size( 6000, 3000 ) );
aWriter.DrawRect( aRect );
aWriter.DrawText( aRect, OUString( "Link annot 1" ) );
sal_Int32 nFirstLink = aWriter.CreateLink( aRect );
PDFNote aNote;
aNote.Title = "A small test note";
aNote.Contents = "There is no business like show business like no business i know. Everything about it is appealing.";
aWriter.CreateNote( Rectangle( Point( aRect.Right(), aRect.Top() ), Size( 6000, 3000 ) ), aNote );
Rectangle aTargetRect( Point( 3000, 23000 ), Size( 12000, 6000 ) );
aWriter.SetFillColor( Color( COL_LIGHTGREEN ) );
aWriter.DrawRect( aTargetRect );
aWriter.DrawText( aTargetRect, "Dest second link" );
sal_Int32 nSecondDest = aWriter.CreateDest( aTargetRect );
aWriter.BeginStructureElement( PDFWriter::Section );
aWriter.BeginStructureElement( PDFWriter::Heading );
aWriter.DrawText( Point(4500, 9000), "A small structure test" );
aWriter.EndStructureElement();
aWriter.BeginStructureElement( PDFWriter::Paragraph );
aWriter.SetStructureAttribute( PDFWriter::WritingMode, PDFWriter::LrTb );
aWriter.SetStructureAttribute( PDFWriter::TextDecorationType, PDFWriter::Underline );
aWriter.DrawText( Rectangle( Point( 4500, 10000 ), Size( 12000, 6000 ) ),
"It was the best of PDF, it was the worst of PDF ... or so. This is a pretty nonsensical text to denote a paragraph. I suggest you stop reading it. Because if you read on you might get bored. So continue on your on risk. Hey, you're still here ? Why do you continue to read this as it is of no use at all ? OK, it's your time, but still... . Woah, i even get bored writing this, so let's end this here and now.",
DrawTextFlags::MultiLine | DrawTextFlags::WordBreak
);
aWriter.SetActualText( "It was the best of PDF, it was the worst of PDF ... or so. This is a pretty nonsensical text to denote a paragraph. I suggest you stop reading it. Because if you read on you might get bored. So continue on your on risk. Hey, you're still here ? Why do you continue to read this as it is of no use at all ? OK, it's your time, but still... . Woah, i even get bored writing this, so let's end this here and now." );
aWriter.SetAlternateText( "This paragraph contains some lengthy nonsense to test structural element emission of PDFWriter." );
aWriter.EndStructureElement();
aWriter.BeginStructureElement( PDFWriter::Paragraph );
aWriter.SetStructureAttribute( PDFWriter::WritingMode, PDFWriter::LrTb );
aWriter.DrawText( Rectangle( Point( 4500, 19000 ), Size( 12000, 1000 ) ),
"This paragraph is nothing special either but ends on the next page structurewise",
DrawTextFlags::MultiLine | DrawTextFlags::WordBreak
);
aWriter.NewPage( 595, 842 );
// test AddStream interface
aWriter.AddStream( "text/plain", new PDFTestOutputStream(), true );
// set transitional mode
aWriter.SetPageTransition( PDFWriter::WipeRightToLeft, 1500 );
aWriter.SetMapMode( MapMode( MapUnit::Map100thMM ) );
aWriter.SetTextColor( Color( COL_BLACK ) );
aWriter.SetFont( Font( OUString( "Times" ), Size( 0, 500 ) ) );
aWriter.DrawText( Rectangle( Point( 4500, 1500 ), Size( 12000, 3000 ) ),
"Here's where all things come to an end ... well at least the paragraph from the last page.",
DrawTextFlags::MultiLine | DrawTextFlags::WordBreak
);
aWriter.EndStructureElement();
aWriter.SetFillColor( Color( COL_LIGHTBLUE ) );
// disable structure
aWriter.BeginStructureElement( PDFWriter::NonStructElement );
aWriter.DrawRect( aRect );
aWriter.BeginStructureElement( PDFWriter::Paragraph );
aWriter.DrawText( aRect, "Link annot 2" );
sal_Int32 nSecondLink = aWriter.CreateLink( aRect );
aWriter.SetFillColor( Color( COL_LIGHTGREEN ) );
aWriter.BeginStructureElement( PDFWriter::ListItem );
aWriter.DrawRect( aTargetRect );
aWriter.DrawText( aTargetRect, "Dest first link" );
sal_Int32 nFirstDest = aWriter.CreateDest( aTargetRect );
// enable structure
aWriter.EndStructureElement();
aWriter.EndStructureElement();
aWriter.EndStructureElement();
aWriter.BeginStructureElement( PDFWriter::Figure );
aWriter.BeginStructureElement( PDFWriter::Caption );
aWriter.DrawText( Point( 4500, 9000 ), "Some drawing stuff inside the structure" );
aWriter.EndStructureElement();
// test clipping
basegfx::B2DPolyPolygon aClip;
basegfx::B2DPolygon aClipPoly;
aClipPoly.append( basegfx::B2DPoint( 8250, 9600 ) );
aClipPoly.append( basegfx::B2DPoint( 16500, 11100 ) );
aClipPoly.append( basegfx::B2DPoint( 8250, 12600 ) );
aClipPoly.append( basegfx::B2DPoint( 4500, 11100 ) );
aClipPoly.setClosed( true );
aClip.append( aClipPoly );
aWriter.Push( PushFlags::CLIPREGION | PushFlags::FILLCOLOR );
aWriter.SetClipRegion( aClip );
aWriter.DrawEllipse( Rectangle( Point( 4500, 9600 ), Size( 12000, 3000 ) ) );
aWriter.MoveClipRegion( 1000, 500 );
aWriter.SetFillColor( Color( COL_RED ) );
aWriter.DrawEllipse( Rectangle( Point( 4500, 9600 ), Size( 12000, 3000 ) ) );
aWriter.Pop();
// test transparency
// draw background
Rectangle aTranspRect( Point( 7500, 13500 ), Size( 9000, 6000 ) );
aWriter.SetFillColor( Color( COL_LIGHTRED ) );
aWriter.DrawRect( aTranspRect );
aWriter.BeginTransparencyGroup();
aWriter.SetFillColor( Color( COL_LIGHTGREEN ) );
aWriter.DrawEllipse( aTranspRect );
aWriter.SetTextColor( Color( COL_LIGHTBLUE ) );
aWriter.DrawText( aTranspRect,
"Some transparent text",
DrawTextFlags::Center | DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak );
aWriter.EndTransparencyGroup( aTranspRect, 50 );
// prepare an alpha mask
Bitmap aTransMask( Size( 256, 256 ), 8, &Bitmap::GetGreyPalette( 256 ) );
Bitmap::ScopedWriteAccess pAcc(aTransMask);
for( int nX = 0; nX < 256; nX++ )
for( int nY = 0; nY < 256; nY++ )
pAcc->SetPixel( nX, nY, BitmapColor( (sal_uInt8)((nX+nY)/2) ) );
pAcc.reset();
aTransMask.SetPrefMapMode( MapUnit::MapMM );
aTransMask.SetPrefSize( Size( 10, 10 ) );
aWriter.DrawBitmap( Point( 600, 13500 ), Size( 3000, 3000 ), aTransMask );
aTranspRect = Rectangle( Point( 4200, 13500 ), Size( 3000, 3000 ) );
aWriter.SetFillColor( Color( COL_LIGHTRED ) );
aWriter.DrawRect( aTranspRect );
aWriter.SetFillColor( Color( COL_LIGHTGREEN ) );
aWriter.DrawEllipse( aTranspRect );
aWriter.SetTextColor( Color( COL_LIGHTBLUE ) );
aWriter.DrawText( aTranspRect,
"Some transparent text",
DrawTextFlags::Center | DrawTextFlags::VCenter | DrawTextFlags::MultiLine | DrawTextFlags::WordBreak );
aTranspRect = Rectangle( Point( 1500, 16500 ), Size( 4800, 3000 ) );
aWriter.SetFillColor( Color( COL_LIGHTRED ) );
aWriter.DrawRect( aTranspRect );
Bitmap aImageBmp( Size( 256, 256 ), 24 );
pAcc = Bitmap::ScopedWriteAccess(aImageBmp);
pAcc->SetFillColor( Color( 0xff, 0, 0xff ) );
pAcc->FillRect( Rectangle( Point( 0, 0 ), Size( 256, 256 ) ) );
pAcc.reset();
BitmapEx aBmpEx( aImageBmp, AlphaMask( aTransMask ) );
aWriter.DrawBitmapEx( Point( 1500, 19500 ), Size( 4800, 3000 ), aBmpEx );
aWriter.EndStructureElement();
aWriter.EndStructureElement();
LineInfo aLI( LineStyle::Dash, 3 );
aLI.SetDashCount( 2 );
aLI.SetDashLen( 50 );
aLI.SetDotCount( 2 );
aLI.SetDotLen( 25 );
aLI.SetDistance( 15 );
Point aLIPoints[] = { Point( 4000, 10000 ),
Point( 8000, 12000 ),
Point( 3000, 19000 ) };
tools::Polygon aLIPoly( 3, aLIPoints );
aWriter.SetLineColor( Color( COL_BLUE ) );
aWriter.SetFillColor();
aWriter.DrawPolyLine( aLIPoly, aLI );
aLI.SetDashCount( 4 );
aLIPoly.Move( 1000, 1000 );
aWriter.DrawPolyLine( aLIPoly, aLI );
aWriter.NewPage( 595, 842 );
aWriter.SetMapMode( MapMode( MapUnit::Map100thMM ) );
Wallpaper aWall( aTransMask );
aWall.SetStyle( WallpaperStyle::Tile );
aWriter.DrawWallpaper( Rectangle( Point( 4400, 4200 ), Size( 10200, 6300 ) ), aWall );
aWriter.NewPage( 595, 842 );
aWriter.SetMapMode( MapMode( MapUnit::Map100thMM ) );
aWriter.SetFont( Font( OUString( "Times" ), Size( 0, 500 ) ) );
aWriter.SetTextColor( Color( COL_BLACK ) );
aRect = Rectangle( Point( 4500, 6000 ), Size( 6000, 1500 ) );
aWriter.DrawRect( aRect );
aWriter.DrawText( aRect, "www.heise.de" );
sal_Int32 nURILink = aWriter.CreateLink( aRect );
aWriter.SetLinkURL( nURILink, OUString( "http://www.heise.de" ) );
aWriter.SetLinkDest( nFirstLink, nFirstDest );
aWriter.SetLinkDest( nSecondLink, nSecondDest );
// include a button
PDFWriter::PushButtonWidget aBtn;
aBtn.Name = "testButton";
aBtn.Description = "A test button";
aBtn.Text = "hit me";
aBtn.Location = Rectangle( Point( 4500, 9000 ), Size( 4500, 3000 ) );
aBtn.Border = aBtn.Background = true;
aWriter.CreateControl( aBtn );
// include a uri button
PDFWriter::PushButtonWidget aUriBtn;
aUriBtn.Name = "wwwButton";
aUriBtn.Description = "A URI button";
aUriBtn.Text = "to www";
aUriBtn.Location = Rectangle( Point( 9500, 9000 ), Size( 4500, 3000 ) );
aUriBtn.Border = aUriBtn.Background = true;
aUriBtn.URL = "http://www.heise.de";
aWriter.CreateControl( aUriBtn );
// include a dest button
PDFWriter::PushButtonWidget aDstBtn;
aDstBtn.Name = "destButton";
aDstBtn.Description = "A Dest button";
aDstBtn.Text = "to paragraph";
aDstBtn.Location = Rectangle( Point( 14500, 9000 ), Size( 4500, 3000 ) );
aDstBtn.Border = aDstBtn.Background = true;
aDstBtn.Dest = nFirstDest;
aWriter.CreateControl( aDstBtn );
PDFWriter::CheckBoxWidget aCBox;
aCBox.Name = "textCheckBox";
aCBox.Description = "A test check box";
aCBox.Text = "check me";
aCBox.Location = Rectangle( Point( 4500, 13500 ), Size( 3000, 750 ) );
aCBox.Checked = true;
aCBox.Border = aCBox.Background = false;
aWriter.CreateControl( aCBox );
PDFWriter::CheckBoxWidget aCBox2;
aCBox2.Name = "textCheckBox2";
aCBox2.Description = "Another test check box";
aCBox2.Text = "check me right";
aCBox2.Location = Rectangle( Point( 4500, 14250 ), Size( 3000, 750 ) );
aCBox2.Checked = true;
aCBox2.Border = aCBox2.Background = false;
aCBox2.ButtonIsLeft = false;
aWriter.CreateControl( aCBox2 );
PDFWriter::RadioButtonWidget aRB1;
aRB1.Name = "rb1_1";
aRB1.Description = "radio 1 button 1";
aRB1.Text = "Despair";
aRB1.Location = Rectangle( Point( 4500, 15000 ), Size( 6000, 1000 ) );
aRB1.Selected = true;
aRB1.RadioGroup = 1;
aRB1.Border = aRB1.Background = true;
aRB1.ButtonIsLeft = false;
aRB1.BorderColor = Color( COL_LIGHTGREEN );
aRB1.BackgroundColor = Color( COL_LIGHTBLUE );
aRB1.TextColor = Color( COL_LIGHTRED );
aRB1.TextFont = Font( OUString( "Courier" ), Size( 0, 800 ) );
aWriter.CreateControl( aRB1 );
PDFWriter::RadioButtonWidget aRB2;
aRB2.Name = "rb2_1";
aRB2.Description = "radio 2 button 1";
aRB2.Text = "Joy";
aRB2.Location = Rectangle( Point( 10500, 15000 ), Size( 3000, 1000 ) );
aRB2.Selected = true;
aRB2.RadioGroup = 2;
aWriter.CreateControl( aRB2 );
PDFWriter::RadioButtonWidget aRB3;
aRB3.Name = "rb1_2";
aRB3.Description = "radio 1 button 2";
aRB3.Text = "Desperation";
aRB3.Location = Rectangle( Point( 4500, 16000 ), Size( 3000, 1000 ) );
aRB3.Selected = true;
aRB3.RadioGroup = 1;
aWriter.CreateControl( aRB3 );
PDFWriter::EditWidget aEditBox;
aEditBox.Name = "testEdit";
aEditBox.Description = "A test edit field";
aEditBox.Text = "A little test text";
aEditBox.TextStyle = DrawTextFlags::Left | DrawTextFlags::VCenter;
aEditBox.Location = Rectangle( Point( 10000, 18000 ), Size( 5000, 1500 ) );
aEditBox.MaxLen = 100;
aEditBox.Border = aEditBox.Background = true;
aEditBox.BorderColor = Color( COL_BLACK );
aWriter.CreateControl( aEditBox );
// normal list box
PDFWriter::ListBoxWidget aLstBox;
aLstBox.Name = "testListBox";
aLstBox.Text = "One";
aLstBox.Description = "select me";
aLstBox.Location = Rectangle( Point( 4500, 18000 ), Size( 3000, 1500 ) );
aLstBox.Sort = true;
aLstBox.MultiSelect = true;
aLstBox.Border = aLstBox.Background = true;
aLstBox.BorderColor = Color( COL_BLACK );
aLstBox.Entries.push_back( OUString( "One" ) );
aLstBox.Entries.push_back( OUString( "Two" ) );
aLstBox.Entries.push_back( OUString( "Three" ) );
aLstBox.Entries.push_back( OUString( "Four" ) );
aLstBox.SelectedEntries.push_back( 1 );
aLstBox.SelectedEntries.push_back( 2 );
aWriter.CreateControl( aLstBox );
// dropdown list box
aLstBox.Name = "testDropDownListBox";
aLstBox.DropDown = true;
aLstBox.Location = Rectangle( Point( 4500, 19500 ), Size( 3000, 500 ) );
aWriter.CreateControl( aLstBox );
// combo box
PDFWriter::ComboBoxWidget aComboBox;
aComboBox.Name = "testComboBox";
aComboBox.Text = "test a combobox";
aComboBox.Entries.push_back( OUString( "Larry" ) );
aComboBox.Entries.push_back( OUString( "Curly" ) );
aComboBox.Entries.push_back( OUString( "Moe" ) );
aComboBox.Location = Rectangle( Point( 4500, 20000 ), Size( 3000, 500 ) );
aWriter.CreateControl( aComboBox );
// test outlines
sal_Int32 nPage1OL = aWriter.CreateOutlineItem();
aWriter.SetOutlineItemText( nPage1OL, OUString( "Page 1" ) );
aWriter.SetOutlineItemDest( nPage1OL, nSecondDest );
aWriter.CreateOutlineItem( nPage1OL, OUString( "Dest 2" ), nSecondDest );
aWriter.CreateOutlineItem( nPage1OL, OUString( "Dest 2 revisited" ), nSecondDest );
aWriter.CreateOutlineItem( nPage1OL, OUString( "Dest 2 again" ), nSecondDest );
sal_Int32 nPage2OL = aWriter.CreateOutlineItem();
aWriter.SetOutlineItemText( nPage2OL, OUString( "Page 2" ) );
aWriter.CreateOutlineItem( nPage2OL, OUString( "Dest 1" ), nFirstDest );
aWriter.EndStructureElement(); // close document
aWriter.Emit();
}
#endif
static const sal_Int32 nLog10Divisor = 1;
static const double fDivisor = 10.0;
static inline double pixelToPoint( double px ) { return px/fDivisor; }
static inline sal_Int32 pointToPixel( double pt ) { return sal_Int32(pt*fDivisor); }
const sal_uInt8 PDFWriterImpl::s_nPadString[32] =
{
0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A
};
static void appendHex( sal_Int8 nInt, OStringBuffer& rBuffer )
{
static const sal_Char pHexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
rBuffer.append( pHexDigits[ (nInt >> 4) & 15 ] );
rBuffer.append( pHexDigits[ nInt & 15 ] );
}
static void appendName( const OUString& rStr, OStringBuffer& rBuffer )
{
// FIXME i59651 add a check for max length of 127 chars? Per PDF spec 1.4, appendix C.1
// I guess than when reading the #xx sequence it will count for a single character.
OString aStr( OUStringToOString( rStr, RTL_TEXTENCODING_UTF8 ) );
const sal_Char* pStr = aStr.getStr();
int nLen = aStr.getLength();
for( int i = 0; i < nLen; i++ )
{
/* #i16920# PDF recommendation: output UTF8, any byte
* outside the interval [33(=ASCII'!');126(=ASCII'~')]
* should be escaped hexadecimal
* for the sake of ghostscript which also reads PDF
* but has a narrower acceptance rate we only pass
* alphanumerics and '-' literally.
*/
if( (pStr[i] >= 'A' && pStr[i] <= 'Z' ) ||
(pStr[i] >= 'a' && pStr[i] <= 'z' ) ||
(pStr[i] >= '0' && pStr[i] <= '9' ) ||
pStr[i] == '-' )
{
rBuffer.append( pStr[i] );
}
else
{
rBuffer.append( '#' );
appendHex( (sal_Int8)pStr[i], rBuffer );
}
}
}
static void appendName( const sal_Char* pStr, OStringBuffer& rBuffer )
{
//FIXME i59651 see above
while( pStr && *pStr )
{
if( (*pStr >= 'A' && *pStr <= 'Z' ) ||
(*pStr >= 'a' && *pStr <= 'z' ) ||
(*pStr >= '0' && *pStr <= '9' ) ||
*pStr == '-' )
{
rBuffer.append( *pStr );
}
else
{
rBuffer.append( '#' );
appendHex( (sal_Int8)*pStr, rBuffer );
}
pStr++;
}
}
//used only to emit encoded passwords
static void appendLiteralString( const sal_Char* pStr, sal_Int32 nLength, OStringBuffer& rBuffer )
{
while( nLength )
{
switch( *pStr )
{
case '\n' :
rBuffer.append( "\\n" );
break;
case '\r' :
rBuffer.append( "\\r" );
break;
case '\t' :
rBuffer.append( "\\t" );
break;
case '\b' :
rBuffer.append( "\\b" );
break;
case '\f' :
rBuffer.append( "\\f" );
break;
case '(' :
case ')' :
case '\\' :
rBuffer.append( "\\" );
rBuffer.append( (sal_Char) *pStr );
break;
default:
rBuffer.append( (sal_Char) *pStr );
break;
}
pStr++;
nLength--;
}
}
/**--->i56629
* Convert a string before using it.
*
* This string conversion function is needed because the destination name
* in a PDF file seen through an Internet browser should be
* specially crafted, in order to be used directly by the browser.
* In this way the fragment part of a hyperlink to a PDF file (e.g. something
* as 'test1/test2/a-file.pdf\#thefragment) will be (hopefully) interpreted by the
* PDF reader (currently only Adobe Reader plug-in seems to be working that way) called
* from inside the Internet browser as: 'open the file test1/test2/a-file.pdf
* and go to named destination thefragment using default zoom'.
* The conversion is needed because in case of a fragment in the form: Slide%201
* (meaning Slide 1) as it is converted obeying the Inet rules, it will become Slide25201
* using this conversion, in both the generated named destinations, fragment and GoToR
* destination.
*
* The names for destinations are name objects and so they don't need to be encrypted
* even though they expose the content of PDF file (e.g. guessing the PDF content from the
* destination name).
*
* Further limitation: it is advisable to use standard ASCII characters for
* OOo bookmarks.
*/
static void appendDestinationName( const OUString& rString, OStringBuffer& rBuffer )
{
const sal_Unicode* pStr = rString.getStr();
sal_Int32 nLen = rString.getLength();
for( int i = 0; i < nLen; i++ )
{
sal_Unicode aChar = pStr[i];
if( (aChar >= '0' && aChar <= '9' ) ||
(aChar >= 'a' && aChar <= 'z' ) ||
(aChar >= 'A' && aChar <= 'Z' ) ||
aChar == '-' )
{
rBuffer.append((sal_Char)aChar);
}
else
{
sal_Int8 aValueHigh = sal_Int8(aChar >> 8);
if(aValueHigh > 0)
appendHex( aValueHigh, rBuffer );
appendHex( (sal_Int8)(aChar & 255 ), rBuffer );
}
}
}
//<--- i56629
void PDFWriter::AppendUnicodeTextString(const OUString& rString, OStringBuffer& rBuffer)
{
rBuffer.append( "FEFF" );
const sal_Unicode* pStr = rString.getStr();
sal_Int32 nLen = rString.getLength();
for( int i = 0; i < nLen; i++ )
{
sal_Unicode aChar = pStr[i];
appendHex( (sal_Int8)(aChar >> 8), rBuffer );
appendHex( (sal_Int8)(aChar & 255 ), rBuffer );
}
}
void PDFWriterImpl::createWidgetFieldName( sal_Int32 i_nWidgetIndex, const PDFWriter::AnyWidget& i_rControl )
{
/* #i80258# previously we use appendName here
however we need a slightly different coding scheme than the normal
name encoding for field names
*/
const OUString& rName = (m_aContext.Version > PDFWriter::PDFVersion::PDF_1_2) ? i_rControl.Name : i_rControl.Text;
OString aStr( OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ) );
const sal_Char* pStr = aStr.getStr();
int nLen = aStr.getLength();
OStringBuffer aBuffer( rName.getLength()+64 );
for( int i = 0; i < nLen; i++ )
{
/* #i16920# PDF recommendation: output UTF8, any byte
* outside the interval [32(=ASCII' ');126(=ASCII'~')]
* should be escaped hexadecimal
*/
if( (pStr[i] >= 32 && pStr[i] <= 126 ) )
aBuffer.append( pStr[i] );
else
{
aBuffer.append( '#' );
appendHex( (sal_Int8)pStr[i], aBuffer );
}
}
OString aFullName( aBuffer.makeStringAndClear() );
/* #i82785# create hierarchical fields down to the for each dot in i_rName */
sal_Int32 nTokenIndex = 0, nLastTokenIndex = 0;
OString aPartialName;
OString aDomain;
do
{
nLastTokenIndex = nTokenIndex;
aPartialName = aFullName.getToken( 0, '.', nTokenIndex );
if( nTokenIndex != -1 )
{
// find or create a hierarchical field
// first find the fully qualified name up to this field
aDomain = aFullName.copy( 0, nTokenIndex-1 );
std::unordered_map< OString, sal_Int32, OStringHash >::const_iterator it = m_aFieldNameMap.find( aDomain );
if( it == m_aFieldNameMap.end() )
{
// create new hierarchy field
sal_Int32 nNewWidget = m_aWidgets.size();
m_aWidgets.push_back( PDFWidget() );
m_aWidgets[nNewWidget].m_nObject = createObject();
m_aWidgets[nNewWidget].m_eType = PDFWriter::Hierarchy;
m_aWidgets[nNewWidget].m_aName = aPartialName;
m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
m_aFieldNameMap[aDomain] = nNewWidget;
m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject;
if( nLastTokenIndex > 0 )
{
// this field is not a root field and
// needs to be inserted to its parent
OString aParentDomain( aDomain.copy( 0, nLastTokenIndex-1 ) );
it = m_aFieldNameMap.find( aParentDomain );
OSL_ENSURE( it != m_aFieldNameMap.end(), "field name not found" );
if( it != m_aFieldNameMap.end() )
{
OSL_ENSURE( it->second < sal_Int32(m_aWidgets.size()), "invalid field number entry" );
if( it->second < sal_Int32(m_aWidgets.size()) )
{
PDFWidget& rParentField( m_aWidgets[it->second] );
rParentField.m_aKids.push_back( m_aWidgets[nNewWidget].m_nObject );
rParentField.m_aKidsIndex.push_back( nNewWidget );
m_aWidgets[nNewWidget].m_nParent = rParentField.m_nObject;
}
}
}
}
else if( m_aWidgets[it->second].m_eType != PDFWriter::Hierarchy )
{
// this is invalid, someone tries to have a terminal field as parent
// example: a button with the name foo.bar exists and
// another button is named foo.bar.no
// workaround: put the second terminal field as much up in the hierarchy as
// necessary to have a non-terminal field as parent (or none at all)
// since it->second already is terminal, we just need to use its parent
aDomain.clear();
aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
if( nLastTokenIndex > 0 )
{
aDomain = aFullName.copy( 0, nLastTokenIndex-1 );
OStringBuffer aBuf( aDomain.getLength() + 1 + aPartialName.getLength() );
aBuf.append( aDomain );
aBuf.append( '.' );
aBuf.append( aPartialName );
aFullName = aBuf.makeStringAndClear();
}
else
aFullName = aPartialName;
break;
}
}
} while( nTokenIndex != -1 );
// insert widget into its hierarchy field
if( !aDomain.isEmpty() )
{
std::unordered_map< OString, sal_Int32, OStringHash >::const_iterator it = m_aFieldNameMap.find( aDomain );
if( it != m_aFieldNameMap.end() )
{
OSL_ENSURE( it->second >= 0 && it->second < sal_Int32( m_aWidgets.size() ), "invalid field index" );
if( it->second >= 0 && it->second < sal_Int32(m_aWidgets.size()) )
{
m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[it->second].m_nObject;
m_aWidgets[it->second].m_aKids.push_back( m_aWidgets[i_nWidgetIndex].m_nObject);
m_aWidgets[it->second].m_aKidsIndex.push_back( i_nWidgetIndex );
}
}
}
if( aPartialName.isEmpty() )
{
// how funny, an empty field name
if( i_rControl.getType() == PDFWriter::RadioButton )
{
aPartialName = "RadioGroup";
aPartialName += OString::number( static_cast<const PDFWriter::RadioButtonWidget&>(i_rControl).RadioGroup );
}
else
aPartialName = OString( "Widget" );
}
if( ! m_aContext.AllowDuplicateFieldNames )
{
std::unordered_map<OString, sal_Int32, OStringHash>::iterator it = m_aFieldNameMap.find( aFullName );
if( it != m_aFieldNameMap.end() ) // not unique
{
std::unordered_map< OString, sal_Int32, OStringHash >::const_iterator check_it;
OString aTry;
sal_Int32 nTry = 2;
do
{
OStringBuffer aUnique( aFullName.getLength() + 16 );
aUnique.append( aFullName );
aUnique.append( '_' );
aUnique.append( nTry++ );
aTry = aUnique.makeStringAndClear();
check_it = m_aFieldNameMap.find( aTry );
} while( check_it != m_aFieldNameMap.end() );
aFullName = aTry;
m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 );
}
else
m_aFieldNameMap[ aFullName ] = i_nWidgetIndex;
}
// finally
m_aWidgets[i_nWidgetIndex].m_aName = aPartialName;
}
static void appendFixedInt( sal_Int32 nValue, OStringBuffer& rBuffer )
{
if( nValue < 0 )
{
rBuffer.append( '-' );
nValue = -nValue;
}
const sal_Int32 nFactor = 10;
const sal_Int32 nInt = nValue / nFactor;
rBuffer.append( nInt );
sal_Int32 nDecimal = nValue % nFactor;
if (nDecimal)
{
rBuffer.append('.');
rBuffer.append(nDecimal);
}
}
// appends a double. PDF does not accept exponential format, only fixed point
static void appendDouble( double fValue, OStringBuffer& rBuffer, sal_Int32 nPrecision = 5 )
{
bool bNeg = false;
if( fValue < 0.0 )
{
bNeg = true;
fValue=-fValue;
}
sal_Int64 nInt = (sal_Int64)fValue;
fValue -= (double)nInt;
// optimizing hardware may lead to a value of 1.0 after the subtraction
if( rtl::math::approxEqual(fValue, 1.0) || log10( 1.0-fValue ) <= -nPrecision )
{
nInt++;
fValue = 0.0;
}
sal_Int64 nFrac = 0;
if( fValue )
{
fValue *= pow( 10.0, (double)nPrecision );
nFrac = (sal_Int64)fValue;
}
if( bNeg && ( nInt || nFrac ) )
rBuffer.append( '-' );
rBuffer.append( nInt );
if( nFrac )
{
int i;
rBuffer.append( '.' );
sal_Int64 nBound = (sal_Int64)(pow( 10.0, nPrecision - 1.0 )+0.5);
for ( i = 0; ( i < nPrecision ) && nFrac; i++ )
{
sal_Int64 nNumb = nFrac / nBound;
nFrac -= nNumb * nBound;
rBuffer.append( nNumb );
nBound /= 10;
}
}
}
static void appendColor( const Color& rColor, OStringBuffer& rBuffer, bool bConvertToGrey )
{
if( rColor != Color( COL_TRANSPARENT ) )
{
if( bConvertToGrey )
{
sal_uInt8 cByte = rColor.GetLuminance();
appendDouble( (double)cByte / 255.0, rBuffer );
}
else
{
appendDouble( (double)rColor.GetRed() / 255.0, rBuffer );
rBuffer.append( ' ' );
appendDouble( (double)rColor.GetGreen() / 255.0, rBuffer );
rBuffer.append( ' ' );
appendDouble( (double)rColor.GetBlue() / 255.0, rBuffer );
}
}
}
void PDFWriterImpl::appendStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
{
if( rColor != Color( COL_TRANSPARENT ) )
{
bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
appendColor( rColor, rBuffer, bGrey );
rBuffer.append( bGrey ? " G" : " RG" );
}
}
void PDFWriterImpl::appendNonStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
{
if( rColor != Color( COL_TRANSPARENT ) )
{
bool bGrey = m_aContext.ColorMode == PDFWriter::DrawGreyscale;
appendColor( rColor, rBuffer, bGrey );
rBuffer.append( bGrey ? " g" : " rg" );
}
}
// matrix helper class
// TODO: use basegfx matrix class instead or derive from it
namespace vcl // TODO: use anonymous namespace to keep this class local
{
/* for sparse matrices of the form (2D linear transformations)
* f[0] f[1] 0
* f[2] f[3] 0
* f[4] f[5] 1
*/
class Matrix3
{
double f[6];
void set( double *pn ) { for( int i = 0 ; i < 6; i++ ) f[i] = pn[i]; }
public:
Matrix3();
void skew( double alpha, double beta );
void scale( double sx, double sy );
void rotate( double angle );
void translate( double tx, double ty );
void invert();
void append( PDFWriterImpl::PDFPage& rPage, OStringBuffer& rBuffer );
Point transform( const Point& rPoint ) const;
};
}
Matrix3::Matrix3()
{
// initialize to unity
f[0] = 1.0;
f[1] = 0.0;
f[2] = 0.0;
f[3] = 1.0;
f[4] = 0.0;
f[5] = 0.0;
}
Point Matrix3::transform( const Point& rOrig ) const
{
double x = (double)rOrig.X(), y = (double)rOrig.Y();
return Point( (int)(x*f[0] + y*f[2] + f[4]), (int)(x*f[1] + y*f[3] + f[5]) );
}
void Matrix3::skew( double alpha, double beta )
{
double fn[6];
double tb = tan( beta );
fn[0] = f[0] + f[2]*tb;
fn[1] = f[1];
fn[2] = f[2] + f[3]*tb;
fn[3] = f[3];
fn[4] = f[4] + f[5]*tb;
fn[5] = f[5];
if( alpha != 0.0 )
{
double ta = tan( alpha );
fn[1] += f[0]*ta;
fn[3] += f[2]*ta;
fn[5] += f[4]*ta;
}
set( fn );
}
void Matrix3::scale( double sx, double sy )
{
double fn[6];
fn[0] = sx*f[0];
fn[1] = sy*f[1];
fn[2] = sx*f[2];
fn[3] = sy*f[3];
fn[4] = sx*f[4];
fn[5] = sy*f[5];
set( fn );
}
void Matrix3::rotate( double angle )
{
double fn[6];
double fSin = sin(angle);
double fCos = cos(angle);
fn[0] = f[0]*fCos - f[1]*fSin;
fn[1] = f[0]*fSin + f[1]*fCos;
fn[2] = f[2]*fCos - f[3]*fSin;
fn[3] = f[2]*fSin + f[3]*fCos;
fn[4] = f[4]*fCos - f[5]*fSin;
fn[5] = f[4]*fSin + f[5]*fCos;
set( fn );
}
void Matrix3::translate( double tx, double ty )
{
f[4] += tx;
f[5] += ty;
}
void Matrix3::invert()
{
// short circuit trivial cases
if( f[1]==f[2] && f[1]==0.0 && f[0]==f[3] && f[0]==1.0 )
{
f[4] = -f[4];
f[5] = -f[5];
return;
}
// check determinant
const double fDet = f[0]*f[3]-f[1]*f[2];
if( fDet == 0.0 )
return;
// invert the matrix
double fn[6];
fn[0] = +f[3] / fDet;
fn[1] = -f[1] / fDet;
fn[2] = -f[2] / fDet;
fn[3] = +f[0] / fDet;
// apply inversion to translation
fn[4] = -(f[4]*fn[0] + f[5]*fn[2]);
fn[5] = -(f[4]*fn[1] + f[5]*fn[3]);
set( fn );
}
void Matrix3::append( PDFWriterImpl::PDFPage& rPage, OStringBuffer& rBuffer )
{
appendDouble( f[0], rBuffer );
rBuffer.append( ' ' );
appendDouble( f[1], rBuffer );
rBuffer.append( ' ' );
appendDouble( f[2], rBuffer );
rBuffer.append( ' ' );
appendDouble( f[3], rBuffer );
rBuffer.append( ' ' );
rPage.appendPoint( Point( (long)f[4], (long)f[5] ), rBuffer );
}
static void appendResourceMap( OStringBuffer& rBuf, const char* pPrefix, const PDFWriterImpl::ResourceMap& rList )
{
if( rList.empty() )
return;
rBuf.append( '/' );
rBuf.append( pPrefix );
rBuf.append( "<<" );
int ni = 0;
for( PDFWriterImpl::ResourceMap::const_iterator it = rList.begin(); it != rList.end(); ++it )
{
if( !it->first.isEmpty() && it->second > 0 )
{
rBuf.append( '/' );
rBuf.append( it->first );
rBuf.append( ' ' );
rBuf.append( it->second );
rBuf.append( " 0 R" );
if( ((++ni) & 7) == 0 )
rBuf.append( '\n' );
}
}
rBuf.append( ">>\n" );
}
void PDFWriterImpl::ResourceDict::append( OStringBuffer& rBuf, sal_Int32 nFontDictObject )
{
rBuf.append( "<</Font " );
rBuf.append( nFontDictObject );
rBuf.append( " 0 R\n" );
appendResourceMap( rBuf, "XObject", m_aXObjects );
appendResourceMap( rBuf, "ExtGState", m_aExtGStates );
appendResourceMap( rBuf, "Shading", m_aShadings );
appendResourceMap( rBuf, "Pattern", m_aPatterns );
rBuf.append( "/ProcSet[/PDF/Text" );
if( !m_aXObjects.empty() )
rBuf.append( "/ImageC/ImageI/ImageB" );
rBuf.append( "]\n>>\n" );
};
PDFWriterImpl::PDFPage::PDFPage( PDFWriterImpl* pWriter, sal_Int32 nPageWidth, sal_Int32 nPageHeight, PDFWriter::Orientation eOrientation )
:
m_pWriter( pWriter ),
m_nPageWidth( nPageWidth ),
m_nPageHeight( nPageHeight ),
m_eOrientation( eOrientation ),
m_nPageObject( 0 ), // invalid object number
m_nPageIndex( -1 ), // invalid index
m_nStreamLengthObject( 0 ),
m_nBeginStreamPos( 0 ),
m_eTransition( PDFWriter::PageTransition::Regular ),
m_nTransTime( 0 ),
m_nDuration( 0 ),
m_bHasWidgets( false )
{
// object ref must be only ever updated in emit()
m_nPageObject = m_pWriter->createObject();
}
PDFWriterImpl::PDFPage::~PDFPage()
{
}
void PDFWriterImpl::PDFPage::beginStream()
{
#if OSL_DEBUG_LEVEL > 1
{
OStringBuffer aLine( "PDFWriterImpl::PDFPage::beginStream, +" );
m_pWriter->emitComment( aLine.getStr() );
}
#endif
m_aStreamObjects.push_back(m_pWriter->createObject());
if( ! m_pWriter->updateObject( m_aStreamObjects.back() ) )
return;
m_nStreamLengthObject = m_pWriter->createObject();
// write content stream header
OStringBuffer aLine;
aLine.append( m_aStreamObjects.back() );
aLine.append( " 0 obj\n<</Length " );
aLine.append( m_nStreamLengthObject );
aLine.append( " 0 R" );
if (!g_bDebugDisableCompression)
aLine.append( "/Filter/FlateDecode" );
aLine.append( ">>\nstream\n" );
if( ! m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() ) )
return;
if (osl::File::E_None != m_pWriter->m_aFile.getPos(m_nBeginStreamPos))
{
m_pWriter->m_aFile.close();
m_pWriter->m_bOpen = false;
}
if (!g_bDebugDisableCompression)
m_pWriter->beginCompression();
m_pWriter->checkAndEnableStreamEncryption( m_aStreamObjects.back() );
}
void PDFWriterImpl::PDFPage::endStream()
{
if (!g_bDebugDisableCompression)
m_pWriter->endCompression();
sal_uInt64 nEndStreamPos;
if (osl::File::E_None != m_pWriter->m_aFile.getPos(nEndStreamPos))
{
m_pWriter->m_aFile.close();
m_pWriter->m_bOpen = false;
return;
}
m_pWriter->disableStreamEncryption();
if( ! m_pWriter->writeBuffer( "\nendstream\nendobj\n\n", 19 ) )
return;
// emit stream length object
if( ! m_pWriter->updateObject( m_nStreamLengthObject ) )
return;
OStringBuffer aLine;
aLine.append( m_nStreamLengthObject );
aLine.append( " 0 obj\n" );
aLine.append( (sal_Int64)(nEndStreamPos-m_nBeginStreamPos) );
aLine.append( "\nendobj\n\n" );
m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() );
}
bool PDFWriterImpl::PDFPage::emit(sal_Int32 nParentObject )
{
// emit page object
if( ! m_pWriter->updateObject( m_nPageObject ) )
return false;
OStringBuffer aLine;
aLine.append( m_nPageObject );
aLine.append( " 0 obj\n"
"<</Type/Page/Parent " );
aLine.append( nParentObject );
aLine.append( " 0 R" );
aLine.append( "/Resources " );
aLine.append( m_pWriter->getResourceDictObj() );
aLine.append( " 0 R" );
if( m_nPageWidth && m_nPageHeight )
{
aLine.append( "/MediaBox[0 0 " );
aLine.append( m_nPageWidth );
aLine.append( ' ' );
aLine.append( m_nPageHeight );
aLine.append( "]" );
}
switch( m_eOrientation )
{
case PDFWriter::Orientation::Portrait: aLine.append( "/Rotate 0\n" );break;
case PDFWriter::Orientation::Inherit: break;
}
int nAnnots = m_aAnnotations.size();
if( nAnnots > 0 )
{
aLine.append( "/Annots[\n" );
for( int i = 0; i < nAnnots; i++ )
{
aLine.append( m_aAnnotations[i] );
aLine.append( " 0 R" );
aLine.append( ((i+1)%15) ? " " : "\n" );
}
aLine.append( "]\n" );
}
if( m_aMCIDParents.size() > 0 )
{
OStringBuffer aStructParents( 1024 );
aStructParents.append( "[ " );
int nParents = m_aMCIDParents.size();
for( int i = 0; i < nParents; i++ )
{
aStructParents.append( m_aMCIDParents[i] );
aStructParents.append( " 0 R" );
aStructParents.append( ((i%10) == 9) ? "\n" : " " );
}
aStructParents.append( "]" );
m_pWriter->m_aStructParentTree.push_back( aStructParents.makeStringAndClear() );
aLine.append( "/StructParents " );
aLine.append( sal_Int32(m_pWriter->m_aStructParentTree.size()-1) );
aLine.append( "\n" );
}
if( m_nDuration > 0 )
{
aLine.append( "/Dur " );
aLine.append( (sal_Int32)m_nDuration );
aLine.append( "\n" );
}
if( m_eTransition != PDFWriter::PageTransition::Regular && m_nTransTime > 0 )
{
// transition duration
aLine.append( "/Trans<</D " );
appendDouble( (double)m_nTransTime/1000.0, aLine, 3 );
aLine.append( "\n" );
const char *pStyle = nullptr, *pDm = nullptr, *pM = nullptr, *pDi = nullptr;
switch( m_eTransition )
{
case PDFWriter::PageTransition::SplitHorizontalInward:
pStyle = "Split"; pDm = "H"; pM = "I"; break;
case PDFWriter::PageTransition::SplitHorizontalOutward:
pStyle = "Split"; pDm = "H"; pM = "O"; break;
case PDFWriter::PageTransition::SplitVerticalInward:
pStyle = "Split"; pDm = "V"; pM = "I"; break;
case PDFWriter::PageTransition::SplitVerticalOutward:
pStyle = "Split"; pDm = "V"; pM = "O"; break;
case PDFWriter::PageTransition::BlindsHorizontal:
pStyle = "Blinds"; pDm = "H"; break;
case PDFWriter::PageTransition::BlindsVertical:
pStyle = "Blinds"; pDm = "V"; break;
case PDFWriter::PageTransition::BoxInward:
pStyle = "Box"; pM = "I"; break;
case PDFWriter::PageTransition::BoxOutward:
pStyle = "Box"; pM = "O"; break;
case PDFWriter::PageTransition::WipeLeftToRight:
pStyle = "Wipe"; pDi = "0"; break;
case PDFWriter::PageTransition::WipeBottomToTop:
pStyle = "Wipe"; pDi = "90"; break;
case PDFWriter::PageTransition::WipeRightToLeft:
pStyle = "Wipe"; pDi = "180"; break;
case PDFWriter::PageTransition::WipeTopToBottom:
pStyle = "Wipe"; pDi = "270"; break;
case PDFWriter::PageTransition::Dissolve:
pStyle = "Dissolve"; break;
case PDFWriter::PageTransition::Regular:
break;
}
// transition style
if( pStyle )
{
aLine.append( "/S/" );
aLine.append( pStyle );
aLine.append( "\n" );
}
if( pDm )
{
aLine.append( "/Dm/" );
aLine.append( pDm );
aLine.append( "\n" );
}
if( pM )
{
aLine.append( "/M/" );
aLine.append( pM );
aLine.append( "\n" );
}
if( pDi )
{
aLine.append( "/Di " );
aLine.append( pDi );
aLine.append( "\n" );
}
aLine.append( ">>\n" );
}
if( m_pWriter->getVersion() > PDFWriter::PDFVersion::PDF_1_3 && ! m_pWriter->m_bIsPDF_A1 )
{
aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/I true>>" );
}
aLine.append( "/Contents" );
unsigned int nStreamObjects = m_aStreamObjects.size();
if( nStreamObjects > 1 )
aLine.append( '[' );
for(sal_Int32 i : m_aStreamObjects)
{
aLine.append( ' ' );
aLine.append( i );
aLine.append( " 0 R" );
}
if( nStreamObjects > 1 )
aLine.append( ']' );
aLine.append( ">>\nendobj\n\n" );
return m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() );
}
namespace vcl
{
template < class GEOMETRY >
GEOMETRY lcl_convert( const MapMode& _rSource, const MapMode& _rDest, OutputDevice* _pPixelConversion, const GEOMETRY& _rObject )
{
GEOMETRY aPoint;
if ( MapUnit::MapPixel == _rSource.GetMapUnit() )
{
aPoint = _pPixelConversion->PixelToLogic( _rObject, _rDest );
}
else
{
aPoint = OutputDevice::LogicToLogic( _rObject, _rSource, _rDest );
}
return aPoint;
}
}
void PDFWriterImpl::PDFPage::appendPoint( const Point& rPoint, OStringBuffer& rBuffer ) const
{
Point aPoint( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
m_pWriter->m_aMapMode,
m_pWriter->getReferenceDevice(),
rPoint ) );
sal_Int32 nValue = aPoint.X();
appendFixedInt( nValue, rBuffer );
rBuffer.append( ' ' );
nValue = pointToPixel(getHeight()) - aPoint.Y();
appendFixedInt( nValue, rBuffer );
}
void PDFWriterImpl::PDFPage::appendPixelPoint( const basegfx::B2DPoint& rPoint, OStringBuffer& rBuffer ) const
{
double fValue = pixelToPoint(rPoint.getX());
appendDouble( fValue, rBuffer, nLog10Divisor );
rBuffer.append( ' ' );
fValue = double(getHeight()) - pixelToPoint(rPoint.getY());
appendDouble( fValue, rBuffer, nLog10Divisor );
}
void PDFWriterImpl::PDFPage::appendRect( const Rectangle& rRect, OStringBuffer& rBuffer ) const
{
appendPoint( rRect.BottomLeft() + Point( 0, 1 ), rBuffer );
rBuffer.append( ' ' );
appendMappedLength( (sal_Int32)rRect.GetWidth(), rBuffer, false );
rBuffer.append( ' ' );
appendMappedLength( (sal_Int32)rRect.GetHeight(), rBuffer );
rBuffer.append( " re" );
}
void PDFWriterImpl::PDFPage::convertRect( Rectangle& rRect ) const
{
Point aLL = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
m_pWriter->m_aMapMode,
m_pWriter->getReferenceDevice(),
rRect.BottomLeft() + Point( 0, 1 )
);
Size aSize = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
m_pWriter->m_aMapMode,
m_pWriter->getReferenceDevice(),
rRect.GetSize() );
rRect.Left() = aLL.X();
rRect.Right() = aLL.X() + aSize.Width();
rRect.Top() = pointToPixel(getHeight()) - aLL.Y();
rRect.Bottom() = rRect.Top() + aSize.Height();
}
void PDFWriterImpl::PDFPage::appendPolygon( const tools::Polygon& rPoly, OStringBuffer& rBuffer, bool bClose ) const
{
sal_uInt16 nPoints = rPoly.GetSize();
/*
* #108582# applications do weird things
*/
sal_uInt32 nBufLen = rBuffer.getLength();
if( nPoints > 0 )
{
const PolyFlags* pFlagArray = rPoly.GetConstFlagAry();
appendPoint( rPoly[0], rBuffer );
rBuffer.append( " m\n" );
for( sal_uInt16 i = 1; i < nPoints; i++ )
{
if( pFlagArray && pFlagArray[i] == PolyFlags::Control && nPoints-i > 2 )
{
// bezier
SAL_WARN_IF( pFlagArray[i+1] != PolyFlags::Control || pFlagArray[i+2] == PolyFlags::Control, "vcl.pdfwriter", "unexpected sequence of control points" );
appendPoint( rPoly[i], rBuffer );
rBuffer.append( " " );
appendPoint( rPoly[i+1], rBuffer );
rBuffer.append( " " );
appendPoint( rPoly[i+2], rBuffer );
rBuffer.append( " c" );
i += 2; // add additionally consumed points
}
else
{
// line
appendPoint( rPoly[i], rBuffer );
rBuffer.append( " l" );
}
if( (rBuffer.getLength() - nBufLen) > 65 )
{
rBuffer.append( "\n" );
nBufLen = rBuffer.getLength();
}
else
rBuffer.append( " " );
}
if( bClose )
rBuffer.append( "h\n" );
}
}
void PDFWriterImpl::PDFPage::appendPolygon( const basegfx::B2DPolygon& rPoly, OStringBuffer& rBuffer ) const
{
basegfx::B2DPolygon aPoly( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
m_pWriter->m_aMapMode,
m_pWriter->getReferenceDevice(),
rPoly ) );
if( basegfx::tools::isRectangle( aPoly ) )
{
basegfx::B2DRange aRange( aPoly.getB2DRange() );
basegfx::B2DPoint aBL( aRange.getMinX(), aRange.getMaxY() );
appendPixelPoint( aBL, rBuffer );
rBuffer.append( ' ' );
appendMappedLength( aRange.getWidth(), rBuffer, false, nLog10Divisor );
rBuffer.append( ' ' );
appendMappedLength( aRange.getHeight(), rBuffer, true, nLog10Divisor );
rBuffer.append( " re\n" );
return;
}
sal_uInt32 nPoints = aPoly.count();
if( nPoints > 0 )
{
sal_uInt32 nBufLen = rBuffer.getLength();
basegfx::B2DPoint aLastPoint( aPoly.getB2DPoint( 0 ) );
appendPixelPoint( aLastPoint, rBuffer );
rBuffer.append( " m\n" );
for( sal_uInt32 i = 1; i <= nPoints; i++ )
{
if( i != nPoints || aPoly.isClosed() )
{
sal_uInt32 nCurPoint = i % nPoints;
sal_uInt32 nLastPoint = i-1;
basegfx::B2DPoint aPoint( aPoly.getB2DPoint( nCurPoint ) );
if( aPoly.isNextControlPointUsed( nLastPoint ) &&
aPoly.isPrevControlPointUsed( nCurPoint ) )
{
appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer );
rBuffer.append( ' ' );
appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer );
rBuffer.append( ' ' );
appendPixelPoint( aPoint, rBuffer );
rBuffer.append( " c" );
}
else if( aPoly.isNextControlPointUsed( nLastPoint ) )
{
appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer );
rBuffer.append( ' ' );
appendPixelPoint( aPoint, rBuffer );
rBuffer.append( " y" );
}
else if( aPoly.isPrevControlPointUsed( nCurPoint ) )
{
appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer );
rBuffer.append( ' ' );
appendPixelPoint( aPoint, rBuffer );
rBuffer.append( " v" );
}
else
{
appendPixelPoint( aPoint, rBuffer );
rBuffer.append( " l" );
}
if( (rBuffer.getLength() - nBufLen) > 65 )
{
rBuffer.append( "\n" );
nBufLen = rBuffer.getLength();
}
else
rBuffer.append( " " );
}
}
rBuffer.append( "h\n" );
}
}
void PDFWriterImpl::PDFPage::appendPolyPolygon( const tools::PolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
{
sal_uInt16 nPolygons = rPolyPoly.Count();
for( sal_uInt16 n = 0; n < nPolygons; n++ )
appendPolygon( rPolyPoly[n], rBuffer );
}
void PDFWriterImpl::PDFPage::appendPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly, OStringBuffer& rBuffer ) const
{
sal_uInt32 nPolygons = rPolyPoly.count();
for( sal_uInt32 n = 0; n < nPolygons; n++ )
appendPolygon( rPolyPoly.getB2DPolygon( n ), rBuffer );
}
void PDFWriterImpl::PDFPage::appendMappedLength( sal_Int32 nLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32* pOutLength ) const
{
sal_Int32 nValue = nLength;
if ( nLength < 0 )
{
rBuffer.append( '-' );
nValue = -nLength;
}
Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
m_pWriter->m_aMapMode,
m_pWriter->getReferenceDevice(),
Size( nValue, nValue ) ) );
nValue = bVertical ? aSize.Height() : aSize.Width();
if( pOutLength )
*pOutLength = ((nLength < 0 ) ? -nValue : nValue);
appendFixedInt( nValue, rBuffer );
}
void PDFWriterImpl::PDFPage::appendMappedLength( double fLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32 nPrecision ) const
{
Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
m_pWriter->m_aMapMode,
m_pWriter->getReferenceDevice(),
Size( 1000, 1000 ) ) );
fLength *= pixelToPoint((double)(bVertical ? aSize.Height() : aSize.Width()) / 1000.0);
appendDouble( fLength, rBuffer, nPrecision );
}
bool PDFWriterImpl::PDFPage::appendLineInfo( const LineInfo& rInfo, OStringBuffer& rBuffer ) const
{
if(LineStyle::Dash == rInfo.GetStyle() && rInfo.GetDashLen() != rInfo.GetDotLen())
{
// dashed and non-degraded case, check for implementation limits of dash array
// in PDF reader apps (e.g. acroread)
if(2 * (rInfo.GetDashCount() + rInfo.GetDotCount()) > 10)
{
return false;
}
}
if(basegfx::B2DLineJoin::NONE != rInfo.GetLineJoin())
{
// LineJoin used, ExtLineInfo required
return false;
}
if(css::drawing::LineCap_BUTT != rInfo.GetLineCap())
{
// LineCap used, ExtLineInfo required
return false;
}
if( rInfo.GetStyle() == LineStyle::Dash )
{
rBuffer.append( "[ " );
if( rInfo.GetDashLen() == rInfo.GetDotLen() ) // degraded case
{
appendMappedLength( (sal_Int32)rInfo.GetDashLen(), rBuffer );
rBuffer.append( ' ' );
appendMappedLength( (sal_Int32)rInfo.GetDistance(), rBuffer );
rBuffer.append( ' ' );
}
else
{
for( int n = 0; n < rInfo.GetDashCount(); n++ )
{
appendMappedLength( (sal_Int32)rInfo.GetDashLen(), rBuffer );
rBuffer.append( ' ' );
appendMappedLength( (sal_Int32)rInfo.GetDistance(), rBuffer );
rBuffer.append( ' ' );
}
for( int m = 0; m < rInfo.GetDotCount(); m++ )
{
appendMappedLength( (sal_Int32)rInfo.GetDotLen(), rBuffer );
rBuffer.append( ' ' );
appendMappedLength( (sal_Int32)rInfo.GetDistance(), rBuffer );
rBuffer.append( ' ' );
}
}
rBuffer.append( "] 0 d\n" );
}
if( rInfo.GetWidth() > 1 )
{
appendMappedLength( (sal_Int32)rInfo.GetWidth(), rBuffer );
rBuffer.append( " w\n" );
}
else if( rInfo.GetWidth() == 0 )
{
// "pixel" line
appendDouble( 72.0/double(m_pWriter->getReferenceDevice()->GetDPIX()), rBuffer );
rBuffer.append( " w\n" );
}
return true;
}
void PDFWriterImpl::PDFPage::appendWaveLine( sal_Int32 nWidth, sal_Int32 nY, sal_Int32 nDelta, OStringBuffer& rBuffer ) const
{
if( nWidth <= 0 )
return;
if( nDelta < 1 )
nDelta = 1;
rBuffer.append( "0 " );
appendMappedLength( nY, rBuffer );
rBuffer.append( " m\n" );
for( sal_Int32 n = 0; n < nWidth; )
{
n += nDelta;
appendMappedLength( n, rBuffer, false );
rBuffer.append( ' ' );
appendMappedLength( nDelta+nY, rBuffer );
rBuffer.append( ' ' );
n += nDelta;
appendMappedLength( n, rBuffer, false );
rBuffer.append( ' ' );
appendMappedLength( nY, rBuffer );
rBuffer.append( " v " );
if( n < nWidth )
{
n += nDelta;
appendMappedLength( n, rBuffer, false );
rBuffer.append( ' ' );
appendMappedLength( nY-nDelta, rBuffer );
rBuffer.append( ' ' );
n += nDelta;
appendMappedLength( n, rBuffer, false );
rBuffer.append( ' ' );
appendMappedLength( nY, rBuffer );
rBuffer.append( " v\n" );
}
}
rBuffer.append( "S\n" );
}
PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext,
const css::uno::Reference< css::beans::XMaterialHolder >& xEnc,
PDFWriter& i_rOuterFace)
:
m_pReferenceDevice( nullptr ),
m_aMapMode( MapUnit::MapPoint, Point(), Fraction( 1, pointToPixel(1) ), Fraction( 1, pointToPixel(1) ) ),
m_nCurrentStructElement( 0 ),
m_bEmitStructure( true ),
m_nNextFID( 1 ),
m_nInheritedPageWidth( 595 ), // default A4
m_nInheritedPageHeight( 842 ), // default A4
m_nCurrentPage( -1 ),
m_nCatalogObject(0),
m_nSignatureObject( -1 ),
m_nSignatureContentOffset( 0 ),
m_nSignatureLastByteRangeNoOffset( 0 ),
m_nResourceDict( -1 ),
m_nFontDictObject( -1 ),
m_aContext(rContext),
m_aFile(m_aContext.URL),
m_bOpen(false),
m_aDocDigest( rtl_digest_createMD5() ),
m_aCipher( nullptr ),
m_aDigest( nullptr ),
m_nKeyLength(0),
m_nRC4KeyLength(0),
m_bEncryptThisStream( false ),
m_nAccessPermissions(0),
m_pEncryptionBuffer( nullptr ),
m_nEncryptionBufferSize( 0 ),
m_bIsPDF_A1( false ),
m_rOuterFace( i_rOuterFace )
{
#ifdef DO_TEST_PDF
static bool bOnce = true;
if( bOnce )
{
bOnce = false;
doTestCode();
}
#endif
m_aStructure.push_back( PDFStructureElement() );
m_aStructure[0].m_nOwnElement = 0;
m_aStructure[0].m_nParentElement = 0;
Font aFont;
aFont.SetFamilyName( "Times" );
aFont.SetFontSize( Size( 0, 12 ) );
GraphicsState aState;
aState.m_aMapMode = m_aMapMode;
aState.m_aFont = aFont;
m_aGraphicsStack.push_front( aState );
osl::File::RC aError = m_aFile.open(osl_File_OpenFlag_Write | osl_File_OpenFlag_Create);
if (aError != osl::File::E_None)
{
if (aError == osl::File::E_EXIST)
{
aError = m_aFile.open(osl_File_OpenFlag_Write);
if (aError == osl::File::E_None)
aError = m_aFile.setSize(0);
}
}
if (aError != osl::File::E_None)
return;
m_bOpen = true;
// setup DocInfo
setupDocInfo();
/* prepare the cypher engine, can be done in CTOR, free in DTOR */
m_aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream );
m_aDigest = rtl_digest_createMD5();
/* the size of the Codec default maximum */
/* is this 0x4000 required to be the same as MAX_SIGNATURE_CONTENT_LENGTH or just coincidentally the same at the moment? */
if (!checkEncryptionBufferSize(0x4000))
{
m_aFile.close();
m_bOpen = false;
return;
}
if( xEnc.is() )
prepareEncryption( xEnc );
if( m_aContext.Encryption.Encrypt() )
{
// sanity check
if( m_aContext.Encryption.OValue.size() != ENCRYPTED_PWD_SIZE ||
m_aContext.Encryption.UValue.size() != ENCRYPTED_PWD_SIZE ||
m_aContext.Encryption.EncryptionKey.size() != MAXIMUM_RC4_KEY_LENGTH
)
{
// the field lengths are invalid ? This was not setup by initEncryption.
// do not encrypt after all
m_aContext.Encryption.OValue.clear();
m_aContext.Encryption.UValue.clear();
OSL_ENSURE( false, "encryption data failed sanity check, encryption disabled" );
}
else // setup key lengths
m_nAccessPermissions = computeAccessPermissions( m_aContext.Encryption, m_nKeyLength, m_nRC4KeyLength );
}
// write header
OStringBuffer aBuffer( 20 );
aBuffer.append( "%PDF-" );
switch( m_aContext.Version )
{
case PDFWriter::PDFVersion::PDF_1_2: aBuffer.append( "1.2" );break;
case PDFWriter::PDFVersion::PDF_1_3: aBuffer.append( "1.3" );break;
case PDFWriter::PDFVersion::PDF_A_1:
default:
case PDFWriter::PDFVersion::PDF_1_4: aBuffer.append( "1.4" );break;
case PDFWriter::PDFVersion::PDF_1_5: aBuffer.append( "1.5" );break;
}
// append something binary as comment (suggested in PDF Reference)
aBuffer.append( "\n%\303\244\303\274\303\266\303\237\n" );
if( !writeBuffer( aBuffer.getStr(), aBuffer.getLength() ) )
{
m_aFile.close();
m_bOpen = false;
return;
}
// insert outline root
m_aOutline.push_back( PDFOutlineEntry() );
m_bIsPDF_A1 = (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_1);
if( m_bIsPDF_A1 )
m_aContext.Version = PDFWriter::PDFVersion::PDF_1_4; //meaning we need PDF 1.4, PDF/A flavour
}
PDFWriterImpl::~PDFWriterImpl()
{
if( m_aDocDigest )
rtl_digest_destroyMD5( m_aDocDigest );
m_pReferenceDevice.disposeAndClear();
if( m_aCipher )
rtl_cipher_destroyARCFOUR( m_aCipher );
if( m_aDigest )
rtl_digest_destroyMD5( m_aDigest );
rtl_freeMemory( m_pEncryptionBuffer );
}
void PDFWriterImpl::setupDocInfo()
{
std::vector< sal_uInt8 > aId;
m_aCreationDateString = PDFWriter::GetDateTime();
computeDocumentIdentifier( aId, m_aContext.DocumentInfo, m_aCreationDateString, m_aCreationMetaDateString );
if( m_aContext.Encryption.DocumentIdentifier.empty() )
m_aContext.Encryption.DocumentIdentifier = aId;
}
OString PDFWriter::GetDateTime()
{
OStringBuffer aRet;
TimeValue aTVal, aGMT;
oslDateTime aDT;
osl_getSystemTime(&aGMT);
osl_getLocalTimeFromSystemTime(&aGMT, &aTVal);
osl_getDateTimeFromTimeValue(&aTVal, &aDT);
aRet.append("D:");
aRet.append((sal_Char)('0' + ((aDT.Year / 1000) % 10)));
aRet.append((sal_Char)('0' + ((aDT.Year / 100) % 10)));
aRet.append((sal_Char)('0' + ((aDT.Year / 10) % 10)));
aRet.append((sal_Char)('0' + (aDT.Year % 10)));
aRet.append((sal_Char)('0' + ((aDT.Month / 10) % 10)));
aRet.append((sal_Char)('0' + (aDT.Month % 10)));
aRet.append((sal_Char)('0' + ((aDT.Day / 10) % 10)));
aRet.append((sal_Char)('0' + (aDT.Day % 10)));
aRet.append((sal_Char)('0' + ((aDT.Hours / 10) % 10)));
aRet.append((sal_Char)('0' + (aDT.Hours % 10)));
aRet.append((sal_Char)('0' + ((aDT.Minutes / 10) % 10)));
aRet.append((sal_Char)('0' + (aDT.Minutes % 10)));
aRet.append((sal_Char)('0' + ((aDT.Seconds / 10) % 10)));
aRet.append((sal_Char)('0' + (aDT.Seconds % 10)));
sal_uInt32 nDelta = 0;
if (aGMT.Seconds > aTVal.Seconds)
{
aRet.append("-");
nDelta = aGMT.Seconds-aTVal.Seconds;
}
else if (aGMT.Seconds < aTVal.Seconds)
{
aRet.append("+");
nDelta = aTVal.Seconds-aGMT.Seconds;
}
else
aRet.append("Z");
if (nDelta)
{
aRet.append((sal_Char)('0' + ((nDelta / 36000) % 10)));
aRet.append((sal_Char)('0' + ((nDelta / 3600) % 10)));
aRet.append("'");
aRet.append((sal_Char)('0' + ((nDelta / 600) % 6)));
aRet.append((sal_Char)('0' + ((nDelta / 60) % 10)));
}
aRet.append( "'" );
return aRet.makeStringAndClear();
}
void PDFWriterImpl::computeDocumentIdentifier( std::vector< sal_uInt8 >& o_rIdentifier,
const vcl::PDFWriter::PDFDocInfo& i_rDocInfo,
const OString& i_rCString1,
OString& o_rCString2
)
{
o_rIdentifier.clear();
//build the document id
OString aInfoValuesOut;
OStringBuffer aID( 1024 );
if( !i_rDocInfo.Title.isEmpty() )
PDFWriter::AppendUnicodeTextString(i_rDocInfo.Title, aID);
if( !i_rDocInfo.Author.isEmpty() )
PDFWriter::AppendUnicodeTextString(i_rDocInfo.Author, aID);
if( !i_rDocInfo.Subject.isEmpty() )
PDFWriter::AppendUnicodeTextString(i_rDocInfo.Subject, aID);
if( !i_rDocInfo.Keywords.isEmpty() )
PDFWriter::AppendUnicodeTextString(i_rDocInfo.Keywords, aID);
if( !i_rDocInfo.Creator.isEmpty() )
PDFWriter::AppendUnicodeTextString(i_rDocInfo.Creator, aID);
if( !i_rDocInfo.Producer.isEmpty() )
PDFWriter::AppendUnicodeTextString(i_rDocInfo.Producer, aID);
TimeValue aTVal, aGMT;
oslDateTime aDT;
osl_getSystemTime( &aGMT );
osl_getLocalTimeFromSystemTime( &aGMT, &aTVal );
osl_getDateTimeFromTimeValue( &aTVal, &aDT );
OStringBuffer aCreationMetaDateString(64);
//--> i59651, we fill the Metadata date string as well, if PDF/A is requested
// according to ISO 19005-1:2005 6.7.3 the date is corrected for
// local time zone offset UTC only, whereas Acrobat 8 seems
// to use the localtime notation only
// according to a recommendation in XMP Specification (Jan 2004, page 75)
// the Acrobat way seems the right approach
aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Year/1000)%10)) );
aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Year/100)%10)) );
aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Year/10)%10)) );
aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Year)%10)) );
aCreationMetaDateString.append( "-" );
aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Month/10)%10)) );
aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Month)%10)) );
aCreationMetaDateString.append( "-" );
aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Day/10)%10)) );
aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Day)%10)) );
aCreationMetaDateString.append( "T" );
aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Hours/10)%10)) );
aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Hours)%10)) );
aCreationMetaDateString.append( ":" );
aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Minutes/10)%10)) );
aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Minutes)%10)) );
aCreationMetaDateString.append( ":" );
aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Seconds/10)%10)) );
aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Seconds)%10)) );
sal_uInt32 nDelta = 0;
if( aGMT.Seconds > aTVal.Seconds )
{
nDelta = aGMT.Seconds-aTVal.Seconds;
aCreationMetaDateString.append( "-" );
}
else if( aGMT.Seconds < aTVal.Seconds )
{
nDelta = aTVal.Seconds-aGMT.Seconds;
aCreationMetaDateString.append( "+" );
}
else
{
aCreationMetaDateString.append( "Z" );
}
if( nDelta )
{
aCreationMetaDateString.append( (sal_Char)('0' + ((nDelta/36000)%10)) );
aCreationMetaDateString.append( (sal_Char)('0' + ((nDelta/3600)%10)) );
aCreationMetaDateString.append( ":" );
aCreationMetaDateString.append( (sal_Char)('0' + ((nDelta/600)%6)) );
aCreationMetaDateString.append( (sal_Char)('0' + ((nDelta/60)%10)) );
}
aID.append( i_rCString1.getStr(), i_rCString1.getLength() );
aInfoValuesOut = aID.makeStringAndClear();
o_rCString2 = aCreationMetaDateString.makeStringAndClear();
rtlDigest aDigest = rtl_digest_createMD5();
OSL_ENSURE( aDigest != nullptr, "PDFWriterImpl::computeDocumentIdentifier: cannot obtain a digest object !" );
if( aDigest )
{
rtlDigestError nError = rtl_digest_updateMD5( aDigest, &aGMT, sizeof( aGMT ) );
if( nError == rtl_Digest_E_None )
nError = rtl_digest_updateMD5( aDigest, aInfoValuesOut.getStr(), aInfoValuesOut.getLength() );
if( nError == rtl_Digest_E_None )
{
o_rIdentifier = std::vector< sal_uInt8 >( 16, 0 );
//the binary form of the doc id is needed for encryption stuff
rtl_digest_getMD5( aDigest, &o_rIdentifier[0], 16 );
}
rtl_digest_destroyMD5(aDigest);
}
}
/* i12626 methods */
/*
check if the Unicode string must be encrypted or not, perform the requested task,
append the string as unicode hex, encrypted if needed
*/
inline void PDFWriterImpl::appendUnicodeTextStringEncrypt( const OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
{
rOutBuffer.append( "<" );
if( m_aContext.Encryption.Encrypt() )
{
const sal_Unicode* pStr = rInString.getStr();
sal_Int32 nLen = rInString.getLength();
//prepare a unicode string, encrypt it
if( checkEncryptionBufferSize( nLen*2 ) )
{
enableStringEncryption( nInObjectNumber );
sal_uInt8 *pCopy = m_pEncryptionBuffer;
sal_Int32 nChars = 2;
*pCopy++ = 0xFE;
*pCopy++ = 0xFF;
// we need to prepare a byte stream from the unicode string buffer
for( int i = 0; i < nLen; i++ )
{
sal_Unicode aUnChar = pStr[i];
*pCopy++ = (sal_uInt8)( aUnChar >> 8 );
*pCopy++ = (sal_uInt8)( aUnChar & 255 );
nChars += 2;
}
//encrypt in place
rtl_cipher_encodeARCFOUR( m_aCipher, m_pEncryptionBuffer, nChars, m_pEncryptionBuffer, nChars );
//now append, hexadecimal (appendHex), the encrypted result
for(int i = 0; i < nChars; i++)
appendHex( m_pEncryptionBuffer[i], rOutBuffer );
}
}
else
PDFWriter::AppendUnicodeTextString(rInString, rOutBuffer);
rOutBuffer.append( ">" );
}
inline void PDFWriterImpl::appendLiteralStringEncrypt( OStringBuffer& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
{
rOutBuffer.append( "(" );
sal_Int32 nChars = rInString.getLength();
//check for encryption, if ok, encrypt the string, then convert with appndLiteralString
if( m_aContext.Encryption.Encrypt() && checkEncryptionBufferSize( nChars ) )
{
//encrypt the string in a buffer, then append it
enableStringEncryption( nInObjectNumber );
rtl_cipher_encodeARCFOUR( m_aCipher, rInString.getStr(), nChars, m_pEncryptionBuffer, nChars );
appendLiteralString( reinterpret_cast<sal_Char*>(m_pEncryptionBuffer), nChars, rOutBuffer );
}
else
appendLiteralString( rInString.getStr(), nChars , rOutBuffer );
rOutBuffer.append( ")" );
}
inline void PDFWriterImpl::appendLiteralStringEncrypt( const OString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
{
OStringBuffer aBufferString( rInString );
appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer);
}
void PDFWriterImpl::appendLiteralStringEncrypt( const OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer, rtl_TextEncoding nEnc )
{
OString aBufferString( OUStringToOString( rInString, nEnc ) );
sal_Int32 nLen = aBufferString.getLength();
OStringBuffer aBuf( nLen );
const sal_Char* pT = aBufferString.getStr();
for( sal_Int32 i = 0; i < nLen; i++, pT++ )
{
if( (*pT & 0x80) == 0 )
aBuf.append( *pT );
else
{
aBuf.append( '<' );
appendHex( *pT, aBuf );
aBuf.append( '>' );
}
}
aBufferString = aBuf.makeStringAndClear();
appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer);
}
/* end i12626 methods */
void PDFWriterImpl::emitComment( const char* pComment )
{
OStringBuffer aLine( 64 );
aLine.append( "% " );
aLine.append( pComment );
aLine.append( "\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
}
bool PDFWriterImpl::compressStream( SvMemoryStream* pStream )
{
if (!g_bDebugDisableCompression)
{
pStream->Seek( STREAM_SEEK_TO_END );
sal_uLong nEndPos = pStream->Tell();
pStream->Seek( STREAM_SEEK_TO_BEGIN );
ZCodec aCodec( 0x4000, 0x4000 );
SvMemoryStream aStream;
aCodec.BeginCompression();
aCodec.Write( aStream, static_cast<const sal_uInt8*>(pStream->GetData()), nEndPos );
aCodec.EndCompression();
nEndPos = aStream.Tell();
pStream->Seek( STREAM_SEEK_TO_BEGIN );
aStream.Seek( STREAM_SEEK_TO_BEGIN );
pStream->SetStreamSize( nEndPos );
pStream->WriteBytes( aStream.GetData(), nEndPos );
return true;
}
else
return false;
}
void PDFWriterImpl::beginCompression()
{
if (!g_bDebugDisableCompression)
{
m_pCodec = o3tl::make_unique<ZCodec>( 0x4000, 0x4000 );
m_pMemStream = o3tl::make_unique<SvMemoryStream>();
m_pCodec->BeginCompression();
}
}
void PDFWriterImpl::endCompression()
{
if (!g_bDebugDisableCompression && m_pCodec)
{
m_pCodec->EndCompression();
m_pCodec.reset();
sal_uInt64 nLen = m_pMemStream->Tell();
m_pMemStream->Seek( 0 );
writeBuffer( m_pMemStream->GetData(), nLen );
m_pMemStream.reset();
}
}
bool PDFWriterImpl::writeBuffer( const void* pBuffer, sal_uInt64 nBytes )
{
if( ! m_bOpen ) // we are already down the drain
return false;
if( ! nBytes ) // huh ?
return true;
if( !m_aOutputStreams.empty() )
{
m_aOutputStreams.front().m_pStream->Seek( STREAM_SEEK_TO_END );
m_aOutputStreams.front().m_pStream->WriteBytes(
pBuffer, sal::static_int_cast<std::size_t>(nBytes));
return true;
}
sal_uInt64 nWritten;
if( m_pCodec )
{
m_pCodec->Write( *m_pMemStream, static_cast<const sal_uInt8*>(pBuffer), (sal_uLong)nBytes );
nWritten = nBytes;
}
else
{
bool buffOK = true;
if( m_bEncryptThisStream )
{
/* implement the encryption part of the PDF spec encryption algorithm 3.1 */
buffOK = checkEncryptionBufferSize( static_cast<sal_Int32>(nBytes) );
if( buffOK )
rtl_cipher_encodeARCFOUR( m_aCipher,
pBuffer, static_cast<sal_Size>(nBytes),
m_pEncryptionBuffer, static_cast<sal_Size>(nBytes) );
}
const void* pWriteBuffer = ( m_bEncryptThisStream && buffOK ) ? m_pEncryptionBuffer : pBuffer;
if( m_aDocDigest )
rtl_digest_updateMD5( m_aDocDigest, pWriteBuffer, static_cast<sal_uInt32>(nBytes) );
if (m_aFile.write(pWriteBuffer, nBytes, nWritten) != osl::File::E_None)
nWritten = 0;
if( nWritten != nBytes )
{
m_aFile.close();
m_bOpen = false;
}
}
return nWritten == nBytes;
}
OutputDevice* PDFWriterImpl::getReferenceDevice()
{
if( ! m_pReferenceDevice )
{
VclPtrInstance<VirtualDevice> pVDev(DeviceFormat::DEFAULT);
m_pReferenceDevice = pVDev;
if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 )
pVDev->SetReferenceDevice( VirtualDevice::RefDevMode::PDF1 );
else
pVDev->SetReferenceDevice( m_aContext.DPIx, m_aContext.DPIy );
pVDev->SetOutputSizePixel( Size( 640, 480 ) );
pVDev->SetMapMode( MapUnit::MapMM );
m_pReferenceDevice->mpPDFWriter = this;
m_pReferenceDevice->ImplUpdateFontData();
}
return m_pReferenceDevice;
}
static FontAttributes GetDevFontAttributes( const PDFWriterImpl::BuiltinFont& rBuiltin )
{
FontAttributes aDFA;
aDFA.SetFamilyName( OUString::createFromAscii( rBuiltin.m_pName ) );
aDFA.SetStyleName( OUString::createFromAscii( rBuiltin.m_pStyleName ) );
aDFA.SetFamilyType( rBuiltin.m_eFamily );
aDFA.SetSymbolFlag( rBuiltin.m_eCharSet != RTL_TEXTENCODING_MS_1252 );
aDFA.SetPitch( rBuiltin.m_ePitch );
aDFA.SetWeight( rBuiltin.m_eWeight );
aDFA.SetItalic( rBuiltin.m_eItalic );
aDFA.SetWidthType( rBuiltin.m_eWidthType );
aDFA.SetQuality( 50000 );
return aDFA;
}
PdfBuiltinFontFace::PdfBuiltinFontFace( const PDFWriterImpl::BuiltinFont& rBuiltin )
: PhysicalFontFace( GetDevFontAttributes(rBuiltin) ),
mrBuiltin( rBuiltin )
{}
LogicalFontInstance* PdfBuiltinFontFace::CreateFontInstance( FontSelectPattern& rFSD ) const
{
LogicalFontInstance* pEntry = new LogicalFontInstance( rFSD );
return pEntry;
}
void PDFWriterImpl::newPage( sal_Int32 nPageWidth, sal_Int32 nPageHeight, PDFWriter::Orientation eOrientation )
{
endPage();
m_nCurrentPage = m_aPages.size();
m_aPages.push_back( PDFPage(this, nPageWidth, nPageHeight, eOrientation ) );
m_aPages.back().m_nPageIndex = m_nCurrentPage;
m_aPages.back().beginStream();
// setup global graphics state
// linewidth is "1 pixel" by default
OStringBuffer aBuf( 16 );
appendDouble( 72.0/double(getReferenceDevice()->GetDPIX()), aBuf );
aBuf.append( " w\n" );
writeBuffer( aBuf.getStr(), aBuf.getLength() );
}
void PDFWriterImpl::endPage()
{
if( !m_aPages.empty() )
{
// close eventual MC sequence
endStructureElementMCSeq();
// sanity check
if( !m_aOutputStreams.empty() )
{
OSL_FAIL( "redirection across pages !!!" );
m_aOutputStreams.clear(); // leak !
m_aMapMode.SetOrigin( Point() );
}
m_aGraphicsStack.clear();
m_aGraphicsStack.push_back( GraphicsState() );
// this should pop the PDF graphics stack if necessary
updateGraphicsState();
m_aPages.back().endStream();
// reset the default font
Font aFont;
aFont.SetFamilyName( "Times" );
aFont.SetFontSize( Size( 0, 12 ) );
m_aCurrentPDFState = m_aGraphicsStack.front();
m_aGraphicsStack.front().m_aFont = aFont;
for( std::list<BitmapEmit>::iterator it = m_aBitmaps.begin();
it != m_aBitmaps.end(); ++it )
{
if( ! it->m_aBitmap.IsEmpty() )
{
writeBitmapObject( *it );
it->m_aBitmap = BitmapEx();
}
}
for( std::list<JPGEmit>::iterator jpeg = m_aJPGs.begin(); jpeg != m_aJPGs.end(); ++jpeg )
{
if( jpeg->m_pStream )
{
writeJPG( *jpeg );
jpeg->m_pStream.reset();
jpeg->m_aMask = Bitmap();
}
}
for( std::list<TransparencyEmit>::iterator t = m_aTransparentObjects.begin();
t != m_aTransparentObjects.end(); ++t )
{
if( t->m_pContentStream )
{
writeTransparentObject( *t );
delete t->m_pContentStream;
t->m_pContentStream = nullptr;
}
}
}
}
sal_Int32 PDFWriterImpl::createObject()
{
m_aObjects.push_back( ~0U );
return m_aObjects.size();
}
bool PDFWriterImpl::updateObject( sal_Int32 n )
{
if( ! m_bOpen )
return false;
sal_uInt64 nOffset = ~0U;
osl::File::RC aError = m_aFile.getPos(nOffset);
SAL_WARN_IF( aError != osl::File::E_None, "vcl.pdfwriter", "could not register object" );
if (aError != osl::File::E_None)
{
m_aFile.close();
m_bOpen = false;
}
m_aObjects[ n-1 ] = nOffset;
return aError == osl::File::E_None;
}
#define CHECK_RETURN( x ) if( !(x) ) return 0
#define CHECK_RETURN2( x ) if( !(x) ) return
sal_Int32 PDFWriterImpl::emitStructParentTree( sal_Int32 nObject )
{
if( nObject > 0 )
{
OStringBuffer aLine( 1024 );
aLine.append( nObject );
aLine.append( " 0 obj\n"
"<</Nums[\n" );
sal_Int32 nTreeItems = m_aStructParentTree.size();
for( sal_Int32 n = 0; n < nTreeItems; n++ )
{
aLine.append( n );
aLine.append( ' ' );
aLine.append( m_aStructParentTree[n] );
aLine.append( "\n" );
}
aLine.append( "]>>\nendobj\n\n" );
CHECK_RETURN( updateObject( nObject ) );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
}
return nObject;
}
const sal_Char* PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr )
{
static std::map< PDFWriter::StructAttribute, const char* > aAttributeStrings;
// fill maps once
if( aAttributeStrings.empty() )
{
aAttributeStrings[ PDFWriter::Placement ] = "Placement";
aAttributeStrings[ PDFWriter::WritingMode ] = "WritingMode";
aAttributeStrings[ PDFWriter::SpaceBefore ] = "SpaceBefore";
aAttributeStrings[ PDFWriter::SpaceAfter ] = "SpaceAfter";
aAttributeStrings[ PDFWriter::StartIndent ] = "StartIndent";
aAttributeStrings[ PDFWriter::EndIndent ] = "EndIndent";
aAttributeStrings[ PDFWriter::TextIndent ] = "TextIndent";
aAttributeStrings[ PDFWriter::TextAlign ] = "TextAlign";
aAttributeStrings[ PDFWriter::Width ] = "Width";
aAttributeStrings[ PDFWriter::Height ] = "Height";
aAttributeStrings[ PDFWriter::BlockAlign ] = "BlockAlign";
aAttributeStrings[ PDFWriter::InlineAlign ] = "InlineAlign";
aAttributeStrings[ PDFWriter::LineHeight ] = "LineHeight";
aAttributeStrings[ PDFWriter::BaselineShift ] = "BaselineShift";
aAttributeStrings[ PDFWriter::TextDecorationType ] = "TextDecorationType";
aAttributeStrings[ PDFWriter::ListNumbering ] = "ListNumbering";
aAttributeStrings[ PDFWriter::RowSpan ] = "RowSpan";
aAttributeStrings[ PDFWriter::ColSpan ] = "ColSpan";
aAttributeStrings[ PDFWriter::LinkAnnotation ] = "LinkAnnotation";
}
std::map< PDFWriter::StructAttribute, const char* >::const_iterator it =
aAttributeStrings.find( eAttr );
#if OSL_DEBUG_LEVEL > 1
if( it == aAttributeStrings.end() )
SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttribute " << eAttr);
#endif
return it != aAttributeStrings.end() ? it->second : "";
}
const sal_Char* PDFWriterImpl::getAttributeValueTag( PDFWriter::StructAttributeValue eVal )
{
static std::map< PDFWriter::StructAttributeValue, const char* > aValueStrings;
if( aValueStrings.empty() )
{
aValueStrings[ PDFWriter::NONE ] = "None";
aValueStrings[ PDFWriter::Block ] = "Block";
aValueStrings[ PDFWriter::Inline ] = "Inline";
aValueStrings[ PDFWriter::Before ] = "Before";
aValueStrings[ PDFWriter::After ] = "After";
aValueStrings[ PDFWriter::Start ] = "Start";
aValueStrings[ PDFWriter::End ] = "End";
aValueStrings[ PDFWriter::LrTb ] = "LrTb";
aValueStrings[ PDFWriter::RlTb ] = "RlTb";
aValueStrings[ PDFWriter::TbRl ] = "TbRl";
aValueStrings[ PDFWriter::Center ] = "Center";
aValueStrings[ PDFWriter::Justify ] = "Justify";
aValueStrings[ PDFWriter::Auto ] = "Auto";
aValueStrings[ PDFWriter::Middle ] = "Middle";
aValueStrings[ PDFWriter::Normal ] = "Normal";
aValueStrings[ PDFWriter::Underline ] = "Underline";
aValueStrings[ PDFWriter::Overline ] = "Overline";
aValueStrings[ PDFWriter::LineThrough ] = "LineThrough";
aValueStrings[ PDFWriter::Disc ] = "Disc";
aValueStrings[ PDFWriter::Circle ] = "Circle";
aValueStrings[ PDFWriter::Square ] = "Square";
aValueStrings[ PDFWriter::Decimal ] = "Decimal";
aValueStrings[ PDFWriter::UpperRoman ] = "UpperRoman";
aValueStrings[ PDFWriter::LowerRoman ] = "LowerRoman";
aValueStrings[ PDFWriter::UpperAlpha ] = "UpperAlpha";
aValueStrings[ PDFWriter::LowerAlpha ] = "LowerAlpha";
}
std::map< PDFWriter::StructAttributeValue, const char* >::const_iterator it =
aValueStrings.find( eVal );
#if OSL_DEBUG_LEVEL > 1
if( it == aValueStrings.end() )
SAL_INFO("vcl.pdfwriter", "invalid PDFWriter::StructAttributeValue " << eVal);
#endif
return it != aValueStrings.end() ? it->second : "";
}
static void appendStructureAttributeLine( PDFWriter::StructAttribute i_eAttr, const PDFWriterImpl::PDFStructureAttribute& i_rVal, OStringBuffer& o_rLine, bool i_bIsFixedInt )
{
o_rLine.append( "/" );
o_rLine.append( PDFWriterImpl::getAttributeTag( i_eAttr ) );
if( i_rVal.eValue != PDFWriter::Invalid )
{
o_rLine.append( "/" );
o_rLine.append( PDFWriterImpl::getAttributeValueTag( i_rVal.eValue ) );
}
else
{
// numerical value
o_rLine.append( " " );
if( i_bIsFixedInt )
appendFixedInt( i_rVal.nValue, o_rLine );
else
o_rLine.append( i_rVal.nValue );
}
o_rLine.append( "\n" );
}
OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle )
{
// create layout, list and table attribute sets
OStringBuffer aLayout(256), aList(64), aTable(64);
for( PDFStructAttributes::const_iterator it = i_rEle.m_aAttributes.begin();
it != i_rEle.m_aAttributes.end(); ++it )
{
if( it->first == PDFWriter::ListNumbering )
appendStructureAttributeLine( it->first, it->second, aList, true );
else if( it->first == PDFWriter::RowSpan ||
it->first == PDFWriter::ColSpan )
appendStructureAttributeLine( it->first, it->second, aTable, false );
else if( it->first == PDFWriter::LinkAnnotation )
{
sal_Int32 nLink = it->second.nValue;
std::map< sal_Int32, sal_Int32 >::const_iterator link_it =
m_aLinkPropertyMap.find( nLink );
if( link_it != m_aLinkPropertyMap.end() )
nLink = link_it->second;
if( nLink >= 0 && nLink < (sal_Int32)m_aLinks.size() )
{
// update struct parent of link
OStringBuffer aStructParentEntry( 32 );
aStructParentEntry.append( i_rEle.m_nObject );
aStructParentEntry.append( " 0 R" );
m_aStructParentTree.push_back( aStructParentEntry.makeStringAndClear() );
m_aLinks[ nLink ].m_nStructParent = m_aStructParentTree.size()-1;
sal_Int32 nRefObject = createObject();
OStringBuffer aRef( 256 );
aRef.append( nRefObject );
aRef.append( " 0 obj\n"
"<</Type/OBJR/Obj " );
aRef.append( m_aLinks[ nLink ].m_nObject );
aRef.append( " 0 R>>\n"
"endobj\n\n"
);
if (updateObject(nRefObject))
{
writeBuffer( aRef.getStr(), aRef.getLength() );
}
i_rEle.m_aKids.push_back( PDFStructureElementKid( nRefObject ) );
}
else
{
OSL_FAIL( "unresolved link id for Link structure" );
#if OSL_DEBUG_LEVEL > 1
SAL_INFO("vcl.pdfwriter", "unresolved link id " << nLink << " for Link structure");
{
OStringBuffer aLine( "unresolved link id " );
aLine.append( nLink );
aLine.append( " for Link structure" );
emitComment( aLine.getStr() );
}
#endif
}
}
else
appendStructureAttributeLine( it->first, it->second, aLayout, true );
}
if( ! i_rEle.m_aBBox.IsEmpty() )
{
aLayout.append( "/BBox[" );
appendFixedInt( i_rEle.m_aBBox.Left(), aLayout );
aLayout.append( " " );
appendFixedInt( i_rEle.m_aBBox.Top(), aLayout );
aLayout.append( " " );
appendFixedInt( i_rEle.m_aBBox.Right(), aLayout );
aLayout.append( " " );
appendFixedInt( i_rEle.m_aBBox.Bottom(), aLayout );
aLayout.append( "]\n" );
}
std::vector< sal_Int32 > aAttribObjects;
if( !aLayout.isEmpty() )
{
aAttribObjects.push_back( createObject() );
if (updateObject( aAttribObjects.back() ))
{
OStringBuffer aObj( 64 );
aObj.append( aAttribObjects.back() );
aObj.append( " 0 obj\n"
"<</O/Layout\n" );
aLayout.append( ">>\nendobj\n\n" );
writeBuffer( aObj.getStr(), aObj.getLength() );
writeBuffer( aLayout.getStr(), aLayout.getLength() );
}
}
if( !aList.isEmpty() )
{
aAttribObjects.push_back( createObject() );
if (updateObject( aAttribObjects.back() ))
{
OStringBuffer aObj( 64 );
aObj.append( aAttribObjects.back() );
aObj.append( " 0 obj\n"
"<</O/List\n" );
aList.append( ">>\nendobj\n\n" );
writeBuffer( aObj.getStr(), aObj.getLength() );
writeBuffer( aList.getStr(), aList.getLength() );
}
}
if( !aTable.isEmpty() )
{
aAttribObjects.push_back( createObject() );
if (updateObject( aAttribObjects.back() ))
{
OStringBuffer aObj( 64 );
aObj.append( aAttribObjects.back() );
aObj.append( " 0 obj\n"
"<</O/Table\n" );
aTable.append( ">>\nendobj\n\n" );
writeBuffer( aObj.getStr(), aObj.getLength() );