diff options
author | Jens-Heiner Rechtien <hr@openoffice.org> | 2004-09-08 15:21:42 +0000 |
---|---|---|
committer | Jens-Heiner Rechtien <hr@openoffice.org> | 2004-09-08 15:21:42 +0000 |
commit | a921db6cce1298ad286cb778e5138d6a4ddc4508 (patch) | |
tree | 5248d7986c34033e3ad5a5620ec5bdc4ecf308ab /vcl/source | |
parent | 7da0fe5d7b135a4ae26ffefd25f0bd2ceaaa62f5 (diff) |
INTEGRATION: CWS pdf01 (1.62.32); FILE MERGED
2004/08/18 09:24:01 pl 1.62.32.45: enable compression for pages again
2004/08/07 13:18:44 pl 1.62.32.44: resolved merge problem
2004/08/06 22:05:00 pl 1.62.32.43: RESYNC: (1.67-1.70); FILE MERGED
2004/08/04 09:10:46 pl 1.62.32.42: #i32457# make wavy text lines look more like on conventional output devices
2004/08/04 08:45:36 pl 1.62.32.41: #i32452# use correct URL syntax on submit button
2004/07/08 13:36:44 pl 1.62.32.40: do not emit empty attribute object reference
2004/07/07 12:53:13 pl 1.62.32.39: add: SetCurrentStructureElement
2004/07/06 15:13:55 pl 1.62.32.38: implement arbitrary link property attribute ids
2004/07/05 16:02:14 pl 1.62.32.37: keep structure entry order
2004/07/05 13:50:33 pl 1.62.32.36: prepare link annotation attribute
2004/07/05 11:57:11 pl 1.62.32.35: fix an invalid pointer problem
2004/07/02 10:08:19 fme 1.62.32.34: pl: omit empty K entries in document structure
2004/07/02 09:12:32 pl 1.62.32.33: change NonStruct element behaviour
2004/07/01 16:44:16 pl 1.62.32.32: edit, combobox and dropdown listbox work on AR5 now
2004/06/30 15:14:44 pl 1.62.32.31: add: pushbutton with action
2004/06/30 10:53:51 pl 1.62.32.30: add: combobox
2004/06/23 19:06:01 pl 1.62.32.29: text color, corrected transparency
2004/06/22 18:10:47 pl 1.62.32.28: change controls to accomodate AR5
2004/06/17 17:19:05 pl 1.62.32.27: add: EditWidget
2004/06/16 16:56:35 pl 1.62.32.26: working radio buttons
2004/06/16 12:14:51 pl 1.62.32.25: do not emit widget field names as unicode
2004/06/16 09:29:50 pl 1.62.32.24: omit structure unless producing tagged pdf
2004/06/09 16:02:49 pl 1.62.32.23: add: RadioButtonWidget, corrected flags
2004/06/04 15:28:12 pl 1.62.32.22: add: appearance streams, checkbox
2004/06/04 11:20:05 pl 1.62.32.21: cleaned up warnings
2004/06/04 10:25:42 pl 1.62.32.20: #i29038# default pushbutton appearance
2004/05/28 15:20:24 pl 1.62.32.19: #i29581# add transparency groups
2004/05/18 13:57:02 pl 1.62.32.18: begin to implement PDF widgets
2004/05/17 16:31:14 pl 1.62.32.17: no border for links, applications draw link style
2004/05/13 10:16:48 pl 1.62.32.16: #i29128# merge from CWS pdfdrawlinebug
2004/05/12 17:14:42 pl 1.62.32.15: #i29090# add: file id
2004/05/11 15:15:19 pl 1.62.32.14: #i29037# add: transitional effects for PDF
2004/05/11 11:31:15 pl 1.62.32.13: #i28572# add: ActualText and Alt attributes
2004/05/10 15:23:20 pl 1.62.32.12: compile issue on Solaris
2004/05/07 15:11:54 pl 1.62.32.11: fix a rectangle format issue
2004/05/07 14:53:42 pl 1.62.32.10: #i28572# add: structure attributes
2004/05/05 14:18:27 pl 1.62.32.9: correct some typos that hindered tagged PDF from working
2004/05/05 11:51:49 pl 1.62.32.8: RESYNC: (1.66-1.67); FILE MERGED
2004/05/05 11:49:17 pl 1.62.32.7: clean up constructor
2004/04/30 13:54:16 pl 1.62.32.6: #i28572# PDF structure
2004/04/28 16:51:31 pl 1.62.32.5: compile on Solaris
2004/04/28 15:48:58 pl 1.62.32.4: #i28482# add: document notes for PDF export
2004/03/23 14:32:18 pl 1.62.32.3: RESYNC: (1.62-1.66); FILE MERGED
2004/02/02 13:44:20 pl 1.62.32.2: #i24992# add: outline functionality
2004/01/30 14:09:30 pl 1.62.32.1: #i24905# add links
Diffstat (limited to 'vcl/source')
-rw-r--r-- | vcl/source/gdi/pdfwriter_impl.cxx | 3550 |
1 files changed, 3363 insertions, 187 deletions
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index b76979cc228a..514781fc94ec 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -2,9 +2,9 @@ * * $RCSfile: pdfwriter_impl.cxx,v $ * - * $Revision: 1.71 $ + * $Revision: 1.72 $ * - * last change: $Author: kz $ $Date: 2004-08-30 16:21:37 $ + * last change: $Author: hr $ $Date: 2004-09-08 16:21:42 $ * * The Contents of this file are made available subject to the terms of * either of the following licenses @@ -76,21 +76,315 @@ #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 > 1) || defined DBG_UTIL -#define MARK( x ) emitComment( x ) -#else -#define MARK( x ) +#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.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.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 ); + + // preapare 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 ); +#if 0 + 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 ); #endif + 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(); + + aWriter.NewPage(); + aWriter.SetMapMode( MapMode( MAP_100TH_MM ) ); + aWriter.SetFont( Font( String( RTL_CONSTASCII_USTRINGPARAM( "Times" ) ), Size( 0, 500 ) ) ); + 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 ); + + 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 void appendHex( sal_Int8 nInt, OStringBuffer& rBuffer ) { static const sal_Char pHexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', @@ -148,18 +442,71 @@ static void appendName( const sal_Char* pStr, OStringBuffer& rBuffer ) } } -static void appendUnicodeTextString( const String& rString, OStringBuffer& rBuffer ) +static void appendUnicodeTextString( const rtl::OUString& rString, OStringBuffer& rBuffer ) { rBuffer.append( "<FEFF" ); - for( int i = 0; i < rString.Len(); i++ ) + const sal_Unicode* pStr = rString.getStr(); + sal_Int32 nLen = rString.getLength(); + for( int i = 0; i < nLen; i++ ) { - sal_Unicode aChar = rString.GetChar(i); + sal_Unicode aChar = pStr[i]; appendHex( (sal_Int8)(aChar >> 8), rBuffer ); appendHex( (sal_Int8)(aChar & 255 ), rBuffer ); } rBuffer.append( ">" ); } +static OString convertWidgetFieldName( const rtl::OUString& rString ) +{ + OStringBuffer aBuffer( rString.getLength()+64 ); + + const sal_Unicode* pStr = rString.getStr(); + sal_Int32 nLen = rString.getLength(); + for( int i = 0; i < nLen; i++ ) + { + sal_Unicode aChar = pStr[i]; + if( aChar < 256 ) + { + if( aChar == '.' || aChar == ' ' ) + aChar = '_'; + aBuffer.append( sal_Char(aChar) ); + } + else + { + aBuffer.append( "<U+" ); + appendHex( (sal_Int8)(aChar >> 8), aBuffer ); + appendHex( (sal_Int8)(aChar & 255 ), aBuffer ); + aBuffer.append( ">" ); + } + } + return aBuffer.makeStringAndClear(); +} + +static void appendFixedInt( sal_Int32 nValue, OStringBuffer& rBuffer, sal_Int32 nLog10Divisor = 1 ) +{ + if( nValue < 0 ) + { + rBuffer.append( '-' ); + nValue = -nValue; + } + sal_Int32 nFactor = 1; + while( nLog10Divisor-- ) + nFactor *= 10; + + sal_Int32 nInt = nValue / nFactor; + rBuffer.append( nInt ); + if( nFactor > 1 ) + { + sal_Int32 nDecimal = nValue % nFactor; + if( nDecimal ) + { + rBuffer.append( '.' ); + rBuffer.append( nDecimal ); + } + } +} + + // appends a double. PDF does not accept exponential format, only fixed point static void appendDouble( double fValue, OStringBuffer& rBuffer, int nPrecision = 5 ) { @@ -200,7 +547,7 @@ static void appendDouble( double fValue, OStringBuffer& rBuffer, int nPrecision nBound /= 10; } } - } +} static void appendColor( const Color& rColor, OStringBuffer& rBuffer ) @@ -353,10 +700,16 @@ PDFWriterImpl::PDFPage::PDFPage( PDFWriterImpl* pWriter, sal_Int32 nPageWidth, s 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_nBeginStreamPos( 0 ), + m_eTransition( PDFWriter::Regular ), + m_nTransTime( 0 ), + m_nDuration( 0 ) { + // object ref must be only ever updated in emit() + m_nPageObject = m_pWriter->createObject(); } PDFWriterImpl::PDFPage::~PDFPage() @@ -417,7 +770,6 @@ void PDFWriterImpl::PDFPage::endStream() bool PDFWriterImpl::PDFPage::emit(sal_Int32 nParentObject ) { // emit page object - m_nPageObject = m_pWriter->createObject(); if( ! m_pWriter->updateObject( m_nPageObject ) ) return false; OStringBuffer aLine; @@ -428,6 +780,9 @@ bool PDFWriterImpl::PDFPage::emit(sal_Int32 nParentObject ) " /Parent " ); aLine.append( nParentObject ); aLine.append( " 0 R\r\n" ); + aLine.append( " /Resources " ); + aLine.append( m_pWriter->getResourceDictObj() ); + aLine.append( " 0 R\r\n" ); if( m_nPageWidth && m_nPageHeight ) { aLine.append( " /MediaBox [ 0 0 " ); @@ -439,13 +794,113 @@ bool PDFWriterImpl::PDFPage::emit(sal_Int32 nParentObject ) switch( m_eOrientation ) { case PDFWriter::Landscape: aLine.append( " /Rotate 90\r\n" );break; - case PDFWriter::Seascape: aLine.append( " /Rotate -90\r\n" );break; - case PDFWriter::Portrait: aLine.append( " /Rotate 0\r\n" );break; + case PDFWriter::Seascape: aLine.append( " /Rotate -90\r\n" );break; + case PDFWriter::Portrait: aLine.append( " /Rotate 0\r\n" );break; case PDFWriter::Inherit: default: break; } + int nAnnots = m_aAnnotations.size(); + if( nAnnots > 0 ) + { + aLine.append( " /Annots [ " ); + for( int i = 0; i < nAnnots; i++ ) + { + aLine.append( m_aAnnotations[i] ); + aLine.append( " 0 R" ); + aLine.append( ((i%5)==4) ? "\r\n " : " " ); + } + aLine.append( "]\r\n" ); + } + if( m_aMCIDParents.size() > 0 ) + { + aLine.append( " /StructParents " ); + aLine.append( m_nPageIndex ); + aLine.append( "\r\n" ); + } + if( m_nDuration > 0 ) + { + aLine.append( " /Dur " ); + aLine.append( (sal_Int32)m_nDuration ); + aLine.append( "\r\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( "\r\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; + 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( "\r\n" ); + } + if( pDm ) + { + aLine.append( " /Dm /" ); + aLine.append( pDm ); + aLine.append( "\r\n" ); + } + if( pM ) + { + aLine.append( " /M /" ); + aLine.append( pM ); + aLine.append( "\r\n" ); + } + if( pDi ) + { + aLine.append( " /Di " ); + aLine.append( pDi ); + aLine.append( "\r\n" ); + } + aLine.append( " >>\r\n" ); + } + if( m_pWriter->getVersion() > PDFWriter::PDF_1_3 ) + { + aLine.append( " /Group << /S /Transparency /CS /DeviceRGB /I true >>\r\n" ); + } aLine.append( " /Contents " ); aLine.append( m_nStreamObject ); aLine.append( " 0 R\r\n>>\r\nendobj\r\n\r\n" ); @@ -470,14 +925,13 @@ GEOMETRY lcl_convert( const MapMode& _rSource, const MapMode& _rDest, OutputDevi } } -void PDFWriterImpl::PDFPage::appendPoint( const Point& rPoint, OStringBuffer& rBuffer, bool bNeg, Point* pOutPoint ) +void PDFWriterImpl::PDFPage::appendPoint( const Point& rPoint, OStringBuffer& rBuffer, bool bNeg, Point* pOutPoint ) const { - Point aPoint( lcl_convert( - m_pWriter->m_aGraphicsStack.front().m_aMapMode, - m_pWriter->m_aMapMode, - m_pWriter->getReferenceDevice(), - rPoint - ) ); + Point aPoint( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, + m_pWriter->m_aMapMode, + m_pWriter->getReferenceDevice(), + rPoint ) ); + if( pOutPoint ) *pOutPoint = aPoint; @@ -485,42 +939,18 @@ void PDFWriterImpl::PDFPage::appendPoint( const Point& rPoint, OStringBuffer& rB if( bNeg ) nValue = -nValue; - if( nValue < 0 ) - { - rBuffer.append( '-' ); - nValue = -nValue; - } - sal_Int32 nInt = nValue / 10; - sal_Int32 nDecimal = nValue % 10; - rBuffer.append( nInt ); - if( nDecimal ) - { - rBuffer.append( '.' ); - rBuffer.append( nDecimal ); - } + appendFixedInt( nValue, rBuffer ); rBuffer.append( ' ' ); - nValue = 10*(m_nPageHeight ? m_nPageHeight : m_pWriter->m_nInheritedPageHeight) - aPoint.Y(); + nValue = 10*getHeight() - aPoint.Y(); if( bNeg ) nValue = -nValue; - if( nValue < 0 ) - { - rBuffer.append( '-' ); - nValue = -nValue; - } - nInt = nValue / 10; - nDecimal = nValue % 10; - rBuffer.append( nInt ); - if( nDecimal ) - { - rBuffer.append( '.' ); - rBuffer.append( nDecimal ); - } + appendFixedInt( nValue, rBuffer ); } -void PDFWriterImpl::PDFPage::appendRect( const Rectangle& rRect, OStringBuffer& rBuffer ) +void PDFWriterImpl::PDFPage::appendRect( const Rectangle& rRect, OStringBuffer& rBuffer ) const { appendPoint( rRect.BottomLeft() + Point( 0, 1 ), rBuffer ); rBuffer.append( ' ' ); @@ -530,19 +960,24 @@ void PDFWriterImpl::PDFPage::appendRect( const Rectangle& rRect, OStringBuffer& rBuffer.append( " re" ); } -void PDFWriterImpl::PDFPage::convertRect( Rectangle& rRect ) +void PDFWriterImpl::PDFPage::convertRect( Rectangle& rRect ) const { - Rectangle aConvertRect( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, - m_pWriter->m_aMapMode, - m_pWriter->getReferenceDevice(), - rRect - ) ); - sal_Int32 nMirror = m_nPageHeight ? m_nPageHeight : m_pWriter->m_nInheritedPageHeight; - rRect = Rectangle( Point( aConvertRect.BottomLeft().X(), 10*nMirror-aConvertRect.BottomLeft().Y() ), - aConvertRect.GetSize() ); + 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() = 10*getHeight() - aLL.Y(); + rRect.Bottom() = rRect.Top() + aSize.Height(); } -void PDFWriterImpl::PDFPage::appendPolygon( const Polygon& rPoly, OStringBuffer& rBuffer, bool bClose ) +void PDFWriterImpl::PDFPage::appendPolygon( const Polygon& rPoly, OStringBuffer& rBuffer, bool bClose ) const { int nPoints = rPoly.GetSize(); /* @@ -588,14 +1023,14 @@ void PDFWriterImpl::PDFPage::appendPolygon( const Polygon& rPoly, OStringBuffer& } } -void PDFWriterImpl::PDFPage::appendPolyPolygon( const PolyPolygon& rPolyPoly, OStringBuffer& rBuffer, bool bClose ) +void PDFWriterImpl::PDFPage::appendPolyPolygon( const PolyPolygon& rPolyPoly, OStringBuffer& rBuffer, bool bClose ) const { int nPolygons = rPolyPoly.Count(); for( int n = 0; n < nPolygons; n++ ) appendPolygon( rPolyPoly[n], rBuffer, bClose ); } -void PDFWriterImpl::PDFPage::appendMappedLength( sal_Int32 nLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32* pOutLength ) +void PDFWriterImpl::PDFPage::appendMappedLength( sal_Int32 nLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32* pOutLength ) const { sal_Int32 nValue = nLength; if ( nLength < 0 ) @@ -611,18 +1046,11 @@ void PDFWriterImpl::PDFPage::appendMappedLength( sal_Int32 nLength, OStringBuffe nValue = bVertical ? aSize.Height() : aSize.Width(); if( pOutLength ) *pOutLength = (nLength < 0 ) ? -nValue : nValue; - sal_Int32 nInt = nValue / 10; - sal_Int32 nDecimal = nValue % 10; - rBuffer.append( nInt ); - if( nDecimal ) - { - rBuffer.append( '.' ); - rBuffer.append( nDecimal ); - } + appendFixedInt( nValue, rBuffer ); } -void PDFWriterImpl::PDFPage::appendMappedLength( double fLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32* pOutLength ) +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, @@ -634,7 +1062,7 @@ void PDFWriterImpl::PDFPage::appendMappedLength( double fLength, OStringBuffer& appendDouble( fLength, rBuffer ); } -void PDFWriterImpl::PDFPage::appendLineInfo( const LineInfo& rInfo, OStringBuffer& rBuffer ) +void PDFWriterImpl::PDFPage::appendLineInfo( const LineInfo& rInfo, OStringBuffer& rBuffer ) const { if( rInfo.GetStyle() == LINE_DASH ) { @@ -664,7 +1092,7 @@ void PDFWriterImpl::PDFPage::appendLineInfo( const LineInfo& rInfo, OStringBuffe rBuffer.append( "0 w\r\n" ); } -void PDFWriterImpl::PDFPage::appendWaveLine( sal_Int32 nWidth, sal_Int32 nY, sal_Int32 nDelta, OStringBuffer& rBuffer ) +void PDFWriterImpl::PDFPage::appendWaveLine( sal_Int32 nWidth, sal_Int32 nY, sal_Int32 nDelta, OStringBuffer& rBuffer ) const { if( nWidth <= 0 ) return; @@ -707,20 +1135,36 @@ void PDFWriterImpl::PDFPage::appendWaveLine( sal_Int32 nWidth, sal_Int32 nY, sal * class PDFWriterImpl */ -PDFWriterImpl::PDFWriterImpl( const OUString& rFilename, PDFWriter::PDFVersion eVersion, PDFWriter::Compression eCompression ) +PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext ) : m_pReferenceDevice( NULL ), m_aMapMode( MAP_POINT, Point(), Fraction( 1L, 10L ), Fraction( 1L, 10L ) ), + 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_eVersion( eVersion ), - m_eCompression( eCompression ), - m_aFileName( rFilename ), + m_nResourceDict( -1 ), + m_nFontResourceDict( -1 ), m_pCodec( NULL ) { +#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 ) ); @@ -730,12 +1174,12 @@ PDFWriterImpl::PDFWriterImpl( const OUString& rFilename, PDFWriter::PDFVersion e aState.m_aFont = aFont; m_aGraphicsStack.push_front( aState ); - oslFileError aError = osl_openFile( m_aFileName.pData, &m_aFile, osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ); + 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_aFileName.pData, &m_aFile, osl_File_OpenFlag_Write ); + 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 ); } @@ -747,7 +1191,7 @@ PDFWriterImpl::PDFWriterImpl( const OUString& rFilename, PDFWriter::PDFVersion e // write header OStringBuffer aBuffer( 20 ); aBuffer.append( "%PDF-" ); - switch( m_eVersion ) + switch( m_aContext.Version ) { case PDFWriter::PDF_1_2: aBuffer.append( "1.2" );break; case PDFWriter::PDF_1_3: aBuffer.append( "1.3" );break; @@ -762,6 +1206,9 @@ PDFWriterImpl::PDFWriterImpl( const OUString& rFilename, PDFWriter::PDFVersion e m_bOpen = false; return; } + + // insert outline root + m_aOutline.push_back( PDFOutlineEntry() ); } PDFWriterImpl::~PDFWriterImpl() @@ -779,11 +1226,11 @@ void PDFWriterImpl::setDocInfo( const PDFDocInfo& rInfo ) m_aDocInfo.Producer = rInfo.Producer; } -void PDFWriterImpl::emitComment( const OString& rComment ) +void PDFWriterImpl::emitComment( const char* pComment ) { - OStringBuffer aLine( rComment.getLength()+5 ); + OStringBuffer aLine( 64 ); aLine.append( "% " ); - aLine.append( rComment ); + aLine.append( (const sal_Char*)pComment ); aLine.append( "\r\n" ); writeBuffer( aLine.getStr(), aLine.getLength() ); } @@ -822,6 +1269,13 @@ bool PDFWriterImpl::writeBuffer( const void* pBuffer, sal_uInt64 nBytes ) 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, nBytes ); + return true; + } + sal_uInt64 nWritten; if( m_pCodec ) { @@ -851,13 +1305,7 @@ OutputDevice* PDFWriterImpl::getReferenceDevice() m_pReferenceDevice = pVDev; - VirtualDevice::RefDevMode eMode = VirtualDevice::REFDEV_MODE06; - switch( m_eCompression ) - { - case( PDFWriter::Print ): eMode = VirtualDevice::REFDEV_MODE48; break; - case( PDFWriter::Press ): eMode = VirtualDevice::REFDEV_MODE96; break; - } - pVDev->SetReferenceDevice( eMode ); + pVDev->SetReferenceDevice( VirtualDevice::REFDEV_MODE96 ); pVDev->SetOutputSizePixel( Size( 640, 480 ) ); pVDev->SetMapMode( MAP_MM ); @@ -1098,20 +1546,33 @@ SalLayout* PDFWriterImpl::GetTextLayout( ImplLayoutArgs& rArgs, ImplFontSelectDa sal_Int32 PDFWriterImpl::newPage( sal_Int32 nPageWidth, sal_Int32 nPageHeight, PDFWriter::Orientation eOrientation ) { endPage(); + m_nCurrentPage = m_aPages.size(); m_aPages.push_back( PDFPage(this, nPageWidth, nPageHeight, eOrientation ) ); + m_aPages.back().m_nPageIndex = m_nCurrentPage; m_aPages.back().beginStream(); // setup global graphics state // linewidth is 0 (as thin as possible) by default writeBuffer( "0 w\r\n", 5 ); - return ++m_nCurrentPage; + 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() ); @@ -1153,10 +1614,11 @@ void PDFWriterImpl::endPage() for( std::list<TransparencyEmit>::iterator t = m_aTransparentObjects.begin(); t != m_aTransparentObjects.end(); ++t ) { - if( t->m_aContentStream.getLength() ) + if( t->m_pContentStream ) { writeTransparentObject( *t ); - t->m_aContentStream = OStringBuffer(); + delete t->m_pContentStream; + t->m_pContentStream = NULL; } } } @@ -1187,6 +1649,352 @@ bool PDFWriterImpl::updateObject( sal_Int32 n ) #define CHECK_RETURN( x ) if( !(x) ) return 0 +sal_Int32 PDFWriterImpl::emitStructParentTree() +{ + OStringBuffer aLine( 1024 ); + sal_Int32 nParentTree = createObject(); + + aLine.append( nParentTree ); + aLine.append( " 0 obj\r\n" + "<< /Nums [\r\n" ); + int nPages = m_aPages.size(); + for( sal_Int32 n = 0; n < nPages; n++ ) + { + int nParents = m_aPages[n].m_aMCIDParents.size(); + if( nParents ) + { + DBG_ASSERT( m_aPages[n].m_nPageIndex == n, "wrong page index" ); + aLine.append( m_aPages[n].m_nPageIndex ); // actually page index must equal n + aLine.append( " [ " ); + for( int i = 0; i < nParents; i++ ) + { + aLine.append( m_aPages[n].m_aMCIDParents[i] ); + aLine.append( " 0 R" ); + aLine.append( ((i%10) == 9) ? "\r\n" : " " ); + } + aLine.append( "]\r\n" ); + } + } + aLine.append( "]\r\n>>\r\nendobj\r\n\r\n" ); + CHECK_RETURN( updateObject( nParentTree ) ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + return nParentTree; +} + +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::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( "\r\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() ) + { + sal_Int32 nRefObject = createObject(); + OStringBuffer aRef( 256 ); + aRef.append( nRefObject ); + aRef.append( " 0 obj\r\n" + "<< /Type /OBJR\r\n" + " /Obj " ); + aRef.append( m_aLinks[ nLink ].m_nObject ); + aRef.append( " 0 R\r\n" + ">>\r\n" + "endobj\r\n\r\n" + ); + updateObject( nRefObject ); + writeBuffer( aRef.getStr(), aRef.getLength() ); + + rEle.m_aKids.append( nRefObject ); + rEle.m_aKids.append( " 0 R " ); + } + else + { + DBG_ERROR( "unresolved link id for Link structure" ); +#if OSL_DEBUG_LEVEL > 1 + fprintf( stderr, "unresolved link id %ld for Link structure", nLink ); +#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( " ]\r\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\r\n" + "<< /O /Layout\r\n" ); + aLayout.append( ">>\r\nendobj\r\n\r\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\r\n" + "<< /O /List\r\n" ); + aList.append( ">>\r\nendobj\r\n\r\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\r\n" + "<< /O /Table\r\n" ); + aTable.append( ">>\r\nendobj\r\n\r\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\r\n" + "<< /Type " ); + if( rEle.m_nOwnElement == rEle.m_nParentElement ) + { + sal_Int32 nParentTree = emitStructParentTree(); + CHECK_RETURN( nParentTree ); + aLine.append( "/StructTreeRoot\r\n" ); + aLine.append( " /ParentTree " ); + aLine.append( nParentTree ); + aLine.append( " 0 R\r\n" ); + } + else + { + aLine.append( "/StructElem\r\n" + " /S /" ); + aLine.append( getStructureTag( rEle.m_eType ) ); + aLine.append( "\r\n" + " /P " ); + aLine.append( m_aStructure[ rEle.m_nParentElement ].m_nObject ); + aLine.append( " 0 R\r\n" + " /Pg " ); + aLine.append( rEle.m_nFirstPageObject ); + aLine.append( " 0 R\r\n" ); + if( rEle.m_aActualText.getLength() ) + { + aLine.append( " /ActualText " ); + appendUnicodeTextString( rEle.m_aActualText, aLine ); + aLine.append( "\r\n" ); + } + if( rEle.m_aAltText.getLength() ) + { + aLine.append( " /Alt " ); + appendUnicodeTextString( rEle.m_aAltText, aLine ); + aLine.append( "\r\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( "\r\n" ); + } + } + if( rEle.m_aKids.getLength() ) + { + aLine.append( " /K [ " ); + aLine.append( rEle.m_aKids.makeStringAndClear() ); + aLine.append( "]\r\n" ); + } + aLine.append( ">>\r\nendobj\r\n\r\n" ); + + CHECK_RETURN( updateObject( rEle.m_nObject ) ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + + return rEle.m_nObject; +} + bool PDFWriterImpl::emitGradients() { for( std::list<GradientEmit>::iterator it = m_aGradients.begin(); @@ -1207,18 +2015,18 @@ bool PDFWriterImpl::emitTilings() aTilingStream.setLength( 0 ); aTilingObj.setLength( 0 ); - sal_Int32 nX = (sal_Int32)it->m_aRectangle.BottomLeft().X(); - sal_Int32 nY = (sal_Int32)it->m_aRectangle.BottomLeft().Y()+1; + 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(); - appendDouble( (double)nW/10.0, aTilingStream, 1 ); + appendFixedInt( nW, aTilingStream ); aTilingStream.append( " 0 0 " ); - appendDouble( (double)nH/10.0, aTilingStream, 1 ); + appendFixedInt( nH, aTilingStream ); aTilingStream.append( ' ' ); - appendDouble( (double)nX/10.0, aTilingStream, 1 ); + appendFixedInt( nX, aTilingStream ); aTilingStream.append( ' ' ); - appendDouble( (double)nY/10.0, aTilingStream, 1 ); + appendFixedInt( nY, aTilingStream ); aTilingStream.append( " cm\r\n /Im" ); aTilingStream.append( it->m_nBitmapObject ); aTilingStream.append( " Do\r\n" ); @@ -1230,14 +2038,14 @@ bool PDFWriterImpl::emitTilings() " /PatternType 1\r\n" " /PaintType 1\r\n" " /TilingType 1\r\n" - " /BBox [ " ); - appendDouble( (double)nX/10.0, aTilingObj, 1 ); + " /BBox [" ); + appendFixedInt( nX, aTilingObj ); aTilingObj.append( ' ' ); - appendDouble( (double)nY/10.0, aTilingObj, 1 ); + appendFixedInt( nY, aTilingObj ); aTilingObj.append( ' ' ); - appendDouble( (double)(nX+nW)/10.0, aTilingObj, 1 ); + appendFixedInt( nX+nW, aTilingObj ); aTilingObj.append( ' ' ); - appendDouble( (double)(nY+nH)/10.0, aTilingObj, 1 ); + appendFixedInt( nY+nH, aTilingObj ); aTilingObj.append( " ]\r\n" " /XStep " ); appendDouble( (double)nW/10.0, aTilingObj, 1 ); @@ -1973,9 +2781,6 @@ sal_Int32 PDFWriterImpl::emitFonts() { sal_Int32 nFontDict = 0; - if( ! m_aSubsets.size() && ! m_aEmbeddedFonts.size() ) // no fonts - return 0; - if( ! m_pReferenceDevice->ImplGetGraphics() ) return 0; @@ -2142,23 +2947,34 @@ sal_Int32 PDFWriterImpl::emitFonts() } } - nFontDict = createObject(); - CHECK_RETURN( updateObject( nFontDict ) ); + nFontDict = getFontDictObj(); aLine.setLength( 0 ); aLine.append( nFontDict ); aLine.append( " 0 obj\r\n" - "<< " ); + "<<\r\n" ); for( std::map< sal_Int32, sal_Int32 >::iterator mit = aFontIDToObject.begin(); mit != aFontIDToObject.end(); ++mit ) { - aLine.append( "/F" ); + aLine.append( " /F" ); aLine.append( mit->first ); aLine.append( ' ' ); aLine.append( mit->second ); - aLine.append( " 0 R\r\n" - " " ); + aLine.append( " 0 R\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\r\n" ); + ImplPdfBuiltinFontData aZapfData(m_aBuiltinFonts[13]); + aLine.append( " /ZaDb " ); + aLine.append( emitBuiltinFont( &aZapfData ) ); + aLine.append( " 0 R\r\n" ); } aLine.append( ">>\r\n" "endobj\r\n\r\n" ); + CHECK_RETURN( updateObject( nFontDict ) ); CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); return nFontDict; @@ -2220,6 +3036,7 @@ sal_Int32 PDFWriterImpl::emitResources() // 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() @@ -2255,6 +3072,8 @@ sal_Int32 PDFWriterImpl::emitResources() aLine.append( ' ' ); aLine.append( t->m_nObject ); aLine.append( " 0 R\r\n " ); + if( t->m_nExtGStateObject > 0 ) + aExtGStates.push_back( t->m_nExtGStateObject ); } aLine.append( ">>\r\n" @@ -2263,8 +3082,34 @@ sal_Int32 PDFWriterImpl::emitResources() 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\r\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) ? "\r\n " : " " ); + aExtGStates.pop_front(); + i++; + } + aLine.append( "\r\n>>\r\nendobj\r\n\r\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + } + // emit Resource dict - sal_Int32 nResourceDict = createObject(); + sal_Int32 nResourceDict = getResourceDictObj(); CHECK_RETURN( updateObject( nResourceDict ) ); aLine.setLength( 0 ); aLine.append( nResourceDict ); @@ -2281,6 +3126,12 @@ sal_Int32 PDFWriterImpl::emitResources() aLine.append( nXObjectDict ); aLine.append( " 0 R\r\n" ); } + if( nExtGStateObject ) + { + aLine.append( " /ExtGState " ); + aLine.append( nExtGStateObject ); + aLine.append( " 0 R\r\n" ); + } if( nShadingDict ) { aLine.append( " /Shading " ); @@ -2293,9 +3144,9 @@ sal_Int32 PDFWriterImpl::emitResources() aLine.append( nPatternDict ); aLine.append( " 0 R\r\n" ); } - aLine.append( " /ProcSet [ /PDF " ); + aLine.append( " /ProcSet [ /PDF /Text " ); if( nXObjectDict ) - aLine.append( "/ImageC /ImageI " ); + aLine.append( "/ImageC /ImageI /ImageB " ); aLine.append( "]\r\n" ); aLine.append( ">>\r\n" "endobj\r\n\r\n" ); @@ -2303,6 +3154,998 @@ sal_Int32 PDFWriterImpl::emitResources() 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\r\n" ); + aLine.append( "<<\r\n" ); + if( ! rItem.m_aChildren.empty() ) + { + // children list: Count, First, Last + aLine.append( " /Count " ); + aLine.append( (sal_Int32)rItem.m_aChildren.size() ); + aLine.append( "\r\n" + " /First " ); + aLine.append( m_aOutline[rItem.m_aChildren.front()].m_nObject ); + aLine.append( " 0 R\r\n" + " /Last " ); + aLine.append( m_aOutline[rItem.m_aChildren.back()].m_nObject ); + aLine.append( " 0 R\r\n" ); + } + if( i > 0 ) + { + // Title, Dest, Parent, Prev, Next + aLine.append( " /Title " ); + appendUnicodeTextString( rItem.m_aTitle, aLine ); + aLine.append( "\r\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( "\r\n" ); + } + aLine.append( " /Parent " ); + aLine.append( rItem.m_nParentObject ); + aLine.append( " 0 R\r\n" ); + if( rItem.m_nPrevObject ) + { + aLine.append( " /Prev " ); + aLine.append( rItem.m_nPrevObject ); + aLine.append( " 0 R\r\n" ); + } + if( rItem.m_nNextObject ) + { + aLine.append( " /Next " ); + aLine.append( rItem.m_nNextObject ); + aLine.append( " 0 R\r\n" ); + } + } + aLine.append( ">>\r\nendobj\r\n\r\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\r\n" ); + aLine.append( "<< /Type /Annot\r\n" + " /Subtype /Link\r\n" + " /Border [0 0 0]\r\n" + " /Rect [" ); + + appendFixedInt( rLink.m_aRect.Left(), aLine ); + aLine.append( ' ' ); + appendFixedInt( rLink.m_aRect.Top(), aLine ); + aLine.append( ' ' ); + appendFixedInt( rLink.m_aRect.Right(), aLine ); + aLine.append( ' ' ); + appendFixedInt( rLink.m_aRect.Bottom(), aLine ); + aLine.append( "]\r\n" ); + if( rLink.m_nDest >= 0 ) + { + aLine.append( " /Dest " ); + appendDest( rLink.m_nDest, aLine ); + aLine.append( "\r\n" ); + } + else + { + aLine.append( " /A << /Type /Action\r\n" + " /S /URI\r\n" + " /URI (" ); + aLine.append( rtl::OUStringToOString( rLink.m_aURL, RTL_TEXTENCODING_ASCII_US ) ); + aLine.append( ")\r\n" + " >>\r\n" ); + } + aLine.append( ">>\r\nendobj\r\n\r\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\r\n" ); + aLine.append( "<< /Type /Annot\r\n" + " /Subtype /Text\r\n" + " /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( "]\r\n" ); + + // contents of the note (type text string) + aLine.append( " /Contents " ); + appendUnicodeTextString( rNote.m_aContents.Contents, aLine ); + aLine.append( "\r\n" ); + + // optional title + if( rNote.m_aContents.Title.Len() ) + { + aLine.append( " /T " ); + appendUnicodeTextString( rNote.m_aContents.Title, aLine ); + aLine.append( "\r\n" ); + } + + aLine.append( ">>\r\nendobj\r\n\r\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; + 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( ~0 ); + + // 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( 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 + fille with the background color but nor text. Urgh. + */ + rButton.m_aMKDict = "/BC [] /BG [] /CA ()"; +} + +void PDFWriterImpl::createDefaultEditAppearance( PDFWidget& rEdit, const PDFWriter::EditWidget& rWidget ) +{ + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + SvMemoryStream* pEditStream = new SvMemoryStream( 1024, 1024 ); + + push( ~0 ); + + // prepare font to use + Font aFont = replaceFont( rWidget.TextFont, rSettings.GetFieldFont() ); + aFont.SetName( String( RTL_CONSTASCII_USTRINGPARAM( "Helvetica" ) ) ); + + if( rWidget.Background || rWidget.Border ) + { + setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetDialogColor() ) : Color( COL_TRANSPARENT ) ); + setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : Color( COL_TRANSPARENT ) ); + drawRectangle( rEdit.m_aRect ); + + if( rWidget.Border ) + { + // adjust edit area accounting for border + sal_Int32 nDelta = aFont.GetHeight()/4; + if( nDelta < 1 ) + nDelta = 1; + rEdit.m_aRect.Left() += nDelta; + rEdit.m_aRect.Top() += nDelta; + rEdit.m_aRect.Right() -= nDelta; + rEdit.m_aRect.Bottom() -= nDelta; + } + } + + // prepare DA string + OStringBuffer aDA( 32 ); + appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA ); + aDA.append( " /HelvReg " ); + m_aPages[ m_nCurrentPage ].appendMappedLength( 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\r\nEMC\r\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( ~0 ); + + // prepare font to use + Font aFont = replaceFont( rWidget.TextFont, rSettings.GetFieldFont() ); + aFont.SetName( String( RTL_CONSTASCII_USTRINGPARAM( "Helvetica" ) ) ); + + if( rWidget.Background || rWidget.Border ) + { + setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetDialogColor() ) : Color( COL_TRANSPARENT ) ); + setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : Color( COL_TRANSPARENT ) ); + drawRectangle( rBox.m_aRect ); + + if( rWidget.Border ) + { + // adjust listbox area accounting for border + sal_Int32 nDelta = aFont.GetHeight()/4; + if( nDelta < 1 ) + nDelta = 1; + rBox.m_aRect.Left() += nDelta; + rBox.m_aRect.Top() += nDelta; + rBox.m_aRect.Right() -= nDelta; + rBox.m_aRect.Bottom() -= nDelta; + } + } + + 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\r\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\r\nEMC\r\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( 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( ~0 ); + + 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\r\n", 4 ); + 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 (8)"; + + rBox.m_aRect = aCheckRect; + + // create appearance streams + sal_Char cMark = '8'; + sal_Int32 nCharXOffset = 1000-m_aBuiltinFonts[13].m_aWidths[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\r\nq BT\r\n" ); + appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA ); + aDA.append( " /ZaDb " ); + m_aPages[ m_nCurrentPage ].appendMappedLength( aCheckRect.GetHeight(), aDA ); + aDA.append( " Tf\r\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\r\nET\r\nQ\r\nEMC\r\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\r\nEMC\r\n", 14 ); + 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( ~0 ); + + 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\r\n", 4 ); + 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 (l)"; + + rBox.m_aRect = aCheckRect; + + // create appearance streams + push( ~0 ); + SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 ); + + beginRedirect( pCheckStream, aCheckRect ); + aDA.append( "/Tx BMC\r\nq BT\r\n" ); + appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA ); + aDA.append( " /ZaDb " ); + m_aPages[m_nCurrentPage].appendMappedLength( aCheckRect.GetHeight(), aDA ); + aDA.append( " Tf\r\n0 0 Td\r\nET\r\nQ\r\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( "\r\nEMC\r\n", 7 ); + endRedirect(); + + pop(); + rBox.m_aAppearances[ "N" ][ rBox.m_aName ] = pCheckStream; + + SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 ); + beginRedirect( pUncheckStream, aCheckRect ); + writeBuffer( "/Tx BMC\r\nEMC\r\n", 14 ); + 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 <<\r\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; + + 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 ) ); + OStringBuffer aTranslation; + OStringBuffer aLine; + aLine.append( nObject ); + + aLine.append( " 0 obj\r\n" + "<< /Type /XObject\r\n" + " /Subtype /Form\r\n" + " /BBox [ 0 0 " ); + appendFixedInt( rWidget.m_aRect.GetWidth()-1, aLine ); + aLine.append( " " ); + appendFixedInt( rWidget.m_aRect.GetHeight()-1, aLine ); + aLine.append( " ]\r\n" + " /Resources " ); + aLine.append( getResourceDictObj() ); + aLine.append( " 0 R\r\n" + " /Length " ); + aLine.append( nStreamLen+aTranslation.getLength() ); + aLine.append( "\r\n" + ">>\r\nstream\r\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + CHECK_RETURN( writeBuffer( aTranslation.getStr(), aTranslation.getLength() ) ); + CHECK_RETURN( writeBuffer( pApppearanceStream->GetData(), nStreamLen ) ); + CHECK_RETURN( writeBuffer( "endstream\r\nendobj\r\n\r\n", 21 ) ); + + if( bUseSubDict ) + { + rAnnotDict.append( " /" ); + rAnnotDict.append( stream_it->first ); + rAnnotDict.append( " " ); + } + rAnnotDict.append( nObject ); + rAnnotDict.append( " 0 R" ); + + delete pApppearanceStream; + } + + rAnnotDict.append( bUseSubDict ? " >>\r\n" : "\r\n" ); + } + rAnnotDict.append( " >>\r\n" ); + if( aStandardAppearance.getLength() ) + { + rAnnotDict.append( " /AS /" ); + rAnnotDict.append( aStandardAppearance ); + rAnnotDict.append( "\r\n" ); + } + } + + return true; +} + +bool PDFWriterImpl::emitWidgetAnnotations() +{ + int nAnnots = m_aWidgets.size(); + for( int i = 0; i < nAnnots; i++ ) + { + PDFWidget& rWidget = m_aWidgets[i]; + + OStringBuffer aLine( 1024 ); + OStringBuffer aValue( 256 ); + aLine.append( rWidget.m_nObject ); + aLine.append( " 0 obj\r\n" + "<< " ); + // emit widget annotation only for terminal fields + if( rWidget.m_aKids.empty() ) + { + aLine.append( "/Type /Annot\r\n" + " /Subtype /Widget\r\n" + " /F 4\r\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( "]\r\n" + " " + ); + } + aLine.append( "/FT /" ); + switch( rWidget.m_eType ) + { + case PDFWriter::CheckBox: + case PDFWriter::RadioButton: + aValue.append( "/" ); + aValue.append( OUStringToOString( rWidget.m_aValue, RTL_TEXTENCODING_MS_1252 ) ); + case PDFWriter::PushButton: + aLine.append( "Btn" ); + break; + case PDFWriter::ListBox: + case PDFWriter::ComboBox: + if( rWidget.m_nFlags & 0x200000 ) // multiselect + { + aValue.append( "[ " ); + appendUnicodeTextString( rWidget.m_aValue, aValue ); + aValue.append( " ]" ); + } + else + appendUnicodeTextString( rWidget.m_aValue, aValue ); + aLine.append( "Ch" ); + break; + case PDFWriter::Edit: + aLine.append( "Tx" ); + appendUnicodeTextString( rWidget.m_aValue, aValue ); + break; + } + aLine.append( "\r\n" ); + aLine.append( " /P " ); + aLine.append( m_aPages[ rWidget.m_nPage ].m_nPageObject ); + aLine.append( " 0 R\r\n" ); + + if( rWidget.m_nParent ) + { + aLine.append( " /Parent " ); + aLine.append( rWidget.m_nParent ); + aLine.append( " 0 R\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%10) == 9) ? "\r\n" : " " ); + } + aLine.append( "]\r\n" ); + } + // 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( rWidget.m_eType != PDFWriter::CheckBox || rWidget.m_nRadioGroup == -1 ) + { + aLine.append( " /T (" ); + aLine.append( rWidget.m_aName ); + aLine.append( ")\r\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 " ); + appendUnicodeTextString( rWidget.m_aDescription, aLine ); + aLine.append( "\r\n" ); + } + + if( rWidget.m_nFlags ) + { + aLine.append( " /Ff " ); + aLine.append( rWidget.m_nFlags ); + aLine.append( "\r\n" ); + } + if( aValue.getLength() ) + { + OString aVal = aValue.makeStringAndClear(); + aLine.append( " /V " ); + aLine.append( aVal ); + aLine.append( "\r\n" + " /DV " ); + aLine.append( aVal ); + aLine.append( "\r\n" ); + } + if( rWidget.m_eType == PDFWriter::ListBox || rWidget.m_eType == PDFWriter::ComboBox ) + { + sal_Int32 nTI = -1; + aLine.append( " /Opt [\r\n" ); + sal_Int32 i = 0; + for( std::list< OUString >::const_iterator it = rWidget.m_aListEntries.begin(); it != rWidget.m_aListEntries.end(); ++it, ++i ) + { + appendUnicodeTextString( *it, aLine ); + aLine.append( "\r\n" ); + if( *it == rWidget.m_aValue ) + nTI = i; + } + aLine.append( "]\r\n" ); + if( nTI > 0 ) + { + aLine.append( " /TI " ); + aLine.append( nTI ); + aLine.append( "\r\n" ); + if( rWidget.m_nFlags & 0x200000 ) // Multiselect + { + aLine.append( " /I [" ); + aLine.append( nTI ); + aLine.append( "]\r\n" ); + } + } + } + if( rWidget.m_eType == PDFWriter::Edit && rWidget.m_nMaxLen > 0 ) + { + aLine.append( " /MaxLen " ); + aLine.append( rWidget.m_nMaxLen ); + aLine.append( "\r\n" ); + } + if( rWidget.m_eType == PDFWriter::PushButton ) + { + if( rWidget.m_aListEntries.empty() ) + { + // create a reset form action + aLine.append( " /AA << /D << /Type /Action" + " /S /ResetForm >> >>\r\n" ); + } + else + { + // create a submit form action + aLine.append( " /AA << /D << /Type /Action" + " /S /SubmitForm" + " /F (" ); + aLine.append( OUStringToOString( rWidget.m_aListEntries.front(), RTL_TEXTENCODING_ASCII_US ) ); + 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( " >> >>\r\n" ); + } + } + if( rWidget.m_aDAString.getLength() ) + { + aLine.append( " /DR << /Font " ); + aLine.append( getFontDictObj() ); + aLine.append( " 0 R >>\r\n" ); + aLine.append( " /DA (" ); + aLine.append( rWidget.m_aDAString ); + aLine.append( ")\r\n" ); + if( rWidget.m_nTextStyle & TEXT_DRAW_CENTER ) + aLine.append( " /Q 1\r\n" ); + else if( rWidget.m_nTextStyle & TEXT_DRAW_RIGHT ) + aLine.append( " /Q 2\r\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 ); + aLine.append( " >>\r\n" ); + } + + CHECK_RETURN( emitAppearances( rWidget, aLine ) ); + + aLine.append( ">>\r\n" + "endobj\r\n\r\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 @@ -2314,12 +4157,22 @@ bool PDFWriterImpl::emitCatalog() // 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::list<PDFPage>::iterator it = m_aPages.begin(); it != m_aPages.end(); ++it ) + for( std::vector<PDFPage>::iterator it = m_aPages.begin(); it != m_aPages.end(); ++it ) if( ! it->emit( nTreeNode ) ) return false; - sal_Int32 nResourceDict = emitResources(); + 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 ) ) @@ -2331,8 +4184,9 @@ bool PDFWriterImpl::emitCatalog() aLine.append( " 0 obj\r\n" ); aLine.append( "<< /Type /Pages\r\n" ); aLine.append( " /Resources " ); - aLine.append( nResourceDict ); + aLine.append( getResourceDictObj() ); aLine.append( " 0 R\r\n" ); + switch( m_eInheritedOrientation ) { case PDFWriter::Landscape: aLine.append( " /Rotate 90\r\n" );break; @@ -2349,7 +4203,7 @@ bool PDFWriterImpl::emitCatalog() aLine.append( m_nInheritedPageHeight ); aLine.append( " ]\r\n" " /Kids [ " ); - for( std::list<PDFPage>::const_iterator iter = m_aPages.begin(); iter != m_aPages.end(); ++iter ) + for( std::vector<PDFPage>::const_iterator iter = m_aPages.begin(); iter != m_aPages.end(); ++iter ) { aLine.append( iter->m_nPageObject ); aLine.append( " 0 R\r\n" @@ -2363,6 +4217,9 @@ bool PDFWriterImpl::emitCatalog() "endobj\r\n\r\n" ); CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + // emit annotation objects + CHECK_RETURN( emitAnnotations() ); + // emit Catalog m_nCatalogObject = createObject(); if( ! updateObject( m_nCatalogObject ) ) @@ -2373,21 +4230,56 @@ bool PDFWriterImpl::emitCatalog() "<< /Type /Catalog\r\n" " /Pages " ); aLine.append( nTreeNode ); - aLine.append( " 0 R\r\n" - ">>\r\n" + aLine.append( " 0 R\r\n" ); + if( nOutlineDict ) + { + aLine.append( " /Outlines " ); + aLine.append( nOutlineDict ); + aLine.append( " 0 R\r\n" ); + } + if( nStructureDict ) + { + aLine.append( " /StructTreeRoot " ); + aLine.append( nStructureDict ); + aLine.append( " 0 R\r\n" ); + } + if( m_aContext.Tagged && m_aContext.Version > PDFWriter::PDF_1_3 ) + { + aLine.append( " /MarkInfo << /Marked true >>\r\n" ); + } + if( m_aWidgets.size() > 0 ) + { + aLine.append( " /AcroForm << /Fields [\r\n" ); + int nWidgets = m_aWidgets.size(); + int nOut = 0; + for( int i = 0; i < nWidgets; i++ ) + { + // output only root fields + if( m_aWidgets[i].m_nParent < 1 ) + { + aLine.append( m_aWidgets[i].m_nObject ); + aLine.append( (nOut++ % 5)==4 ? " 0 R\r\n" : " 0 R " ); + } + } + aLine.append( "\r\n] /DR " ); + aLine.append( getResourceDictObj() ); + aLine.append( " 0 R /NeedAppearances true >>\r\n" ); + } + aLine.append( ">>\r\n" "endobj\r\n\r\n" ); CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); return true; } -sal_Int32 PDFWriterImpl::emitInfoDict() +sal_Int32 PDFWriterImpl::emitInfoDict( OString& rIDOut ) { sal_Int32 nObject = createObject(); if( updateObject( nObject ) ) { OStringBuffer aLine( 1024 ); + OStringBuffer aID( 1024 ); aLine.append( nObject ); aLine.append( " 0 obj\r\n" "<< " ); @@ -2395,36 +4287,42 @@ sal_Int32 PDFWriterImpl::emitInfoDict() { aLine.append( "/Title " ); appendUnicodeTextString( m_aDocInfo.Title, aLine ); + appendUnicodeTextString( m_aDocInfo.Title, aID ); aLine.append( "\r\n" ); } if( m_aDocInfo.Author.Len() ) { aLine.append( "/Author " ); appendUnicodeTextString( m_aDocInfo.Author, aLine ); + appendUnicodeTextString( m_aDocInfo.Author, aID ); aLine.append( "\r\n" ); } if( m_aDocInfo.Subject.Len() ) { aLine.append( "/Subject " ); appendUnicodeTextString( m_aDocInfo.Subject, aLine ); + appendUnicodeTextString( m_aDocInfo.Subject, aID ); aLine.append( "\r\n" ); } if( m_aDocInfo.Keywords.Len() ) { aLine.append( "/Keywords " ); appendUnicodeTextString( m_aDocInfo.Keywords, aLine ); + appendUnicodeTextString( m_aDocInfo.Keywords, aID ); aLine.append( "\r\n" ); } if( m_aDocInfo.Creator.Len() ) { aLine.append( "/Creator " ); appendUnicodeTextString( m_aDocInfo.Creator, aLine ); + appendUnicodeTextString( m_aDocInfo.Creator, aID ); aLine.append( "\r\n" ); } if( m_aDocInfo.Producer.Len() ) { aLine.append( "/Producer " ); appendUnicodeTextString( m_aDocInfo.Producer, aLine ); + appendUnicodeTextString( m_aDocInfo.Producer, aID ); aLine.append( "\r\n" ); } TimeValue aTVal, aGMT; @@ -2432,47 +4330,54 @@ sal_Int32 PDFWriterImpl::emitInfoDict() osl_getSystemTime( &aGMT ); osl_getLocalTimeFromSystemTime( &aGMT, &aTVal ); osl_getDateTimeFromTimeValue( &aTVal, &aDT ); - aLine.append( "/CreationDate (D:" ); - aLine.append( (sal_Char)('0' + ((aDT.Year/1000)%10)) ); - aLine.append( (sal_Char)('0' + ((aDT.Year/100)%10)) ); - aLine.append( (sal_Char)('0' + ((aDT.Year/10)%10)) ); - aLine.append( (sal_Char)('0' + ((aDT.Year)%10)) ); - aLine.append( (sal_Char)('0' + ((aDT.Month/10)%10)) ); - aLine.append( (sal_Char)('0' + ((aDT.Month)%10)) ); - aLine.append( (sal_Char)('0' + ((aDT.Day/10)%10)) ); - aLine.append( (sal_Char)('0' + ((aDT.Day)%10)) ); - aLine.append( (sal_Char)('0' + ((aDT.Hours/10)%10)) ); - aLine.append( (sal_Char)('0' + ((aDT.Hours)%10)) ); - aLine.append( (sal_Char)('0' + ((aDT.Minutes/10)%10)) ); - aLine.append( (sal_Char)('0' + ((aDT.Minutes)%10)) ); - aLine.append( (sal_Char)('0' + ((aDT.Seconds/10)%10)) ); - aLine.append( (sal_Char)('0' + ((aDT.Seconds)%10)) ); + OStringBuffer aDateString(64); + aDateString.append( "(D:" ); + aDateString.append( (sal_Char)('0' + ((aDT.Year/1000)%10)) ); + aDateString.append( (sal_Char)('0' + ((aDT.Year/100)%10)) ); + aDateString.append( (sal_Char)('0' + ((aDT.Year/10)%10)) ); + aDateString.append( (sal_Char)('0' + ((aDT.Year)%10)) ); + aDateString.append( (sal_Char)('0' + ((aDT.Month/10)%10)) ); + aDateString.append( (sal_Char)('0' + ((aDT.Month)%10)) ); + aDateString.append( (sal_Char)('0' + ((aDT.Day/10)%10)) ); + aDateString.append( (sal_Char)('0' + ((aDT.Day)%10)) ); + aDateString.append( (sal_Char)('0' + ((aDT.Hours/10)%10)) ); + aDateString.append( (sal_Char)('0' + ((aDT.Hours)%10)) ); + aDateString.append( (sal_Char)('0' + ((aDT.Minutes/10)%10)) ); + aDateString.append( (sal_Char)('0' + ((aDT.Minutes)%10)) ); + aDateString.append( (sal_Char)('0' + ((aDT.Seconds/10)%10)) ); + aDateString.append( (sal_Char)('0' + ((aDT.Seconds)%10)) ); sal_uInt32 nDelta = 0; if( aGMT.Seconds > aTVal.Seconds ) { - aLine.append( "-" ); + aDateString.append( "-" ); nDelta = aGMT.Seconds-aTVal.Seconds; } else if( aGMT.Seconds < aTVal.Seconds ) { - aLine.append( "+" ); + aDateString.append( "+" ); nDelta = aTVal.Seconds-aGMT.Seconds; } else - aLine.append( "Z" ); + aDateString.append( "Z" ); if( nDelta ) { - aLine.append( (sal_Char)('0' + ((nDelta/36000)%10)) ); - aLine.append( (sal_Char)('0' + ((nDelta/3600)%10)) ); - aLine.append( "'" ); - aLine.append( (sal_Char)('0' + ((nDelta/600)%6)) ); - aLine.append( (sal_Char)('0' + ((nDelta/60)%10)) ); + aDateString.append( (sal_Char)('0' + ((nDelta/36000)%10)) ); + aDateString.append( (sal_Char)('0' + ((nDelta/3600)%10)) ); + aDateString.append( "'" ); + aDateString.append( (sal_Char)('0' + ((nDelta/600)%6)) ); + aDateString.append( (sal_Char)('0' + ((nDelta/60)%10)) ); } - aLine.append( "')\r\n" ); + aDateString.append( "')" ); + aLine.append( "/CreationDate " ); + aLine.append( aDateString.getStr(), aDateString.getLength() ); + aLine.append( "\r\n" ); + aID.append( aDateString.getStr(), aDateString.getLength() ); aLine.append( ">>\r\nendobj\r\n\r\n" ); if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) ) nObject = 0; + + rIDOut = aID.makeStringAndClear(); } else nObject = 0; @@ -2483,7 +4388,8 @@ sal_Int32 PDFWriterImpl::emitInfoDict() bool PDFWriterImpl::emitTrailer() { // emit doc info - sal_Int32 nDocInfoObject = emitInfoDict(); + OString aInfoValuesOut; + sal_Int32 nDocInfoObject = emitInfoDict( aInfoValuesOut ); // emit xref table @@ -2512,6 +4418,34 @@ bool PDFWriterImpl::emitTrailer() CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); } + // setup document id + OStringBuffer aDocID(32); + rtlDigest aDigest = rtl_digest_createMD5(); + if( aDigest ) + { + sal_uInt64 nOffset = 0; + if( osl_File_E_None == osl_getFilePos( m_aFile, &nOffset ) ) + { + TimeValue aGMT; + osl_getSystemTime( &aGMT ); + rtlDigestError nError = rtl_digest_updateMD5( aDigest, &aGMT, sizeof( aGMT ) ); + if( nError == rtl_Digest_E_None ) + nError = rtl_digest_updateMD5( aDigest, m_aContext.URL.getStr(), m_aContext.URL.getLength()*sizeof(sal_Unicode) ); + if( nError == rtl_Digest_E_None ) + nError = rtl_digest_updateMD5( aDigest, &nOffset, sizeof( nOffset ) ); + if( nError == rtl_Digest_E_None ) + nError = rtl_digest_updateMD5( aDigest, aInfoValuesOut.getStr(), aInfoValuesOut.getLength() ); + if( nError == rtl_Digest_E_None ) + { + sal_uInt8 nMD5Sum[ RTL_DIGEST_LENGTH_MD5 ]; + rtl_digest_getMD5( aDigest, nMD5Sum, sizeof(nMD5Sum) ); + for( unsigned int i = 0; i < sizeof(nMD5Sum)/sizeof(nMD5Sum[0]); i++ ) + appendHex( nMD5Sum[i], aDocID ); + } + } + rtl_digest_destroyMD5( aDigest ); + } + // emit trailer aLine.setLength( 0 ); aLine.append( "trailer\r\n" @@ -2527,6 +4461,15 @@ bool PDFWriterImpl::emitTrailer() aLine.append( nDocInfoObject ); aLine.append( " 0 R\r\n" ); } + if( aDocID.getLength() ) + { + aLine.append( " /ID [ <" ); + aLine.append( aDocID.getStr(), aDocID.getLength() ); + aLine.append( ">\r\n" + " <" ); + aLine.append( aDocID.getStr(), aDocID.getLength() ); + aLine.append( "> ]\r\n" ); + } aLine.append( ">>\r\n" "startxref\r\n" ); aLine.append( (sal_Int64)nXRefOffset ); @@ -3684,10 +5627,12 @@ void PDFWriterImpl::drawTextLine( const Point& rPos, long nWidth, FontStrikeout (nLineHeight > 3) ) nLineHeight = 3; - long nLineWidth = nLineHeight; + long nLineWidth = getReferenceDevice()->mnDPIX/450; + if( ! nLineWidth ) + nLineWidth = 1; if ( eUnderline == UNDERLINE_BOLDWAVE ) - nLineWidth = 3*nLineWidth/2; + nLineWidth = 3*nLineWidth; m_aPages.back().appendMappedLength( (sal_Int32)nLineWidth, aLine ); aLine.append( " w " ); @@ -3999,6 +5944,9 @@ void PDFWriterImpl::drawPolyPolygon( const PolyPolygon& rPolyPoly ) void PDFWriterImpl::drawTransparent( const PolyPolygon& rPolyPoly, sal_uInt32 nTransparentPercent ) { + DBG_ASSERT( nTransparentPercent <= 100, "invalid alpha value" ); + nTransparentPercent = nTransparentPercent % 100; + MARK( "drawTransparent" ); updateGraphicsState(); @@ -4007,7 +5955,7 @@ void PDFWriterImpl::drawTransparent( const PolyPolygon& rPolyPoly, sal_uInt32 nT m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) ) return; - if( m_eVersion < PDFWriter::PDF_1_4 ) + if( m_aContext.Version < PDFWriter::PDF_1_4 ) { drawPolyPolygon( rPolyPoly ); return; @@ -4020,15 +5968,18 @@ void PDFWriterImpl::drawTransparent( const PolyPolygon& rPolyPoly, sal_uInt32 nT 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; + m_aTransparentObjects.back().m_pContentStream = new SvMemoryStream( 256, 256 ); // create XObject's content stream - m_aPages.back().appendPolyPolygon( rPolyPoly, m_aTransparentObjects.back().m_aContentStream ); + OStringBuffer aContent( 256 ); + m_aPages.back().appendPolyPolygon( rPolyPoly, aContent ); if( m_aCurrentPDFState.m_aLineColor != Color( COL_TRANSPARENT ) && m_aCurrentPDFState.m_aFillColor != Color( COL_TRANSPARENT ) ) - m_aTransparentObjects.back().m_aContentStream.append( " B*\r\n" ); + aContent.append( " B*\r\n" ); else if( m_aCurrentPDFState.m_aLineColor != Color( COL_TRANSPARENT ) ) - m_aTransparentObjects.back().m_aContentStream.append( " S\r\n" ); + aContent.append( " S\r\n" ); else - m_aTransparentObjects.back().m_aContentStream.append( " f*\r\n" ); + aContent.append( " f*\r\n" ); + m_aTransparentObjects.back().m_pContentStream->Write( aContent.getStr(), aContent.getLength() ); OStringBuffer aLine( 80 ); // insert XObject @@ -4038,6 +5989,114 @@ void PDFWriterImpl::drawTransparent( const PolyPolygon& rPolyPoly, sal_uInt32 nT 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() - 10*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() +{ + 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\r\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\r\n" ); + writeBuffer( aLine.getStr(), aLine.getLength() ); + } +} + void PDFWriterImpl::drawRectangle( const Rectangle& rRect ) { MARK( "drawRectangle" ); @@ -4422,10 +6481,10 @@ bool PDFWriterImpl::writeTransparentObject( TransparencyEmit& rObject ) CHECK_RETURN( updateObject( rObject.m_nObject ) ); OStringBuffer aProlog; - sal_Int32 nStateObject = createObject(); - aProlog.append( "/EGS" ); - aProlog.append( nStateObject ); - aProlog.append( " gs\r\n" ); + + rObject.m_pContentStream->Seek( STREAM_SEEK_TO_END ); + ULONG nSize = rObject.m_pContentStream->Tell(); + rObject.m_pContentStream->Seek( STREAM_SEEK_TO_BEGIN ); OStringBuffer aLine( 512 ); CHECK_RETURN( updateObject( rObject.m_nObject ) ); @@ -4434,28 +6493,25 @@ bool PDFWriterImpl::writeTransparentObject( TransparencyEmit& rObject ) "<< /Type /XObject\r\n" " /Subtype /Form\r\n" " /BBox [ " ); - appendDouble( ((double)rObject.m_aBoundRect.TopLeft().X())/10.0, aLine ); + appendFixedInt( rObject.m_aBoundRect.Left(), aLine ); aLine.append( ' ' ); - appendDouble( ((double)rObject.m_aBoundRect.TopLeft().Y())/10.0, aLine ); + appendFixedInt( rObject.m_aBoundRect.Top(), aLine ); aLine.append( ' ' ); - appendDouble( ((double)rObject.m_aBoundRect.BottomRight().X())/10.0, aLine ); + appendFixedInt( rObject.m_aBoundRect.Right(), aLine ); aLine.append( ' ' ); - appendDouble( ((double)rObject.m_aBoundRect.BottomRight().Y()+1)/10.0, aLine ); + appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aLine ); aLine.append( " ]\r\n" - " /Resources << /ExtGState << /EGS" ); - aLine.append( nStateObject ); - aLine.append( ' ' ); - aLine.append( nStateObject ); - aLine.append( " 0 R >> >>\r\n" ); - aLine.append( " /Group << /S /Transparency /CS /DeviceRGB >>\r\n" ); - aLine.append( " /Length " ); - aLine.append( (sal_Int32)(rObject.m_aContentStream.getLength()+aProlog.getLength()) ); + " /Resources " ); + aLine.append( getResourceDictObj() ); + aLine.append( " 0 R\r\n" + " /Length " ); + aLine.append( (sal_Int32)(nSize+aProlog.getLength()) ); aLine.append( "\r\n" ">>\r\n" "stream\r\n" ); CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); CHECK_RETURN( writeBuffer( aProlog.getStr(), aProlog.getLength() ) ); - CHECK_RETURN( writeBuffer( rObject.m_aContentStream.getStr(), rObject.m_aContentStream.getLength() ) ); + CHECK_RETURN( writeBuffer( rObject.m_pContentStream->GetData(), nSize ) ); aLine.setLength( 0 ); aLine.append( "endstream\r\n" "endobj\r\n\r\n" ); @@ -4463,17 +6519,62 @@ bool PDFWriterImpl::writeTransparentObject( TransparencyEmit& rObject ) // write ExtGState dict for this XObject aLine.setLength( 0 ); - CHECK_RETURN( updateObject( nStateObject ) ); - aLine.append( nStateObject ); + aLine.append( rObject.m_nExtGStateObject ); aLine.append( " 0 obj\r\n" - "<< /CA " ); - appendDouble( rObject.m_fAlpha, aLine ); - aLine.append( "\r\n" + "<< " ); + if( ! rObject.m_pSoftMaskStream ) + { + aLine.append( "/CA " ); + appendDouble( rObject.m_fAlpha, aLine ); + aLine.append( "\r\n" " /ca " ); - appendDouble( rObject.m_fAlpha, aLine ); - aLine.append( "\r\n" - ">>\r\n" + appendDouble( rObject.m_fAlpha, aLine ); + aLine.append( "\r\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 >>\r\n" ); + + OStringBuffer aMask; + aMask.append( nMaskObject ); + aMask.append( " 0 obj\r\n" + "<< /Type /XObject\r\n" + " /Subtype /Form\r\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( " ]\r\n" + " /Resources " ); + aMask.append( getResourceDictObj() ); + aMask.append( " 0 R\r\n" ); + aMask.append( " /Group << /S /Transparency /CS /DeviceRGB >>\r\n" ); + aMask.append( " /Length " ); + aMask.append( nMaskSize ); + aMask.append( "\r\n" + ">>\r\n" + "stream\r\n" ); + CHECK_RETURN( updateObject( nMaskObject ) ); + CHECK_RETURN( writeBuffer( aMask.getStr(), aMask.getLength() ) ); + CHECK_RETURN( writeBuffer( rObject.m_pSoftMaskStream->GetData(), nMaskSize ) ); + aMask.setLength( 0 ); + aMask.append( "endstream\r\n" + "endobj\r\n\r\n" ); + CHECK_RETURN( writeBuffer( aMask.getStr(), aMask.getLength() ) ); + } + aLine.append( ">>\r\n" "endobj\r\n\r\n" ); + CHECK_RETURN( updateObject( rObject.m_nExtGStateObject ) ); CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); return true; @@ -4595,7 +6696,7 @@ bool PDFWriterImpl::writeJPG( JPGEmit& rObject ) if( !!rObject.m_aMask ) { if( rObject.m_aMask.GetBitCount() == 1 || - ( rObject.m_aMask.GetBitCount() == 8 && m_eVersion >= PDFWriter::PDF_1_4 ) + ( rObject.m_aMask.GetBitCount() == 8 && m_aContext.Version >= PDFWriter::PDF_1_4 ) ) nMaskObject = createObject(); } @@ -4658,7 +6759,7 @@ bool PDFWriterImpl::writeBitmapObject( BitmapEmit& rObject, bool bMask ) aBitmap = rObject.m_aBitmap.GetBitmap(); if( rObject.m_aBitmap.IsAlpha() ) { - if( m_eVersion >= PDFWriter::PDF_1_4 ) + if( m_aContext.Version >= PDFWriter::PDF_1_4 ) bWriteMask = true; // else draw without alpha channel } @@ -4682,7 +6783,7 @@ bool PDFWriterImpl::writeBitmapObject( BitmapEmit& rObject, bool bMask ) } else { - if( m_eVersion < PDFWriter::PDF_1_4 || ! rObject.m_aBitmap.IsAlpha() ) + if( m_aContext.Version < PDFWriter::PDF_1_4 || ! rObject.m_aBitmap.IsAlpha() ) { aBitmap = rObject.m_aBitmap.GetMask(); aBitmap.Convert( BMP_CONVERSION_1BIT_THRESHOLD ); @@ -4782,12 +6883,12 @@ bool PDFWriterImpl::writeBitmapObject( BitmapEmit& rObject, bool bMask ) } } - if( ! bMask && m_eVersion > PDFWriter::PDF_1_2 ) + if( ! bMask && m_aContext.Version > PDFWriter::PDF_1_2 ) { if( bWriteMask ) { nMaskObject = createObject(); - if( rObject.m_aBitmap.IsAlpha() && m_eVersion > PDFWriter::PDF_1_3 ) + if( rObject.m_aBitmap.IsAlpha() && m_aContext.Version > PDFWriter::PDF_1_3 ) aLine.append( " /SMask " ); else aLine.append( " /Mask " ); @@ -5038,7 +7139,7 @@ void PDFWriterImpl::drawGradient( const Rectangle& rRect, const Gradient& rGradi { MARK( "drawGradient (Rectangle)" ); - if( m_eVersion == PDFWriter::PDF_1_2 ) + if( m_aContext.Version == PDFWriter::PDF_1_2 ) { drawRectangle( rRect ); return; @@ -5083,7 +7184,7 @@ void PDFWriterImpl::drawGradient( const PolyPolygon& rPolyPoly, const Gradient& { MARK( "drawGradient (PolyPolygon)" ); - if( m_eVersion == PDFWriter::PDF_1_2 ) + if( m_aContext.Version == PDFWriter::PDF_1_2 ) { drawPolyPolygon( rPolyPoly ); return; @@ -5376,7 +7477,7 @@ void PDFWriterImpl::updateGraphicsState() aLine.append( "\r\n" ); } - if( m_eVersion >= PDFWriter::PDF_1_4 && m_aCurrentPDFState.m_nTransparentPercent != rNewState.m_nTransparentPercent ) + if( m_aContext.Version >= PDFWriter::PDF_1_4 && m_aCurrentPDFState.m_nTransparentPercent != rNewState.m_nTransparentPercent ) { // TODO: switch extended graphicsstate } @@ -5466,3 +7567,1078 @@ bool PDFWriterImpl::intersectClipRegion( const Region& rRegion ) aRegion = getReferenceDevice()->PixelToLogic( aRegion, m_aMapMode ); 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; +} + +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 = 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\r\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 + if( rEle.m_nFirstPageObject == m_aPages[ m_nCurrentPage ].m_nPageObject ) + { + rEle.m_aKids.append( nMCID ); + rEle.m_aKids.append( " " ); + } + else + { + rEle.m_aKids.append( "<< /Type /MCR /Pg " ); + rEle.m_aKids.append( m_aPages[ m_nCurrentPage ].m_nPageObject ); + rEle.m_aKids.append( " 0 R /MCID " ); + rEle.m_aKids.append( nMCID ); + rEle.m_aKids.append( " >>\r\n" ); + } + + // 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\r\n", 5 ); + 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 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 string + m_aStructure[ rEle.m_nParentElement ].m_aKids.append( rEle.m_nObject ); + m_aStructure[ rEle.m_nParentElement ].m_aKids.append( " 0 R " ); + } + 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; +} + +sal_Int32 PDFWriterImpl::findRadioGroupWidget( sal_Int32 nRadioGroup ) +{ + sal_Int32 nRadioGroupWidget = -1; + + std::map< sal_Int32, sal_Int32 >::const_iterator it = m_aRadioGroupWidgets.find( nRadioGroup ); + + if( it == m_aRadioGroupWidgets.end() ) + { + m_aRadioGroupWidgets[ nRadioGroup ] = 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_aName = "RadioGroup"; + m_aWidgets.back().m_aName += OString::valueOf( nRadioGroup ); + m_aWidgets.back().m_nRadioGroup = nRadioGroup; + m_aWidgets.back().m_nFlags |= 0x00008000; + + } + 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).RadioGroup ); + + PDFWidget& rNewWidget = m_aWidgets[nNewWidget]; + rNewWidget.m_nObject = createObject(); + rNewWidget.m_aRect = rControl.Location; + rNewWidget.m_nPage = nPageNr; + rNewWidget.m_eType = rControl.getType(); + // 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 ); + + // 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.SubmitToURL.getLength() ) + rNewWidget.m_aListEntries.push_back( rBtn.SubmitToURL ); + 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 ); + rNewWidget.m_nParent = rRadioButton.m_nObject; + + rNewWidget.m_aValue = OUString( RTL_CONSTASCII_USTRINGPARAM( "Off" ) ); + if( ! rRadioButton.m_aValue.getLength() && rBtn.Selected ) + { + rNewWidget.m_aValue = OStringToOUString( rNewWidget.m_aName, RTL_TEXTENCODING_MS_1252 ); + rRadioButton.m_aValue = rNewWidget.m_aValue; + } + 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 ); + + 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(), 10*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\r\n", 9 ); +} + +bool PDFWriterImpl::endControlAppearance( PDFWriter::WidgetState eState ) +{ + bool bRet = false; + if( ! m_aOutputStreams.empty() ) + writeBuffer( "\r\nEMC\r\n", 7 ); + 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"; + aStyle = (eState == PDFWriter::Up) ? "Off" : rWidget.m_aName; + } + 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; +} |