Files
libreoffice/vcl/source/gdi/pdfwriter_impl.cxx
Oliver Bolte 9448592df2 INTEGRATION: CWS pchfix02 (1.98.4); FILE MERGED
2006/09/01 17:57:46 kaib 1.98.4.1: #i68856# Added header markers and pch files
2006-09-17 11:08:43 +00:00

9990 lines
367 KiB
C++

/*************************************************************************
*
* OpenOffice.org - a multi-platform office productivity suite
*
* $RCSfile: pdfwriter_impl.cxx,v $
*
* $Revision: 1.99 $
*
* last change: $Author: obo $ $Date: 2006-09-17 12:08:43 $
*
* The Contents of this file are made available subject to
* the terms of GNU Lesser General Public License Version 2.1.
*
*
* GNU Lesser General Public License Version 2.1
* =============================================
* Copyright 2005 by Sun Microsystems, Inc.
* 901 San Antonio Road, Palo Alto, CA 94303, USA
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 2.1, as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*
************************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_vcl.hxx"
#define _USE_MATH_DEFINES
#include <math.h>
#include <algorithm>
#include <pdfwriter_impl.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <rtl/ustrbuf.hxx>
#include <tools/debug.hxx>
#include <tools/zcodec.hxx>
#include <tools/stream.hxx>
#include <virdev.hxx>
#include <bmpacc.hxx>
#include <bitmapex.hxx>
#include <image.hxx>
#include <outdev.h>
#include <sallayout.hxx>
#include <metric.hxx>
#include <svsys.h>
#include <salgdi.hxx>
#include <svapp.hxx>
#include <osl/thread.h>
#include <osl/file.h>
#include <rtl/crc.h>
#include <rtl/digest.h>
#include "implncvt.hxx"
using namespace vcl;
using namespace rtl;
#if OSL_DEBUG_LEVEL < 2
#define COMPRESS_PAGES
#endif
#ifdef DO_TEST_PDF
void doTestCode()
{
static const char* pHome = getenv( "HOME" );
rtl::OUString aTestFile( RTL_CONSTASCII_USTRINGPARAM( "file://" ) );
aTestFile += rtl::OUString( pHome, strlen( pHome ), RTL_TEXTENCODING_MS_1252 );
aTestFile += rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "/pdf_export_test.pdf" ) );
PDFWriter::PDFWriterContext aContext;
aContext.URL = aTestFile;
aContext.Version = PDFWriter::PDF_1_4;
aContext.Tagged = true;
PDFWriter aWriter( aContext );
aWriter.NewPage();
// set duration of 3 sec for first page
aWriter.SetAutoAdvanceTime( 3 );
aWriter.SetMapMode( MapMode( MAP_100TH_MM ) );
aWriter.SetFillColor( Color( COL_LIGHTRED ) );
aWriter.SetLineColor( Color( COL_LIGHTGREEN ) );
aWriter.DrawRect( Rectangle( Point( 2000, 200 ), Size( 8000, 3000 ) ), 5000, 2000 );
aWriter.SetFont( Font( String( RTL_CONSTASCII_USTRINGPARAM( "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, String( RTL_CONSTASCII_USTRINGPARAM( "Link annot 1" ) ) );
sal_Int32 nFirstLink = aWriter.CreateLink( aRect );
PDFNote aNote;
aNote.Title = String( RTL_CONSTASCII_USTRINGPARAM( "A small test note" ) );
aNote.Contents = String( RTL_CONSTASCII_USTRINGPARAM( "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, String( RTL_CONSTASCII_USTRINGPARAM( "Dest second link" ) ) );
sal_Int32 nSecondDest = aWriter.CreateDest( aTargetRect );
aWriter.BeginStructureElement( PDFWriter::Section );
aWriter.BeginStructureElement( PDFWriter::Heading );
aWriter.DrawText( Point(4500, 9000), String( RTL_CONSTASCII_USTRINGPARAM( "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 ) ),
String( RTL_CONSTASCII_USTRINGPARAM( "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." ) ),
TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK
);
aWriter.SetActualText( String( RTL_CONSTASCII_USTRINGPARAM( "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( String( RTL_CONSTASCII_USTRINGPARAM( "This paragraph contains some lengthy nonsense to test structural element emission of PDFWriter." ) ) );
aWriter.EndStructureElement();
sal_Int32 nLongPara = aWriter.BeginStructureElement( PDFWriter::Paragraph );
aWriter.SetStructureAttribute( PDFWriter::WritingMode, PDFWriter::LrTb );
aWriter.DrawText( Rectangle( Point( 4500, 19000 ), Size( 12000, 1000 ) ),
String( RTL_CONSTASCII_USTRINGPARAM( "This paragraph is nothing special either but ends on the next page structurewise" ) ),
TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK
);
aWriter.NewPage();
// set transitional mode
aWriter.SetPageTransition( PDFWriter::WipeRightToLeft, 1500 );
aWriter.SetMapMode( MapMode( MAP_100TH_MM ) );
aWriter.SetTextColor( Color( COL_BLACK ) );
aWriter.SetFont( Font( String( RTL_CONSTASCII_USTRINGPARAM( "Times" ) ), Size( 0, 500 ) ) );
aWriter.DrawText( Rectangle( Point( 4500, 1500 ), Size( 12000, 3000 ) ),
String( RTL_CONSTASCII_USTRINGPARAM( "Here's where all things come to an end ... well at least the paragaph from the last page." ) ),
TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK
);
aWriter.EndStructureElement();
aWriter.SetFillColor( Color( COL_LIGHTBLUE ) );
// disable structure
aWriter.BeginStructureElement( PDFWriter::NonStructElement );
aWriter.DrawRect( aRect );
aWriter.BeginStructureElement( PDFWriter::Paragraph );
aWriter.DrawText( aRect, String( RTL_CONSTASCII_USTRINGPARAM( "Link annot 2" ) ) );
sal_Int32 nSecondLink = aWriter.CreateLink( aRect );
aWriter.SetFillColor( Color( COL_LIGHTGREEN ) );
aWriter.BeginStructureElement( PDFWriter::ListItem );
aWriter.DrawRect( aTargetRect );
aWriter.DrawText( aTargetRect, String( RTL_CONSTASCII_USTRINGPARAM( "Dest first link" ) ) );
sal_Int32 nFirstDest = aWriter.CreateDest( aTargetRect );
// enable structure
aWriter.EndStructureElement();
// add something to the long paragraph as an afterthought
sal_Int32 nSaveStruct = aWriter.GetCurrentStructureElement();
aWriter.SetCurrentStructureElement( nLongPara );
aWriter.DrawText( Rectangle( Point( 4500,4500 ), Size( 12000, 1000 ) ),
String( RTL_CONSTASCII_USTRINGPARAM( "Add something to the longish paragraph above." ) ),
TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK );
aWriter.SetCurrentStructureElement( nSaveStruct );
aWriter.EndStructureElement();
aWriter.EndStructureElement();
aWriter.BeginStructureElement( PDFWriter::Figure );
aWriter.BeginStructureElement( PDFWriter::Caption );
aWriter.DrawText( Point( 4500, 9000 ), String( RTL_CONSTASCII_USTRINGPARAM( "Some drawing stuff inside the structure" ) ) );
aWriter.EndStructureElement();
aWriter.DrawEllipse( Rectangle( Point( 4500, 9600 ), Size( 12000, 3000 ) ) );
// 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,
String( RTL_CONSTASCII_USTRINGPARAM( "Some transparent text" ) ),
TEXT_DRAW_CENTER | TEXT_DRAW_VCENTER | TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK );
aWriter.EndTransparencyGroup( aTranspRect, 50 );
// prepare an alpha mask
Bitmap aTransMask( Size( 256, 256 ), 8, &Bitmap::GetGreyPalette( 256 ) );
BitmapWriteAccess* pAcc = aTransMask.AcquireWriteAccess();
for( int nX = 0; nX < 256; nX++ )
for( int nY = 0; nY < 256; nY++ )
pAcc->SetPixel( nX, nY, BitmapColor( (BYTE)((nX+nY)/2) ) );
aTransMask.ReleaseAccess( pAcc );
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,
String( RTL_CONSTASCII_USTRINGPARAM( "Some transparent text" ) ),
TEXT_DRAW_CENTER | TEXT_DRAW_VCENTER | TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK );
aTranspRect = Rectangle( Point( 1500, 16500 ), Size( 4800, 3000 ) );
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,
String( RTL_CONSTASCII_USTRINGPARAM( "Some transparent text" ) ),
TEXT_DRAW_CENTER | TEXT_DRAW_VCENTER | TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK );
aWriter.EndTransparencyGroup( aTranspRect, aTransMask );
Bitmap aImageBmp( Size( 256, 256 ), 24 );
pAcc = aImageBmp.AcquireWriteAccess();
pAcc->SetFillColor( Color( 0xff, 0, 0xff ) );
pAcc->FillRect( Rectangle( Point( 0, 0 ), Size( 256, 256 ) ) );
aImageBmp.ReleaseAccess( pAcc );
BitmapEx aBmpEx( aImageBmp, AlphaMask( aTransMask ) );
aWriter.DrawBitmapEx( Point( 1500, 19500 ), Size( 4800, 3000 ), aBmpEx );
aWriter.EndStructureElement();
aWriter.EndStructureElement();
LineInfo aLI( LINE_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 ) };
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();
aWriter.SetMapMode( MapMode( MAP_100TH_MM ) );
aWriter.SetFont( Font( String( RTL_CONSTASCII_USTRINGPARAM( "Times" ) ), Size( 0, 500 ) ) );
aWriter.SetTextColor( Color( COL_BLACK ) );
aRect = Rectangle( Point( 4500, 6000 ), Size( 6000, 1500 ) );
aWriter.DrawRect( aRect );
aWriter.DrawText( aRect, String( RTL_CONSTASCII_USTRINGPARAM( "www.heise.de" ) ) );
sal_Int32 nURILink = aWriter.CreateLink( aRect );
aWriter.SetLinkURL( nURILink, OUString( RTL_CONSTASCII_USTRINGPARAM( "http://www.heise.de" ) ) );
aWriter.SetLinkDest( nFirstLink, nFirstDest );
aWriter.SetLinkDest( nSecondLink, nSecondDest );
// include a button
PDFWriter::PushButtonWidget aBtn;
aBtn.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "testButton" ) );
aBtn.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "A test button" ) );
aBtn.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "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 = OUString( RTL_CONSTASCII_USTRINGPARAM( "wwwButton" ) );
aUriBtn.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "A URI button" ) );
aUriBtn.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "to www" ) );
aUriBtn.Location = Rectangle( Point( 9500, 9000 ), Size( 4500, 3000 ) );
aUriBtn.Border = aUriBtn.Background = true;
aUriBtn.URL = OUString( RTL_CONSTASCII_USTRINGPARAM( "http://www.heise.de" ) );
aWriter.CreateControl( aUriBtn );
// include a dest button
PDFWriter::PushButtonWidget aDstBtn;
aDstBtn.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "destButton" ) );
aDstBtn.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "A Dest button" ) );
aDstBtn.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "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 = OUString( RTL_CONSTASCII_USTRINGPARAM( "textCheckBox" ) );
aCBox.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "A test check box" ) );
aCBox.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "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 = OUString( RTL_CONSTASCII_USTRINGPARAM( "textCheckBox2" ) );
aCBox2.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "Another test check box" ) );
aCBox2.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "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 = OUString( RTL_CONSTASCII_USTRINGPARAM( "rb1_1" ) );
aRB1.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "radio 1 button 1" ) );
aRB1.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "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( String( RTL_CONSTASCII_USTRINGPARAM( "Courier" ) ), Size( 0, 800 ) );
aWriter.CreateControl( aRB1 );
PDFWriter::RadioButtonWidget aRB2;
aRB2.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "rb2_1" ) );
aRB2.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "radio 2 button 1" ) );
aRB2.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "Joy" ) );
aRB2.Location = Rectangle( Point( 10500, 15000 ), Size( 3000, 1000 ) );
aRB2.Selected = true;
aRB2.RadioGroup = 2;
aWriter.CreateControl( aRB2 );
PDFWriter::RadioButtonWidget aRB3;
aRB3.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "rb1_2" ) );
aRB3.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "radio 1 button 2" ) );
aRB3.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "Desperation" ) );
aRB3.Location = Rectangle( Point( 4500, 16000 ), Size( 3000, 1000 ) );
aRB3.Selected = true;
aRB3.RadioGroup = 1;
aWriter.CreateControl( aRB3 );
PDFWriter::EditWidget aEditBox;
aEditBox.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "testEdit" ) );
aEditBox.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "A test edit field" ) );
aEditBox.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "A little test text" ) );
aEditBox.TextStyle = TEXT_DRAW_LEFT | TEXT_DRAW_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 = OUString( RTL_CONSTASCII_USTRINGPARAM( "testListBox" ) );
aLstBox.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "One" ) );
aLstBox.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "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( RTL_CONSTASCII_USTRINGPARAM( "One" ) ) );
aLstBox.Entries.push_back( OUString( RTL_CONSTASCII_USTRINGPARAM( "Two" ) ) );
aLstBox.Entries.push_back( OUString( RTL_CONSTASCII_USTRINGPARAM( "Three" ) ) );
aLstBox.Entries.push_back( OUString( RTL_CONSTASCII_USTRINGPARAM( "Four" ) ) );
aWriter.CreateControl( aLstBox );
// dropdown list box
aLstBox.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "testDropDownListBox" ) );
aLstBox.DropDown = true;
aLstBox.Location = Rectangle( Point( 4500, 19500 ), Size( 3000, 500 ) );
aWriter.CreateControl( aLstBox );
// combo box
PDFWriter::ComboBoxWidget aComboBox;
aComboBox.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "testComboBox" ) );
aComboBox.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "test a combobox" ) );
aComboBox.Entries.push_back( OUString( RTL_CONSTASCII_USTRINGPARAM( "Larry" ) ) );
aComboBox.Entries.push_back( OUString( RTL_CONSTASCII_USTRINGPARAM( "Curly" ) ) );
aComboBox.Entries.push_back( OUString( RTL_CONSTASCII_USTRINGPARAM( "Moe" ) ) );
aComboBox.Location = Rectangle( Point( 4500, 20000 ), Size( 3000, 500 ) );
aWriter.CreateControl( aComboBox );
// test outlines
sal_Int32 nPage1OL = aWriter.CreateOutlineItem();
aWriter.SetOutlineItemText( nPage1OL, OUString( RTL_CONSTASCII_USTRINGPARAM( "Page 1" ) ) );
aWriter.SetOutlineItemDest( nPage1OL, nSecondDest );
aWriter.CreateOutlineItem( nPage1OL, OUString( RTL_CONSTASCII_USTRINGPARAM( "Dest 2" ) ), nSecondDest );
aWriter.CreateOutlineItem( nPage1OL, OUString( RTL_CONSTASCII_USTRINGPARAM( "Dest 2 revisited" ) ), nSecondDest );
aWriter.CreateOutlineItem( nPage1OL, OUString( RTL_CONSTASCII_USTRINGPARAM( "Dest 2 again" ) ), nSecondDest );
sal_Int32 nPage2OL = aWriter.CreateOutlineItem();
aWriter.SetOutlineItemText( nPage2OL, OUString( RTL_CONSTASCII_USTRINGPARAM( "Page 2" ) ) );
aWriter.CreateOutlineItem( nPage2OL, OUString( RTL_CONSTASCII_USTRINGPARAM( "Dest 1" ) ), nFirstDest );
aWriter.Emit();
}
#endif
static const sal_Int32 nLog10Divisor = 1;
static const double fDivisor = 10.0;
static inline double pixelToPoint( sal_Int32 px ) { return double(px)/fDivisor; }
static inline double pixelToPoint( double px ) { return px/fDivisor; }
static inline sal_Int32 pointToPixel( double pt ) { return sal_Int32(pt*fDivisor); }
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 )
{
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 )
{
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--;
}
}
static void appendUnicodeTextString( const rtl::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 );
}
}
OString PDFWriterImpl::convertWidgetFieldName( const rtl::OUString& rString )
{
OStringBuffer aBuffer( rString.getLength()+64 );
appendName( rString, aBuffer );
// replace all '.' by '_'
sal_Int32 nLen = aBuffer.getLength();
for( sal_Int32 i = 0; i < nLen; i++ )
{
sal_Char aChar = aBuffer.charAt( i );
if( aChar == '.' )
aBuffer.setCharAt( i, '_' );
}
OString aRet = aBuffer.makeStringAndClear();
std::hash_map<OString, sal_Int32, OStringHash>::iterator it = m_aFieldNameMap.find( aRet );
if( it != m_aFieldNameMap.end() ) // not unique
{
std::hash_map< OString, sal_Int32, OStringHash >::const_iterator check_it;
OString aTry;
do
{
OStringBuffer aUnique( aRet.getLength() + 16 );
aUnique.append( aRet );
aUnique.append( '_' );
aUnique.append( it->second );
it->second++;
aTry = aUnique.makeStringAndClear();
check_it = m_aFieldNameMap.find( aTry );
} while( check_it != m_aFieldNameMap.end() );
aRet = aTry;
}
else
m_aFieldNameMap[ aRet ] = 2;
return aRet;
}
static void appendFixedInt( sal_Int32 nValue, OStringBuffer& rBuffer, sal_Int32 nPrecision = nLog10Divisor )
{
if( nValue < 0 )
{
rBuffer.append( '-' );
nValue = -nValue;
}
sal_Int32 nFactor = 1, nDiv = nPrecision;
while( nDiv-- )
nFactor *= 10;
sal_Int32 nInt = nValue / nFactor;
rBuffer.append( nInt );
if( nFactor > 1 )
{
sal_Int32 nDecimal = nValue % nFactor;
if( nDecimal )
{
rBuffer.append( '.' );
// omit trailing zeros
while( (nDecimal % 10) == 0 )
nDecimal /= 10;
rBuffer.append( nDecimal );
}
}
}
// appends a double. PDF does not accept exponential format, only fixed point
static void appendDouble( double fValue, OStringBuffer& rBuffer, int 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( 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 )
{
if( rColor != Color( COL_TRANSPARENT ) )
{
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 );
}
}
static void appendStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
{
if( rColor != Color( COL_TRANSPARENT ) )
{
appendColor( rColor, rBuffer );
rBuffer.append( " RG" );
}
}
static void appendNonStrokingColor( const Color& rColor, OStringBuffer& rBuffer )
{
if( rColor != Color( COL_TRANSPARENT ) )
{
appendColor( rColor, rBuffer );
rBuffer.append( " rg" );
}
}
// matrix helper class
namespace vcl
{
/* 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 < 5; i++ ) f[i] = pn[i]; }
public:
Matrix3();
~Matrix3() {}
void skew( double alpha, double beta );
void scale( double sx, double sy );
void rotate( double angle );
void translate( double tx, double ty );
void append( PDFWriterImpl::PDFPage& rPage, OStringBuffer& rBuffer, Point* pBack = NULL );
Point transform( const Point& rPoint );
};
}
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 )
{
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::append( PDFWriterImpl::PDFPage& rPage, OStringBuffer& rBuffer, Point* pBack )
{
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, false, pBack );
}
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_nStreamObject( 0 ),
m_nStreamLengthObject( 0 ),
m_nBeginStreamPos( 0 ),
m_eTransition( PDFWriter::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_nStreamObject = m_pWriter->createObject();
if( ! m_pWriter->updateObject( m_nStreamObject ) )
return;
m_nStreamLengthObject = m_pWriter->createObject();
// write content stream header
OStringBuffer aLine;
aLine.append( m_nStreamObject );
aLine.append( " 0 obj\n<</Length " );
aLine.append( m_nStreamLengthObject );
aLine.append( " 0 R" );
#if defined ( COMPRESS_PAGES ) && !defined ( DEBUG_DISABLE_PDFCOMPRESSION )
aLine.append( "/Filter/FlateDecode" );
#endif
aLine.append( ">>\nstream\n" );
if( ! m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() ) )
return;
if( osl_File_E_None != osl_getFilePos( m_pWriter->m_aFile, &m_nBeginStreamPos ) )
{
osl_closeFile( m_pWriter->m_aFile );
m_pWriter->m_bOpen = false;
}
#if defined ( COMPRESS_PAGES ) && !defined ( DEBUG_DISABLE_PDFCOMPRESSION )
m_pWriter->beginCompression();
#endif
m_pWriter->checkAndEnableStreamEncryption( m_nStreamObject );
}
void PDFWriterImpl::PDFPage::endStream()
{
sal_uInt64 nEndStreamPos;
if( osl_File_E_None != osl_getFilePos( m_pWriter->m_aFile, &nEndStreamPos ) )
{
osl_closeFile( m_pWriter->m_aFile );
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::Landscape: aLine.append( "/Rotate 90\n" );break;
case PDFWriter::Seascape: aLine.append( "/Rotate -90\n" );break;
case PDFWriter::Portrait: aLine.append( "/Rotate 0\n" );break;
case PDFWriter::Inherit:
default:
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 0
// FIXME: implement tab order as Structure Tree
if( m_bHasWidgets && m_pWriter->getVersion() >= PDFWriter::PDF_1_5 )
aLine.append( " /Tabs /S\n" );
#endif
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::Regular && m_nTransTime > 0 )
{
// transition duration
aLine.append( "/Trans<</D " );
appendDouble( (double)m_nTransTime/1000.0, aLine, 3 );
aLine.append( "\n" );
const char *pStyle = NULL, *pDm = NULL, *pM = NULL, *pDi = NULL;
switch( m_eTransition )
{
case PDFWriter::SplitHorizontalInward:
pStyle = "Split"; pDm = "H"; pM = "I"; break;
case PDFWriter::SplitHorizontalOutward:
pStyle = "Split"; pDm = "H"; pM = "O"; break;
case PDFWriter::SplitVerticalInward:
pStyle = "Split"; pDm = "V"; pM = "I"; break;
case PDFWriter::SplitVerticalOutward:
pStyle = "Split"; pDm = "V"; pM = "O"; break;
case PDFWriter::BlindsHorizontal:
pStyle = "Blinds"; pDm = "H"; break;
case PDFWriter::BlindsVertical:
pStyle = "Blinds"; pDm = "V"; break;
case PDFWriter::BoxInward:
pStyle = "Box"; pM = "I"; break;
case PDFWriter::BoxOutward:
pStyle = "Box"; pM = "O"; break;
case PDFWriter::WipeLeftToRight:
pStyle = "Wipe"; pDi = "0"; break;
case PDFWriter::WipeBottomToTop:
pStyle = "Wipe"; pDi = "90"; break;
case PDFWriter::WipeRightToLeft:
pStyle = "Wipe"; pDi = "180"; break;
case PDFWriter::WipeTopToBottom:
pStyle = "Wipe"; pDi = "270"; break;
case PDFWriter::Dissolve:
pStyle = "Dissolve"; break;
case PDFWriter::GlitterLeftToRight:
pStyle = "Glitter"; pDi = "0"; break;
case PDFWriter::GlitterTopToBottom:
pStyle = "Glitter"; pDi = "270"; break;
case PDFWriter::GlitterTopLeftToBottomRight:
pStyle = "Glitter"; pDi = "315"; break;
case PDFWriter::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::PDF_1_3 )
{
aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/I true>>" );
}
aLine.append( "/Contents " );
aLine.append( m_nStreamObject );
aLine.append( " 0 R>>\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 ( MAP_PIXEL == _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, bool bNeg, Point* pOutPoint ) const
{
if( pOutPoint )
{
Point aPoint( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
m_pWriter->m_aMapMode,
m_pWriter->getReferenceDevice(),
rPoint ) );
*pOutPoint = aPoint;
}
Point aPoint( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
m_pWriter->m_aMapMode,
m_pWriter->getReferenceDevice(),
rPoint ) );
sal_Int32 nValue = aPoint.X();
if( bNeg )
nValue = -nValue;
appendFixedInt( nValue, rBuffer );
rBuffer.append( ' ' );
nValue = pointToPixel(getHeight()) - aPoint.Y();
if( bNeg )
nValue = -nValue;
appendFixedInt( nValue, rBuffer );
}
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, true );
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 Polygon& rPoly, OStringBuffer& rBuffer, bool bClose ) const
{
USHORT nPoints = rPoly.GetSize();
/*
* #108582# applications do weird things
*/
sal_uInt32 nBufLen = rBuffer.getLength();
if( nPoints > 0 )
{
const BYTE* pFlagArray = rPoly.GetConstFlagAry();
appendPoint( rPoly[0], rBuffer );
rBuffer.append( " m\n" );
for( USHORT i = 1; i < nPoints; i++ )
{
if( pFlagArray && pFlagArray[i] == POLY_CONTROL && nPoints-i > 2 )
{
// bezier
DBG_ASSERT( pFlagArray[i+1] == POLY_CONTROL && pFlagArray[i+2] != POLY_CONTROL, "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::appendPolyPolygon( const PolyPolygon& rPolyPoly, OStringBuffer& rBuffer, bool bClose ) const
{
USHORT nPolygons = rPolyPoly.Count();
for( USHORT n = 0; n < nPolygons; n++ )
appendPolygon( rPolyPoly[n], rBuffer, bClose );
}
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, 1 );
}
void PDFWriterImpl::PDFPage::appendMappedLength( double fLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32* pOutLength ) const
{
Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode,
m_pWriter->m_aMapMode,
m_pWriter->getReferenceDevice(),
Size( 1000, 1000 ) ) );
if( pOutLength )
*pOutLength = (sal_Int32)(fLength*(double)(bVertical ? aSize.Height() : aSize.Width())/1000.0);
fLength *= pixelToPoint((double)(bVertical ? aSize.Height() : aSize.Width()) / 1000.0);
appendDouble( fLength, rBuffer );
}
bool PDFWriterImpl::PDFPage::appendLineInfo( const LineInfo& rInfo, OStringBuffer& rBuffer ) const
{
bool bRet = true;
if( rInfo.GetStyle() == LINE_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
{
// check for implementation limits of dash array
// in PDF reader apps (e.g. acroread)
if( 2*(rInfo.GetDashCount() + rInfo.GetDotCount()) > 10 )
bRet = false;
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 )
rBuffer.append( "0 w\n" );
return bRet;
}
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, true );
rBuffer.append( " m\n" );
for( sal_Int32 n = 0; n < nWidth; )
{
n += nDelta;
appendMappedLength( n, rBuffer, false );
rBuffer.append( ' ' );
appendMappedLength( nDelta+nY, rBuffer, true );
rBuffer.append( ' ' );
n += nDelta;
appendMappedLength( n, rBuffer, false );
rBuffer.append( ' ' );
appendMappedLength( nY, rBuffer, true );
rBuffer.append( " v " );
if( n < nWidth )
{
n += nDelta;
appendMappedLength( n, rBuffer, false );
rBuffer.append( ' ' );
appendMappedLength( nY-nDelta, rBuffer, true );
rBuffer.append( ' ' );
n += nDelta;
appendMappedLength( n, rBuffer, false );
rBuffer.append( ' ' );
appendMappedLength( nY, rBuffer, true );
rBuffer.append( " v\n" );
}
}
rBuffer.append( "S\n" );
}
/*
* class PDFWriterImpl
*/
PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext )
:
m_pReferenceDevice( NULL ),
m_aMapMode( MAP_POINT, Point(), Fraction( 1L, pointToPixel(1) ), Fraction( 1L, pointToPixel(1) ) ),
m_nCurrentStructElement( 0 ),
m_bEmitStructure( true ),
m_bNewMCID( false ),
m_nCurrentControl( -1 ),
m_nNextFID( 1 ),
m_nInheritedPageWidth( 595 ), // default A4
m_nInheritedPageHeight( 842 ), // default A4
m_eInheritedOrientation( PDFWriter::Portrait ),
m_nCurrentPage( -1 ),
m_nResourceDict( -1 ),
m_nFontResourceDict( -1 ),
m_pCodec( NULL ),
m_aCipher( (rtlCipher)NULL ),
m_aDigest( NULL ),
m_bEncryptThisStream( false ),
m_aDocID( 32 ),
m_aCreationDateString( 64 ),
m_pEncryptionBuffer( NULL ),
m_nEncryptionBufferSize( 0 )
{
#ifdef DO_TEST_PDF
static bool bOnce = true;
if( bOnce )
{
bOnce = false;
doTestCode();
}
#endif
m_aContext = rContext;
m_aStructure.push_back( PDFStructureElement() );
m_aStructure[0].m_nOwnElement = 0;
m_aStructure[0].m_nParentElement = 0;
Font aFont;
aFont.SetName( String( RTL_CONSTASCII_USTRINGPARAM( "Times" ) ) );
aFont.SetSize( Size( 0, 12 ) );
GraphicsState aState;
aState.m_aMapMode = m_aMapMode;
aState.m_aFont = aFont;
m_aGraphicsStack.push_front( aState );
oslFileError aError = osl_openFile( m_aContext.URL.pData, &m_aFile, osl_File_OpenFlag_Write | osl_File_OpenFlag_Create );
if( aError != osl_File_E_None )
{
if( aError == osl_File_E_EXIST )
{
aError = osl_openFile( m_aContext.URL.pData, &m_aFile, osl_File_OpenFlag_Write );
if( aError == osl_File_E_None )
aError = osl_setFileSize( m_aFile, 0 );
}
}
if( aError != osl_File_E_None )
return;
m_bOpen = true;
/* 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 */
checkEncryptionBufferSize( 0x4000 );
// write header
OStringBuffer aBuffer( 20 );
aBuffer.append( "%PDF-" );
switch( m_aContext.Version )
{
case PDFWriter::PDF_1_2: aBuffer.append( "1.2" );break;
case PDFWriter::PDF_1_3: aBuffer.append( "1.3" );break;
default:
case PDFWriter::PDF_1_4: aBuffer.append( "1.4" );break;
case PDFWriter::PDF_1_5: aBuffer.append( "1.5" );break;
}
// append something binary as comment (suggested in PDF Reference)
aBuffer.append( "\n%äüöß\n" );
if( !writeBuffer( aBuffer.getStr(), aBuffer.getLength() ) )
{
osl_closeFile( m_aFile );
m_bOpen = false;
return;
}
// insert outline root
m_aOutline.push_back( PDFOutlineEntry() );
}
PDFWriterImpl::~PDFWriterImpl()
{
delete static_cast<VirtualDevice*>(m_pReferenceDevice);
if( m_aCipher )
rtl_cipher_destroyARCFOUR( m_aCipher );
if( m_aDigest )
rtl_digest_destroyMD5( m_aDigest );
rtl_freeMemory( m_pEncryptionBuffer );
}
void PDFWriterImpl::setDocInfo( const PDFDocInfo& rInfo )
{
m_aDocInfo.Title = rInfo.Title;
m_aDocInfo.Author = rInfo.Author;
m_aDocInfo.Subject = rInfo.Subject;
m_aDocInfo.Keywords = rInfo.Keywords;
m_aDocInfo.Creator = rInfo.Creator;
m_aDocInfo.Producer = rInfo.Producer;
//build the document id
rtl::OString aInfoValuesOut;
OStringBuffer aID( 1024 );
if( m_aDocInfo.Title.Len() )
appendUnicodeTextString( m_aDocInfo.Title, aID );
if( m_aDocInfo.Author.Len() )
appendUnicodeTextString( m_aDocInfo.Author, aID );
if( m_aDocInfo.Subject.Len() )
appendUnicodeTextString( m_aDocInfo.Subject, aID );
if( m_aDocInfo.Keywords.Len() )
appendUnicodeTextString( m_aDocInfo.Keywords, aID );
if( m_aDocInfo.Creator.Len() )
appendUnicodeTextString( m_aDocInfo.Creator, aID );
if( m_aDocInfo.Producer.Len() )
appendUnicodeTextString( m_aDocInfo.Producer, aID );
TimeValue aTVal, aGMT;
oslDateTime aDT;
osl_getSystemTime( &aGMT );
osl_getLocalTimeFromSystemTime( &aGMT, &aTVal );
osl_getDateTimeFromTimeValue( &aTVal, &aDT );
m_aCreationDateString.append( "D:" );
m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Year/1000)%10)) );
m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Year/100)%10)) );
m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Year/10)%10)) );
m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Year)%10)) );
m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Month/10)%10)) );
m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Month)%10)) );
m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Day/10)%10)) );
m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Day)%10)) );
m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Hours/10)%10)) );
m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Hours)%10)) );
m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Minutes/10)%10)) );
m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Minutes)%10)) );
m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Seconds/10)%10)) );
m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Seconds)%10)) );
sal_uInt32 nDelta = 0;
if( aGMT.Seconds > aTVal.Seconds )
{
m_aCreationDateString.append( "-" );
nDelta = aGMT.Seconds-aTVal.Seconds;
}
else if( aGMT.Seconds < aTVal.Seconds )
{
m_aCreationDateString.append( "+" );
nDelta = aTVal.Seconds-aGMT.Seconds;
}
else
m_aCreationDateString.append( "Z" );
if( nDelta )
{
m_aCreationDateString.append( (sal_Char)('0' + ((nDelta/36000)%10)) );
m_aCreationDateString.append( (sal_Char)('0' + ((nDelta/3600)%10)) );
m_aCreationDateString.append( "'" );
m_aCreationDateString.append( (sal_Char)('0' + ((nDelta/600)%6)) );
m_aCreationDateString.append( (sal_Char)('0' + ((nDelta/60)%10)) );
}
m_aCreationDateString.append( "'" );
aID.append( m_aCreationDateString.getStr(), m_aCreationDateString.getLength() );
aInfoValuesOut = aID.makeStringAndClear();
DBG_ASSERT( m_aDigest != NULL, "PDFWrite_Impl::setDocInfo: cannot obtain a digest object !" );
m_aDocID.setLength( 0 );
if( m_aDigest )
{
osl_getSystemTime( &aGMT );
rtlDigestError nError = rtl_digest_updateMD5( m_aDigest, &aGMT, sizeof( aGMT ) );
if( nError == rtl_Digest_E_None )
nError = rtl_digest_updateMD5( m_aDigest, m_aContext.URL.getStr(), m_aContext.URL.getLength()*sizeof(sal_Unicode) );
if( nError == rtl_Digest_E_None )
nError = rtl_digest_updateMD5( m_aDigest, aInfoValuesOut.getStr(), aInfoValuesOut.getLength() );
if( nError == rtl_Digest_E_None )
{
//the binary form of the doc id is needed for encryption stuff
rtl_digest_getMD5( m_aDigest, m_nDocID, 16 );
for( unsigned int i = 0; i < 16; i++ )
appendHex( m_nDocID[i], m_aDocID );
}
}
}
/* 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 rtl::OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer )
{
rOutBuffer.append( "<" );
if( m_aContext.Encrypt )
{
const sal_Unicode* pStr = rInString.getStr();
sal_Int32 nLen = rInString.getLength();
//prepare a unicode string, encrypt it
if( checkEncryptionBufferSize( nLen*2 ) )
{
enableStringEncryption( nInObjectNumber );
register 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( register int i = 0; i < nLen; i++ )
{
register 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(register int i = 0; i < nChars; i++)
appendHex( m_pEncryptionBuffer[i], rOutBuffer );
}
}
else
appendUnicodeTextString( rInString, rOutBuffer );
rOutBuffer.append( ">" );
}
inline void PDFWriterImpl::appendLiteralStringEncrypt( rtl::OStringBuffer& rInString, const sal_Int32 nInObjectNumber, rtl::OStringBuffer& rOutBuffer )
{
rOutBuffer.append( "(" );
sal_Int32 nChars = rInString.getLength();
//check for encryption, if ok, encrypt the string, then convert with appndLiteralString
if( m_aContext.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( (const sal_Char*)m_pEncryptionBuffer, nChars, rOutBuffer );
}
else
rOutBuffer.append( rInString.getStr(), nChars );
rOutBuffer.append( ")" );
}
inline void PDFWriterImpl::appendLiteralStringEncrypt( const rtl::OString& rInString, const sal_Int32 nInObjectNumber, rtl::OStringBuffer& rOutBuffer )
{
rtl::OStringBuffer aBufferString( rInString );
appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer);
}
inline void PDFWriterImpl::appendLiteralStringEncrypt( const rtl::OUString& rInString, const sal_Int32 nInObjectNumber, rtl::OStringBuffer& rOutBuffer )
{
rtl::OString aBufferString( rtl::OUStringToOString( rInString, RTL_TEXTENCODING_ASCII_US ) );
appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer);
}
/* end i12626 methods */
void PDFWriterImpl::emitComment( const char* pComment )
{
OStringBuffer aLine( 64 );
aLine.append( "% " );
aLine.append( (const sal_Char*)pComment );
aLine.append( "\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
}
bool PDFWriterImpl::compressStream( SvMemoryStream* pStream )
{
#ifndef DEBUG_DISABLE_PDFCOMPRESSION
pStream->Seek( STREAM_SEEK_TO_END );
ULONG nEndPos = pStream->Tell();
pStream->Seek( STREAM_SEEK_TO_BEGIN );
ZCodec* pCodec = new ZCodec( 0x4000, 0x4000 );
SvMemoryStream aStream;
pCodec->BeginCompression();
pCodec->Write( aStream, (const BYTE*)pStream->GetData(), nEndPos );
pCodec->EndCompression();
delete pCodec;
nEndPos = aStream.Tell();
pStream->Seek( STREAM_SEEK_TO_BEGIN );
aStream.Seek( STREAM_SEEK_TO_BEGIN );
pStream->SetStreamSize( nEndPos );
pStream->Write( aStream.GetData(), nEndPos );
return true;
#else
return false;
#endif
}
void PDFWriterImpl::beginCompression()
{
#ifndef DEBUG_DISABLE_PDFCOMPRESSION
m_pCodec = new ZCodec( 0x4000, 0x4000 );
m_pMemStream = new SvMemoryStream();
m_pCodec->BeginCompression();
#endif
}
void PDFWriterImpl::endCompression()
{
#ifndef DEBUG_DISABLE_PDFCOMPRESSION
if( m_pCodec )
{
m_pCodec->EndCompression();
delete m_pCodec;
m_pCodec = NULL;
sal_uInt64 nLen = m_pMemStream->Tell();
m_pMemStream->Seek( 0 );
writeBuffer( m_pMemStream->GetData(), nLen );
delete m_pMemStream;
m_pMemStream = NULL;
}
#endif
}
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.begin() != m_aOutputStreams.end() )
{
m_aOutputStreams.front().m_pStream->Seek( STREAM_SEEK_TO_END );
m_aOutputStreams.front().m_pStream->Write( pBuffer, sal::static_int_cast<sal_Size>(nBytes) );
return true;
}
sal_uInt64 nWritten;
if( m_pCodec )
{
m_pCodec->Write( *m_pMemStream, static_cast<const BYTE*>(pBuffer), (ULONG)nBytes );
nWritten = nBytes;
}
else
{
sal_Bool buffOK = sal_True;
if( m_bEncryptThisStream )
{
/* implement the encryption part of the PDF spec encryption algorithm 3.1 */
if( ( buffOK = checkEncryptionBufferSize( static_cast<sal_Int32>(nBytes) ) ) != sal_False )
rtl_cipher_encodeARCFOUR( m_aCipher,
(sal_uInt8*)pBuffer, static_cast<sal_Size>(nBytes),
m_pEncryptionBuffer, static_cast<sal_Size>(nBytes) );
}
if( osl_writeFile( m_aFile,
( m_bEncryptThisStream && buffOK ) ? m_pEncryptionBuffer : pBuffer,
nBytes, &nWritten ) != osl_File_E_None )
nWritten = 0;
if( nWritten != nBytes )
{
osl_closeFile( m_aFile );
m_bOpen = false;
}
}
return nWritten == nBytes;
}
OutputDevice* PDFWriterImpl::getReferenceDevice()
{
if( ! m_pReferenceDevice )
{
VirtualDevice* pVDev = new VirtualDevice( 0 );
m_pReferenceDevice = pVDev;
pVDev->SetReferenceDevice( VirtualDevice::REFDEV_MODE_PDF1 );
pVDev->SetOutputSizePixel( Size( 640, 480 ) );
pVDev->SetMapMode( MAP_MM );
m_pReferenceDevice->mpPDFWriter = this;
m_pReferenceDevice->ImplUpdateFontData( TRUE );
}
return m_pReferenceDevice;
}
class ImplPdfBuiltinFontData : public ImplFontData
{
private:
const PDFWriterImpl::BuiltinFont& mrBuiltin;
public:
enum {PDF_FONT_MAGIC = 0xBDFF0A1C };
ImplPdfBuiltinFontData( const PDFWriterImpl::BuiltinFont& );
const PDFWriterImpl::BuiltinFont* GetBuiltinFont() const { return &mrBuiltin; }
virtual ImplFontData* Clone() const { return new ImplPdfBuiltinFontData(*this); }
virtual ImplFontEntry* CreateFontInstance( ImplFontSelectData& ) const;
};
inline const ImplPdfBuiltinFontData* GetPdfFontData( const ImplFontData* pFontData )
{
const ImplPdfBuiltinFontData* pFD = NULL;
if( pFontData && pFontData->CheckMagic( ImplPdfBuiltinFontData::PDF_FONT_MAGIC ) )
pFD = static_cast<const ImplPdfBuiltinFontData*>( pFontData );
return pFD;
}
static ImplDevFontAttributes GetDevFontAttributes( const PDFWriterImpl::BuiltinFont& rBuiltin )
{
ImplDevFontAttributes aDFA;
aDFA.maName = String::CreateFromAscii( rBuiltin.m_pName );
aDFA.maStyleName = String::CreateFromAscii( rBuiltin.m_pStyleName );
aDFA.meFamily = rBuiltin.m_eFamily;
aDFA.mbSymbolFlag = (rBuiltin.m_eCharSet == RTL_TEXTENCODING_SYMBOL);
aDFA.mePitch = rBuiltin.m_ePitch;
aDFA.meWeight = rBuiltin.m_eWeight;
aDFA.meItalic = rBuiltin.m_eItalic;
aDFA.meWidthType = rBuiltin.m_eWidthType;
aDFA.mbOrientation = true;
aDFA.mbDevice = true;
aDFA.mnQuality = 50000;
aDFA.mbSubsettable = false;
aDFA.mbEmbeddable = false;
return aDFA;
}
ImplPdfBuiltinFontData::ImplPdfBuiltinFontData( const PDFWriterImpl::BuiltinFont& rBuiltin )
: ImplFontData( GetDevFontAttributes(rBuiltin), PDF_FONT_MAGIC ),
mrBuiltin( rBuiltin )
{}
ImplFontEntry* ImplPdfBuiltinFontData::CreateFontInstance( ImplFontSelectData& rFSD ) const
{
ImplFontEntry* pEntry = new ImplFontEntry( rFSD );
return pEntry;
}
ImplDevFontList* PDFWriterImpl::filterDevFontList( ImplDevFontList* pFontList )
{
DBG_ASSERT( m_aSubsets.size() == 0, "Fonts changing during PDF generation, document will be invalid" );
ImplDevFontList* pFiltered = pFontList->Clone( true, true );
// append the PDF builtin fonts
for( unsigned int i = 0; i < sizeof(m_aBuiltinFonts)/sizeof(m_aBuiltinFonts[0]); i++ )
{
ImplFontData* pNewData = new ImplPdfBuiltinFontData( m_aBuiltinFonts[i] );
pFiltered->Add( pNewData );
}
return pFiltered;
}
bool PDFWriterImpl::isBuiltinFont( ImplFontData* pFont ) const
{
const ImplPdfBuiltinFontData* pFD = GetPdfFontData( pFont );
return (pFD != NULL);
}
void PDFWriterImpl::getFontMetric( ImplFontSelectData* pSelect, ImplFontMetricData* pMetric ) const
{
const ImplPdfBuiltinFontData* pFD = GetPdfFontData( pSelect->mpFontData );
if( !pFD )
return;
const BuiltinFont* pBuiltinFont = pFD->GetBuiltinFont();
pMetric->mnOrientation = sal::static_int_cast<short>(pSelect->mnOrientation);
pMetric->meFamily = pBuiltinFont->m_eFamily;
pMetric->mePitch = pBuiltinFont->m_ePitch;
pMetric->meWeight = pBuiltinFont->m_eWeight;
pMetric->meItalic = pBuiltinFont->m_eItalic;
pMetric->mbSymbolFlag = pFD->IsSymbolFont();
pMetric->mnWidth = pSelect->mnHeight;
pMetric->mnAscent = ( pSelect->mnHeight * +pBuiltinFont->m_nAscent + 500 ) / 1000;
pMetric->mnDescent = ( pSelect->mnHeight * -pBuiltinFont->m_nDescent + 500 ) / 1000;
pMetric->mnIntLeading = 0;
pMetric->mnExtLeading = 0;
pMetric->mnSlant = 0;
pMetric->mbScalableFont = true;
pMetric->mbDevice = true;
}
// -----------------------------------------------------------------------
namespace vcl {
class PDFSalLayout : public GenericSalLayout
{
PDFWriterImpl& mrPDFWriterImpl;
const PDFWriterImpl::BuiltinFont& mrBuiltinFont;
bool mbIsSymbolFont;
long mnPixelPerEM;
String maOrigText;
public:
PDFSalLayout( PDFWriterImpl&,
const PDFWriterImpl::BuiltinFont&,
long nPixelPerEM, int nOrientation );
void SetText( const String& rText ) { maOrigText = rText; }
virtual bool LayoutText( ImplLayoutArgs& );
virtual void InitFont() const;
virtual void DrawText( SalGraphics& ) const;
};
}
// -----------------------------------------------------------------------
PDFSalLayout::PDFSalLayout( PDFWriterImpl& rPDFWriterImpl,
const PDFWriterImpl::BuiltinFont& rBuiltinFont,
long nPixelPerEM, int nOrientation )
: mrPDFWriterImpl( rPDFWriterImpl ),
mrBuiltinFont( rBuiltinFont ),
mnPixelPerEM( nPixelPerEM )
{
mbIsSymbolFont = (rBuiltinFont.m_eCharSet == RTL_TEXTENCODING_SYMBOL);
SetOrientation( nOrientation );
}
// -----------------------------------------------------------------------
bool PDFSalLayout::LayoutText( ImplLayoutArgs& rArgs )
{
const String aText( rArgs.mpStr+rArgs.mnMinCharPos, sal::static_int_cast<xub_StrLen>(rArgs.mnEndCharPos-rArgs.mnMinCharPos) );
SetText( aText );
SetUnitsPerPixel( 1000 );
rtl_UnicodeToTextConverter aConv = rtl_createTextToUnicodeConverter( RTL_TEXTENCODING_MS_1252 );
Point aNewPos( 0, 0 );
bool bRightToLeft;
for( int nCharPos = -1; rArgs.GetNextPos( &nCharPos, &bRightToLeft ); )
{
sal_Unicode cChar = rArgs.mpStr[ nCharPos ];
if( bRightToLeft )
cChar = GetMirroredChar( cChar );
if( cChar & 0xff00 )
{
// some characters can be used by conversion
if( (cChar >= 0xf000) && mbIsSymbolFont )
cChar -= 0xf000;
else
{
sal_Char aBuf[4];
sal_uInt32 nInfo;
sal_Size nSrcCvtChars;
sal_Size nConv = rtl_convertUnicodeToText( aConv,
NULL,
&cChar, 1,
aBuf, 1,
RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR,
&nInfo, &nSrcCvtChars );
// check whether conversion was possible
// else fallback font is needed as the standard fonts
// are handled via WinAnsi encoding
if( nConv > 0 )
cChar = ((sal_Unicode)aBuf[0]) & 0x00ff;
}
}
if( cChar & 0xff00 )
{
cChar = 0; // NotDef glyph
rArgs.NeedFallback( nCharPos, bRightToLeft );
}
long nGlyphWidth = (long)mrBuiltinFont.m_aWidths[cChar] * mnPixelPerEM;
long nGlyphFlags = (nGlyphWidth > 0) ? 0 : GlyphItem::IS_IN_CLUSTER;
if( bRightToLeft )
nGlyphFlags |= GlyphItem::IS_RTL_GLYPH;
// TODO: get kerning from builtin fonts
GlyphItem aGI( nCharPos, cChar, aNewPos, nGlyphFlags, nGlyphWidth );
AppendGlyph( aGI );
aNewPos.X() += nGlyphWidth;
}
rtl_destroyUnicodeToTextConverter( aConv );
return true;
}
// -----------------------------------------------------------------------
void PDFSalLayout::InitFont() const
{
// TODO: recreate font with all its attributes
}
// -----------------------------------------------------------------------
void PDFSalLayout::DrawText( SalGraphics& ) const
{
mrPDFWriterImpl.drawLayout( *const_cast<PDFSalLayout*>(this), maOrigText, true );
}
// -----------------------------------------------------------------------
SalLayout* PDFWriterImpl::GetTextLayout( ImplLayoutArgs& rArgs, ImplFontSelectData* pSelect )
{
DBG_ASSERT( (pSelect->mpFontData != NULL),
"PDFWriterImpl::GetTextLayout mpFontData is NULL" );
const ImplPdfBuiltinFontData* pFD = GetPdfFontData( pSelect->mpFontData );
if( !pFD )
return NULL;
const BuiltinFont* pBuiltinFont = pFD->GetBuiltinFont();
long nPixelPerEM = pSelect->mnWidth ? pSelect->mnWidth : pSelect->mnHeight;
int nOrientation = pSelect->mnOrientation;
PDFSalLayout* pLayout = new PDFSalLayout( *this, *pBuiltinFont, nPixelPerEM, nOrientation );
pLayout->SetText( rArgs.mpStr );
return pLayout;
}
sal_Int32 PDFWriterImpl::newPage( sal_Int32 nPageWidth, sal_Int32 nPageHeight, PDFWriter::Orientation eOrientation )
{
if( m_aContext.Encrypt && m_aPages.empty() )
initEncryption();
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 0 (as thin as possible) by default
writeBuffer( "0 w\n", 4 );
return m_nCurrentPage;
}
void PDFWriterImpl::endPage()
{
if( m_aPages.begin() != m_aPages.end() )
{
// close eventual MC sequence
endStructureElementMCSeq();
// sanity check
if( m_aOutputStreams.begin() != m_aOutputStreams.end() )
{
DBG_ERROR( "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();
if( m_pCodec )
endCompression();
m_aPages.back().endStream();
// reset the default font
Font aFont;
aFont.SetName( String( RTL_CONSTASCII_USTRINGPARAM( "Times" ) ) );
aFont.SetSize( 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 );
delete jpeg->m_pStream;
jpeg->m_pStream = NULL;
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 = NULL;
}
}
}
}
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;
oslFileError aError = osl_getFilePos( m_aFile, &nOffset );
DBG_ASSERT( aError == osl_File_E_None, "could not register object" );
if( aError != osl_File_E_None )
{
osl_closeFile( m_aFile );
m_bOpen = false;
}
m_aObjects[ n-1 ] = nOffset;
return aError == osl_File_E_None;
}
#define CHECK_RETURN( x ) if( !(x) ) return 0
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() )
fprintf( stderr, "invalid PDFWriter::StructAttribute %d\n", 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() )
fprintf( stderr, "invalid PDFWriter::StructAttributeValue %d\n", eVal );
#endif
return it != aValueStrings.end() ? it->second : "";
}
static void appendStructureAttributeLine( PDFWriter::StructAttribute eAttr, const PDFWriterImpl::PDFStructureAttribute& rVal, OStringBuffer& rLine )
{
rLine.append( "/" );
rLine.append( PDFWriterImpl::getAttributeTag( eAttr ) );
if( rVal.eValue != PDFWriter::Invalid )
{
rLine.append( "/" );
rLine.append( PDFWriterImpl::getAttributeValueTag( rVal.eValue ) );
}
else
{
// numerical value
rLine.append( " " );
appendFixedInt( rVal.nValue, rLine );
}
rLine.append( "\n" );
}
OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& rEle )
{
// create layout, list and table attribute sets
OStringBuffer aLayout(256), aList(64), aTable(64);
for( PDFStructAttributes::const_iterator it = rEle.m_aAttributes.begin();
it != rEle.m_aAttributes.end(); ++it )
{
if( it->first == PDFWriter::ListNumbering )
appendStructureAttributeLine( it->first, it->second, aList );
else if( it->first == PDFWriter::RowSpan ||
it->first == PDFWriter::ColSpan )
appendStructureAttributeLine( it->first, it->second, aTable );
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( 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"
);
updateObject( nRefObject );
writeBuffer( aRef.getStr(), aRef.getLength() );
rEle.m_aKids.push_back( PDFStructureElementKid( nRefObject ) );
}
else
{
DBG_ERROR( "unresolved link id for Link structure" );
#if OSL_DEBUG_LEVEL > 1
fprintf( stderr, "unresolved link id %ld for Link structure\n", nLink );
{
OStringBuffer aLine( "unresolved link id " );
aLine.append( nLink );
aLine.append( " for Link structure" );
emitComment( aLine.getStr() );
}
#endif
}
}
else
appendStructureAttributeLine( it->first, it->second, aLayout );
}
if( ! rEle.m_aBBox.IsEmpty() )
{
aLayout.append( "/BBox[" );
appendFixedInt( rEle.m_aBBox.Left(), aLayout );
aLayout.append( " " );
appendFixedInt( rEle.m_aBBox.Top(), aLayout );
aLayout.append( " " );
appendFixedInt( rEle.m_aBBox.Right(), aLayout );
aLayout.append( " " );
appendFixedInt( rEle.m_aBBox.Bottom(), aLayout );
aLayout.append( "]\n" );
}
std::vector< sal_Int32 > aAttribObjects;
if( aLayout.getLength() )
{
aAttribObjects.push_back( createObject() );
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.getLength() )
{
aAttribObjects.push_back( createObject() );
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.getLength() )
{
aAttribObjects.push_back( createObject() );
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() );
writeBuffer( aTable.getStr(), aTable.getLength() );
}
OStringBuffer aRet( 64 );
if( aAttribObjects.size() > 1 )
aRet.append( " [" );
for( std::vector< sal_Int32 >::const_iterator at_it = aAttribObjects.begin();
at_it != aAttribObjects.end(); ++at_it )
{
aRet.append( " " );
aRet.append( *at_it );
aRet.append( " 0 R" );
}
if( aAttribObjects.size() > 1 )
aRet.append( " ]" );
return aRet.makeStringAndClear();
}
sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle )
{
if(
// do not emit NonStruct and its children
rEle.m_eType == PDFWriter::NonStructElement &&
rEle.m_nOwnElement != rEle.m_nParentElement // but of course emit the struct tree root
)
return 0;
for( std::list< sal_Int32 >::const_iterator it = rEle.m_aChildren.begin(); it != rEle.m_aChildren.end(); ++it )
{
if( *it > 0 && *it < sal_Int32(m_aStructure.size()) )
{
PDFStructureElement& rChild = m_aStructure[ *it ];
if( rChild.m_eType != PDFWriter::NonStructElement )
{
if( rChild.m_nParentElement == rEle.m_nOwnElement )
emitStructure( rChild );
else
{
DBG_ERROR( "PDFWriterImpl::emitStructure: invalid child structure element" );
#if OSL_DEBUG_LEVEL > 1
fprintf( stderr, "PDFWriterImpl::emitStructure: invalid child structure elemnt with id %ld\n", *it );
#endif
}
}
}
else
{
DBG_ERROR( "PDFWriterImpl::emitStructure: invalid child structure id" );
#if OSL_DEBUG_LEVEL > 1
fprintf( stderr, "PDFWriterImpl::emitStructure: invalid child structure id %ld\n", *it );
#endif
}
}
OStringBuffer aLine( 512 );
aLine.append( rEle.m_nObject );
aLine.append( " 0 obj\n"
"<</Type" );
sal_Int32 nParentTree = -1;
if( rEle.m_nOwnElement == rEle.m_nParentElement )
{
nParentTree = createObject();
CHECK_RETURN( nParentTree );
aLine.append( "/StructTreeRoot\n" );
aLine.append( "/ParentTree " );
aLine.append( nParentTree );
aLine.append( " 0 R\n" );
}
else
{
aLine.append( "/StructElem\n"
"/S/" );
aLine.append( getStructureTag( rEle.m_eType ) );
aLine.append( "\n"
"/P " );
aLine.append( m_aStructure[ rEle.m_nParentElement ].m_nObject );
aLine.append( " 0 R\n"
"/Pg " );
aLine.append( rEle.m_nFirstPageObject );
aLine.append( " 0 R\n" );
if( rEle.m_aActualText.getLength() )
{
aLine.append( "/ActualText" );
appendUnicodeTextStringEncrypt( rEle.m_aActualText, rEle.m_nObject, aLine );
aLine.append( "\n" );
}
if( rEle.m_aAltText.getLength() )
{
aLine.append( "/Alt" );
appendUnicodeTextStringEncrypt( rEle.m_aAltText, rEle.m_nObject, aLine );
aLine.append( "\n" );
}
}
if( ! rEle.m_aBBox.IsEmpty() || rEle.m_aAttributes.size() )
{
OString aAttribs = emitStructureAttributes( rEle );
if( aAttribs.getLength() )
{
aLine.append( "/A" );
aLine.append( aAttribs );
aLine.append( "\n" );
}
}
if( ! rEle.m_aKids.empty() )
{
unsigned int i = 0;
aLine.append( "/K[" );
for( std::list< PDFStructureElementKid >::const_iterator it =
rEle.m_aKids.begin(); it != rEle.m_aKids.end(); ++it, i++ )
{
if( it->nMCID == -1 )
{
aLine.append( it->nObject );
aLine.append( " 0 R" );
aLine.append( ( (i & 15) == 15 ) ? "\n" : " " );
}
else
{
if( it->nObject == rEle.m_nFirstPageObject )
{
aLine.append( it->nMCID );
aLine.append( " " );
}
else
{
aLine.append( "<</Type/MCR/Pg " );
aLine.append( it->nObject );
aLine.append( " 0 R /MCID " );
aLine.append( it->nMCID );
aLine.append( ">>\n" );
}
}
}
aLine.append( "]\n" );
}
aLine.append( ">>\nendobj\n\n" );
CHECK_RETURN( updateObject( rEle.m_nObject ) );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
CHECK_RETURN( emitStructParentTree( nParentTree ) );
return rEle.m_nObject;
}
bool PDFWriterImpl::emitGradients()
{
for( std::list<GradientEmit>::iterator it = m_aGradients.begin();
it != m_aGradients.end(); ++it )
{
CHECK_RETURN( writeGradientFunction( *it ) );
}
return true;
}
bool PDFWriterImpl::emitTilings()
{
OStringBuffer aTilingStream( 1024 );
OStringBuffer aTilingObj( 1024 );
for( std::list<BitmapPatternEmit>::const_iterator it = m_aTilings.begin(); it != m_aTilings.end(); ++it )
{
aTilingStream.setLength( 0 );
aTilingObj.setLength( 0 );
#if OSL_DEBUG_LEVEL > 1
{
OStringBuffer aLine( "PDFWriterImpl::emitTilings" );
emitComment( aLine.getStr() );
}
#endif
sal_Int32 nX = (sal_Int32)it->m_aRectangle.Left();
sal_Int32 nY = (sal_Int32)it->m_aRectangle.Bottom();
sal_Int32 nW = (sal_Int32)it->m_aRectangle.GetWidth();
sal_Int32 nH = (sal_Int32)it->m_aRectangle.GetHeight();
appendFixedInt( nW, aTilingStream );
aTilingStream.append( " 0 0 " );
appendFixedInt( nH, aTilingStream );
aTilingStream.append( ' ' );
appendFixedInt( nX, aTilingStream );
aTilingStream.append( ' ' );
appendFixedInt( nY, aTilingStream );
aTilingStream.append( " cm\n/Im" );
aTilingStream.append( it->m_nBitmapObject );
aTilingStream.append( " Do\n" );
// write pattern object
aTilingObj.append( it->m_nObject );
aTilingObj.append( " 0 obj\n" );
aTilingObj.append( "<</Type/Pattern/PatternType 1\n"
"/PaintType 1\n"
"/TilingType 1\n"
"/BBox[" );
appendFixedInt( nX, aTilingObj );
aTilingObj.append( ' ' );
appendFixedInt( nY, aTilingObj );
aTilingObj.append( ' ' );
appendFixedInt( nX+nW, aTilingObj );
aTilingObj.append( ' ' );
appendFixedInt( nY+nH, aTilingObj );
aTilingObj.append( "]\n"
"/XStep " );
appendDouble( pixelToPoint(nW), aTilingObj, 1 );
aTilingObj.append( "\n"
"/YStep " );
appendDouble( pixelToPoint(nH), aTilingObj, 1 );
aTilingObj.append( "\n"
"/Resources<<\n"
"/XObject<</Im" );
aTilingObj.append( it->m_nBitmapObject );
aTilingObj.append( ' ' );
aTilingObj.append( it->m_nBitmapObject );
aTilingObj.append( " 0 R>>>>\n"
"/Length " );
aTilingObj.append( (sal_Int32)aTilingStream.getLength() );
aTilingObj.append( ">>\nstream\n" );
CHECK_RETURN( updateObject( it->m_nObject ) );
CHECK_RETURN( writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) );
checkAndEnableStreamEncryption( it->m_nObject );
CHECK_RETURN( writeBuffer( aTilingStream.getStr(), aTilingStream.getLength() ) );
disableStreamEncryption();
aTilingObj.setLength( 0 );
aTilingObj.append( "\nendstream\nendobj\n\n" );
CHECK_RETURN( writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) );
}
return true;
}
sal_Int32 PDFWriterImpl::emitBuiltinFont( ImplFontData* pFont )
{
const ImplPdfBuiltinFontData* pFD = GetPdfFontData( pFont );
if( !pFD )
return 0;
const BuiltinFont* pBuiltinFont = pFD->GetBuiltinFont();
OStringBuffer aLine( 1024 );
sal_Int32 nFontObject = createObject();
CHECK_RETURN( updateObject( nFontObject ) );
aLine.append( nFontObject );
aLine.append( " 0 obj\n"
"<</Type/Font/Subtype/Type1/BaseFont/" );
appendName( pBuiltinFont->m_pPSName, aLine );
aLine.append( "\n" );
if( pBuiltinFont->m_eCharSet != RTL_TEXTENCODING_SYMBOL )
aLine.append( "/Encoding/WinAnsiEncoding\n" );
aLine.append( ">>\nendobj\n\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
return nFontObject;
}
std::map< sal_Int32, sal_Int32 > PDFWriterImpl::emitEmbeddedFont( ImplFontData* pFont, EmbedFont& rEmbed )
{
std::map< sal_Int32, sal_Int32 > aRet;
if( isBuiltinFont( pFont ) )
{
aRet[ rEmbed.m_nNormalFontID ] = emitBuiltinFont( pFont );
return aRet;
}
sal_Int32 nFontObject = 0;
sal_Int32 nStreamObject = 0;
sal_Int32 nFontDescriptor = 0;
// prepare font encoding
const std::map< sal_Unicode, sal_Int32 >* pEncoding = m_pReferenceDevice->mpGraphics->GetFontEncodingVector( pFont, NULL );
sal_Int32 nToUnicodeStream = 0;
sal_uInt8 nEncoding[256];
sal_Unicode nEncodedCodes[256];
if( pEncoding )
{
memset( nEncodedCodes, 0, sizeof(nEncodedCodes) );
memset( nEncoding, 0, sizeof(nEncoding) );
for( std::map< sal_Unicode, sal_Int32 >::const_iterator it = pEncoding->begin(); it != pEncoding->end(); ++it )
{
if( it->second != -1 )
{
sal_Int32 nCode = (sal_Int32)(it->second & 0x000000ff);
nEncoding[ nCode ] = static_cast<sal_uInt8>( nCode );
nEncodedCodes[ nCode ] = it->first;
}
}
}
FontSubsetInfo aInfo;
sal_Int32 pWidths[256];
const unsigned char* pFontData = NULL;
long nFontLen = 0;
sal_Int32 nLength1, nLength2;
if( (pFontData = (const unsigned char*)m_pReferenceDevice->mpGraphics->GetEmbedFontData( pFont, nEncodedCodes, pWidths, aInfo, &nFontLen )) != NULL )
{
if( aInfo.m_nFontType != SAL_FONTSUBSETINFO_TYPE_TYPE1 )
goto streamend;
// see whether it is pfb or pfa; if it is a pfb, fill ranges
// of 6 bytes that are not part of the font program
std::list< int > aSections;
std::list< int >::const_iterator it;
int nIndex = 0;
while( pFontData[nIndex] == 0x80 )
{
aSections.push_back( nIndex );
if( pFontData[nIndex+1] == 0x03 )
break;
sal_Int32 nBytes =
((sal_Int32)pFontData[nIndex+2]) |
((sal_Int32)pFontData[nIndex+3]) << 8 |
((sal_Int32)pFontData[nIndex+4]) << 16 |
((sal_Int32)pFontData[nIndex+5]) << 24;
nIndex += nBytes+6;
}
// search for eexec
nIndex = 0;
int nEndAsciiIndex;
int nBeginBinaryIndex;
int nEndBinaryIndex;
do
{
while( nIndex < nFontLen-4 &&
( pFontData[nIndex] != 'e' ||
pFontData[nIndex+1] != 'e' ||
pFontData[nIndex+2] != 'x' ||
pFontData[nIndex+3] != 'e' ||
pFontData[nIndex+4] != 'c'
)
)
nIndex++;
// check whether we are in a excluded section
for( it = aSections.begin(); it != aSections.end() && (nIndex < *it || nIndex > ((*it) + 5) ); ++it )
;
} while( it != aSections.end() && nIndex < nFontLen-4 );
// this should end the ascii part
if( nIndex > nFontLen-5 )
goto streamend;
nEndAsciiIndex = nIndex+4;
// now count backwards until we can account for 512 '0'
// which is the endmarker of the (hopefully) binary data
// do not count the pfb header sections
int nFound = 0;
nIndex = nFontLen-1;
while( nIndex > 0 && nFound < 512 )
{
for( it = aSections.begin(); it != aSections.end() && (nIndex < *it || nIndex > ((*it) + 5) ); ++it )
;
if( it == aSections.end() )
{
// inside the 512 '0' block there may only be whitespace
// according to T1 spec; probably it would be to simple
// if all fonts complied
if( pFontData[nIndex] == '0' )
nFound++;
else if( nFound > 0 &&
pFontData[nIndex] != '\r' &&
pFontData[nIndex] != '\t' &&
pFontData[nIndex] != '\n' &&
pFontData[nIndex] != ' ' )
break;
}
nIndex--;
}
if( nIndex < 1 || nIndex <= nEndAsciiIndex )
goto streamend;
// there may be whitespace to ignore before the 512 '0'
while( pFontData[nIndex] == '\r' || pFontData[nIndex] == '\n' )
{
nIndex--;
for( it = aSections.begin(); it != aSections.end() && (nIndex < *it || nIndex > ((*it) + 5) ); ++it )
;
if( it != aSections.end() )
{
nIndex = (*it)-1;
break; // this is surely a binary boundary, in ascii case it wouldn't matter
}
}
nEndBinaryIndex = nIndex;
// search for beginning of binary section
nBeginBinaryIndex = nEndAsciiIndex;
do
{
nBeginBinaryIndex++;
for( it = aSections.begin(); it != aSections.end() && (nBeginBinaryIndex < *it || nBeginBinaryIndex > ((*it) + 5) ); ++it )
;
} while( nBeginBinaryIndex < nEndBinaryIndex &&
( pFontData[nBeginBinaryIndex] == '\r' ||
pFontData[nBeginBinaryIndex] == '\n' ||
it != aSections.end() ) );
// it seems to be vital to copy the exact whitespace between binary data
// and eexec, else a invalid font results. so make nEndAsciiIndex
// always immediate in front of nBeginBinaryIndex
nEndAsciiIndex = nBeginBinaryIndex-1;
for( it = aSections.begin(); it != aSections.end() && (nEndAsciiIndex < *it || nEndAsciiIndex > ((*it)+5)); ++it )
;
if( it != aSections.end() )
nEndAsciiIndex = (*it)-1;
nLength1 = nEndAsciiIndex+1; // including the last character
for( it = aSections.begin(); it != aSections.end() && *it < nEndAsciiIndex; ++it )
nLength1 -= 6; // decrease by pfb section size
// if the first four bytes are all ascii hex characters, then binary data
// has to be converted to real binary data
for( nIndex = 0; nIndex < 4 &&
( ( pFontData[ nBeginBinaryIndex+nIndex ] >= '0' && pFontData[ nBeginBinaryIndex+nIndex ] <= '9' ) ||
( pFontData[ nBeginBinaryIndex+nIndex ] >= 'a' && pFontData[ nBeginBinaryIndex+nIndex ] <= 'f' ) ||
( pFontData[ nBeginBinaryIndex+nIndex ] >= 'A' && pFontData[ nBeginBinaryIndex+nIndex ] <= 'F' )
); ++nIndex )
;
bool bConvertHexData = true;
if( nIndex < 4 )
{
bConvertHexData = false;
nLength2 = nEndBinaryIndex - nBeginBinaryIndex + 1; // include the last byte
for( it = aSections.begin(); it != aSections.end(); ++it )
if( *it > nBeginBinaryIndex && *it < nEndBinaryIndex )
nLength2 -= 6;
}
else
{
// count the hex ascii characters to get nLength2
nLength2 = 0;
int nNextSectionIndex = 0;
for( it = aSections.begin(); it != aSections.end() && *it < nBeginBinaryIndex; ++it )
;
if( it != aSections.end() )
nNextSectionIndex = *it;
for( nIndex = nBeginBinaryIndex; nIndex <= nEndBinaryIndex; nIndex++ )
{
if( nIndex == nNextSectionIndex )
{
nIndex += 6;
++it;
nNextSectionIndex = (it == aSections.end() ? 0 : *it );
}
if( ( pFontData[ nIndex ] >= '0' && pFontData[ nIndex ] <= '9' ) ||
( pFontData[ nIndex ] >= 'a' && pFontData[ nIndex ] <= 'f' ) ||
( pFontData[ nIndex ] >= 'A' && pFontData[ nIndex ] <= 'F' ) )
nLength2++;
}
DBG_ASSERT( !(nLength2 & 1), "uneven number of hex chars in binary pfa section" );
nLength2 /= 2;
}
// now we can actually write the font stream !
#if OSL_DEBUG_LEVEL > 1
{
OStringBuffer aLine( " PDFWriterImpl::emitEmbeddedFont" );
emitComment( aLine.getStr() );
}
#endif
OStringBuffer aLine( 512 );
nStreamObject = createObject();
if( !updateObject(nStreamObject))
goto streamend;
sal_Int32 nStreamLengthObject = createObject();
aLine.append( nStreamObject );
aLine.append( " 0 obj\n"
"<</Length " );
aLine.append( nStreamLengthObject );
aLine.append( " 0 R"
#ifndef DEBUG_DISABLE_PDFCOMPRESSION
"/Filter/FlateDecode"
#endif
"/Length1 " );
aLine.append( nLength1 );
aLine.append( " /Length2 " );
aLine.append( nLength2 );
aLine.append( " /Length3 0>>\n"
"stream\n" );
if( !writeBuffer( aLine.getStr(), aLine.getLength() ) )
goto streamend;
sal_uInt64 nBeginStreamPos = 0;
osl_getFilePos( m_aFile, &nBeginStreamPos );
beginCompression();
checkAndEnableStreamEncryption( nStreamObject );
// write ascii section
if( aSections.begin() == aSections.end() )
{
if( ! writeBuffer( pFontData, nEndAsciiIndex+1 ) )
{
endCompression();
disableStreamEncryption();
goto streamend;
}
}
else
{
// first section always starts at 0
it = aSections.begin();
nIndex = (*it)+6;
++it;
while( *it < nEndAsciiIndex )
{
if( ! writeBuffer( pFontData+nIndex, (*it)-nIndex ) )
{
endCompression();
disableStreamEncryption();
goto streamend;
}
nIndex = (*it)+6;
++it;
}
// write partial last section
if( ! writeBuffer( pFontData+nIndex, nEndAsciiIndex-nIndex+1 ) )
{
endCompression();
disableStreamEncryption();
goto streamend;
}
}
// write binary section
if( ! bConvertHexData )
{
if( aSections.begin() == aSections.end() )
{
if( ! writeBuffer( pFontData+nBeginBinaryIndex, nEndBinaryIndex-nBeginBinaryIndex+1 ) )
{
endCompression();
disableStreamEncryption();
goto streamend;
}
}
else
{
for( it = aSections.begin(); *it < nBeginBinaryIndex; ++it )
;
if( *it > nEndBinaryIndex )
{
if( ! writeBuffer( pFontData+nBeginBinaryIndex, nEndBinaryIndex-nBeginBinaryIndex+1 ) )
{
endCompression();
disableStreamEncryption();
goto streamend;
}
}
else
{
// write first partial section
if( ! writeBuffer( pFontData+nBeginBinaryIndex, (*it) - nBeginBinaryIndex ) )
{
endCompression();
disableStreamEncryption();
goto streamend;
}
nIndex = (*it)+6;
++it;
while( *it < nEndBinaryIndex )
{
if( ! writeBuffer( pFontData+nIndex, (*it)-nIndex ) )
goto streamend;
nIndex = (*it)+6;
++it;
}
// write partial last section
if( ! writeBuffer( pFontData+nIndex, nEndBinaryIndex-nIndex+1 ) )
{
endCompression();
disableStreamEncryption();
goto streamend;
}
}
}
}
else
{
unsigned char* pWriteBuffer = (unsigned char*)rtl_allocateMemory( nLength2 );
memset( pWriteBuffer, 0, nLength2 );
int nWriteIndex = 0;
int nNextSectionIndex = 0;
for( it = aSections.begin(); it != aSections.end() && *it < nBeginBinaryIndex; ++it )
;
if( it != aSections.end() )
nNextSectionIndex = *it;
for( nIndex = nBeginBinaryIndex; nIndex <= nEndBinaryIndex; nIndex++ )
{
if( nIndex == nNextSectionIndex )
{
nIndex += 6;
++it;
nNextSectionIndex = (it == aSections.end() ? 0 : *it );
}
unsigned char cNibble = 0x80;
if( pFontData[ nIndex ] >= '0' && pFontData[ nIndex ] <= '9' )
cNibble = pFontData[nIndex] - '0';
else if( pFontData[ nIndex ] >= 'a' && pFontData[ nIndex ] <= 'f' )
cNibble = pFontData[nIndex] - 'a' + 10;
else if( pFontData[ nIndex ] >= 'A' && pFontData[ nIndex ] <= 'F' )
cNibble = pFontData[nIndex] - 'A' + 10;
if( cNibble != 0x80 )
{
if( !(nWriteIndex & 1 ) )
cNibble <<= 4;
pWriteBuffer[ nWriteIndex/2 ] |= cNibble;
nWriteIndex++;
}
}
if( ! writeBuffer( pWriteBuffer, nLength2 ) )
{
endCompression();
disableStreamEncryption();
goto streamend;
}
rtl_freeMemory( pWriteBuffer );
}
endCompression();
disableStreamEncryption();
sal_uInt64 nEndStreamPos = 0;
osl_getFilePos( m_aFile, &nEndStreamPos );
// and finally close the stream
aLine.setLength( 0 );
aLine.append( "\nendstream\nendobj\n\n" );
if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
goto streamend;
// write stream length object
aLine.setLength( 0 );
if( ! updateObject( nStreamLengthObject ) )
goto streamend;
aLine.append( nStreamLengthObject );
aLine.append( " 0 obj\n" );
aLine.append( (sal_Int64)(nEndStreamPos-nBeginStreamPos ) );
aLine.append( "\nendobj\n\n" );
if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
goto streamend;
}
if( nStreamObject )
// write font descriptor
nFontDescriptor = emitFontDescriptor( pFont, aInfo, 0, nStreamObject );
if( nFontDescriptor )
{
if( pEncoding )
nToUnicodeStream = createToUnicodeCMap( nEncoding, nEncodedCodes, sizeof(nEncoding)/sizeof(nEncoding[0]) );
// write font object
sal_Int32 nObject = createObject();
if( ! updateObject( nObject ) )
goto streamend;
OStringBuffer aLine( 1024 );
aLine.append( nObject );
aLine.append( " 0 obj\n"
"<</Type/Font/Subtype/Type1/BaseFont/" );
appendName( aInfo.m_aPSName, aLine );
aLine.append( "\n" );
if( !pFont->mbSymbolFlag && pEncoding == 0 )
aLine.append( "/Encoding/WinAnsiEncoding\n" );
if( nToUnicodeStream )
{
aLine.append( "/ToUnicode " );
aLine.append( nToUnicodeStream );
aLine.append( " 0 R\n" );
}
aLine.append( "/FirstChar 0 /LastChar 255\n"
"/Widths[" );
for( int i = 0; i < 256; i++ )
{
aLine.append( pWidths[i] );
aLine.append( ((i&15) == 15) ? "\n" : " " );
}
aLine.append( "]\n"
"/FontDescriptor " );
aLine.append( nFontDescriptor );
aLine.append( " 0 R>>\n"
"endobj\n\n" );
if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
goto streamend;
nFontObject = nObject;
aRet[ rEmbed.m_nNormalFontID ] = nObject;
// write additional encodings
for( std::list< EmbedEncoding >::iterator enc_it = rEmbed.m_aExtendedEncodings.begin(); enc_it != rEmbed.m_aExtendedEncodings.end(); ++enc_it )
{
sal_Int32 aEncWidths[ 256 ];
// emit encoding dict
sal_Int32 nEncObject = createObject();
if( ! updateObject( nEncObject ) )
goto streamend;
OutputDevice* pRef = getReferenceDevice();
pRef->Push( PUSH_FONT | PUSH_MAPMODE );
pRef->SetMapMode( MapMode( MAP_PIXEL ) );
Font aFont( pFont->GetFamilyName(), pFont->GetStyleName(), Size( 0, 1000 ) );
aFont.SetWeight( pFont->GetWeight() );
aFont.SetItalic( pFont->GetSlant() );
aFont.SetPitch( pFont->GetPitch() );
pRef->SetFont( aFont );
pRef->ImplNewFont();
aLine.setLength( 0 );
aLine.append( nEncObject );
aLine.append( " 0 obj\n"
"<</Type/Encoding/Differences[ 0\n" );
int nEncoded = 0;
for( std::vector< EmbedCode >::iterator str_it = enc_it->m_aEncVector.begin(); str_it != enc_it->m_aEncVector.end(); ++str_it )
{
String aStr( str_it->m_aUnicode );
aEncWidths[nEncoded] = pRef->GetTextWidth( aStr );
nEncodedCodes[nEncoded] = str_it->m_aUnicode;
nEncoding[nEncoded] = sal::static_int_cast<sal_uInt8>(nEncoded);
aLine.append( " /" );
aLine.append( str_it->m_aName );
if( !((++nEncoded) & 15) )
aLine.append( "\n" );
}
aLine.append( "]>>\n"
"endobj\n\n" );
pRef->Pop();
if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
goto streamend;
nToUnicodeStream = createToUnicodeCMap( nEncoding, nEncodedCodes, nEncoded );
nObject = createObject();
if( ! updateObject( nObject ) )
goto streamend;
aLine.setLength( 0 );
aLine.append( nObject );
aLine.append( " 0 obj\n"
"<</Type/Font/Subtype/Type1/BaseFont/" );
appendName( aInfo.m_aPSName, aLine );
aLine.append( "\n" );
aLine.append( "/Encoding " );
aLine.append( nEncObject );
aLine.append( " 0 R\n" );
if( nToUnicodeStream )
{
aLine.append( "/ToUnicode " );
aLine.append( nToUnicodeStream );
aLine.append( " 0 R\n" );
}
aLine.append( "/FirstChar 0\n"
"/LastChar " );
aLine.append( (sal_Int32)(nEncoded-1) );
aLine.append( "\n"
"/Widths[" );
for( int i = 0; i < nEncoded; i++ )
{
aLine.append( aEncWidths[i] );
aLine.append( ((i&15) == 15) ? "\n" : " " );
}
aLine.append( " ]\n"
"/FontDescriptor " );
aLine.append( nFontDescriptor );
aLine.append( " 0 R>>\n"
"endobj\n\n" );
if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
goto streamend;
aRet[ enc_it->m_nFontID ] = nObject;
}
}
streamend:
if( pFontData )
m_pReferenceDevice->mpGraphics->FreeEmbedFontData( pFontData, nFontLen );
return aRet;
}
static void appendSubsetName( int nSubsetID, const OUString& rPSName, OStringBuffer& rBuffer )
{
if( nSubsetID )
{
for( int i = 0; i < 6; i++ )
{
int nOffset = (nSubsetID % 26);
nSubsetID /= 26;
rBuffer.append( (sal_Char)('A'+nOffset) );
}
rBuffer.append( '+' );
}
appendName( rPSName, rBuffer );
}
sal_Int32 PDFWriterImpl::createToUnicodeCMap( sal_uInt8* pEncoding, sal_Unicode* pUnicodes, int nGlyphs )
{
int nMapped = 0, n = 0;
for( n = 0; n < nGlyphs; n++ )
if( pUnicodes[n] )
nMapped++;
if( nMapped == 0 )
return 0;
sal_Int32 nStream = createObject();
CHECK_RETURN( updateObject( nStream ) );
OStringBuffer aContents( 1024 );
aContents.append(
"/CIDInit/ProcSet findresource begin\n"
"12 dict begin\n"
"begincmap\n"
"/CIDSystemInfo<<\n"
"/Registry (Adobe)\n"
"/Ordering (UCS)\n"
"/Supplement 0\n"
">> def\n"
"/CMapName/Adobe-Identity-UCS def\n"
"/CMapType 2 def\n"
"1 begincodespacerange\n"
"<00> <FF>\n"
"endcodespacerange\n"
);
int nCount = 0;
for( n = 0; n < nGlyphs; n++ )
{
if( pUnicodes[n] )
{
if( (nCount % 100) == 0 )
{
if( nCount )
aContents.append( "endbfchar\n" );
aContents.append( (sal_Int32)((nMapped-nCount > 100) ? 100 : nMapped-nCount ) );
aContents.append( " beginbfchar\n" );
}
aContents.append( '<' );
appendHex( (sal_Int8)pEncoding[n], aContents );
aContents.append( "> <" );
appendHex( (sal_Int8)(pUnicodes[n] / 256), aContents );
appendHex( (sal_Int8)(pUnicodes[n] & 255), aContents );
aContents.append( ">\n" );
nCount++;
}
}
aContents.append( "endbfchar\n"
"endcmap\n"
"CMapName currentdict /CMap defineresource pop\n"
"end\n"
"end\n" );
#ifndef DEBUG_DISABLE_PDFCOMPRESSION
ZCodec* pCodec = new ZCodec( 0x4000, 0x4000 );
SvMemoryStream aStream;
pCodec->BeginCompression();
pCodec->Write( aStream, (const BYTE*)aContents.getStr(), aContents.getLength() );
pCodec->EndCompression();
delete pCodec;
#endif
#if OSL_DEBUG_LEVEL > 1
{
OStringBuffer aLine( " PDFWriterImpl::createToUnicodeCMap" );
emitComment( aLine.getStr() );
}
#endif
OStringBuffer aLine( 40 );
aLine.append( nStream );
aLine.append( " 0 obj\n<</Length " );
#ifndef DEBUG_DISABLE_PDFCOMPRESSION
sal_Int32 nLen = (sal_Int32)aStream.Tell();
aStream.Seek( 0 );
aLine.append( nLen );
aLine.append( "/Filter/FlateDecode" );
#else
aLine.append( aContents.getLength() );
#endif
aLine.append( ">>\nstream\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
checkAndEnableStreamEncryption( nStream );
#ifndef DEBUG_DISABLE_PDFCOMPRESSION
CHECK_RETURN( writeBuffer( aStream.GetData(), nLen ) );
#else
CHECK_RETURN( writeBuffer( aContents.getStr(), aContents.getLength() ) );
#endif
disableStreamEncryption();
aLine.setLength( 0 );
aLine.append( "\nendstream\n"
"endobj\n\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
return nStream;
}
sal_Int32 PDFWriterImpl::emitFontDescriptor( ImplFontData* pFont, FontSubsetInfo& rInfo, sal_Int32 nSubsetID, sal_Int32 nFontStream )
{
OStringBuffer aLine( 1024 );
// get font flags, see PDF reference 1.4 p. 358
// possibly characters outside Adobe standard encoding
// so set Symbolic flag
sal_Int32 nFontFlags = (1<<2);
if( pFont->GetSlant() == ITALIC_NORMAL || pFont->GetSlant() == ITALIC_OBLIQUE )
nFontFlags |= (1 << 6);
if( pFont->GetPitch() == PITCH_FIXED )
nFontFlags |= 1;
if( pFont->GetFamilyType() == FAMILY_SCRIPT )
nFontFlags |= (1 << 3);
else if( pFont->GetFamilyType() == FAMILY_ROMAN )
nFontFlags |= (1 << 1);
sal_Int32 nFontDescriptor = createObject();
CHECK_RETURN( updateObject( nFontDescriptor ) );
aLine.setLength( 0 );
aLine.append( nFontDescriptor );
aLine.append( " 0 obj\n"
"<</Type/FontDescriptor/FontName/" );
appendSubsetName( nSubsetID, rInfo.m_aPSName, aLine );
aLine.append( "\n"
"/Flags " );
aLine.append( nFontFlags );
aLine.append( "\n"
"/FontBBox[" );
// note: Top and Bottom are reversed in VCL and PDF rectangles
aLine.append( (sal_Int32)rInfo.m_aFontBBox.TopLeft().X() );
aLine.append( ' ' );
aLine.append( (sal_Int32)rInfo.m_aFontBBox.TopLeft().Y() );
aLine.append( ' ' );
aLine.append( (sal_Int32)rInfo.m_aFontBBox.BottomRight().X() );
aLine.append( ' ' );
aLine.append( (sal_Int32)(rInfo.m_aFontBBox.BottomRight().Y()+1) );
aLine.append( "]/ItalicAngle " );
if( pFont->GetSlant() == ITALIC_OBLIQUE || pFont->GetSlant() == ITALIC_NORMAL )
aLine.append( "-30" );
else
aLine.append( "0" );
aLine.append( "\n"
"/Ascent " );
aLine.append( (sal_Int32)rInfo.m_nAscent );
aLine.append( "\n"
"/Descent " );
aLine.append( (sal_Int32)-rInfo.m_nDescent );
aLine.append( "\n"
"/CapHeight " );
aLine.append( (sal_Int32)rInfo.m_nCapHeight );
// According to PDF reference 1.4 StemV is required
// seems a tad strange to me, but well ...
aLine.append( "\n"
"/StemV 80\n"
"/FontFile" );
switch( rInfo.m_nFontType )
{
case SAL_FONTSUBSETINFO_TYPE_TRUETYPE:
aLine.append( '2' );
break;
case SAL_FONTSUBSETINFO_TYPE_TYPE1:
break;
default:
DBG_ERROR( "unknown fonttype in PDF font descriptor" );
return 0;
}
aLine.append( ' ' );
aLine.append( nFontStream );
aLine.append( " 0 R>>\n"
"endobj\n\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
return nFontDescriptor;
}
sal_Int32 PDFWriterImpl::emitFonts()
{
sal_Int32 nFontDict = 0;
if( ! m_pReferenceDevice->ImplGetGraphics() )
return 0;
OStringBuffer aLine( 1024 );
char buf[8192];
std::map< sal_Int32, sal_Int32 > aFontIDToObject;
OUString aTmpName;
osl_createTempFile( NULL, NULL, &aTmpName.pData );
for( FontSubsetData::iterator it = m_aSubsets.begin(); it != m_aSubsets.end(); ++it )
{
for( FontEmitList::iterator lit = it->second.m_aSubsets.begin(); lit != it->second.m_aSubsets.end(); ++lit )
{
sal_Int32 pGlyphIDs[ 256 ];
sal_Int32 pWidths[ 256 ];
sal_uInt8 pEncoding[ 256 ];
sal_Unicode pUnicodes[ 256 ];
int nGlyphs = 1;
// fill arrays and prepare encoding index map
sal_Int32 nToUnicodeStream = 0;
memset( pGlyphIDs, 0, sizeof( pGlyphIDs ) );
memset( pEncoding, 0, sizeof( pEncoding ) );
memset( pUnicodes, 0, sizeof( pUnicodes ) );
for( FontEmitMapping::iterator fit = lit->m_aMapping.begin(); fit != lit->m_aMapping.end();++fit )
{
sal_uInt8 nEnc = fit->second.m_nSubsetGlyphID;
DBG_ASSERT( pGlyphIDs[nEnc] == 0 && pEncoding[nEnc] == 0, "duplicate glyph" );
DBG_ASSERT( nEnc <= lit->m_aMapping.size(), "invalid glyph encoding" );
pGlyphIDs[ nEnc ] = fit->first;
pEncoding[ nEnc ] = nEnc;
pUnicodes[ nEnc ] = fit->second.m_aUnicode;
if( pUnicodes[ nEnc ] )
nToUnicodeStream = 1;
if( nGlyphs < 256 )
nGlyphs++;
else
{
DBG_ERROR( "too many glyphs for subset" );
}
}
FontSubsetInfo aSubsetInfo;
if( m_pReferenceDevice->mpGraphics->CreateFontSubset( aTmpName, it->first, pGlyphIDs, pEncoding, pWidths, nGlyphs, aSubsetInfo ) )
{
DBG_ASSERT( aSubsetInfo.m_nFontType == SAL_FONTSUBSETINFO_TYPE_TRUETYPE, "wrong font type in font subset" );
// create font stream
oslFileHandle aFontFile;
CHECK_RETURN( (osl_File_E_None == osl_openFile( aTmpName.pData, &aFontFile, osl_File_OpenFlag_Read ) ) );
// get file size
sal_uInt64 nLength;
CHECK_RETURN( (osl_File_E_None == osl_setFilePos( aFontFile, osl_Pos_End, 0 ) ) );
CHECK_RETURN( (osl_File_E_None == osl_getFilePos( aFontFile, &nLength ) ) );
CHECK_RETURN( (osl_File_E_None == osl_setFilePos( aFontFile, osl_Pos_Absolut, 0 ) ) );
#if OSL_DEBUG_LEVEL > 1
{
OStringBuffer aLine1( " PDFWriterImpl::emitFonts" );
emitComment( aLine1.getStr() );
}
#endif
sal_Int32 nFontStream = createObject();
sal_Int32 nStreamLengthObject = createObject();
CHECK_RETURN( updateObject( nFontStream ) );
aLine.setLength( 0 );
aLine.append( nFontStream );
aLine.append( " 0 obj\n"
"<</Length " );
aLine.append( (sal_Int32)nStreamLengthObject );
aLine.append( " 0 R"
#ifndef DEBUG_DISABLE_PDFCOMPRESSION
"/Filter/FlateDecode"
#endif
"/Length1 " );
aLine.append( (sal_Int32)nLength );
aLine.append( ">>\n"
"stream\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
sal_uInt64 nStartPos = 0;
CHECK_RETURN( (osl_File_E_None == osl_getFilePos( m_aFile, &nStartPos ) ) );
// copy font file
beginCompression();
checkAndEnableStreamEncryption( nFontStream );
sal_uInt64 nRead;
sal_Bool bEOF = sal_False;
do
{
CHECK_RETURN( (osl_File_E_None == osl_readFile( aFontFile, buf, sizeof( buf ), &nRead ) ) );
CHECK_RETURN( writeBuffer( buf, nRead ) );
CHECK_RETURN( (osl_File_E_None == osl_isEndOfFile( aFontFile, &bEOF ) ) );
} while( ! bEOF );
endCompression();
disableStreamEncryption();
// close the file
osl_closeFile( aFontFile );
sal_uInt64 nEndPos = 0;
CHECK_RETURN( (osl_File_E_None == osl_getFilePos( m_aFile, &nEndPos ) ) );
// end the stream
aLine.setLength( 0 );
aLine.append( "\nendstream\nendobj\n\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
// emit stream length object
CHECK_RETURN( updateObject( nStreamLengthObject ) );
aLine.setLength( 0 );
aLine.append( nStreamLengthObject );
aLine.append( " 0 obj\n" );
aLine.append( (sal_Int64)(nEndPos-nStartPos) );
aLine.append( "\nendobj\n\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
// write font descriptor
sal_Int32 nFontDescriptor = emitFontDescriptor( it->first, aSubsetInfo, lit->m_nFontID, nFontStream );
if( nToUnicodeStream )
nToUnicodeStream = createToUnicodeCMap( pEncoding, pUnicodes, nGlyphs );
sal_Int32 nFontObject = createObject();
CHECK_RETURN( updateObject( nFontObject ) );
aLine.setLength( 0 );
aLine.append( nFontObject );
aLine.append( " 0 obj\n"
"<</Type/Font/Subtype/TrueType/BaseFont/" );
appendSubsetName( lit->m_nFontID, aSubsetInfo.m_aPSName, aLine );
aLine.append( "\n"
"/FirstChar 0\n"
"/LastChar " );
aLine.append( (sal_Int32)(nGlyphs-1) );
aLine.append( "\n"
"/Widths[" );
for( int i = 0; i < nGlyphs; i++ )
{
aLine.append( pWidths[ i ] );
aLine.append( ((i & 15) == 15) ? "\n" : " " );
}
aLine.append( "]\n"
"/FontDescriptor " );
aLine.append( nFontDescriptor );
aLine.append( " 0 R\n" );
if( nToUnicodeStream )
{
aLine.append( "/ToUnicode " );
aLine.append( nToUnicodeStream );
aLine.append( " 0 R\n" );
}
aLine.append( ">>\n"
"endobj\n\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
aFontIDToObject[ lit->m_nFontID ] = nFontObject;
}
}
}
osl_removeFile( aTmpName.pData );
// emit embedded fonts
for( FontEmbedData::iterator eit = m_aEmbeddedFonts.begin(); eit != m_aEmbeddedFonts.end(); ++eit )
{
std::map< sal_Int32, sal_Int32 > aObjects = emitEmbeddedFont( eit->first, eit->second );
for( std::map< sal_Int32, sal_Int32 >::iterator fit = aObjects.begin(); fit != aObjects.end(); ++fit )
{
CHECK_RETURN( fit->second );
aFontIDToObject[ fit->first ] = fit->second;
}
}
nFontDict = getFontDictObj();
aLine.setLength( 0 );
aLine.append( nFontDict );
aLine.append( " 0 obj\n"
"<<\n" );
for( std::map< sal_Int32, sal_Int32 >::iterator mit = aFontIDToObject.begin(); mit != aFontIDToObject.end(); ++mit )
{
aLine.append( "/F" );
aLine.append( mit->first );
aLine.append( ' ' );
aLine.append( mit->second );
aLine.append( " 0 R\n" );
}
// emit helvetica and ZapfDingbats font for widget apperances / variable text
if( ! m_aWidgets.empty() )
{
ImplPdfBuiltinFontData aHelvData(m_aBuiltinFonts[4]);
aLine.append( "/HelvReg " );
aLine.append( emitBuiltinFont( &aHelvData ) );
aLine.append( " 0 R\n" );
ImplPdfBuiltinFontData aZapfData(m_aBuiltinFonts[13]);
aLine.append( "/ZaDb " );
aLine.append( emitBuiltinFont( &aZapfData ) );
aLine.append( " 0 R\n" );
}
aLine.append( ">>\n"
"endobj\n\n" );
CHECK_RETURN( updateObject( nFontDict ) );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
return nFontDict;
}
sal_Int32 PDFWriterImpl::emitResources()
{
OStringBuffer aLine( 512 );
// emit shadings
sal_Int32 nShadingDict = 0;
if( m_aGradients.begin() != m_aGradients.end() )
{
CHECK_RETURN( emitGradients() );
aLine.setLength( 0 );
aLine.append( nShadingDict = createObject() );
aLine.append( " 0 obj\n"
"<<" );
for( std::list<GradientEmit>::iterator it = m_aGradients.begin();
it != m_aGradients.end(); ++it )
{
aLine.append( "/P" );
aLine.append( it->m_nObject );
aLine.append( ' ' );
aLine.append( it->m_nObject );
aLine.append( " 0 R\n" );
}
aLine.append( ">>\n"
"endobj\n\n" );
CHECK_RETURN( updateObject( nShadingDict ) );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
}
// emit patterns
sal_Int32 nPatternDict = 0;
if( m_aTilings.begin() != m_aTilings.end() )
{
CHECK_RETURN( emitTilings() );
aLine.setLength( 0 );
aLine.append( nPatternDict = createObject() );
aLine.append( " 0 obj\n<<" );
for( std::list<BitmapPatternEmit>::const_iterator tile = m_aTilings.begin();
tile != m_aTilings.end(); ++tile )
{
aLine.append( "/P" );
aLine.append( tile->m_nObject );
aLine.append( ' ' );
aLine.append( tile->m_nObject );
aLine.append( " 0 R\n" );
}
aLine.append( ">>\n"
"endobj\n\n" );
CHECK_RETURN( updateObject( nPatternDict ) );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
}
// emit font dict
sal_Int32 nFontDict = emitFonts();
// emit xobject dict
sal_Int32 nXObjectDict = 0;
std::list< sal_Int32 > aExtGStates;
if( m_aBitmaps.begin() != m_aBitmaps.end() ||
m_aJPGs.begin() != m_aJPGs.end() ||
m_aTransparentObjects.begin() != m_aTransparentObjects.end()
)
{
aLine.setLength( 0 );
nXObjectDict = createObject();
aLine.append( nXObjectDict );
aLine.append( " 0 obj\n"
"<<" );
for( std::list<BitmapEmit>::const_iterator it = m_aBitmaps.begin();
it != m_aBitmaps.end(); ++it )
{
aLine.append( "/Im" );
aLine.append( it->m_nObject );
aLine.append( ' ' );
aLine.append( it->m_nObject );
aLine.append( " 0 R\n" );
}
for( std::list<JPGEmit>::const_iterator jpeg = m_aJPGs.begin(); jpeg != m_aJPGs.end(); ++jpeg )
{
aLine.append( "/Im" );
aLine.append( jpeg->m_nObject );
aLine.append( ' ' );
aLine.append( jpeg->m_nObject );
aLine.append( " 0 R\n" );
}
for( std::list<TransparencyEmit>::const_iterator t = m_aTransparentObjects.begin();
t != m_aTransparentObjects.end(); ++t )
{
aLine.append( "/Tr" );
aLine.append( t->m_nObject );
aLine.append( ' ' );
aLine.append( t->m_nObject );
aLine.append( " 0 R\n" );
if( t->m_nExtGStateObject > 0 )
aExtGStates.push_back( t->m_nExtGStateObject );
}
aLine.append( ">>\n"
"endobj\n\n" );
CHECK_RETURN( updateObject( nXObjectDict ) );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
}
// emit ExtGStates
sal_Int32 nExtGStateObject = 0;
if( !aExtGStates.empty() )
{
nExtGStateObject = createObject();
CHECK_RETURN( updateObject( nExtGStateObject ) );
aLine.setLength( 0 );
aLine.append( nExtGStateObject );
aLine.append( " 0 obj\n"
"<<" );
int i = 0;
while( ! aExtGStates.empty() )
{
aLine.append( "/EGS" );
aLine.append( aExtGStates.front() );
aLine.append( " " );
aLine.append( aExtGStates.front() );
aLine.append( " 0 R" );
aLine.append( ((i%5) == 4) ? "\n" : " " );
aExtGStates.pop_front();
i++;
}
aLine.append( "\n>>\nendobj\n\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
}
// emit Resource dict
sal_Int32 nResourceDict = getResourceDictObj();
CHECK_RETURN( updateObject( nResourceDict ) );
aLine.setLength( 0 );
aLine.append( nResourceDict );
aLine.append( " 0 obj\n<<" );
if( nFontDict )
{
aLine.append( "/Font " );
aLine.append( nFontDict );
aLine.append( " 0 R\n" );
}
if( nXObjectDict )
{
aLine.append( "/XObject " );
aLine.append( nXObjectDict );
aLine.append( " 0 R\n" );
}
if( nExtGStateObject )
{
aLine.append( "/ExtGState " );
aLine.append( nExtGStateObject );
aLine.append( " 0 R\n" );
}
if( nShadingDict )
{
aLine.append( "/Shading " );
aLine.append( nShadingDict );
aLine.append( " 0 R\n" );
}
if( nPatternDict )
{
aLine.append( "/Pattern " );
aLine.append( nPatternDict );
aLine.append( " 0 R\n" );
}
aLine.append( "/ProcSet[/PDF/Text" );
if( nXObjectDict )
aLine.append( "/ImageC/ImageI/ImageB" );
aLine.append( "]>>\n"
"endobj\n\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
return nResourceDict;
}
sal_Int32 PDFWriterImpl::emitOutline()
{
int i, nItems = m_aOutline.size();
// do we have an outline at all ?
if( nItems < 2 )
return 0;
// reserve object numbers for all outline items
for( i = 0; i < nItems; ++i )
m_aOutline[i].m_nObject = createObject();
// update all parent, next and prev object ids
for( i = 0; i < nItems; ++i )
{
PDFOutlineEntry& rItem = m_aOutline[i];
int nChildren = rItem.m_aChildren.size();
if( nChildren )
{
for( int n = 0; n < nChildren; ++n )
{
PDFOutlineEntry& rChild = m_aOutline[ rItem.m_aChildren[n] ];
rChild.m_nParentObject = rItem.m_nObject;
rChild.m_nPrevObject = (n > 0) ? m_aOutline[ rItem.m_aChildren[n-1] ].m_nObject : 0;
rChild.m_nNextObject = (n < nChildren-1) ? m_aOutline[ rItem.m_aChildren[n+1] ].m_nObject : 0;
}
}
}
// emit hierarchy
for( i = 0; i < nItems; ++i )
{
PDFOutlineEntry& rItem = m_aOutline[i];
OStringBuffer aLine( 1024 );
CHECK_RETURN( updateObject( rItem.m_nObject ) );
aLine.append( rItem.m_nObject );
aLine.append( " 0 obj\n" );
aLine.append( "<<" );
if( ! rItem.m_aChildren.empty() )
{
// children list: Count, First, Last
aLine.append( "/Count " );
aLine.append( (sal_Int32)rItem.m_aChildren.size() );
aLine.append( "/First " );
aLine.append( m_aOutline[rItem.m_aChildren.front()].m_nObject );
aLine.append( " 0 R/Last " );
aLine.append( m_aOutline[rItem.m_aChildren.back()].m_nObject );
aLine.append( " 0 R\n" );
}
if( i > 0 )
{
// Title, Dest, Parent, Prev, Next
aLine.append( "/Title" );
appendUnicodeTextStringEncrypt( rItem.m_aTitle, rItem.m_nObject, aLine );
aLine.append( "\n" );
// Dest is not required
if( rItem.m_nDestID >= 0 && rItem.m_nDestID < (sal_Int32)m_aDests.size() )
{
aLine.append( "/Dest" );
appendDest( rItem.m_nDestID, aLine );
}
aLine.append( "/Parent " );
aLine.append( rItem.m_nParentObject );
aLine.append( " 0 R" );
if( rItem.m_nPrevObject )
{
aLine.append( "/Prev " );
aLine.append( rItem.m_nPrevObject );
aLine.append( " 0 R" );
}
if( rItem.m_nNextObject )
{
aLine.append( "/Next " );
aLine.append( rItem.m_nNextObject );
aLine.append( " 0 R" );
}
}
aLine.append( ">>\nendobj\n\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
}
return m_aOutline[0].m_nObject;
}
#undef CHECK_RETURN
#define CHECK_RETURN( x ) if( !x ) return false
bool PDFWriterImpl::appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer )
{
if( nDestID < 0 || nDestID >= (sal_Int32)m_aDests.size() )
{
#if OSL_DEBUG_LEVEL > 1
fprintf( stderr, "ERROR: invalid dest %d requested\n", (int)nDestID );
#endif
return false;
}
const PDFDest& rDest = m_aDests[ nDestID ];
const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ];
rBuffer.append( '[' );
rBuffer.append( rDestPage.m_nPageObject );
rBuffer.append( " 0 R" );
switch( rDest.m_eType )
{
case PDFWriter::XYZ:
default:
rBuffer.append( "/XYZ " );
appendFixedInt( rDest.m_aRect.Left(), rBuffer );
rBuffer.append( ' ' );
appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
rBuffer.append( " 0" );
break;
case PDFWriter::Fit:
rBuffer.append( "/Fit" );
break;
case PDFWriter::FitRectangle:
rBuffer.append( "/FitR " );
appendFixedInt( rDest.m_aRect.Left(), rBuffer );
rBuffer.append( ' ' );
appendFixedInt( rDest.m_aRect.Top(), rBuffer );
rBuffer.append( ' ' );
appendFixedInt( rDest.m_aRect.Right(), rBuffer );
rBuffer.append( ' ' );
appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
break;
case PDFWriter::FitHorizontal:
rBuffer.append( "/FitH " );
appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
break;
case PDFWriter::FitVertical:
rBuffer.append( "/FitV " );
appendFixedInt( rDest.m_aRect.Left(), rBuffer );
break;
case PDFWriter::FitPageBoundingBox:
rBuffer.append( "/FitB" );
break;
case PDFWriter::FitPageBoundingBoxHorizontal:
rBuffer.append( "/FitBH " );
appendFixedInt( rDest.m_aRect.Bottom(), rBuffer );
break;
case PDFWriter::FitPageBoundingBoxVertical:
rBuffer.append( "/FitBV " );
appendFixedInt( rDest.m_aRect.Left(), rBuffer );
break;
}
rBuffer.append( ']' );
return true;
}
bool PDFWriterImpl::emitLinkAnnotations()
{
int nAnnots = m_aLinks.size();
for( int i = 0; i < nAnnots; i++ )
{
const PDFLink& rLink = m_aLinks[i];
if( ! updateObject( rLink.m_nObject ) )
continue;
OStringBuffer aLine( 1024 );
aLine.append( rLink.m_nObject );
aLine.append( " 0 obj\n" );
aLine.append( "<</Type/Annot/Subtype/Link/Border[0 0 0]/Rect[" );
appendFixedInt( rLink.m_aRect.Left()-7, aLine );//the +7 to have a better shape of the border rectangle
aLine.append( ' ' );
appendFixedInt( rLink.m_aRect.Top(), aLine );
aLine.append( ' ' );
appendFixedInt( rLink.m_aRect.Right()+7, aLine );//the +7 to have a better shape of the border rectangle
aLine.append( ' ' );
appendFixedInt( rLink.m_aRect.Bottom(), aLine );
aLine.append( "]" );
if( rLink.m_nDest >= 0 )
{
aLine.append( "/Dest" );
appendDest( rLink.m_nDest, aLine );
}
else
{
aLine.append( "/A<</Type/Action/S/URI/URI" );
appendLiteralStringEncrypt( rLink.m_aURL, rLink.m_nObject, aLine );
aLine.append( ">>\n" );
}
if( rLink.m_nStructParent > 0 )
{
aLine.append( "/StructParent " );
aLine.append( rLink.m_nStructParent );
}
aLine.append( ">>\nendobj\n\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
}
return true;
}
bool PDFWriterImpl::emitNoteAnnotations()
{
// emit note annotations
int nAnnots = m_aNotes.size();
for( int i = 0; i < nAnnots; i++ )
{
const PDFNoteEntry& rNote = m_aNotes[i];
if( ! updateObject( rNote.m_nObject ) )
return false;
OStringBuffer aLine( 1024 );
aLine.append( rNote.m_nObject );
aLine.append( " 0 obj\n" );
aLine.append( "<</Type/Annot/Subtype/Text/Rect[" );
appendFixedInt( rNote.m_aRect.Left(), aLine );
aLine.append( ' ' );
appendFixedInt( rNote.m_aRect.Top(), aLine );
aLine.append( ' ' );
appendFixedInt( rNote.m_aRect.Right(), aLine );
aLine.append( ' ' );
appendFixedInt( rNote.m_aRect.Bottom(), aLine );
aLine.append( "]" );
// contents of the note (type text string)
aLine.append( "/Contents\n" );
appendUnicodeTextStringEncrypt( rNote.m_aContents.Contents, rNote.m_nObject, aLine );
aLine.append( "\n" );
// optional title
if( rNote.m_aContents.Title.Len() )
{
aLine.append( "/T" );
appendUnicodeTextStringEncrypt( rNote.m_aContents.Title, rNote.m_nObject, aLine );
aLine.append( "\n" );
}
aLine.append( ">>\nendobj\n\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
}
return true;
}
Font PDFWriterImpl::replaceFont( const Font& rControlFont, const Font& rAppSetFont )
{
bool bAdjustSize = false;
Font aFont( rControlFont );
if( ! aFont.GetName().Len() )
{
aFont = rAppSetFont;
if( rControlFont.GetHeight() )
aFont.SetSize( Size( 0, rControlFont.GetHeight() ) );
else
bAdjustSize = true;
}
else if( ! aFont.GetHeight() )
{
aFont.SetSize( rAppSetFont.GetSize() );
bAdjustSize = true;
}
if( bAdjustSize )
{
Size aFontSize = aFont.GetSize();
OutputDevice* pDefDev = Application::GetDefaultDevice();
aFontSize = OutputDevice::LogicToLogic( aFontSize, pDefDev->GetMapMode(), getMapMode() );
aFont.SetSize( aFontSize );
}
return aFont;
}
static inline const Color& replaceColor( const Color& rCol1, const Color& rCol2 )
{
return (rCol1 == Color( COL_TRANSPARENT )) ? rCol2 : rCol1;
}
void PDFWriterImpl::createDefaultPushButtonAppearance( PDFWidget& rButton, const PDFWriter::PushButtonWidget& rWidget )
{
const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
// save graphics state
push( sal::static_int_cast<sal_uInt16>(~0U) );
// transform relative to control's coordinates since an
// appearance stream is a form XObject
// this relies on the m_aRect member of rButton NOT already being transformed
// to default user space
if( rWidget.Background || rWidget.Border )
{
setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetLightColor() ) : Color( COL_TRANSPARENT ) );
setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetDialogColor() ) : Color( COL_TRANSPARENT ) );
drawRectangle( rWidget.Location );
}
// prepare font to use
Font aFont = replaceFont( rWidget.TextFont, rSettings.GetPushButtonFont() );
setFont( aFont );
setTextColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ) );
drawText( rButton.m_aRect, rButton.m_aText, rButton.m_nTextStyle );
// create DA string while local mapmode is still in place
// (that is before endRedirect())
OStringBuffer aDA( 256 );
appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ), aDA );
aDA.append( " /HelvReg " );
m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aFont.GetHeight() ), aDA );
aDA.append( " Tf" );
rButton.m_aDAString = aDA.makeStringAndClear();
pop();
rButton.m_aAppearances[ "N" ][ "Standard" ] = new SvMemoryStream();
/* seems like a bad hack but at least works in both AR5 and 6:
we draw the button ourselves and tell AR
the button would be totally transparent with no text
One would expect that simply setting a normal appearance
should suffice, but no, as soon as the user actually presses
the button and an action is tied to it (gasp! a button that
does something) the appearance gets replaced by some crap that AR
creates on the fly even if no DA or MK is given. On AR6 at least
the DA and MK work as expected, but on AR5 this creates a region
filled with the background color but nor text. Urgh.
*/
rButton.m_aMKDict = "/BC [] /BG [] /CA";
rButton.m_aMKDictCAString = "";
}
Font PDFWriterImpl::drawFieldBorder( PDFWidget& rIntern,
const PDFWriter::AnyWidget& rWidget,
const StyleSettings& rSettings )
{
Font aFont = replaceFont( rWidget.TextFont, rSettings.GetFieldFont() );
aFont.SetName( String( RTL_CONSTASCII_USTRINGPARAM( "Helvetica" ) ) );
if( rWidget.Background || rWidget.Border )
{
if( rWidget.Border && rWidget.BorderColor == Color( COL_TRANSPARENT ) )
{
sal_Int32 nDelta = getReferenceDevice()->ImplGetDPIX() / 500;
if( nDelta < 1 )
nDelta = 1;
setLineColor( Color( COL_TRANSPARENT ) );
Rectangle aRect = rIntern.m_aRect;
setFillColor( rSettings.GetLightBorderColor() );
drawRectangle( aRect );
aRect.Left() += nDelta; aRect.Top() += nDelta;
aRect.Right() -= nDelta; aRect.Bottom() -= nDelta;
setFillColor( rSettings.GetFieldColor() );
drawRectangle( aRect );
setFillColor( rSettings.GetLightColor() );
drawRectangle( Rectangle( Point( aRect.Left(), aRect.Bottom()-nDelta ), aRect.BottomRight() ) );
drawRectangle( Rectangle( Point( aRect.Right()-nDelta, aRect.Top() ), aRect.BottomRight() ) );
setFillColor( rSettings.GetDarkShadowColor() );
drawRectangle( Rectangle( aRect.TopLeft(), Point( aRect.Left()+nDelta, aRect.Bottom() ) ) );
drawRectangle( Rectangle( aRect.TopLeft(), Point( aRect.Right(), aRect.Top()+nDelta ) ) );
}
else
{
setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetShadowColor() ) : Color( COL_TRANSPARENT ) );
setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : Color( COL_TRANSPARENT ) );
drawRectangle( rIntern.m_aRect );
}
if( rWidget.Border )
{
// adjust edit area accounting for border
sal_Int32 nDelta = aFont.GetHeight()/4;
if( nDelta < 1 )
nDelta = 1;
rIntern.m_aRect.Left() += nDelta;
rIntern.m_aRect.Top() += nDelta;
rIntern.m_aRect.Right() -= nDelta;
rIntern.m_aRect.Bottom()-= nDelta;
}
}
return aFont;
}
void PDFWriterImpl::createDefaultEditAppearance( PDFWidget& rEdit, const PDFWriter::EditWidget& rWidget )
{
const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
SvMemoryStream* pEditStream = new SvMemoryStream( 1024, 1024 );
push( sal::static_int_cast<sal_uInt16>(~0U) );
// prepare font to use, draw field border
Font aFont = drawFieldBorder( rEdit, rWidget, rSettings );
// prepare DA string
OStringBuffer aDA( 32 );
appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
aDA.append( " /HelvReg " );
m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetHeight() ), aDA );
aDA.append( " Tf" );
/* create an empty appearance stream, let the viewer create
the appearance at runtime. This is because AR5 seems to
paint the widget appearance always, and a dynamically created
appearance on top of it. AR6 is well behaved in that regard, so
that behaviour seems to be a bug. Anyway this empty appearance
relies on /NeedAppearances in the AcroForm dictionary set to "true"
*/
beginRedirect( pEditStream, rEdit.m_aRect );
OStringBuffer aAppearance( 32 );
aAppearance.append( "/Tx BMC\nEMC\n" );
writeBuffer( aAppearance.getStr(), aAppearance.getLength() );
endRedirect();
pop();
rEdit.m_aAppearances[ "N" ][ "Standard" ] = pEditStream;
rEdit.m_aDAString = aDA.makeStringAndClear();
}
void PDFWriterImpl::createDefaultListBoxAppearance( PDFWidget& rBox, const PDFWriter::ListBoxWidget& rWidget )
{
const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
SvMemoryStream* pListBoxStream = new SvMemoryStream( 1024, 1024 );
push( sal::static_int_cast<sal_uInt16>(~0U) );
// prepare font to use, draw field border
Font aFont = drawFieldBorder( rBox, rWidget, rSettings );
beginRedirect( pListBoxStream, rBox.m_aRect );
OStringBuffer aAppearance( 64 );
#if 0
if( ! rWidget.DropDown )
{
// prepare linewidth for DA string hack, see below
Size aFontSize = lcl_convert( m_aGraphicsStack.front().m_aMapMode,
m_aMapMode,
getReferenceDevice(),
Size( 0, aFont.GetHeight() ) );
sal_Int32 nLW = aFontSize.Height() / 40;
appendFixedInt( nLW > 0 ? nLW : 1, aAppearance );
aAppearance.append( " w\n" );
writeBuffer( aAppearance.getStr(), aAppearance.getLength() );
aAppearance.setLength( 0 );
}
#endif
setLineColor( Color( COL_TRANSPARENT ) );
setFillColor( replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) );
drawRectangle( rBox.m_aRect );
// empty appearance, see createDefaultEditAppearance for reference
aAppearance.append( "/Tx BMC\nEMC\n" );
writeBuffer( aAppearance.getStr(), aAppearance.getLength() );
endRedirect();
pop();
rBox.m_aAppearances[ "N" ][ "Standard" ] = pListBoxStream;
// prepare DA string
OStringBuffer aDA( 256 );
#if 0
if( !rWidget.DropDown )
{
/* another of AR5's peculiarities: the selected item of a choice
field is highlighted using the non stroking color - same as the
text color. so workaround that by using text rendering mode 2
(fill, then stroke) and set the stroking color
*/
appendStrokingColor( replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ), aDA );
aDA.append( " 2 Tr " );
}
#endif
appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA );
aDA.append( " /HelvReg " );
m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetHeight() ), aDA );
aDA.append( " Tf" );
rBox.m_aDAString = aDA.makeStringAndClear();
}
void PDFWriterImpl::createDefaultCheckBoxAppearance( PDFWidget& rBox, const PDFWriter::CheckBoxWidget& rWidget )
{
const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
// save graphics state
push( sal::static_int_cast<sal_uInt16>(~0U) );
if( rWidget.Background || rWidget.Border )
{
setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : Color( COL_TRANSPARENT ) );
setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : Color( COL_TRANSPARENT ) );
drawRectangle( rBox.m_aRect );
}
Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
setFont( aFont );
Size aFontSize = aFont.GetSize();
sal_Int32 nDelta = aFontSize.Height()/10;
if( nDelta < 1 )
nDelta = 1;
Rectangle aCheckRect, aTextRect;
if( rWidget.ButtonIsLeft )
{
aCheckRect.Left() = rBox.m_aRect.Left() + nDelta;
aCheckRect.Top() = rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2;
aCheckRect.Right() = aCheckRect.Left() + aFontSize.Height();
aCheckRect.Bottom() = aCheckRect.Top() + aFontSize.Height();
aTextRect.Left() = rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta;
aTextRect.Top() = rBox.m_aRect.Top();
aTextRect.Right() = aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta;
aTextRect.Bottom() = rBox.m_aRect.Bottom();
}
else
{
aCheckRect.Left() = rBox.m_aRect.Right() - nDelta - aFontSize.Height();
aCheckRect.Top() = rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2;
aCheckRect.Right() = aCheckRect.Left() + aFontSize.Height();
aCheckRect.Bottom() = aCheckRect.Top() + aFontSize.Height();
aTextRect.Left() = rBox.m_aRect.Left();
aTextRect.Top() = rBox.m_aRect.Top();
aTextRect.Right() = aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta;
aTextRect.Bottom() = rBox.m_aRect.Bottom();
}
setLineColor( Color( COL_BLACK ) );
setFillColor( Color( COL_TRANSPARENT ) );
OStringBuffer aLW( 32 );
aLW.append( "q " );
m_aPages[m_nCurrentPage].appendMappedLength( nDelta, aLW );
aLW.append( " w " );
writeBuffer( aLW.getStr(), aLW.getLength() );
drawRectangle( aCheckRect );
writeBuffer( " Q\n", 3 );
setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
pop();
OStringBuffer aDA( 256 );
appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
aDA.append( " /ZaDb 0 Tf" );
rBox.m_aDAString = aDA.makeStringAndClear();
rBox.m_aMKDict = "/CA";
rBox.m_aMKDictCAString = "8";
rBox.m_aRect = aCheckRect;
// create appearance streams
sal_Char cMark = '8';
sal_Int32 nCharXOffset = 1000-m_aBuiltinFonts[13].m_aWidths[sal_Int32(cMark)];
nCharXOffset *= aCheckRect.GetHeight();
nCharXOffset /= 2000;
sal_Int32 nCharYOffset = 1000-
(m_aBuiltinFonts[13].m_nAscent+m_aBuiltinFonts[13].m_nDescent); // descent is negative
nCharYOffset *= aCheckRect.GetHeight();
nCharYOffset /= 2000;
SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
beginRedirect( pCheckStream, aCheckRect );
aDA.append( "/Tx BMC\nq BT\n" );
appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
aDA.append( " /ZaDb " );
m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
aDA.append( " Tf\n" );
m_aPages[ m_nCurrentPage ].appendMappedLength( nCharXOffset, aDA );
aDA.append( " " );
m_aPages[ m_nCurrentPage ].appendMappedLength( nCharYOffset, aDA );
aDA.append( " Td (" );
aDA.append( cMark );
aDA.append( ") Tj\nET\nQ\nEMC\n" );
writeBuffer( aDA.getStr(), aDA.getLength() );
endRedirect();
rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream;
SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
beginRedirect( pUncheckStream, aCheckRect );
writeBuffer( "/Tx BMC\nEMC\n", 12 );
endRedirect();
rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream;
}
void PDFWriterImpl::createDefaultRadioButtonAppearance( PDFWidget& rBox, const PDFWriter::RadioButtonWidget& rWidget )
{
const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings();
// save graphics state
push( sal::static_int_cast<sal_uInt16>(~0U) );
if( rWidget.Background || rWidget.Border )
{
setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : Color( COL_TRANSPARENT ) );
setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : Color( COL_TRANSPARENT ) );
drawRectangle( rBox.m_aRect );
}
Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() );
setFont( aFont );
Size aFontSize = aFont.GetSize();
sal_Int32 nDelta = aFontSize.Height()/10;
if( nDelta < 1 )
nDelta = 1;
Rectangle aCheckRect, aTextRect;
if( rWidget.ButtonIsLeft )
{
aCheckRect.Left() = rBox.m_aRect.Left() + nDelta;
aCheckRect.Top() = rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2;
aCheckRect.Right() = aCheckRect.Left() + aFontSize.Height();
aCheckRect.Bottom() = aCheckRect.Top() + aFontSize.Height();
aTextRect.Left() = rBox.m_aRect.Left() + aCheckRect.GetWidth()+5*nDelta;
aTextRect.Top() = rBox.m_aRect.Top();
aTextRect.Right() = aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta;
aTextRect.Bottom() = rBox.m_aRect.Bottom();
}
else
{
aCheckRect.Left() = rBox.m_aRect.Right() - nDelta - aFontSize.Height();
aCheckRect.Top() = rBox.m_aRect.Top() + (rBox.m_aRect.GetHeight()-aFontSize.Height())/2;
aCheckRect.Right() = aCheckRect.Left() + aFontSize.Height();
aCheckRect.Bottom() = aCheckRect.Top() + aFontSize.Height();
aTextRect.Left() = rBox.m_aRect.Left();
aTextRect.Top() = rBox.m_aRect.Top();
aTextRect.Right() = aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta;
aTextRect.Bottom() = rBox.m_aRect.Bottom();
}
setLineColor( Color( COL_BLACK ) );
setFillColor( Color( COL_TRANSPARENT ) );
OStringBuffer aLW( 32 );
aLW.append( "q " );
m_aPages[ m_nCurrentPage ].appendMappedLength( nDelta, aLW );
aLW.append( " w " );
writeBuffer( aLW.getStr(), aLW.getLength() );
drawEllipse( aCheckRect );
writeBuffer( " Q\n", 3 );
setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle );
pop();
OStringBuffer aDA( 256 );
appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
aDA.append( " /ZaDb 0 Tf" );
rBox.m_aDAString = aDA.makeStringAndClear();
//to encrypt this (el)
rBox.m_aMKDict = "/CA";
//after this assignement, to m_aMKDic cannot be added anything
rBox.m_aMKDictCAString = "l";
rBox.m_aRect = aCheckRect;
// create appearance streams
push( sal::static_int_cast<sal_uInt16>(~0U) );
SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 );
beginRedirect( pCheckStream, aCheckRect );
aDA.append( "/Tx BMC\nq BT\n" );
appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA );
aDA.append( " /ZaDb " );
m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA );
aDA.append( " Tf\n0 0 Td\nET\nQ\n" );
writeBuffer( aDA.getStr(), aDA.getLength() );
setFillColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) );
setLineColor( Color( COL_TRANSPARENT ) );
aCheckRect.Left() += 3*nDelta;
aCheckRect.Top() += 3*nDelta;
aCheckRect.Bottom() -= 3*nDelta;
aCheckRect.Right() -= 3*nDelta;
drawEllipse( aCheckRect );
writeBuffer( "\nEMC\n", 5 );
endRedirect();
pop();
rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream;
SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 );
beginRedirect( pUncheckStream, aCheckRect );
writeBuffer( "/Tx BMC\nEMC\n", 12 );
endRedirect();
rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream;
}
bool PDFWriterImpl::emitAppearances( PDFWidget& rWidget, OStringBuffer& rAnnotDict )
{
// TODO: check and insert default streams
rtl::OString aStandardAppearance;
switch( rWidget.m_eType )
{
case PDFWriter::CheckBox:
aStandardAppearance = OUStringToOString( rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US );
break;
default:
break;
}
if( rWidget.m_aAppearances.size() )
{
rAnnotDict.append( "/AP<<\n" );
for( PDFAppearanceMap::iterator dict_it = rWidget.m_aAppearances.begin(); dict_it != rWidget.m_aAppearances.end(); ++dict_it )
{
rAnnotDict.append( "/" );
rAnnotDict.append( dict_it->first );
bool bUseSubDict = (dict_it->second.size() > 1);
rAnnotDict.append( bUseSubDict ? "<<" : " " );
for( PDFAppearanceStreams::const_iterator stream_it = dict_it->second.begin();
stream_it != dict_it->second.end(); ++stream_it )
{
SvMemoryStream* pApppearanceStream = stream_it->second;
dict_it->second[ stream_it->first ] = NULL;
bool bDeflate = compressStream( pApppearanceStream );
pApppearanceStream->Seek( STREAM_SEEK_TO_END );
sal_Int64 nStreamLen = pApppearanceStream->Tell();
pApppearanceStream->Seek( STREAM_SEEK_TO_BEGIN );
sal_Int32 nObject = createObject();
CHECK_RETURN( updateObject( nObject ) );
#if OSL_DEBUG_LEVEL > 1
{
OStringBuffer aLine( " PDFWriterImpl::emitAppearances" );
emitComment( aLine.getStr() );
}
#endif
OStringBuffer aLine;
aLine.append( nObject );
aLine.append( " 0 obj\n"
"<</Type/XObject\n"
"/Subtype/Form\n"
"/BBox[0 0 " );
appendFixedInt( rWidget.m_aRect.GetWidth()-1, aLine );
aLine.append( " " );
appendFixedInt( rWidget.m_aRect.GetHeight()-1, aLine );
aLine.append( "]\n"
"/Resources " );
aLine.append( getResourceDictObj() );
aLine.append( " 0 R\n"
"/Length " );
aLine.append( nStreamLen );
aLine.append( "\n" );
if( bDeflate )
aLine.append( "/Filter/FlateDecode\n" );
aLine.append( ">>\nstream\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
checkAndEnableStreamEncryption( nObject );
CHECK_RETURN( writeBuffer( pApppearanceStream->GetData(), nStreamLen ) );
disableStreamEncryption();
CHECK_RETURN( writeBuffer( "\nendstream\nendobj\n\n", 19 ) );
if( bUseSubDict )
{
rAnnotDict.append( " /" );
rAnnotDict.append( stream_it->first );
rAnnotDict.append( " " );
}
rAnnotDict.append( nObject );
rAnnotDict.append( " 0 R" );
delete pApppearanceStream;
}
rAnnotDict.append( bUseSubDict ? ">>\n" : "\n" );
}
rAnnotDict.append( ">>\n" );
if( aStandardAppearance.getLength() )
{
rAnnotDict.append( "/AS /" );
rAnnotDict.append( aStandardAppearance );
rAnnotDict.append( "\n" );
}
}
return true;
}
bool PDFWriterImpl::emitWidgetAnnotations()
{
ensureUniqueRadioOnValues();
int nAnnots = m_aWidgets.size();
for( int a = 0; a < nAnnots; a++ )
{
PDFWidget& rWidget = m_aWidgets[a];
OStringBuffer aLine( 1024 );
OStringBuffer aValue( 256 );
aLine.append( rWidget.m_nObject );
aLine.append( " 0 obj\n"
"<<" );
// emit widget annotation only for terminal fields
if( rWidget.m_aKids.empty() )
{
aLine.append( "/Type/Annot/Subtype/Widget/F 4\n"
"/Rect[" );
appendFixedInt( rWidget.m_aRect.Left()-1, aLine );
aLine.append( ' ' );
appendFixedInt( rWidget.m_aRect.Top()+1, aLine );
aLine.append( ' ' );
appendFixedInt( rWidget.m_aRect.Right()+1, aLine );
aLine.append( ' ' );
appendFixedInt( rWidget.m_aRect.Bottom()-1, aLine );
aLine.append( "]\n" );
}
aLine.append( "/FT/" );
switch( rWidget.m_eType )
{
case PDFWriter::RadioButton:
case PDFWriter::CheckBox:
// for radio buttons only the RadioButton field, not the
// CheckBox children should have a value, else acrobat reader
// does not always check the right button
// of course real check boxes (not belonging to a readio group)
// need their values, too
if( rWidget.m_eType == PDFWriter::RadioButton || rWidget.m_nRadioGroup < 0 )
{
aValue.append( "/" );
// check for radio group with all buttons unpressed
if( rWidget.m_aValue.getLength() == 0 )
aValue.append( "Off" );
else
appendName( rWidget.m_aValue, aValue );
}
case PDFWriter::PushButton:
aLine.append( "Btn" );
break;
case PDFWriter::ListBox:
case PDFWriter::ComboBox:
if( rWidget.m_nFlags & 0x200000 ) // multiselect
{
aValue.append( "[" );
appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue );
aValue.append( "]" );
}
else
appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue );
aLine.append( "Ch" );
break;
case PDFWriter::Edit:
aLine.append( "Tx" );
appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue );
break;
}
aLine.append( "\n" );
aLine.append( "/P " );
aLine.append( m_aPages[ rWidget.m_nPage ].m_nPageObject );
aLine.append( " 0 R\n" );
if( rWidget.m_nParent )
{
aLine.append( "/Parent " );
aLine.append( rWidget.m_nParent );
aLine.append( " 0 R\n" );
}
if( rWidget.m_aKids.size() )
{
aLine.append( "/Kids[" );
for( unsigned int i = 0; i < rWidget.m_aKids.size(); i++ )
{
aLine.append( rWidget.m_aKids[i] );
aLine.append( " 0 R" );
aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
}
aLine.append( "]\n" );
}
if( rWidget.m_aName.getLength() )
{
aLine.append( "/T" );
appendLiteralStringEncrypt( rWidget.m_aName, rWidget.m_nObject, aLine );
aLine.append( "\n" );
}
if( m_aContext.Version > PDFWriter::PDF_1_2 )
{
// the alternate field name should be unicode able since it is
// supposed to be used in UI
aLine.append( "/TU" );
appendUnicodeTextStringEncrypt( rWidget.m_aDescription, rWidget.m_nObject, aLine );
aLine.append( "\n" );
}
if( rWidget.m_nFlags )
{
aLine.append( "/Ff " );
aLine.append( rWidget.m_nFlags );
aLine.append( "\n" );
}
if( aValue.getLength() )
{
OString aVal = aValue.makeStringAndClear();
aLine.append( "/V " );
aLine.append( aVal );
aLine.append( "\n"
"/DV " );
aLine.append( aVal );
aLine.append( "\n" );
}
if( rWidget.m_eType == PDFWriter::ListBox || rWidget.m_eType == PDFWriter::ComboBox )
{
sal_Int32 nTI = -1;
aLine.append( "/Opt[\n" );
sal_Int32 i = 0;
for( std::list< OUString >::const_iterator it = rWidget.m_aListEntries.begin(); it != rWidget.m_aListEntries.end(); ++it, ++i )
{
appendUnicodeTextStringEncrypt( *it, rWidget.m_nObject, aLine );
aLine.append( "\n" );
if( *it == rWidget.m_aValue )
nTI = i;
}
aLine.append( "]\n" );
if( nTI > 0 )
{
aLine.append( "/TI " );
aLine.append( nTI );
aLine.append( "\n" );
if( rWidget.m_nFlags & 0x200000 ) // Multiselect
{
aLine.append( "/I [" );
aLine.append( nTI );
aLine.append( "]\n" );
}
}
}
if( rWidget.m_eType == PDFWriter::Edit && rWidget.m_nMaxLen > 0 )
{
aLine.append( "/MaxLen " );
aLine.append( rWidget.m_nMaxLen );
aLine.append( "\n" );
}
if( rWidget.m_eType == PDFWriter::PushButton )
{
OStringBuffer aDest;
if( appendDest( rWidget.m_nDest, aDest ) )
{
aLine.append( "/AA<</D<</Type/Action/S/GoTo/D " );
aLine.append( aDest.makeStringAndClear() );
aLine.append( ">>>>\n" );
}
else if( rWidget.m_aListEntries.empty() )
{
// create a reset form action
aLine.append( "/AA<</D<</Type/Action/S/ResetForm>>>>\n" );
}
else if( rWidget.m_bSubmit )
{
// create a submit form action
aLine.append( "/AA<</D<</Type/Action/S/SubmitForm/F" );
appendLiteralStringEncrypt( rWidget.m_aListEntries.front(), rWidget.m_nObject, aLine );
aLine.append( "/Flags " );
sal_Int32 nFlags = 0;
switch( m_aContext.SubmitFormat )
{
case PDFWriter::HTML:
nFlags |= 4;
break;
case PDFWriter::XML:
if( m_aContext.Version > PDFWriter::PDF_1_3 )
nFlags |= 32;
break;
case PDFWriter::PDF:
if( m_aContext.Version > PDFWriter::PDF_1_3 )
nFlags |= 256;
break;
case PDFWriter::FDF:
default:
break;
}
aLine.append( nFlags );
aLine.append( ">>>>\n" );
}
else
{
// create a URI action
aLine.append( "/AA<</D<</Type/Action/S/URI/URI(" );
aLine.append( OUStringToOString( rWidget.m_aListEntries.front(), RTL_TEXTENCODING_ASCII_US ) );
aLine.append( ")>>>>\n" );
}
}
if( rWidget.m_aDAString.getLength() )
{
aLine.append( "/DR<</Font " );
aLine.append( getFontDictObj() );
aLine.append( " 0 R>>\n" );
aLine.append( "/DA" );
appendLiteralStringEncrypt( rWidget.m_aDAString, rWidget.m_nObject, aLine );
aLine.append( "\n" );
if( rWidget.m_nTextStyle & TEXT_DRAW_CENTER )
aLine.append( "/Q 1\n" );
else if( rWidget.m_nTextStyle & TEXT_DRAW_RIGHT )
aLine.append( "/Q 2\n" );
}
// appearance charactristics for terminal fields
// which are supposed to have an appearance constructed
// by the viewer application
if( rWidget.m_aMKDict.getLength() )
{
aLine.append( "/MK<<" );
aLine.append( rWidget.m_aMKDict );
//add the CA string, encrypting it
appendLiteralStringEncrypt(rWidget.m_aMKDictCAString, rWidget.m_nObject, aLine);
aLine.append( ">>\n" );
}
CHECK_RETURN( emitAppearances( rWidget, aLine ) );
aLine.append( ">>\n"
"endobj\n\n" );
CHECK_RETURN( updateObject( rWidget.m_nObject ) );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
}
return true;
}
bool PDFWriterImpl::emitAnnotations()
{
if( m_aPages.size() < 1 )
return false;
CHECK_RETURN( emitLinkAnnotations() );
CHECK_RETURN( emitNoteAnnotations() );
CHECK_RETURN( emitWidgetAnnotations() );
return true;
}
#undef CHECK_RETURN
#define CHECK_RETURN( x ) if( !x ) return false
bool PDFWriterImpl::emitCatalog()
{
// build page tree
// currently there is only one node that contains all leaves
// first create a page tree node id
sal_Int32 nTreeNode = createObject();
// emit global resource dictionary (page emit needs it)
CHECK_RETURN( emitResources() );
// emit all pages
for( std::vector<PDFPage>::iterator it = m_aPages.begin(); it != m_aPages.end(); ++it )
if( ! it->emit( nTreeNode ) )
return false;
sal_Int32 nOutlineDict = emitOutline();
sal_Int32 nStructureDict = 0;
if(m_aStructure.size() > 1)
{
nStructureDict = m_aStructure[0].m_nObject = createObject();
emitStructure( m_aStructure[ 0 ] );
}
// adjust tree node file offset
if( ! updateObject( nTreeNode ) )
return false;
// emit tree node
OStringBuffer aLine( 2048 );
aLine.append( nTreeNode );
aLine.append( " 0 obj\n" );
aLine.append( "<</Type/Pages\n" );
aLine.append( "/Resources " );
aLine.append( getResourceDictObj() );
aLine.append( " 0 R\n" );
switch( m_eInheritedOrientation )
{
case PDFWriter::Landscape: aLine.append( "/Rotate 90\n" );break;
case PDFWriter::Seascape: aLine.append( "/Rotate -90\n" );break;
case PDFWriter::Inherit: // actually Inherit would be a bug, but insignificant
case PDFWriter::Portrait:
default:
break;
}
aLine.append( "/MediaBox[ 0 0 " );
aLine.append( m_nInheritedPageWidth );
aLine.append( ' ' );
aLine.append( m_nInheritedPageHeight );
aLine.append( " ]\n"
"/Kids[ " );
unsigned int i = 0;
for( std::vector<PDFPage>::const_iterator iter = m_aPages.begin(); iter != m_aPages.end(); ++iter, i++ )
{
aLine.append( iter->m_nPageObject );
aLine.append( " 0 R" );
aLine.append( ( (i&15) == 15 ) ? "\n" : " " );
}
aLine.append( "]\n"
"/Count " );
aLine.append( (sal_Int32)m_aPages.size() );
aLine.append( ">>\n"
"endobj\n\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
// emit annotation objects
CHECK_RETURN( emitAnnotations() );
// emit Catalog
m_nCatalogObject = createObject();
if( ! updateObject( m_nCatalogObject ) )
return false;
aLine.setLength( 0 );
aLine.append( m_nCatalogObject );
aLine.append( " 0 obj\n"
"<</Type/Catalog/Pages " );
aLine.append( nTreeNode );
aLine.append( " 0 R\n" );
if( m_aContext.PageLayout != PDFWriter::DefaultLayout )
switch( m_aContext.PageLayout )
{
default :
case PDFWriter::SinglePage :
aLine.append( "/PageLayout/SinglePage\n" );
break;
case PDFWriter::Continuous :
aLine.append( "/PageLayout/OneColumn\n" );
break;
case PDFWriter::ContinuousFacing :
//the flag m_aContext.FirstPageLeft below is used to set the page on the left side
aLine.append( "/PageLayout/TwoColumnRight\n" );//odd page on the right side
break;
}
if( m_aContext.PDFDocumentMode != PDFWriter::ModeDefault && !m_aContext.OpenInFullScreenMode )
switch( m_aContext.PDFDocumentMode )
{
default :
aLine.append( "/PageMode/UseNone\n" );
break;
case PDFWriter::UseOutlines :
aLine.append( "/PageMode/UseOutlines\n" ); //document is opened with outline pane open
break;
case PDFWriter::UseThumbs :
aLine.append( "/PageMode/UseThumbs\n" ); //document is opened with thumbnails pane open
break;
}
else if( m_aContext.OpenInFullScreenMode )
aLine.append( "/PageMode/FullScreen\n" ); //document is opened full screen
switch( m_aContext.PDFDocumentAction )
{
case PDFWriter::ActionDefault : //do nothing, this is the Acrobat default
default:
break;
case PDFWriter::FitInWindow :
aLine.append( "/OpenAction[0 /Fit]\n" ); //Open fit page
break;
case PDFWriter::FitWidth :
aLine.append( "/OpenAction[0 /FitH " );
aLine.append( m_nInheritedPageHeight );//Open fit width
aLine.append( "]\n" );
break;
case PDFWriter::FitVisible :
aLine.append( "/OpenAction[0 /FitBH " );
aLine.append( m_nInheritedPageHeight );//Open fit visible
aLine.append( "]\n" );
break;
}
// viewer preferences, if we had some, then emit
if( m_aContext.HideViewerToolbar ||
( m_aContext.Version > PDFWriter::PDF_1_3 && m_aDocInfo.Title.Len() && m_aContext.DisplayPDFDocumentTitle ) ||
m_aContext.HideViewerMenubar ||
m_aContext.HideViewerWindowControls || m_aContext.FitWindow ||
m_aContext.CenterWindow || (m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing ) ||
m_aContext.OpenInFullScreenMode )
{
aLine.append( "/ViewerPreferences<<" );
if( m_aContext.HideViewerToolbar )
aLine.append( "/HideToolbar true\n" );
if( m_aContext.HideViewerMenubar )
aLine.append( "/HideMenubar true\n" );
if( m_aContext.HideViewerWindowControls )
aLine.append( "/HideWindowUI true\n" );
if( m_aContext.FitWindow )
aLine.append( "/FitWindow true\n" );
if( m_aContext.CenterWindow )
aLine.append( "/CenterWindow true\n" );
if( m_aContext.Version > PDFWriter::PDF_1_3 && m_aDocInfo.Title.Len() && m_aContext.DisplayPDFDocumentTitle )
aLine.append( "/DisplayDocTitle true\n" );
if( m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing )
aLine.append( "/Direction/R2L\n" );
if( m_aContext.OpenInFullScreenMode )
switch( m_aContext.PDFDocumentMode )
{
default :
case PDFWriter::ModeDefault :
aLine.append( "/NonFullScreenPageMode/UseNone\n" );
break;
case PDFWriter::UseOutlines :
aLine.append( "/NonFullScreenPageMode/UseOutlines\n" );
break;
case PDFWriter::UseThumbs :
aLine.append( "/NonFullScreenPageMode/UseThumbs\n" );
break;
}
aLine.append( ">>\n" );
}
if( nOutlineDict )
{
aLine.append( "/Outlines " );
aLine.append( nOutlineDict );
aLine.append( " 0 R\n" );
}
if( nStructureDict )
{
aLine.append( "/StructTreeRoot " );
aLine.append( nStructureDict );
aLine.append( " 0 R\n" );
}
if( m_aContext.Tagged && m_aContext.Version > PDFWriter::PDF_1_3 )
{
aLine.append( "/MarkInfo<</Marked true>>\n" );
}
if( m_aWidgets.size() > 0 )
{
aLine.append( "/AcroForm<</Fields[\n" );
int nWidgets = m_aWidgets.size();
int nOut = 0;
for( int j = 0; j < nWidgets; j++ )
{
// output only root fields
if( m_aWidgets[j].m_nParent < 1 )
{
aLine.append( m_aWidgets[j].m_nObject );
aLine.append( (nOut++ % 5)==4 ? " 0 R\n" : " 0 R " );
}
}
aLine.append( "\n]/DR " );
aLine.append( getResourceDictObj() );
aLine.append( " 0 R /NeedAppearances true>>\n" );
}
aLine.append( ">>\n"
"endobj\n\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
return true;
}
sal_Int32 PDFWriterImpl::emitInfoDict( )
{
sal_Int32 nObject = createObject();
if( updateObject( nObject ) )
{
OStringBuffer aLine( 1024 );
aLine.append( nObject );
aLine.append( " 0 obj\n"
"<<" );
if( m_aDocInfo.Title.Len() )
{
aLine.append( "/Title" );
appendUnicodeTextStringEncrypt( m_aDocInfo.Title, nObject, aLine );
aLine.append( "\n" );
}
if( m_aDocInfo.Author.Len() )
{
aLine.append( "/Author" );
appendUnicodeTextStringEncrypt( m_aDocInfo.Author, nObject, aLine );
aLine.append( "\n" );
}
if( m_aDocInfo.Subject.Len() )
{
aLine.append( "/Subject" );
appendUnicodeTextStringEncrypt( m_aDocInfo.Subject, nObject, aLine );
aLine.append( "\n" );
}
if( m_aDocInfo.Keywords.Len() )
{
aLine.append( "/Keywords" );
appendUnicodeTextStringEncrypt( m_aDocInfo.Keywords, nObject, aLine );
aLine.append( "\n" );
}
if( m_aDocInfo.Creator.Len() )
{
aLine.append( "/Creator" );
appendUnicodeTextStringEncrypt( m_aDocInfo.Creator, nObject, aLine );
aLine.append( "\n" );
}
if( m_aDocInfo.Producer.Len() )
{
aLine.append( "/Producer" );
appendUnicodeTextStringEncrypt( m_aDocInfo.Producer, nObject, aLine );
aLine.append( "\n" );
}
aLine.append( "/CreationDate" );
appendLiteralStringEncrypt( m_aCreationDateString, nObject, aLine );
aLine.append( ">>\nendobj\n\n" );
if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) )
nObject = 0;
}
else
nObject = 0;
return nObject;
}
bool PDFWriterImpl::emitTrailer()
{
// emit doc info
OString aInfoValuesOut;
sal_Int32 nDocInfoObject = emitInfoDict( );
sal_Int32 nSecObject = 0;
if( m_aContext.Encrypt == true )
{
//emit the security information
//must be emitted as indirect dictionary object, since
//Acrobat Reader 5 works only with this kind of implementation
nSecObject = createObject();
if( updateObject( nSecObject ) )
{
OStringBuffer aLineS( 1024 );
aLineS.append( nSecObject );
aLineS.append( " 0 obj\n"
"<</Filter/Standard/V " );
// check the version
if( m_aContext.Security128bit == true )
aLineS.append( "2/Length 128/R 3" );
else
aLineS.append( "1/R 2" );
// emit the owner password, must not be encrypted
aLineS.append( "/O(" );
appendLiteralString( (const sal_Char*)m_nEncryptedOwnerPassword, 32, aLineS );
aLineS.append( ")/U(" );
appendLiteralString( (const sal_Char*)m_nEncryptedUserPassword, 32, aLineS );
aLineS.append( ")/P " );// the permission set
aLineS.append( m_nAccessPermissions );
aLineS.append( ">>\nendobj\n\n" );
if( !writeBuffer( aLineS.getStr(), aLineS.getLength() ) )
nSecObject = 0;
}
else
nSecObject = 0;
}
// emit xref table
// remember start
sal_uInt64 nXRefOffset = 0;
CHECK_RETURN( (osl_File_E_None == osl_getFilePos( m_aFile, &nXRefOffset )) );
CHECK_RETURN( writeBuffer( "xref\n", 5 ) );
sal_Int32 nObjects = m_aObjects.size();
OStringBuffer aLine;
aLine.append( "0 " );
aLine.append( (sal_Int32)(nObjects+1) );
aLine.append( "\n" );
aLine.append( "0000000000 65535 f \n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
for( sal_Int32 i = 0; i < nObjects; i++ )
{
aLine.setLength( 0 );
OString aOffset = OString::valueOf( (sal_Int64)m_aObjects[i] );
for( sal_Int32 j = 0; j < (10-aOffset.getLength()); j++ )
aLine.append( '0' );
aLine.append( aOffset );
aLine.append( " 00000 n \n" );
DBG_ASSERT( aLine.getLength() == 20, "invalid xref entry" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
}
// document id set in setDocInfo method
// emit trailer
aLine.setLength( 0 );
aLine.append( "trailer\n"
"<</Size " );
aLine.append( (sal_Int32)(nObjects+1) );
aLine.append( "/Root " );
aLine.append( m_nCatalogObject );
aLine.append( " 0 R\n" );
if( nSecObject |= 0 )
{
aLine.append( "/Encrypt ");
aLine.append( nSecObject );
aLine.append( " 0 R\n" );
}
if( nDocInfoObject )
{
aLine.append( "/Info " );
aLine.append( nDocInfoObject );
aLine.append( " 0 R\n" );
}
if( m_aDocID.getLength() )
{
aLine.append( "/ID [ <" );
aLine.append( m_aDocID.getStr(), m_aDocID.getLength() );
aLine.append( ">\n"
"<" );
aLine.append( m_aDocID.getStr(), m_aDocID.getLength() );
aLine.append( "> ]\n" );
}
aLine.append( ">>\n"
"startxref\n" );
aLine.append( (sal_Int64)nXRefOffset );
aLine.append( "\n"
"%%EOF\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
return true;
}
struct AnnotationSortEntry
{
sal_Int32 nTabOrder;
sal_Int32 nObject;
sal_Int32 nWidgetIndex;
AnnotationSortEntry( sal_Int32 nTab, sal_Int32 nObj, sal_Int32 nI ) :
nTabOrder( nTab ),
nObject( nObj ),
nWidgetIndex( nI )
{}
};
struct AnnotSortContainer
{
std::set< sal_Int32 > aObjects;
std::vector< AnnotationSortEntry > aSortedAnnots;
};
struct AnnotSorterLess
{
std::vector< PDFWriterImpl::PDFWidget >& m_rWidgets;
AnnotSorterLess( std::vector< PDFWriterImpl::PDFWidget >& rWidgets ) : m_rWidgets( rWidgets ) {}
bool operator()( const AnnotationSortEntry& rLeft, const AnnotationSortEntry& rRight )
{
if( rLeft.nTabOrder < rRight.nTabOrder )
return true;
if( rRight.nTabOrder < rLeft.nTabOrder )
return false;
if( rLeft.nWidgetIndex < 0 && rRight.nWidgetIndex < 0 )
return false;
if( rRight.nWidgetIndex < 0 )
return true;
if( rLeft.nWidgetIndex < 0 )
return false;
// remember: widget rects are in PDF coordinates, so they are ordered down up
if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() >
m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() )
return true;
if( m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() >
m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() )
return false;
if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Left() <
m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Left() )
return true;
return false;
}
};
void PDFWriterImpl::sortWidgets()
{
// sort widget annotations on each page as per their
// TabOrder attribute
std::hash_map< sal_Int32, AnnotSortContainer > sorted;
int nWidgets = m_aWidgets.size();
for( int nW = 0; nW < nWidgets; nW++ )
{
const PDFWidget& rWidget = m_aWidgets[nW];
AnnotSortContainer& rCont = sorted[ rWidget.m_nPage ];
// optimize vector allocation
if( rCont.aSortedAnnots.empty() )
rCont.aSortedAnnots.reserve( m_aPages[ rWidget.m_nPage ].m_aAnnotations.size() );
// insert widget to tab sorter
// RadioButtons are not page annotations, only their individual check boxes are
if( rWidget.m_eType != PDFWriter::RadioButton )
{
rCont.aObjects.insert( rWidget.m_nObject );
rCont.aSortedAnnots.push_back( AnnotationSortEntry( rWidget.m_nTabOrder, rWidget.m_nObject, nW ) );
}
}
for( std::hash_map< sal_Int32, AnnotSortContainer >::iterator it = sorted.begin(); it != sorted.end(); ++it )
{
// append entries for non widget annotations
PDFPage& rPage = m_aPages[ it->first ];
unsigned int nAnnots = rPage.m_aAnnotations.size();
for( unsigned int nA = 0; nA < nAnnots; nA++ )
if( it->second.aObjects.find( rPage.m_aAnnotations[nA] ) == it->second.aObjects.end())
it->second.aSortedAnnots.push_back( AnnotationSortEntry( 10000, rPage.m_aAnnotations[nA], -1 ) );
AnnotSorterLess aLess( m_aWidgets );
std::stable_sort( it->second.aSortedAnnots.begin(), it->second.aSortedAnnots.end(), aLess );
// sanity check
if( it->second.aSortedAnnots.size() == nAnnots)
{
for( unsigned int nA = 0; nA < nAnnots; nA++ )
rPage.m_aAnnotations[nA] = it->second.aSortedAnnots[nA].nObject;
}
else
{
DBG_ASSERT( 0, "wrong number of sorted annotations" );
#if OSL_DEBUG_LEVEL > 0
fprintf( stderr, "PDFWriterImpl::sortWidgets(): wrong number of sorted assertions on page nr %ld\n"
" %ld sorted and %ld unsorted\n", (long int)it->first, (long int)it->second.aSortedAnnots.size(), (long int)nAnnots );
#endif
}
}
// FIXME: implement tab order in structure tree for PDF 1.5
}
bool PDFWriterImpl::emit()
{
endPage();
// resort structure tree and annotations if necessary
// needed for widget tab order
sortWidgets();
// emit catalog
CHECK_RETURN( emitCatalog() );
// emit trailer
CHECK_RETURN( emitTrailer() );
osl_closeFile( m_aFile );
m_bOpen = false;
return true;
}
void PDFWriterImpl::registerGlyphs(
int nGlyphs,
sal_Int32* pGlyphs,
sal_Unicode* pUnicodes,
sal_uInt8* pMappedGlyphs,
sal_Int32* pMappedFontObjects,
ImplFontData* pFallbackFonts[] )
{
ImplFontData* pDevFont = m_pReferenceDevice->mpFontEntry->maFontSelData.mpFontData;
for( int i = 0; i < nGlyphs; i++ )
{
if( ! pGlyphs[i] )
continue;
ImplFontData* pCurrentFont = pFallbackFonts[i] ? pFallbackFonts[i] : pDevFont;
if( pCurrentFont->mbSubsettable )
{
FontSubset& rSubset = m_aSubsets[ pCurrentFont ];
// search for glyphID
FontMapping::iterator it = rSubset.m_aMapping.find( pGlyphs[i] );
if( it != rSubset.m_aMapping.end() )
{
pMappedFontObjects[i] = it->second.m_nFontID;
pMappedGlyphs[i] = it->second.m_nSubsetGlyphID;
}
else
{
// create new subset if necessary
if( rSubset.m_aSubsets.begin() == rSubset.m_aSubsets.end() ||
rSubset.m_aSubsets.back().m_aMapping.size() > 254 )
{
rSubset.m_aSubsets.push_back( FontEmit( m_nNextFID++ ) );
}
// copy font id
pMappedFontObjects[i] = rSubset.m_aSubsets.back().m_nFontID;
// create new glyph in subset
sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(rSubset.m_aSubsets.back().m_aMapping.size()+1);
pMappedGlyphs[i] = nNewId;
// add new glyph to emitted font subset
GlyphEmit& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[ pGlyphs[i] ];
rNewGlyphEmit.m_nSubsetGlyphID = nNewId;
rNewGlyphEmit.m_aUnicode = (pUnicodes ? pUnicodes[i] : 0);
// add new glyph to font mapping
Glyph& rNewGlyph = rSubset.m_aMapping[ pGlyphs[i] ];
rNewGlyph.m_nFontID = pMappedFontObjects[i];
rNewGlyph.m_nSubsetGlyphID = nNewId;
}
}
else
{
sal_Int32 nFontID = 0;
FontEmbedData::iterator it = m_aEmbeddedFonts.find( pCurrentFont );
if( it != m_aEmbeddedFonts.end() )
nFontID = it->second.m_nNormalFontID;
else
{
nFontID = m_nNextFID++;
m_aEmbeddedFonts[ pCurrentFont ] = EmbedFont();
m_aEmbeddedFonts[ pCurrentFont ].m_nNormalFontID = nFontID;
}
EmbedFont& rEmbedFont = m_aEmbeddedFonts[pCurrentFont];
const std::map< sal_Unicode, sal_Int32 >* pEncoding = NULL;
const std::map< sal_Unicode, rtl::OString >* pNonEncoded = NULL;
getReferenceDevice()->ImplGetGraphics();
pEncoding = m_pReferenceDevice->mpGraphics->GetFontEncodingVector( pCurrentFont, &pNonEncoded );
std::map< sal_Unicode, sal_Int32 >::const_iterator enc_it;
std::map< sal_Unicode, rtl::OString >::const_iterator nonenc_it;
sal_Int32 nCurFontID = nFontID;
sal_Unicode cChar = pUnicodes[i];
if( pEncoding )
{
enc_it = pEncoding->find( cChar );
if( enc_it != pEncoding->end() && enc_it->second > 0 )
{
DBG_ASSERT( (enc_it->second & 0xffffff00) == 0, "Invalid character code" );
cChar = (sal_Unicode)enc_it->second;
}
else if( (enc_it == pEncoding->end() || enc_it->second == -1) &&
pNonEncoded &&
(nonenc_it = pNonEncoded->find( cChar )) != pNonEncoded->end() )
{
nCurFontID = 0;
// find non encoded glyph
for( std::list< EmbedEncoding >::iterator nec_it = rEmbedFont.m_aExtendedEncodings.begin(); nec_it != rEmbedFont.m_aExtendedEncodings.end(); ++nec_it )
{
if( nec_it->m_aCMap.find( cChar ) != nec_it->m_aCMap.end() )
{
nCurFontID = nec_it->m_nFontID;
cChar = (sal_Unicode)nec_it->m_aCMap[ cChar ];
break;
}
}
if( nCurFontID == 0 ) // new nonencoded glyph
{
if( rEmbedFont.m_aExtendedEncodings.empty() || rEmbedFont.m_aExtendedEncodings.back().m_aEncVector.size() == 255 )
{
rEmbedFont.m_aExtendedEncodings.push_back( EmbedEncoding() );
rEmbedFont.m_aExtendedEncodings.back().m_nFontID = m_nNextFID++;
}
EmbedEncoding& rEncoding = rEmbedFont.m_aExtendedEncodings.back();
rEncoding.m_aEncVector.push_back( EmbedCode() );
rEncoding.m_aEncVector.back().m_aUnicode = cChar;
rEncoding.m_aEncVector.back().m_aName = nonenc_it->second;
rEncoding.m_aCMap[ cChar ] = (sal_Int8)(rEncoding.m_aEncVector.size()-1);
nCurFontID = rEncoding.m_nFontID;
cChar = (sal_Unicode)rEncoding.m_aCMap[ cChar ];
}
}
else
pEncoding = NULL;
}
if( ! pEncoding )
{
if( cChar & 0xff00 )
{
// some characters can be used by conversion
if( cChar >= 0xf000 && cChar <= 0xf0ff ) // symbol encoding in private use area
cChar -= 0xf000;
else
{
String aString( cChar);
ByteString aChar( aString, RTL_TEXTENCODING_MS_1252 );
cChar = ((sal_Unicode)aChar.GetChar( 0 )) & 0x00ff;
}
}
}
pMappedGlyphs[ i ] = (sal_Int8)cChar;
pMappedFontObjects[ i ] = nCurFontID;
}
}
}
void PDFWriterImpl::drawRelief( SalLayout& rLayout, const String& rText, bool bTextLines )
{
push( PUSH_ALL );
FontRelief eRelief = m_aCurrentPDFState.m_aFont.GetRelief();
Color aTextColor = m_aCurrentPDFState.m_aFont.GetColor();
Color aTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
Color aReliefColor( COL_LIGHTGRAY );
if( aTextColor == COL_BLACK )
aTextColor = Color( COL_WHITE );
if( aTextLineColor == COL_BLACK )
aTextLineColor = Color( COL_WHITE );
if( aTextColor == COL_WHITE )
aReliefColor = Color( COL_BLACK );
Font aSetFont = m_aCurrentPDFState.m_aFont;
aSetFont.SetRelief( RELIEF_NONE );
aSetFont.SetShadow( FALSE );
aSetFont.SetColor( aReliefColor );
setTextLineColor( aTextLineColor );
setFont( aSetFont );
long nOff = 1 + getReferenceDevice()->mnDPIX/300;
if( eRelief == RELIEF_ENGRAVED )
nOff = -nOff;
rLayout.DrawOffset() += Point( nOff, nOff );
updateGraphicsState();
drawLayout( rLayout, rText, bTextLines );
rLayout.DrawOffset() -= Point( nOff, nOff );
setTextLineColor( aTextLineColor );
aSetFont.SetColor( aTextColor );
setFont( aSetFont );
updateGraphicsState();
drawLayout( rLayout, rText, bTextLines );
// clean up the mess
pop();
}
void PDFWriterImpl::drawShadow( SalLayout& rLayout, const String& rText, bool bTextLines )
{
Font aSaveFont = m_aCurrentPDFState.m_aFont;
Color aSaveTextLineColor = m_aCurrentPDFState.m_aTextLineColor;
Font& rFont = m_aCurrentPDFState.m_aFont;
if( rFont.GetColor() == Color( COL_BLACK ) || rFont.GetColor().GetLuminance() < 8 )
rFont.SetColor( Color( COL_LIGHTGRAY ) );
else
rFont.SetColor( Color( COL_BLACK ) );
rFont.SetShadow( FALSE );
rFont.SetOutline( FALSE );
setFont( rFont );
setTextLineColor( rFont.GetColor() );
updateGraphicsState();
long nOff = 1 + ((m_pReferenceDevice->mpFontEntry->mnLineHeight-24)/24);
if( rFont.IsOutline() )
nOff++;
rLayout.DrawBase() += Point( nOff, nOff );
drawLayout( rLayout, rText, bTextLines );
rLayout.DrawBase() -= Point( nOff, nOff );
setFont( aSaveFont );
setTextLineColor( aSaveTextLineColor );
updateGraphicsState();
}
void PDFWriterImpl::drawLayout( SalLayout& rLayout, const String& rText, bool bTextLines )
{
// relief takes precedence over shadow (see outdev3.cxx)
if( m_aCurrentPDFState.m_aFont.GetRelief() != RELIEF_NONE )
{
drawRelief( rLayout, rText, bTextLines );
return;
}
else if( m_aCurrentPDFState.m_aFont.IsShadow() )
drawShadow( rLayout, rText, bTextLines );
OStringBuffer aLine( 512 );
const int nMaxGlyphs = 256;
sal_Int32 pGlyphs[nMaxGlyphs];
sal_uInt8 pMappedGlyphs[nMaxGlyphs];
sal_Int32 pMappedFontObjects[nMaxGlyphs];
sal_Unicode pUnicodes[nMaxGlyphs];
int pCharPosAry[nMaxGlyphs];
sal_Int32 nAdvanceWidths[nMaxGlyphs];
ImplFontData* pFallbackFonts[nMaxGlyphs];
sal_Int32 *pAdvanceWidths = m_aCurrentPDFState.m_aFont.IsVertical() ? nAdvanceWidths : NULL;
sal_Int32 nGlyphFlags[nMaxGlyphs];
int nGlyphs;
int nIndex = 0;
Point aCurPos, aLastPos(0, 0), aCumulativePos(0,0), aGlyphPos;
bool bFirst = true, bWasYChange = false;
int nMinCharPos = 0, nMaxCharPos = rText.Len()-1;
double fXScale = 1.0;
double fSkew = 0.0;
sal_Int32 nFontHeight = m_pReferenceDevice->mpFontEntry->maFontSelData.mnHeight;
TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlign();
// transform font height back to current units
// note: the layout calculates in outdevs device pixel !!
nFontHeight = m_pReferenceDevice->ImplDevicePixelToLogicHeight( nFontHeight );
if( m_aCurrentPDFState.m_aFont.GetWidth() )
{
Font aFont( m_aCurrentPDFState.m_aFont );
aFont.SetWidth( 0 );
FontMetric aMetric = m_pReferenceDevice->GetFontMetric( aFont );
if( aMetric.GetWidth() != m_aCurrentPDFState.m_aFont.GetWidth() )
{
fXScale =
(double)m_aCurrentPDFState.m_aFont.GetWidth() /
(double)aMetric.GetWidth();
}
// force state before GetFontMetric
m_pReferenceDevice->ImplNewFont();
}
// perform artificial italics if necessary
if( ( m_aCurrentPDFState.m_aFont.GetItalic() == ITALIC_NORMAL ||
m_aCurrentPDFState.m_aFont.GetItalic() == ITALIC_OBLIQUE ) &&
!( m_pReferenceDevice->mpFontEntry->maFontSelData.mpFontData->GetSlant() == ITALIC_NORMAL ||
m_pReferenceDevice->mpFontEntry->maFontSelData.mpFontData->GetSlant() == ITALIC_OBLIQUE )
)
{
fSkew = M_PI/12.0;
}
// if the mapmode is distorted we need to adjust for that also
if( m_aCurrentPDFState.m_aMapMode.GetScaleX() != m_aCurrentPDFState.m_aMapMode.GetScaleY() )
{
fXScale *= double(m_aCurrentPDFState.m_aMapMode.GetScaleX()) / double(m_aCurrentPDFState.m_aMapMode.GetScaleY());
}
int nAngle = m_aCurrentPDFState.m_aFont.GetOrientation();
// normalize angles
while( nAngle < 0 )
nAngle += 3600;
nAngle = nAngle % 3600;
double fAngle = (double)nAngle * M_PI / 1800.0;
Matrix3 aRotScale;
aRotScale.scale( fXScale, 1.0 );
if( fAngle != 0.0 )
aRotScale.rotate( -fAngle );
bool bPop = false;
bool bABold = false;
// artificial bold necessary ?
if( m_pReferenceDevice->mpFontEntry->maFontSelData.mpFontData->GetWeight() <= WEIGHT_MEDIUM &&
m_pReferenceDevice->mpFontEntry->maFontSelData.GetWeight() > WEIGHT_MEDIUM )
{
if( ! bPop )
aLine.append( "q " );
bPop = true;
bABold = true;
}
// setup text colors (if necessary)
Color aStrokeColor( COL_TRANSPARENT );
Color aNonStrokeColor( COL_TRANSPARENT );
if( m_aCurrentPDFState.m_aFont.IsOutline() )
{
aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
aNonStrokeColor = Color( COL_WHITE );
}
else
aNonStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
if( bABold )
aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor();
if( aStrokeColor != Color( COL_TRANSPARENT ) && aStrokeColor != m_aCurrentPDFState.m_aLineColor )
{
if( ! bPop )
aLine.append( "q " );
bPop = true;
appendStrokingColor( aStrokeColor, aLine );
aLine.append( "\n" );
}
if( aNonStrokeColor != Color( COL_TRANSPARENT ) && aNonStrokeColor != m_aCurrentPDFState.m_aFillColor )
{
if( ! bPop )
aLine.append( "q " );
bPop = true;
appendNonStrokingColor( aNonStrokeColor, aLine );
aLine.append( "\n" );
}
// begin text object
aLine.append( "BT\n" );
// outline attribute ?
if( m_aCurrentPDFState.m_aFont.IsOutline() || bABold )
{
// set correct text mode, set stroke width
aLine.append( "2 Tr " ); // fill, then stroke
if( m_aCurrentPDFState.m_aFont.IsOutline() )
{
// unclear what to do in case of outline and artificial bold
// for the time being outline wins
aLine.append( "0.25 w \n" );
}
else
{
double fW = (double)m_aCurrentPDFState.m_aFont.GetHeight() / 30.0;
m_aPages.back().appendMappedLength( fW, aLine );
aLine.append ( " w\n" );
}
}
FontMetric aRefDevFontMetric = m_pReferenceDevice->GetFontMetric();
sal_Int32 nLastMappedFont = -1;
while( (nGlyphs = rLayout.GetNextGlyphs( nMaxGlyphs, pGlyphs, aCurPos, nIndex, pAdvanceWidths, pCharPosAry )) != 0 )
{
bWasYChange = (aGlyphPos.Y() != aCurPos.Y());
aGlyphPos = aCurPos;
// back transformation to current coordinate system
aCurPos = m_pReferenceDevice->PixelToLogic( aCurPos );
Point aOffset;
if ( eAlign == ALIGN_BOTTOM )
aOffset.Y() -= aRefDevFontMetric.GetDescent();
else if ( eAlign == ALIGN_TOP )
aOffset.Y() += aRefDevFontMetric.GetAscent();
if( aOffset.X() || aOffset.Y() )
{
aOffset = aRotScale.transform( aOffset );
aCurPos += aOffset;
}
for( int i = 0; i < nGlyphs; i++ )
{
if( pGlyphs[i] & GF_FONTMASK )
pFallbackFonts[i] = ((MultiSalLayout&)rLayout).GetFallbackFontData((pGlyphs[i] & GF_FONTMASK) >> GF_FONTSHIFT);
else
pFallbackFonts[i] = NULL;
nGlyphFlags[i] = (pGlyphs[i] & GF_FLAGMASK);
#ifndef WNT
// #104930# workaround for Win32 bug: the glyph ids are actually
// Unicodes for vertical fonts because Win32 does not return
// the correct glyph ids; this is indicated by GF_ISCHAR which is
// needed in SalGraphics::CreateFontSubset to convert the Unicodes
// to vertical glyph ids. Doing this here on a per character
// basis would be a major performance hit.
pGlyphs[i] &= GF_IDXMASK;
#endif
if( pCharPosAry[i] >= nMinCharPos && pCharPosAry[i] <= nMaxCharPos )
pUnicodes[i] = rText.GetChar( sal::static_int_cast<xub_StrLen>(pCharPosAry[i]) );
else
pUnicodes[i] = 0;
// note: in case of ctl one character may result
// in multiple glyphs. The current SalLayout
// implementations set -1 then to indicate that no direct
// mapping is possible
}
registerGlyphs( nGlyphs, pGlyphs, pUnicodes, pMappedGlyphs, pMappedFontObjects, pFallbackFonts );
if( pAdvanceWidths )
{
// have to emit each glyph on its own
long nXOffset = 0;
for( int n = 0; n < nGlyphs; n++ )
{
double fDeltaAngle = 0.0;
double fYScale = 1.0;
double fTempXScale = fXScale;
double fSkewB = fSkew;
double fSkewA = 0.0;
Point aDeltaPos;
if( ( nGlyphFlags[n] & GF_ROTMASK ) == GF_ROTL )
{
fDeltaAngle = M_PI/2.0;
aDeltaPos.X() = aRefDevFontMetric.GetAscent();
aDeltaPos.Y() = (int)((double)m_pReferenceDevice->GetFontMetric().GetDescent() * fXScale);
fYScale = fXScale;
fTempXScale = 1.0;
fSkewA = -fSkewB;
fSkewB = 0.0;
}
else if( ( nGlyphFlags[n] & GF_ROTMASK ) == GF_ROTR )
{
fDeltaAngle = -M_PI/2.0;
aDeltaPos.X() = (int)((double)aRefDevFontMetric.GetDescent()*fXScale);
aDeltaPos.Y() = -aRefDevFontMetric.GetAscent();
fYScale = fXScale;
fTempXScale = 1.0;
fSkewA = fSkewB;
fSkewB = 0.0;
}
aDeltaPos += (m_pReferenceDevice->PixelToLogic( Point( (int)((double)nXOffset/fXScale)/rLayout.GetUnitsPerPixel(), 0 ) ) - m_pReferenceDevice->PixelToLogic( Point() ) );
nXOffset += pAdvanceWidths[n];
if( ! pGlyphs[n] )
continue;
aDeltaPos = aRotScale.transform( aDeltaPos );
Matrix3 aMat;
if( fSkewB != 0.0 || fSkewA != 0.0 )
aMat.skew( fSkewA, fSkewB );
aMat.scale( fTempXScale, fYScale );
aMat.rotate( fAngle+fDeltaAngle );
aMat.translate( aCurPos.X()+aDeltaPos.X(), aCurPos.Y()+aDeltaPos.Y() );
aMat.append( m_aPages.back(), aLine );
aLine.append( " Tm" );
if( nLastMappedFont != pMappedFontObjects[n] )
{
nLastMappedFont = pMappedFontObjects[n];
aLine.append( " /F" );
aLine.append( pMappedFontObjects[n] );
aLine.append( ' ' );
m_aPages.back().appendMappedLength( nFontHeight, aLine, true );
aLine.append( " Tf" );
}
aLine.append( " <" );
appendHex( (sal_Int8)pMappedGlyphs[n], aLine );
aLine.append( "> Tj\n" );
}
}
else // normal case
{
// optimize use of Td vs. Tm
if( fAngle == 0.0 && fXScale == 1.0 && ( !bFirst || fSkew == 0.0 ) )
{
if( bFirst )
{
m_aPages.back().appendPoint( aCurPos, aLine, false, &aCumulativePos );
bFirst = false;
aLastPos = aCurPos;
}
else
{
sal_Int32 nDiffL = 0;
Point aDiff = aCurPos - aLastPos;
m_aPages.back().appendMappedLength( (sal_Int32)aDiff.X(), aLine, false, &nDiffL );
aCumulativePos.X() += nDiffL;
aLine.append( ' ' );
if( bWasYChange )
{
m_aPages.back().appendMappedLength( (sal_Int32)aDiff.Y(), aLine, true, &nDiffL );
aCumulativePos.Y() += nDiffL;
}
else
{
aLine.append( '0' );
}
// back project last position to catch rounding errors
Point aBackPos = lcl_convert( m_aMapMode,
m_aGraphicsStack.front().m_aMapMode,
getReferenceDevice(),
aCumulativePos
);
// catch rounding error in back projection on Y axis;
// else the back projection can produce a sinuous text baseline
if( ! bWasYChange )
aBackPos.Y() = aLastPos.Y();
aLastPos = aBackPos;
}
aLine.append( " Td " );
}
else
{
Matrix3 aMat;
if( fSkew != 0.0 )
aMat.skew( 0.0, fSkew );
aMat.scale( fXScale, 1.0 );
aMat.rotate( fAngle );
aMat.translate( aCurPos.X(), aCurPos.Y() );
aMat.append( m_aPages.back(), aLine, &aCumulativePos );
aLine.append( " Tm\n" );
aLastPos = aCurPos;
bFirst = false;
}
int nLast = 0;
while( nLast < nGlyphs )
{
while( ! pGlyphs[nLast] && nLast < nGlyphs )
nLast++;
if( nLast >= nGlyphs )
break;
int nNext = nLast+1;
while( nNext < nGlyphs && pMappedFontObjects[ nNext ] == pMappedFontObjects[nLast] && pGlyphs[nNext] )
nNext++;
if( nLastMappedFont != pMappedFontObjects[nLast] )
{
aLine.append( "/F" );
aLine.append( pMappedFontObjects[nLast] );
aLine.append( ' ' );
m_aPages.back().appendMappedLength( nFontHeight, aLine, true );
aLine.append( " Tf " );
nLastMappedFont = pMappedFontObjects[nLast];
}
aLine.append( "<" );
for( int i = nLast; i < nNext; i++ )
{
appendHex( (sal_Int8)pMappedGlyphs[i], aLine );
if( i && (i % 35) == 0 )
aLine.append( "\n" );
}
aLine.append( "> Tj\n" );
nLast = nNext;
}
}
}
// end textobject
aLine.append( "ET\n" );
if( bPop )
aLine.append( "Q\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
// draw eventual textlines
FontStrikeout eStrikeout = m_aCurrentPDFState.m_aFont.GetStrikeout();
FontUnderline eUnderline = m_aCurrentPDFState.m_aFont.GetUnderline();
if( bTextLines &&
(
( eUnderline != UNDERLINE_NONE && eUnderline != UNDERLINE_DONTKNOW ) ||
( eStrikeout != STRIKEOUT_NONE && eStrikeout != STRIKEOUT_DONTKNOW )
)
)
{
BOOL bUnderlineAbove = OutputDevice::ImplIsUnderlineAbove( m_aCurrentPDFState.m_aFont );
if( m_aCurrentPDFState.m_aFont.IsWordLineMode() )
{
Point aPos, aStartPt;
sal_Int32 nWidth = 0, nAdvance=0;
for( int nStart = 0;;)
{
sal_Int32 nGlyphIndex;
if( !rLayout.GetNextGlyphs( 1, &nGlyphIndex, aPos, nStart, &nAdvance ) )
break;
if( !rLayout.IsSpacingGlyph( nGlyphIndex ) )
{
if( !nWidth )
aStartPt = aPos;
nWidth += nAdvance;
}
else if( nWidth > 0 )
{
drawTextLine( m_pReferenceDevice->PixelToLogic( aStartPt ),
m_pReferenceDevice->ImplDevicePixelToLogicWidth( nWidth ),
eStrikeout, eUnderline, bUnderlineAbove );
nWidth = 0;
}
}
if( nWidth > 0 )
{
drawTextLine( m_pReferenceDevice->PixelToLogic( aStartPt ),
m_pReferenceDevice->ImplDevicePixelToLogicWidth( nWidth ),
eStrikeout, eUnderline, bUnderlineAbove );
}
}
else
{
Point aStartPt = rLayout.GetDrawPosition();
int nWidth = rLayout.GetTextWidth() / rLayout.GetUnitsPerPixel();
drawTextLine( m_pReferenceDevice->PixelToLogic( aStartPt ),
m_pReferenceDevice->ImplDevicePixelToLogicWidth( nWidth ),
eStrikeout, eUnderline, bUnderlineAbove );
}
}
// write eventual emphasis marks
if( m_aCurrentPDFState.m_aFont.GetEmphasisMark() & EMPHASISMARK_STYLE )
{
PolyPolygon aEmphPoly;
Rectangle aEmphRect1;
Rectangle aEmphRect2;
long nEmphYOff;
long nEmphWidth;
long nEmphHeight;
BOOL bEmphPolyLine;
FontEmphasisMark nEmphMark;
push( PUSH_ALL );
aLine.setLength( 0 );
aLine.append( "q\n" );
nEmphMark = m_pReferenceDevice->ImplGetEmphasisMarkStyle( m_aCurrentPDFState.m_aFont );
if ( nEmphMark & EMPHASISMARK_POS_BELOW )
nEmphHeight = m_pReferenceDevice->mnEmphasisDescent;
else
nEmphHeight = m_pReferenceDevice->mnEmphasisAscent;
m_pReferenceDevice->ImplGetEmphasisMark( aEmphPoly,
bEmphPolyLine,
aEmphRect1,
aEmphRect2,
nEmphYOff,
nEmphWidth,
nEmphMark,
m_pReferenceDevice->ImplDevicePixelToLogicWidth(nEmphHeight),
m_pReferenceDevice->mpFontEntry->mnOrientation );
if ( bEmphPolyLine )
{
setLineColor( m_aCurrentPDFState.m_aFont.GetColor() );
setFillColor( Color( COL_TRANSPARENT ) );
}
else
{
setFillColor( m_aCurrentPDFState.m_aFont.GetColor() );
setLineColor( Color( COL_TRANSPARENT ) );
}
writeBuffer( aLine.getStr(), aLine.getLength() );
Point aOffset = Point(0,0);
if ( nEmphMark & EMPHASISMARK_POS_BELOW )
aOffset.Y() += m_pReferenceDevice->mpFontEntry->maMetric.mnDescent + nEmphYOff;
else
aOffset.Y() -= m_pReferenceDevice->mpFontEntry->maMetric.mnAscent + nEmphYOff;
long nEmphWidth2 = nEmphWidth / 2;
long nEmphHeight2 = nEmphHeight / 2;
aOffset += Point( nEmphWidth2, nEmphHeight2 );
if ( eAlign == ALIGN_BOTTOM )
aOffset.Y() -= m_pReferenceDevice->mpFontEntry->maMetric.mnDescent;
else if ( eAlign == ALIGN_TOP )
aOffset.Y() += m_pReferenceDevice->mpFontEntry->maMetric.mnAscent;
for( int nStart = 0;;)
{
Point aPos;
sal_Int32 nGlyphIndex, nAdvance;
if( !rLayout.GetNextGlyphs( 1, &nGlyphIndex, aPos, nStart, &nAdvance ) )
break;
if( !rLayout.IsSpacingGlyph( nGlyphIndex ) )
{
Point aAdjOffset = aOffset;
aAdjOffset.X() += (nAdvance - nEmphWidth) / 2;
aAdjOffset = aRotScale.transform( aAdjOffset );
aAdjOffset -= Point( nEmphWidth2, nEmphHeight2 );
aPos += aAdjOffset;
aPos = m_pReferenceDevice->PixelToLogic( aPos );
drawEmphasisMark( aPos.X(), aPos.Y(),
aEmphPoly, bEmphPolyLine,
aEmphRect1, aEmphRect2 );
}
}
writeBuffer( "Q\n", 2 );
pop();
}
}
void PDFWriterImpl::drawEmphasisMark( long nX, long nY,
const PolyPolygon& rPolyPoly, BOOL bPolyLine,
const Rectangle& rRect1, const Rectangle& rRect2 )
{
// TODO: pass nWidth as width of this mark
// long nWidth = 0;
if ( rPolyPoly.Count() )
{
if ( bPolyLine )
{
Polygon aPoly = rPolyPoly.GetObject( 0 );
aPoly.Move( nX, nY );
drawPolyLine( aPoly );
}
else
{
PolyPolygon aPolyPoly = rPolyPoly;
aPolyPoly.Move( nX, nY );
drawPolyPolygon( aPolyPoly );
}
}
if ( !rRect1.IsEmpty() )
{
Rectangle aRect( Point( nX+rRect1.Left(),
nY+rRect1.Top() ), rRect1.GetSize() );
drawRectangle( aRect );
}
if ( !rRect2.IsEmpty() )
{
Rectangle aRect( Point( nX+rRect2.Left(),
nY+rRect2.Top() ), rRect2.GetSize() );
drawRectangle( aRect );
}
}
void PDFWriterImpl::drawText( const Point& rPos, const String& rText, xub_StrLen nIndex, xub_StrLen nLen, bool bTextLines )
{
MARK( "drawText" );
updateGraphicsState();
// get a layout from the OuputDevice's SalGraphics
// this also enforces font substitution and sets the font on SalGraphics
SalLayout* pLayout = m_pReferenceDevice->ImplLayout( rText, nIndex, nLen, rPos );
if( pLayout )
{
drawLayout( *pLayout, rText, bTextLines );
pLayout->Release();
}
}
void PDFWriterImpl::drawTextArray( const Point& rPos, const String& rText, const sal_Int32* pDXArray, xub_StrLen nIndex, xub_StrLen nLen, bool bTextLines )
{
MARK( "drawText with array" );
updateGraphicsState();
// get a layout from the OuputDevice's SalGraphics
// this also enforces font substitution and sets the font on SalGraphics
SalLayout* pLayout = m_pReferenceDevice->ImplLayout( rText, nIndex, nLen, rPos, 0, pDXArray );
if( pLayout )
{
drawLayout( *pLayout, rText, bTextLines );
pLayout->Release();
}
}
void PDFWriterImpl::drawStretchText( const Point& rPos, ULONG nWidth, const String& rText, xub_StrLen nIndex, xub_StrLen nLen, bool bTextLines )
{
MARK( "drawStretchText" );
updateGraphicsState();
// get a layout from the OuputDevice's SalGraphics
// this also enforces font substitution and sets the font on SalGraphics
SalLayout* pLayout = m_pReferenceDevice->ImplLayout( rText, nIndex, nLen, rPos, nWidth );
if( pLayout )
{
drawLayout( *pLayout, rText, bTextLines );
pLayout->Release();
}
}
void PDFWriterImpl::drawText( const Rectangle& rRect, const String& rOrigStr, USHORT nStyle, bool bTextLines )
{
long nWidth = rRect.GetWidth();
long nHeight = rRect.GetHeight();
if ( nWidth <= 0 || nHeight <= 0 )
return;
MARK( "drawText with rectangle" );
updateGraphicsState();
// clip with rectangle
OStringBuffer aLine;
aLine.append( "q " );
m_aPages.back().appendRect( rRect, aLine );
aLine.append( " W* n\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
// if disabled text is needed, put in here
Point aPos = rRect.TopLeft();
long nTextHeight = m_pReferenceDevice->GetTextHeight();
xub_StrLen nMnemonicPos = STRING_NOTFOUND;
String aStr = rOrigStr;
if ( nStyle & TEXT_DRAW_MNEMONIC )
aStr = m_pReferenceDevice->GetNonMnemonicString( aStr, nMnemonicPos );
// multiline text
if ( nStyle & TEXT_DRAW_MULTILINE )
{
XubString aLastLine;
ImplMultiTextLineInfo aMultiLineInfo;
ImplTextLineInfo* pLineInfo;
long nMaxTextWidth;
xub_StrLen i;
xub_StrLen nLines;
xub_StrLen nFormatLines;
if ( nTextHeight )
{
nMaxTextWidth = m_pReferenceDevice->ImplGetTextLines( aMultiLineInfo, nWidth, aStr, nStyle );
nLines = (xub_StrLen)(nHeight/nTextHeight);
nFormatLines = aMultiLineInfo.Count();
if ( !nLines )
nLines = 1;
if ( nFormatLines > nLines )
{
if ( nStyle & TEXT_DRAW_ENDELLIPSIS )
{
// handle last line
nFormatLines = nLines-1;
pLineInfo = aMultiLineInfo.GetLine( nFormatLines );
aLastLine = aStr.Copy( pLineInfo->GetIndex() );
aLastLine.ConvertLineEnd( LINEEND_LF );
// replace line feed by space
xub_StrLen nLastLineLen = aLastLine.Len();
for ( i = 0; i < nLastLineLen; i++ )
{
if ( aLastLine.GetChar( i ) == _LF )
aLastLine.SetChar( i, ' ' );
}
aLastLine = m_pReferenceDevice->GetEllipsisString( aLastLine, nWidth, nStyle );
nStyle &= ~(TEXT_DRAW_VCENTER | TEXT_DRAW_BOTTOM);
nStyle |= TEXT_DRAW_TOP;
}
}
// vertical alignment
if ( nStyle & TEXT_DRAW_BOTTOM )
aPos.Y() += nHeight-(nFormatLines*nTextHeight);
else if ( nStyle & TEXT_DRAW_VCENTER )
aPos.Y() += (nHeight-(nFormatLines*nTextHeight))/2;
// draw all lines excluding the last
for ( i = 0; i < nFormatLines; i++ )
{
pLineInfo = aMultiLineInfo.GetLine( i );
if ( nStyle & TEXT_DRAW_RIGHT )
aPos.X() += nWidth-pLineInfo->GetWidth();
else if ( nStyle & TEXT_DRAW_CENTER )
aPos.X() += (nWidth-pLineInfo->GetWidth())/2;
xub_StrLen nIndex = pLineInfo->GetIndex();
xub_StrLen nLineLen = pLineInfo->GetLen();
drawText( aPos, aStr, nIndex, nLineLen, bTextLines );
// mnemonics should not appear in documents,
// if the need arises, put them in here
aPos.Y() += nTextHeight;
aPos.X() = rRect.Left();
}
// output last line left adjusted since it was shortened
if ( aLastLine.Len() )
drawText( aPos, aLastLine, 0, STRING_LEN, bTextLines );
}
}
else
{
long nTextWidth = m_pReferenceDevice->GetTextWidth( aStr );
// Evt. Text kuerzen
if ( nTextWidth > nWidth )
{
if ( nStyle & (TEXT_DRAW_ENDELLIPSIS | TEXT_DRAW_PATHELLIPSIS | TEXT_DRAW_NEWSELLIPSIS) )
{
aStr = m_pReferenceDevice->GetEllipsisString( aStr, nWidth, nStyle );
nStyle &= ~(TEXT_DRAW_CENTER | TEXT_DRAW_RIGHT);
nStyle |= TEXT_DRAW_LEFT;
nTextWidth = m_pReferenceDevice->GetTextWidth( aStr );
}
}
// vertical alignment
if ( nStyle & TEXT_DRAW_RIGHT )
aPos.X() += nWidth-nTextWidth;
else if ( nStyle & TEXT_DRAW_CENTER )
aPos.X() += (nWidth-nTextWidth)/2;
if ( nStyle & TEXT_DRAW_BOTTOM )
aPos.Y() += nHeight-nTextHeight;
else if ( nStyle & TEXT_DRAW_VCENTER )
aPos.Y() += (nHeight-nTextHeight)/2;
// mnemonics should be inserted here if the need arises
// draw the actual text
drawText( aPos, aStr, 0, STRING_LEN, bTextLines );
}
// reset clip region to original value
aLine.setLength( 0 );
aLine.append( "Q\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
}
void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop )
{
MARK( "drawLine" );
updateGraphicsState();
if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) )
return;
OStringBuffer aLine;
m_aPages.back().appendPoint( rStart, aLine );
aLine.append( " m " );
m_aPages.back().appendPoint( rStop, aLine );
aLine.append( " l S\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
}
void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo )
{
MARK( "drawLine with LineInfo" );
updateGraphicsState();
if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) )
return;
if( rInfo.GetStyle() == LINE_SOLID && rInfo.GetWidth() < 2 )
{
drawLine( rStart, rStop );
return;
}
OStringBuffer aLine;
aLine.append( "q " );
if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
{
m_aPages.back().appendPoint( rStart, aLine );
aLine.append( " m " );
m_aPages.back().appendPoint( rStop, aLine );
aLine.append( " l S Q\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
}
else
{
PDFWriter::ExtLineInfo aInfo;
convertLineInfoToExtLineInfo( rInfo, aInfo );
Point aPolyPoints[2] = { rStart, rStop };
Polygon aPoly( 2, aPolyPoints );
drawPolyLine( aPoly, aInfo );
}
}
void PDFWriterImpl::drawWaveLine( const Point& rStart, const Point& rStop, sal_Int32 nDelta, sal_Int32 nLineWidth )
{
Point aDiff( rStop-rStart );
double fLen = sqrt( (double)(aDiff.X()*aDiff.X() + aDiff.Y()*aDiff.Y()) );
if( fLen < 1.0 )
return;
MARK( "drawWaveLine" );
updateGraphicsState();
if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) )
return;
OStringBuffer aLine( 512 );
aLine.append( "q " );
m_aPages.back().appendMappedLength( nLineWidth, aLine, true );
aLine.append( " w " );
appendDouble( (double)aDiff.X()/fLen, aLine );
aLine.append( ' ' );
appendDouble( -(double)aDiff.Y()/fLen, aLine );
aLine.append( ' ' );
appendDouble( (double)aDiff.Y()/fLen, aLine );
aLine.append( ' ' );
appendDouble( (double)aDiff.X()/fLen, aLine );
aLine.append( ' ' );
m_aPages.back().appendPoint( rStart, aLine );
aLine.append( " cm " );
m_aPages.back().appendWaveLine( (sal_Int32)fLen, 0, nDelta, aLine );
aLine.append( "Q\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
}
#define WCONV( x ) m_pReferenceDevice->ImplDevicePixelToLogicWidth( x )
#define HCONV( x ) m_pReferenceDevice->ImplDevicePixelToLogicHeight( x )
void PDFWriterImpl::drawTextLine( const Point& rPos, long nWidth, FontStrikeout eStrikeout, FontUnderline eUnderline, bool bUnderlineAbove )
{
if ( !nWidth ||
( ((eStrikeout == STRIKEOUT_NONE)||(eStrikeout == STRIKEOUT_DONTKNOW)) &&
((eUnderline == UNDERLINE_NONE)||(eUnderline == UNDERLINE_DONTKNOW)) ) )
return;
MARK( "drawTextLine" );
updateGraphicsState();
// note: units in pFontEntry are ref device pixel
ImplFontEntry* pFontEntry = m_pReferenceDevice->mpFontEntry;
Color aUnderlineColor = m_aCurrentPDFState.m_aTextLineColor;
Color aStrikeoutColor = m_aCurrentPDFState.m_aFont.GetColor();
long nLineHeight = 0;
long nLinePos = 0;
long nLinePos2 = 0;
bool bNormalLines = true;
if ( bNormalLines &&
((eStrikeout == STRIKEOUT_SLASH) || (eStrikeout == STRIKEOUT_X)) )
{
String aStrikeoutChar = String::CreateFromAscii( eStrikeout == STRIKEOUT_SLASH ? "/" : "X" );
String aStrikeout = aStrikeoutChar;
while( m_pReferenceDevice->GetTextWidth( aStrikeout ) < nWidth )
aStrikeout.Append( aStrikeout );
// do not get broader than nWidth modulo 1 character
while( m_pReferenceDevice->GetTextWidth( aStrikeout ) >= nWidth )
aStrikeout.Erase( 0, 1 );
aStrikeout.Append( aStrikeoutChar );
BOOL bShadow = m_aCurrentPDFState.m_aFont.IsShadow();
if( bShadow )
{
Font aFont = m_aCurrentPDFState.m_aFont;
aFont.SetShadow( FALSE );
setFont( aFont );
updateGraphicsState();
}
// strikeout string is left aligned non-CTL text
ULONG nOrigTLM = m_pReferenceDevice->GetLayoutMode();
m_pReferenceDevice->SetLayoutMode( TEXT_LAYOUT_BIDI_STRONG|TEXT_LAYOUT_COMPLEX_DISABLED );
drawText( rPos, aStrikeout, 0, aStrikeout.Len(), false );
m_pReferenceDevice->SetLayoutMode( nOrigTLM );
if( bShadow )
{
Font aFont = m_aCurrentPDFState.m_aFont;
aFont.SetShadow( TRUE );
setFont( aFont );
updateGraphicsState();
}
switch( eUnderline )
{
case UNDERLINE_NONE:
case UNDERLINE_DONTKNOW:
case UNDERLINE_SMALLWAVE:
case UNDERLINE_WAVE:
case UNDERLINE_DOUBLEWAVE:
case UNDERLINE_BOLDWAVE:
{
bNormalLines = FALSE;
}
break;
default:
{
; // No gcc warning
}
}
}
Point aPos( rPos );
TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlign();
if( eAlign == ALIGN_TOP )
aPos.Y() += HCONV( pFontEntry->maMetric.mnAscent );
else if( eAlign == ALIGN_BOTTOM )
aPos.Y() -= HCONV( pFontEntry->maMetric.mnDescent );
OStringBuffer aLine( 512 );
// save GS
aLine.append( "q " );
// rotate and translate matrix
double fAngle = (double)m_aCurrentPDFState.m_aFont.GetOrientation() * M_PI / 1800.0;
Matrix3 aMat;
aMat.rotate( fAngle );
aMat.translate( aPos.X(), aPos.Y() );
aMat.append( m_aPages.back(), aLine );
aLine.append( " cm\n" );
if ( aUnderlineColor.GetTransparency() != 0 )
aUnderlineColor = aStrikeoutColor;
if ( (eUnderline == UNDERLINE_SMALLWAVE) ||
(eUnderline == UNDERLINE_WAVE) ||
(eUnderline == UNDERLINE_DOUBLEWAVE) ||
(eUnderline == UNDERLINE_BOLDWAVE) )
{
appendStrokingColor( aUnderlineColor, aLine );
aLine.append( "\n" );
if ( bUnderlineAbove )
{
if ( !pFontEntry->maMetric.mnAboveWUnderlineSize )
m_pReferenceDevice->ImplInitAboveTextLineSize();
nLinePos = HCONV( pFontEntry->maMetric.mnAboveWUnderlineOffset );
nLineHeight = HCONV( pFontEntry->maMetric.mnAboveWUnderlineSize );
}
else
{
if ( !pFontEntry->maMetric.mnWUnderlineSize )
m_pReferenceDevice->ImplInitTextLineSize();
nLinePos = HCONV( pFontEntry->maMetric.mnWUnderlineOffset );
nLineHeight = HCONV( pFontEntry->maMetric.mnWUnderlineSize );
}
if ( (eUnderline == UNDERLINE_SMALLWAVE) &&
(nLineHeight > 3) )
nLineHeight = 3;
long nLineWidth = getReferenceDevice()->mnDPIX/450;
if( ! nLineWidth )
nLineWidth = 1;
if ( eUnderline == UNDERLINE_BOLDWAVE )
nLineWidth = 3*nLineWidth;
m_aPages.back().appendMappedLength( (sal_Int32)nLineWidth, aLine );
aLine.append( " w " );
if ( eUnderline == UNDERLINE_DOUBLEWAVE )
{
long nOrgLineHeight = nLineHeight;
nLineHeight /= 3;
if ( nLineHeight < 2 )
{
if ( nOrgLineHeight > 1 )
nLineHeight = 2;
else
nLineHeight = 1;
}
long nLineDY = nOrgLineHeight-(nLineHeight*2);
if ( nLineDY < nLineWidth )
nLineDY = nLineWidth;
long nLineDY2 = nLineDY/2;
if ( !nLineDY2 )
nLineDY2 = 1;
nLinePos -= nLineWidth-nLineDY2;
m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
nLinePos += nLineWidth+nLineDY;
m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine );
}
else
{
if( eUnderline != UNDERLINE_BOLDWAVE )
nLinePos -= nLineWidth/2;
m_aPages.back().appendWaveLine( nWidth, -nLinePos, nLineHeight, aLine );
}
if ( (eStrikeout == STRIKEOUT_NONE) ||
(eStrikeout == STRIKEOUT_DONTKNOW) )
bNormalLines = false;
}
if ( bNormalLines )
{
if ( eUnderline > UNDERLINE_BOLDWAVE )
eUnderline = UNDERLINE_SINGLE;
if ( (eUnderline == UNDERLINE_SINGLE) ||
(eUnderline == UNDERLINE_DOTTED) ||
(eUnderline == UNDERLINE_DASH) ||
(eUnderline == UNDERLINE_LONGDASH) ||
(eUnderline == UNDERLINE_DASHDOT) ||
(eUnderline == UNDERLINE_DASHDOTDOT) )
{
if ( bUnderlineAbove )
{
if ( !pFontEntry->maMetric.mnAboveUnderlineSize )
m_pReferenceDevice->ImplInitAboveTextLineSize();
nLineHeight = HCONV( pFontEntry->maMetric.mnAboveUnderlineSize );
nLinePos = HCONV( pFontEntry->maMetric.mnAboveUnderlineOffset );
}
else
{
if ( !pFontEntry->maMetric.mnUnderlineSize )
m_pReferenceDevice->ImplInitTextLineSize();
nLineHeight = HCONV( pFontEntry->maMetric.mnUnderlineSize );
nLinePos = HCONV( pFontEntry->maMetric.mnUnderlineOffset );
}
}
else if ( (eUnderline == UNDERLINE_BOLD) ||
(eUnderline == UNDERLINE_BOLDDOTTED) ||
(eUnderline == UNDERLINE_BOLDDASH) ||
(eUnderline == UNDERLINE_BOLDLONGDASH) ||
(eUnderline == UNDERLINE_BOLDDASHDOT) ||
(eUnderline == UNDERLINE_BOLDDASHDOTDOT) )
{
if ( bUnderlineAbove )
{
if ( !pFontEntry->maMetric.mnAboveBUnderlineSize )
m_pReferenceDevice->ImplInitAboveTextLineSize();
nLineHeight = HCONV( pFontEntry->maMetric.mnAboveBUnderlineSize );
nLinePos = HCONV( pFontEntry->maMetric.mnAboveBUnderlineOffset );
}
else
{
if ( !pFontEntry->maMetric.mnBUnderlineSize )
m_pReferenceDevice->ImplInitTextLineSize();
nLineHeight = HCONV( pFontEntry->maMetric.mnBUnderlineSize );
nLinePos = HCONV( pFontEntry->maMetric.mnBUnderlineOffset );
nLinePos += nLineHeight/2;
}
}
else if ( eUnderline == UNDERLINE_DOUBLE )
{
if ( bUnderlineAbove )
{
if ( !pFontEntry->maMetric.mnAboveDUnderlineSize )
m_pReferenceDevice->ImplInitAboveTextLineSize();
nLineHeight = HCONV( pFontEntry->maMetric.mnAboveDUnderlineSize );
nLinePos = HCONV( pFontEntry->maMetric.mnAboveDUnderlineOffset1 );
nLinePos2 = HCONV( pFontEntry->maMetric.mnAboveDUnderlineOffset2 );
}
else
{
if ( !pFontEntry->maMetric.mnDUnderlineSize )
m_pReferenceDevice->ImplInitTextLineSize();
nLineHeight = HCONV( pFontEntry->maMetric.mnDUnderlineSize );
nLinePos = HCONV( pFontEntry->maMetric.mnDUnderlineOffset1 );
nLinePos2 = HCONV( pFontEntry->maMetric.mnDUnderlineOffset2 );
}
}
else
nLineHeight = 0;
if ( nLineHeight )
{
m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine, true );
aLine.append( " w " );
appendStrokingColor( aUnderlineColor, aLine );
aLine.append( "\n" );
if ( (eUnderline == UNDERLINE_DOTTED) ||
(eUnderline == UNDERLINE_BOLDDOTTED) )
{
aLine.append( "[ " );
m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine, false );
aLine.append( " ] 0 d\n" );
}
else if ( (eUnderline == UNDERLINE_DASH) ||
(eUnderline == UNDERLINE_LONGDASH) ||
(eUnderline == UNDERLINE_BOLDDASH) ||
(eUnderline == UNDERLINE_BOLDLONGDASH) )
{
sal_Int32 nDashLength = 4*nLineHeight;
sal_Int32 nVoidLength = 2*nLineHeight;
if ( ( eUnderline == UNDERLINE_LONGDASH ) || ( eUnderline == UNDERLINE_BOLDLONGDASH ) )
nDashLength = 8*nLineHeight;
aLine.append( "[ " );
m_aPages.back().appendMappedLength( nDashLength, aLine, false );
aLine.append( ' ' );
m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
aLine.append( " ] 0 d\n" );
}
else if ( (eUnderline == UNDERLINE_DASHDOT) ||
(eUnderline == UNDERLINE_BOLDDASHDOT) )
{
sal_Int32 nDashLength = 4*nLineHeight;
sal_Int32 nVoidLength = 2*nLineHeight;
aLine.append( "[ " );
m_aPages.back().appendMappedLength( nDashLength, aLine, false );
aLine.append( ' ' );
m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
aLine.append( ' ' );
m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine, false );
aLine.append( ' ' );
m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
aLine.append( " ] 0 d\n" );
}
else if ( (eUnderline == UNDERLINE_DASHDOTDOT) ||
(eUnderline == UNDERLINE_BOLDDASHDOTDOT) )
{
sal_Int32 nDashLength = 4*nLineHeight;
sal_Int32 nVoidLength = 2*nLineHeight;
aLine.append( "[ " );
m_aPages.back().appendMappedLength( nDashLength, aLine, false );
aLine.append( ' ' );
m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
aLine.append( ' ' );
m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine, false );
aLine.append( ' ' );
m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
aLine.append( ' ' );
m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine, false );
aLine.append( ' ' );
m_aPages.back().appendMappedLength( nVoidLength, aLine, false );
aLine.append( " ] 0 d\n" );
}
aLine.append( "0 " );
m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos), aLine, true );
aLine.append( " m " );
m_aPages.back().appendMappedLength( (sal_Int32)nWidth, aLine, false );
aLine.append( ' ' );
m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos), aLine, true );
aLine.append( " l S\n" );
if ( eUnderline == UNDERLINE_DOUBLE )
{
aLine.append( "0 " );
m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos2-nLineHeight), aLine, true );
aLine.append( " m " );
m_aPages.back().appendMappedLength( (sal_Int32)nWidth, aLine, false );
aLine.append( ' ' );
m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos2-nLineHeight), aLine, true );
aLine.append( " l S\n" );
}
}
if ( eStrikeout > STRIKEOUT_X )
eStrikeout = STRIKEOUT_SINGLE;
if ( eStrikeout == STRIKEOUT_SINGLE )
{
if ( !pFontEntry->maMetric.mnStrikeoutSize )
m_pReferenceDevice->ImplInitTextLineSize();
nLineHeight = HCONV( pFontEntry->maMetric.mnStrikeoutSize );
nLinePos = HCONV( pFontEntry->maMetric.mnStrikeoutOffset );
}
else if ( eStrikeout == STRIKEOUT_BOLD )
{
if ( !pFontEntry->maMetric.mnBStrikeoutSize )
m_pReferenceDevice->ImplInitTextLineSize();
nLineHeight = HCONV( pFontEntry->maMetric.mnBStrikeoutSize );
nLinePos = HCONV( pFontEntry->maMetric.mnBStrikeoutOffset );
}
else if ( eStrikeout == STRIKEOUT_DOUBLE )
{
if ( !pFontEntry->maMetric.mnDStrikeoutSize )
m_pReferenceDevice->ImplInitTextLineSize();
nLineHeight = HCONV( pFontEntry->maMetric.mnDStrikeoutSize );
nLinePos = HCONV( pFontEntry->maMetric.mnDStrikeoutOffset1 );
nLinePos2 = HCONV( pFontEntry->maMetric.mnDStrikeoutOffset2 );
}
else
nLineHeight = 0;
if ( nLineHeight )
{
m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine, true );
aLine.append( " w " );
appendStrokingColor( aStrikeoutColor, aLine );
aLine.append( "\n" );
aLine.append( "0 " );
m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos), aLine, true );
aLine.append( " m " );
m_aPages.back().appendMappedLength( (sal_Int32)nWidth, aLine, true );
aLine.append( ' ' );
m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos), aLine, true );
aLine.append( " l S\n" );
if ( eStrikeout == STRIKEOUT_DOUBLE )
{
aLine.append( "0 " );
m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos2-nLineHeight), aLine, true );
aLine.append( " m " );
m_aPages.back().appendMappedLength( (sal_Int32)nWidth, aLine, true );
aLine.append( ' ' );
m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos2-nLineHeight), aLine, true );
aLine.append( " l S\n" );
}
}
}
aLine.append( "Q\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
}
void PDFWriterImpl::drawPolygon( const Polygon& rPoly )
{
MARK( "drawPolygon" );
updateGraphicsState();
if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) &&
m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) )
return;
int nPoints = rPoly.GetSize();
OStringBuffer aLine( 20 * nPoints );
m_aPages.back().appendPolygon( rPoly, aLine );
if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) &&
m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) )
aLine.append( "B*\n" );
else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) )
aLine.append( "S\n" );
else
aLine.append( "f*\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
}
void PDFWriterImpl::drawPolyPolygon( const PolyPolygon& rPolyPoly )
{
MARK( "drawPolyPolygon" );
updateGraphicsState();
if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) &&
m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) )
return;
int nPolygons = rPolyPoly.Count();
OStringBuffer aLine( 40 * nPolygons );
m_aPages.back().appendPolyPolygon( rPolyPoly, aLine );
if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) &&
m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) )
aLine.append( "B*\n" );
else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) )
aLine.append( "S\n" );
else
aLine.append( "f*\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
}
void PDFWriterImpl::drawTransparent( const PolyPolygon& rPolyPoly, sal_uInt32 nTransparentPercent )
{
DBG_ASSERT( nTransparentPercent <= 100, "invalid alpha value" );
nTransparentPercent = nTransparentPercent % 100;
MARK( "drawTransparent" );
updateGraphicsState();
if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) &&
m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) )
return;
if( m_aContext.Version < PDFWriter::PDF_1_4 )
{
drawPolyPolygon( rPolyPoly );
return;
}
// create XObject
m_aTransparentObjects.push_back( TransparencyEmit() );
// FIXME: polygons with beziers may yield incorrect bound rect
m_aTransparentObjects.back().m_aBoundRect = rPolyPoly.GetBoundRect();
// convert rectangle to default user space
m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
m_aTransparentObjects.back().m_nObject = createObject();
m_aTransparentObjects.back().m_nExtGStateObject = createObject();
m_aTransparentObjects.back().m_fAlpha = (double)(100-nTransparentPercent) / 100.0;
m_aTransparentObjects.back().m_pContentStream = new SvMemoryStream( 256, 256 );
// create XObject's content stream
OStringBuffer aContent( 256 );
m_aPages.back().appendPolyPolygon( rPolyPoly, aContent );
if( m_aCurrentPDFState.m_aLineColor != Color( COL_TRANSPARENT ) &&
m_aCurrentPDFState.m_aFillColor != Color( COL_TRANSPARENT ) )
aContent.append( " B*\n" );
else if( m_aCurrentPDFState.m_aLineColor != Color( COL_TRANSPARENT ) )
aContent.append( " S\n" );
else
aContent.append( " f*\n" );
m_aTransparentObjects.back().m_pContentStream->Write( aContent.getStr(), aContent.getLength() );
OStringBuffer aLine( 80 );
// insert XObject
aLine.append( "q /EGS" );
aLine.append( m_aTransparentObjects.back().m_nExtGStateObject );
aLine.append( " gs " );
aLine.append( "/Tr" );
aLine.append( m_aTransparentObjects.back().m_nObject );
aLine.append( " Do Q\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
}
void PDFWriterImpl::beginRedirect( SvStream* pStream, const Rectangle& rTargetRect )
{
m_aOutputStreams.push_front( StreamRedirect() );
m_aOutputStreams.front().m_pStream = pStream;
m_aOutputStreams.front().m_aMapMode = m_aMapMode;
if( !rTargetRect.IsEmpty() )
{
Rectangle aTargetRect = lcl_convert( m_aGraphicsStack.front().m_aMapMode,
m_aMapMode,
getReferenceDevice(),
rTargetRect );
Point aDelta = aTargetRect.BottomLeft();
sal_Int32 nPageHeight = m_aPages[m_nCurrentPage].getHeight();
aDelta.Y() = aTargetRect.Bottom() - pointToPixel(nPageHeight);
m_aMapMode.SetOrigin( m_aMapMode.GetOrigin() + aDelta );
}
// setup graphics state for independent object stream
// force reemitting colors
m_aCurrentPDFState.m_aLineColor = Color( COL_TRANSPARENT );
m_aCurrentPDFState.m_aFillColor = Color( COL_TRANSPARENT );
}
SvStream* PDFWriterImpl::endRedirect()
{
SvStream* pStream = NULL;
if( m_aOutputStreams.begin() != m_aOutputStreams.end() )
{
pStream = m_aOutputStreams.front().m_pStream;
m_aMapMode = m_aOutputStreams.front().m_aMapMode;
m_aOutputStreams.pop_front();
}
// force reemitting colors
m_aCurrentPDFState.m_aLineColor = Color( COL_TRANSPARENT );
m_aCurrentPDFState.m_aFillColor = Color( COL_TRANSPARENT );
return pStream;
}
void PDFWriterImpl::beginTransparencyGroup()
{
updateGraphicsState();
if( m_aContext.Version >= PDFWriter::PDF_1_4 )
beginRedirect( new SvMemoryStream( 1024, 1024 ), Rectangle() );
}
void PDFWriterImpl::endTransparencyGroup( const Rectangle& rBoundingBox, sal_uInt32 nTransparentPercent )
{
DBG_ASSERT( nTransparentPercent <= 100, "invalid alpha value" );
nTransparentPercent = nTransparentPercent % 100;
if( m_aContext.Version >= PDFWriter::PDF_1_4 )
{
// create XObject
m_aTransparentObjects.push_back( TransparencyEmit() );
m_aTransparentObjects.back().m_aBoundRect = rBoundingBox;
// convert rectangle to default user space
m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
m_aTransparentObjects.back().m_nObject = createObject();
m_aTransparentObjects.back().m_fAlpha = (double)(100-nTransparentPercent) / 100.0;
// get XObject's content stream
m_aTransparentObjects.back().m_pContentStream = static_cast<SvMemoryStream*>(endRedirect());
m_aTransparentObjects.back().m_nExtGStateObject = createObject();
OStringBuffer aLine( 80 );
// insert XObject
aLine.append( "q /EGS" );
aLine.append( m_aTransparentObjects.back().m_nExtGStateObject );
aLine.append( " gs /Tr" );
aLine.append( m_aTransparentObjects.back().m_nObject );
aLine.append( " Do Q\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
}
}
void PDFWriterImpl::endTransparencyGroup( const Rectangle& rBoundingBox, const Bitmap& rAlphaMask )
{
if( m_aContext.Version >= PDFWriter::PDF_1_4 )
{
// create XObject
m_aTransparentObjects.push_back( TransparencyEmit() );
m_aTransparentObjects.back().m_aBoundRect = rBoundingBox;
// convert rectangle to default user space
m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect );
m_aTransparentObjects.back().m_nObject = createObject();
m_aTransparentObjects.back().m_fAlpha = 0.0;
// get XObject's content stream
m_aTransparentObjects.back().m_pContentStream = static_cast<SvMemoryStream*>(endRedirect());
m_aTransparentObjects.back().m_nExtGStateObject = createObject();
// draw soft mask
beginRedirect( new SvMemoryStream( 1024, 1024 ), Rectangle() );
drawBitmap( rBoundingBox.TopLeft(), rBoundingBox.GetSize(), rAlphaMask );
m_aTransparentObjects.back().m_pSoftMaskStream = static_cast<SvMemoryStream*>(endRedirect());
OStringBuffer aLine( 80 );
// insert XObject
aLine.append( "q /EGS" );
aLine.append( m_aTransparentObjects.back().m_nExtGStateObject );
aLine.append( " gs /Tr" );
aLine.append( m_aTransparentObjects.back().m_nObject );
aLine.append( " Do Q\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
}
}
void PDFWriterImpl::drawRectangle( const Rectangle& rRect )
{
MARK( "drawRectangle" );
updateGraphicsState();
if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) &&
m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) )
return;
OStringBuffer aLine( 40 );
m_aPages.back().appendRect( rRect, aLine );
if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) &&
m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) )
aLine.append( " B*\n" );
else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) )
aLine.append( " S\n" );
else
aLine.append( " f*\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
}
void PDFWriterImpl::drawRectangle( const Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound )
{
MARK( "drawRectangle with rounded edges" );
if( !nHorzRound && !nVertRound )
drawRectangle( rRect );
updateGraphicsState();
if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) &&
m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) )
return;
if( nHorzRound > (sal_uInt32)rRect.GetWidth()/2 )
nHorzRound = rRect.GetWidth()/2;
if( nVertRound > (sal_uInt32)rRect.GetHeight()/2 )
nVertRound = rRect.GetHeight()/2;
Point aPoints[16];
const double kappa = 0.5522847498;
const sal_uInt32 kx = (sal_uInt32)((kappa*(double)nHorzRound)+0.5);
const sal_uInt32 ky = (sal_uInt32)((kappa*(double)nVertRound)+0.5);
aPoints[1] = Point( rRect.TopLeft().X() + nHorzRound, rRect.TopLeft().Y() );
aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() );
aPoints[2] = Point( rRect.TopRight().X()+1 - nHorzRound, aPoints[1].Y() );
aPoints[3] = Point( aPoints[2].X()+kx, aPoints[2].Y() );
aPoints[5] = Point( rRect.TopRight().X()+1, rRect.TopRight().Y()+nVertRound );
aPoints[4] = Point( aPoints[5].X(), aPoints[5].Y()-ky );
aPoints[6] = Point( aPoints[5].X(), rRect.BottomRight().Y()+1 - nVertRound );
aPoints[7] = Point( aPoints[6].X(), aPoints[6].Y()+ky );
aPoints[9] = Point( rRect.BottomRight().X()+1-nHorzRound, rRect.BottomRight().Y()+1 );
aPoints[8] = Point( aPoints[9].X()+kx, aPoints[9].Y() );
aPoints[10] = Point( rRect.BottomLeft().X() + nHorzRound, aPoints[9].Y() );
aPoints[11] = Point( aPoints[10].X()-kx, aPoints[10].Y() );
aPoints[13] = Point( rRect.BottomLeft().X(), rRect.BottomLeft().Y()+1-nVertRound );
aPoints[12] = Point( aPoints[13].X(), aPoints[13].Y()+ky );
aPoints[14] = Point( rRect.TopLeft().X(), rRect.TopLeft().Y()+nVertRound );
aPoints[15] = Point( aPoints[14].X(), aPoints[14].Y()-ky );
OStringBuffer aLine( 80 );
m_aPages.back().appendPoint( aPoints[1], aLine );
aLine.append( " m " );
m_aPages.back().appendPoint( aPoints[2], aLine );
aLine.append( " l " );
m_aPages.back().appendPoint( aPoints[3], aLine );
aLine.append( ' ' );
m_aPages.back().appendPoint( aPoints[4], aLine );
aLine.append( ' ' );
m_aPages.back().appendPoint( aPoints[5], aLine );
aLine.append( " c\n" );
m_aPages.back().appendPoint( aPoints[6], aLine );
aLine.append( " l " );
m_aPages.back().appendPoint( aPoints[7], aLine );
aLine.append( ' ' );
m_aPages.back().appendPoint( aPoints[8], aLine );
aLine.append( ' ' );
m_aPages.back().appendPoint( aPoints[9], aLine );
aLine.append( " c\n" );
m_aPages.back().appendPoint( aPoints[10], aLine );
aLine.append( " l " );
m_aPages.back().appendPoint( aPoints[11], aLine );
aLine.append( ' ' );
m_aPages.back().appendPoint( aPoints[12], aLine );
aLine.append( ' ' );
m_aPages.back().appendPoint( aPoints[13], aLine );
aLine.append( " c\n" );
m_aPages.back().appendPoint( aPoints[14], aLine );
aLine.append( " l " );
m_aPages.back().appendPoint( aPoints[15], aLine );
aLine.append( ' ' );
m_aPages.back().appendPoint( aPoints[0], aLine );
aLine.append( ' ' );
m_aPages.back().appendPoint( aPoints[1], aLine );
aLine.append( " c " );
if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) &&
m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) )
aLine.append( "b*\n" );
else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) )
aLine.append( "s\n" );
else
aLine.append( "f*\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
}
void PDFWriterImpl::drawEllipse( const Rectangle& rRect )
{
MARK( "drawEllipse" );
updateGraphicsState();
if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) &&
m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) )
return;
Point aPoints[12];
const double kappa = 0.5522847498;
const sal_uInt32 kx = (sal_uInt32)((kappa*(double)rRect.GetWidth()/2.0)+0.5);
const sal_uInt32 ky = (sal_uInt32)((kappa*(double)rRect.GetHeight()/2.0)+0.5);
aPoints[1] = Point( rRect.TopLeft().X() + rRect.GetWidth()/2, rRect.TopLeft().Y() );
aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() );
aPoints[2] = Point( aPoints[1].X() + kx, aPoints[1].Y() );
aPoints[4] = Point( rRect.TopRight().X()+1, rRect.TopRight().Y() + rRect.GetHeight()/2 );
aPoints[3] = Point( aPoints[4].X(), aPoints[4].Y() - ky );
aPoints[5] = Point( aPoints[4].X(), aPoints[4].Y() + ky );
aPoints[7] = Point( rRect.BottomLeft().X() + rRect.GetWidth()/2, rRect.BottomLeft().Y()+1 );
aPoints[6] = Point( aPoints[7].X() + kx, aPoints[7].Y() );
aPoints[8] = Point( aPoints[7].X() - kx, aPoints[7].Y() );
aPoints[10] = Point( rRect.TopLeft().X(), rRect.TopLeft().Y() + rRect.GetHeight()/2 );
aPoints[9] = Point( aPoints[10].X(), aPoints[10].Y() + ky );
aPoints[11] = Point( aPoints[10].X(), aPoints[10].Y() - ky );
OStringBuffer aLine( 80 );
m_aPages.back().appendPoint( aPoints[1], aLine );
aLine.append( " m " );
m_aPages.back().appendPoint( aPoints[2], aLine );
aLine.append( ' ' );
m_aPages.back().appendPoint( aPoints[3], aLine );
aLine.append( ' ' );
m_aPages.back().appendPoint( aPoints[4], aLine );
aLine.append( " c\n" );
m_aPages.back().appendPoint( aPoints[5], aLine );
aLine.append( ' ' );
m_aPages.back().appendPoint( aPoints[6], aLine );
aLine.append( ' ' );
m_aPages.back().appendPoint( aPoints[7], aLine );
aLine.append( " c\n" );
m_aPages.back().appendPoint( aPoints[8], aLine );
aLine.append( ' ' );
m_aPages.back().appendPoint( aPoints[9], aLine );
aLine.append( ' ' );
m_aPages.back().appendPoint( aPoints[10], aLine );
aLine.append( " c\n" );
m_aPages.back().appendPoint( aPoints[11], aLine );
aLine.append( ' ' );
m_aPages.back().appendPoint( aPoints[0], aLine );
aLine.append( ' ' );
m_aPages.back().appendPoint( aPoints[1], aLine );
aLine.append( " c " );
if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) &&
m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) )
aLine.append( "b*\n" );
else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) )
aLine.append( "s\n" );
else
aLine.append( "f*\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
}
static double calcAngle( const Rectangle& rRect, const Point& rPoint )
{
Point aOrigin((rRect.Left()+rRect.Right()+1)/2,
(rRect.Top()+rRect.Bottom()+1)/2);
Point aPoint = rPoint - aOrigin;
double fX = (double)aPoint.X();
double fY = (double)-aPoint.Y();
if( rRect.GetWidth() > rRect.GetHeight() )
fY = fY*((double)rRect.GetWidth()/(double)rRect.GetHeight());
else if( rRect.GetHeight() > rRect.GetWidth() )
fX = fX*((double)rRect.GetHeight()/(double)rRect.GetWidth());
return atan2( fY, fX );
}
void PDFWriterImpl::drawArc( const Rectangle& rRect, const Point& rStart, const Point& rStop, bool bWithPie, bool bWithChord )
{
MARK( "drawArc" );
updateGraphicsState();
if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) &&
m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) )
return;
// calculate start and stop angles
double fStartAngle = calcAngle( rRect, rStart );
double fStopAngle = calcAngle( rRect, rStop );
while( fStopAngle < fStartAngle )
fStopAngle += 2.0*M_PI;
int nFragments = (int)((fStopAngle-fStartAngle)/(M_PI/2.0))+1;
double fFragmentDelta = (fStopAngle-fStartAngle)/(double)nFragments;
double kappa = fabs( 4.0 * (1.0-cos(fFragmentDelta/2.0))/sin(fFragmentDelta/2.0) / 3.0);
double halfWidth = (double)rRect.GetWidth()/2.0;
double halfHeight = (double)rRect.GetHeight()/2.0;
Point aCenter( (rRect.Left()+rRect.Right()+1)/2,
(rRect.Top()+rRect.Bottom()+1)/2 );
OStringBuffer aLine( 30*nFragments );
Point aPoint( (int)(halfWidth * cos(fStartAngle) ),
-(int)(halfHeight * sin(fStartAngle) ) );
aPoint += aCenter;
m_aPages.back().appendPoint( aPoint, aLine );
aLine.append( " m " );
for( int i = 0; i < nFragments; i++ )
{
double fStartFragment = fStartAngle + (double)i*fFragmentDelta;
double fStopFragment = fStartFragment + fFragmentDelta;
aPoint = Point( (int)(halfWidth * (cos(fStartFragment) - kappa*sin(fStartFragment) ) ),
-(int)(halfHeight * (sin(fStartFragment) + kappa*cos(fStartFragment) ) ) );
aPoint += aCenter;
m_aPages.back().appendPoint( aPoint, aLine );
aLine.append( ' ' );
aPoint = Point( (int)(halfWidth * (cos(fStopFragment) + kappa*sin(fStopFragment) ) ),
-(int)(halfHeight * (sin(fStopFragment) - kappa*cos(fStopFragment) ) ) );
aPoint += aCenter;
m_aPages.back().appendPoint( aPoint, aLine );
aLine.append( ' ' );
aPoint = Point( (int)(halfWidth * cos(fStopFragment) ),
-(int)(halfHeight * sin(fStopFragment) ) );
aPoint += aCenter;
m_aPages.back().appendPoint( aPoint, aLine );
aLine.append( " c\n" );
}
if( bWithChord || bWithPie )
{
if( bWithPie )
{
m_aPages.back().appendPoint( aCenter, aLine );
aLine.append( " l " );
}
aLine.append( "h " );
}
if( ! bWithChord && ! bWithPie )
aLine.append( "S\n" );
else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) &&
m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) )
aLine.append( "B*\n" );
else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) )
aLine.append( "S\n" );
else
aLine.append( "f*\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
}
void PDFWriterImpl::drawPolyLine( const Polygon& rPoly )
{
MARK( "drawPolyLine" );
USHORT nPoints = rPoly.GetSize();
if( nPoints < 2 )
return;
updateGraphicsState();
if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) )
return;
OStringBuffer aLine( 20 * nPoints );
m_aPages.back().appendPolygon( rPoly, aLine, rPoly[0] == rPoly[nPoints-1] );
aLine.append( "S\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
}
void PDFWriterImpl::drawPolyLine( const Polygon& rPoly, const LineInfo& rInfo )
{
MARK( "drawPolyLine with LineInfo" );
updateGraphicsState();
if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) )
return;
OStringBuffer aLine;
aLine.append( "q " );
if( m_aPages.back().appendLineInfo( rInfo, aLine ) )
{
writeBuffer( aLine.getStr(), aLine.getLength() );
drawPolyLine( rPoly );
writeBuffer( "Q\n", 2 );
}
else
{
PDFWriter::ExtLineInfo aInfo;
convertLineInfoToExtLineInfo( rInfo, aInfo );
drawPolyLine( rPoly, aInfo );
}
}
void PDFWriterImpl::convertLineInfoToExtLineInfo( const LineInfo& rIn, PDFWriter::ExtLineInfo& rOut )
{
DBG_ASSERT( rIn.GetStyle() == LINE_DASH, "invalid conversion" );
rOut.m_fLineWidth = rIn.GetWidth();
rOut.m_fTransparency = 0.0;
rOut.m_eCap = PDFWriter::capButt;
rOut.m_eJoin = PDFWriter::joinMiter;
rOut.m_fMiterLimit = 10;
rOut.m_aDashArray.clear();
int nDashes = rIn.GetDashCount();
int nDashLen = rIn.GetDashLen();
int nDistance = rIn.GetDistance();
for( int n = 0; n < nDashes; n++ )
{
rOut.m_aDashArray.push_back( nDashLen );
rOut.m_aDashArray.push_back( nDistance );
}
int nDots = rIn.GetDotCount();
int nDotLen = rIn.GetDotLen();
for( int n = 0; n < nDots; n++ )
{
rOut.m_aDashArray.push_back( nDotLen );
rOut.m_aDashArray.push_back( nDistance );
}
}
void PDFWriterImpl::drawPolyLine( const Polygon& rPoly, const PDFWriter::ExtLineInfo& rInfo )
{
MARK( "drawPolyLine with ExtLineInfo" );
updateGraphicsState();
if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) )
return;
if( rInfo.m_fTransparency >= 1.0 )
return;
if( rInfo.m_fTransparency != 0.0 )
beginTransparencyGroup();
OStringBuffer aLine;
aLine.append( "q " );
m_aPages.back().appendMappedLength( rInfo.m_fLineWidth, aLine );
aLine.append( " w" );
if( rInfo.m_aDashArray.size() < 10 ) // implmentation limit of acrobat reader
{
switch( rInfo.m_eCap )
{
default:
case PDFWriter::capButt: aLine.append( " 0 J" );break;
case PDFWriter::capRound: aLine.append( " 1 J" );break;
case PDFWriter::capSquare: aLine.append( " 2 J" );break;
}
switch( rInfo.m_eJoin )
{
default:
case PDFWriter::joinMiter:
{
double fLimit = rInfo.m_fMiterLimit;
if( rInfo.m_fLineWidth < rInfo.m_fMiterLimit )
fLimit = fLimit / rInfo.m_fLineWidth;
if( fLimit < 1.0 )
fLimit = 1.0;
aLine.append( " 0 j " );
appendDouble( fLimit, aLine );
aLine.append( " M" );
}
break;
case PDFWriter::joinRound: aLine.append( " 1 j" );break;
case PDFWriter::joinBevel: aLine.append( " 2 j" );break;
}
if( rInfo.m_aDashArray.size() > 0 )
{
aLine.append( " [ " );
for( std::vector<double>::const_iterator it = rInfo.m_aDashArray.begin();
it != rInfo.m_aDashArray.end(); ++it )
{
m_aPages.back().appendMappedLength( *it, aLine );
aLine.append( ' ' );
}
aLine.append( "] 0 d" );
}
aLine.append( "\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
drawPolyLine( rPoly );
}
else
{
basegfx::B2DPolygon aPoly( rPoly.getB2DPolygon() );
basegfx::B2DPolyPolygon aPolyPoly( basegfx::tools::applyLineDashing( aPoly, rInfo.m_aDashArray ) );
sal_uInt32 nPolygons = aPolyPoly.count();
for( sal_uInt32 nPoly = 0; nPoly < nPolygons; nPoly++ )
{
aLine.append( (nPoly != 0 && (nPoly & 7) == 0) ? "\n" : " " );
aPoly = aPolyPoly.getB2DPolygon( nPoly );
DBG_ASSERT( aPoly.count() != 2, "erroneous sub polygon" );
basegfx::B2DPoint aStart = aPoly.getB2DPoint( 0 );
basegfx::B2DPoint aStop = aPoly.getB2DPoint( 1 );
m_aPages.back().appendPoint( Point( FRound(aStart.getX()),
FRound(aStart.getY()) ),
aLine );
aLine.append( " m " );
m_aPages.back().appendPoint( Point( FRound(aStop.getX()),
FRound(aStop.getY()) ),
aLine );
aLine.append( " l" );
}
aLine.append( " S " );
writeBuffer( aLine.getStr(), aLine.getLength() );
}
writeBuffer( "Q\n", 2 );
if( rInfo.m_fTransparency != 0.0 )
{
// FIXME: actually this may be incorrect with bezier polygons
Rectangle aBoundRect( rPoly.GetBoundRect() );
// avoid clipping with thick lines
if( rInfo.m_fLineWidth > 0.0 )
{
sal_Int32 nLW = sal_Int32(rInfo.m_fLineWidth);
aBoundRect.Top() -= nLW;
aBoundRect.Left() -= nLW;
aBoundRect.Right() += nLW;
aBoundRect.Bottom() += nLW;
}
endTransparencyGroup( aBoundRect, (USHORT)(100.0*rInfo.m_fTransparency) );
}
}
void PDFWriterImpl::drawPixel( const Point& rPoint, const Color& rColor )
{
MARK( "drawPixel" );
Color aColor = ( rColor == Color( COL_TRANSPARENT ) ? m_aGraphicsStack.front().m_aLineColor : rColor );
if( aColor == Color( COL_TRANSPARENT ) )
return;
// pixels are drawn in line color, so have to set
// the nonstroking color to line color
Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
setFillColor( aColor );
updateGraphicsState();
OStringBuffer aLine( 20 );
m_aPages.back().appendPoint( rPoint, aLine );
aLine.append( ' ' );
appendDouble( 1.0/double(getReferenceDevice()->ImplGetDPIX()), aLine );
aLine.append( ' ' );
appendDouble( 1.0/double(getReferenceDevice()->ImplGetDPIY()), aLine );
aLine.append( " re f\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
setFillColor( aOldFillColor );
}
void PDFWriterImpl::drawPixel( const Polygon& rPoints, const Color* pColors )
{
MARK( "drawPixel with Polygon" );
updateGraphicsState();
if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) && ! pColors )
return;
USHORT nPoints = rPoints.GetSize();
OStringBuffer aLine( nPoints*40 );
aLine.append( "q " );
if( ! pColors )
{
appendNonStrokingColor( m_aGraphicsStack.front().m_aLineColor, aLine );
aLine.append( ' ' );
}
OStringBuffer aPixel(32);
aPixel.append( ' ' );
appendDouble( 1.0/double(getReferenceDevice()->ImplGetDPIX()), aPixel );
aPixel.append( ' ' );
appendDouble( 1.0/double(getReferenceDevice()->ImplGetDPIY()), aPixel );
OString aPixelStr = aPixel.makeStringAndClear();
for( USHORT i = 0; i < nPoints; i++ )
{
if( pColors )
{
if( pColors[i] == Color( COL_TRANSPARENT ) )
continue;
appendNonStrokingColor( pColors[i], aLine );
aLine.append( ' ' );
}
m_aPages.back().appendPoint( rPoints[i], aLine );
aLine.append( aPixelStr );
aLine.append( " re f\n" );
}
aLine.append( "Q\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
}
class AccessReleaser
{
BitmapReadAccess* m_pAccess;
public:
AccessReleaser( BitmapReadAccess* pAccess ) : m_pAccess( pAccess ){}
~AccessReleaser() { delete m_pAccess; }
};
bool PDFWriterImpl::writeTransparentObject( TransparencyEmit& rObject )
{
CHECK_RETURN( updateObject( rObject.m_nObject ) );
bool bFlateFilter = compressStream( rObject.m_pContentStream );
rObject.m_pContentStream->Seek( STREAM_SEEK_TO_END );
ULONG nSize = rObject.m_pContentStream->Tell();
rObject.m_pContentStream->Seek( STREAM_SEEK_TO_BEGIN );
#if OSL_DEBUG_LEVEL > 1
{
OStringBuffer aLine( " PDFWriterImpl::writeTransparentObject" );
emitComment( aLine.getStr() );
}
#endif
OStringBuffer aLine( 512 );
CHECK_RETURN( updateObject( rObject.m_nObject ) );
aLine.append( rObject.m_nObject );
aLine.append( " 0 obj\n"
"<</Type/XObject\n"
"/Subtype/Form\n"
"/BBox[ " );
appendFixedInt( rObject.m_aBoundRect.Left(), aLine );
aLine.append( ' ' );
appendFixedInt( rObject.m_aBoundRect.Top(), aLine );
aLine.append( ' ' );
appendFixedInt( rObject.m_aBoundRect.Right(), aLine );
aLine.append( ' ' );
appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aLine );
aLine.append( " ]\n" );
/* #i42884# the PDF reference recommends that each Form XObject
* should have a resource dict; alas if that is the same object
* as the one of the page it triggers an endless recursion in
* acroread 5 (6 and up have that fixed). Since we have only one
* resource dict anyway, let's use the one from the page by NOT
* emitting a Resources entry.
*/
#if 0
aLine.append( " /Resources " );
aLine.append( getResourceDictObj() );
aLine.append( " 0 R\n" );
#endif
aLine.append( "/Length " );
aLine.append( (sal_Int32)(nSize) );
aLine.append( "\n" );
if( bFlateFilter )
aLine.append( "/Filter/FlateDecode\n" );
aLine.append( ">>\n"
"stream\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
checkAndEnableStreamEncryption( rObject.m_nObject );
CHECK_RETURN( writeBuffer( rObject.m_pContentStream->GetData(), nSize ) );
disableStreamEncryption();
aLine.setLength( 0 );
aLine.append( "\n"
"endstream\n"
"endobj\n\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
// write ExtGState dict for this XObject
aLine.setLength( 0 );
aLine.append( rObject.m_nExtGStateObject );
aLine.append( " 0 obj\n"
"<<" );
if( ! rObject.m_pSoftMaskStream )
{
aLine.append( "/CA " );
appendDouble( rObject.m_fAlpha, aLine );
aLine.append( "\n"
" /ca " );
appendDouble( rObject.m_fAlpha, aLine );
aLine.append( "\n" );
}
else
{
rObject.m_pSoftMaskStream->Seek( STREAM_SEEK_TO_END );
sal_Int32 nMaskSize = (sal_Int32)rObject.m_pSoftMaskStream->Tell();
rObject.m_pSoftMaskStream->Seek( STREAM_SEEK_TO_BEGIN );
sal_Int32 nMaskObject = createObject();
aLine.append( "/SMask<</Type/Mask/S/Luminosity/G " );
aLine.append( nMaskObject );
aLine.append( " 0 R>>\n" );
OStringBuffer aMask;
aMask.append( nMaskObject );
aMask.append( " 0 obj\n"
"<</Type/XObject\n"
"/Subtype/Form\n"
"/BBox[" );
appendFixedInt( rObject.m_aBoundRect.Left(), aMask );
aMask.append( ' ' );
appendFixedInt( rObject.m_aBoundRect.Top(), aMask );
aMask.append( ' ' );
appendFixedInt( rObject.m_aBoundRect.Right(), aMask );
aMask.append( ' ' );
appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aMask );
aMask.append( "]\n" );
/* #i42884# see above */
#if 0
aLine.append( "/Resources " );
aMask.append( getResourceDictObj() );
aMask.append( " 0 R\n" );
#endif
aMask.append( "/Group<</S/Transparency/CS/DeviceRGB>>\n" );
aMask.append( "/Length " );
aMask.append( nMaskSize );
aMask.append( ">>\n"
"stream\n" );
CHECK_RETURN( updateObject( nMaskObject ) );
checkAndEnableStreamEncryption( nMaskObject );
CHECK_RETURN( writeBuffer( aMask.getStr(), aMask.getLength() ) );
CHECK_RETURN( writeBuffer( rObject.m_pSoftMaskStream->GetData(), nMaskSize ) );
disableStreamEncryption();
aMask.setLength( 0 );
aMask.append( "\nendstream\n"
"endobj\n\n" );
CHECK_RETURN( writeBuffer( aMask.getStr(), aMask.getLength() ) );
}
aLine.append( ">>\n"
"endobj\n\n" );
CHECK_RETURN( updateObject( rObject.m_nExtGStateObject ) );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
return true;
}
bool PDFWriterImpl::writeGradientFunction( GradientEmit& rObject )
{
sal_Int32 nFunctionObject = createObject();
CHECK_RETURN( updateObject( nFunctionObject ) );
OutputDevice* pRefDevice = getReferenceDevice();
pRefDevice->Push( PUSH_ALL );
if( rObject.m_aSize.Width() > pRefDevice->GetOutputSizePixel().Width() )
rObject.m_aSize.Width() = pRefDevice->GetOutputSizePixel().Width();
if( rObject.m_aSize.Height() > pRefDevice->GetOutputSizePixel().Height() )
rObject.m_aSize.Height() = pRefDevice->GetOutputSizePixel().Height();
pRefDevice->SetMapMode( MapMode( MAP_PIXEL ) );
pRefDevice->DrawGradient( Rectangle( Point( 0, 0 ), rObject.m_aSize ), rObject.m_aGradient );
Bitmap aSample = pRefDevice->GetBitmap( Point( 0, 0 ), rObject.m_aSize );
BitmapReadAccess* pAccess = aSample.AcquireReadAccess();
AccessReleaser aReleaser( pAccess );
Size aSize = aSample.GetSizePixel();
sal_Int32 nStreamLengthObject = createObject();
#if OSL_DEBUG_LEVEL > 1
{
OStringBuffer aLine( " PDFWriterImpl::writeGradientFunction" );
emitComment( aLine.getStr() );
}
#endif
OStringBuffer aLine( 120 );
aLine.append( nFunctionObject );
aLine.append( " 0 obj\n"
"<</FunctionType 0\n"
"/Domain[ 0 1 0 1 ]\n"
"/Size[ " );
aLine.append( (sal_Int32)aSize.Width() );
aLine.append( ' ' );
aLine.append( (sal_Int32)aSize.Height() );
aLine.append( " ]\n"
"/BitsPerSample 8\n"
"/Range[ 0 1 0 1 0 1 ]\n"
"/Length " );
aLine.append( nStreamLengthObject );
aLine.append( " 0 R\n"
#ifndef DEBUG_DISABLE_PDFCOMPRESSION
"/Filter/FlateDecode"
#endif
">>\n"
"stream\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
sal_uInt64 nStartStreamPos = 0;
CHECK_RETURN( (osl_File_E_None == osl_getFilePos( m_aFile, &nStartStreamPos )) );
checkAndEnableStreamEncryption( nFunctionObject );
beginCompression();
for( int y = 0; y < aSize.Height(); y++ )
{
for( int x = 0; x < aSize.Width(); x++ )
{
sal_uInt8 aCol[3];
BitmapColor aColor = pAccess->GetColor( y, x );
aCol[0] = aColor.GetRed();
aCol[1] = aColor.GetGreen();
aCol[2] = aColor.GetBlue();
CHECK_RETURN( writeBuffer( aCol, 3 ) );
}
}
endCompression();
disableStreamEncryption();
sal_uInt64 nEndStreamPos = 0;
CHECK_RETURN( (osl_File_E_None == osl_getFilePos( m_aFile, &nEndStreamPos )) );
aLine.setLength( 0 );
aLine.append( "\nendstream\nendobj\n\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
// write stream length
CHECK_RETURN( updateObject( nStreamLengthObject ) );
aLine.setLength( 0 );
aLine.append( nStreamLengthObject );
aLine.append( " 0 obj\n" );
aLine.append( (sal_Int64)(nEndStreamPos-nStartStreamPos) );
aLine.append( "\nendobj\n\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
CHECK_RETURN( updateObject( rObject.m_nObject ) );
aLine.setLength( 0 );
aLine.append( rObject.m_nObject );
aLine.append( " 0 obj\n"
"<</ShadingType 1\n"
"/ColorSpace/DeviceRGB\n"
"/AntiAlias true\n"
"/Domain[ 0 1 0 1 ]\n"
"/Matrix[ " );
aLine.append( (sal_Int32)aSize.Width() );
aLine.append( " 0 0 " );
aLine.append( (sal_Int32)aSize.Height() );
aLine.append( " 0 0 ]\n"
"/Function " );
aLine.append( nFunctionObject );
aLine.append( " 0 R\n"
">>\n"
"endobj\n\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
pRefDevice->Pop();
return true;
}
bool PDFWriterImpl::writeJPG( JPGEmit& rObject )
{
CHECK_RETURN( rObject.m_pStream );
CHECK_RETURN( updateObject( rObject.m_nObject ) );
sal_Int32 nLength = 0;
rObject.m_pStream->Seek( STREAM_SEEK_TO_END );
nLength = rObject.m_pStream->Tell();
rObject.m_pStream->Seek( STREAM_SEEK_TO_BEGIN );
sal_Int32 nMaskObject = 0;
if( !!rObject.m_aMask )
{
if( rObject.m_aMask.GetBitCount() == 1 ||
( rObject.m_aMask.GetBitCount() == 8 && m_aContext.Version >= PDFWriter::PDF_1_4 )
)
nMaskObject = createObject();
}
#if OSL_DEBUG_LEVEL > 1
{
OStringBuffer aLine( " PDFWriterImpl::writeJPG" );
emitComment( aLine.getStr() );
}
#endif
OStringBuffer aLine(200);
aLine.append( rObject.m_nObject );
aLine.append( " 0 obj\n"
"<</Type/XObject/Subtype/Image/Width " );
aLine.append( (sal_Int32)rObject.m_aID.m_aPixelSize.Width() );
aLine.append( " /Height " );
aLine.append( (sal_Int32)rObject.m_aID.m_aPixelSize.Height() );
aLine.append( " /BitsPerComponent 8 " );
if( rObject.m_bTrueColor )
aLine.append( "/ColorSpace/DeviceRGB" );
else
aLine.append( "/ColorSpace/DeviceGray" );
aLine.append( "/Filter/DCTDecode/Length " );
aLine.append( nLength );
if( nMaskObject )
{
aLine.append( rObject.m_aMask.GetBitCount() == 1 ? " /Mask " : " /SMask " );
aLine.append( nMaskObject );
aLine.append( " 0 R " );
}
aLine.append( ">>\nstream\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
checkAndEnableStreamEncryption( rObject.m_nObject );
CHECK_RETURN( writeBuffer( rObject.m_pStream->GetData(), nLength ) );
disableStreamEncryption();
aLine.setLength( 0 );
aLine.append( "\nendstream\nendobj\n\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
if( nMaskObject )
{
BitmapEmit aEmit;
aEmit.m_nObject = nMaskObject;
if( rObject.m_aMask.GetBitCount() == 1 )
aEmit.m_aBitmap = BitmapEx( rObject.m_aMask, rObject.m_aMask );
else if( rObject.m_aMask.GetBitCount() == 8 )
aEmit.m_aBitmap = BitmapEx( rObject.m_aMask, AlphaMask( rObject.m_aMask ) );
writeBitmapObject( aEmit, true );
}
return true;
}
bool PDFWriterImpl::writeBitmapObject( BitmapEmit& rObject, bool bMask )
{
CHECK_RETURN( updateObject( rObject.m_nObject ) );
Bitmap aBitmap;
Color aTransparentColor( COL_TRANSPARENT );
bool bWriteMask = false;
if( ! bMask )
{
aBitmap = rObject.m_aBitmap.GetBitmap();
if( rObject.m_aBitmap.IsAlpha() )
{
if( m_aContext.Version >= PDFWriter::PDF_1_4 )
bWriteMask = true;
// else draw without alpha channel
}
else
{
switch( rObject.m_aBitmap.GetTransparentType() )
{
case TRANSPARENT_NONE:
// comes from drawMask function
if( aBitmap.GetBitCount() == 1 && rObject.m_bDrawMask )
bMask = true;
break;
case TRANSPARENT_COLOR:
aTransparentColor = rObject.m_aBitmap.GetTransparentColor();
break;
case TRANSPARENT_BITMAP:
bWriteMask = true;
break;
}
}
}
else
{
if( m_aContext.Version < PDFWriter::PDF_1_4 || ! rObject.m_aBitmap.IsAlpha() )
{
aBitmap = rObject.m_aBitmap.GetMask();
aBitmap.Convert( BMP_CONVERSION_1BIT_THRESHOLD );
DBG_ASSERT( aBitmap.GetBitCount() == 1, "mask conversion failed" );
}
else if( aBitmap.GetBitCount() != 8 )
{
aBitmap = rObject.m_aBitmap.GetAlpha().GetBitmap();
aBitmap.Convert( BMP_CONVERSION_8BIT_GREYS );
DBG_ASSERT( aBitmap.GetBitCount() == 8, "alpha mask conversion failed" );
}
}
BitmapReadAccess* pAccess = aBitmap.AcquireReadAccess();
AccessReleaser aReleaser( pAccess );
bool bTrueColor;
sal_Int32 nBitsPerComponent;
switch( aBitmap.GetBitCount() )
{
case 1:
case 2:
case 4:
case 8:
bTrueColor = false;
nBitsPerComponent = aBitmap.GetBitCount();
break;
default:
bTrueColor = true;
nBitsPerComponent = 8;
break;
}
sal_Int32 nStreamLengthObject = createObject();
sal_Int32 nMaskObject = 0;
#if OSL_DEBUG_LEVEL > 1
{
OStringBuffer aLine( " PDFWriterImpl::writeBitmapObject" );
emitComment( aLine.getStr() );
}
#endif
OStringBuffer aLine(1024);
aLine.append( rObject.m_nObject );
aLine.append( " 0 obj\n"
"<</Type/XObject/Subtype/Image/Width " );
aLine.append( (sal_Int32)aBitmap.GetSizePixel().Width() );
aLine.append( " /Height " );
aLine.append( (sal_Int32)aBitmap.GetSizePixel().Height() );
aLine.append( " /BitsPerComponent " );
aLine.append( nBitsPerComponent );
aLine.append( " /Length " );
aLine.append( nStreamLengthObject );
aLine.append( " 0 R\n" );
#ifndef DEBUG_DISABLE_PDFCOMPRESSION
aLine.append( "/Filter/FlateDecode" );
#endif
if( ! bMask )
{
aLine.append( "/ColorSpace" );
if( bTrueColor )
aLine.append( "/DeviceRGB\n" );
else if( aBitmap.HasGreyPalette() )
{
aLine.append( "/DeviceGray\n" );
if( aBitmap.GetBitCount() == 1 )
{
// #i47395# 1 bit bitmaps occasionally have an inverted grey palette
sal_Int32 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( Color( COL_BLACK ) ) );
DBG_ASSERT( nBlackIndex == 0 || nBlackIndex == 1, "wrong black index" );
if( nBlackIndex == 1 )
aLine.append( "/Decode[1 0]\n" );
}
}
else
{
aLine.append( "[ /Indexed/DeviceRGB " );
aLine.append( (sal_Int32)(pAccess->GetPaletteEntryCount()-1) );
aLine.append( " <\n" );
if( m_aContext.Encrypt )
{
enableStringEncryption( rObject.m_nObject );
//check encryption buffer size
if( checkEncryptionBufferSize( pAccess->GetPaletteEntryCount()*3 ) )
{
int nChar = 0;
//fill the encryption buffer
for( USHORT i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
{
const BitmapColor& rColor = pAccess->GetPaletteColor( i );
m_pEncryptionBuffer[nChar++] = rColor.GetRed();
m_pEncryptionBuffer[nChar++] = rColor.GetGreen();
m_pEncryptionBuffer[nChar++] = rColor.GetBlue();
}
//encrypt the colorspace lookup table
rtl_cipher_encodeARCFOUR( m_aCipher, m_pEncryptionBuffer, nChar, m_pEncryptionBuffer, nChar );
//now queue the data for output
nChar = 0;
for( USHORT i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
{
appendHex(m_pEncryptionBuffer[nChar++], aLine );
appendHex(m_pEncryptionBuffer[nChar++], aLine );
appendHex(m_pEncryptionBuffer[nChar++], aLine );
if( (i+1) & 15 )
aLine.append( ' ' );
else
aLine.append( "\n" );
}
}
}
else //no encryption requested
{
for( USHORT i = 0; i < pAccess->GetPaletteEntryCount(); i++ )
{
const BitmapColor& rColor = pAccess->GetPaletteColor( i );
appendHex( rColor.GetRed(), aLine );
appendHex( rColor.GetGreen(), aLine );
appendHex( rColor.GetBlue(), aLine );
if( (i+1) & 15 )
aLine.append( ' ' );
else
aLine.append( "\n" );
}
}
aLine.append( "> ]\n" );
}
}
else
{
if( aBitmap.GetBitCount() == 1 )
{
aLine.append( " /ImageMask true\n" );
sal_Int32 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( Color( COL_BLACK ) ) );
DBG_ASSERT( nBlackIndex == 0 || nBlackIndex == 1, "wrong black index" );
if( nBlackIndex )
aLine.append( "/Decode[ 1 0 ]\n" );
else
aLine.append( "/Decode[ 0 1 ]\n" );
}
else if( aBitmap.GetBitCount() == 8 )
{
aLine.append( "/ColorSpace/DeviceGray\n"
"/Decode [ 1 0 ]\n" );
}
}
if( ! bMask && m_aContext.Version > PDFWriter::PDF_1_2 )
{
if( bWriteMask )
{
nMaskObject = createObject();
if( rObject.m_aBitmap.IsAlpha() && m_aContext.Version > PDFWriter::PDF_1_3 )
aLine.append( "/SMask " );
else
aLine.append( "/Mask " );
aLine.append( nMaskObject );
aLine.append( " 0 R\n" );
}
else if( aTransparentColor != Color( COL_TRANSPARENT ) )
{
aLine.append( "/Mask[ " );
if( bTrueColor )
{
aLine.append( (sal_Int32)aTransparentColor.GetRed() );
aLine.append( ' ' );
aLine.append( (sal_Int32)aTransparentColor.GetRed() );
aLine.append( ' ' );
aLine.append( (sal_Int32)aTransparentColor.GetGreen() );
aLine.append( ' ' );
aLine.append( (sal_Int32)aTransparentColor.GetGreen() );
aLine.append( ' ' );
aLine.append( (sal_Int32)aTransparentColor.GetBlue() );
aLine.append( ' ' );
aLine.append( (sal_Int32)aTransparentColor.GetBlue() );
}
else
{
sal_Int32 nIndex = pAccess->GetBestPaletteIndex( BitmapColor( aTransparentColor ) );
aLine.append( nIndex );
}
aLine.append( " ]\n" );
}
}
aLine.append( ">>\n"
"stream\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
sal_uInt64 nStartPos = 0;
CHECK_RETURN( (osl_File_E_None == osl_getFilePos( m_aFile, &nStartPos )) );
checkAndEnableStreamEncryption( rObject.m_nObject );
beginCompression();
if( ! bTrueColor || pAccess->GetScanlineFormat() == BMP_FORMAT_24BIT_TC_RGB )
{
const int nScanLineBytes = 1 + ( pAccess->GetBitCount() * ( pAccess->Width() - 1 ) / 8U );
for( int i = 0; i < pAccess->Height(); i++ )
{
CHECK_RETURN( writeBuffer( pAccess->GetScanline( i ), nScanLineBytes ) );
}
}
else
{
const int nScanLineBytes = pAccess->Width()*3;
sal_uInt8 *pCol = (sal_uInt8*)rtl_allocateMemory( nScanLineBytes );
for( int y = 0; y < pAccess->Height(); y++ )
{
for( int x = 0; x < pAccess->Width(); x++ )
{
BitmapColor aColor = pAccess->GetColor( y, x );
pCol[3*x+0] = aColor.GetRed();
pCol[3*x+1] = aColor.GetGreen();
pCol[3*x+2] = aColor.GetBlue();
}
CHECK_RETURN( writeBuffer( pCol, nScanLineBytes ) );
}
rtl_freeMemory( pCol );
}
endCompression();
disableStreamEncryption();
sal_uInt64 nEndPos = 0;
CHECK_RETURN( (osl_File_E_None == osl_getFilePos( m_aFile, &nEndPos )) );
aLine.setLength( 0 );
aLine.append( "\nendstream\nendobj\n\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
CHECK_RETURN( updateObject( nStreamLengthObject ) );
aLine.setLength( 0 );
aLine.append( nStreamLengthObject );
aLine.append( " 0 obj\n" );
aLine.append( (sal_Int64)(nEndPos-nStartPos) );
aLine.append( "\nendobj\n\n" );
CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) );
if( nMaskObject )
{
BitmapEmit aEmit;
aEmit.m_nObject = nMaskObject;
aEmit.m_aBitmap = rObject.m_aBitmap;
return writeBitmapObject( aEmit, true );
}
return true;
}
void PDFWriterImpl::drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const Size& rSizePixel, const Rectangle& rTargetArea, const Bitmap& rMask )
{
MARK( "drawJPGBitmap" );
OStringBuffer aLine( 80 );
updateGraphicsState();
// #i40055# sanity check
if( ! (rTargetArea.GetWidth() && rTargetArea.GetHeight() ) )
return;
if( ! (rSizePixel.Width() && rSizePixel.Height()) )
return;
SvMemoryStream* pStream = new SvMemoryStream;
rDCTData.Seek( 0 );
*pStream << rDCTData;
pStream->Seek( STREAM_SEEK_TO_END );
BitmapID aID;
aID.m_aPixelSize = rSizePixel;
aID.m_nSize = pStream->Tell();
pStream->Seek( STREAM_SEEK_TO_BEGIN );
aID.m_nChecksum = rtl_crc32( 0, pStream->GetData(), aID.m_nSize );
if( ! rMask.IsEmpty() )
aID.m_nMaskChecksum = rMask.GetChecksum();
std::list< JPGEmit >::const_iterator it;
for( it = m_aJPGs.begin(); it != m_aJPGs.end() && ! (aID == it->m_aID); ++it )
;
if( it == m_aJPGs.end() )
{
m_aJPGs.push_front( JPGEmit() );
JPGEmit& rEmit = m_aJPGs.front();
rEmit.m_nObject = createObject();
rEmit.m_aID = aID;
rEmit.m_pStream = pStream;
rEmit.m_bTrueColor = bIsTrueColor;
if( !! rMask && rMask.GetSizePixel() == rSizePixel )
rEmit.m_aMask = rMask;
it = m_aJPGs.begin();
}
else
delete pStream;
aLine.append( "q " );
m_aPages.back().appendMappedLength( (sal_Int32)rTargetArea.GetWidth(), aLine, false );
aLine.append( " 0 0 " );
m_aPages.back().appendMappedLength( (sal_Int32)rTargetArea.GetHeight(), aLine, true );
aLine.append( ' ' );
m_aPages.back().appendPoint( rTargetArea.BottomLeft(), aLine );
aLine.append( " cm\n/Im" );
aLine.append( it->m_nObject );
aLine.append( " Do Q\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
}
void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEmit& rBitmap, const Color& rFillColor )
{
OStringBuffer aLine( 80 );
updateGraphicsState();
aLine.append( "q " );
if( rFillColor != Color( COL_TRANSPARENT ) )
{
appendNonStrokingColor( rFillColor, aLine );
aLine.append( ' ' );
}
m_aPages.back().appendMappedLength( (sal_Int32)rDestSize.Width(), aLine, false );
aLine.append( " 0 0 " );
m_aPages.back().appendMappedLength( (sal_Int32)rDestSize.Height(), aLine, true );
aLine.append( ' ' );
m_aPages.back().appendPoint( rDestPoint + Point( 0, rDestSize.Height()-1 ), aLine );
aLine.append( " cm\n/Im" );
aLine.append( rBitmap.m_nObject );
aLine.append( " Do Q\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
}
const PDFWriterImpl::BitmapEmit& PDFWriterImpl::createBitmapEmit( const BitmapEx& rBitmap, bool bDrawMask )
{
BitmapID aID;
aID.m_aPixelSize = rBitmap.GetSizePixel();
aID.m_nSize = rBitmap.GetBitCount();
aID.m_nChecksum = rBitmap.GetBitmap().GetChecksum();
aID.m_nMaskChecksum = 0;
if( rBitmap.IsAlpha() )
aID.m_nMaskChecksum = rBitmap.GetAlpha().GetChecksum();
else
{
Bitmap aMask = rBitmap.GetMask();
if( ! aMask.IsEmpty() )
aID.m_nMaskChecksum = aMask.GetChecksum();
}
for( std::list< BitmapEmit >::const_iterator it = m_aBitmaps.begin(); it != m_aBitmaps.end(); ++it )
{
if( aID == it->m_aID )
return *it;
}
m_aBitmaps.push_front( BitmapEmit() );
m_aBitmaps.front().m_aID = aID;
m_aBitmaps.front().m_aBitmap = rBitmap;
m_aBitmaps.front().m_nObject = createObject();
m_aBitmaps.front().m_bDrawMask = bDrawMask;
return m_aBitmaps.front();
}
void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap )
{
MARK( "drawBitmap (Bitmap)" );
// #i40055# sanity check
if( ! (rDestSize.Width() && rDestSize.Height()) )
return;
const BitmapEmit& rEmit = createBitmapEmit( BitmapEx( rBitmap ) );
drawBitmap( rDestPoint, rDestSize, rEmit, Color( COL_TRANSPARENT ) );
}
void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEx& rBitmap )
{
MARK( "drawBitmap (BitmapEx)" );
// #i40055# sanity check
if( ! (rDestSize.Width() && rDestSize.Height()) )
return;
const BitmapEmit& rEmit = createBitmapEmit( rBitmap );
drawBitmap( rDestPoint, rDestSize, rEmit, Color( COL_TRANSPARENT ) );
}
void PDFWriterImpl::drawMask( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap, const Color& rFillColor )
{
MARK( "drawMask" );
// #i40055# sanity check
if( ! (rDestSize.Width() && rDestSize.Height()) )
return;
Bitmap aBitmap( rBitmap );
if( aBitmap.GetBitCount() > 1 )
aBitmap.Convert( BMP_CONVERSION_1BIT_THRESHOLD );
DBG_ASSERT( aBitmap.GetBitCount() == 1, "mask conversion failed" );
const BitmapEmit& rEmit = createBitmapEmit( BitmapEx( aBitmap ), true );
drawBitmap( rDestPoint, rDestSize, rEmit, rFillColor );
}
sal_Int32 PDFWriterImpl::createGradient( const Gradient& rGradient, const Size& rSize )
{
Size aPtSize( lcl_convert( m_aGraphicsStack.front().m_aMapMode,
MapMode( MAP_POINT ),
getReferenceDevice(),
rSize ) );
// check if we already have this gradient
for( std::list<GradientEmit>::iterator it = m_aGradients.begin(); it != m_aGradients.end(); ++it )
{
if( it->m_aGradient == rGradient )
{
if( it->m_aSize.Width() < aPtSize.Width() )
it->m_aSize.Width() = aPtSize.Width();
if( it->m_aSize.Height() <= aPtSize.Height() )
it->m_aSize.Height() = aPtSize.Height();
return it->m_nObject;
}
}
m_aGradients.push_back( GradientEmit() );
m_aGradients.back().m_aGradient = rGradient;
m_aGradients.back().m_nObject = createObject();
m_aGradients.back().m_aSize = aPtSize;
return m_aGradients.back().m_nObject;
}
void PDFWriterImpl::drawGradient( const Rectangle& rRect, const Gradient& rGradient )
{
MARK( "drawGradient (Rectangle)" );
if( m_aContext.Version == PDFWriter::PDF_1_2 )
{
drawRectangle( rRect );
return;
}
sal_Int32 nGradient = createGradient( rGradient, rRect.GetSize() );
Point aTranslate( rRect.BottomLeft() );
aTranslate += Point( 0, 1 );
updateGraphicsState();
OStringBuffer aLine( 80 );
aLine.append( "q 1 0 0 1 " );
m_aPages.back().appendPoint( aTranslate, aLine );
aLine.append( " cm " );
// if a stroke is appended reset the clip region before stroke
if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) )
aLine.append( "q " );
aLine.append( "0 0 " );
m_aPages.back().appendMappedLength( (sal_Int32)rRect.GetWidth(), aLine, false );
aLine.append( ' ' );
m_aPages.back().appendMappedLength( (sal_Int32)rRect.GetHeight(), aLine, true );
aLine.append( " re W n\n" );
aLine.append( "/P" );
aLine.append( nGradient );
aLine.append( " sh " );
if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) )
{
aLine.append( "Q 0 0 " );
m_aPages.back().appendMappedLength( (sal_Int32)rRect.GetWidth(), aLine, false );
aLine.append( ' ' );
m_aPages.back().appendMappedLength( (sal_Int32)rRect.GetHeight(), aLine, true );
aLine.append( " re S " );
}
aLine.append( "Q\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
}
void PDFWriterImpl::drawGradient( const PolyPolygon& rPolyPoly, const Gradient& rGradient )
{
MARK( "drawGradient (PolyPolygon)" );
if( m_aContext.Version == PDFWriter::PDF_1_2 )
{
drawPolyPolygon( rPolyPoly );
return;
}
sal_Int32 nGradient = createGradient( rGradient, rPolyPoly.GetBoundRect().GetSize() );
updateGraphicsState();
Rectangle aBoundRect = rPolyPoly.GetBoundRect();
Point aTranslate = aBoundRect.BottomLeft() + Point( 0, 1 );
int nPolygons = rPolyPoly.Count();
OStringBuffer aLine( 80*nPolygons );
aLine.append( "q " );
// set PolyPolygon as clip path
m_aPages.back().appendPolyPolygon( rPolyPoly, aLine );
aLine.append( "W* n\n" );
aLine.append( "1 0 0 1 " );
m_aPages.back().appendPoint( aTranslate, aLine );
aLine.append( " cm\n" );
aLine.append( "/P" );
aLine.append( nGradient );
aLine.append( " sh Q\n" );
if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) )
{
// and draw the surrounding path
m_aPages.back().appendPolyPolygon( rPolyPoly, aLine );
aLine.append( "S\n" );
}
writeBuffer( aLine.getStr(), aLine.getLength() );
}
void PDFWriterImpl::drawHatch( const PolyPolygon& rPolyPoly, const Hatch& rHatch )
{
MARK( "drawHatch" );
updateGraphicsState();
if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) &&
m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) )
return;
if( rPolyPoly.Count() )
{
PolyPolygon aPolyPoly( rPolyPoly );
aPolyPoly.Optimize( POLY_OPTIMIZE_NO_SAME );
push( PUSH_LINECOLOR );
setLineColor( rHatch.GetColor() );
getReferenceDevice()->ImplDrawHatch( aPolyPoly, rHatch, FALSE );
pop();
}
}
void PDFWriterImpl::drawWallpaper( const Rectangle& rRect, const Wallpaper& rWall )
{
MARK( "drawWallpaper" );
bool bDrawColor = false;
bool bDrawGradient = false;
bool bDrawBitmap = false;
BitmapEx aBitmap;
Point aBmpPos = rRect.TopLeft();
Size aBmpSize;
if( rWall.IsBitmap() )
{
aBitmap = rWall.GetBitmap();
aBmpSize = lcl_convert( aBitmap.GetPrefMapMode(),
getMapMode(),
getReferenceDevice(),
aBitmap.GetPrefSize() );
Rectangle aRect( rRect );
if( rWall.IsRect() )
{
aRect = rWall.GetRect();
aBmpPos = aRect.TopLeft();
aBmpSize = aRect.GetSize();
}
if( rWall.GetStyle() != WALLPAPER_SCALE )
{
if( rWall.GetStyle() != WALLPAPER_TILE )
{
bDrawBitmap = true;
if( rWall.IsGradient() )
bDrawGradient = true;
else
bDrawColor = true;
switch( rWall.GetStyle() )
{
case WALLPAPER_TOPLEFT:
break;
case WALLPAPER_TOP:
aBmpPos.X() += (aRect.GetWidth()-aBmpSize.Width())/2;
break;
case WALLPAPER_LEFT:
aBmpPos.Y() += (aRect.GetHeight()-aBmpSize.Height())/2;
break;
case WALLPAPER_TOPRIGHT:
aBmpPos.X() += aRect.GetWidth()-aBmpSize.Width();
break;
case WALLPAPER_CENTER:
aBmpPos.X() += (aRect.GetWidth()-aBmpSize.Width())/2;
aBmpPos.Y() += (aRect.GetHeight()-aBmpSize.Height())/2;
break;
case WALLPAPER_RIGHT:
aBmpPos.X() += aRect.GetWidth()-aBmpSize.Width();
aBmpPos.Y() += (aRect.GetHeight()-aBmpSize.Height())/2;
break;
case WALLPAPER_BOTTOMLEFT:
aBmpPos.Y() += aRect.GetHeight()-aBmpSize.Height();
break;
case WALLPAPER_BOTTOM:
aBmpPos.X() += (aRect.GetWidth()-aBmpSize.Width())/2;
aBmpPos.Y() += aRect.GetHeight()-aBmpSize.Height();
break;
case WALLPAPER_BOTTOMRIGHT:
aBmpPos.X() += aRect.GetWidth()-aBmpSize.Width();
aBmpPos.Y() += aRect.GetHeight()-aBmpSize.Height();
break;
default: ;
}
}
else
{
// push the bitmap
const BitmapEmit& rEmit = createBitmapEmit( BitmapEx( aBitmap ) );
// convert to page coordinates; this needs to be done here
// since the emit does not know the page anymore
Rectangle aConvertRect( aBmpPos, aBmpSize );
m_aPages.back().convertRect( aConvertRect );
// push the pattern
m_aTilings.push_back( BitmapPatternEmit() );
m_aTilings.back().m_nObject = createObject();
m_aTilings.back().m_nBitmapObject = rEmit.m_nObject;
m_aTilings.back().m_aRectangle = aConvertRect;
updateGraphicsState();
// fill a rRect with the pattern
OStringBuffer aLine( 100 );
aLine.append( "q /Pattern cs /P" );
aLine.append( m_aTilings.back().m_nObject );
aLine.append( " scn " );
m_aPages.back().appendRect( rRect, aLine );
aLine.append( " f Q\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
}
}
else
{
aBmpPos = aRect.TopLeft();
aBmpSize = aRect.GetSize();
bDrawBitmap = true;
}
if( aBitmap.IsTransparent() )
{
if( rWall.IsGradient() )
bDrawGradient = true;
else
bDrawColor = true;
}
}
else if( rWall.IsGradient() )
bDrawGradient = true;
else
bDrawColor = true;
if( bDrawGradient )
{
drawGradient( rRect, rWall.GetGradient() );
}
if( bDrawColor )
{
Color aOldLineColor = m_aGraphicsStack.front().m_aLineColor;
Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor;
setLineColor( Color( COL_TRANSPARENT ) );
setFillColor( rWall.GetColor() );
drawRectangle( rRect );
setLineColor( aOldLineColor );
setFillColor( aOldFillColor );
}
if( bDrawBitmap )
{
// set temporary clip region since aBmpPos and aBmpSize
// may be outside rRect
OStringBuffer aLine( 20 );
aLine.append( "q " );
m_aPages.back().appendRect( rRect, aLine );
aLine.append( " W n\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
drawBitmap( aBmpPos, aBmpSize, aBitmap );
writeBuffer( "Q\n", 2 );
}
}
void PDFWriterImpl::updateGraphicsState()
{
OStringBuffer aLine( 256 );
GraphicsState& rNewState = m_aGraphicsStack.front();
// first set clip region since it might invalidate everything else
if( (rNewState.m_nUpdateFlags & GraphicsState::updateClipRegion) )
{
rNewState.m_nUpdateFlags &= ~GraphicsState::updateClipRegion;
Region& rNewClip = rNewState.m_aClipRegion;
/* #103137# equality operator is not implemented
* const as API promises but may change Region
* from Polygon to rectangles. Arrrgghh !!!!
*/
Region aLeft = m_aCurrentPDFState.m_aClipRegion;
Region aRight = rNewClip;
if( aLeft != aRight )
{
if( ! m_aCurrentPDFState.m_aClipRegion.IsEmpty() &&
! m_aCurrentPDFState.m_aClipRegion.IsNull() )
{
aLine.append( "Q " );
// invalidate everything but the clip region
m_aCurrentPDFState = GraphicsState();
rNewState.m_nUpdateFlags = sal::static_int_cast<sal_uInt16>(~GraphicsState::updateClipRegion);
}
if( ! rNewClip.IsEmpty() && ! rNewClip.IsNull() )
{
// clip region is always stored in private PDF mapmode
MapMode aNewMapMode = rNewState.m_aMapMode;
rNewState.m_aMapMode = m_aMapMode;
getReferenceDevice()->SetMapMode( rNewState.m_aMapMode );
m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
aLine.append( "q " );
if( rNewClip.HasPolyPolygon() )
{
m_aPages.back().appendPolyPolygon( rNewClip.GetPolyPolygon(), aLine );
aLine.append( "W* n\n" );
}
else
{
// need to clip all rectangles
RegionHandle aHandle = rNewClip.BeginEnumRects();
Rectangle aRect;
while( rNewClip.GetNextEnumRect( aHandle, aRect ) )
{
m_aPages.back().appendRect( aRect, aLine );
if( aLine.getLength() > 80 )
{
aLine.append( "\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
aLine.setLength( 0 );
}
else
aLine.append( ' ' );
}
rNewClip.EndEnumRects( aHandle );
aLine.append( "W* n\n" );
}
rNewState.m_aMapMode = aNewMapMode;
getReferenceDevice()->SetMapMode( rNewState.m_aMapMode );
m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode;
}
}
}
if( (rNewState.m_nUpdateFlags & GraphicsState::updateMapMode) )
{
rNewState.m_nUpdateFlags &= ~GraphicsState::updateMapMode;
getReferenceDevice()->SetMapMode( rNewState.m_aMapMode );
}
if( (rNewState.m_nUpdateFlags & GraphicsState::updateFont) )
{
rNewState.m_nUpdateFlags &= ~GraphicsState::updateFont;
getReferenceDevice()->SetFont( rNewState.m_aFont );
getReferenceDevice()->ImplNewFont();
}
if( (rNewState.m_nUpdateFlags & GraphicsState::updateLayoutMode) )
{
rNewState.m_nUpdateFlags &= ~GraphicsState::updateLayoutMode;
getReferenceDevice()->SetLayoutMode( rNewState.m_nLayoutMode );
}
if( (rNewState.m_nUpdateFlags & GraphicsState::updateDigitLanguage) )
{
rNewState.m_nUpdateFlags &= ~GraphicsState::updateDigitLanguage;
getReferenceDevice()->SetDigitLanguage( rNewState.m_aDigitLanguage );
}
if( (rNewState.m_nUpdateFlags & GraphicsState::updateLineColor) )
{
rNewState.m_nUpdateFlags &= ~GraphicsState::updateLineColor;
if( m_aCurrentPDFState.m_aLineColor != rNewState.m_aLineColor &&
rNewState.m_aLineColor != Color( COL_TRANSPARENT ) )
{
appendStrokingColor( rNewState.m_aLineColor, aLine );
aLine.append( "\n" );
}
}
if( (rNewState.m_nUpdateFlags & GraphicsState::updateFillColor) )
{
rNewState.m_nUpdateFlags &= ~GraphicsState::updateFillColor;
if( m_aCurrentPDFState.m_aFillColor != rNewState.m_aFillColor &&
rNewState.m_aFillColor != Color( COL_TRANSPARENT ) )
{
appendNonStrokingColor( rNewState.m_aFillColor, aLine );
aLine.append( "\n" );
}
}
if( (rNewState.m_nUpdateFlags & GraphicsState::updateTransparentPercent) )
{
rNewState.m_nUpdateFlags &= ~GraphicsState::updateTransparentPercent;
if( m_aContext.Version >= PDFWriter::PDF_1_4 && m_aCurrentPDFState.m_nTransparentPercent != rNewState.m_nTransparentPercent )
{
// TODO: switch extended graphicsstate
}
}
// everything is up to date now
m_aCurrentPDFState = m_aGraphicsStack.front();
if( aLine.getLength() )
writeBuffer( aLine.getStr(), aLine.getLength() );
}
/* #i47544# imitate OutputDevice behaviour:
* if a font with a nontransparent color is set, it overwrites the current
* text color. OTOH setting the text color will overwrite the color of the font.
*/
void PDFWriterImpl::setFont( const Font& rFont )
{
Color aColor = rFont.GetColor();
if( aColor == Color( COL_TRANSPARENT ) )
aColor = m_aGraphicsStack.front().m_aFont.GetColor();
m_aGraphicsStack.front().m_aFont = rFont;
m_aGraphicsStack.front().m_aFont.SetColor( aColor );
m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsState::updateFont;
}
void PDFWriterImpl::push( sal_uInt16 nFlags )
{
m_aGraphicsStack.push_front( m_aGraphicsStack.front() );
m_aGraphicsStack.front().m_nFlags = nFlags;
}
void PDFWriterImpl::pop()
{
GraphicsState aState = m_aGraphicsStack.front();
m_aGraphicsStack.pop_front();
GraphicsState& rOld = m_aGraphicsStack.front();
// move those parameters back that were not pushed
// in the first place
if( ! (aState.m_nFlags & PUSH_LINECOLOR) )
setLineColor( aState.m_aLineColor );
if( ! (aState.m_nFlags & PUSH_FILLCOLOR) )
setFillColor( aState.m_aFillColor );
if( ! (aState.m_nFlags & PUSH_FONT) )
setFont( aState.m_aFont );
if( ! (aState.m_nFlags & PUSH_TEXTCOLOR) )
setTextColor( aState.m_aFont.GetColor() );
if( ! (aState.m_nFlags & PUSH_MAPMODE) )
setMapMode( aState.m_aMapMode );
if( ! (aState.m_nFlags & PUSH_CLIPREGION) )
// do not use setClipRegion here
// it would convert again assuming the current mapmode
rOld.m_aClipRegion = aState.m_aClipRegion;
if( ! (aState.m_nFlags & PUSH_TEXTLINECOLOR ) )
setTextLineColor( aState.m_aTextLineColor );
if( ! (aState.m_nFlags & PUSH_TEXTALIGN ) )
setTextAlign( aState.m_aFont.GetAlign() );
if( ! (aState.m_nFlags & PUSH_TEXTFILLCOLOR) )
setTextFillColor( aState.m_aFont.GetFillColor() );
if( ! (aState.m_nFlags & PUSH_REFPOINT) )
{
// what ?
}
// invalidate graphics state
m_aGraphicsStack.front().m_nUpdateFlags = sal::static_int_cast<sal_uInt16>(~0U);
}
void PDFWriterImpl::setMapMode( const MapMode& rMapMode )
{
m_aGraphicsStack.front().m_aMapMode = rMapMode;
getReferenceDevice()->SetMapMode( rMapMode );
m_aCurrentPDFState.m_aMapMode = rMapMode;
}
void PDFWriterImpl::setClipRegion( const Region& rRegion )
{
Region aRegion = getReferenceDevice()->LogicToPixel( rRegion, m_aGraphicsStack.front().m_aMapMode );
aRegion = getReferenceDevice()->PixelToLogic( aRegion, m_aMapMode );
m_aGraphicsStack.front().m_aClipRegion = aRegion;
m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsState::updateClipRegion;
}
void PDFWriterImpl::moveClipRegion( sal_Int32 nX, sal_Int32 nY )
{
Point aPoint( lcl_convert( m_aGraphicsStack.front().m_aMapMode,
m_aMapMode,
getReferenceDevice(),
Point( nX, nY ) ) );
aPoint -= lcl_convert( m_aGraphicsStack.front().m_aMapMode,
m_aMapMode,
getReferenceDevice(),
Point() );
m_aGraphicsStack.front().m_aClipRegion.Move( aPoint.X(), aPoint.Y() );
m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsState::updateClipRegion;
}
bool PDFWriterImpl::intersectClipRegion( const Rectangle& rRect )
{
Rectangle aRect( lcl_convert( m_aGraphicsStack.front().m_aMapMode,
m_aMapMode,
getReferenceDevice(),
rRect ) );
m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsState::updateClipRegion;
return m_aGraphicsStack.front().m_aClipRegion.Intersect( aRect );
}
bool PDFWriterImpl::intersectClipRegion( const Region& rRegion )
{
Region aRegion = getReferenceDevice()->LogicToPixel( rRegion, m_aGraphicsStack.front().m_aMapMode );
aRegion = getReferenceDevice()->PixelToLogic( aRegion, m_aMapMode );
m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsState::updateClipRegion;
return m_aGraphicsStack.front().m_aClipRegion.Intersect( aRegion );
}
void PDFWriterImpl::createNote( const Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr )
{
if( nPageNr < 0 )
nPageNr = m_nCurrentPage;
if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() )
return;
m_aNotes.push_back( PDFNoteEntry() );
m_aNotes.back().m_nObject = createObject();
m_aNotes.back().m_aContents = rNote;
m_aNotes.back().m_aRect = rRect;
// convert to default user space now, since the mapmode may change
m_aPages[nPageNr].convertRect( m_aNotes.back().m_aRect );
// insert note to page's annotation list
m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aNotes.back().m_nObject );
}
sal_Int32 PDFWriterImpl::createLink( const Rectangle& rRect, sal_Int32 nPageNr )
{
if( nPageNr < 0 )
nPageNr = m_nCurrentPage;
if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() )
return -1;
sal_Int32 nRet = m_aLinks.size();
m_aLinks.push_back( PDFLink() );
m_aLinks.back().m_nObject = createObject();
m_aLinks.back().m_nPage = nPageNr;
m_aLinks.back().m_aRect = rRect;
// convert to default user space now, since the mapmode may change
m_aPages[nPageNr].convertRect( m_aLinks.back().m_aRect );
// insert link to page's annotation list
m_aPages[ nPageNr ].m_aAnnotations.push_back( m_aLinks.back().m_nObject );
return nRet;
}
sal_Int32 PDFWriterImpl::createDest( const Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType )
{
if( nPageNr < 0 )
nPageNr = m_nCurrentPage;
if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() )
return -1;
sal_Int32 nRet = m_aDests.size();
m_aDests.push_back( PDFDest() );
m_aDests.back().m_nPage = nPageNr;
m_aDests.back().m_eType = eType;
m_aDests.back().m_aRect = rRect;
// convert to default user space now, since the mapmode may change
m_aPages[nPageNr].convertRect( m_aDests.back().m_aRect );
return nRet;
}
sal_Int32 PDFWriterImpl::setLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
{
if( nLinkId < 0 || nLinkId >= (sal_Int32)m_aLinks.size() )
return -1;
if( nDestId < 0 || nDestId >= (sal_Int32)m_aDests.size() )
return -2;
m_aLinks[ nLinkId ].m_nDest = nDestId;
return 0;
}
static OUString escapeStringLiteral( const OUString& rStr )
{
OUStringBuffer aBuf( rStr.getLength()*2 );
const sal_Unicode* pUni = rStr.getStr();
int nLen = rStr.getLength();
for( ; nLen; nLen--, pUni++ )
{
switch( *pUni )
{
case sal_Unicode(')'):
case sal_Unicode('('):
case sal_Unicode('\\'):
aBuf.append( sal_Unicode( '\\' ) );
default:
aBuf.append( *pUni );
break;
}
}
return aBuf.makeStringAndClear();
}
sal_Int32 PDFWriterImpl::setLinkURL( sal_Int32 nLinkId, const OUString& rURL )
{
if( nLinkId < 0 || nLinkId >= (sal_Int32)m_aLinks.size() )
return -1;
m_aLinks[ nLinkId ].m_nDest = -1;
m_aLinks[ nLinkId ].m_aURL = escapeStringLiteral( rURL );
return 0;
}
void PDFWriterImpl::setLinkPropertyId( sal_Int32 nLinkId, sal_Int32 nPropertyId )
{
m_aLinkPropertyMap[ nPropertyId ] = nLinkId;
}
sal_Int32 PDFWriterImpl::createOutlineItem( sal_Int32 nParent, const OUString& rText, sal_Int32 nDestID )
{
// create new item
sal_Int32 nNewItem = m_aOutline.size();
m_aOutline.push_back( PDFOutlineEntry() );
// set item attributes
setOutlineItemParent( nNewItem, nParent );
setOutlineItemText( nNewItem, rText );
setOutlineItemDest( nNewItem, nDestID );
return nNewItem;
}
sal_Int32 PDFWriterImpl::setOutlineItemParent( sal_Int32 nItem, sal_Int32 nNewParent )
{
if( nItem < 1 || nItem >= (sal_Int32)m_aOutline.size() )
return -1;
int nRet = 0;
if( nNewParent < 0 || nNewParent >= (sal_Int32)m_aOutline.size() || nNewParent == nItem )
{
nNewParent = 0;
nRet = -2;
}
// remove item from previous parent
sal_Int32 nParentID = m_aOutline[ nItem ].m_nParentID;
if( nParentID >= 0 && nParentID < (sal_Int32)m_aOutline.size() )
{
PDFOutlineEntry& rParent = m_aOutline[ nParentID ];
for( std::vector<sal_Int32>::iterator it = rParent.m_aChildren.begin();
it != rParent.m_aChildren.end(); ++it )
{
if( *it == nItem )
{
rParent.m_aChildren.erase( it );
break;
}
}
}
// insert item to new parent's list of children
m_aOutline[ nNewParent ].m_aChildren.push_back( nItem );
return nRet;
}
sal_Int32 PDFWriterImpl::setOutlineItemText( sal_Int32 nItem, const OUString& rText )
{
if( nItem < 1 || nItem >= (sal_Int32)m_aOutline.size() )
return -1;
m_aOutline[ nItem ].m_aTitle = rText;
return 0;
}
sal_Int32 PDFWriterImpl::setOutlineItemDest( sal_Int32 nItem, sal_Int32 nDestID )
{
if( nItem < 1 || nItem >= (sal_Int32)m_aOutline.size() ) // item does not exist
return -1;
if( nDestID < 0 || nDestID >= (sal_Int32)m_aDests.size() ) // dest does not exist
return -2;
m_aOutline[nItem].m_nDestID = nDestID;
return 0;
}
const sal_Char* PDFWriterImpl::getStructureTag( PDFWriter::StructElement eType )
{
static std::map< PDFWriter::StructElement, const char* > aTagStrings;
if( aTagStrings.empty() )
{
aTagStrings[ PDFWriter::NonStructElement] = "NonStruct";
aTagStrings[ PDFWriter::Document ] = "Document";
aTagStrings[ PDFWriter::Part ] = "Part";
aTagStrings[ PDFWriter::Article ] = "Art";
aTagStrings[ PDFWriter::Section ] = "Sect";
aTagStrings[ PDFWriter::Division ] = "Div";
aTagStrings[ PDFWriter::BlockQuote ] = "BlockQuote";
aTagStrings[ PDFWriter::Caption ] = "Caption";
aTagStrings[ PDFWriter::TOC ] = "TOC";
aTagStrings[ PDFWriter::TOCI ] = "TOCI";
aTagStrings[ PDFWriter::Index ] = "Index";
aTagStrings[ PDFWriter::Paragraph ] = "P";
aTagStrings[ PDFWriter::Heading ] = "H";
aTagStrings[ PDFWriter::H1 ] = "H1";
aTagStrings[ PDFWriter::H2 ] = "H2";
aTagStrings[ PDFWriter::H3 ] = "H3";
aTagStrings[ PDFWriter::H4 ] = "H4";
aTagStrings[ PDFWriter::H5 ] = "H5";
aTagStrings[ PDFWriter::H6 ] = "H6";
aTagStrings[ PDFWriter::List ] = "L";
aTagStrings[ PDFWriter::ListItem ] = "LI";
aTagStrings[ PDFWriter::LILabel ] = "Lbl";
aTagStrings[ PDFWriter::LIBody ] = "LBody";
aTagStrings[ PDFWriter::Table ] = "Table";
aTagStrings[ PDFWriter::TableRow ] = "TR";
aTagStrings[ PDFWriter::TableHeader ] = "TH";
aTagStrings[ PDFWriter::TableData ] = "TD";
aTagStrings[ PDFWriter::Span ] = "Span";
aTagStrings[ PDFWriter::Quote ] = "Quote";
aTagStrings[ PDFWriter::Note ] = "Note";
aTagStrings[ PDFWriter::Reference ] = "Reference";
aTagStrings[ PDFWriter::BibEntry ] = "BibEntry";
aTagStrings[ PDFWriter::Code ] = "Code";
aTagStrings[ PDFWriter::Link ] = "Link";
aTagStrings[ PDFWriter::Figure ] = "Figure";
aTagStrings[ PDFWriter::Formula ] = "Formula";
aTagStrings[ PDFWriter::Form ] = "Form";
}
std::map< PDFWriter::StructElement, const char* >::const_iterator it = aTagStrings.find( eType );
return it != aTagStrings.end() ? it->second : "Div";
}
void PDFWriterImpl::beginStructureElementMCSeq()
{
if( m_bEmitStructure &&
m_nCurrentStructElement > 0 && // StructTreeRoot
! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence
)
{
PDFStructureElement& rEle = m_aStructure[ m_nCurrentStructElement ];
OStringBuffer aLine( 128 );
sal_Int32 nMCID = m_aPages[ m_nCurrentPage ].m_aMCIDParents.size();
aLine.append( "/" );
aLine.append( getStructureTag( rEle.m_eType ) );
aLine.append( "<</MCID " );
aLine.append( nMCID );
aLine.append( ">>BDC\n" );
writeBuffer( aLine.getStr(), aLine.getLength() );
// update the element's content list
#if OSL_DEBUG_LEVEL > 1
fprintf( stderr, "beginning marked content id %ld on page object %ld, structure first page = %ld\n",
nMCID,
m_aPages[ m_nCurrentPage ].m_nPageObject,
rEle.m_nFirstPageObject );
#endif
rEle.m_aKids.push_back( PDFStructureElementKid( nMCID, m_aPages[m_nCurrentPage].m_nPageObject ) );
// update the page's mcid parent list
m_aPages[ m_nCurrentPage ].m_aMCIDParents.push_back( rEle.m_nObject );
// mark element MC sequence as open
rEle.m_bOpenMCSeq = true;
}
}
void PDFWriterImpl::endStructureElementMCSeq()
{
if( m_bEmitStructure &&
m_nCurrentStructElement > 0 && // StructTreeRoot
m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // must have an opened MC sequence
)
{
writeBuffer( "EMC\n", 4 );
m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = false;
}
}
bool PDFWriterImpl::checkEmitStructure()
{
bool bEmit = false;
if( m_aContext.Tagged )
{
bEmit = true;
sal_Int32 nEle = m_nCurrentStructElement;
while( nEle > 0 && nEle < sal_Int32(m_aStructure.size()) )
{
if( m_aStructure[ nEle ].m_eType == PDFWriter::NonStructElement )
{
bEmit = false;
break;
}
nEle = m_aStructure[ nEle ].m_nParentElement;
}
}
return bEmit;
}
sal_Int32 PDFWriterImpl::beginStructureElement( PDFWriter::StructElement eType )
{
if( m_nCurrentPage < 0 )
return -1;
if( ! m_aContext.Tagged )
return -1;
// close eventual current MC sequence
endStructureElementMCSeq();
if( m_nCurrentStructElement == 0 &&
eType != PDFWriter::Document && eType != PDFWriter::NonStructElement )
{
// struct tree root hit, but not beginning document
// this might happen with setCurrentStructureElement
// silently insert structure into document again if one properly exists
if( ! m_aStructure[ 0 ].m_aChildren.empty() )
{
PDFWriter::StructElement childType = PDFWriter::NonStructElement;
sal_Int32 nNewCurElement = 0;
const std::list< sal_Int32 >& rRootChildren = m_aStructure[0].m_aChildren;
for( std::list< sal_Int32 >::const_iterator it = rRootChildren.begin();
childType != PDFWriter::Document && it != rRootChildren.end(); ++it )
{
nNewCurElement = *it;
childType = m_aStructure[ nNewCurElement ].m_eType;
}
if( childType == PDFWriter::Document )
{
m_nCurrentStructElement = nNewCurElement;
DBG_ASSERT( 0, "Structure element inserted to StructTreeRoot that is not a document" );
}
else
DBG_ERROR( "document structure in disorder !" );
}
else
DBG_ERROR( "PDF document structure MUST be contained in a Document element" );
}
#if OSL_DEBUG_LEVEL > 1
if( m_bEmitStructure )
{
OStringBuffer aLine( "beginStructureElement " );
aLine.append( sal_Int32(m_aStructure.size() ) );
aLine.append( ": " );
aLine.append( getStructureTag( eType ) );
emitComment( aLine.getStr() );
}
#endif
sal_Int32 nNewId = sal_Int32(m_aStructure.size());
m_aStructure.push_back( PDFStructureElement() );
PDFStructureElement& rEle = m_aStructure.back();
rEle.m_eType = eType;
rEle.m_nOwnElement = nNewId;
rEle.m_nParentElement = m_nCurrentStructElement;
rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject;
m_aStructure[ m_nCurrentStructElement ].m_aChildren.push_back( nNewId );
m_nCurrentStructElement = nNewId;
// check whether to emit structure henceforth
m_bEmitStructure = checkEmitStructure();
if( m_bEmitStructure ) // don't create nonexistant objects
{
rEle.m_nObject = createObject();
// update parent's kids list
m_aStructure[ rEle.m_nParentElement ].m_aKids.push_back( rEle.m_nObject );
}
return nNewId;
}
void PDFWriterImpl::endStructureElement()
{
if( m_nCurrentPage < 0 )
return;
if( ! m_aContext.Tagged )
return;
if( m_nCurrentStructElement == 0 )
{
// hit the struct tree root, that means there is an endStructureElement
// without corresponding beginStructureElement
return;
}
// end the marked content sequence
endStructureElementMCSeq();
#if OSL_DEBUG_LEVEL > 1
OStringBuffer aLine( "endStructureElement " );
aLine.append( m_nCurrentStructElement );
aLine.append( ": " );
aLine.append( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) );
#endif
// "end" the structure element, the parent becomes current element
m_nCurrentStructElement = m_aStructure[ m_nCurrentStructElement ].m_nParentElement;
// check whether to emit structure henceforth
m_bEmitStructure = checkEmitStructure();
#if OSL_DEBUG_LEVEL > 1
if( m_bEmitStructure )
emitComment( aLine.getStr() );
#endif
}
bool PDFWriterImpl::setCurrentStructureElement( sal_Int32 nEle )
{
bool bSuccess = false;
if( m_aContext.Tagged && nEle >= 0 && nEle < sal_Int32(m_aStructure.size()) )
{
// end eventual previous marked content sequence
endStructureElementMCSeq();
m_nCurrentStructElement = nEle;
m_bEmitStructure = checkEmitStructure();
#if OSL_DEBUG_LEVEL > 1
OStringBuffer aLine( "setCurrentStructureElement " );
aLine.append( m_nCurrentStructElement );
aLine.append( ": " );
aLine.append( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) );
if( ! m_bEmitStructure )
aLine.append( " (inside NonStruct)" );
emitComment( aLine.getStr() );
#endif
bSuccess = true;
}
return bSuccess;
}
sal_Int32 PDFWriterImpl::getCurrentStructureElement()
{
return m_nCurrentStructElement;
}
bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr, enum PDFWriter::StructAttributeValue eVal )
{
if( !m_aContext.Tagged )
return false;
bool bInsert = false;
if( m_nCurrentStructElement > 0 && m_bEmitStructure )
{
PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
switch( eAttr )
{
case PDFWriter::Placement:
if( eVal == PDFWriter::Block ||
eVal == PDFWriter::Inline ||
eVal == PDFWriter::Before ||
eVal == PDFWriter::Start ||
eVal == PDFWriter::End )
bInsert = true;
break;
case PDFWriter::WritingMode:
if( eVal == PDFWriter::LrTb ||
eVal == PDFWriter::RlTb ||
eVal == PDFWriter::TbRl )
{
bInsert = true;
}
break;
case PDFWriter::TextAlign:
if( eVal == PDFWriter::Start ||
eVal == PDFWriter::Center ||
eVal == PDFWriter::End ||
eVal == PDFWriter::Justify )
{
if( eType == PDFWriter::Paragraph ||
eType == PDFWriter::Heading ||
eType == PDFWriter::H1 ||
eType == PDFWriter::H2 ||
eType == PDFWriter::H3 ||
eType == PDFWriter::H4 ||
eType == PDFWriter::H5 ||
eType == PDFWriter::H6 ||
eType == PDFWriter::List ||
eType == PDFWriter::ListItem ||
eType == PDFWriter::LILabel ||
eType == PDFWriter::LIBody ||
eType == PDFWriter::Table ||
eType == PDFWriter::TableRow ||
eType == PDFWriter::TableHeader ||
eType == PDFWriter::TableData )
{
bInsert = true;
}
}
break;
case PDFWriter::Width:
case PDFWriter::Height:
if( eVal == PDFWriter::Auto )
{
if( eType == PDFWriter::Figure ||
eType == PDFWriter::Formula ||
eType == PDFWriter::Form ||
eType == PDFWriter::Table ||
eType == PDFWriter::TableHeader ||
eType == PDFWriter::TableData )
{
bInsert = true;
}
}
break;
case PDFWriter::BlockAlign:
if( eVal == PDFWriter::Before ||
eVal == PDFWriter::Middle ||
eVal == PDFWriter::After ||
eVal == PDFWriter::Justify )
{
if( eType == PDFWriter::TableHeader ||
eType == PDFWriter::TableData )
{
bInsert = true;
}
}
break;
case PDFWriter::InlineAlign:
if( eVal == PDFWriter::Start ||
eVal == PDFWriter::Center ||
eVal == PDFWriter::End )
{
if( eType == PDFWriter::TableHeader ||
eType == PDFWriter::TableData )
{
bInsert = true;
}
}
break;
case PDFWriter::LineHeight:
if( eVal == PDFWriter::Normal ||
eVal == PDFWriter::Auto )
{
// only for ILSE and BLSE
if( eType == PDFWriter::Paragraph ||
eType == PDFWriter::Heading ||
eType == PDFWriter::H1 ||
eType == PDFWriter::H2 ||
eType == PDFWriter::H3 ||
eType == PDFWriter::H4 ||
eType == PDFWriter::H5 ||
eType == PDFWriter::H6 ||
eType == PDFWriter::List ||
eType == PDFWriter::ListItem ||
eType == PDFWriter::LILabel ||
eType == PDFWriter::LIBody ||
eType == PDFWriter::Table ||
eType == PDFWriter::TableRow ||
eType == PDFWriter::TableHeader ||
eType == PDFWriter::TableData ||
eType == PDFWriter::Span ||
eType == PDFWriter::Quote ||
eType == PDFWriter::Note ||
eType == PDFWriter::Reference ||
eType == PDFWriter::BibEntry ||
eType == PDFWriter::Code ||
eType == PDFWriter::Link )
{
bInsert = true;
}
}
break;
case PDFWriter::TextDecorationType:
if( eVal == PDFWriter::NONE ||
eVal == PDFWriter::Underline ||
eVal == PDFWriter::Overline ||
eVal == PDFWriter::LineThrough )
{
// only for ILSE and BLSE
if( eType == PDFWriter::Paragraph ||
eType == PDFWriter::Heading ||
eType == PDFWriter::H1 ||
eType == PDFWriter::H2 ||
eType == PDFWriter::H3 ||
eType == PDFWriter::H4 ||
eType == PDFWriter::H5 ||
eType == PDFWriter::H6 ||
eType == PDFWriter::List ||
eType == PDFWriter::ListItem ||
eType == PDFWriter::LILabel ||
eType == PDFWriter::LIBody ||
eType == PDFWriter::Table ||
eType == PDFWriter::TableRow ||
eType == PDFWriter::TableHeader ||
eType == PDFWriter::TableData ||
eType == PDFWriter::Span ||
eType == PDFWriter::Quote ||
eType == PDFWriter::Note ||
eType == PDFWriter::Reference ||
eType == PDFWriter::BibEntry ||
eType == PDFWriter::Code ||
eType == PDFWriter::Link )
{
bInsert = true;
}
}
break;
case PDFWriter::ListNumbering:
if( eVal == PDFWriter::NONE ||
eVal == PDFWriter::Disc ||
eVal == PDFWriter::Circle ||
eVal == PDFWriter::Square ||
eVal == PDFWriter::Decimal ||
eVal == PDFWriter::UpperRoman ||
eVal == PDFWriter::LowerRoman ||
eVal == PDFWriter::UpperAlpha ||
eVal == PDFWriter::LowerAlpha )
{
if( eType == PDFWriter::List )
bInsert = true;
}
break;
default: break;
}
}
if( bInsert )
m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( eVal );
#if OSL_DEBUG_LEVEL > 1
else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
fprintf( stderr, "rejecting setStructureAttribute( %s, %s ) on %s element\n",
getAttributeTag( eAttr ),
getAttributeValueTag( eVal ),
getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) );
#endif
return bInsert;
}
bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttribute eAttr, sal_Int32 nValue )
{
if( ! m_aContext.Tagged )
return false;
bool bInsert = false;
if( m_nCurrentStructElement > 0 && m_bEmitStructure )
{
PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
switch( eAttr )
{
case PDFWriter::SpaceBefore:
case PDFWriter::SpaceAfter:
case PDFWriter::StartIndent:
case PDFWriter::EndIndent:
// just for BLSE
if( eType == PDFWriter::Paragraph ||
eType == PDFWriter::Heading ||
eType == PDFWriter::H1 ||
eType == PDFWriter::H2 ||
eType == PDFWriter::H3 ||
eType == PDFWriter::H4 ||
eType == PDFWriter::H5 ||
eType == PDFWriter::H6 ||
eType == PDFWriter::List ||
eType == PDFWriter::ListItem ||
eType == PDFWriter::LILabel ||
eType == PDFWriter::LIBody ||
eType == PDFWriter::Table ||
eType == PDFWriter::TableRow ||
eType == PDFWriter::TableHeader ||
eType == PDFWriter::TableData )
{
bInsert = true;
}
break;
case PDFWriter::TextIndent:
// paragraph like BLSE and additional elements
if( eType == PDFWriter::Paragraph ||
eType == PDFWriter::Heading ||
eType == PDFWriter::H1 ||
eType == PDFWriter::H2 ||
eType == PDFWriter::H3 ||
eType == PDFWriter::H4 ||
eType == PDFWriter::H5 ||
eType == PDFWriter::H6 ||
eType == PDFWriter::LILabel ||
eType == PDFWriter::LIBody ||
eType == PDFWriter::TableHeader ||
eType == PDFWriter::TableData )
{
bInsert = true;
}
break;
case PDFWriter::Width:
case PDFWriter::Height:
if( eType == PDFWriter::Figure ||
eType == PDFWriter::Formula ||
eType == PDFWriter::Form ||
eType == PDFWriter::Table ||
eType == PDFWriter::TableHeader ||
eType == PDFWriter::TableData )
{
bInsert = true;
}
break;
case PDFWriter::LineHeight:
case PDFWriter::BaselineShift:
// only for ILSE and BLSE
if( eType == PDFWriter::Paragraph ||
eType == PDFWriter::Heading ||
eType == PDFWriter::H1 ||
eType == PDFWriter::H2 ||
eType == PDFWriter::H3 ||
eType == PDFWriter::H4 ||
eType == PDFWriter::H5 ||
eType == PDFWriter::H6 ||
eType == PDFWriter::List ||
eType == PDFWriter::ListItem ||
eType == PDFWriter::LILabel ||
eType == PDFWriter::LIBody ||
eType == PDFWriter::Table ||
eType == PDFWriter::TableRow ||
eType == PDFWriter::TableHeader ||
eType == PDFWriter::TableData ||
eType == PDFWriter::Span ||
eType == PDFWriter::Quote ||
eType == PDFWriter::Note ||
eType == PDFWriter::Reference ||
eType == PDFWriter::BibEntry ||
eType == PDFWriter::Code ||
eType == PDFWriter::Link )
{
bInsert = true;
}
break;
case PDFWriter::RowSpan:
case PDFWriter::ColSpan:
// only for table cells
if( eType == PDFWriter::TableHeader ||
eType == PDFWriter::TableData )
{
bInsert = true;
}
break;
case PDFWriter::LinkAnnotation:
if( eType == PDFWriter::Link )
bInsert = true;
break;
default: break;
}
}
if( bInsert )
m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( nValue );
#if OSL_DEBUG_LEVEL > 1
else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
fprintf( stderr, "rejecting setStructureAttributeNumerical( %s, %d ) on %s element\n",
getAttributeTag( eAttr ),
(int)nValue,
getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) );
#endif
return bInsert;
}
void PDFWriterImpl::setStructureBoundingBox( const Rectangle& rRect )
{
sal_Int32 nPageNr = m_nCurrentPage;
if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() || !m_aContext.Tagged )
return;
if( m_nCurrentStructElement > 0 && m_bEmitStructure )
{
PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType;
if( eType == PDFWriter::Figure ||
eType == PDFWriter::Formula ||
eType == PDFWriter::Form ||
eType == PDFWriter::Table )
{
m_aStructure[ m_nCurrentStructElement ].m_aBBox = rRect;
// convert to default user space now, since the mapmode may change
m_aPages[nPageNr].convertRect( m_aStructure[ m_nCurrentStructElement ].m_aBBox );
}
}
}
void PDFWriterImpl::setActualText( const String& rText )
{
if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
{
m_aStructure[ m_nCurrentStructElement ].m_aActualText = rText;
}
}
void PDFWriterImpl::setAlternateText( const String& rText )
{
if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )
{
m_aStructure[ m_nCurrentStructElement ].m_aAltText = rText;
}
}
void PDFWriterImpl::setAutoAdvanceTime( sal_uInt32 nSeconds, sal_Int32 nPageNr )
{
if( nPageNr < 0 )
nPageNr = m_nCurrentPage;
if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() )
return;
m_aPages[ nPageNr ].m_nDuration = nSeconds;
}
void PDFWriterImpl::setPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr )
{
if( nPageNr < 0 )
nPageNr = m_nCurrentPage;
if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() )
return;
m_aPages[ nPageNr ].m_eTransition = eType;
m_aPages[ nPageNr ].m_nTransTime = nMilliSec;
}
void PDFWriterImpl::ensureUniqueRadioOnValues()
{
// loop over radio groups
for( std::map<sal_Int32,sal_Int32>::const_iterator group = m_aRadioGroupWidgets.begin();
group != m_aRadioGroupWidgets.end(); ++group )
{
PDFWidget& rGroupWidget = m_aWidgets[ group->second ];
// check whether all kids have a unique OnValue
std::hash_map< OUString, sal_Int32, OUStringHash > aOnValues;
int nChildren = rGroupWidget.m_aKidsIndex.size();
bool bIsUnique = true;
for( int nKid = 0; nKid < nChildren && bIsUnique; nKid++ )
{
int nKidIndex = rGroupWidget.m_aKidsIndex[nKid];
const OUString& rVal = m_aWidgets[nKidIndex].m_aOnValue;
#if OSL_DEBUG_LEVEL > 1
fprintf( stderr, "OnValue: %s\n", OUStringToOString( rVal, RTL_TEXTENCODING_UTF8 ).getStr() );
#endif
if( aOnValues.find( rVal ) == aOnValues.end() )
{
aOnValues[ rVal ] = 1;
}
else
{
bIsUnique = false;
}
}
if( ! bIsUnique )
{
#if OSL_DEBUG_LEVEL > 1
fprintf( stderr, "enforcing unique OnValues\n" );
#endif
// make unique by using ascending OnValues
for( int nKid = 0; nKid < nChildren; nKid++ )
{
int nKidIndex = rGroupWidget.m_aKidsIndex[nKid];
PDFWidget& rKid = m_aWidgets[nKidIndex];
rKid.m_aOnValue = OUString::valueOf( sal_Int32(nKid+1) );
if( ! rKid.m_aValue.equalsAscii( "Off" ) )
rKid.m_aValue = rKid.m_aOnValue;
}
}
// finally move the "Yes" appearance to the OnValue appearance
for( int nKid = 0; nKid < nChildren; nKid++ )
{
int nKidIndex = rGroupWidget.m_aKidsIndex[nKid];
PDFWidget& rKid = m_aWidgets[nKidIndex];
PDFAppearanceMap::iterator app_it = rKid.m_aAppearances.find( "N" );
if( app_it != rKid.m_aAppearances.end() )
{
PDFAppearanceStreams::iterator stream_it = app_it->second.find( "Yes" );
if( stream_it != app_it->second.end() )
{
SvMemoryStream* pStream = stream_it->second;
app_it->second.erase( stream_it );
OStringBuffer aBuf( rKid.m_aOnValue.getLength()*2 );
appendName( rKid.m_aOnValue, aBuf );
(app_it->second)[ aBuf.makeStringAndClear() ] = pStream;
}
#if OSL_DEBUG_LEVEL > 1
else
fprintf( stderr, "error: RadioButton without \"Yes\" stream\n" );
#endif
}
// update selected radio button
if( ! rKid.m_aValue.equalsAscii( "Off" ) )
{
rGroupWidget.m_aValue = rKid.m_aValue;
}
}
}
}
sal_Int32 PDFWriterImpl::findRadioGroupWidget( const PDFWriter::RadioButtonWidget& rBtn )
{
sal_Int32 nRadioGroupWidget = -1;
std::map< sal_Int32, sal_Int32 >::const_iterator it = m_aRadioGroupWidgets.find( rBtn.RadioGroup );
if( it == m_aRadioGroupWidgets.end() )
{
m_aRadioGroupWidgets[ rBtn.RadioGroup ] = nRadioGroupWidget =
sal_Int32(m_aWidgets.size());
// new group, insert the radiobutton
m_aWidgets.push_back( PDFWidget() );
m_aWidgets.back().m_nObject = createObject();
m_aWidgets.back().m_nPage = m_nCurrentPage;
m_aWidgets.back().m_eType = PDFWriter::RadioButton;
m_aWidgets.back().m_nRadioGroup = rBtn.RadioGroup;
m_aWidgets.back().m_nFlags |= 0x00008000;
// create radio button field name
const rtl::OUString& rName = (m_aContext.Version > PDFWriter::PDF_1_2) ?
rBtn.Name : rBtn.Text;
if( rName.getLength() )
{
m_aWidgets.back().m_aName = convertWidgetFieldName( rName );
}
else
{
m_aWidgets.back().m_aName = "RadioGroup";
m_aWidgets.back().m_aName += OString::valueOf( rBtn.RadioGroup );
}
}
else
nRadioGroupWidget = it->second;
return nRadioGroupWidget;
}
sal_Int32 PDFWriterImpl::createControl( const PDFWriter::AnyWidget& rControl, sal_Int32 nPageNr )
{
if( nPageNr < 0 )
nPageNr = m_nCurrentPage;
if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() )
return -1;
m_aWidgets.push_back( PDFWidget() );
sal_Int32 nNewWidget = m_aWidgets.size()-1;
// create eventual radio button before getting any references
// from m_aWidgets as the push_back operation potentially assigns new
// memory to the vector and thereby invalidates the reference
int nRadioGroupWidget = -1;
if( rControl.getType() == PDFWriter::RadioButton )
nRadioGroupWidget = findRadioGroupWidget( static_cast<const PDFWriter::RadioButtonWidget&>(rControl) );
PDFWidget& rNewWidget = m_aWidgets[nNewWidget];
rNewWidget.m_nObject = createObject();
rNewWidget.m_aRect = rControl.Location;
rNewWidget.m_nPage = nPageNr;
rNewWidget.m_eType = rControl.getType();
// for unknown reasons the radio buttons of a radio group must not have a
// field name, else the buttons are in fact check boxes -
// that is multiple buttons of the radio group can be selected
if( rControl.getType() != PDFWriter::RadioButton )
{
// acrobat reader since 3.0 does not support unicode text
// strings for the field name; so we need to encode unicodes
// larger than 255
rNewWidget.m_aName =
convertWidgetFieldName( (m_aContext.Version > PDFWriter::PDF_1_2) ?
rControl.Name : rControl.Text );
}
rNewWidget.m_aDescription = rControl.Description;
rNewWidget.m_aText = rControl.Text;
rNewWidget.m_nTextStyle = rControl.TextStyle &
( TEXT_DRAW_LEFT | TEXT_DRAW_CENTER | TEXT_DRAW_RIGHT | TEXT_DRAW_TOP |
TEXT_DRAW_VCENTER | TEXT_DRAW_BOTTOM |
TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK );
rNewWidget.m_nTabOrder = rControl.TabOrder;
// various properties are set via the flags (/Ff) property of the field dict
if( rControl.ReadOnly )
rNewWidget.m_nFlags |= 1;
if( rControl.getType() == PDFWriter::PushButton )
{
const PDFWriter::PushButtonWidget& rBtn = static_cast<const PDFWriter::PushButtonWidget&>(rControl);
if( rNewWidget.m_nTextStyle == 0 )
rNewWidget.m_nTextStyle =
TEXT_DRAW_CENTER | TEXT_DRAW_VCENTER |
TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK;
rNewWidget.m_nFlags |= 0x00010000;
if( rBtn.URL.getLength() )
rNewWidget.m_aListEntries.push_back( rBtn.URL );
rNewWidget.m_bSubmit = rBtn.Submit;
rNewWidget.m_nDest = rBtn.Dest;
createDefaultPushButtonAppearance( rNewWidget, rBtn );
}
else if( rControl.getType() == PDFWriter::RadioButton )
{
const PDFWriter::RadioButtonWidget& rBtn = static_cast<const PDFWriter::RadioButtonWidget&>(rControl);
if( rNewWidget.m_nTextStyle == 0 )
rNewWidget.m_nTextStyle =
TEXT_DRAW_VCENTER | TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK;
/* PDF sees a RadioButton group as one radio button with
* children which are in turn check boxes
*
* so we need to create a radio button on demand for a new group
* and insert a checkbox for each RadioButtonWidget as its child
*/
rNewWidget.m_eType = PDFWriter::CheckBox;
rNewWidget.m_nRadioGroup = rBtn.RadioGroup;
DBG_ASSERT( nRadioGroupWidget >= 0 && nRadioGroupWidget < (sal_Int32)m_aWidgets.size(), "no radio group parent" );
PDFWidget& rRadioButton = m_aWidgets[nRadioGroupWidget];
rRadioButton.m_aKids.push_back( rNewWidget.m_nObject );
rRadioButton.m_aKidsIndex.push_back( nNewWidget );
rNewWidget.m_nParent = rRadioButton.m_nObject;
rNewWidget.m_aValue = OUString( RTL_CONSTASCII_USTRINGPARAM( "Off" ) );
rNewWidget.m_aOnValue = rBtn.OnValue;
if( ! rRadioButton.m_aValue.getLength() && rBtn.Selected )
{
rNewWidget.m_aValue = rNewWidget.m_aOnValue;
rRadioButton.m_aValue = rNewWidget.m_aOnValue;
}
createDefaultRadioButtonAppearance( rNewWidget, rBtn );
// union rect of radio group
Rectangle aRect = rNewWidget.m_aRect;
m_aPages[ nPageNr ].convertRect( aRect );
rRadioButton.m_aRect.Union( aRect );
}
else if( rControl.getType() == PDFWriter::CheckBox )
{
const PDFWriter::CheckBoxWidget& rBox = static_cast<const PDFWriter::CheckBoxWidget&>(rControl);
if( rNewWidget.m_nTextStyle == 0 )
rNewWidget.m_nTextStyle =
TEXT_DRAW_VCENTER | TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK;
rNewWidget.m_aValue = OUString::createFromAscii( rBox.Checked ? "Yes" : "Off" );
// create default appearance before m_aRect gets transformed
createDefaultCheckBoxAppearance( rNewWidget, rBox );
}
else if( rControl.getType() == PDFWriter::ListBox )
{
if( rNewWidget.m_nTextStyle == 0 )
rNewWidget.m_nTextStyle = TEXT_DRAW_VCENTER;
const PDFWriter::ListBoxWidget& rLstBox = static_cast<const PDFWriter::ListBoxWidget&>(rControl);
rNewWidget.m_aListEntries = rLstBox.Entries;
rNewWidget.m_aValue = rLstBox.Text;
if( rLstBox.DropDown )
rNewWidget.m_nFlags |= 0x00020000;
if( rLstBox.Sort )
{
rNewWidget.m_nFlags |= 0x00080000;
rNewWidget.m_aListEntries.sort();
}
if( rLstBox.MultiSelect && !rLstBox.DropDown )
rNewWidget.m_nFlags |= 0x00200000;
createDefaultListBoxAppearance( rNewWidget, rLstBox );
}
else if( rControl.getType() == PDFWriter::ComboBox )
{
if( rNewWidget.m_nTextStyle == 0 )
rNewWidget.m_nTextStyle = TEXT_DRAW_VCENTER;
const PDFWriter::ComboBoxWidget& rBox = static_cast<const PDFWriter::ComboBoxWidget&>(rControl);
rNewWidget.m_aValue = rBox.Text;
rNewWidget.m_aListEntries = rBox.Entries;
rNewWidget.m_nFlags |= 0x00060000; // combo and edit flag
if( rBox.Sort )
{
rNewWidget.m_nFlags |= 0x00080000;
rNewWidget.m_aListEntries.sort();
}
PDFWriter::ListBoxWidget aLBox;
aLBox.Name = rBox.Name;
aLBox.Description = rBox.Description;
aLBox.Text = rBox.Text;
aLBox.TextStyle = rBox.TextStyle;
aLBox.ReadOnly = rBox.ReadOnly;
aLBox.Border = rBox.Border;
aLBox.BorderColor = rBox.BorderColor;
aLBox.Background = rBox.Background;
aLBox.BackgroundColor = rBox.BackgroundColor;
aLBox.TextFont = rBox.TextFont;
aLBox.TextColor = rBox.TextColor;
aLBox.DropDown = true;
aLBox.Sort = rBox.Sort;
aLBox.MultiSelect = false;
aLBox.Entries = rBox.Entries;
createDefaultListBoxAppearance( rNewWidget, aLBox );
}
else if( rControl.getType() == PDFWriter::Edit )
{
if( rNewWidget.m_nTextStyle == 0 )
rNewWidget.m_nTextStyle = TEXT_DRAW_LEFT | TEXT_DRAW_VCENTER;
const PDFWriter::EditWidget& rEdit = static_cast<const PDFWriter::EditWidget&>(rControl);
if( rEdit.MultiLine )
{
rNewWidget.m_nFlags |= 0x00001000;
rNewWidget.m_nTextStyle |= TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK;
}
if( rEdit.Password )
rNewWidget.m_nFlags |= 0x00002000;
if( rEdit.FileSelect && m_aContext.Version > PDFWriter::PDF_1_3 )
rNewWidget.m_nFlags |= 0x00100000;
rNewWidget.m_nMaxLen = rEdit.MaxLen;
rNewWidget.m_aValue = rEdit.Text;
createDefaultEditAppearance( rNewWidget, rEdit );
}
// convert to default user space now, since the mapmode may change
// note: create default appearances before m_aRect gets transformed
m_aPages[ nPageNr ].convertRect( rNewWidget.m_aRect );
// insert widget to page's annotation list
m_aPages[ nPageNr ].m_aAnnotations.push_back( rNewWidget.m_nObject );
// mark page as having widgets
m_aPages[ nPageNr ].m_bHasWidgets = true;
return nNewWidget;
}
void PDFWriterImpl::beginControlAppearance( sal_Int32 nControl )
{
if( nControl < 0 || nControl >= (sal_Int32)m_aWidgets.size() )
return;
PDFWidget& rWidget = m_aWidgets[ nControl ];
m_nCurrentControl = nControl;
SvMemoryStream* pControlStream = new SvMemoryStream( 1024, 1024 );
// back conversion of control rect to current MapMode; necessary because
// MapMode between createControl and beginControlAppearance
// could have changed; therefore the widget rectangle is
// already converted
Rectangle aBack( Point( rWidget.m_aRect.Left(), pointToPixel(m_aPages[m_nCurrentPage].getHeight()) - rWidget.m_aRect.Top() - rWidget.m_aRect.GetHeight() ),
rWidget.m_aRect.GetSize() );
aBack = lcl_convert( m_aMapMode,
m_aGraphicsStack.front().m_aMapMode,
getReferenceDevice(),
aBack );
beginRedirect( pControlStream, aBack );
writeBuffer( "/Tx BMC\n", 8 );
}
bool PDFWriterImpl::endControlAppearance( PDFWriter::WidgetState eState )
{
bool bRet = false;
if( ! m_aOutputStreams.empty() )
writeBuffer( "\nEMC\n", 5 );
SvMemoryStream* pAppearance = static_cast<SvMemoryStream*>(endRedirect());
if( pAppearance && m_nCurrentControl >= 0 && m_nCurrentControl < (sal_Int32)m_aWidgets.size() )
{
PDFWidget& rWidget = m_aWidgets[ m_nCurrentControl ];
OString aState, aStyle;
switch( rWidget.m_eType )
{
case PDFWriter::PushButton:
if( eState == PDFWriter::Up || eState == PDFWriter::Down )
{
aState = (eState == PDFWriter::Up) ? "N" : "D";
aStyle = "Standard";
}
break;
case PDFWriter::CheckBox:
if( eState == PDFWriter::Up || eState == PDFWriter::Down )
{
aState = "N";
aStyle = (eState == PDFWriter::Up) ? "Off" : "Yes";
/* cf PDFReference 3rd ed. V1.4 p539:
recommended name for on state is "Yes",
recommended name for off state is "Off"
*/
}
break;
case PDFWriter::RadioButton:
if( eState == PDFWriter::Up || eState == PDFWriter::Down )
{
aState = "N";
if( eState == PDFWriter::Up )
aStyle = "Off";
else
{
OStringBuffer aBuf( rWidget.m_aOnValue.getLength()*2 );
appendName( rWidget.m_aOnValue, aBuf );
aStyle = aBuf.makeStringAndClear();
}
}
break;
case PDFWriter::Edit:
aState = "N";
aStyle = "Standard";
break;
case PDFWriter::ListBox:
case PDFWriter::ComboBox:
break;
}
if( aState.getLength() && aStyle.getLength() )
{
// delete eventual existing stream
PDFAppearanceStreams::iterator it =
rWidget.m_aAppearances[ aState ].find( aStyle );
if( it != rWidget.m_aAppearances[ aState ].end() )
delete it->second;
rWidget.m_aAppearances[ aState ][ aStyle ] = pAppearance;
bRet = true;
}
}
if( ! bRet )
delete pAppearance;
m_nCurrentControl = -1;
return bRet;
}
/*************************************************************
begin i12626 methods
Implements Algorithm 3.2, step 1 only
*/
void PDFWriterImpl::padPassword( rtl::OUString aPassword, sal_uInt8 *paPasswordTarget )
{
// get ansi-1252 version of the password string CHECKIT ! i12626
rtl::OString aString = rtl::OUStringToOString( aPassword, RTL_TEXTENCODING_MS_1252 );
//copy the string to the target
sal_Int32 nToCopy = ( aString.getLength() < 32 ) ? aString.getLength() : 32;
sal_Int32 nCurrentChar;
for( nCurrentChar = 0; nCurrentChar < nToCopy; nCurrentChar++ )
paPasswordTarget[nCurrentChar] = (sal_uInt8)( aString.getStr()[nCurrentChar] );
//pad it
if( nCurrentChar < 32 )
{//fill with standard byte string
sal_Int32 i,y;
for( i = nCurrentChar, y = 0 ; i < 32; i++, y++ )
paPasswordTarget[i] = m_nPadString[y];
}
}
/**********************************
Algorithm 3.2 Compute the encryption key used
step 1 should already be done before calling, the paThePaddedPassword parameter should contain
the padded password and must be 32 byte long, the encryption key is returned into the paEncryptionKey parameter,
it will be 16 byte long for 128 bit security; for 40 bit security only the first 5 bytes are used
TODO: in pdf ver 1.5 and 1.6 the step 6 is different, should be implemented. See spec.
*/
void PDFWriterImpl::computeEncryptionKey(sal_uInt8 *paThePaddedPassword, sal_uInt8 *paEncryptionKey )
{
//step 2
if( m_aDigest )
{
rtlDigestError nError = rtl_digest_updateMD5( m_aDigest, paThePaddedPassword, ENCRYPTED_PWD_SIZE );
//step 3
if( nError == rtl_Digest_E_None )
nError = rtl_digest_updateMD5( m_aDigest, m_nEncryptedOwnerPassword , sizeof( m_nEncryptedOwnerPassword ) );
//Step 4
sal_uInt8 nPerm[4];
nPerm[0] = (sal_uInt8)m_nAccessPermissions;
nPerm[1] = (sal_uInt8)( m_nAccessPermissions >> 8 );
nPerm[2] = (sal_uInt8)( m_nAccessPermissions >> 16 );
nPerm[3] = (sal_uInt8)( m_nAccessPermissions >> 24 );
if( nError == rtl_Digest_E_None )
nError = rtl_digest_updateMD5( m_aDigest, nPerm , sizeof( nPerm ) );
//step 5, get the document ID, binary form
if( nError == rtl_Digest_E_None )
nError = rtl_digest_updateMD5( m_aDigest, m_nDocID , sizeof( m_nDocID ) );
//get the digest
sal_uInt8 nMD5Sum[ RTL_DIGEST_LENGTH_MD5 ];
if( nError == rtl_Digest_E_None )
{
rtl_digest_getMD5( m_aDigest, nMD5Sum, sizeof( nMD5Sum ) );
//step 6, only if 128 bit
if( m_aContext.Security128bit )
{
for( sal_Int32 i = 0; i < 50; i++ )
{
nError = rtl_digest_updateMD5( m_aDigest, &nMD5Sum, sizeof( nMD5Sum ) );
if( nError != rtl_Digest_E_None )
break;
rtl_digest_getMD5( m_aDigest, nMD5Sum, sizeof( nMD5Sum ) );
}
}
}
//Step 7
for( sal_Int32 i = 0; i < MD5_DIGEST_SIZE; i++ )
paEncryptionKey[i] = nMD5Sum[i];
}
}
/**********************************
Algorithm 3.3 Compute the encryption dictionary /O value, save into the class data member
the step numbers down here correspond to the ones in PDF v.1.4 specfication
*/
void PDFWriterImpl::computeODictionaryValue()
{
//step 1 already done, data is in m_nPaddedOwnerPassword
//step 2
if( m_aDigest )
{
rtlDigestError nError = rtl_digest_updateMD5( m_aDigest, &m_nPaddedOwnerPassword, sizeof( m_nPaddedOwnerPassword ) );
if( nError == rtl_Digest_E_None )
{
sal_uInt8 nMD5Sum[ RTL_DIGEST_LENGTH_MD5 ];
rtl_digest_getMD5( m_aDigest, nMD5Sum, sizeof(nMD5Sum) );
//step 3, only if 128 bit
if( m_aContext.Security128bit )
{
sal_Int32 i;
for( i = 0; i < 50; i++ )
{
nError = rtl_digest_updateMD5( m_aDigest, nMD5Sum, sizeof( nMD5Sum ) );
if( nError != rtl_Digest_E_None )
break;
rtl_digest_getMD5( m_aDigest, nMD5Sum, sizeof( nMD5Sum ) );
}
}
//Step 4, the key is in nMD5Sum
//step 5 already done, data is in m_nPaddedUserPassword
//step 6
rtl_cipher_initARCFOUR( m_aCipher, rtl_Cipher_DirectionEncode,
nMD5Sum, m_nKeyLength , NULL, 0 );
// encrypt the user password using the key set above
rtl_cipher_encodeARCFOUR( m_aCipher, m_nPaddedUserPassword, sizeof( m_nPaddedUserPassword ), // the data to be encrypted
m_nEncryptedOwnerPassword, sizeof( m_nEncryptedOwnerPassword ) ); //encrypted data, stored in class data member
//Step 7, only if 128 bit
if( m_aContext.Security128bit )
{
sal_uInt32 i, y;
sal_uInt8 nLocalKey[ SECUR_128BIT_KEY ]; // 16 = 128 bit key
for( i = 1; i <= 19; i++ ) // do it 19 times, start with 1
{
for( y = 0; y < sizeof( nLocalKey ); y++ )
nLocalKey[y] = (sal_uInt8)( nMD5Sum[y] ^ i );
rtl_cipher_initARCFOUR( m_aCipher, rtl_Cipher_DirectionEncode,
nLocalKey, SECUR_128BIT_KEY, NULL, 0 ); //destination data area, on init can be NULL
rtl_cipher_encodeARCFOUR( m_aCipher, m_nEncryptedOwnerPassword, sizeof( m_nEncryptedOwnerPassword ), // the data to be encrypted
m_nEncryptedOwnerPassword, sizeof( m_nEncryptedOwnerPassword ) ); // encrypted data, can be the same as the input, encrypt "in place"
//step 8, store in class data member
}
}
}
}
}
/**********************************
Algorithms 3.4 and 3.5 Compute the encryption dictionary /U value, save into the class data member, revision 2 (40 bit) or 3 (128 bit)
*/
void PDFWriterImpl::computeUDictionaryValue()
{
//step 1, common to both 3.4 and 3.5
computeEncryptionKey( m_nPaddedUserPassword , m_nEncryptionKey );
if( m_aContext.Security128bit == false )
{
//3.4
//step 2 and 3
rtl_cipher_initARCFOUR( m_aCipher, rtl_Cipher_DirectionEncode,
m_nEncryptionKey, 5 , // key and key length
NULL, 0 ); //destination data area
// encrypt the user password using the key set above, save for later use
rtl_cipher_encodeARCFOUR( m_aCipher, m_nPadString, sizeof( m_nPadString ), // the data to be encrypted
m_nEncryptedUserPassword, sizeof( m_nEncryptedUserPassword ) ); //encrypted data, stored in class data member
}
else
{
//or 3.5, for 128 bit security
//step6, initilize the last 16 bytes of the encrypted user password to 0
for(sal_uInt32 i = MD5_DIGEST_SIZE; i < sizeof( m_nEncryptedUserPassword ); i++)
m_nEncryptedUserPassword[i] = 0;
//step 2
if( m_aDigest )
{
rtlDigestError nError = rtl_digest_updateMD5( m_aDigest, m_nPadString, sizeof( m_nPadString ) );
//step 3
if( nError == rtl_Digest_E_None )
nError = rtl_digest_updateMD5( m_aDigest, m_nDocID , sizeof(m_nDocID) );
sal_uInt8 nMD5Sum[ RTL_DIGEST_LENGTH_MD5 ];
rtl_digest_getMD5( m_aDigest, nMD5Sum, sizeof(nMD5Sum) );
//Step 4
rtl_cipher_initARCFOUR( m_aCipher, rtl_Cipher_DirectionEncode,
m_nEncryptionKey, SECUR_128BIT_KEY, NULL, 0 ); //destination data area
rtl_cipher_encodeARCFOUR( m_aCipher, nMD5Sum, sizeof( nMD5Sum ), // the data to be encrypted
m_nEncryptedUserPassword, sizeof( nMD5Sum ) ); //encrypted data, stored in class data member
//step 5
sal_uInt32 i, y;
sal_uInt8 nLocalKey[SECUR_128BIT_KEY];
for( i = 1; i <= 19; i++ ) // do it 19 times, start with 1
{
for( y = 0; y < sizeof( nLocalKey ) ; y++ )
nLocalKey[y] = (sal_uInt8)( m_nEncryptionKey[y] ^ i );
rtl_cipher_initARCFOUR( m_aCipher, rtl_Cipher_DirectionEncode,
nLocalKey, SECUR_128BIT_KEY, // key and key length
NULL, 0 ); //destination data area, on init can be NULL
rtl_cipher_encodeARCFOUR( m_aCipher, m_nEncryptedUserPassword, SECUR_128BIT_KEY, // the data to be encrypted
m_nEncryptedUserPassword, SECUR_128BIT_KEY ); // encrypted data, can be the same as the input, encrypt "in place"
}
}
}
}
/* init the encryption engine
1. init the document id, used both for building the document id and for building the encryption key(s)
2. build the encryption key following algorithms described in the PDF specification
*/
void PDFWriterImpl::initEncryption()
{
m_aOwnerPassword = m_aContext.OwnerPassword;
m_aUserPassword = m_aContext.UserPassword;
/* password stuff computing, before sending out anything */
DBG_ASSERT( m_aCipher != NULL, "PDFWriterImpl::initEncryption: a cipher (ARCFOUR) object is not available !" );
DBG_ASSERT( m_aDigest != NULL, "PDFWriterImpl::initEncryption: a digest (MD5) object is not available !" );
if( m_aCipher && m_aDigest )
{
//if there is no owner password, force it to the user password
if( m_aOwnerPassword.getLength() == 0 )
m_aOwnerPassword = m_aUserPassword;
initPadString();
/*
1) pad passwords
*/
padPassword( m_aOwnerPassword, m_nPaddedOwnerPassword );
padPassword( m_aUserPassword, m_nPaddedUserPassword );
/*
2) compute the access permissions, in numerical form
the default value depends on the revision 2 (40 bit) or 3 (128 bit security):
- for 40 bit security the unused bit must be set to 1, since they are not used
- for 128 bit security the same bit must be preset to 0 and set later if needed
according to the table 3.15, pdf v 1.4 */
m_nAccessPermissions = ( m_aContext.Security128bit ) ? 0xfffff0c0 : 0xffffffc0 ;
/* check permissions for 40 bit security case */
m_nAccessPermissions |= ( m_aContext.AccessPermissions.CanPrintTheDocument ) ? 1 << 2 : 0;
m_nAccessPermissions |= ( m_aContext.AccessPermissions.CanModifyTheContent ) ? 1 << 3 : 0;
m_nAccessPermissions |= ( m_aContext.AccessPermissions.CanCopyOrExtract ) ? 1 << 4 : 0;
m_nAccessPermissions |= ( m_aContext.AccessPermissions.CanAddOrModify ) ? 1 << 5 : 0;
m_nKeyLength = SECUR_40BIT_KEY;
m_nRC4KeyLength = SECUR_40BIT_KEY+5; // for this value see PDF spec v 1.4, algorithm 3.1 step 4, where n is 5
if( m_aContext.Security128bit )
{
m_nKeyLength = SECUR_128BIT_KEY;
m_nRC4KeyLength = 16; // for this value see PDF spec v 1.4, algorithm 3.1 step 4, where n is 16, thus maximum
// permitted value is 16
m_nAccessPermissions |= ( m_aContext.AccessPermissions.CanFillInteractive ) ? 1 << 8 : 0;
m_nAccessPermissions |= ( m_aContext.AccessPermissions.CanExtractForAccessibility ) ? 1 << 9 : 0;
m_nAccessPermissions |= ( m_aContext.AccessPermissions.CanAssemble ) ? 1 << 10 : 0;
m_nAccessPermissions |= ( m_aContext.AccessPermissions.CanPrintFull ) ? 1 << 11 : 0;
}
computeODictionaryValue();
computeUDictionaryValue();
//clear out exceding key values, prepares for generation number default to 0 as well
// see checkAndEnableStreamEncryption in pdfwriter_impl.hxx
sal_Int32 i, y;
for( i = m_nKeyLength, y = 0; y < 5 ; y++ )
m_nEncryptionKey[i++] = 0;
}
else //either no cipher or no digest or both, something is wrong with memory or something else
m_aContext.Encrypt = false; //then turn the encryption off
}
/* end i12626 methods */