diff options
Diffstat (limited to 'vcl/source/gdi/pdfwriter_impl.cxx')
-rw-r--r-- | vcl/source/gdi/pdfwriter_impl.cxx | 12314 |
1 files changed, 12314 insertions, 0 deletions
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx new file mode 100644 index 000000000000..5d75c829da8a --- /dev/null +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -0,0 +1,12314 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_vcl.hxx" + +#define _USE_MATH_DEFINES +#include <math.h> +#include <algorithm> + +#include <tools/urlobj.hxx> +#include <pdfwriter_impl.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <rtl/ustrbuf.hxx> +#include <tools/debug.hxx> +#include <tools/zcodec.hxx> +#include <tools/stream.hxx> +#include <i18npool/mslangid.hxx> +#include <vcl/virdev.hxx> +#include <vcl/bmpacc.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/image.hxx> +#include <vcl/outdev.h> +#include <vcl/sallayout.hxx> +#include <vcl/metric.hxx> +#include <vcl/fontsubset.hxx> +#include <vcl/textlayout.hxx> +#include <svsys.h> +#include <vcl/salgdi.hxx> +#include <vcl/svapp.hxx> +#include <osl/thread.h> +#include <osl/file.h> +#include <rtl/crc.h> +#include <rtl/digest.h> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/util/URL.hpp> +#include "cppuhelper/implbase1.hxx" +#include <icc/sRGB-IEC61966-2.1.hxx> +#include <vcl/lineinfo.hxx> +#include "vcl/strhelper.hxx" + +using namespace vcl; +using namespace rtl; + +#if (OSL_DEBUG_LEVEL < 2) +#define COMPRESS_PAGES +#else +#define DEBUG_DISABLE_PDFCOMPRESSION // also do not compress streams +#endif + +#ifdef DO_TEST_PDF +class PDFTestOutputStream : public PDFOutputStream +{ + public: + virtual ~PDFTestOutputStream(); + virtual void write( const com::sun::star::uno::Reference< com::sun::star::io::XOutputStream >& xStream ); +}; + +PDFTestOutputStream::~PDFTestOutputStream() +{ +} + +void PDFTestOutputStream::write( const com::sun::star::uno::Reference< com::sun::star::io::XOutputStream >& xStream ) +{ + OString aStr( "lalala\ntest\ntest\ntest" ); + com::sun::star::uno::Sequence< sal_Int8 > aData( aStr.getLength() ); + rtl_copyMemory( aData.getArray(), aStr.getStr(), aStr.getLength() ); + xStream->writeBytes( aData ); +} + +// this test code cannot be used to test PDF/A-1 because it forces +// control item (widgets) to bypass the structure controlling +// the embedding of such elements in actual run +void doTestCode() +{ + static const char* pHome = getenv( "HOME" ); + 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; + aContext.InitialPage = 2; + + PDFWriter aWriter( aContext ); + PDFDocInfo aDocInfo; + aDocInfo.Title = OUString( RTL_CONSTASCII_USTRINGPARAM( "PDF export test document" ) ); + aDocInfo.Producer = OUString( RTL_CONSTASCII_USTRINGPARAM( "VCL" ) ); + aWriter.SetDocInfo( aDocInfo ); + aWriter.NewPage( 595, 842 ); + aWriter.BeginStructureElement( PDFWriter::Document ); + // set duration of 3 sec for first page + aWriter.SetAutoAdvanceTime( 3 ); + aWriter.SetMapMode( MapMode( MAP_100TH_MM ) ); + + aWriter.SetFillColor( Color( COL_LIGHTRED ) ); + aWriter.SetLineColor( Color( COL_LIGHTGREEN ) ); + aWriter.DrawRect( Rectangle( Point( 2000, 200 ), Size( 8000, 3000 ) ), 5000, 2000 ); + + aWriter.SetFont( Font( String( RTL_CONSTASCII_USTRINGPARAM( "Times" ) ), Size( 0, 500 ) ) ); + aWriter.SetTextColor( Color( COL_BLACK ) ); + aWriter.SetLineColor( Color( COL_BLACK ) ); + aWriter.SetFillColor( Color( COL_LIGHTBLUE ) ); + + Rectangle aRect( Point( 5000, 5000 ), Size( 6000, 3000 ) ); + aWriter.DrawRect( aRect ); + aWriter.DrawText( aRect, String( RTL_CONSTASCII_USTRINGPARAM( "Link annot 1" ) ) ); + sal_Int32 nFirstLink = aWriter.CreateLink( aRect ); + PDFNote aNote; + aNote.Title = String( RTL_CONSTASCII_USTRINGPARAM( "A small test note" ) ); + aNote.Contents = String( RTL_CONSTASCII_USTRINGPARAM( "There is no business like show business like no business i know. Everything about it is appealing." ) ); + aWriter.CreateNote( Rectangle( Point( aRect.Right(), aRect.Top() ), Size( 6000, 3000 ) ), aNote ); + + Rectangle aTargetRect( Point( 3000, 23000 ), Size( 12000, 6000 ) ); + aWriter.SetFillColor( Color( COL_LIGHTGREEN ) ); + aWriter.DrawRect( aTargetRect ); + aWriter.DrawText( aTargetRect, String( RTL_CONSTASCII_USTRINGPARAM( "Dest second link" ) ) ); + sal_Int32 nSecondDest = aWriter.CreateDest( aTargetRect ); + + aWriter.BeginStructureElement( PDFWriter::Section ); + aWriter.BeginStructureElement( PDFWriter::Heading ); + aWriter.DrawText( Point(4500, 9000), String( RTL_CONSTASCII_USTRINGPARAM( "A small structure test" ) ) ); + aWriter.EndStructureElement(); + aWriter.BeginStructureElement( PDFWriter::Paragraph ); + aWriter.SetStructureAttribute( PDFWriter::WritingMode, PDFWriter::LrTb ); + aWriter.SetStructureAttribute( PDFWriter::TextDecorationType, PDFWriter::Underline ); + aWriter.DrawText( Rectangle( Point( 4500, 10000 ), Size( 12000, 6000 ) ), + String( RTL_CONSTASCII_USTRINGPARAM( "It was the best of PDF, it was the worst of PDF ... or so. This is a pretty nonsensical text to denote a paragraph. I suggest you stop reading it. Because if you read on you might get bored. So continue on your on risk. Hey, you're still here ? Why do you continue to read this as it is of no use at all ? OK, it's your time, but still... . Woah, i even get bored writing this, so let's end this here and now." ) ), + TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK + ); + aWriter.SetActualText( String( RTL_CONSTASCII_USTRINGPARAM( "It was the best of PDF, it was the worst of PDF ... or so. This is a pretty nonsensical text to denote a paragraph. I suggest you stop reading it. Because if you read on you might get bored. So continue on your on risk. Hey, you're still here ? Why do you continue to read this as it is of no use at all ? OK, it's your time, but still... . Woah, i even get bored writing this, so let's end this here and now." ) ) ); + aWriter.SetAlternateText( String( RTL_CONSTASCII_USTRINGPARAM( "This paragraph contains some lengthy nonsense to test structural element emission of PDFWriter." ) ) ); + aWriter.EndStructureElement(); + sal_Int32 nLongPara = aWriter.BeginStructureElement( PDFWriter::Paragraph ); + aWriter.SetStructureAttribute( PDFWriter::WritingMode, PDFWriter::LrTb ); + aWriter.DrawText( Rectangle( Point( 4500, 19000 ), Size( 12000, 1000 ) ), + String( RTL_CONSTASCII_USTRINGPARAM( "This paragraph is nothing special either but ends on the next page structurewise" ) ), + TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK + ); + + aWriter.NewPage( 595, 842 ); + // test AddStream interface + aWriter.AddStream( String( RTL_CONSTASCII_USTRINGPARAM( "text/plain" ) ), new PDFTestOutputStream(), true ); + // set transitional mode + aWriter.SetPageTransition( PDFWriter::WipeRightToLeft, 1500 ); + aWriter.SetMapMode( MapMode( MAP_100TH_MM ) ); + aWriter.SetTextColor( Color( COL_BLACK ) ); + aWriter.SetFont( Font( String( RTL_CONSTASCII_USTRINGPARAM( "Times" ) ), Size( 0, 500 ) ) ); + aWriter.DrawText( Rectangle( Point( 4500, 1500 ), Size( 12000, 3000 ) ), + String( RTL_CONSTASCII_USTRINGPARAM( "Here's where all things come to an end ... well at least the paragaph from the last page." ) ), + TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK + ); + aWriter.EndStructureElement(); + + aWriter.SetFillColor( Color( COL_LIGHTBLUE ) ); + // disable structure + aWriter.BeginStructureElement( PDFWriter::NonStructElement ); + aWriter.DrawRect( aRect ); + aWriter.BeginStructureElement( PDFWriter::Paragraph ); + aWriter.DrawText( aRect, String( RTL_CONSTASCII_USTRINGPARAM( "Link annot 2" ) ) ); + sal_Int32 nSecondLink = aWriter.CreateLink( aRect ); + + aWriter.SetFillColor( Color( COL_LIGHTGREEN ) ); + aWriter.BeginStructureElement( PDFWriter::ListItem ); + aWriter.DrawRect( aTargetRect ); + aWriter.DrawText( aTargetRect, String( RTL_CONSTASCII_USTRINGPARAM( "Dest first link" ) ) ); + sal_Int32 nFirstDest = aWriter.CreateDest( aTargetRect ); + // enable structure + aWriter.EndStructureElement(); + // add something to the long paragraph as an afterthought + sal_Int32 nSaveStruct = aWriter.GetCurrentStructureElement(); + aWriter.SetCurrentStructureElement( nLongPara ); + aWriter.DrawText( Rectangle( Point( 4500,4500 ), Size( 12000, 1000 ) ), + String( RTL_CONSTASCII_USTRINGPARAM( "Add something to the longish paragraph above." ) ), + TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK ); + aWriter.SetCurrentStructureElement( nSaveStruct ); + aWriter.EndStructureElement(); + aWriter.EndStructureElement(); + aWriter.BeginStructureElement( PDFWriter::Figure ); + aWriter.BeginStructureElement( PDFWriter::Caption ); + aWriter.DrawText( Point( 4500, 9000 ), String( RTL_CONSTASCII_USTRINGPARAM( "Some drawing stuff inside the structure" ) ) ); + aWriter.EndStructureElement(); + + // test clipping + basegfx::B2DPolyPolygon aClip; + basegfx::B2DPolygon aClipPoly; + aClipPoly.append( basegfx::B2DPoint( 8250, 9600 ) ); + aClipPoly.append( basegfx::B2DPoint( 16500, 11100 ) ); + aClipPoly.append( basegfx::B2DPoint( 8250, 12600 ) ); + aClipPoly.append( basegfx::B2DPoint( 4500, 11100 ) ); + aClipPoly.setClosed( true ); + //aClipPoly.flip(); + aClip.append( aClipPoly ); + + aWriter.Push( PUSH_CLIPREGION | PUSH_FILLCOLOR ); + aWriter.SetClipRegion( aClip ); + aWriter.DrawEllipse( Rectangle( Point( 4500, 9600 ), Size( 12000, 3000 ) ) ); + aWriter.MoveClipRegion( 1000, 500 ); + aWriter.SetFillColor( Color( COL_RED ) ); + aWriter.DrawEllipse( Rectangle( Point( 4500, 9600 ), Size( 12000, 3000 ) ) ); + aWriter.Pop(); + // test transparency + // draw background + Rectangle aTranspRect( Point( 7500, 13500 ), Size( 9000, 6000 ) ); + aWriter.SetFillColor( Color( COL_LIGHTRED ) ); + aWriter.DrawRect( aTranspRect ); + aWriter.BeginTransparencyGroup(); + + aWriter.SetFillColor( Color( COL_LIGHTGREEN ) ); + aWriter.DrawEllipse( aTranspRect ); + aWriter.SetTextColor( Color( COL_LIGHTBLUE ) ); + aWriter.DrawText( aTranspRect, + String( RTL_CONSTASCII_USTRINGPARAM( "Some transparent text" ) ), + TEXT_DRAW_CENTER | TEXT_DRAW_VCENTER | TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK ); + + aWriter.EndTransparencyGroup( aTranspRect, 50 ); + + // prepare an alpha mask + Bitmap aTransMask( Size( 256, 256 ), 8, &Bitmap::GetGreyPalette( 256 ) ); + BitmapWriteAccess* pAcc = aTransMask.AcquireWriteAccess(); + for( int nX = 0; nX < 256; nX++ ) + for( int nY = 0; nY < 256; nY++ ) + pAcc->SetPixel( nX, nY, BitmapColor( (BYTE)((nX+nY)/2) ) ); + aTransMask.ReleaseAccess( pAcc ); + aTransMask.SetPrefMapMode( MAP_MM ); + aTransMask.SetPrefSize( Size( 10, 10 ) ); + + aWriter.DrawBitmap( Point( 600, 13500 ), Size( 3000, 3000 ), aTransMask ); + + aTranspRect = Rectangle( Point( 4200, 13500 ), Size( 3000, 3000 ) ); + aWriter.SetFillColor( Color( COL_LIGHTRED ) ); + aWriter.DrawRect( aTranspRect ); + aWriter.SetFillColor( Color( COL_LIGHTGREEN ) ); + aWriter.DrawEllipse( aTranspRect ); + aWriter.SetTextColor( Color( COL_LIGHTBLUE ) ); + aWriter.DrawText( aTranspRect, + String( RTL_CONSTASCII_USTRINGPARAM( "Some transparent text" ) ), + TEXT_DRAW_CENTER | TEXT_DRAW_VCENTER | TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK ); + aTranspRect = Rectangle( Point( 1500, 16500 ), Size( 4800, 3000 ) ); + aWriter.SetFillColor( Color( COL_LIGHTRED ) ); + aWriter.DrawRect( aTranspRect ); + aWriter.BeginTransparencyGroup(); + aWriter.SetFillColor( Color( COL_LIGHTGREEN ) ); + aWriter.DrawEllipse( aTranspRect ); + aWriter.SetTextColor( Color( COL_LIGHTBLUE ) ); + aWriter.DrawText( aTranspRect, + String( RTL_CONSTASCII_USTRINGPARAM( "Some transparent text" ) ), + TEXT_DRAW_CENTER | TEXT_DRAW_VCENTER | TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK ); + aWriter.EndTransparencyGroup( aTranspRect, aTransMask ); + + Bitmap aImageBmp( Size( 256, 256 ), 24 ); + pAcc = aImageBmp.AcquireWriteAccess(); + pAcc->SetFillColor( Color( 0xff, 0, 0xff ) ); + pAcc->FillRect( Rectangle( Point( 0, 0 ), Size( 256, 256 ) ) ); + aImageBmp.ReleaseAccess( pAcc ); + BitmapEx aBmpEx( aImageBmp, AlphaMask( aTransMask ) ); + aWriter.DrawBitmapEx( Point( 1500, 19500 ), Size( 4800, 3000 ), aBmpEx ); + + + aWriter.EndStructureElement(); + aWriter.EndStructureElement(); + + LineInfo aLI( LINE_DASH, 3 ); + aLI.SetDashCount( 2 ); + aLI.SetDashLen( 50 ); + aLI.SetDotCount( 2 ); + aLI.SetDotLen( 25 ); + aLI.SetDistance( 15 ); + Point aLIPoints[] = { Point( 4000, 10000 ), + Point( 8000, 12000 ), + Point( 3000, 19000 ) }; + Polygon aLIPoly( 3, aLIPoints ); + aWriter.SetLineColor( Color( COL_BLUE ) ); + aWriter.SetFillColor(); + aWriter.DrawPolyLine( aLIPoly, aLI ); + + aLI.SetDashCount( 4 ); + aLIPoly.Move( 1000, 1000 ); + aWriter.DrawPolyLine( aLIPoly, aLI ); + + aWriter.NewPage( 595, 842 ); + aWriter.SetMapMode( MapMode( MAP_100TH_MM ) ); + Wallpaper aWall( aTransMask ); + aWall.SetStyle( WALLPAPER_TILE ); + aWriter.DrawWallpaper( Rectangle( Point( 4400, 4200 ), Size( 10200, 6300 ) ), aWall ); + + aWriter.Push( PUSH_ALL ); + aWriter.BeginPattern(Rectangle(Point(0,0),Size(2000,1000))); + aWriter.SetFillColor( Color( COL_RED ) ); + aWriter.SetLineColor( Color( COL_LIGHTBLUE ) ); + Point aFillPoints[] = { Point( 1000, 0 ), + Point( 0, 1000 ), + Point( 2000, 1000 ) }; + aWriter.DrawPolygon( Polygon( 3, aFillPoints ) ); + aWriter.DrawBitmap( Point( 200, 200 ), Size( 1600, 600 ), aTransMask ); + aWriter.DrawText( Rectangle( Point( 200, 200 ), Size( 1600, 600 ) ), String( RTL_CONSTASCII_USTRINGPARAM( "Pattern" ) ) ); + sal_Int32 nPattern = aWriter.EndPattern( SvtGraphicFill::Transform() ); + aWriter.Pop(); + Rectangle aPolyRect( Point( 3800, 11200 ), Size( 10200, 6300 ) ); + aWriter.DrawPolyPolygon( PolyPolygon( Polygon( aPolyRect ) ), nPattern, true ); + aWriter.SetFillColor(); + aWriter.SetLineColor( Color( COL_LIGHTBLUE ) ); + aWriter.DrawRect( aPolyRect ); + + aWriter.NewPage( 595, 842 ); + aWriter.SetMapMode( MapMode( MAP_100TH_MM ) ); + aWriter.SetFont( Font( String( RTL_CONSTASCII_USTRINGPARAM( "Times" ) ), Size( 0, 500 ) ) ); + aWriter.SetTextColor( Color( COL_BLACK ) ); + aRect = Rectangle( Point( 4500, 6000 ), Size( 6000, 1500 ) ); + aWriter.DrawRect( aRect ); + aWriter.DrawText( aRect, String( RTL_CONSTASCII_USTRINGPARAM( "www.heise.de" ) ) ); + sal_Int32 nURILink = aWriter.CreateLink( aRect ); + aWriter.SetLinkURL( nURILink, OUString( RTL_CONSTASCII_USTRINGPARAM( "http://www.heise.de" ) ) ); + + aWriter.SetLinkDest( nFirstLink, nFirstDest ); + aWriter.SetLinkDest( nSecondLink, nSecondDest ); + + // include a button + PDFWriter::PushButtonWidget aBtn; + aBtn.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "testButton" ) ); + aBtn.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "A test button" ) ); + aBtn.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "hit me" ) ); + aBtn.Location = Rectangle( Point( 4500, 9000 ), Size( 4500, 3000 ) ); + aBtn.Border = aBtn.Background = true; + aWriter.CreateControl( aBtn ); + + // include a uri button + PDFWriter::PushButtonWidget aUriBtn; + aUriBtn.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "wwwButton" ) ); + aUriBtn.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "A URI button" ) ); + aUriBtn.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "to www" ) ); + aUriBtn.Location = Rectangle( Point( 9500, 9000 ), Size( 4500, 3000 ) ); + aUriBtn.Border = aUriBtn.Background = true; + aUriBtn.URL = OUString( RTL_CONSTASCII_USTRINGPARAM( "http://www.heise.de" ) ); + aWriter.CreateControl( aUriBtn ); + + // include a dest button + PDFWriter::PushButtonWidget aDstBtn; + aDstBtn.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "destButton" ) ); + aDstBtn.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "A Dest button" ) ); + aDstBtn.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "to paragraph" ) ); + aDstBtn.Location = Rectangle( Point( 14500, 9000 ), Size( 4500, 3000 ) ); + aDstBtn.Border = aDstBtn.Background = true; + aDstBtn.Dest = nFirstDest; + aWriter.CreateControl( aDstBtn ); + + PDFWriter::CheckBoxWidget aCBox; + aCBox.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "textCheckBox" ) ); + aCBox.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "A test check box" ) ); + aCBox.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "check me" ) ); + aCBox.Location = Rectangle( Point( 4500, 13500 ), Size( 3000, 750 ) ); + aCBox.Checked = true; + aCBox.Border = aCBox.Background = false; + aWriter.CreateControl( aCBox ); + + PDFWriter::CheckBoxWidget aCBox2; + aCBox2.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "textCheckBox2" ) ); + aCBox2.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "Another test check box" ) ); + aCBox2.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "check me right" ) ); + aCBox2.Location = Rectangle( Point( 4500, 14250 ), Size( 3000, 750 ) ); + aCBox2.Checked = true; + aCBox2.Border = aCBox2.Background = false; + aCBox2.ButtonIsLeft = false; + aWriter.CreateControl( aCBox2 ); + + PDFWriter::RadioButtonWidget aRB1; + aRB1.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "rb1_1" ) ); + aRB1.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "radio 1 button 1" ) ); + aRB1.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "Despair" ) ); + aRB1.Location = Rectangle( Point( 4500, 15000 ), Size( 6000, 1000 ) ); + aRB1.Selected = true; + aRB1.RadioGroup = 1; + aRB1.Border = aRB1.Background = true; + aRB1.ButtonIsLeft = false; + aRB1.BorderColor = Color( COL_LIGHTGREEN ); + aRB1.BackgroundColor = Color( COL_LIGHTBLUE ); + aRB1.TextColor = Color( COL_LIGHTRED ); + aRB1.TextFont = Font( String( RTL_CONSTASCII_USTRINGPARAM( "Courier" ) ), Size( 0, 800 ) ); + aWriter.CreateControl( aRB1 ); + + PDFWriter::RadioButtonWidget aRB2; + aRB2.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "rb2_1" ) ); + aRB2.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "radio 2 button 1" ) ); + aRB2.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "Joy" ) ); + aRB2.Location = Rectangle( Point( 10500, 15000 ), Size( 3000, 1000 ) ); + aRB2.Selected = true; + aRB2.RadioGroup = 2; + aWriter.CreateControl( aRB2 ); + + PDFWriter::RadioButtonWidget aRB3; + aRB3.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "rb1_2" ) ); + aRB3.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "radio 1 button 2" ) ); + aRB3.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "Desperation" ) ); + aRB3.Location = Rectangle( Point( 4500, 16000 ), Size( 3000, 1000 ) ); + aRB3.Selected = true; + aRB3.RadioGroup = 1; + aWriter.CreateControl( aRB3 ); + + PDFWriter::EditWidget aEditBox; + aEditBox.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "testEdit" ) ); + aEditBox.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "A test edit field" ) ); + aEditBox.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "A little test text" ) ); + aEditBox.TextStyle = TEXT_DRAW_LEFT | TEXT_DRAW_VCENTER; + aEditBox.Location = Rectangle( Point( 10000, 18000 ), Size( 5000, 1500 ) ); + aEditBox.MaxLen = 100; + aEditBox.Border = aEditBox.Background = true; + aEditBox.BorderColor = Color( COL_BLACK ); + aWriter.CreateControl( aEditBox ); + + // normal list box + PDFWriter::ListBoxWidget aLstBox; + aLstBox.Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "testListBox" ) ); + aLstBox.Text = OUString( RTL_CONSTASCII_USTRINGPARAM( "One" ) ); + aLstBox.Description = OUString( RTL_CONSTASCII_USTRINGPARAM( "select me" ) ); + aLstBox.Location = Rectangle( Point( 4500, 18000 ), Size( 3000, 1500 ) ); + aLstBox.Sort = true; + aLstBox.MultiSelect = true; + aLstBox.Border = aLstBox.Background = true; + aLstBox.BorderColor = Color( COL_BLACK ); + aLstBox.Entries.push_back( OUString( RTL_CONSTASCII_USTRINGPARAM( "One" ) ) ); + aLstBox.Entries.push_back( OUString( RTL_CONSTASCII_USTRINGPARAM( "Two" ) ) ); + aLstBox.Entries.push_back( OUString( RTL_CONSTASCII_USTRINGPARAM( "Three" ) ) ); + aLstBox.Entries.push_back( OUString( RTL_CONSTASCII_USTRINGPARAM( "Four" ) ) ); + aLstBox.SelectedEntries.push_back( 1 ); + aLstBox.SelectedEntries.push_back( 2 ); + 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.EndStructureElement(); // close document + aWriter.Emit(); +} +#endif + +static const sal_Int32 nLog10Divisor = 1; +static const double fDivisor = 10.0; + +static inline double pixelToPoint( sal_Int32 px ) { return double(px)/fDivisor; } +static inline double pixelToPoint( double px ) { return px/fDivisor; } +static inline sal_Int32 pointToPixel( double pt ) { return sal_Int32(pt*fDivisor); } + +static void appendHex( sal_Int8 nInt, OStringBuffer& rBuffer ) +{ + static const sal_Char pHexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + rBuffer.append( pHexDigits[ (nInt >> 4) & 15 ] ); + rBuffer.append( pHexDigits[ nInt & 15 ] ); +} + +static void appendName( const OUString& rStr, OStringBuffer& rBuffer ) +{ +// FIXME i59651 add a check for max length of 127 chars? Per PDF spec 1.4, appendix C.1 +// I guess than when reading the #xx sequence it will count for a single character. + OString aStr( OUStringToOString( rStr, RTL_TEXTENCODING_UTF8 ) ); + const sal_Char* pStr = aStr.getStr(); + int nLen = aStr.getLength(); + for( int i = 0; i < nLen; i++ ) + { + /* #i16920# PDF recommendation: output UTF8, any byte + * outside the interval [33(=ASCII'!');126(=ASCII'~')] + * should be escaped hexadecimal + * for the sake of ghostscript which also reads PDF + * but has a narrower acceptance rate we only pass + * alphanumerics and '-' literally. + */ + if( (pStr[i] >= 'A' && pStr[i] <= 'Z' ) || + (pStr[i] >= 'a' && pStr[i] <= 'z' ) || + (pStr[i] >= '0' && pStr[i] <= '9' ) || + pStr[i] == '-' ) + { + rBuffer.append( pStr[i] ); + } + else + { + rBuffer.append( '#' ); + appendHex( (sal_Int8)pStr[i], rBuffer ); + } + } +} + +static void appendName( const sal_Char* pStr, OStringBuffer& rBuffer ) +{ +//FIXME i59651 see above + while( pStr && *pStr ) + { + if( (*pStr >= 'A' && *pStr <= 'Z' ) || + (*pStr >= 'a' && *pStr <= 'z' ) || + (*pStr >= '0' && *pStr <= '9' ) || + *pStr == '-' ) + { + rBuffer.append( *pStr ); + } + else + { + rBuffer.append( '#' ); + appendHex( (sal_Int8)*pStr, rBuffer ); + } + pStr++; + } +} + +//used only to emit encoded passwords +static void appendLiteralString( const sal_Char* pStr, sal_Int32 nLength, OStringBuffer& rBuffer ) +{ + while( nLength ) + { + switch( *pStr ) + { + case '\n' : + rBuffer.append( "\\n" ); + break; + case '\r' : + rBuffer.append( "\\r" ); + break; + case '\t' : + rBuffer.append( "\\t" ); + break; + case '\b' : + rBuffer.append( "\\b" ); + break; + case '\f' : + rBuffer.append( "\\f" ); + break; + case '(' : + case ')' : + case '\\' : + rBuffer.append( "\\" ); + rBuffer.append( (sal_Char) *pStr ); + break; + default: + rBuffer.append( (sal_Char) *pStr ); + break; + } + pStr++; + nLength--; + } +} + +/**--->i56629 + * Convert a string before using it. + * + * This string conversion function is needed because the destination name + * in a PDF file seen through an Internet browser should be + * specially crafted, in order to be used directly by the browser. + * In this way the fragment part of a hyperlink to a PDF file (e.g. something + * as 'test1/test2/a-file.pdf#thefragment) will be (hopefully) interpreted by the + * PDF reader (currently only Adobe Reader plug-in seems to be working that way) called + * from inside the Internet browser as: 'open the file test1/test2/a-file.pdf + * and go to named destination thefragment using default zoom'. + * The conversion is needed because in case of a fragment in the form: Slide%201 + * (meaning Slide 1) as it is converted obeying the Inet rules, it will become Slide25201 + * using this conversion, in both the generated named destinations, fragment and GoToR + * destination. + * + * The names for destinations are name objects and so they don't need to be encrypted + * even though they expose the content of PDF file (e.g. guessing the PDF content from the + * destination name). + * + * Fhurter limitation: it is advisable to use standard ASCII characters for + * OOo bookmarks. +*/ +static void appendDestinationName( const rtl::OUString& rString, OStringBuffer& rBuffer ) +{ + const sal_Unicode* pStr = rString.getStr(); + sal_Int32 nLen = rString.getLength(); + for( int i = 0; i < nLen; i++ ) + { + sal_Unicode aChar = pStr[i]; + if( (aChar >= '0' && aChar <= '9' ) || + (aChar >= 'a' && aChar <= 'z' ) || + (aChar >= 'A' && aChar <= 'Z' ) || + aChar == '-' ) + { + rBuffer.append((sal_Char)aChar); + } + else + { + sal_Int8 aValueHigh = sal_Int8(aChar >> 8); + if(aValueHigh > 0) + appendHex( aValueHigh, rBuffer ); + appendHex( (sal_Int8)(aChar & 255 ), rBuffer ); + } + } +} +//<--- i56629 + +static void appendUnicodeTextString( const rtl::OUString& rString, OStringBuffer& rBuffer ) +{ + rBuffer.append( "FEFF" ); + const sal_Unicode* pStr = rString.getStr(); + sal_Int32 nLen = rString.getLength(); + for( int i = 0; i < nLen; i++ ) + { + sal_Unicode aChar = pStr[i]; + appendHex( (sal_Int8)(aChar >> 8), rBuffer ); + appendHex( (sal_Int8)(aChar & 255 ), rBuffer ); + } +} + +void PDFWriterImpl::createWidgetFieldName( sal_Int32 i_nWidgetIndex, const PDFWriter::AnyWidget& i_rControl ) +{ + /* #i80258# previously we use appendName here + however we need a slightly different coding scheme than the normal + name encoding for field names + */ + const OUString& rName = (m_aContext.Version > PDFWriter::PDF_1_2) ? i_rControl.Name : i_rControl.Text; + OString aStr( OUStringToOString( rName, RTL_TEXTENCODING_UTF8 ) ); + const sal_Char* pStr = aStr.getStr(); + int nLen = aStr.getLength(); + + OStringBuffer aBuffer( rName.getLength()+64 ); + for( int i = 0; i < nLen; i++ ) + { + /* #i16920# PDF recommendation: output UTF8, any byte + * outside the interval [32(=ASCII' ');126(=ASCII'~')] + * should be escaped hexadecimal + */ + if( (pStr[i] >= 32 && pStr[i] <= 126 ) ) + aBuffer.append( pStr[i] ); + else + { + aBuffer.append( '#' ); + appendHex( (sal_Int8)pStr[i], aBuffer ); + } + } + + OString aFullName( aBuffer.makeStringAndClear() ); + + /* #i82785# create hierarchical fields down to the for each dot in i_rName */ + sal_Int32 nTokenIndex = 0, nLastTokenIndex = 0; + OString aPartialName; + OString aDomain; + do + { + nLastTokenIndex = nTokenIndex; + aPartialName = aFullName.getToken( 0, '.', nTokenIndex ); + if( nTokenIndex != -1 ) + { + // find or create a hierarchical field + // first find the fully qualified name up to this field + aDomain = aFullName.copy( 0, nTokenIndex-1 ); + std::hash_map< rtl::OString, sal_Int32, rtl::OStringHash >::const_iterator it = m_aFieldNameMap.find( aDomain ); + if( it == m_aFieldNameMap.end() ) + { + // create new hierarchy field + sal_Int32 nNewWidget = m_aWidgets.size(); + m_aWidgets.push_back( PDFWidget() ); + m_aWidgets[nNewWidget].m_nObject = createObject(); + m_aWidgets[nNewWidget].m_eType = PDFWriter::Hierarchy; + m_aWidgets[nNewWidget].m_aName = aPartialName; + m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject; + m_aFieldNameMap[aDomain] = nNewWidget; + m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[nNewWidget].m_nObject; + if( nLastTokenIndex > 0 ) + { + // this field is not a root field and + // needs to be inserted to its parent + OString aParentDomain( aDomain.copy( 0, nLastTokenIndex-1 ) ); + it = m_aFieldNameMap.find( aParentDomain ); + OSL_ENSURE( it != m_aFieldNameMap.end(), "field name not found" ); + if( it != m_aFieldNameMap.end() ) + { + OSL_ENSURE( it->second < sal_Int32(m_aWidgets.size()), "invalid field number entry" ); + if( it->second < sal_Int32(m_aWidgets.size()) ) + { + PDFWidget& rParentField( m_aWidgets[it->second] ); + rParentField.m_aKids.push_back( m_aWidgets[nNewWidget].m_nObject ); + rParentField.m_aKidsIndex.push_back( nNewWidget ); + m_aWidgets[nNewWidget].m_nParent = rParentField.m_nObject; + } + } + } + } + else if( m_aWidgets[it->second].m_eType != PDFWriter::Hierarchy ) + { + // this is invalid, someone tries to have a terminal field as parent + // example: a button with the name foo.bar exists and + // another button is named foo.bar.no + // workaround: put the second terminal field as much up in the hierarchy as + // necessary to have a non-terminal field as parent (or none at all) + // since it->second already is terminal, we just need to use its parent + aDomain = OString(); + aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 ); + if( nLastTokenIndex > 0 ) + { + aDomain = aFullName.copy( 0, nLastTokenIndex-1 ); + OStringBuffer aBuf( aDomain.getLength() + 1 + aPartialName.getLength() ); + aBuf.append( aDomain ); + aBuf.append( '.' ); + aBuf.append( aPartialName ); + aFullName = aBuf.makeStringAndClear(); + } + else + aFullName = aPartialName; + break; + } + } + } while( nTokenIndex != -1 ); + + // insert widget into its hierarchy field + if( aDomain.getLength() ) + { + std::hash_map< rtl::OString, sal_Int32, rtl::OStringHash >::const_iterator it = m_aFieldNameMap.find( aDomain ); + if( it != m_aFieldNameMap.end() ) + { + OSL_ENSURE( it->second >= 0 && it->second < sal_Int32( m_aWidgets.size() ), "invalid field index" ); + if( it->second >= 0 && it->second < sal_Int32(m_aWidgets.size()) ) + { + m_aWidgets[i_nWidgetIndex].m_nParent = m_aWidgets[it->second].m_nObject; + m_aWidgets[it->second].m_aKids.push_back( m_aWidgets[i_nWidgetIndex].m_nObject); + m_aWidgets[it->second].m_aKidsIndex.push_back( i_nWidgetIndex ); + } + } + } + + if( aPartialName.getLength() == 0 ) + { + // how funny, an empty field name + if( i_rControl.getType() == PDFWriter::RadioButton ) + { + aPartialName = "RadioGroup"; + aPartialName += OString::valueOf( static_cast<const PDFWriter::RadioButtonWidget&>(i_rControl).RadioGroup ); + } + else + aPartialName = OString( "Widget" ); + } + + if( ! m_aContext.AllowDuplicateFieldNames ) + { + std::hash_map<OString, sal_Int32, OStringHash>::iterator it = m_aFieldNameMap.find( aFullName ); + + if( it != m_aFieldNameMap.end() ) // not unique + { + std::hash_map< OString, sal_Int32, OStringHash >::const_iterator check_it; + OString aTry; + sal_Int32 nTry = 2; + do + { + OStringBuffer aUnique( aFullName.getLength() + 16 ); + aUnique.append( aFullName ); + aUnique.append( '_' ); + aUnique.append( nTry++ ); + aTry = aUnique.makeStringAndClear(); + check_it = m_aFieldNameMap.find( aTry ); + } while( check_it != m_aFieldNameMap.end() ); + aFullName = aTry; + m_aFieldNameMap[ aFullName ] = i_nWidgetIndex; + aPartialName = aFullName.copy( aFullName.lastIndexOf( '.' )+1 ); + } + else + m_aFieldNameMap[ aFullName ] = i_nWidgetIndex; + } + + // finally + m_aWidgets[i_nWidgetIndex].m_aName = aPartialName; +} + +static void appendFixedInt( sal_Int32 nValue, OStringBuffer& rBuffer, sal_Int32 nPrecision = nLog10Divisor ) +{ + if( nValue < 0 ) + { + rBuffer.append( '-' ); + nValue = -nValue; + } + sal_Int32 nFactor = 1, nDiv = nPrecision; + while( nDiv-- ) + nFactor *= 10; + + sal_Int32 nInt = nValue / nFactor; + rBuffer.append( nInt ); + if( nFactor > 1 ) + { + sal_Int32 nDecimal = nValue % nFactor; + if( nDecimal ) + { + rBuffer.append( '.' ); + // omit trailing zeros + while( (nDecimal % 10) == 0 ) + nDecimal /= 10; + rBuffer.append( nDecimal ); + } + } +} + + +// appends a double. PDF does not accept exponential format, only fixed point +static void appendDouble( double fValue, OStringBuffer& rBuffer, sal_Int32 nPrecision = 5 ) +{ + bool bNeg = false; + if( fValue < 0.0 ) + { + bNeg = true; + fValue=-fValue; + } + + sal_Int64 nInt = (sal_Int64)fValue; + fValue -= (double)nInt; + // optimizing hardware may lead to a value of 1.0 after the subtraction + if( fValue == 1.0 || log10( 1.0-fValue ) <= -nPrecision ) + { + nInt++; + fValue = 0.0; + } + sal_Int64 nFrac = 0; + if( fValue ) + { + fValue *= pow( 10.0, (double)nPrecision ); + nFrac = (sal_Int64)fValue; + } + if( bNeg && ( nInt || nFrac ) ) + rBuffer.append( '-' ); + rBuffer.append( nInt ); + if( nFrac ) + { + int i; + rBuffer.append( '.' ); + sal_Int64 nBound = (sal_Int64)(pow( 10.0, nPrecision - 1.0 )+0.5); + for ( i = 0; ( i < nPrecision ) && nFrac; i++ ) + { + sal_Int64 nNumb = nFrac / nBound; + nFrac -= nNumb * nBound; + rBuffer.append( nNumb ); + nBound /= 10; + } + } +} + + +static void appendColor( const Color& rColor, OStringBuffer& rBuffer ) +{ + + if( rColor != Color( COL_TRANSPARENT ) ) + { + appendDouble( (double)rColor.GetRed() / 255.0, rBuffer ); + rBuffer.append( ' ' ); + appendDouble( (double)rColor.GetGreen() / 255.0, rBuffer ); + rBuffer.append( ' ' ); + appendDouble( (double)rColor.GetBlue() / 255.0, rBuffer ); + } +} + +static void appendStrokingColor( const Color& rColor, OStringBuffer& rBuffer ) +{ + if( rColor != Color( COL_TRANSPARENT ) ) + { + appendColor( rColor, rBuffer ); + rBuffer.append( " RG" ); + } +} + +static void appendNonStrokingColor( const Color& rColor, OStringBuffer& rBuffer ) +{ + if( rColor != Color( COL_TRANSPARENT ) ) + { + appendColor( rColor, rBuffer ); + rBuffer.append( " rg" ); + } +} + +// matrix helper class +// TODO: use basegfx matrix class instead or derive from it +namespace vcl // TODO: use anonymous namespace to keep this class local +{ +/* for sparse matrices of the form (2D linear transformations) + * f[0] f[1] 0 + * f[2] f[3] 0 + * f[4] f[5] 1 + */ +class Matrix3 +{ + double f[6]; + + void set( double *pn ) { for( int i = 0 ; i < 6; i++ ) f[i] = pn[i]; } +public: + Matrix3(); + ~Matrix3() {} + + void skew( double alpha, double beta ); + void scale( double sx, double sy ); + void rotate( double angle ); + void translate( double tx, double ty ); + bool invert(); + + void append( PDFWriterImpl::PDFPage& rPage, OStringBuffer& rBuffer, Point* pBack = NULL ); + + Point transform( const Point& rPoint ) const; +}; +} + +Matrix3::Matrix3() +{ + // initialize to unity + f[0] = 1.0; + f[1] = 0.0; + f[2] = 0.0; + f[3] = 1.0; + f[4] = 0.0; + f[5] = 0.0; +} + +Point Matrix3::transform( const Point& rOrig ) const +{ + double x = (double)rOrig.X(), y = (double)rOrig.Y(); + return Point( (int)(x*f[0] + y*f[2] + f[4]), (int)(x*f[1] + y*f[3] + f[5]) ); +} + +void Matrix3::skew( double alpha, double beta ) +{ + double fn[6]; + double tb = tan( beta ); + fn[0] = f[0] + f[2]*tb; + fn[1] = f[1]; + fn[2] = f[2] + f[3]*tb; + fn[3] = f[3]; + fn[4] = f[4] + f[5]*tb; + fn[5] = f[5]; + if( alpha != 0.0 ) + { + double ta = tan( alpha ); + fn[1] += f[0]*ta; + fn[3] += f[2]*ta; + fn[5] += f[4]*ta; + } + set( fn ); +} + +void Matrix3::scale( double sx, double sy ) +{ + double fn[6]; + fn[0] = sx*f[0]; + fn[1] = sy*f[1]; + fn[2] = sx*f[2]; + fn[3] = sy*f[3]; + fn[4] = sx*f[4]; + fn[5] = sy*f[5]; + set( fn ); +} + +void Matrix3::rotate( double angle ) +{ + double fn[6]; + double fSin = sin(angle); + double fCos = cos(angle); + fn[0] = f[0]*fCos - f[1]*fSin; + fn[1] = f[0]*fSin + f[1]*fCos; + fn[2] = f[2]*fCos - f[3]*fSin; + fn[3] = f[2]*fSin + f[3]*fCos; + fn[4] = f[4]*fCos - f[5]*fSin; + fn[5] = f[4]*fSin + f[5]*fCos; + set( fn ); +} + +void Matrix3::translate( double tx, double ty ) +{ + f[4] += tx; + f[5] += ty; +} + +bool Matrix3::invert() +{ + // short circuit trivial cases + if( f[1]==f[2] && f[1]==0.0 && f[0]==f[3] && f[0]==1.0 ) + { + f[4] = -f[4]; + f[5] = -f[5]; + return true; + } + + // check determinant + const double fDet = f[0]*f[3]-f[1]*f[2]; + if( fDet == 0.0 ) + return false; + + // invert the matrix + double fn[6]; + fn[0] = +f[3] / fDet; + fn[1] = -f[1] / fDet; + fn[2] = -f[2] / fDet; + fn[3] = +f[0] / fDet; + + // apply inversion to translation + fn[4] = -(f[4]*fn[0] + f[5]*fn[2]); + fn[5] = -(f[4]*fn[1] + f[5]*fn[3]); + + set( fn ); + return true; +} + +void Matrix3::append( PDFWriterImpl::PDFPage& rPage, OStringBuffer& rBuffer, Point* pBack ) +{ + appendDouble( f[0], rBuffer ); + rBuffer.append( ' ' ); + appendDouble( f[1], rBuffer ); + rBuffer.append( ' ' ); + appendDouble( f[2], rBuffer ); + rBuffer.append( ' ' ); + appendDouble( f[3], rBuffer ); + rBuffer.append( ' ' ); + rPage.appendPoint( Point( (long)f[4], (long)f[5] ), rBuffer, false, pBack ); +} + +static void appendResourceMap( OStringBuffer& rBuf, const char* pPrefix, const PDFWriterImpl::ResourceMap& rList ) +{ + if( rList.empty() ) + return; + rBuf.append( '/' ); + rBuf.append( pPrefix ); + rBuf.append( "<<" ); + int ni = 0; + for( PDFWriterImpl::ResourceMap::const_iterator it = rList.begin(); it != rList.end(); ++it ) + { + if( it->first.getLength() && it->second > 0 ) + { + rBuf.append( '/' ); + rBuf.append( it->first ); + rBuf.append( ' ' ); + rBuf.append( it->second ); + rBuf.append( " 0 R" ); + if( ((++ni) & 7) == 0 ) + rBuf.append( '\n' ); + } + } + rBuf.append( ">>\n" ); +} + +void PDFWriterImpl::ResourceDict::append( OStringBuffer& rBuf, sal_Int32 nFontDictObject ) +{ + rBuf.append( "<</Font " ); + rBuf.append( nFontDictObject ); + rBuf.append( " 0 R\n" ); + appendResourceMap( rBuf, "XObject", m_aXObjects ); + appendResourceMap( rBuf, "ExtGState", m_aExtGStates ); + appendResourceMap( rBuf, "Shading", m_aShadings ); + appendResourceMap( rBuf, "Pattern", m_aPatterns ); + rBuf.append( "/ProcSet[/PDF/Text" ); + if( !m_aXObjects.empty() ) + rBuf.append( "/ImageC/ImageI/ImageB" ); + rBuf.append( "]\n>>\n" ); +}; + +PDFWriterImpl::PDFPage::PDFPage( PDFWriterImpl* pWriter, sal_Int32 nPageWidth, sal_Int32 nPageHeight, PDFWriter::Orientation eOrientation ) + : + m_pWriter( pWriter ), + m_nPageWidth( nPageWidth ), + m_nPageHeight( nPageHeight ), + m_eOrientation( eOrientation ), + m_nPageObject( 0 ), // invalid object number + m_nPageIndex( -1 ), // invalid index + m_nStreamLengthObject( 0 ), + m_nBeginStreamPos( 0 ), + m_eTransition( PDFWriter::Regular ), + m_nTransTime( 0 ), + m_nDuration( 0 ), + m_bHasWidgets( false ) +{ + // object ref must be only ever updated in emit() + m_nPageObject = m_pWriter->createObject(); +} + +PDFWriterImpl::PDFPage::~PDFPage() +{ +} + +void PDFWriterImpl::PDFPage::beginStream() +{ +#if OSL_DEBUG_LEVEL > 1 + { + OStringBuffer aLine( "PDFWriterImpl::PDFPage::beginStream, +" ); + m_pWriter->emitComment( aLine.getStr() ); + } +#endif + m_aStreamObjects.push_back(m_pWriter->createObject()); + if( ! m_pWriter->updateObject( m_aStreamObjects.back() ) ) + return; + + m_nStreamLengthObject = m_pWriter->createObject(); + // write content stream header + OStringBuffer aLine; + aLine.append( m_aStreamObjects.back() ); + aLine.append( " 0 obj\n<</Length " ); + aLine.append( m_nStreamLengthObject ); + aLine.append( " 0 R" ); +#if defined ( COMPRESS_PAGES ) && !defined ( DEBUG_DISABLE_PDFCOMPRESSION ) + aLine.append( "/Filter/FlateDecode" ); +#endif + aLine.append( ">>\nstream\n" ); + if( ! m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() ) ) + return; + if( osl_File_E_None != osl_getFilePos( m_pWriter->m_aFile, &m_nBeginStreamPos ) ) + { + osl_closeFile( m_pWriter->m_aFile ); + m_pWriter->m_bOpen = false; + } +#if defined ( COMPRESS_PAGES ) && !defined ( DEBUG_DISABLE_PDFCOMPRESSION ) + m_pWriter->beginCompression(); +#endif + m_pWriter->checkAndEnableStreamEncryption( m_aStreamObjects.back() ); +} + +void PDFWriterImpl::PDFPage::endStream() +{ +#if defined ( COMPRESS_PAGES ) && !defined ( DEBUG_DISABLE_PDFCOMPRESSION ) + m_pWriter->endCompression(); +#endif + sal_uInt64 nEndStreamPos; + if( osl_File_E_None != osl_getFilePos( m_pWriter->m_aFile, &nEndStreamPos ) ) + { + osl_closeFile( m_pWriter->m_aFile ); + m_pWriter->m_bOpen = false; + return; + } + m_pWriter->disableStreamEncryption(); + if( ! m_pWriter->writeBuffer( "\nendstream\nendobj\n\n", 19 ) ) + return; + // emit stream length object + if( ! m_pWriter->updateObject( m_nStreamLengthObject ) ) + return; + OStringBuffer aLine; + aLine.append( m_nStreamLengthObject ); + aLine.append( " 0 obj\n" ); + aLine.append( (sal_Int64)(nEndStreamPos-m_nBeginStreamPos) ); + aLine.append( "\nendobj\n\n" ); + m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() ); +} + +bool PDFWriterImpl::PDFPage::emit(sal_Int32 nParentObject ) +{ + // emit page object + if( ! m_pWriter->updateObject( m_nPageObject ) ) + return false; + OStringBuffer aLine; + + aLine.append( m_nPageObject ); + aLine.append( " 0 obj\n" + "<</Type/Page/Parent " ); + aLine.append( nParentObject ); + aLine.append( " 0 R" ); + aLine.append( "/Resources " ); + aLine.append( m_pWriter->getResourceDictObj() ); + aLine.append( " 0 R" ); + if( m_nPageWidth && m_nPageHeight ) + { + aLine.append( "/MediaBox[0 0 " ); + aLine.append( m_nPageWidth ); + aLine.append( ' ' ); + aLine.append( m_nPageHeight ); + aLine.append( "]" ); + } + switch( m_eOrientation ) + { + case PDFWriter::Landscape: aLine.append( "/Rotate 90\n" );break; + case PDFWriter::Seascape: aLine.append( "/Rotate -90\n" );break; + case PDFWriter::Portrait: aLine.append( "/Rotate 0\n" );break; + + case PDFWriter::Inherit: + default: + break; + } + int nAnnots = m_aAnnotations.size(); + if( nAnnots > 0 ) + { + aLine.append( "/Annots[\n" ); + for( int i = 0; i < nAnnots; i++ ) + { + aLine.append( m_aAnnotations[i] ); + aLine.append( " 0 R" ); + aLine.append( ((i+1)%15) ? " " : "\n" ); + } + aLine.append( "]\n" ); + } + #if 0 + // FIXME: implement tab order as Structure Tree + if( m_bHasWidgets && m_pWriter->getVersion() >= PDFWriter::PDF_1_5 ) + aLine.append( " /Tabs /S\n" ); + #endif + if( m_aMCIDParents.size() > 0 ) + { + OStringBuffer aStructParents( 1024 ); + aStructParents.append( "[ " ); + int nParents = m_aMCIDParents.size(); + for( int i = 0; i < nParents; i++ ) + { + aStructParents.append( m_aMCIDParents[i] ); + aStructParents.append( " 0 R" ); + aStructParents.append( ((i%10) == 9) ? "\n" : " " ); + } + aStructParents.append( "]" ); + m_pWriter->m_aStructParentTree.push_back( aStructParents.makeStringAndClear() ); + + aLine.append( "/StructParents " ); + aLine.append( sal_Int32(m_pWriter->m_aStructParentTree.size()-1) ); + aLine.append( "\n" ); + } + if( m_nDuration > 0 ) + { + aLine.append( "/Dur " ); + aLine.append( (sal_Int32)m_nDuration ); + aLine.append( "\n" ); + } + if( m_eTransition != PDFWriter::Regular && m_nTransTime > 0 ) + { + // transition duration + aLine.append( "/Trans<</D " ); + appendDouble( (double)m_nTransTime/1000.0, aLine, 3 ); + aLine.append( "\n" ); + const char *pStyle = NULL, *pDm = NULL, *pM = NULL, *pDi = NULL; + switch( m_eTransition ) + { + case PDFWriter::SplitHorizontalInward: + pStyle = "Split"; pDm = "H"; pM = "I"; break; + case PDFWriter::SplitHorizontalOutward: + pStyle = "Split"; pDm = "H"; pM = "O"; break; + case PDFWriter::SplitVerticalInward: + pStyle = "Split"; pDm = "V"; pM = "I"; break; + case PDFWriter::SplitVerticalOutward: + pStyle = "Split"; pDm = "V"; pM = "O"; break; + case PDFWriter::BlindsHorizontal: + pStyle = "Blinds"; pDm = "H"; break; + case PDFWriter::BlindsVertical: + pStyle = "Blinds"; pDm = "V"; break; + case PDFWriter::BoxInward: + pStyle = "Box"; pM = "I"; break; + case PDFWriter::BoxOutward: + pStyle = "Box"; pM = "O"; break; + case PDFWriter::WipeLeftToRight: + pStyle = "Wipe"; pDi = "0"; break; + case PDFWriter::WipeBottomToTop: + pStyle = "Wipe"; pDi = "90"; break; + case PDFWriter::WipeRightToLeft: + pStyle = "Wipe"; pDi = "180"; break; + case PDFWriter::WipeTopToBottom: + pStyle = "Wipe"; pDi = "270"; break; + case PDFWriter::Dissolve: + pStyle = "Dissolve"; break; + case PDFWriter::GlitterLeftToRight: + pStyle = "Glitter"; pDi = "0"; break; + case PDFWriter::GlitterTopToBottom: + pStyle = "Glitter"; pDi = "270"; break; + case PDFWriter::GlitterTopLeftToBottomRight: + pStyle = "Glitter"; pDi = "315"; break; + case PDFWriter::Regular: + break; + } + // transition style + if( pStyle ) + { + aLine.append( "/S/" ); + aLine.append( pStyle ); + aLine.append( "\n" ); + } + if( pDm ) + { + aLine.append( "/Dm/" ); + aLine.append( pDm ); + aLine.append( "\n" ); + } + if( pM ) + { + aLine.append( "/M/" ); + aLine.append( pM ); + aLine.append( "\n" ); + } + if( pDi ) + { + aLine.append( "/Di " ); + aLine.append( pDi ); + aLine.append( "\n" ); + } + aLine.append( ">>\n" ); + } + if( m_pWriter->getVersion() > PDFWriter::PDF_1_3 && ! m_pWriter->m_bIsPDF_A1 ) + { + aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/I true>>" ); + } + aLine.append( "/Contents" ); + unsigned int nStreamObjects = m_aStreamObjects.size(); + if( nStreamObjects > 1 ) + aLine.append( '[' ); + for( unsigned int i = 0; i < m_aStreamObjects.size(); i++ ) + { + aLine.append( ' ' ); + aLine.append( m_aStreamObjects[i] ); + aLine.append( " 0 R" ); + } + if( nStreamObjects > 1 ) + aLine.append( ']' ); + aLine.append( ">>\nendobj\n\n" ); + return m_pWriter->writeBuffer( aLine.getStr(), aLine.getLength() ); +} + +namespace vcl +{ +template < class GEOMETRY > +GEOMETRY lcl_convert( const MapMode& _rSource, const MapMode& _rDest, OutputDevice* _pPixelConversion, const GEOMETRY& _rObject ) +{ + GEOMETRY aPoint; + if ( MAP_PIXEL == _rSource.GetMapUnit() ) + { + aPoint = _pPixelConversion->PixelToLogic( _rObject, _rDest ); + } + else + { + aPoint = OutputDevice::LogicToLogic( _rObject, _rSource, _rDest ); + } + return aPoint; +} +} + +void PDFWriterImpl::PDFPage::appendPoint( const Point& rPoint, OStringBuffer& rBuffer, bool bNeg, Point* pOutPoint ) const +{ + if( pOutPoint ) + { + Point aPoint( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, + m_pWriter->m_aMapMode, + m_pWriter->getReferenceDevice(), + rPoint ) ); + *pOutPoint = aPoint; + } + + Point aPoint( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, + m_pWriter->m_aMapMode, + m_pWriter->getReferenceDevice(), + rPoint ) ); + + sal_Int32 nValue = aPoint.X(); + if( bNeg ) + nValue = -nValue; + + appendFixedInt( nValue, rBuffer ); + + rBuffer.append( ' ' ); + + nValue = pointToPixel(getHeight()) - aPoint.Y(); + if( bNeg ) + nValue = -nValue; + + appendFixedInt( nValue, rBuffer ); +} + +void PDFWriterImpl::PDFPage::appendPixelPoint( const basegfx::B2DPoint& rPoint, OStringBuffer& rBuffer ) const +{ + double fValue = pixelToPoint(rPoint.getX()); + + appendDouble( fValue, rBuffer, nLog10Divisor ); + + rBuffer.append( ' ' ); + + fValue = double(getHeight()) - pixelToPoint(rPoint.getY()); + + appendDouble( fValue, rBuffer, nLog10Divisor ); +} + +void PDFWriterImpl::PDFPage::appendRect( const Rectangle& rRect, OStringBuffer& rBuffer ) const +{ + appendPoint( rRect.BottomLeft() + Point( 0, 1 ), rBuffer ); + rBuffer.append( ' ' ); + appendMappedLength( (sal_Int32)rRect.GetWidth(), rBuffer, false ); + rBuffer.append( ' ' ); + appendMappedLength( (sal_Int32)rRect.GetHeight(), rBuffer, true ); + rBuffer.append( " re" ); +} + +void PDFWriterImpl::PDFPage::convertRect( Rectangle& rRect ) const +{ + Point aLL = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, + m_pWriter->m_aMapMode, + m_pWriter->getReferenceDevice(), + rRect.BottomLeft() + Point( 0, 1 ) + ); + Size aSize = lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, + m_pWriter->m_aMapMode, + m_pWriter->getReferenceDevice(), + rRect.GetSize() ); + rRect.Left() = aLL.X(); + rRect.Right() = aLL.X() + aSize.Width(); + rRect.Top() = pointToPixel(getHeight()) - aLL.Y(); + rRect.Bottom() = rRect.Top() + aSize.Height(); +} + +void PDFWriterImpl::PDFPage::appendPolygon( const Polygon& rPoly, OStringBuffer& rBuffer, bool bClose ) const +{ + USHORT nPoints = rPoly.GetSize(); + /* + * #108582# applications do weird things + */ + sal_uInt32 nBufLen = rBuffer.getLength(); + if( nPoints > 0 ) + { + const BYTE* pFlagArray = rPoly.GetConstFlagAry(); + appendPoint( rPoly[0], rBuffer ); + rBuffer.append( " m\n" ); + for( USHORT i = 1; i < nPoints; i++ ) + { + if( pFlagArray && pFlagArray[i] == POLY_CONTROL && nPoints-i > 2 ) + { + // bezier + DBG_ASSERT( pFlagArray[i+1] == POLY_CONTROL && pFlagArray[i+2] != POLY_CONTROL, "unexpected sequence of control points" ); + appendPoint( rPoly[i], rBuffer ); + rBuffer.append( " " ); + appendPoint( rPoly[i+1], rBuffer ); + rBuffer.append( " " ); + appendPoint( rPoly[i+2], rBuffer ); + rBuffer.append( " c" ); + i += 2; // add additionally consumed points + } + else + { + // line + appendPoint( rPoly[i], rBuffer ); + rBuffer.append( " l" ); + } + if( (rBuffer.getLength() - nBufLen) > 65 ) + { + rBuffer.append( "\n" ); + nBufLen = rBuffer.getLength(); + } + else + rBuffer.append( " " ); + } + if( bClose ) + rBuffer.append( "h\n" ); + } +} + +void PDFWriterImpl::PDFPage::appendPolygon( const basegfx::B2DPolygon& rPoly, OStringBuffer& rBuffer, bool bClose ) const +{ + basegfx::B2DPolygon aPoly( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, + m_pWriter->m_aMapMode, + m_pWriter->getReferenceDevice(), + rPoly ) ); + + if( basegfx::tools::isRectangle( aPoly ) ) + { + basegfx::B2DRange aRange( aPoly.getB2DRange() ); + basegfx::B2DPoint aBL( aRange.getMinX(), aRange.getMaxY() ); + appendPixelPoint( aBL, rBuffer ); + rBuffer.append( ' ' ); + appendMappedLength( aRange.getWidth(), rBuffer, false, NULL, nLog10Divisor ); + rBuffer.append( ' ' ); + appendMappedLength( aRange.getHeight(), rBuffer, true, NULL, nLog10Divisor ); + rBuffer.append( " re\n" ); + return; + } + sal_uInt32 nPoints = aPoly.count(); + if( nPoints > 0 ) + { + sal_uInt32 nBufLen = rBuffer.getLength(); + basegfx::B2DPoint aLastPoint( aPoly.getB2DPoint( 0 ) ); + appendPixelPoint( aLastPoint, rBuffer ); + rBuffer.append( " m\n" ); + for( sal_uInt32 i = 1; i <= nPoints; i++ ) + { + if( i != nPoints || aPoly.isClosed() ) + { + sal_uInt32 nCurPoint = i % nPoints; + sal_uInt32 nLastPoint = i-1; + basegfx::B2DPoint aPoint( aPoly.getB2DPoint( nCurPoint ) ); + if( aPoly.isNextControlPointUsed( nLastPoint ) && + aPoly.isPrevControlPointUsed( nCurPoint ) ) + { + appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer ); + rBuffer.append( ' ' ); + appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer ); + rBuffer.append( ' ' ); + appendPixelPoint( aPoint, rBuffer ); + rBuffer.append( " c" ); + } + else if( aPoly.isNextControlPointUsed( nLastPoint ) ) + { + appendPixelPoint( aPoly.getNextControlPoint( nLastPoint ), rBuffer ); + rBuffer.append( ' ' ); + appendPixelPoint( aPoint, rBuffer ); + rBuffer.append( " y" ); + } + else if( aPoly.isPrevControlPointUsed( nCurPoint ) ) + { + appendPixelPoint( aPoly.getPrevControlPoint( nCurPoint ), rBuffer ); + rBuffer.append( ' ' ); + appendPixelPoint( aPoint, rBuffer ); + rBuffer.append( " v" ); + } + else + { + appendPixelPoint( aPoint, rBuffer ); + rBuffer.append( " l" ); + } + if( (rBuffer.getLength() - nBufLen) > 65 ) + { + rBuffer.append( "\n" ); + nBufLen = rBuffer.getLength(); + } + else + rBuffer.append( " " ); + } + } + if( bClose ) + rBuffer.append( "h\n" ); + } +} + +void PDFWriterImpl::PDFPage::appendPolyPolygon( const PolyPolygon& rPolyPoly, OStringBuffer& rBuffer, bool bClose ) const +{ + USHORT nPolygons = rPolyPoly.Count(); + for( USHORT n = 0; n < nPolygons; n++ ) + appendPolygon( rPolyPoly[n], rBuffer, bClose ); +} + +void PDFWriterImpl::PDFPage::appendPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPoly, OStringBuffer& rBuffer, bool bClose ) const +{ + sal_uInt32 nPolygons = rPolyPoly.count(); + for( sal_uInt32 n = 0; n < nPolygons; n++ ) + appendPolygon( rPolyPoly.getB2DPolygon( n ), rBuffer, bClose ); +} + +void PDFWriterImpl::PDFPage::appendMappedLength( sal_Int32 nLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32* pOutLength ) const +{ + sal_Int32 nValue = nLength; + if ( nLength < 0 ) + { + rBuffer.append( '-' ); + nValue = -nLength; + } + Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, + m_pWriter->m_aMapMode, + m_pWriter->getReferenceDevice(), + Size( nValue, nValue ) ) ); + nValue = bVertical ? aSize.Height() : aSize.Width(); + if( pOutLength ) + *pOutLength = ((nLength < 0 ) ? -nValue : nValue); + + appendFixedInt( nValue, rBuffer, 1 ); +} + +void PDFWriterImpl::PDFPage::appendMappedLength( double fLength, OStringBuffer& rBuffer, bool bVertical, sal_Int32* pOutLength, sal_Int32 nPrecision ) const +{ + Size aSize( lcl_convert( m_pWriter->m_aGraphicsStack.front().m_aMapMode, + m_pWriter->m_aMapMode, + m_pWriter->getReferenceDevice(), + Size( 1000, 1000 ) ) ); + if( pOutLength ) + *pOutLength = (sal_Int32)(fLength*(double)(bVertical ? aSize.Height() : aSize.Width())/1000.0); + fLength *= pixelToPoint((double)(bVertical ? aSize.Height() : aSize.Width()) / 1000.0); + appendDouble( fLength, rBuffer, nPrecision ); +} + +bool PDFWriterImpl::PDFPage::appendLineInfo( const LineInfo& rInfo, OStringBuffer& rBuffer ) const +{ + bool bRet = true; + if( rInfo.GetStyle() == LINE_DASH ) + { + rBuffer.append( "[ " ); + if( rInfo.GetDashLen() == rInfo.GetDotLen() ) // degraded case + { + appendMappedLength( (sal_Int32)rInfo.GetDashLen(), rBuffer ); + rBuffer.append( ' ' ); + appendMappedLength( (sal_Int32)rInfo.GetDistance(), rBuffer ); + rBuffer.append( ' ' ); + } + else + { + // check for implementation limits of dash array + // in PDF reader apps (e.g. acroread) + if( 2*(rInfo.GetDashCount() + rInfo.GetDotCount()) > 10 ) + bRet = false; + for( int n = 0; n < rInfo.GetDashCount(); n++ ) + { + appendMappedLength( (sal_Int32)rInfo.GetDashLen(), rBuffer ); + rBuffer.append( ' ' ); + appendMappedLength( (sal_Int32)rInfo.GetDistance(), rBuffer ); + rBuffer.append( ' ' ); + } + for( int m = 0; m < rInfo.GetDotCount(); m++ ) + { + appendMappedLength( (sal_Int32)rInfo.GetDotLen(), rBuffer ); + rBuffer.append( ' ' ); + appendMappedLength( (sal_Int32)rInfo.GetDistance(), rBuffer ); + rBuffer.append( ' ' ); + } + } + rBuffer.append( "] 0 d\n" ); + } + if( rInfo.GetWidth() > 1 ) + { + appendMappedLength( (sal_Int32)rInfo.GetWidth(), rBuffer ); + rBuffer.append( " w\n" ); + } + else if( rInfo.GetWidth() == 0 ) + { + // "pixel" line + appendDouble( 72.0/double(m_pWriter->getReferenceDevice()->ImplGetDPIX()), rBuffer ); + rBuffer.append( " w\n" ); + } + return bRet; +} + +void PDFWriterImpl::PDFPage::appendWaveLine( sal_Int32 nWidth, sal_Int32 nY, sal_Int32 nDelta, OStringBuffer& rBuffer ) const +{ + if( nWidth <= 0 ) + return; + if( nDelta < 1 ) + nDelta = 1; + + rBuffer.append( "0 " ); + appendMappedLength( nY, rBuffer, true ); + rBuffer.append( " m\n" ); + for( sal_Int32 n = 0; n < nWidth; ) + { + n += nDelta; + appendMappedLength( n, rBuffer, false ); + rBuffer.append( ' ' ); + appendMappedLength( nDelta+nY, rBuffer, true ); + rBuffer.append( ' ' ); + n += nDelta; + appendMappedLength( n, rBuffer, false ); + rBuffer.append( ' ' ); + appendMappedLength( nY, rBuffer, true ); + rBuffer.append( " v " ); + if( n < nWidth ) + { + n += nDelta; + appendMappedLength( n, rBuffer, false ); + rBuffer.append( ' ' ); + appendMappedLength( nY-nDelta, rBuffer, true ); + rBuffer.append( ' ' ); + n += nDelta; + appendMappedLength( n, rBuffer, false ); + rBuffer.append( ' ' ); + appendMappedLength( nY, rBuffer, true ); + rBuffer.append( " v\n" ); + } + } + rBuffer.append( "S\n" ); +} + +/* + * class PDFWriterImpl + */ + +PDFWriterImpl::PDFWriterImpl( const PDFWriter::PDFWriterContext& rContext, PDFWriter& i_rOuterFace ) + : + m_pReferenceDevice( NULL ), + m_aMapMode( MAP_POINT, Point(), Fraction( 1L, pointToPixel(1) ), Fraction( 1L, pointToPixel(1) ) ), + m_nCurrentStructElement( 0 ), + m_bEmitStructure( true ), + m_bNewMCID( false ), + m_nCurrentControl( -1 ), + m_bEmbedStandardFonts( false ), + m_nNextFID( 1 ), + m_nInheritedPageWidth( 595 ), // default A4 + m_nInheritedPageHeight( 842 ), // default A4 + m_eInheritedOrientation( PDFWriter::Portrait ), + m_nCurrentPage( -1 ), + m_nResourceDict( -1 ), + m_nFontDictObject( -1 ), + m_pCodec( NULL ), + m_aDocDigest( rtl_digest_createMD5() ), + m_aCipher( (rtlCipher)NULL ), + m_aDigest( NULL ), + m_bEncryptThisStream( false ), + m_aDocID( 32 ), + m_aCreationDateString( 64 ), + m_aCreationMetaDateString( 64 ), + m_pEncryptionBuffer( NULL ), + m_nEncryptionBufferSize( 0 ), + m_bIsPDF_A1( false ), + m_rOuterFace( i_rOuterFace ) +{ +#ifdef DO_TEST_PDF + static bool bOnce = true; + if( bOnce ) + { + bOnce = false; + doTestCode(); + } +#endif + m_aContext = rContext; + m_aStructure.push_back( PDFStructureElement() ); + m_aStructure[0].m_nOwnElement = 0; + m_aStructure[0].m_nParentElement = 0; + + Font aFont; + aFont.SetName( String( RTL_CONSTASCII_USTRINGPARAM( "Times" ) ) ); + aFont.SetSize( Size( 0, 12 ) ); + + GraphicsState aState; + aState.m_aMapMode = m_aMapMode; + aState.m_aFont = aFont; + m_aGraphicsStack.push_front( aState ); + + oslFileError aError = osl_openFile( m_aContext.URL.pData, &m_aFile, osl_File_OpenFlag_Write | osl_File_OpenFlag_Create ); + if( aError != osl_File_E_None ) + { + if( aError == osl_File_E_EXIST ) + { + aError = osl_openFile( m_aContext.URL.pData, &m_aFile, osl_File_OpenFlag_Write ); + if( aError == osl_File_E_None ) + aError = osl_setFileSize( m_aFile, 0 ); + } + } + if( aError != osl_File_E_None ) + return; + + m_bOpen = true; + +/* prepare the cypher engine, can be done in CTOR, free in DTOR */ + + m_aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream ); + m_aDigest = rtl_digest_createMD5(); + +/* the size of the Codec default maximum */ + checkEncryptionBufferSize( 0x4000 ); + + // write header + OStringBuffer aBuffer( 20 ); + aBuffer.append( "%PDF-" ); + switch( m_aContext.Version ) + { + case PDFWriter::PDF_1_2: aBuffer.append( "1.2" );break; + case PDFWriter::PDF_1_3: aBuffer.append( "1.3" );break; + case PDFWriter::PDF_A_1: + default: + case PDFWriter::PDF_1_4: aBuffer.append( "1.4" );break; + case PDFWriter::PDF_1_5: aBuffer.append( "1.5" );break; + } + // append something binary as comment (suggested in PDF Reference) + aBuffer.append( "\n%äüöß\n" ); + if( !writeBuffer( aBuffer.getStr(), aBuffer.getLength() ) ) + { + osl_closeFile( m_aFile ); + m_bOpen = false; + return; + } + + // insert outline root + m_aOutline.push_back( PDFOutlineEntry() ); + + m_bIsPDF_A1 = (m_aContext.Version == PDFWriter::PDF_A_1); + if( m_bIsPDF_A1 ) + m_aContext.Version = PDFWriter::PDF_1_4; //meaning we need PDF 1.4, PDF/A flavour + + m_bEmbedStandardFonts = m_aContext.EmbedStandardFonts; +} + +PDFWriterImpl::~PDFWriterImpl() +{ + if( m_aDocDigest ) + rtl_digest_destroyMD5( m_aDocDigest ); + delete static_cast<VirtualDevice*>(m_pReferenceDevice); + + if( m_aCipher ) + rtl_cipher_destroyARCFOUR( m_aCipher ); + if( m_aDigest ) + rtl_digest_destroyMD5( m_aDigest ); + + rtl_freeMemory( m_pEncryptionBuffer ); +} + +void PDFWriterImpl::setDocInfo( const PDFDocInfo& rInfo ) +{ + m_aDocInfo.Title = rInfo.Title; + m_aDocInfo.Author = rInfo.Author; + m_aDocInfo.Subject = rInfo.Subject; + m_aDocInfo.Keywords = rInfo.Keywords; + m_aDocInfo.Creator = rInfo.Creator; + m_aDocInfo.Producer = rInfo.Producer; + +//build the document id + rtl::OString aInfoValuesOut; + OStringBuffer aID( 1024 ); + if( m_aDocInfo.Title.Len() ) + appendUnicodeTextString( m_aDocInfo.Title, aID ); + if( m_aDocInfo.Author.Len() ) + appendUnicodeTextString( m_aDocInfo.Author, aID ); + if( m_aDocInfo.Subject.Len() ) + appendUnicodeTextString( m_aDocInfo.Subject, aID ); + if( m_aDocInfo.Keywords.Len() ) + appendUnicodeTextString( m_aDocInfo.Keywords, aID ); + if( m_aDocInfo.Creator.Len() ) + appendUnicodeTextString( m_aDocInfo.Creator, aID ); + if( m_aDocInfo.Producer.Len() ) + appendUnicodeTextString( m_aDocInfo.Producer, aID ); + + TimeValue aTVal, aGMT; + oslDateTime aDT; + osl_getSystemTime( &aGMT ); + osl_getLocalTimeFromSystemTime( &aGMT, &aTVal ); + osl_getDateTimeFromTimeValue( &aTVal, &aDT ); + m_aCreationDateString.append( "D:" ); + m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Year/1000)%10)) ); + m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Year/100)%10)) ); + m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Year/10)%10)) ); + m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Year)%10)) ); + m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Month/10)%10)) ); + m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Month)%10)) ); + m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Day/10)%10)) ); + m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Day)%10)) ); + m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Hours/10)%10)) ); + m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Hours)%10)) ); + m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Minutes/10)%10)) ); + m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Minutes)%10)) ); + m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Seconds/10)%10)) ); + m_aCreationDateString.append( (sal_Char)('0' + ((aDT.Seconds)%10)) ); +//--> i59651, we fill the Metadata date string as well, if PDF/A is requested + if( m_bIsPDF_A1 ) + { +// according to ISO 19005-1:2005 6.7.3 the date is corrected for +// local time zone offset UTC only, whereas Acrobat 8 seems +// to use the localtime notation only +// according to a raccomandation in XMP Specification (Jan 2004, page 75) +// the Acrobat way seems the right approach + m_aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Year/1000)%10)) ); + m_aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Year/100)%10)) ); + m_aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Year/10)%10)) ); + m_aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Year)%10)) ); + m_aCreationMetaDateString.append( "-" ); + m_aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Month/10)%10)) ); + m_aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Month)%10)) ); + m_aCreationMetaDateString.append( "-" ); + m_aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Day/10)%10)) ); + m_aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Day)%10)) ); + m_aCreationMetaDateString.append( "T" ); + m_aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Hours/10)%10)) ); + m_aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Hours)%10)) ); + m_aCreationMetaDateString.append( ":" ); + m_aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Minutes/10)%10)) ); + m_aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Minutes)%10)) ); + m_aCreationMetaDateString.append( ":" ); + m_aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Seconds/10)%10)) ); + m_aCreationMetaDateString.append( (sal_Char)('0' + ((aDT.Seconds)%10)) ); + } + sal_uInt32 nDelta = 0; + if( aGMT.Seconds > aTVal.Seconds ) + { + m_aCreationDateString.append( "-" ); + nDelta = aGMT.Seconds-aTVal.Seconds; + if( m_bIsPDF_A1 ) + m_aCreationMetaDateString.append( "-" ); + } + else if( aGMT.Seconds < aTVal.Seconds ) + { + m_aCreationDateString.append( "+" ); + nDelta = aTVal.Seconds-aGMT.Seconds; + if( m_bIsPDF_A1 ) + m_aCreationMetaDateString.append( "+" ); + } + else + { + m_aCreationDateString.append( "Z" ); + if( m_bIsPDF_A1 ) + m_aCreationMetaDateString.append( "Z" ); + + } + if( nDelta ) + { + m_aCreationDateString.append( (sal_Char)('0' + ((nDelta/36000)%10)) ); + m_aCreationDateString.append( (sal_Char)('0' + ((nDelta/3600)%10)) ); + m_aCreationDateString.append( "'" ); + m_aCreationDateString.append( (sal_Char)('0' + ((nDelta/600)%6)) ); + m_aCreationDateString.append( (sal_Char)('0' + ((nDelta/60)%10)) ); + if( m_bIsPDF_A1 ) + { + m_aCreationMetaDateString.append( (sal_Char)('0' + ((nDelta/36000)%10)) ); + m_aCreationMetaDateString.append( (sal_Char)('0' + ((nDelta/3600)%10)) ); + m_aCreationMetaDateString.append( ":" ); + m_aCreationMetaDateString.append( (sal_Char)('0' + ((nDelta/600)%6)) ); + m_aCreationMetaDateString.append( (sal_Char)('0' + ((nDelta/60)%10)) ); + } + } + m_aCreationDateString.append( "'" ); + aID.append( m_aCreationDateString.getStr(), m_aCreationDateString.getLength() ); + + aInfoValuesOut = aID.makeStringAndClear(); + + DBG_ASSERT( m_aDigest != NULL, "PDFWrite_Impl::setDocInfo: cannot obtain a digest object !" ); + + m_aDocID.setLength( 0 ); + if( m_aDigest ) + { + osl_getSystemTime( &aGMT ); + rtlDigestError nError = rtl_digest_updateMD5( m_aDigest, &aGMT, sizeof( aGMT ) ); + if( nError == rtl_Digest_E_None ) + nError = rtl_digest_updateMD5( m_aDigest, m_aContext.URL.getStr(), m_aContext.URL.getLength()*sizeof(sal_Unicode) ); + if( nError == rtl_Digest_E_None ) + nError = rtl_digest_updateMD5( m_aDigest, aInfoValuesOut.getStr(), aInfoValuesOut.getLength() ); + if( nError == rtl_Digest_E_None ) + { +//the binary form of the doc id is needed for encryption stuff + rtl_digest_getMD5( m_aDigest, m_nDocID, 16 ); + for( unsigned int i = 0; i < 16; i++ ) + appendHex( m_nDocID[i], m_aDocID ); + } + } +} + +/* i12626 methods */ +/* +check if the Unicode string must be encrypted or not, perform the requested task, +append the string as unicode hex, encrypted if needed + */ +inline void PDFWriterImpl::appendUnicodeTextStringEncrypt( const rtl::OUString& rInString, const sal_Int32 nInObjectNumber, OStringBuffer& rOutBuffer ) +{ + rOutBuffer.append( "<" ); + if( m_aContext.Encrypt ) + { + const sal_Unicode* pStr = rInString.getStr(); + sal_Int32 nLen = rInString.getLength(); +//prepare a unicode string, encrypt it + if( checkEncryptionBufferSize( nLen*2 ) ) + { + enableStringEncryption( nInObjectNumber ); + register sal_uInt8 *pCopy = m_pEncryptionBuffer; + sal_Int32 nChars = 2; + *pCopy++ = 0xFE; + *pCopy++ = 0xFF; +// we need to prepare a byte stream from the unicode string buffer + for( register int i = 0; i < nLen; i++ ) + { + register sal_Unicode aUnChar = pStr[i]; + *pCopy++ = (sal_uInt8)( aUnChar >> 8 ); + *pCopy++ = (sal_uInt8)( aUnChar & 255 ); + nChars += 2; + } +//encrypt in place + rtl_cipher_encodeARCFOUR( m_aCipher, m_pEncryptionBuffer, nChars, m_pEncryptionBuffer, nChars ); +//now append, hexadecimal (appendHex), the encrypted result + for(register int i = 0; i < nChars; i++) + appendHex( m_pEncryptionBuffer[i], rOutBuffer ); + } + } + else + appendUnicodeTextString( rInString, rOutBuffer ); + rOutBuffer.append( ">" ); +} + +inline void PDFWriterImpl::appendLiteralStringEncrypt( rtl::OStringBuffer& rInString, const sal_Int32 nInObjectNumber, rtl::OStringBuffer& rOutBuffer ) +{ + rOutBuffer.append( "(" ); + sal_Int32 nChars = rInString.getLength(); +//check for encryption, if ok, encrypt the string, then convert with appndLiteralString + if( m_aContext.Encrypt && checkEncryptionBufferSize( nChars ) ) + { +//encrypt the string in a buffer, then append it + enableStringEncryption( nInObjectNumber ); + rtl_cipher_encodeARCFOUR( m_aCipher, rInString.getStr(), nChars, m_pEncryptionBuffer, nChars ); + appendLiteralString( (const sal_Char*)m_pEncryptionBuffer, nChars, rOutBuffer ); + } + else + appendLiteralString( rInString.getStr(), nChars , rOutBuffer ); + rOutBuffer.append( ")" ); +} + +inline void PDFWriterImpl::appendLiteralStringEncrypt( const rtl::OString& rInString, const sal_Int32 nInObjectNumber, rtl::OStringBuffer& rOutBuffer ) +{ + rtl::OStringBuffer aBufferString( rInString ); + appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer); +} + +inline void PDFWriterImpl::appendLiteralStringEncrypt( const rtl::OUString& rInString, const sal_Int32 nInObjectNumber, rtl::OStringBuffer& rOutBuffer ) +{ + rtl::OString aBufferString( rtl::OUStringToOString( rInString, RTL_TEXTENCODING_ASCII_US ) ); + appendLiteralStringEncrypt( aBufferString, nInObjectNumber, rOutBuffer); +} + +/* end i12626 methods */ + +void PDFWriterImpl::emitComment( const char* pComment ) +{ + OStringBuffer aLine( 64 ); + aLine.append( "% " ); + aLine.append( (const sal_Char*)pComment ); + aLine.append( "\n" ); + writeBuffer( aLine.getStr(), aLine.getLength() ); +} + +bool PDFWriterImpl::compressStream( SvMemoryStream* pStream ) +{ +#ifndef DEBUG_DISABLE_PDFCOMPRESSION + pStream->Seek( STREAM_SEEK_TO_END ); + ULONG nEndPos = pStream->Tell(); + pStream->Seek( STREAM_SEEK_TO_BEGIN ); + ZCodec* pCodec = new ZCodec( 0x4000, 0x4000 ); + SvMemoryStream aStream; + pCodec->BeginCompression(); + pCodec->Write( aStream, (const BYTE*)pStream->GetData(), nEndPos ); + pCodec->EndCompression(); + delete pCodec; + nEndPos = aStream.Tell(); + pStream->Seek( STREAM_SEEK_TO_BEGIN ); + aStream.Seek( STREAM_SEEK_TO_BEGIN ); + pStream->SetStreamSize( nEndPos ); + pStream->Write( aStream.GetData(), nEndPos ); + return true; +#else + (void)pStream; + return false; +#endif +} + +void PDFWriterImpl::beginCompression() +{ +#ifndef DEBUG_DISABLE_PDFCOMPRESSION + m_pCodec = new ZCodec( 0x4000, 0x4000 ); + m_pMemStream = new SvMemoryStream(); + m_pCodec->BeginCompression(); +#endif +} + +void PDFWriterImpl::endCompression() +{ +#ifndef DEBUG_DISABLE_PDFCOMPRESSION + if( m_pCodec ) + { + m_pCodec->EndCompression(); + delete m_pCodec; + m_pCodec = NULL; + sal_uInt64 nLen = m_pMemStream->Tell(); + m_pMemStream->Seek( 0 ); + writeBuffer( m_pMemStream->GetData(), nLen ); + delete m_pMemStream; + m_pMemStream = NULL; + } +#endif +} + +bool PDFWriterImpl::writeBuffer( const void* pBuffer, sal_uInt64 nBytes ) +{ + if( ! m_bOpen ) // we are already down the drain + return false; + + if( ! nBytes ) // huh ? + return true; + + if( m_aOutputStreams.begin() != m_aOutputStreams.end() ) + { + m_aOutputStreams.front().m_pStream->Seek( STREAM_SEEK_TO_END ); + m_aOutputStreams.front().m_pStream->Write( pBuffer, sal::static_int_cast<sal_Size>(nBytes) ); + return true; + } + + sal_uInt64 nWritten; + if( m_pCodec ) + { + m_pCodec->Write( *m_pMemStream, static_cast<const BYTE*>(pBuffer), (ULONG)nBytes ); + nWritten = nBytes; + } + else + { + sal_Bool buffOK = sal_True; + if( m_bEncryptThisStream ) + { +/* implement the encryption part of the PDF spec encryption algorithm 3.1 */ + if( ( buffOK = checkEncryptionBufferSize( static_cast<sal_Int32>(nBytes) ) ) != sal_False ) + rtl_cipher_encodeARCFOUR( m_aCipher, + (sal_uInt8*)pBuffer, static_cast<sal_Size>(nBytes), + m_pEncryptionBuffer, static_cast<sal_Size>(nBytes) ); + } + + const void* pWriteBuffer = ( m_bEncryptThisStream && buffOK ) ? m_pEncryptionBuffer : pBuffer; + if( m_aDocDigest ) + rtl_digest_updateMD5( m_aDocDigest, pWriteBuffer, static_cast<sal_uInt32>(nBytes) ); + + if( osl_writeFile( m_aFile, + pWriteBuffer, + nBytes, &nWritten ) != osl_File_E_None ) + nWritten = 0; + + if( nWritten != nBytes ) + { + osl_closeFile( m_aFile ); + m_bOpen = false; + } + } + + return nWritten == nBytes; +} + +OutputDevice* PDFWriterImpl::getReferenceDevice() +{ + if( ! m_pReferenceDevice ) + { + VirtualDevice* pVDev = new VirtualDevice( 0 ); + + m_pReferenceDevice = pVDev; + + if( m_aContext.DPIx == 0 || m_aContext.DPIy == 0 ) + pVDev->SetReferenceDevice( VirtualDevice::REFDEV_MODE_PDF1 ); + else + pVDev->SetReferenceDevice( m_aContext.DPIx, m_aContext.DPIy ); + + pVDev->SetOutputSizePixel( Size( 640, 480 ) ); + pVDev->SetMapMode( MAP_MM ); + + m_pReferenceDevice->mpPDFWriter = this; + m_pReferenceDevice->ImplUpdateFontData( TRUE ); + } + return m_pReferenceDevice; +} + +class ImplPdfBuiltinFontData : public ImplFontData +{ +private: + const PDFWriterImpl::BuiltinFont& mrBuiltin; + +public: + enum {PDF_FONT_MAGIC = 0xBDFF0A1C }; + ImplPdfBuiltinFontData( const PDFWriterImpl::BuiltinFont& ); + const PDFWriterImpl::BuiltinFont* GetBuiltinFont() const { return &mrBuiltin; } + + virtual ImplFontData* Clone() const { return new ImplPdfBuiltinFontData(*this); } + virtual ImplFontEntry* CreateFontInstance( ImplFontSelectData& ) const; + virtual sal_IntPtr GetFontId() const { return reinterpret_cast<sal_IntPtr>(&mrBuiltin); } +}; + +inline const ImplPdfBuiltinFontData* GetPdfFontData( const ImplFontData* pFontData ) +{ + const ImplPdfBuiltinFontData* pFD = NULL; + if( pFontData && pFontData->CheckMagic( ImplPdfBuiltinFontData::PDF_FONT_MAGIC ) ) + pFD = static_cast<const ImplPdfBuiltinFontData*>( pFontData ); + return pFD; +} + +static ImplDevFontAttributes GetDevFontAttributes( const PDFWriterImpl::BuiltinFont& rBuiltin ) +{ + ImplDevFontAttributes aDFA; + aDFA.maName = String::CreateFromAscii( rBuiltin.m_pName ); + aDFA.maStyleName = String::CreateFromAscii( rBuiltin.m_pStyleName ); + aDFA.meFamily = rBuiltin.m_eFamily; + aDFA.mbSymbolFlag = (rBuiltin.m_eCharSet != RTL_TEXTENCODING_MS_1252 ); + aDFA.mePitch = rBuiltin.m_ePitch; + aDFA.meWeight = rBuiltin.m_eWeight; + aDFA.meItalic = rBuiltin.m_eItalic; + aDFA.meWidthType = rBuiltin.m_eWidthType; + + aDFA.mbOrientation = true; + aDFA.mbDevice = true; + aDFA.mnQuality = 50000; + aDFA.mbSubsettable = false; + aDFA.mbEmbeddable = false; + return aDFA; +} + +ImplPdfBuiltinFontData::ImplPdfBuiltinFontData( const PDFWriterImpl::BuiltinFont& rBuiltin ) +: ImplFontData( GetDevFontAttributes(rBuiltin), PDF_FONT_MAGIC ), + mrBuiltin( rBuiltin ) +{} + +ImplFontEntry* ImplPdfBuiltinFontData::CreateFontInstance( ImplFontSelectData& rFSD ) const +{ + ImplFontEntry* pEntry = new ImplFontEntry( rFSD ); + return pEntry; +} + +ImplDevFontList* PDFWriterImpl::filterDevFontList( ImplDevFontList* pFontList ) +{ + DBG_ASSERT( m_aSubsets.size() == 0, "Fonts changing during PDF generation, document will be invalid" ); + ImplDevFontList* pFiltered = pFontList->Clone( true, true ); + + // append the PDF builtin fonts + if( !m_bIsPDF_A1 && !m_bEmbedStandardFonts) + for( unsigned int i = 0; i < sizeof(m_aBuiltinFonts)/sizeof(m_aBuiltinFonts[0]); i++ ) + { + ImplFontData* pNewData = new ImplPdfBuiltinFontData( m_aBuiltinFonts[i] ); + pFiltered->Add( pNewData ); + } + return pFiltered; +} + +bool PDFWriterImpl::isBuiltinFont( const ImplFontData* pFont ) const +{ + const ImplPdfBuiltinFontData* pFD = GetPdfFontData( pFont ); + return (pFD != NULL); +} + +void PDFWriterImpl::getFontMetric( ImplFontSelectData* pSelect, ImplFontMetricData* pMetric ) const +{ + const ImplPdfBuiltinFontData* pFD = GetPdfFontData( pSelect->mpFontData ); + if( !pFD ) + return; + const BuiltinFont* pBuiltinFont = pFD->GetBuiltinFont(); + + pMetric->mnOrientation = sal::static_int_cast<short>(pSelect->mnOrientation); + pMetric->meFamily = pBuiltinFont->m_eFamily; + pMetric->mePitch = pBuiltinFont->m_ePitch; + pMetric->meWeight = pBuiltinFont->m_eWeight; + pMetric->meItalic = pBuiltinFont->m_eItalic; + pMetric->mbSymbolFlag = pFD->IsSymbolFont(); + pMetric->mnWidth = pSelect->mnHeight; + pMetric->mnAscent = ( pSelect->mnHeight * +pBuiltinFont->m_nAscent + 500 ) / 1000; + pMetric->mnDescent = ( pSelect->mnHeight * -pBuiltinFont->m_nDescent + 500 ) / 1000; + pMetric->mnIntLeading = 0; + pMetric->mnExtLeading = 0; + pMetric->mnSlant = 0; + pMetric->mbScalableFont = true; + pMetric->mbDevice = true; +} + +// ----------------------------------------------------------------------- + +namespace vcl { + +class PDFSalLayout : public GenericSalLayout +{ + PDFWriterImpl& mrPDFWriterImpl; + const PDFWriterImpl::BuiltinFont& mrBuiltinFont; + bool mbIsSymbolFont; + long mnPixelPerEM; + String maOrigText; + +public: + PDFSalLayout( PDFWriterImpl&, + const PDFWriterImpl::BuiltinFont&, + long nPixelPerEM, int nOrientation ); + + void SetText( const String& rText ) { maOrigText = rText; } + virtual bool LayoutText( ImplLayoutArgs& ); + virtual void InitFont() const; + virtual void DrawText( SalGraphics& ) const; +}; + +} + +// ----------------------------------------------------------------------- + +PDFSalLayout::PDFSalLayout( PDFWriterImpl& rPDFWriterImpl, + const PDFWriterImpl::BuiltinFont& rBuiltinFont, + long nPixelPerEM, int nOrientation ) +: mrPDFWriterImpl( rPDFWriterImpl ), + mrBuiltinFont( rBuiltinFont ), + mnPixelPerEM( nPixelPerEM ) +{ + mbIsSymbolFont = (rBuiltinFont.m_eCharSet != RTL_TEXTENCODING_MS_1252); + SetOrientation( nOrientation ); +} + +// ----------------------------------------------------------------------- + +bool PDFSalLayout::LayoutText( ImplLayoutArgs& rArgs ) +{ + const String aText( rArgs.mpStr+rArgs.mnMinCharPos, sal::static_int_cast<xub_StrLen>(rArgs.mnEndCharPos-rArgs.mnMinCharPos) ); + SetText( aText ); + SetUnitsPerPixel( 1000 ); + + rtl_UnicodeToTextConverter aConv = rtl_createTextToUnicodeConverter( mrBuiltinFont.m_eCharSet ); + + Point aNewPos( 0, 0 ); + bool bRightToLeft; + for( int nCharPos = -1; rArgs.GetNextPos( &nCharPos, &bRightToLeft ); ) + { + // TODO: handle unicode surrogates + // on the other hand the PDF builtin fonts don't support them anyway + sal_Unicode cChar = rArgs.mpStr[ nCharPos ]; + if( bRightToLeft ) + cChar = static_cast<sal_Unicode>(GetMirroredChar( cChar )); + + if( 1 ) // TODO: shortcut for ASCII? + { + sal_Char aBuf[4]; + sal_uInt32 nInfo; + sal_Size nSrcCvtChars; + + sal_Size nConv = rtl_convertUnicodeToText( aConv, + NULL, + &cChar, 1, + aBuf, sizeof(aBuf)/sizeof(*aBuf), + RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR, + &nInfo, &nSrcCvtChars ); + // check whether conversion was possible + // else fallback font is needed as the standard fonts + // are handled via WinAnsi encoding + if( nConv > 0 ) + cChar = ((sal_Unicode)aBuf[0]) & 0x00ff; + } + if( cChar & 0xff00 ) + { + cChar = 0; // NotDef glyph + rArgs.NeedFallback( nCharPos, bRightToLeft ); + } + + long nGlyphWidth = (long)mrBuiltinFont.m_aWidths[cChar] * mnPixelPerEM; + long nGlyphFlags = 0; // builtin fonts don't have diacritic glyphs + if( bRightToLeft ) + nGlyphFlags |= GlyphItem::IS_RTL_GLYPH; + // TODO: get kerning from builtin fonts + GlyphItem aGI( nCharPos, cChar, aNewPos, nGlyphFlags, nGlyphWidth ); + AppendGlyph( aGI ); + + aNewPos.X() += nGlyphWidth; + } + + rtl_destroyUnicodeToTextConverter( aConv ); + + return true; +} + +// ----------------------------------------------------------------------- + +void PDFSalLayout::InitFont() const +{ + // TODO: recreate font with all its attributes +} + +// ----------------------------------------------------------------------- + +void PDFSalLayout::DrawText( SalGraphics& ) const +{ + mrPDFWriterImpl.drawLayout( *const_cast<PDFSalLayout*>(this), maOrigText, true ); +} + +// ----------------------------------------------------------------------- + +SalLayout* PDFWriterImpl::GetTextLayout( ImplLayoutArgs& rArgs, ImplFontSelectData* pSelect ) +{ + DBG_ASSERT( (pSelect->mpFontData != NULL), + "PDFWriterImpl::GetTextLayout mpFontData is NULL" ); + + const ImplPdfBuiltinFontData* pFD = GetPdfFontData( pSelect->mpFontData ); + if( !pFD ) + return NULL; + const BuiltinFont* pBuiltinFont = pFD->GetBuiltinFont(); + + long nPixelPerEM = pSelect->mnWidth ? pSelect->mnWidth : pSelect->mnHeight; + int nOrientation = pSelect->mnOrientation; + PDFSalLayout* pLayout = new PDFSalLayout( *this, *pBuiltinFont, nPixelPerEM, nOrientation ); + pLayout->SetText( rArgs.mpStr ); + return pLayout; +} + +sal_Int32 PDFWriterImpl::newPage( sal_Int32 nPageWidth, sal_Int32 nPageHeight, PDFWriter::Orientation eOrientation ) +{ + if( m_aContext.Encrypt && m_aPages.empty() ) + initEncryption(); + + endPage(); + m_nCurrentPage = m_aPages.size(); + m_aPages.push_back( PDFPage(this, nPageWidth, nPageHeight, eOrientation ) ); + m_aPages.back().m_nPageIndex = m_nCurrentPage; + m_aPages.back().beginStream(); + + // setup global graphics state + // linewidth is "1 pixel" by default + OStringBuffer aBuf( 16 ); + appendDouble( 72.0/double(getReferenceDevice()->ImplGetDPIX()), aBuf ); + aBuf.append( " w\n" ); + writeBuffer( aBuf.getStr(), aBuf.getLength() ); + + return m_nCurrentPage; +} + +void PDFWriterImpl::endPage() +{ + if( m_aPages.begin() != m_aPages.end() ) + { + // close eventual MC sequence + endStructureElementMCSeq(); + + // sanity check + if( m_aOutputStreams.begin() != m_aOutputStreams.end() ) + { + DBG_ERROR( "redirection across pages !!!" ); + m_aOutputStreams.clear(); // leak ! + m_aMapMode.SetOrigin( Point() ); + } + + m_aGraphicsStack.clear(); + m_aGraphicsStack.push_back( GraphicsState() ); + + // this should pop the PDF graphics stack if necessary + updateGraphicsState(); + + m_aPages.back().endStream(); + + // reset the default font + Font aFont; + aFont.SetName( String( RTL_CONSTASCII_USTRINGPARAM( "Times" ) ) ); + aFont.SetSize( Size( 0, 12 ) ); + + m_aCurrentPDFState = m_aGraphicsStack.front(); + m_aGraphicsStack.front().m_aFont = aFont; + + for( std::list<BitmapEmit>::iterator it = m_aBitmaps.begin(); + it != m_aBitmaps.end(); ++it ) + { + if( ! it->m_aBitmap.IsEmpty() ) + { + writeBitmapObject( *it ); + it->m_aBitmap = BitmapEx(); + } + } + for( std::list<JPGEmit>::iterator jpeg = m_aJPGs.begin(); jpeg != m_aJPGs.end(); ++jpeg ) + { + if( jpeg->m_pStream ) + { + writeJPG( *jpeg ); + delete jpeg->m_pStream; + jpeg->m_pStream = NULL; + jpeg->m_aMask = Bitmap(); + } + } + for( std::list<TransparencyEmit>::iterator t = m_aTransparentObjects.begin(); + t != m_aTransparentObjects.end(); ++t ) + { + if( t->m_pContentStream ) + { + writeTransparentObject( *t ); + delete t->m_pContentStream; + t->m_pContentStream = NULL; + } + } + } +} + +sal_Int32 PDFWriterImpl::createObject() +{ + m_aObjects.push_back( ~0U ); + return m_aObjects.size(); +} + +bool PDFWriterImpl::updateObject( sal_Int32 n ) +{ + if( ! m_bOpen ) + return false; + + sal_uInt64 nOffset = ~0U; + oslFileError aError = osl_getFilePos( m_aFile, &nOffset ); + DBG_ASSERT( aError == osl_File_E_None, "could not register object" ); + if( aError != osl_File_E_None ) + { + osl_closeFile( m_aFile ); + m_bOpen = false; + } + m_aObjects[ n-1 ] = nOffset; + return aError == osl_File_E_None; +} + +#define CHECK_RETURN( x ) if( !(x) ) return 0 + +sal_Int32 PDFWriterImpl::emitStructParentTree( sal_Int32 nObject ) +{ + if( nObject > 0 ) + { + OStringBuffer aLine( 1024 ); + + aLine.append( nObject ); + aLine.append( " 0 obj\n" + "<</Nums[\n" ); + sal_Int32 nTreeItems = m_aStructParentTree.size(); + for( sal_Int32 n = 0; n < nTreeItems; n++ ) + { + aLine.append( n ); + aLine.append( ' ' ); + aLine.append( m_aStructParentTree[n] ); + aLine.append( "\n" ); + } + aLine.append( "]>>\nendobj\n\n" ); + CHECK_RETURN( updateObject( nObject ) ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + } + return nObject; +} + +const sal_Char* PDFWriterImpl::getAttributeTag( PDFWriter::StructAttribute eAttr ) +{ + static std::map< PDFWriter::StructAttribute, const char* > aAttributeStrings; + // fill maps once + if( aAttributeStrings.empty() ) + { + aAttributeStrings[ PDFWriter::Placement ] = "Placement"; + aAttributeStrings[ PDFWriter::WritingMode ] = "WritingMode"; + aAttributeStrings[ PDFWriter::SpaceBefore ] = "SpaceBefore"; + aAttributeStrings[ PDFWriter::SpaceAfter ] = "SpaceAfter"; + aAttributeStrings[ PDFWriter::StartIndent ] = "StartIndent"; + aAttributeStrings[ PDFWriter::EndIndent ] = "EndIndent"; + aAttributeStrings[ PDFWriter::TextIndent ] = "TextIndent"; + aAttributeStrings[ PDFWriter::TextAlign ] = "TextAlign"; + aAttributeStrings[ PDFWriter::Width ] = "Width"; + aAttributeStrings[ PDFWriter::Height ] = "Height"; + aAttributeStrings[ PDFWriter::BlockAlign ] = "BlockAlign"; + aAttributeStrings[ PDFWriter::InlineAlign ] = "InlineAlign"; + aAttributeStrings[ PDFWriter::LineHeight ] = "LineHeight"; + aAttributeStrings[ PDFWriter::BaselineShift ] = "BaselineShift"; + aAttributeStrings[ PDFWriter::TextDecorationType ] = "TextDecorationType"; + aAttributeStrings[ PDFWriter::ListNumbering ] = "ListNumbering"; + aAttributeStrings[ PDFWriter::RowSpan ] = "RowSpan"; + aAttributeStrings[ PDFWriter::ColSpan ] = "ColSpan"; + aAttributeStrings[ PDFWriter::LinkAnnotation ] = "LinkAnnotation"; + } + + std::map< PDFWriter::StructAttribute, const char* >::const_iterator it = + aAttributeStrings.find( eAttr ); + +#if OSL_DEBUG_LEVEL > 1 + if( it == aAttributeStrings.end() ) + fprintf( stderr, "invalid PDFWriter::StructAttribute %d\n", eAttr ); +#endif + + return it != aAttributeStrings.end() ? it->second : ""; +} + +const sal_Char* PDFWriterImpl::getAttributeValueTag( PDFWriter::StructAttributeValue eVal ) +{ + static std::map< PDFWriter::StructAttributeValue, const char* > aValueStrings; + + if( aValueStrings.empty() ) + { + aValueStrings[ PDFWriter::NONE ] = "None"; + aValueStrings[ PDFWriter::Block ] = "Block"; + aValueStrings[ PDFWriter::Inline ] = "Inline"; + aValueStrings[ PDFWriter::Before ] = "Before"; + aValueStrings[ PDFWriter::After ] = "After"; + aValueStrings[ PDFWriter::Start ] = "Start"; + aValueStrings[ PDFWriter::End ] = "End"; + aValueStrings[ PDFWriter::LrTb ] = "LrTb"; + aValueStrings[ PDFWriter::RlTb ] = "RlTb"; + aValueStrings[ PDFWriter::TbRl ] = "TbRl"; + aValueStrings[ PDFWriter::Center ] = "Center"; + aValueStrings[ PDFWriter::Justify ] = "Justify"; + aValueStrings[ PDFWriter::Auto ] = "Auto"; + aValueStrings[ PDFWriter::Middle ] = "Middle"; + aValueStrings[ PDFWriter::Normal ] = "Normal"; + aValueStrings[ PDFWriter::Underline ] = "Underline"; + aValueStrings[ PDFWriter::Overline ] = "Overline"; + aValueStrings[ PDFWriter::LineThrough ] = "LineThrough"; + aValueStrings[ PDFWriter::Disc ] = "Disc"; + aValueStrings[ PDFWriter::Circle ] = "Circle"; + aValueStrings[ PDFWriter::Square ] = "Square"; + aValueStrings[ PDFWriter::Decimal ] = "Decimal"; + aValueStrings[ PDFWriter::UpperRoman ] = "UpperRoman"; + aValueStrings[ PDFWriter::LowerRoman ] = "LowerRoman"; + aValueStrings[ PDFWriter::UpperAlpha ] = "UpperAlpha"; + aValueStrings[ PDFWriter::LowerAlpha ] = "LowerAlpha"; + } + + std::map< PDFWriter::StructAttributeValue, const char* >::const_iterator it = + aValueStrings.find( eVal ); + +#if OSL_DEBUG_LEVEL > 1 + if( it == aValueStrings.end() ) + fprintf( stderr, "invalid PDFWriter::StructAttributeValue %d\n", eVal ); +#endif + + return it != aValueStrings.end() ? it->second : ""; +} + +static void appendStructureAttributeLine( PDFWriter::StructAttribute i_eAttr, const PDFWriterImpl::PDFStructureAttribute& i_rVal, OStringBuffer& o_rLine, bool i_bIsFixedInt ) +{ + o_rLine.append( "/" ); + o_rLine.append( PDFWriterImpl::getAttributeTag( i_eAttr ) ); + + if( i_rVal.eValue != PDFWriter::Invalid ) + { + o_rLine.append( "/" ); + o_rLine.append( PDFWriterImpl::getAttributeValueTag( i_rVal.eValue ) ); + } + else + { + // numerical value + o_rLine.append( " " ); + if( i_bIsFixedInt ) + appendFixedInt( i_rVal.nValue, o_rLine ); + else + o_rLine.append( i_rVal.nValue ); + } + o_rLine.append( "\n" ); +} + +OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle ) +{ + // create layout, list and table attribute sets + OStringBuffer aLayout(256), aList(64), aTable(64); + for( PDFStructAttributes::const_iterator it = i_rEle.m_aAttributes.begin(); + it != i_rEle.m_aAttributes.end(); ++it ) + { + if( it->first == PDFWriter::ListNumbering ) + appendStructureAttributeLine( it->first, it->second, aList, true ); + else if( it->first == PDFWriter::RowSpan || + it->first == PDFWriter::ColSpan ) + appendStructureAttributeLine( it->first, it->second, aTable, false ); + else if( it->first == PDFWriter::LinkAnnotation ) + { + sal_Int32 nLink = it->second.nValue; + std::map< sal_Int32, sal_Int32 >::const_iterator link_it = + m_aLinkPropertyMap.find( nLink ); + if( link_it != m_aLinkPropertyMap.end() ) + nLink = link_it->second; + if( nLink >= 0 && nLink < (sal_Int32)m_aLinks.size() ) + { + // update struct parent of link + OStringBuffer aStructParentEntry( 32 ); + aStructParentEntry.append( i_rEle.m_nObject ); + aStructParentEntry.append( " 0 R" ); + m_aStructParentTree.push_back( aStructParentEntry.makeStringAndClear() ); + m_aLinks[ nLink ].m_nStructParent = m_aStructParentTree.size()-1; + + sal_Int32 nRefObject = createObject(); + OStringBuffer aRef( 256 ); + aRef.append( nRefObject ); + aRef.append( " 0 obj\n" + "<</Type/OBJR/Obj " ); + aRef.append( m_aLinks[ nLink ].m_nObject ); + aRef.append( " 0 R>>\n" + "endobj\n\n" + ); + updateObject( nRefObject ); + writeBuffer( aRef.getStr(), aRef.getLength() ); + + i_rEle.m_aKids.push_back( PDFStructureElementKid( nRefObject ) ); + } + else + { + DBG_ERROR( "unresolved link id for Link structure" ); +#if OSL_DEBUG_LEVEL > 1 + fprintf( stderr, "unresolved link id %" SAL_PRIdINT32 " for Link structure\n", nLink ); + { + OStringBuffer aLine( "unresolved link id " ); + aLine.append( nLink ); + aLine.append( " for Link structure" ); + emitComment( aLine.getStr() ); + } +#endif + } + } + else + appendStructureAttributeLine( it->first, it->second, aLayout, true ); + } + if( ! i_rEle.m_aBBox.IsEmpty() ) + { + aLayout.append( "/BBox[" ); + appendFixedInt( i_rEle.m_aBBox.Left(), aLayout ); + aLayout.append( " " ); + appendFixedInt( i_rEle.m_aBBox.Top(), aLayout ); + aLayout.append( " " ); + appendFixedInt( i_rEle.m_aBBox.Right(), aLayout ); + aLayout.append( " " ); + appendFixedInt( i_rEle.m_aBBox.Bottom(), aLayout ); + aLayout.append( "]\n" ); + } + + std::vector< sal_Int32 > aAttribObjects; + if( aLayout.getLength() ) + { + aAttribObjects.push_back( createObject() ); + updateObject( aAttribObjects.back() ); + OStringBuffer aObj( 64 ); + aObj.append( aAttribObjects.back() ); + aObj.append( " 0 obj\n" + "<</O/Layout\n" ); + aLayout.append( ">>\nendobj\n\n" ); + writeBuffer( aObj.getStr(), aObj.getLength() ); + writeBuffer( aLayout.getStr(), aLayout.getLength() ); + } + if( aList.getLength() ) + { + aAttribObjects.push_back( createObject() ); + updateObject( aAttribObjects.back() ); + OStringBuffer aObj( 64 ); + aObj.append( aAttribObjects.back() ); + aObj.append( " 0 obj\n" + "<</O/List\n" ); + aList.append( ">>\nendobj\n\n" ); + writeBuffer( aObj.getStr(), aObj.getLength() ); + writeBuffer( aList.getStr(), aList.getLength() ); + } + if( aTable.getLength() ) + { + aAttribObjects.push_back( createObject() ); + updateObject( aAttribObjects.back() ); + OStringBuffer aObj( 64 ); + aObj.append( aAttribObjects.back() ); + aObj.append( " 0 obj\n" + "<</O/Table\n" ); + aTable.append( ">>\nendobj\n\n" ); + writeBuffer( aObj.getStr(), aObj.getLength() ); + writeBuffer( aTable.getStr(), aTable.getLength() ); + } + + OStringBuffer aRet( 64 ); + if( aAttribObjects.size() > 1 ) + aRet.append( " [" ); + for( std::vector< sal_Int32 >::const_iterator at_it = aAttribObjects.begin(); + at_it != aAttribObjects.end(); ++at_it ) + { + aRet.append( " " ); + aRet.append( *at_it ); + aRet.append( " 0 R" ); + } + if( aAttribObjects.size() > 1 ) + aRet.append( " ]" ); + return aRet.makeStringAndClear(); +} + +sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle ) +{ + if( + // do not emit NonStruct and its children + rEle.m_eType == PDFWriter::NonStructElement && + rEle.m_nOwnElement != rEle.m_nParentElement // but of course emit the struct tree root + ) + return 0; + + for( std::list< sal_Int32 >::const_iterator it = rEle.m_aChildren.begin(); it != rEle.m_aChildren.end(); ++it ) + { + if( *it > 0 && *it < sal_Int32(m_aStructure.size()) ) + { + PDFStructureElement& rChild = m_aStructure[ *it ]; + if( rChild.m_eType != PDFWriter::NonStructElement ) + { + if( rChild.m_nParentElement == rEle.m_nOwnElement ) + emitStructure( rChild ); + else + { + DBG_ERROR( "PDFWriterImpl::emitStructure: invalid child structure element" ); +#if OSL_DEBUG_LEVEL > 1 + fprintf( stderr, "PDFWriterImpl::emitStructure: invalid child structure elemnt with id %" SAL_PRIdINT32 "\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 %" SAL_PRIdINT32 "\n", *it ); +#endif + } + } + + OStringBuffer aLine( 512 ); + aLine.append( rEle.m_nObject ); + aLine.append( " 0 obj\n" + "<</Type" ); + sal_Int32 nParentTree = -1; + if( rEle.m_nOwnElement == rEle.m_nParentElement ) + { + nParentTree = createObject(); + CHECK_RETURN( nParentTree ); + aLine.append( "/StructTreeRoot\n" ); + aLine.append( "/ParentTree " ); + aLine.append( nParentTree ); + aLine.append( " 0 R\n" ); + if( ! m_aRoleMap.empty() ) + { + aLine.append( "/RoleMap<<" ); + for( std::hash_map<OString,OString,OStringHash>::const_iterator + it = m_aRoleMap.begin(); it != m_aRoleMap.end(); ++it ) + { + aLine.append( '/' ); + aLine.append(it->first); + aLine.append( '/' ); + aLine.append( it->second ); + aLine.append( '\n' ); + } + aLine.append( ">>\n" ); + } + } + else + { + aLine.append( "/StructElem\n" + "/S/" ); + if( rEle.m_aAlias.getLength() > 0 ) + aLine.append( rEle.m_aAlias ); + else + aLine.append( getStructureTag( rEle.m_eType ) ); + aLine.append( "\n" + "/P " ); + aLine.append( m_aStructure[ rEle.m_nParentElement ].m_nObject ); + aLine.append( " 0 R\n" + "/Pg " ); + aLine.append( rEle.m_nFirstPageObject ); + aLine.append( " 0 R\n" ); + if( rEle.m_aActualText.getLength() ) + { + aLine.append( "/ActualText" ); + appendUnicodeTextStringEncrypt( rEle.m_aActualText, rEle.m_nObject, aLine ); + aLine.append( "\n" ); + } + if( rEle.m_aAltText.getLength() ) + { + aLine.append( "/Alt" ); + appendUnicodeTextStringEncrypt( rEle.m_aAltText, rEle.m_nObject, aLine ); + aLine.append( "\n" ); + } + } + if( ! rEle.m_aBBox.IsEmpty() || rEle.m_aAttributes.size() ) + { + OString aAttribs = emitStructureAttributes( rEle ); + if( aAttribs.getLength() ) + { + aLine.append( "/A" ); + aLine.append( aAttribs ); + aLine.append( "\n" ); + } + } + if( rEle.m_aLocale.Language.getLength() > 0 ) + { + OUStringBuffer aLocBuf( 16 ); + aLocBuf.append( rEle.m_aLocale.Language.toAsciiLowerCase() ); + if( rEle.m_aLocale.Country.getLength() > 0 ) + { + aLocBuf.append( sal_Unicode('-') ); + aLocBuf.append( rEle.m_aLocale.Country ); + } + aLine.append( "/Lang" ); + appendLiteralStringEncrypt( aLocBuf.makeStringAndClear(), rEle.m_nObject, aLine ); + aLine.append( "\n" ); + } + if( ! rEle.m_aKids.empty() ) + { + unsigned int i = 0; + aLine.append( "/K[" ); + for( std::list< PDFStructureElementKid >::const_iterator it = + rEle.m_aKids.begin(); it != rEle.m_aKids.end(); ++it, i++ ) + { + if( it->nMCID == -1 ) + { + aLine.append( it->nObject ); + aLine.append( " 0 R" ); + aLine.append( ( (i & 15) == 15 ) ? "\n" : " " ); + } + else + { + if( it->nObject == rEle.m_nFirstPageObject ) + { + aLine.append( it->nMCID ); + aLine.append( " " ); + } + else + { + aLine.append( "<</Type/MCR/Pg " ); + aLine.append( it->nObject ); + aLine.append( " 0 R /MCID " ); + aLine.append( it->nMCID ); + aLine.append( ">>\n" ); + } + } + } + aLine.append( "]\n" ); + } + aLine.append( ">>\nendobj\n\n" ); + + CHECK_RETURN( updateObject( rEle.m_nObject ) ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + + CHECK_RETURN( emitStructParentTree( nParentTree ) ); + + return rEle.m_nObject; +} + +bool PDFWriterImpl::emitGradients() +{ + for( std::list<GradientEmit>::iterator it = m_aGradients.begin(); + it != m_aGradients.end(); ++it ) + { + CHECK_RETURN( writeGradientFunction( *it ) ); + } + return true; +} + +bool PDFWriterImpl::emitTilings() +{ + OStringBuffer aTilingObj( 1024 ); + + for( std::vector<TilingEmit>::iterator it = m_aTilings.begin(); it != m_aTilings.end(); ++it ) + { + DBG_ASSERT( it->m_pTilingStream, "tiling without stream" ); + if( ! it->m_pTilingStream ) + continue; + + aTilingObj.setLength( 0 ); + +#if OSL_DEBUG_LEVEL > 1 + { + OStringBuffer aLine( "PDFWriterImpl::emitTilings" ); + emitComment( aLine.getStr() ); + } +#endif + + sal_Int32 nX = (sal_Int32)it->m_aRectangle.Left(); + sal_Int32 nY = (sal_Int32)it->m_aRectangle.Top(); + sal_Int32 nW = (sal_Int32)it->m_aRectangle.GetWidth(); + sal_Int32 nH = (sal_Int32)it->m_aRectangle.GetHeight(); + if( it->m_aCellSize.Width() == 0 ) + it->m_aCellSize.Width() = nW; + if( it->m_aCellSize.Height() == 0 ) + it->m_aCellSize.Height() = nH; + + bool bDeflate = compressStream( it->m_pTilingStream ); + it->m_pTilingStream->Seek( STREAM_SEEK_TO_END ); + sal_Size nTilingStreamSize = it->m_pTilingStream->Tell(); + it->m_pTilingStream->Seek( STREAM_SEEK_TO_BEGIN ); + + // write pattern object + aTilingObj.append( it->m_nObject ); + aTilingObj.append( " 0 obj\n" ); + aTilingObj.append( "<</Type/Pattern/PatternType 1\n" + "/PaintType 1\n" + "/TilingType 2\n" + "/BBox[" ); + appendFixedInt( nX, aTilingObj ); + aTilingObj.append( ' ' ); + appendFixedInt( nY, aTilingObj ); + aTilingObj.append( ' ' ); + appendFixedInt( nX+nW, aTilingObj ); + aTilingObj.append( ' ' ); + appendFixedInt( nY+nH, aTilingObj ); + aTilingObj.append( "]\n" + "/XStep " ); + appendFixedInt( it->m_aCellSize.Width(), aTilingObj ); + aTilingObj.append( "\n" + "/YStep " ); + appendFixedInt( it->m_aCellSize.Height(), aTilingObj ); + aTilingObj.append( "\n" ); + if( it->m_aTransform.matrix[0] != 1.0 || + it->m_aTransform.matrix[1] != 0.0 || + it->m_aTransform.matrix[3] != 0.0 || + it->m_aTransform.matrix[4] != 1.0 || + it->m_aTransform.matrix[2] != 0.0 || + it->m_aTransform.matrix[5] != 0.0 ) + { + aTilingObj.append( "/Matrix [" ); + // TODO: scaling, mirroring on y, etc + appendDouble( it->m_aTransform.matrix[0], aTilingObj ); + aTilingObj.append( ' ' ); + appendDouble( it->m_aTransform.matrix[1], aTilingObj ); + aTilingObj.append( ' ' ); + appendDouble( it->m_aTransform.matrix[3], aTilingObj ); + aTilingObj.append( ' ' ); + appendDouble( it->m_aTransform.matrix[4], aTilingObj ); + aTilingObj.append( ' ' ); + appendDouble( it->m_aTransform.matrix[2], aTilingObj ); + aTilingObj.append( ' ' ); + appendDouble( it->m_aTransform.matrix[5], aTilingObj ); + aTilingObj.append( "]\n" ); + } + aTilingObj.append( "/Resources" ); + it->m_aResources.append( aTilingObj, getFontDictObject() ); + if( bDeflate ) + aTilingObj.append( "/Filter/FlateDecode" ); + aTilingObj.append( "/Length " ); + aTilingObj.append( (sal_Int32)nTilingStreamSize ); + aTilingObj.append( ">>\nstream\n" ); + CHECK_RETURN( updateObject( it->m_nObject ) ); + CHECK_RETURN( writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) ); + checkAndEnableStreamEncryption( it->m_nObject ); + nTilingStreamSize = writeBuffer( it->m_pTilingStream->GetData(), nTilingStreamSize ); + delete it->m_pTilingStream; + it->m_pTilingStream = NULL; + if( nTilingStreamSize == 0 ) + return false; + disableStreamEncryption(); + aTilingObj.setLength( 0 ); + aTilingObj.append( "\nendstream\nendobj\n\n" ); + CHECK_RETURN( writeBuffer( aTilingObj.getStr(), aTilingObj.getLength() ) ); + } + return true; +} + +sal_Int32 PDFWriterImpl::emitBuiltinFont( const ImplFontData* pFont, sal_Int32 nFontObject ) +{ + const ImplPdfBuiltinFontData* pFD = GetPdfFontData( pFont ); + if( !pFD ) + return 0; + const BuiltinFont* pBuiltinFont = pFD->GetBuiltinFont(); + + OStringBuffer aLine( 1024 ); + + if( nFontObject <= 0 ) + nFontObject = createObject(); + CHECK_RETURN( updateObject( nFontObject ) ); + aLine.append( nFontObject ); + aLine.append( " 0 obj\n" + "<</Type/Font/Subtype/Type1/BaseFont/" ); + appendName( pBuiltinFont->m_pPSName, aLine ); + aLine.append( "\n" ); + if( pBuiltinFont->m_eCharSet == RTL_TEXTENCODING_MS_1252 ) + aLine.append( "/Encoding/WinAnsiEncoding\n" ); + aLine.append( ">>\nendobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + return nFontObject; +} + +std::map< sal_Int32, sal_Int32 > PDFWriterImpl::emitSystemFont( const ImplFontData* pFont, EmbedFont& rEmbed ) +{ + std::map< sal_Int32, sal_Int32 > aRet; + if( isBuiltinFont( pFont ) ) + { + aRet[ rEmbed.m_nNormalFontID ] = emitBuiltinFont( pFont ); + return aRet; + } + + sal_Int32 nFontObject = 0; + sal_Int32 nFontDescriptor = 0; + rtl::OString aSubType( "/Type1" ); + FontSubsetInfo aInfo; + // fill in dummy values + aInfo.m_nAscent = 1000; + aInfo.m_nDescent = 200; + aInfo.m_nCapHeight = 1000; + aInfo.m_aFontBBox = Rectangle( Point( -200, -200 ), Size( 1700, 1700 ) ); + aInfo.m_aPSName = pFont->maName; + sal_Int32 pWidths[256]; + rtl_zeroMemory( pWidths, sizeof(pWidths) ); + if( pFont->IsEmbeddable() ) + { + const unsigned char* pFontData = NULL; + long nFontLen = 0; + sal_Ucs nEncodedCodes[256]; + sal_Int32 pEncWidths[256]; + if( (pFontData = (const unsigned char*)m_pReferenceDevice->mpGraphics->GetEmbedFontData( pFont, nEncodedCodes, pEncWidths, aInfo, &nFontLen )) != NULL ) + { + m_pReferenceDevice->mpGraphics->FreeEmbedFontData( pFontData, nFontLen ); + for( int i = 0; i < 256; i++ ) + { + if( nEncodedCodes[i] >= 32 && nEncodedCodes[i] < 256 ) + { + pWidths[i] = pEncWidths[ i ]; + } + } + } + } + else if( pFont->mbSubsettable ) + { + aSubType = rtl::OString( "/TrueType" ); + Int32Vector aGlyphWidths; + Ucs2UIntMap aUnicodeMap; + m_pReferenceDevice->mpGraphics->GetGlyphWidths( pFont, false, aGlyphWidths, aUnicodeMap ); + + OUString aTmpName; + osl_createTempFile( NULL, NULL, &aTmpName.pData ); + sal_Int32 pGlyphIDs[ 256 ]; + sal_uInt8 pEncoding[ 256 ]; + sal_Ucs pUnicodes[ 256 ]; + sal_Int32 pDuWidths[ 256 ]; + + memset( pGlyphIDs, 0, sizeof( pGlyphIDs ) ); + memset( pEncoding, 0, sizeof( pEncoding ) ); + memset( pUnicodes, 0, sizeof( pUnicodes ) ); + memset( pDuWidths, 0, sizeof( pDuWidths ) ); + + for( sal_Ucs c = 32; c < 256; c++ ) + { + pUnicodes[c] = c; + pEncoding[c] = c; + pGlyphIDs[c] = 0; + if( aUnicodeMap.find( c ) != aUnicodeMap.end() ) + pWidths[ c ] = aGlyphWidths[ aUnicodeMap[ c ] ]; + } + + m_pReferenceDevice->mpGraphics->CreateFontSubset( aTmpName, pFont, pGlyphIDs, pEncoding, pDuWidths, 256, aInfo ); + osl_removeFile( aTmpName.pData ); + } + else + { + DBG_ERROR( "system font neither embeddable nor subsettable" ); + } + + // write font descriptor + nFontDescriptor = emitFontDescriptor( pFont, aInfo, 0, 0 ); + if( nFontDescriptor ) + { + // write font object + sal_Int32 nObject = createObject(); + if( updateObject( nObject ) ) + { + OStringBuffer aLine( 1024 ); + aLine.append( nObject ); + aLine.append( " 0 obj\n" + "<</Type/Font/Subtype" ); + aLine.append( aSubType ); + aLine.append( "/BaseFont/" ); + appendName( aInfo.m_aPSName, aLine ); + aLine.append( "\n" ); + if( !pFont->mbSymbolFlag ) + aLine.append( "/Encoding/WinAnsiEncoding\n" ); + aLine.append( "/FirstChar 32 /LastChar 255\n" + "/Widths[" ); + for( int i = 32; i < 256; i++ ) + { + aLine.append( pWidths[i] ); + aLine.append( ((i&15) == 15) ? "\n" : " " ); + } + aLine.append( "]\n" + "/FontDescriptor " ); + aLine.append( nFontDescriptor ); + aLine.append( " 0 R>>\n" + "endobj\n\n" ); + writeBuffer( aLine.getStr(), aLine.getLength() ); + + nFontObject = nObject; + aRet[ rEmbed.m_nNormalFontID ] = nObject; + } + } + + return aRet; +} + +typedef int ThreeInts[3]; +static bool getPfbSegmentLengths( const unsigned char* pFontBytes, int nByteLen, + ThreeInts& rSegmentLengths ) +{ + if( !pFontBytes || (nByteLen < 0) ) + return false; + const unsigned char* pPtr = pFontBytes; + const unsigned char* pEnd = pFontBytes + nByteLen; + + for( int i = 0; i < 3; ++i) { + // read segment1 header + if( pPtr+6 >= pEnd ) + return false; + if( (pPtr[0] != 0x80) || (pPtr[1] >= 0x03) ) + return false; + const int nLen = (pPtr[5]<<24) + (pPtr[4]<<16) + (pPtr[3]<<8) + pPtr[2]; + if( nLen <= 0) + return false; + rSegmentLengths[i] = nLen; + pPtr += nLen + 6; + } + + // read segment-end header + if( pPtr+2 >= pEnd ) + return false; + if( (pPtr[0] != 0x80) || (pPtr[1] != 0x03) ) + return false; + + return true; +} + +struct FontException : public std::exception +{ +}; + +// TODO: always subset instead of embedding the full font => this method becomes obsolete then +std::map< sal_Int32, sal_Int32 > PDFWriterImpl::emitEmbeddedFont( const ImplFontData* pFont, EmbedFont& rEmbed ) +{ + std::map< sal_Int32, sal_Int32 > aRet; + if( isBuiltinFont( pFont ) ) + { + aRet[ rEmbed.m_nNormalFontID ] = emitBuiltinFont( pFont ); + return aRet; + } + + sal_Int32 nFontObject = 0; + sal_Int32 nStreamObject = 0; + sal_Int32 nFontDescriptor = 0; + + // prepare font encoding + const Ucs2SIntMap* pEncoding = m_pReferenceDevice->mpGraphics->GetFontEncodingVector( pFont, NULL ); + sal_Int32 nToUnicodeStream = 0; + sal_uInt8 nEncoding[256]; + sal_Ucs nEncodedCodes[256]; + std::vector<sal_Ucs> aUnicodes; + aUnicodes.reserve( 256 ); + sal_Int32 pUnicodesPerGlyph[256]; + sal_Int32 pEncToUnicodeIndex[256]; + if( pEncoding ) + { + rtl_zeroMemory( nEncoding, sizeof(nEncoding) ); + rtl_zeroMemory( nEncodedCodes, sizeof(nEncodedCodes) ); + rtl_zeroMemory( pUnicodesPerGlyph, sizeof(pUnicodesPerGlyph) ); + rtl_zeroMemory( pEncToUnicodeIndex, sizeof(pEncToUnicodeIndex) ); + for( Ucs2SIntMap::const_iterator it = pEncoding->begin(); it != pEncoding->end(); ++it ) + { + if( it->second != -1 ) + { + sal_Int32 nCode = (sal_Int32)(it->second & 0x000000ff); + nEncoding[ nCode ] = static_cast<sal_uInt8>( nCode ); + nEncodedCodes[ nCode ] = it->first; + pEncToUnicodeIndex[ nCode ] = static_cast<sal_Int32>(aUnicodes.size()); + aUnicodes.push_back( it->first ); + pUnicodesPerGlyph[ nCode ] = 1; + } + } + } + + FontSubsetInfo aInfo; + sal_Int32 pWidths[256]; + const unsigned char* pFontData = NULL; + long nFontLen = 0; + sal_Int32 nLength1, nLength2; + try + { + if( (pFontData = (const unsigned char*)m_pReferenceDevice->mpGraphics->GetEmbedFontData( pFont, nEncodedCodes, pWidths, aInfo, &nFontLen )) != NULL ) + { + if( (aInfo.m_nFontType & FontSubsetInfo::ANY_TYPE1) == 0 ) + throw FontException(); + // see whether it is pfb or pfa; if it is a pfb, fill ranges + // of 6 bytes that are not part of the font program + std::list< int > aSections; + std::list< int >::const_iterator it; + int nIndex = 0; + while( pFontData[nIndex] == 0x80 && nIndex < nFontLen-1 ) + { + aSections.push_back( nIndex ); + if( pFontData[nIndex+1] == 0x03 ) + break; + sal_Int32 nBytes = + ((sal_Int32)pFontData[nIndex+2]) | + ((sal_Int32)pFontData[nIndex+3]) << 8 | + ((sal_Int32)pFontData[nIndex+4]) << 16 | + ((sal_Int32)pFontData[nIndex+5]) << 24; + nIndex += nBytes+6; + } + + // search for eexec + // TODO: use getPfbSegmentLengths() if possible to skip the search thingies below + nIndex = 0; + int nEndAsciiIndex; + int nBeginBinaryIndex; + int nEndBinaryIndex; + do + { + while( nIndex < nFontLen-4 && + ( pFontData[nIndex] != 'e' || + pFontData[nIndex+1] != 'e' || + pFontData[nIndex+2] != 'x' || + pFontData[nIndex+3] != 'e' || + pFontData[nIndex+4] != 'c' + ) + ) + nIndex++; + // check whether we are in a excluded section + for( it = aSections.begin(); it != aSections.end() && (nIndex < *it || nIndex > ((*it) + 5) ); ++it ) + ; + } while( it != aSections.end() && nIndex < nFontLen-4 ); + // this should end the ascii part + if( nIndex > nFontLen-5 ) + throw FontException(); + + nEndAsciiIndex = nIndex+4; + // now count backwards until we can account for 512 '0' + // which is the endmarker of the (hopefully) binary data + // do not count the pfb header sections + int nFound = 0; + nIndex = nFontLen-1; + while( nIndex > 0 && nFound < 512 ) + { + for( it = aSections.begin(); it != aSections.end() && (nIndex < *it || nIndex > ((*it) + 5) ); ++it ) + ; + if( it == aSections.end() ) + { + // inside the 512 '0' block there may only be whitespace + // according to T1 spec; probably it would be to simple + // if all fonts complied + if( pFontData[nIndex] == '0' ) + nFound++; + else if( nFound > 0 && + pFontData[nIndex] != '\r' && + pFontData[nIndex] != '\t' && + pFontData[nIndex] != '\n' && + pFontData[nIndex] != ' ' ) + break; + } + nIndex--; + } + + if( nIndex < 1 || nIndex <= nEndAsciiIndex ) + throw FontException(); + + // nLength3 is the rest of the file - excluding any section headers + // nIndex now points to the first of the 512 '0' characters marking the + // fixed content portion + sal_Int32 nLength3 = nFontLen - nIndex; + for( it = aSections.begin(); it != aSections.end(); ++it ) + { + if( *it >= nIndex ) + { + // special case: nIndex inside a section marker + if( nIndex >= (*it) && (*it)+5 > nIndex ) + nLength3 -= (*it)+5 - nIndex; + else + { + if( *it < nFontLen - 6 ) + nLength3 -= 6; + else // the last section 0x8003 is only 2 bytes after all + nLength3 -= (nFontLen - *it); + } + } + } + + // there may be whitespace to ignore before the 512 '0' + while( pFontData[nIndex] == '\r' || pFontData[nIndex] == '\n' ) + { + nIndex--; + for( it = aSections.begin(); it != aSections.end() && (nIndex < *it || nIndex > ((*it) + 5) ); ++it ) + ; + if( it != aSections.end() ) + { + nIndex = (*it)-1; + break; // this is surely a binary boundary, in ascii case it wouldn't matter + } + } + nEndBinaryIndex = nIndex; + + // search for beginning of binary section + nBeginBinaryIndex = nEndAsciiIndex; + do + { + nBeginBinaryIndex++; + for( it = aSections.begin(); it != aSections.end() && (nBeginBinaryIndex < *it || nBeginBinaryIndex > ((*it) + 5) ); ++it ) + ; + } while( nBeginBinaryIndex < nEndBinaryIndex && + ( pFontData[nBeginBinaryIndex] == '\r' || + pFontData[nBeginBinaryIndex] == '\n' || + it != aSections.end() ) ); + + // it seems to be vital to copy the exact whitespace between binary data + // and eexec, else a invalid font results. so make nEndAsciiIndex + // always immediate in front of nBeginBinaryIndex + nEndAsciiIndex = nBeginBinaryIndex-1; + for( it = aSections.begin(); it != aSections.end() && (nEndAsciiIndex < *it || nEndAsciiIndex > ((*it)+5)); ++it ) + ; + if( it != aSections.end() ) + nEndAsciiIndex = (*it)-1; + + nLength1 = nEndAsciiIndex+1; // including the last character + for( it = aSections.begin(); it != aSections.end() && *it < nEndAsciiIndex; ++it ) + nLength1 -= 6; // decrease by pfb section size + + // if the first four bytes are all ascii hex characters, then binary data + // has to be converted to real binary data + for( nIndex = 0; nIndex < 4 && + ( ( pFontData[ nBeginBinaryIndex+nIndex ] >= '0' && pFontData[ nBeginBinaryIndex+nIndex ] <= '9' ) || + ( pFontData[ nBeginBinaryIndex+nIndex ] >= 'a' && pFontData[ nBeginBinaryIndex+nIndex ] <= 'f' ) || + ( pFontData[ nBeginBinaryIndex+nIndex ] >= 'A' && pFontData[ nBeginBinaryIndex+nIndex ] <= 'F' ) + ); ++nIndex ) + ; + bool bConvertHexData = true; + if( nIndex < 4 ) + { + bConvertHexData = false; + nLength2 = nEndBinaryIndex - nBeginBinaryIndex + 1; // include the last byte + for( it = aSections.begin(); it != aSections.end(); ++it ) + if( *it > nBeginBinaryIndex && *it < nEndBinaryIndex ) + nLength2 -= 6; + } + else + { + // count the hex ascii characters to get nLength2 + nLength2 = 0; + int nNextSectionIndex = 0; + for( it = aSections.begin(); it != aSections.end() && *it < nBeginBinaryIndex; ++it ) + ; + if( it != aSections.end() ) + nNextSectionIndex = *it; + for( nIndex = nBeginBinaryIndex; nIndex <= nEndBinaryIndex; nIndex++ ) + { + if( nIndex == nNextSectionIndex ) + { + nIndex += 6; + ++it; + nNextSectionIndex = (it == aSections.end() ? 0 : *it ); + } + if( ( pFontData[ nIndex ] >= '0' && pFontData[ nIndex ] <= '9' ) || + ( pFontData[ nIndex ] >= 'a' && pFontData[ nIndex ] <= 'f' ) || + ( pFontData[ nIndex ] >= 'A' && pFontData[ nIndex ] <= 'F' ) ) + nLength2++; + } + DBG_ASSERT( !(nLength2 & 1), "uneven number of hex chars in binary pfa section" ); + nLength2 /= 2; + } + + // now we can actually write the font stream ! + #if OSL_DEBUG_LEVEL > 1 + { + OStringBuffer aLine( " PDFWriterImpl::emitEmbeddedFont" ); + emitComment( aLine.getStr() ); + } + #endif + OStringBuffer aLine( 512 ); + nStreamObject = createObject(); + if( !updateObject(nStreamObject)) + throw FontException(); + sal_Int32 nStreamLengthObject = createObject(); + aLine.append( nStreamObject ); + aLine.append( " 0 obj\n" + "<</Length " ); + aLine.append( nStreamLengthObject ); + aLine.append( " 0 R" + #ifndef DEBUG_DISABLE_PDFCOMPRESSION + "/Filter/FlateDecode" + #endif + "/Length1 " ); + aLine.append( nLength1 ); + aLine.append( " /Length2 " ); + aLine.append( nLength2 ); + aLine.append( " /Length3 "); + aLine.append( nLength3 ); + aLine.append( ">>\n" + "stream\n" ); + if( !writeBuffer( aLine.getStr(), aLine.getLength() ) ) + throw FontException(); + + sal_uInt64 nBeginStreamPos = 0; + osl_getFilePos( m_aFile, &nBeginStreamPos ); + + beginCompression(); + checkAndEnableStreamEncryption( nStreamObject ); + + // write ascii section + if( aSections.begin() == aSections.end() ) + { + if( ! writeBuffer( pFontData, nEndAsciiIndex+1 ) ) + throw FontException(); + } + else + { + // first section always starts at 0 + it = aSections.begin(); + nIndex = (*it)+6; + ++it; + while( *it < nEndAsciiIndex ) + { + if( ! writeBuffer( pFontData+nIndex, (*it)-nIndex ) ) + throw FontException(); + nIndex = (*it)+6; + ++it; + } + // write partial last section + if( ! writeBuffer( pFontData+nIndex, nEndAsciiIndex-nIndex+1 ) ) + throw FontException(); + } + + // write binary section + if( ! bConvertHexData ) + { + if( aSections.begin() == aSections.end() ) + { + if( ! writeBuffer( pFontData+nBeginBinaryIndex, nFontLen-nBeginBinaryIndex ) ) + throw FontException(); + } + else + { + for( it = aSections.begin(); *it < nBeginBinaryIndex; ++it ) + ; + // write first partial section + if( ! writeBuffer( pFontData+nBeginBinaryIndex, (*it) - nBeginBinaryIndex ) ) + throw FontException(); + // write following sections + while( it != aSections.end() ) + { + nIndex = (*it)+6; + ++it; + if( nIndex < nFontLen ) // last section marker is usually the EOF which has only 2 bytes + { + sal_Int32 nSectionLen = (it == aSections.end()) ? nFontLen - nIndex : (*it) - nIndex; + if( ! writeBuffer( pFontData+nIndex, nSectionLen ) ) + throw FontException(); + } + } + } + } + else + { + boost::shared_array<unsigned char> pWriteBuffer( new unsigned char[ nLength2 ] ); + rtl_zeroMemory( pWriteBuffer.get(), nLength2 ); + int nWriteIndex = 0; + + int nNextSectionIndex = 0; + for( it = aSections.begin(); it != aSections.end() && *it < nBeginBinaryIndex; ++it ) + ; + if( it != aSections.end() ) + nNextSectionIndex = *it; + for( nIndex = nBeginBinaryIndex; nIndex <= nEndBinaryIndex; nIndex++ ) + { + if( nIndex == nNextSectionIndex ) + { + nIndex += 6; + ++it; + nNextSectionIndex = (it == aSections.end() ? nFontLen : *it ); + } + unsigned char cNibble = 0x80; + if( pFontData[ nIndex ] >= '0' && pFontData[ nIndex ] <= '9' ) + cNibble = pFontData[nIndex] - '0'; + else if( pFontData[ nIndex ] >= 'a' && pFontData[ nIndex ] <= 'f' ) + cNibble = pFontData[nIndex] - 'a' + 10; + else if( pFontData[ nIndex ] >= 'A' && pFontData[ nIndex ] <= 'F' ) + cNibble = pFontData[nIndex] - 'A' + 10; + if( cNibble != 0x80 ) + { + if( !(nWriteIndex & 1 ) ) + cNibble <<= 4; + pWriteBuffer.get()[ nWriteIndex/2 ] |= cNibble; + nWriteIndex++; + } + } + if( ! writeBuffer( pWriteBuffer.get(), nLength2 ) ) + throw FontException(); + if( aSections.empty() ) + { + if( ! writeBuffer( pFontData+nIndex, nFontLen-nIndex ) ) + throw FontException(); + } + else + { + // write rest of this section + if( nIndex < nNextSectionIndex ) + { + if( ! writeBuffer( pFontData+nIndex, nNextSectionIndex - nIndex ) ) + throw FontException(); + } + // write following sections + while( it != aSections.end() ) + { + nIndex = (*it)+6; + ++it; + if( nIndex < nFontLen ) // last section marker is usually the EOF which has only 2 bytes + { + sal_Int32 nSectionLen = (it == aSections.end()) ? nFontLen - nIndex : (*it) - nIndex; + if( ! writeBuffer( pFontData+nIndex, nSectionLen ) ) + throw FontException(); + } + } + } + } + endCompression(); + disableStreamEncryption(); + + + sal_uInt64 nEndStreamPos = 0; + osl_getFilePos( m_aFile, &nEndStreamPos ); + + // and finally close the stream + aLine.setLength( 0 ); + aLine.append( "\nendstream\nendobj\n\n" ); + if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) ) + throw FontException(); + + // write stream length object + aLine.setLength( 0 ); + if( ! updateObject( nStreamLengthObject ) ) + throw FontException(); + aLine.append( nStreamLengthObject ); + aLine.append( " 0 obj\n" ); + aLine.append( (sal_Int64)(nEndStreamPos-nBeginStreamPos ) ); + aLine.append( "\nendobj\n\n" ); + if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) ) + throw FontException(); + } + else + { + rtl::OStringBuffer aErrorComment( 256 ); + aErrorComment.append( "GetEmbedFontData failed for font \"" ); + aErrorComment.append( OUStringToOString( pFont->GetFamilyName(), RTL_TEXTENCODING_UTF8 ) ); + aErrorComment.append( '\"' ); + if( pFont->GetSlant() == ITALIC_NORMAL ) + aErrorComment.append( " italic" ); + else if( pFont->GetSlant() == ITALIC_OBLIQUE ) + aErrorComment.append( " oblique" ); + aErrorComment.append( " weight=" ); + aErrorComment.append( sal_Int32(pFont->GetWeight()) ); + emitComment( aErrorComment.getStr() ); + } + + if( nStreamObject ) + // write font descriptor + nFontDescriptor = emitFontDescriptor( pFont, aInfo, 0, nStreamObject ); + + if( nFontDescriptor ) + { + if( pEncoding ) + nToUnicodeStream = createToUnicodeCMap( nEncoding, &aUnicodes[0], pUnicodesPerGlyph, pEncToUnicodeIndex, sizeof(nEncoding)/sizeof(nEncoding[0]) ); + + // write font object + sal_Int32 nObject = createObject(); + if( ! updateObject( nObject ) ) + throw FontException(); + + OStringBuffer aLine( 1024 ); + aLine.append( nObject ); + aLine.append( " 0 obj\n" + "<</Type/Font/Subtype/Type1/BaseFont/" ); + appendName( aInfo.m_aPSName, aLine ); + aLine.append( "\n" ); + if( !pFont->mbSymbolFlag && pEncoding == 0 ) + aLine.append( "/Encoding/WinAnsiEncoding\n" ); + if( nToUnicodeStream ) + { + aLine.append( "/ToUnicode " ); + aLine.append( nToUnicodeStream ); + aLine.append( " 0 R\n" ); + } + aLine.append( "/FirstChar 0 /LastChar 255\n" + "/Widths[" ); + for( int i = 0; i < 256; i++ ) + { + aLine.append( pWidths[i] ); + aLine.append( ((i&15) == 15) ? "\n" : " " ); + } + aLine.append( "]\n" + "/FontDescriptor " ); + aLine.append( nFontDescriptor ); + aLine.append( " 0 R>>\n" + "endobj\n\n" ); + if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) ) + throw FontException(); + + nFontObject = nObject; + + aRet[ rEmbed.m_nNormalFontID ] = nObject; + + // write additional encodings + for( std::list< EmbedEncoding >::iterator enc_it = rEmbed.m_aExtendedEncodings.begin(); enc_it != rEmbed.m_aExtendedEncodings.end(); ++enc_it ) + { + sal_Int32 aEncWidths[ 256 ]; + // emit encoding dict + sal_Int32 nEncObject = createObject(); + if( ! updateObject( nEncObject ) ) + throw FontException(); + + OutputDevice* pRef = getReferenceDevice(); + pRef->Push( PUSH_FONT | PUSH_MAPMODE ); + pRef->SetMapMode( MapMode( MAP_PIXEL ) ); + Font aFont( pFont->GetFamilyName(), pFont->GetStyleName(), Size( 0, 1000 ) ); + aFont.SetWeight( pFont->GetWeight() ); + aFont.SetItalic( pFont->GetSlant() ); + aFont.SetPitch( pFont->GetPitch() ); + pRef->SetFont( aFont ); + pRef->ImplNewFont(); + + aLine.setLength( 0 ); + aLine.append( nEncObject ); + aLine.append( " 0 obj\n" + "<</Type/Encoding/Differences[ 0\n" ); + int nEncoded = 0; + aUnicodes.clear(); + for( std::vector< EmbedCode >::iterator str_it = enc_it->m_aEncVector.begin(); str_it != enc_it->m_aEncVector.end(); ++str_it ) + { + String aStr( str_it->m_aUnicode ); + aEncWidths[nEncoded] = pRef->GetTextWidth( aStr ); + nEncodedCodes[nEncoded] = str_it->m_aUnicode; + nEncoding[nEncoded] = sal::static_int_cast<sal_uInt8>(nEncoded); + pEncToUnicodeIndex[nEncoded] = static_cast<sal_Int32>(aUnicodes.size()); + aUnicodes.push_back( nEncodedCodes[nEncoded] ); + pUnicodesPerGlyph[nEncoded] = 1; + + aLine.append( " /" ); + aLine.append( str_it->m_aName ); + if( !((++nEncoded) & 15) ) + aLine.append( "\n" ); + } + aLine.append( "]>>\n" + "endobj\n\n" ); + + pRef->Pop(); + + if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) ) + throw FontException(); + + nToUnicodeStream = createToUnicodeCMap( nEncoding, &aUnicodes[0], pUnicodesPerGlyph, pEncToUnicodeIndex, nEncoded ); + + nObject = createObject(); + if( ! updateObject( nObject ) ) + throw FontException(); + + aLine.setLength( 0 ); + aLine.append( nObject ); + aLine.append( " 0 obj\n" + "<</Type/Font/Subtype/Type1/BaseFont/" ); + appendName( aInfo.m_aPSName, aLine ); + aLine.append( "\n" ); + aLine.append( "/Encoding " ); + aLine.append( nEncObject ); + aLine.append( " 0 R\n" ); + if( nToUnicodeStream ) + { + aLine.append( "/ToUnicode " ); + aLine.append( nToUnicodeStream ); + aLine.append( " 0 R\n" ); + } + aLine.append( "/FirstChar 0\n" + "/LastChar " ); + aLine.append( (sal_Int32)(nEncoded-1) ); + aLine.append( "\n" + "/Widths[" ); + for( int i = 0; i < nEncoded; i++ ) + { + aLine.append( aEncWidths[i] ); + aLine.append( ((i&15) == 15) ? "\n" : " " ); + } + aLine.append( " ]\n" + "/FontDescriptor " ); + aLine.append( nFontDescriptor ); + aLine.append( " 0 R>>\n" + "endobj\n\n" ); + if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) ) + throw FontException(); + + aRet[ enc_it->m_nFontID ] = nObject; + } + } + } + catch( FontException& ) + { + // these do nothing in case there was no compression or encryption ongoing + endCompression(); + disableStreamEncryption(); + } + + if( pFontData ) + m_pReferenceDevice->mpGraphics->FreeEmbedFontData( pFontData, nFontLen ); + + return aRet; +} + +static void appendSubsetName( int nSubsetID, const OUString& rPSName, OStringBuffer& rBuffer ) +{ + if( nSubsetID ) + { + for( int i = 0; i < 6; i++ ) + { + int nOffset = (nSubsetID % 26); + nSubsetID /= 26; + rBuffer.append( (sal_Char)('A'+nOffset) ); + } + rBuffer.append( '+' ); + } + appendName( rPSName, rBuffer ); +} + +sal_Int32 PDFWriterImpl::createToUnicodeCMap( sal_uInt8* pEncoding, + sal_Ucs* pUnicodes, + sal_Int32* pUnicodesPerGlyph, + sal_Int32* pEncToUnicodeIndex, + int nGlyphs ) +{ + int nMapped = 0, n = 0; + for( n = 0; n < nGlyphs; n++ ) + if( pUnicodes[pEncToUnicodeIndex[n]] && pUnicodesPerGlyph[n] ) + nMapped++; + + if( nMapped == 0 ) + return 0; + + sal_Int32 nStream = createObject(); + CHECK_RETURN( updateObject( nStream ) ); + + OStringBuffer aContents( 1024 ); + aContents.append( + "/CIDInit/ProcSet findresource begin\n" + "12 dict begin\n" + "begincmap\n" + "/CIDSystemInfo<<\n" + "/Registry (Adobe)\n" + "/Ordering (UCS)\n" + "/Supplement 0\n" + ">> def\n" + "/CMapName/Adobe-Identity-UCS def\n" + "/CMapType 2 def\n" + "1 begincodespacerange\n" + "<00> <FF>\n" + "endcodespacerange\n" + ); + int nCount = 0; + for( n = 0; n < nGlyphs; n++ ) + { + if( pUnicodes[pEncToUnicodeIndex[n]] && pUnicodesPerGlyph[n] ) + { + if( (nCount % 100) == 0 ) + { + if( nCount ) + aContents.append( "endbfchar\n" ); + aContents.append( (sal_Int32)((nMapped-nCount > 100) ? 100 : nMapped-nCount ) ); + aContents.append( " beginbfchar\n" ); + } + aContents.append( '<' ); + appendHex( (sal_Int8)pEncoding[n], aContents ); + aContents.append( "> <" ); + // TODO: handle unicodes>U+FFFF + sal_Int32 nIndex = pEncToUnicodeIndex[n]; + for( sal_Int32 j = 0; j < pUnicodesPerGlyph[n]; j++ ) + { + appendHex( (sal_Int8)(pUnicodes[nIndex + j] / 256), aContents ); + appendHex( (sal_Int8)(pUnicodes[nIndex + j] & 255), aContents ); + } + aContents.append( ">\n" ); + nCount++; + } + } + aContents.append( "endbfchar\n" + "endcmap\n" + "CMapName currentdict /CMap defineresource pop\n" + "end\n" + "end\n" ); +#ifndef DEBUG_DISABLE_PDFCOMPRESSION + ZCodec* pCodec = new ZCodec( 0x4000, 0x4000 ); + SvMemoryStream aStream; + pCodec->BeginCompression(); + pCodec->Write( aStream, (const BYTE*)aContents.getStr(), aContents.getLength() ); + pCodec->EndCompression(); + delete pCodec; +#endif + +#if OSL_DEBUG_LEVEL > 1 + { + OStringBuffer aLine( " PDFWriterImpl::createToUnicodeCMap" ); + emitComment( aLine.getStr() ); + } +#endif + OStringBuffer aLine( 40 ); + + aLine.append( nStream ); + aLine.append( " 0 obj\n<</Length " ); +#ifndef DEBUG_DISABLE_PDFCOMPRESSION + sal_Int32 nLen = (sal_Int32)aStream.Tell(); + aStream.Seek( 0 ); + aLine.append( nLen ); + aLine.append( "/Filter/FlateDecode" ); +#else + aLine.append( aContents.getLength() ); +#endif + aLine.append( ">>\nstream\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + checkAndEnableStreamEncryption( nStream ); +#ifndef DEBUG_DISABLE_PDFCOMPRESSION + CHECK_RETURN( writeBuffer( aStream.GetData(), nLen ) ); +#else + CHECK_RETURN( writeBuffer( aContents.getStr(), aContents.getLength() ) ); +#endif + disableStreamEncryption(); + aLine.setLength( 0 ); + aLine.append( "\nendstream\n" + "endobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + return nStream; +} + +sal_Int32 PDFWriterImpl::emitFontDescriptor( const ImplFontData* pFont, FontSubsetInfo& rInfo, sal_Int32 nSubsetID, sal_Int32 nFontStream ) +{ + OStringBuffer aLine( 1024 ); + // get font flags, see PDF reference 1.4 p. 358 + // possibly characters outside Adobe standard encoding + // so set Symbolic flag + sal_Int32 nFontFlags = (1<<2); + if( pFont->GetSlant() == ITALIC_NORMAL || pFont->GetSlant() == ITALIC_OBLIQUE ) + nFontFlags |= (1 << 6); + if( pFont->GetPitch() == PITCH_FIXED ) + nFontFlags |= 1; + if( pFont->GetFamilyType() == FAMILY_SCRIPT ) + nFontFlags |= (1 << 3); + else if( pFont->GetFamilyType() == FAMILY_ROMAN ) + nFontFlags |= (1 << 1); + + sal_Int32 nFontDescriptor = createObject(); + CHECK_RETURN( updateObject( nFontDescriptor ) ); + aLine.setLength( 0 ); + aLine.append( nFontDescriptor ); + aLine.append( " 0 obj\n" + "<</Type/FontDescriptor/FontName/" ); + appendSubsetName( nSubsetID, rInfo.m_aPSName, aLine ); + aLine.append( "\n" + "/Flags " ); + aLine.append( nFontFlags ); + aLine.append( "\n" + "/FontBBox[" ); + // note: Top and Bottom are reversed in VCL and PDF rectangles + aLine.append( (sal_Int32)rInfo.m_aFontBBox.TopLeft().X() ); + aLine.append( ' ' ); + aLine.append( (sal_Int32)rInfo.m_aFontBBox.TopLeft().Y() ); + aLine.append( ' ' ); + aLine.append( (sal_Int32)rInfo.m_aFontBBox.BottomRight().X() ); + aLine.append( ' ' ); + aLine.append( (sal_Int32)(rInfo.m_aFontBBox.BottomRight().Y()+1) ); + aLine.append( "]/ItalicAngle " ); + if( pFont->GetSlant() == ITALIC_OBLIQUE || pFont->GetSlant() == ITALIC_NORMAL ) + aLine.append( "-30" ); + else + aLine.append( "0" ); + aLine.append( "\n" + "/Ascent " ); + aLine.append( (sal_Int32)rInfo.m_nAscent ); + aLine.append( "\n" + "/Descent " ); + aLine.append( (sal_Int32)-rInfo.m_nDescent ); + aLine.append( "\n" + "/CapHeight " ); + aLine.append( (sal_Int32)rInfo.m_nCapHeight ); + // According to PDF reference 1.4 StemV is required + // seems a tad strange to me, but well ... + aLine.append( "\n" + "/StemV 80\n" ); + if( nFontStream ) + { + aLine.append( "/FontFile" ); + switch( rInfo.m_nFontType ) + { + case FontSubsetInfo::SFNT_TTF: + aLine.append( '2' ); + break; + case FontSubsetInfo::TYPE1_PFA: + case FontSubsetInfo::TYPE1_PFB: + case FontSubsetInfo::ANY_TYPE1: + break; + default: + DBG_ERROR( "unknown fonttype in PDF font descriptor" ); + return 0; + } + aLine.append( ' ' ); + aLine.append( nFontStream ); + aLine.append( " 0 R\n" ); + } + aLine.append( ">>\n" + "endobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + + return nFontDescriptor; +} + +void PDFWriterImpl::appendBuiltinFontsToDict( OStringBuffer& rDict ) const +{ + for( std::map< sal_Int32, sal_Int32 >::const_iterator it = + m_aBuiltinFontToObjectMap.begin(); it != m_aBuiltinFontToObjectMap.end(); ++it ) + { + rDict.append( m_aBuiltinFonts[it->first].getNameObject() ); + rDict.append( ' ' ); + rDict.append( it->second ); + rDict.append( " 0 R" ); + } +} + +bool PDFWriterImpl::emitFonts() +{ + if( ! m_pReferenceDevice->ImplGetGraphics() ) + return false; + + OStringBuffer aLine( 1024 ); + + std::map< sal_Int32, sal_Int32 > aFontIDToObject; + + OUString aTmpName; + osl_createTempFile( NULL, NULL, &aTmpName.pData ); + for( FontSubsetData::iterator it = m_aSubsets.begin(); it != m_aSubsets.end(); ++it ) + { + for( FontEmitList::iterator lit = it->second.m_aSubsets.begin(); lit != it->second.m_aSubsets.end(); ++lit ) + { + sal_Int32 pGlyphIDs[ 256 ]; + sal_Int32 pWidths[ 256 ]; + sal_uInt8 pEncoding[ 256 ]; + sal_Int32 pEncToUnicodeIndex[ 256 ]; + sal_Int32 pUnicodesPerGlyph[ 256 ]; + std::vector<sal_Ucs> aUnicodes; + aUnicodes.reserve( 256 ); + int nGlyphs = 1; + // fill arrays and prepare encoding index map + sal_Int32 nToUnicodeStream = 0; + + rtl_zeroMemory( pGlyphIDs, sizeof( pGlyphIDs ) ); + rtl_zeroMemory( pEncoding, sizeof( pEncoding ) ); + rtl_zeroMemory( pUnicodesPerGlyph, sizeof( pUnicodesPerGlyph ) ); + rtl_zeroMemory( pEncToUnicodeIndex, sizeof( pEncToUnicodeIndex ) ); + for( FontEmitMapping::iterator fit = lit->m_aMapping.begin(); fit != lit->m_aMapping.end();++fit ) + { + sal_uInt8 nEnc = fit->second.getGlyphId(); + + DBG_ASSERT( pGlyphIDs[nEnc] == 0 && pEncoding[nEnc] == 0, "duplicate glyph" ); + DBG_ASSERT( nEnc <= lit->m_aMapping.size(), "invalid glyph encoding" ); + + pGlyphIDs[ nEnc ] = fit->first; + pEncoding[ nEnc ] = nEnc; + pEncToUnicodeIndex[ nEnc ] = static_cast<sal_Int32>(aUnicodes.size()); + pUnicodesPerGlyph[ nEnc ] = fit->second.countCodes(); + for( sal_Int32 n = 0; n < pUnicodesPerGlyph[ nEnc ]; n++ ) + aUnicodes.push_back( fit->second.getCode( n ) ); + if( fit->second.getCode(0) ) + nToUnicodeStream = 1; + if( nGlyphs < 256 ) + nGlyphs++; + else + { + DBG_ERROR( "too many glyphs for subset" ); + } + } + FontSubsetInfo aSubsetInfo; + if( m_pReferenceDevice->mpGraphics->CreateFontSubset( aTmpName, it->first, pGlyphIDs, pEncoding, pWidths, nGlyphs, aSubsetInfo ) ) + { + // create font stream + oslFileHandle aFontFile; + CHECK_RETURN( (osl_File_E_None == osl_openFile( aTmpName.pData, &aFontFile, osl_File_OpenFlag_Read ) ) ); + // get file size + sal_uInt64 nLength1; + CHECK_RETURN( (osl_File_E_None == osl_setFilePos( aFontFile, osl_Pos_End, 0 ) ) ); + CHECK_RETURN( (osl_File_E_None == osl_getFilePos( aFontFile, &nLength1 ) ) ); + CHECK_RETURN( (osl_File_E_None == osl_setFilePos( aFontFile, osl_Pos_Absolut, 0 ) ) ); + + #if OSL_DEBUG_LEVEL > 1 + { + OStringBuffer aLine1( " PDFWriterImpl::emitFonts" ); + emitComment( aLine1.getStr() ); + } + #endif + sal_Int32 nFontStream = createObject(); + sal_Int32 nStreamLengthObject = createObject(); + CHECK_RETURN( updateObject( nFontStream ) ); + aLine.setLength( 0 ); + aLine.append( nFontStream ); + aLine.append( " 0 obj\n" + "<</Length " ); + aLine.append( (sal_Int32)nStreamLengthObject ); + aLine.append( " 0 R" + #ifndef DEBUG_DISABLE_PDFCOMPRESSION + "/Filter/FlateDecode" + #endif + "/Length1 " ); + + sal_uInt64 nStartPos = 0; + if( aSubsetInfo.m_nFontType == FontSubsetInfo::SFNT_TTF ) + { + aLine.append( (sal_Int32)nLength1 ); + + aLine.append( ">>\n" + "stream\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + CHECK_RETURN( (osl_File_E_None == osl_getFilePos( m_aFile, &nStartPos ) ) ); + + // copy font file + beginCompression(); + checkAndEnableStreamEncryption( nFontStream ); + sal_Bool bEOF = sal_False; + do + { + char buf[8192]; + sal_uInt64 nRead; + CHECK_RETURN( (osl_File_E_None == osl_readFile( aFontFile, buf, sizeof( buf ), &nRead ) ) ); + CHECK_RETURN( writeBuffer( buf, nRead ) ); + CHECK_RETURN( (osl_File_E_None == osl_isEndOfFile( aFontFile, &bEOF ) ) ); + } while( ! bEOF ); + } + else if( (aSubsetInfo.m_nFontType & FontSubsetInfo::CFF_FONT) != 0 ) + { + // TODO: implement + DBG_ERROR( "PDFWriterImpl does not support CFF-font subsets yet!" ); + } + else if( (aSubsetInfo.m_nFontType & FontSubsetInfo::TYPE1_PFB) != 0 ) // TODO: also support PFA? + { + boost::shared_array<unsigned char> pBuffer( new unsigned char[ nLength1 ] ); + + sal_uInt64 nBytesRead = 0; + CHECK_RETURN( (osl_File_E_None == osl_readFile( aFontFile, pBuffer.get(), nLength1, &nBytesRead ) ) ); + DBG_ASSERT( nBytesRead==nLength1, "PDF-FontSubset read incomplete!" ); + CHECK_RETURN( (osl_File_E_None == osl_setFilePos( aFontFile, osl_Pos_Absolut, 0 ) ) ); + // get the PFB-segment lengths + ThreeInts aSegmentLengths = {0,0,0}; + getPfbSegmentLengths( pBuffer.get(), (int)nBytesRead, aSegmentLengths ); + // the lengths below are mandatory for PDF-exported Type1 fonts + // because the PFB segment headers get stripped! WhyOhWhy. + aLine.append( (sal_Int32)aSegmentLengths[0] ); + aLine.append( "/Length2 " ); + aLine.append( (sal_Int32)aSegmentLengths[1] ); + aLine.append( "/Length3 " ); + aLine.append( (sal_Int32)aSegmentLengths[2] ); + + aLine.append( ">>\n" + "stream\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + CHECK_RETURN( (osl_File_E_None == osl_getFilePos( m_aFile, &nStartPos ) ) ); + + // emit PFB-sections without section headers + beginCompression(); + checkAndEnableStreamEncryption( nFontStream ); + CHECK_RETURN( writeBuffer( &pBuffer[6], aSegmentLengths[0] ) ); + CHECK_RETURN( writeBuffer( &pBuffer[12] + aSegmentLengths[0], aSegmentLengths[1] ) ); + CHECK_RETURN( writeBuffer( &pBuffer[18] + aSegmentLengths[0] + aSegmentLengths[1], aSegmentLengths[2] ) ); + } + else + { + fprintf( stderr, "PDF: CreateFontSubset result in not yet supported format=%d\n",aSubsetInfo.m_nFontType); + aLine.append( "0 >>\nstream\n" ); + } + + endCompression(); + disableStreamEncryption(); + // close the file + osl_closeFile( aFontFile ); + + sal_uInt64 nEndPos = 0; + CHECK_RETURN( (osl_File_E_None == osl_getFilePos( m_aFile, &nEndPos ) ) ); + // end the stream + aLine.setLength( 0 ); + aLine.append( "\nendstream\nendobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + + // emit stream length object + CHECK_RETURN( updateObject( nStreamLengthObject ) ); + aLine.setLength( 0 ); + aLine.append( nStreamLengthObject ); + aLine.append( " 0 obj\n" ); + aLine.append( (sal_Int64)(nEndPos-nStartPos) ); + aLine.append( "\nendobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + + // write font descriptor + sal_Int32 nFontDescriptor = emitFontDescriptor( it->first, aSubsetInfo, lit->m_nFontID, nFontStream ); + + if( nToUnicodeStream ) + nToUnicodeStream = createToUnicodeCMap( pEncoding, &aUnicodes[0], pUnicodesPerGlyph, pEncToUnicodeIndex, nGlyphs ); + + sal_Int32 nFontObject = createObject(); + CHECK_RETURN( updateObject( nFontObject ) ); + aLine.setLength( 0 ); + aLine.append( nFontObject ); + + aLine.append( " 0 obj\n" ); + aLine.append( ((aSubsetInfo.m_nFontType & FontSubsetInfo::ANY_TYPE1) != 0) ? + "<</Type/Font/Subtype/Type1/BaseFont/" : + "<</Type/Font/Subtype/TrueType/BaseFont/" ); + appendSubsetName( lit->m_nFontID, aSubsetInfo.m_aPSName, aLine ); + aLine.append( "\n" + "/FirstChar 0\n" + "/LastChar " ); + aLine.append( (sal_Int32)(nGlyphs-1) ); + aLine.append( "\n" + "/Widths[" ); + for( int i = 0; i < nGlyphs; i++ ) + { + aLine.append( pWidths[ i ] ); + aLine.append( ((i & 15) == 15) ? "\n" : " " ); + } + aLine.append( "]\n" + "/FontDescriptor " ); + aLine.append( nFontDescriptor ); + aLine.append( " 0 R\n" ); + if( nToUnicodeStream ) + { + aLine.append( "/ToUnicode " ); + aLine.append( nToUnicodeStream ); + aLine.append( " 0 R\n" ); + } + aLine.append( ">>\n" + "endobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + + aFontIDToObject[ lit->m_nFontID ] = nFontObject; + } + else + { + const ImplFontData* pFont = it->first; + rtl::OStringBuffer aErrorComment( 256 ); + aErrorComment.append( "CreateFontSubset failed for font \"" ); + aErrorComment.append( OUStringToOString( pFont->GetFamilyName(), RTL_TEXTENCODING_UTF8 ) ); + aErrorComment.append( '\"' ); + if( pFont->GetSlant() == ITALIC_NORMAL ) + aErrorComment.append( " italic" ); + else if( pFont->GetSlant() == ITALIC_OBLIQUE ) + aErrorComment.append( " oblique" ); + aErrorComment.append( " weight=" ); + aErrorComment.append( sal_Int32(pFont->GetWeight()) ); + emitComment( aErrorComment.getStr() ); + } + } + } + osl_removeFile( aTmpName.pData ); + + // emit embedded fonts + for( FontEmbedData::iterator eit = m_aEmbeddedFonts.begin(); eit != m_aEmbeddedFonts.end(); ++eit ) + { + std::map< sal_Int32, sal_Int32 > aObjects = emitEmbeddedFont( eit->first, eit->second ); + for( std::map< sal_Int32, sal_Int32 >::iterator fit = aObjects.begin(); fit != aObjects.end(); ++fit ) + { + CHECK_RETURN( fit->second ); + aFontIDToObject[ fit->first ] = fit->second; + } + } + + // emit system fonts + for( FontEmbedData::iterator sit = m_aSystemFonts.begin(); sit != m_aSystemFonts.end(); ++sit ) + { + std::map< sal_Int32, sal_Int32 > aObjects = emitSystemFont( sit->first, sit->second ); + for( std::map< sal_Int32, sal_Int32 >::iterator fit = aObjects.begin(); fit != aObjects.end(); ++fit ) + { + CHECK_RETURN( fit->second ); + aFontIDToObject[ fit->first ] = fit->second; + } + } + + OStringBuffer aFontDict( 1024 ); + aFontDict.append( getFontDictObject() ); + aFontDict.append( " 0 obj\n" + "<<" ); + int ni = 0; + for( std::map< sal_Int32, sal_Int32 >::iterator mit = aFontIDToObject.begin(); mit != aFontIDToObject.end(); ++mit ) + { + aFontDict.append( "/F" ); + aFontDict.append( mit->first ); + aFontDict.append( ' ' ); + aFontDict.append( mit->second ); + aFontDict.append( " 0 R" ); + if( ((++ni) & 7) == 0 ) + aFontDict.append( '\n' ); + } + // emit builtin font for widget apperances / variable text + for( std::map< sal_Int32, sal_Int32 >::iterator it = m_aBuiltinFontToObjectMap.begin(); + it != m_aBuiltinFontToObjectMap.end(); ++it ) + { + ImplPdfBuiltinFontData aData(m_aBuiltinFonts[it->first]); + it->second = emitBuiltinFont( &aData, it->second ); + } + appendBuiltinFontsToDict( aFontDict ); + aFontDict.append( "\n>>\nendobj\n\n" ); + + CHECK_RETURN( updateObject( getFontDictObject() ) ); + CHECK_RETURN( writeBuffer( aFontDict.getStr(), aFontDict.getLength() ) ); + return true; +} + +sal_Int32 PDFWriterImpl::emitResources() +{ + // emit shadings + if( ! m_aGradients.empty() ) + CHECK_RETURN( emitGradients() ); + // emit tilings + if( ! m_aTilings.empty() ) + CHECK_RETURN( emitTilings() ); + + // emit font dict + CHECK_RETURN( emitFonts() ); + + // emit Resource dict + OStringBuffer aLine( 512 ); + sal_Int32 nResourceDict = getResourceDictObj(); + CHECK_RETURN( updateObject( nResourceDict ) ); + aLine.setLength( 0 ); + aLine.append( nResourceDict ); + aLine.append( " 0 obj\n" ); + m_aGlobalResourceDict.append( aLine, getFontDictObject() ); + aLine.append( "endobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + return nResourceDict; +} + +sal_Int32 PDFWriterImpl::updateOutlineItemCount( std::vector< sal_Int32 >& rCounts, + sal_Int32 nItemLevel, + sal_Int32 nCurrentItemId ) +{ + /* The /Count number of an item is + positive: the number of visible subitems + negative: the negative number of subitems that will become visible if + the item gets opened + see PDF ref 1.4 p 478 + */ + + sal_Int32 nCount = 0; + + if( m_aContext.OpenBookmarkLevels < 0 || // all levels arevisible + m_aContext.OpenBookmarkLevels >= nItemLevel // this level is visible + ) + { + PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ]; + sal_Int32 nChildren = rItem.m_aChildren.size(); + for( sal_Int32 i = 0; i < nChildren; i++ ) + nCount += updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] ); + rCounts[nCurrentItemId] = nCount; + // return 1 (this item) + visible sub items + if( nCount < 0 ) + nCount = 0; + nCount++; + } + else + { + // this bookmark level is invisible + PDFOutlineEntry& rItem = m_aOutline[ nCurrentItemId ]; + sal_Int32 nChildren = rItem.m_aChildren.size(); + rCounts[ nCurrentItemId ] = -sal_Int32(rItem.m_aChildren.size()); + for( sal_Int32 i = 0; i < nChildren; i++ ) + updateOutlineItemCount( rCounts, nItemLevel+1, rItem.m_aChildren[i] ); + nCount = -1; + } + + return nCount; +} + +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; + } + + } + } + + // calculate Count entries for all items + std::vector< sal_Int32 > aCounts( nItems ); + updateOutlineItemCount( aCounts, 0, 0 ); + + // emit hierarchy + for( i = 0; i < nItems; ++i ) + { + PDFOutlineEntry& rItem = m_aOutline[i]; + OStringBuffer aLine( 1024 ); + + CHECK_RETURN( updateObject( rItem.m_nObject ) ); + aLine.append( rItem.m_nObject ); + aLine.append( " 0 obj\n" ); + aLine.append( "<<" ); + // number of visible children (all levels) + if( i > 0 || aCounts[0] > 0 ) + { + aLine.append( "/Count " ); + aLine.append( aCounts[i] ); + } + if( ! rItem.m_aChildren.empty() ) + { + // children list: First, Last + aLine.append( "/First " ); + aLine.append( m_aOutline[rItem.m_aChildren.front()].m_nObject ); + aLine.append( " 0 R/Last " ); + aLine.append( m_aOutline[rItem.m_aChildren.back()].m_nObject ); + aLine.append( " 0 R\n" ); + } + if( i > 0 ) + { + // Title, Dest, Parent, Prev, Next + aLine.append( "/Title" ); + appendUnicodeTextStringEncrypt( rItem.m_aTitle, rItem.m_nObject, aLine ); + aLine.append( "\n" ); + // Dest is not required + if( rItem.m_nDestID >= 0 && rItem.m_nDestID < (sal_Int32)m_aDests.size() ) + { + aLine.append( "/Dest" ); + appendDest( rItem.m_nDestID, aLine ); + } + aLine.append( "/Parent " ); + aLine.append( rItem.m_nParentObject ); + aLine.append( " 0 R" ); + if( rItem.m_nPrevObject ) + { + aLine.append( "/Prev " ); + aLine.append( rItem.m_nPrevObject ); + aLine.append( " 0 R" ); + } + if( rItem.m_nNextObject ) + { + aLine.append( "/Next " ); + aLine.append( rItem.m_nNextObject ); + aLine.append( " 0 R" ); + } + } + aLine.append( ">>\nendobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + } + + return m_aOutline[0].m_nObject; +} + +#undef CHECK_RETURN +#define CHECK_RETURN( x ) if( !x ) return false + +bool PDFWriterImpl::appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer ) +{ + if( nDestID < 0 || nDestID >= (sal_Int32)m_aDests.size() ) + { +#if OSL_DEBUG_LEVEL > 1 + fprintf( stderr, "ERROR: invalid dest %d requested\n", (int)nDestID ); +#endif + return false; + } + + + const PDFDest& rDest = m_aDests[ nDestID ]; + const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ]; + + rBuffer.append( '[' ); + rBuffer.append( rDestPage.m_nPageObject ); + rBuffer.append( " 0 R" ); + + switch( rDest.m_eType ) + { + case PDFWriter::XYZ: + default: + rBuffer.append( "/XYZ " ); + appendFixedInt( rDest.m_aRect.Left(), rBuffer ); + rBuffer.append( ' ' ); + appendFixedInt( rDest.m_aRect.Bottom(), rBuffer ); + rBuffer.append( " 0" ); + break; + case PDFWriter::Fit: + rBuffer.append( "/Fit" ); + break; + case PDFWriter::FitRectangle: + rBuffer.append( "/FitR " ); + appendFixedInt( rDest.m_aRect.Left(), rBuffer ); + rBuffer.append( ' ' ); + appendFixedInt( rDest.m_aRect.Top(), rBuffer ); + rBuffer.append( ' ' ); + appendFixedInt( rDest.m_aRect.Right(), rBuffer ); + rBuffer.append( ' ' ); + appendFixedInt( rDest.m_aRect.Bottom(), rBuffer ); + break; + case PDFWriter::FitHorizontal: + rBuffer.append( "/FitH " ); + appendFixedInt( rDest.m_aRect.Bottom(), rBuffer ); + break; + case PDFWriter::FitVertical: + rBuffer.append( "/FitV " ); + appendFixedInt( rDest.m_aRect.Left(), rBuffer ); + break; + case PDFWriter::FitPageBoundingBox: + rBuffer.append( "/FitB" ); + break; + case PDFWriter::FitPageBoundingBoxHorizontal: + rBuffer.append( "/FitBH " ); + appendFixedInt( rDest.m_aRect.Bottom(), rBuffer ); + break; + case PDFWriter::FitPageBoundingBoxVertical: + rBuffer.append( "/FitBV " ); + appendFixedInt( rDest.m_aRect.Left(), rBuffer ); + break; + } + rBuffer.append( ']' ); + + return true; +} + +bool PDFWriterImpl::emitLinkAnnotations() +{ + int nAnnots = m_aLinks.size(); + for( int i = 0; i < nAnnots; i++ ) + { + const PDFLink& rLink = m_aLinks[i]; + if( ! updateObject( rLink.m_nObject ) ) + continue; + + OStringBuffer aLine( 1024 ); + aLine.append( rLink.m_nObject ); + aLine.append( " 0 obj\n" ); +//i59651 key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should' +// see PDF 8.4.2 and ISO 19005-1:2005 6.5.3 + aLine.append( "<</Type/Annot" ); + if( m_bIsPDF_A1 ) + aLine.append( "/F 4" ); + aLine.append( "/Subtype/Link/Border[0 0 0]/Rect[" ); + + appendFixedInt( rLink.m_aRect.Left()-7, aLine );//the +7 to have a better shape of the border rectangle + aLine.append( ' ' ); + appendFixedInt( rLink.m_aRect.Top(), aLine ); + aLine.append( ' ' ); + appendFixedInt( rLink.m_aRect.Right()+7, aLine );//the +7 to have a better shape of the border rectangle + aLine.append( ' ' ); + appendFixedInt( rLink.m_aRect.Bottom(), aLine ); + aLine.append( "]" ); + if( rLink.m_nDest >= 0 ) + { + aLine.append( "/Dest" ); + appendDest( rLink.m_nDest, aLine ); + } + else + { +/*--->i56629 +destination is external to the document, so +we check in the following sequence: + + if target type is neither .pdf, nor .od[tpgs], then + check if relative or absolute and act accordingly (use URI or 'launch application' as requested) + end processing + else if target is .od[tpgs]: then + if conversion of type from od[tpgs] to pdf is requested, convert it and this becomes the new target file + processing continue + + if (new)target is .pdf : then + if GotToR is requested, then + convert the target in GoToR where the fragment of the URI is + considered the named destination in the target file, set relative or absolute as requested + else strip the fragment from URL and then set URI or 'launch application' as requested +*/ +// +// FIXME: check if the decode mechanisms for URL processing throughout this implementation +// are the correct one!! +// +// extract target file type + INetURLObject aDocumentURL( m_aContext.BaseURL ); + INetURLObject aTargetURL( rLink.m_aURL ); + sal_Int32 nChangeFileExtensionToPDF = 0; + sal_Int32 nSetGoToRMode = 0; + sal_Bool bTargetHasPDFExtension = sal_False; + INetProtocol eTargetProtocol = aTargetURL.GetProtocol(); + sal_Bool bIsUNCPath = sal_False; +// check if the protocol is a known one, or if there is no protocol at all (on target only) +// if there is no protocol, make the target relative to the current document directory +// getting the needed URL information from the current document path + if( eTargetProtocol == INET_PROT_NOT_VALID ) + { + if( rLink.m_aURL.getLength() > 4 && rLink.m_aURL.compareToAscii( "\\\\\\\\", 4 ) == 0) + { + bIsUNCPath = sal_True; + } + else + { + INetURLObject aNewBase( aDocumentURL );//duplicate document URL + aNewBase.removeSegment(); //remove last segment from it, obtaining the base URL of the + //target document + aNewBase.insertName( rLink.m_aURL ); + aTargetURL = aNewBase;//reassign the new target URL +//recompute the target protocol, with the new URL +//normal URL processing resumes + eTargetProtocol = aTargetURL.GetProtocol(); + } + } + + rtl::OUString aFileExtension = aTargetURL.GetFileExtension(); + +// Check if the URL ends in '/': if yes it's a directory, +// it will be forced to a URI link. +// possibly a malformed URI, leave it as it is, force as URI + if( aTargetURL.hasFinalSlash() ) + m_aContext.DefaultLinkAction = PDFWriter::URIAction; + + if( aFileExtension.getLength() > 0 ) + { + if( m_aContext.ConvertOOoTargetToPDFTarget ) + { +//examine the file type (.odm .odt. .odp, odg, ods) + if( aFileExtension.equalsIgnoreAsciiCase(rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "odm" ) ) ) ) + nChangeFileExtensionToPDF++; + if( aFileExtension.equalsIgnoreAsciiCase(rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "odt" ) ) ) ) + nChangeFileExtensionToPDF++; + else if( aFileExtension.equalsIgnoreAsciiCase(rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "odp" ) ) ) ) + nChangeFileExtensionToPDF++; + else if( aFileExtension.equalsIgnoreAsciiCase(rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "odg" ) ) ) ) + nChangeFileExtensionToPDF++; + else if( aFileExtension.equalsIgnoreAsciiCase(rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "ods" ) ) ) ) + nChangeFileExtensionToPDF++; + if( nChangeFileExtensionToPDF ) + aTargetURL.setExtension(rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "pdf" ) ) ); + } +//check if extension is pdf, see if GoToR should be forced + bTargetHasPDFExtension = aTargetURL.GetFileExtension().equalsIgnoreAsciiCase(rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "pdf" ) ) ); + if( m_aContext.ForcePDFAction && bTargetHasPDFExtension ) + nSetGoToRMode++; + } +//prepare the URL, if relative or not + INetProtocol eBaseProtocol = aDocumentURL.GetProtocol(); +//queue the string common to all types of actions + aLine.append( "/A<</Type/Action/S"); + if( bIsUNCPath ) // handle Win UNC paths + { + aLine.append( "/Launch/Win<</F" ); + // INetURLObject is not good with UNC paths, use original path + appendLiteralStringEncrypt( rLink.m_aURL, rLink.m_nObject, aLine ); + aLine.append( ">>" ); + } + else + { + sal_Int32 nSetRelative = 0; +//check if relative file link is requested and if the protocol is 'file://' + if( m_aContext.RelFsys && eBaseProtocol == eTargetProtocol && eTargetProtocol == INET_PROT_FILE ) + nSetRelative++; + + rtl::OUString aFragment = aTargetURL.GetMark( INetURLObject::NO_DECODE /*DECODE_WITH_CHARSET*/ ); //fragment as is, + if( nSetGoToRMode == 0 ) + switch( m_aContext.DefaultLinkAction ) + { + default: + case PDFWriter::URIAction : + case PDFWriter::URIActionDestination : + aLine.append( "/URI/URI" ); + break; + case PDFWriter::LaunchAction: +// now: +// if a launch action is requested and the hyperlink target has a fragment +// and the target file does not have a pdf extension, or it's not a 'file:://' protocol +// then force the uri action on it +// This code will permit the correct opening of application on web pages, the one that +// normally have fragments (but I may be wrong...) +// and will force the use of URI when the protocol is not file:// + if( (aFragment.getLength() > 0 && !bTargetHasPDFExtension) || + eTargetProtocol != INET_PROT_FILE ) + aLine.append( "/URI/URI" ); + else + aLine.append( "/Launch/F" ); + break; + } +//fragment are encoded in the same way as in the named destination processing + rtl::OUString aURLNoMark = aTargetURL.GetURLNoMark( INetURLObject::DECODE_WITH_CHARSET ); + if( nSetGoToRMode ) + {//add the fragment + aLine.append("/GoToR"); + aLine.append("/F"); + appendLiteralStringEncrypt( nSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURLNoMark, + INetURLObject::WAS_ENCODED, + INetURLObject::DECODE_WITH_CHARSET ) : + aURLNoMark, rLink.m_nObject, aLine ); + if( aFragment.getLength() > 0 ) + { + aLine.append("/D/"); + appendDestinationName( aFragment , aLine ); + } + } + else + { +// change the fragment to accomodate the bookmark (only if the file extension is PDF and +// the requested action is of the correct type) + if(m_aContext.DefaultLinkAction == PDFWriter::URIActionDestination && + bTargetHasPDFExtension && aFragment.getLength() > 0 ) + { + OStringBuffer aLineLoc( 1024 ); + appendDestinationName( aFragment , aLineLoc ); +//substitute the fragment + aTargetURL.SetMark( aLineLoc.getStr() ); + } + rtl::OUString aURL = aTargetURL.GetMainURL( (nSetRelative || eTargetProtocol == INET_PROT_FILE) ? INetURLObject::DECODE_WITH_CHARSET : INetURLObject::NO_DECODE ); +// check if we have a URL available, if the string is empty, set it as the original one +// if( aURL.getLength() == 0 ) +// appendLiteralStringEncrypt( rLink.m_aURL , rLink.m_nObject, aLine ); +// else + appendLiteralStringEncrypt( nSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURL ) : + aURL , rLink.m_nObject, aLine ); + } +//<--- i56629 + } + aLine.append( ">>\n" ); + } + if( rLink.m_nStructParent > 0 ) + { + aLine.append( "/StructParent " ); + aLine.append( rLink.m_nStructParent ); + } + aLine.append( ">>\nendobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + } + + return true; +} + +bool PDFWriterImpl::emitNoteAnnotations() +{ + // emit note annotations + int nAnnots = m_aNotes.size(); + for( int i = 0; i < nAnnots; i++ ) + { + const PDFNoteEntry& rNote = m_aNotes[i]; + if( ! updateObject( rNote.m_nObject ) ) + return false; + + OStringBuffer aLine( 1024 ); + aLine.append( rNote.m_nObject ); + aLine.append( " 0 obj\n" ); +//i59651 key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should' +// see PDF 8.4.2 and ISO 19005-1:2005 6.5.3 + aLine.append( "<</Type/Annot" ); + if( m_bIsPDF_A1 ) + aLine.append( "/F 4" ); + aLine.append( "/Subtype/Text/Rect[" ); + + appendFixedInt( rNote.m_aRect.Left(), aLine ); + aLine.append( ' ' ); + appendFixedInt( rNote.m_aRect.Top(), aLine ); + aLine.append( ' ' ); + appendFixedInt( rNote.m_aRect.Right(), aLine ); + aLine.append( ' ' ); + appendFixedInt( rNote.m_aRect.Bottom(), aLine ); + aLine.append( "]" ); + + // contents of the note (type text string) + aLine.append( "/Contents\n" ); + appendUnicodeTextStringEncrypt( rNote.m_aContents.Contents, rNote.m_nObject, aLine ); + aLine.append( "\n" ); + + // optional title + if( rNote.m_aContents.Title.Len() ) + { + aLine.append( "/T" ); + appendUnicodeTextStringEncrypt( rNote.m_aContents.Title, rNote.m_nObject, aLine ); + aLine.append( "\n" ); + } + + aLine.append( ">>\nendobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + } + return true; +} + +Font PDFWriterImpl::replaceFont( const Font& rControlFont, const Font& rAppSetFont ) +{ + bool bAdjustSize = false; + + Font aFont( rControlFont ); + if( ! aFont.GetName().Len() ) + { + aFont = rAppSetFont; + if( rControlFont.GetHeight() ) + aFont.SetSize( Size( 0, rControlFont.GetHeight() ) ); + else + bAdjustSize = true; + if( rControlFont.GetItalic() != ITALIC_DONTKNOW ) + aFont.SetItalic( rControlFont.GetItalic() ); + if( rControlFont.GetWeight() != WEIGHT_DONTKNOW ) + aFont.SetWeight( rControlFont.GetWeight() ); + } + 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; +} + +sal_Int32 PDFWriterImpl::getBestBuiltinFont( const Font& rFont ) +{ + sal_Int32 nBest = 4; // default to Helvetica + OUString aFontName( rFont.GetName() ); + aFontName = aFontName.toAsciiLowerCase(); + + if( aFontName.indexOf( OUString( RTL_CONSTASCII_USTRINGPARAM( "times" ) ) ) != -1 ) + nBest = 8; + else if( aFontName.indexOf( OUString( RTL_CONSTASCII_USTRINGPARAM( "courier" ) ) ) != -1 ) + nBest = 0; + else if( aFontName.indexOf( OUString( RTL_CONSTASCII_USTRINGPARAM( "dingbats" ) ) ) != -1 ) + nBest = 13; + else if( aFontName.indexOf( OUString( RTL_CONSTASCII_USTRINGPARAM( "symbol" ) ) ) != -1 ) + nBest = 12; + if( nBest < 12 ) + { + if( rFont.GetItalic() == ITALIC_OBLIQUE || rFont.GetItalic() == ITALIC_NORMAL ) + nBest += 1; + if( rFont.GetWeight() > WEIGHT_MEDIUM ) + nBest += 2; + } + + if( m_aBuiltinFontToObjectMap.find( nBest ) == m_aBuiltinFontToObjectMap.end() ) + m_aBuiltinFontToObjectMap[ nBest ] = createObject(); + + return nBest; +} + +static inline const Color& replaceColor( const Color& rCol1, const Color& rCol2 ) +{ + return (rCol1 == Color( COL_TRANSPARENT )) ? rCol2 : rCol1; +} + +void PDFWriterImpl::createDefaultPushButtonAppearance( PDFWidget& rButton, const PDFWriter::PushButtonWidget& rWidget ) +{ + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + + // save graphics state + push( sal::static_int_cast<sal_uInt16>(~0U) ); + + // transform relative to control's coordinates since an + // appearance stream is a form XObject + // this relies on the m_aRect member of rButton NOT already being transformed + // to default user space + if( rWidget.Background || rWidget.Border ) + { + setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetLightColor() ) : Color( COL_TRANSPARENT ) ); + setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetDialogColor() ) : Color( COL_TRANSPARENT ) ); + drawRectangle( rWidget.Location ); + } + // prepare font to use + Font aFont = replaceFont( rWidget.TextFont, rSettings.GetPushButtonFont() ); + setFont( aFont ); + setTextColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ) ); + + drawText( rButton.m_aRect, rButton.m_aText, rButton.m_nTextStyle ); + + // create DA string while local mapmode is still in place + // (that is before endRedirect()) + OStringBuffer aDA( 256 ); + appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetButtonTextColor() ), aDA ); + Font aDummyFont( String( RTL_CONSTASCII_USTRINGPARAM( "Helvetica" ) ), aFont.GetSize() ); + sal_Int32 nDummyBuiltin = getBestBuiltinFont( aDummyFont ); + aDA.append( ' ' ); + aDA.append( m_aBuiltinFonts[nDummyBuiltin].getNameObject() ); + aDA.append( ' ' ); + m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aFont.GetHeight() ), aDA ); + aDA.append( " Tf" ); + rButton.m_aDAString = aDA.makeStringAndClear(); + + pop(); + + rButton.m_aAppearances[ "N" ][ "Standard" ] = new SvMemoryStream(); + + /* seems like a bad hack but at least works in both AR5 and 6: + we draw the button ourselves and tell AR + the button would be totally transparent with no text + + One would expect that simply setting a normal appearance + should suffice, but no, as soon as the user actually presses + the button and an action is tied to it (gasp! a button that + does something) the appearance gets replaced by some crap that AR + creates on the fly even if no DA or MK is given. On AR6 at least + the DA and MK work as expected, but on AR5 this creates a region + filled with the background color but nor text. Urgh. + */ + rButton.m_aMKDict = "/BC [] /BG [] /CA"; + rButton.m_aMKDictCAString = ""; +} + +Font PDFWriterImpl::drawFieldBorder( PDFWidget& rIntern, + const PDFWriter::AnyWidget& rWidget, + const StyleSettings& rSettings ) +{ + Font aFont = replaceFont( rWidget.TextFont, rSettings.GetFieldFont() ); + + if( rWidget.Background || rWidget.Border ) + { + if( rWidget.Border && rWidget.BorderColor == Color( COL_TRANSPARENT ) ) + { + sal_Int32 nDelta = getReferenceDevice()->ImplGetDPIX() / 500; + if( nDelta < 1 ) + nDelta = 1; + setLineColor( Color( COL_TRANSPARENT ) ); + Rectangle aRect = rIntern.m_aRect; + setFillColor( rSettings.GetLightBorderColor() ); + drawRectangle( aRect ); + aRect.Left() += nDelta; aRect.Top() += nDelta; + aRect.Right() -= nDelta; aRect.Bottom() -= nDelta; + setFillColor( rSettings.GetFieldColor() ); + drawRectangle( aRect ); + setFillColor( rSettings.GetLightColor() ); + drawRectangle( Rectangle( Point( aRect.Left(), aRect.Bottom()-nDelta ), aRect.BottomRight() ) ); + drawRectangle( Rectangle( Point( aRect.Right()-nDelta, aRect.Top() ), aRect.BottomRight() ) ); + setFillColor( rSettings.GetDarkShadowColor() ); + drawRectangle( Rectangle( aRect.TopLeft(), Point( aRect.Left()+nDelta, aRect.Bottom() ) ) ); + drawRectangle( Rectangle( aRect.TopLeft(), Point( aRect.Right(), aRect.Top()+nDelta ) ) ); + } + else + { + setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetShadowColor() ) : Color( COL_TRANSPARENT ) ); + setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : Color( COL_TRANSPARENT ) ); + drawRectangle( rIntern.m_aRect ); + } + + if( rWidget.Border ) + { + // adjust edit area accounting for border + sal_Int32 nDelta = aFont.GetHeight()/4; + if( nDelta < 1 ) + nDelta = 1; + rIntern.m_aRect.Left() += nDelta; + rIntern.m_aRect.Top() += nDelta; + rIntern.m_aRect.Right() -= nDelta; + rIntern.m_aRect.Bottom()-= nDelta; + } + } + return aFont; +} + +void PDFWriterImpl::createDefaultEditAppearance( PDFWidget& rEdit, const PDFWriter::EditWidget& rWidget ) +{ + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + SvMemoryStream* pEditStream = new SvMemoryStream( 1024, 1024 ); + + push( sal::static_int_cast<sal_uInt16>(~0U) ); + + // prepare font to use, draw field border + Font aFont = drawFieldBorder( rEdit, rWidget, rSettings ); + sal_Int32 nBest = m_aContext.FieldsUseSystemFonts ? getSystemFont( aFont ): getBestBuiltinFont( aFont ); + + // prepare DA string + OStringBuffer aDA( 32 ); + appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA ); + aDA.append( ' ' ); + if( m_aContext.FieldsUseSystemFonts ) + { + aDA.append( "/F" ); + aDA.append( nBest ); + + OStringBuffer aDR( 32 ); + aDR.append( "/Font " ); + aDR.append( getFontDictObject() ); + aDR.append( " 0 R" ); + rEdit.m_aDRDict = aDR.makeStringAndClear(); + } + else + aDA.append( m_aBuiltinFonts[nBest].getNameObject() ); + aDA.append( ' ' ); + m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetHeight() ), aDA ); + aDA.append( " Tf" ); + + /* create an empty appearance stream, let the viewer create + the appearance at runtime. This is because AR5 seems to + paint the widget appearance always, and a dynamically created + appearance on top of it. AR6 is well behaved in that regard, so + that behaviour seems to be a bug. Anyway this empty appearance + relies on /NeedAppearances in the AcroForm dictionary set to "true" + */ + beginRedirect( pEditStream, rEdit.m_aRect ); + OStringBuffer aAppearance( 32 ); + aAppearance.append( "/Tx BMC\nEMC\n" ); + writeBuffer( aAppearance.getStr(), aAppearance.getLength() ); + + endRedirect(); + pop(); + + rEdit.m_aAppearances[ "N" ][ "Standard" ] = pEditStream; + + rEdit.m_aDAString = aDA.makeStringAndClear(); +} + +void PDFWriterImpl::createDefaultListBoxAppearance( PDFWidget& rBox, const PDFWriter::ListBoxWidget& rWidget ) +{ + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + SvMemoryStream* pListBoxStream = new SvMemoryStream( 1024, 1024 ); + + push( sal::static_int_cast<sal_uInt16>(~0U) ); + + // prepare font to use, draw field border + Font aFont = drawFieldBorder( rBox, rWidget, rSettings ); + sal_Int32 nBest = m_aContext.FieldsUseSystemFonts ? getSystemFont( aFont ): getBestBuiltinFont( aFont ); + + beginRedirect( pListBoxStream, rBox.m_aRect ); + OStringBuffer aAppearance( 64 ); + +#if 0 + if( ! rWidget.DropDown ) + { + // prepare linewidth for DA string hack, see below + Size aFontSize = lcl_convert( m_aGraphicsStack.front().m_aMapMode, + m_aMapMode, + getReferenceDevice(), + Size( 0, aFont.GetHeight() ) ); + sal_Int32 nLW = aFontSize.Height() / 40; + appendFixedInt( nLW > 0 ? nLW : 1, aAppearance ); + aAppearance.append( " w\n" ); + writeBuffer( aAppearance.getStr(), aAppearance.getLength() ); + aAppearance.setLength( 0 ); + } +#endif + + setLineColor( Color( COL_TRANSPARENT ) ); + setFillColor( replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) ); + drawRectangle( rBox.m_aRect ); + + // empty appearance, see createDefaultEditAppearance for reference + aAppearance.append( "/Tx BMC\nEMC\n" ); + writeBuffer( aAppearance.getStr(), aAppearance.getLength() ); + + endRedirect(); + pop(); + + rBox.m_aAppearances[ "N" ][ "Standard" ] = pListBoxStream; + + // prepare DA string + OStringBuffer aDA( 256 ); +#if 0 + if( !rWidget.DropDown ) + { + /* another of AR5's peculiarities: the selected item of a choice + field is highlighted using the non stroking color - same as the + text color. so workaround that by using text rendering mode 2 + (fill, then stroke) and set the stroking color + */ + appendStrokingColor( replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ), aDA ); + aDA.append( " 2 Tr " ); + } +#endif + // prepare DA string + appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetFieldTextColor() ), aDA ); + aDA.append( ' ' ); + if( m_aContext.FieldsUseSystemFonts ) + { + aDA.append( "/F" ); + aDA.append( nBest ); + + OStringBuffer aDR( 32 ); + aDR.append( "/Font " ); + aDR.append( getFontDictObject() ); + aDR.append( " 0 R" ); + rBox.m_aDRDict = aDR.makeStringAndClear(); + } + else + aDA.append( m_aBuiltinFonts[nBest].getNameObject() ); + aDA.append( ' ' ); + m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aFont.GetHeight() ), aDA ); + aDA.append( " Tf" ); + rBox.m_aDAString = aDA.makeStringAndClear(); +} + +void PDFWriterImpl::createDefaultCheckBoxAppearance( PDFWidget& rBox, const PDFWriter::CheckBoxWidget& rWidget ) +{ + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + + // save graphics state + push( sal::static_int_cast<sal_uInt16>(~0U) ); + + if( rWidget.Background || rWidget.Border ) + { + setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : Color( COL_TRANSPARENT ) ); + setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : Color( COL_TRANSPARENT ) ); + drawRectangle( rBox.m_aRect ); + } + + Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() ); + setFont( aFont ); + Size aFontSize = aFont.GetSize(); + if( aFontSize.Height() > rBox.m_aRect.GetHeight() ) + aFontSize.Height() = rBox.m_aRect.GetHeight(); + 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(); + + // #i74206# handle small controls without text area + while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta ) + { + aCheckRect.Right() -= nDelta; + aCheckRect.Top() += nDelta/2; + aCheckRect.Bottom() -= nDelta - (nDelta/2); + } + + 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(); + + // #i74206# handle small controls without text area + while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta ) + { + aCheckRect.Left() += nDelta; + aCheckRect.Top() += nDelta/2; + aCheckRect.Bottom() -= nDelta - (nDelta/2); + } + + aTextRect.Left() = rBox.m_aRect.Left(); + aTextRect.Top() = rBox.m_aRect.Top(); + aTextRect.Right() = aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta; + aTextRect.Bottom() = rBox.m_aRect.Bottom(); + } + setLineColor( Color( COL_BLACK ) ); + setFillColor( Color( COL_TRANSPARENT ) ); + OStringBuffer aLW( 32 ); + aLW.append( "q " ); + m_aPages[m_nCurrentPage].appendMappedLength( nDelta, aLW ); + aLW.append( " w " ); + writeBuffer( aLW.getStr(), aLW.getLength() ); + drawRectangle( aCheckRect ); + writeBuffer( " Q\n", 3 ); + setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) ); + drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle ); + + pop(); + + OStringBuffer aDA( 256 ); + appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA ); + sal_Int32 nBest = getBestBuiltinFont( Font( String( RTL_CONSTASCII_USTRINGPARAM( "ZapfDingbats" ) ), aFont.GetSize() ) ); + aDA.append( ' ' ); + aDA.append( m_aBuiltinFonts[nBest].getNameObject() ); + aDA.append( " 0 Tf" ); + rBox.m_aDAString = aDA.makeStringAndClear(); + rBox.m_aMKDict = "/CA"; + rBox.m_aMKDictCAString = "8"; + rBox.m_aRect = aCheckRect; + + // create appearance streams + sal_Char cMark = '8'; + sal_Int32 nCharXOffset = 1000-m_aBuiltinFonts[13].m_aWidths[sal_Int32(cMark)]; + nCharXOffset *= aCheckRect.GetHeight(); + nCharXOffset /= 2000; + sal_Int32 nCharYOffset = 1000- + (m_aBuiltinFonts[13].m_nAscent+m_aBuiltinFonts[13].m_nDescent); // descent is negative + nCharYOffset *= aCheckRect.GetHeight(); + nCharYOffset /= 2000; + + SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 ); + beginRedirect( pCheckStream, aCheckRect ); + aDA.append( "/Tx BMC\nq BT\n" ); + appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA ); + aDA.append( ' ' ); + aDA.append( m_aBuiltinFonts[nBest].getNameObject() ); + aDA.append( ' ' ); + m_aPages[ m_nCurrentPage ].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA ); + aDA.append( " Tf\n" ); + m_aPages[ m_nCurrentPage ].appendMappedLength( nCharXOffset, aDA ); + aDA.append( " " ); + m_aPages[ m_nCurrentPage ].appendMappedLength( nCharYOffset, aDA ); + aDA.append( " Td (" ); + aDA.append( cMark ); + aDA.append( ") Tj\nET\nQ\nEMC\n" ); + writeBuffer( aDA.getStr(), aDA.getLength() ); + endRedirect(); + rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream; + + SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 ); + beginRedirect( pUncheckStream, aCheckRect ); + writeBuffer( "/Tx BMC\nEMC\n", 12 ); + endRedirect(); + rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream; +} + +void PDFWriterImpl::createDefaultRadioButtonAppearance( PDFWidget& rBox, const PDFWriter::RadioButtonWidget& rWidget ) +{ + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + + // save graphics state + push( sal::static_int_cast<sal_uInt16>(~0U) ); + + if( rWidget.Background || rWidget.Border ) + { + setLineColor( rWidget.Border ? replaceColor( rWidget.BorderColor, rSettings.GetCheckedColor() ) : Color( COL_TRANSPARENT ) ); + setFillColor( rWidget.Background ? replaceColor( rWidget.BackgroundColor, rSettings.GetFieldColor() ) : Color( COL_TRANSPARENT ) ); + drawRectangle( rBox.m_aRect ); + } + + Font aFont = replaceFont( rWidget.TextFont, rSettings.GetRadioCheckFont() ); + setFont( aFont ); + Size aFontSize = aFont.GetSize(); + if( aFontSize.Height() > rBox.m_aRect.GetHeight() ) + aFontSize.Height() = rBox.m_aRect.GetHeight(); + 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(); + + // #i74206# handle small controls without text area + while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta ) + { + aCheckRect.Right() -= nDelta; + aCheckRect.Top() += nDelta/2; + aCheckRect.Bottom() -= nDelta - (nDelta/2); + } + + 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(); + + // #i74206# handle small controls without text area + while( aCheckRect.GetWidth() > rBox.m_aRect.GetWidth() && aCheckRect.GetWidth() > nDelta ) + { + aCheckRect.Left() += nDelta; + aCheckRect.Top() += nDelta/2; + aCheckRect.Bottom() -= nDelta - (nDelta/2); + } + + aTextRect.Left() = rBox.m_aRect.Left(); + aTextRect.Top() = rBox.m_aRect.Top(); + aTextRect.Right() = aTextRect.Left() + rBox.m_aRect.GetWidth() - aCheckRect.GetWidth()-6*nDelta; + aTextRect.Bottom() = rBox.m_aRect.Bottom(); + } + setLineColor( Color( COL_BLACK ) ); + setFillColor( Color( COL_TRANSPARENT ) ); + OStringBuffer aLW( 32 ); + aLW.append( "q " ); + m_aPages[ m_nCurrentPage ].appendMappedLength( nDelta, aLW ); + aLW.append( " w " ); + writeBuffer( aLW.getStr(), aLW.getLength() ); + drawEllipse( aCheckRect ); + writeBuffer( " Q\n", 3 ); + setTextColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) ); + drawText( aTextRect, rBox.m_aText, rBox.m_nTextStyle ); + + pop(); + + OStringBuffer aDA( 256 ); + appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA ); + sal_Int32 nBest = getBestBuiltinFont( Font( String( RTL_CONSTASCII_USTRINGPARAM( "ZapfDingbats" ) ), aFont.GetSize() ) ); + aDA.append( ' ' ); + aDA.append( m_aBuiltinFonts[nBest].getNameObject() ); + aDA.append( " 0 Tf" ); + rBox.m_aDAString = aDA.makeStringAndClear(); +//to encrypt this (el) + rBox.m_aMKDict = "/CA"; +//after this assignement, to m_aMKDic cannot be added anything + rBox.m_aMKDictCAString = "l"; + + rBox.m_aRect = aCheckRect; + + // create appearance streams + push( sal::static_int_cast<sal_uInt16>(~0U) ); + SvMemoryStream* pCheckStream = new SvMemoryStream( 256, 256 ); + + beginRedirect( pCheckStream, aCheckRect ); + aDA.append( "/Tx BMC\nq BT\n" ); + appendNonStrokingColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ), aDA ); + aDA.append( ' ' ); + aDA.append( m_aBuiltinFonts[nBest].getNameObject() ); + aDA.append( ' ' ); + m_aPages[m_nCurrentPage].appendMappedLength( sal_Int32( aCheckRect.GetHeight() ), aDA ); + aDA.append( " Tf\n0 0 Td\nET\nQ\n" ); + writeBuffer( aDA.getStr(), aDA.getLength() ); + setFillColor( replaceColor( rWidget.TextColor, rSettings.GetRadioCheckTextColor() ) ); + setLineColor( Color( COL_TRANSPARENT ) ); + aCheckRect.Left() += 3*nDelta; + aCheckRect.Top() += 3*nDelta; + aCheckRect.Bottom() -= 3*nDelta; + aCheckRect.Right() -= 3*nDelta; + drawEllipse( aCheckRect ); + writeBuffer( "\nEMC\n", 5 ); + endRedirect(); + + pop(); + rBox.m_aAppearances[ "N" ][ "Yes" ] = pCheckStream; + + SvMemoryStream* pUncheckStream = new SvMemoryStream( 256, 256 ); + beginRedirect( pUncheckStream, aCheckRect ); + writeBuffer( "/Tx BMC\nEMC\n", 12 ); + endRedirect(); + rBox.m_aAppearances[ "N" ][ "Off" ] = pUncheckStream; +} + +bool PDFWriterImpl::emitAppearances( PDFWidget& rWidget, OStringBuffer& rAnnotDict ) +{ + + // TODO: check and insert default streams + rtl::OString aStandardAppearance; + switch( rWidget.m_eType ) + { + case PDFWriter::CheckBox: + aStandardAppearance = OUStringToOString( rWidget.m_aValue, RTL_TEXTENCODING_ASCII_US ); + break; + default: + break; + } + + if( rWidget.m_aAppearances.size() ) + { + rAnnotDict.append( "/AP<<\n" ); + for( PDFAppearanceMap::iterator dict_it = rWidget.m_aAppearances.begin(); dict_it != rWidget.m_aAppearances.end(); ++dict_it ) + { + rAnnotDict.append( "/" ); + rAnnotDict.append( dict_it->first ); + bool bUseSubDict = (dict_it->second.size() > 1); + rAnnotDict.append( bUseSubDict ? "<<" : " " ); + + for( PDFAppearanceStreams::const_iterator stream_it = dict_it->second.begin(); + stream_it != dict_it->second.end(); ++stream_it ) + { + SvMemoryStream* pApppearanceStream = stream_it->second; + dict_it->second[ stream_it->first ] = NULL; + + bool bDeflate = compressStream( pApppearanceStream ); + + pApppearanceStream->Seek( STREAM_SEEK_TO_END ); + sal_Int64 nStreamLen = pApppearanceStream->Tell(); + pApppearanceStream->Seek( STREAM_SEEK_TO_BEGIN ); + sal_Int32 nObject = createObject(); + CHECK_RETURN( updateObject( nObject ) ); +#if OSL_DEBUG_LEVEL > 1 + { + OStringBuffer aLine( " PDFWriterImpl::emitAppearances" ); + emitComment( aLine.getStr() ); + } +#endif + OStringBuffer aLine; + aLine.append( nObject ); + + aLine.append( " 0 obj\n" + "<</Type/XObject\n" + "/Subtype/Form\n" + "/BBox[0 0 " ); + appendFixedInt( rWidget.m_aRect.GetWidth()-1, aLine ); + aLine.append( " " ); + appendFixedInt( rWidget.m_aRect.GetHeight()-1, aLine ); + aLine.append( "]\n" + "/Resources " ); + aLine.append( getResourceDictObj() ); + aLine.append( " 0 R\n" + "/Length " ); + aLine.append( nStreamLen ); + aLine.append( "\n" ); + if( bDeflate ) + aLine.append( "/Filter/FlateDecode\n" ); + aLine.append( ">>\nstream\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + checkAndEnableStreamEncryption( nObject ); + CHECK_RETURN( writeBuffer( pApppearanceStream->GetData(), nStreamLen ) ); + disableStreamEncryption(); + CHECK_RETURN( writeBuffer( "\nendstream\nendobj\n\n", 19 ) ); + + if( bUseSubDict ) + { + rAnnotDict.append( " /" ); + rAnnotDict.append( stream_it->first ); + rAnnotDict.append( " " ); + } + rAnnotDict.append( nObject ); + rAnnotDict.append( " 0 R" ); + + delete pApppearanceStream; + } + + rAnnotDict.append( bUseSubDict ? ">>\n" : "\n" ); + } + rAnnotDict.append( ">>\n" ); + if( aStandardAppearance.getLength() ) + { + rAnnotDict.append( "/AS /" ); + rAnnotDict.append( aStandardAppearance ); + rAnnotDict.append( "\n" ); + } + } + + return true; +} + +bool PDFWriterImpl::emitWidgetAnnotations() +{ + ensureUniqueRadioOnValues(); + + int nAnnots = m_aWidgets.size(); + for( int a = 0; a < nAnnots; a++ ) + { + PDFWidget& rWidget = m_aWidgets[a]; + + OStringBuffer aLine( 1024 ); + OStringBuffer aValue( 256 ); + aLine.append( rWidget.m_nObject ); + aLine.append( " 0 obj\n" + "<<" ); + if( rWidget.m_eType != PDFWriter::Hierarchy ) + { + // emit widget annotation only for terminal fields + if( rWidget.m_aKids.empty() ) + { + aLine.append( "/Type/Annot/Subtype/Widget/F 4\n" + "/Rect[" ); + appendFixedInt( rWidget.m_aRect.Left()-1, aLine ); + aLine.append( ' ' ); + appendFixedInt( rWidget.m_aRect.Top()+1, aLine ); + aLine.append( ' ' ); + appendFixedInt( rWidget.m_aRect.Right()+1, aLine ); + aLine.append( ' ' ); + appendFixedInt( rWidget.m_aRect.Bottom()-1, aLine ); + aLine.append( "]\n" ); + } + aLine.append( "/FT/" ); + switch( rWidget.m_eType ) + { + case PDFWriter::RadioButton: + case PDFWriter::CheckBox: + // for radio buttons only the RadioButton field, not the + // CheckBox children should have a value, else acrobat reader + // does not always check the right button + // of course real check boxes (not belonging to a readio group) + // need their values, too + if( rWidget.m_eType == PDFWriter::RadioButton || rWidget.m_nRadioGroup < 0 ) + { + aValue.append( "/" ); + // check for radio group with all buttons unpressed + if( rWidget.m_aValue.getLength() == 0 ) + aValue.append( "Off" ); + else + appendName( rWidget.m_aValue, aValue ); + } + case PDFWriter::PushButton: + aLine.append( "Btn" ); + break; + case PDFWriter::ListBox: + if( rWidget.m_nFlags & 0x200000 ) // multiselect + { + aValue.append( "[" ); + for( unsigned int i = 0; i < rWidget.m_aSelectedEntries.size(); i++ ) + { + sal_Int32 nEntry = rWidget.m_aSelectedEntries[i]; + if( nEntry >= 0 && nEntry < sal_Int32(rWidget.m_aListEntries.size()) ) + appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ nEntry ], rWidget.m_nObject, aValue ); + } + aValue.append( "]" ); + } + else if( rWidget.m_aSelectedEntries.size() > 0 && + rWidget.m_aSelectedEntries[0] >= 0 && + rWidget.m_aSelectedEntries[0] < sal_Int32(rWidget.m_aListEntries.size()) ) + { + appendUnicodeTextStringEncrypt( rWidget.m_aListEntries[ rWidget.m_aSelectedEntries[0] ], rWidget.m_nObject, aValue ); + } + else + appendUnicodeTextStringEncrypt( rtl::OUString(), rWidget.m_nObject, aValue ); + aLine.append( "Ch" ); + break; + case PDFWriter::ComboBox: + appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue ); + aLine.append( "Ch" ); + break; + case PDFWriter::Edit: + aLine.append( "Tx" ); + appendUnicodeTextStringEncrypt( rWidget.m_aValue, rWidget.m_nObject, aValue ); + break; + case PDFWriter::Hierarchy: // make the compiler happy + break; + } + aLine.append( "\n" ); + aLine.append( "/P " ); + aLine.append( m_aPages[ rWidget.m_nPage ].m_nPageObject ); + aLine.append( " 0 R\n" ); + } + if( rWidget.m_nParent ) + { + aLine.append( "/Parent " ); + aLine.append( rWidget.m_nParent ); + aLine.append( " 0 R\n" ); + } + if( rWidget.m_aKids.size() ) + { + aLine.append( "/Kids[" ); + for( unsigned int i = 0; i < rWidget.m_aKids.size(); i++ ) + { + aLine.append( rWidget.m_aKids[i] ); + aLine.append( " 0 R" ); + aLine.append( ( (i&15) == 15 ) ? "\n" : " " ); + } + aLine.append( "]\n" ); + } + if( rWidget.m_aName.getLength() ) + { + aLine.append( "/T" ); + appendLiteralStringEncrypt( rWidget.m_aName, rWidget.m_nObject, aLine ); + aLine.append( "\n" ); + } + if( m_aContext.Version > PDFWriter::PDF_1_2 && rWidget.m_aDescription.getLength() ) + { + // the alternate field name should be unicode able since it is + // supposed to be used in UI + aLine.append( "/TU" ); + appendUnicodeTextStringEncrypt( rWidget.m_aDescription, rWidget.m_nObject, aLine ); + aLine.append( "\n" ); + } + + if( rWidget.m_nFlags ) + { + aLine.append( "/Ff " ); + aLine.append( rWidget.m_nFlags ); + aLine.append( "\n" ); + } + if( aValue.getLength() ) + { + OString aVal = aValue.makeStringAndClear(); + aLine.append( "/V " ); + aLine.append( aVal ); + aLine.append( "\n" + "/DV " ); + aLine.append( aVal ); + aLine.append( "\n" ); + } + if( rWidget.m_eType == PDFWriter::ListBox || rWidget.m_eType == PDFWriter::ComboBox ) + { + sal_Int32 nTI = -1; + aLine.append( "/Opt[\n" ); + sal_Int32 i = 0; + for( std::vector< OUString >::const_iterator it = rWidget.m_aListEntries.begin(); it != rWidget.m_aListEntries.end(); ++it, ++i ) + { + appendUnicodeTextStringEncrypt( *it, rWidget.m_nObject, aLine ); + aLine.append( "\n" ); + if( *it == rWidget.m_aValue ) + nTI = i; + } + aLine.append( "]\n" ); + if( nTI > 0 ) + { + aLine.append( "/TI " ); + aLine.append( nTI ); + aLine.append( "\n" ); + if( rWidget.m_nFlags & 0x200000 ) // Multiselect + { + aLine.append( "/I [" ); + aLine.append( nTI ); + aLine.append( "]\n" ); + } + } + } + if( rWidget.m_eType == PDFWriter::Edit && rWidget.m_nMaxLen > 0 ) + { + aLine.append( "/MaxLen " ); + aLine.append( rWidget.m_nMaxLen ); + aLine.append( "\n" ); + } + if( rWidget.m_eType == PDFWriter::PushButton ) + { + if(!m_bIsPDF_A1) + { + OStringBuffer aDest; + if( rWidget.m_nDest != -1 && appendDest( rWidget.m_nDest, aDest ) ) + { + aLine.append( "/AA<</D<</Type/Action/S/GoTo/D " ); + aLine.append( aDest.makeStringAndClear() ); + aLine.append( ">>>>\n" ); + } + else if( rWidget.m_aListEntries.empty() ) + { + // create a reset form action + aLine.append( "/AA<</D<</Type/Action/S/ResetForm>>>>\n" ); + } + else if( rWidget.m_bSubmit ) + { + // create a submit form action + aLine.append( "/AA<</D<</Type/Action/S/SubmitForm/F" ); + appendLiteralStringEncrypt( rWidget.m_aListEntries.front(), rWidget.m_nObject, aLine ); + aLine.append( "/Flags " ); + + sal_Int32 nFlags = 0; + switch( m_aContext.SubmitFormat ) + { + case PDFWriter::HTML: + nFlags |= 4; + break; + case PDFWriter::XML: + if( m_aContext.Version > PDFWriter::PDF_1_3 ) + nFlags |= 32; + break; + case PDFWriter::PDF: + if( m_aContext.Version > PDFWriter::PDF_1_3 ) + nFlags |= 256; + break; + case PDFWriter::FDF: + default: + break; + } + if( rWidget.m_bSubmitGet ) + nFlags |= 8; + aLine.append( nFlags ); + aLine.append( ">>>>\n" ); + } + else + { + // create a URI action + aLine.append( "/AA<</D<</Type/Action/S/URI/URI(" ); + aLine.append( OUStringToOString( rWidget.m_aListEntries.front(), RTL_TEXTENCODING_ASCII_US ) ); + aLine.append( ")>>>>\n" ); + } + } + else + m_aErrors.insert( PDFWriter::Warning_FormAction_Omitted_PDFA ); + } + if( rWidget.m_aDAString.getLength() ) + { + if( rWidget.m_aDRDict.getLength() ) + { + aLine.append( "/DR<<" ); + aLine.append( rWidget.m_aDRDict ); + aLine.append( ">>\n" ); + } + else + { + aLine.append( "/DR<</Font<<" ); + appendBuiltinFontsToDict( aLine ); + aLine.append( ">>>>\n" ); + } + aLine.append( "/DA" ); + appendLiteralStringEncrypt( rWidget.m_aDAString, rWidget.m_nObject, aLine ); + aLine.append( "\n" ); + if( rWidget.m_nTextStyle & TEXT_DRAW_CENTER ) + aLine.append( "/Q 1\n" ); + else if( rWidget.m_nTextStyle & TEXT_DRAW_RIGHT ) + aLine.append( "/Q 2\n" ); + } + // appearance charactristics for terminal fields + // which are supposed to have an appearance constructed + // by the viewer application + if( rWidget.m_aMKDict.getLength() ) + { + aLine.append( "/MK<<" ); + aLine.append( rWidget.m_aMKDict ); +//add the CA string, encrypting it + appendLiteralStringEncrypt(rWidget.m_aMKDictCAString, rWidget.m_nObject, aLine); + aLine.append( ">>\n" ); + } + + CHECK_RETURN( emitAppearances( rWidget, aLine ) ); + + aLine.append( ">>\n" + "endobj\n\n" ); + CHECK_RETURN( updateObject( rWidget.m_nObject ) ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + } + return true; +} + +bool PDFWriterImpl::emitAnnotations() +{ + if( m_aPages.size() < 1 ) + return false; + + CHECK_RETURN( emitLinkAnnotations() ); + + CHECK_RETURN( emitNoteAnnotations() ); + + CHECK_RETURN( emitWidgetAnnotations() ); + + return true; +} + +#undef CHECK_RETURN +#define CHECK_RETURN( x ) if( !x ) return false + +bool PDFWriterImpl::emitCatalog() +{ + // build page tree + // currently there is only one node that contains all leaves + + // first create a page tree node id + sal_Int32 nTreeNode = createObject(); + + // emit global resource dictionary (page emit needs it) + CHECK_RETURN( emitResources() ); + + // emit all pages + for( std::vector<PDFPage>::iterator it = m_aPages.begin(); it != m_aPages.end(); ++it ) + if( ! it->emit( nTreeNode ) ) + return false; + + sal_Int32 nNamedDestinationsDictionary = emitNamedDestinations(); + + sal_Int32 nOutlineDict = emitOutline(); + + //emit Output intent i59651 + sal_Int32 nOutputIntentObject = emitOutputIntent(); + + //emit metadata + sal_Int32 nMetadataObject = emitDocumentMetadata(); + + sal_Int32 nStructureDict = 0; + if(m_aStructure.size() > 1) + { +///check if dummy structure containers are needed + addInternalStructureContainer(m_aStructure[0]); + nStructureDict = m_aStructure[0].m_nObject = createObject(); + emitStructure( m_aStructure[ 0 ] ); + } + + // adjust tree node file offset + if( ! updateObject( nTreeNode ) ) + return false; + + // emit tree node + OStringBuffer aLine( 2048 ); + aLine.append( nTreeNode ); + aLine.append( " 0 obj\n" ); + aLine.append( "<</Type/Pages\n" ); + aLine.append( "/Resources " ); + aLine.append( getResourceDictObj() ); + aLine.append( " 0 R\n" ); + + switch( m_eInheritedOrientation ) + { + case PDFWriter::Landscape: aLine.append( "/Rotate 90\n" );break; + case PDFWriter::Seascape: aLine.append( "/Rotate -90\n" );break; + + case PDFWriter::Inherit: // actually Inherit would be a bug, but insignificant + case PDFWriter::Portrait: + default: + break; + } + sal_Int32 nMediaBoxWidth = 0; + sal_Int32 nMediaBoxHeight = 0; + if( m_aPages.empty() ) // sanity check, this should not happen + { + nMediaBoxWidth = m_nInheritedPageWidth; + nMediaBoxHeight = m_nInheritedPageHeight; + } + else + { + for( std::vector<PDFPage>::const_iterator iter = m_aPages.begin(); iter != m_aPages.end(); ++iter ) + { + if( iter->m_nPageWidth > nMediaBoxWidth ) + nMediaBoxWidth = iter->m_nPageWidth; + if( iter->m_nPageHeight > nMediaBoxHeight ) + nMediaBoxHeight = iter->m_nPageHeight; + } + } + aLine.append( "/MediaBox[ 0 0 " ); + aLine.append( nMediaBoxWidth ); + aLine.append( ' ' ); + aLine.append( nMediaBoxHeight ); + aLine.append( " ]\n" + "/Kids[ " ); + unsigned int i = 0; + for( std::vector<PDFPage>::const_iterator iter = m_aPages.begin(); iter != m_aPages.end(); ++iter, i++ ) + { + aLine.append( iter->m_nPageObject ); + aLine.append( " 0 R" ); + aLine.append( ( (i&15) == 15 ) ? "\n" : " " ); + } + aLine.append( "]\n" + "/Count " ); + aLine.append( (sal_Int32)m_aPages.size() ); + aLine.append( ">>\n" + "endobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + + // emit annotation objects + CHECK_RETURN( emitAnnotations() ); + + // emit Catalog + m_nCatalogObject = createObject(); + if( ! updateObject( m_nCatalogObject ) ) + return false; + aLine.setLength( 0 ); + aLine.append( m_nCatalogObject ); + aLine.append( " 0 obj\n" + "<</Type/Catalog/Pages " ); + aLine.append( nTreeNode ); + aLine.append( " 0 R\n" ); +//--->i56629 +//check if there are named destinations to emit (root must be inside the catalog) + if( nNamedDestinationsDictionary ) + { + aLine.append("/Dests "); + aLine.append( nNamedDestinationsDictionary ); + aLine.append( " 0 R\n" ); + } +//<---- + if( m_aContext.PageLayout != PDFWriter::DefaultLayout ) + switch( m_aContext.PageLayout ) + { + default : + case PDFWriter::SinglePage : + aLine.append( "/PageLayout/SinglePage\n" ); + break; + case PDFWriter::Continuous : + aLine.append( "/PageLayout/OneColumn\n" ); + break; + case PDFWriter::ContinuousFacing : +//the flag m_aContext.FirstPageLeft below is used to set the page on the left side + aLine.append( "/PageLayout/TwoColumnRight\n" );//odd page on the right side + break; + } + if( m_aContext.PDFDocumentMode != PDFWriter::ModeDefault && !m_aContext.OpenInFullScreenMode ) + switch( m_aContext.PDFDocumentMode ) + { + default : + aLine.append( "/PageMode/UseNone\n" ); + break; + case PDFWriter::UseOutlines : + aLine.append( "/PageMode/UseOutlines\n" ); //document is opened with outline pane open + break; + case PDFWriter::UseThumbs : + aLine.append( "/PageMode/UseThumbs\n" ); //document is opened with thumbnails pane open + break; + } + else if( m_aContext.OpenInFullScreenMode ) + aLine.append( "/PageMode/FullScreen\n" ); //document is opened full screen + + OStringBuffer aInitPageRef; + if( m_aContext.InitialPage >= 0 && m_aContext.InitialPage < (sal_Int32)m_aPages.size() ) + { + aInitPageRef.append( m_aPages[m_aContext.InitialPage].m_nPageObject ); + aInitPageRef.append( " 0 R" ); + } + else + aInitPageRef.append( "0" ); + switch( m_aContext.PDFDocumentAction ) + { + case PDFWriter::ActionDefault : //do nothing, this is the Acrobat default + default: + if( aInitPageRef.getLength() > 1 ) + { + aLine.append( "/OpenAction[" ); + aLine.append( aInitPageRef ); + aLine.append( " /XYZ null null 0]\n" ); + } + break; + case PDFWriter::FitInWindow : + aLine.append( "/OpenAction[" ); + aLine.append( aInitPageRef ); + aLine.append( " /Fit]\n" ); //Open fit page + break; + case PDFWriter::FitWidth : + aLine.append( "/OpenAction[" ); + aLine.append( aInitPageRef ); + aLine.append( " /FitH " ); + aLine.append( m_nInheritedPageHeight );//Open fit width + aLine.append( "]\n" ); + break; + case PDFWriter::FitVisible : + aLine.append( "/OpenAction[" ); + aLine.append( aInitPageRef ); + aLine.append( " /FitBH " ); + aLine.append( m_nInheritedPageHeight );//Open fit visible + aLine.append( "]\n" ); + break; + case PDFWriter::ActionZoom : + aLine.append( "/OpenAction[" ); + aLine.append( aInitPageRef ); + aLine.append( " /XYZ null null " ); + if( m_aContext.Zoom >= 50 && m_aContext.Zoom <= 1600 ) + aLine.append( (double)m_aContext.Zoom/100.0 ); + else + aLine.append( "0" ); + aLine.append( "]\n" ); + break; + } +// viewer preferences, if we had some, then emit + if( m_aContext.HideViewerToolbar || + ( m_aContext.Version > PDFWriter::PDF_1_3 && m_aDocInfo.Title.Len() && m_aContext.DisplayPDFDocumentTitle ) || + m_aContext.HideViewerMenubar || + m_aContext.HideViewerWindowControls || m_aContext.FitWindow || + m_aContext.CenterWindow || (m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing ) || + m_aContext.OpenInFullScreenMode ) + { + aLine.append( "/ViewerPreferences<<" ); + if( m_aContext.HideViewerToolbar ) + aLine.append( "/HideToolbar true\n" ); + if( m_aContext.HideViewerMenubar ) + aLine.append( "/HideMenubar true\n" ); + if( m_aContext.HideViewerWindowControls ) + aLine.append( "/HideWindowUI true\n" ); + if( m_aContext.FitWindow ) + aLine.append( "/FitWindow true\n" ); + if( m_aContext.CenterWindow ) + aLine.append( "/CenterWindow true\n" ); + if( m_aContext.Version > PDFWriter::PDF_1_3 && m_aDocInfo.Title.Len() && m_aContext.DisplayPDFDocumentTitle ) + aLine.append( "/DisplayDocTitle true\n" ); + if( m_aContext.FirstPageLeft && m_aContext.PageLayout == PDFWriter::ContinuousFacing ) + aLine.append( "/Direction/R2L\n" ); + if( m_aContext.OpenInFullScreenMode ) + switch( m_aContext.PDFDocumentMode ) + { + default : + case PDFWriter::ModeDefault : + aLine.append( "/NonFullScreenPageMode/UseNone\n" ); + break; + case PDFWriter::UseOutlines : + aLine.append( "/NonFullScreenPageMode/UseOutlines\n" ); + break; + case PDFWriter::UseThumbs : + aLine.append( "/NonFullScreenPageMode/UseThumbs\n" ); + break; + } + aLine.append( ">>\n" ); + } + + if( nOutlineDict ) + { + aLine.append( "/Outlines " ); + aLine.append( nOutlineDict ); + aLine.append( " 0 R\n" ); + } + if( nStructureDict ) + { + aLine.append( "/StructTreeRoot " ); + aLine.append( nStructureDict ); + aLine.append( " 0 R\n" ); + } + if( m_aContext.DocumentLocale.Language.getLength() > 0 ) + { + OUStringBuffer aLocBuf( 16 ); + aLocBuf.append( m_aContext.DocumentLocale.Language.toAsciiLowerCase() ); + if( m_aContext.DocumentLocale.Country.getLength() > 0 ) + { + aLocBuf.append( sal_Unicode('-') ); + aLocBuf.append( m_aContext.DocumentLocale.Country ); + } + aLine.append( "/Lang" ); + appendLiteralStringEncrypt( aLocBuf.makeStringAndClear(), m_nCatalogObject, aLine ); + aLine.append( "\n" ); + } + if( m_aContext.Tagged && m_aContext.Version > PDFWriter::PDF_1_3 ) + { + aLine.append( "/MarkInfo<</Marked true>>\n" ); + } + if( m_aWidgets.size() > 0 ) + { + aLine.append( "/AcroForm<</Fields[\n" ); + int nWidgets = m_aWidgets.size(); + int nOut = 0; + for( int j = 0; j < nWidgets; j++ ) + { + // output only root fields + if( m_aWidgets[j].m_nParent < 1 ) + { + aLine.append( m_aWidgets[j].m_nObject ); + aLine.append( (nOut++ % 5)==4 ? " 0 R\n" : " 0 R " ); + } + } + aLine.append( "\n]/DR " ); + aLine.append( getResourceDictObj() ); + aLine.append( " 0 R" ); + if( m_bIsPDF_A1 ) + aLine.append( ">>\n" ); + else + aLine.append( "/NeedAppearances true>>\n" ); + } +//--->i59651 +//check if there is a Metadata object + if( nOutputIntentObject ) + { + aLine.append("/OutputIntents["); + aLine.append( nOutputIntentObject ); + aLine.append( " 0 R]" ); + } + if( nMetadataObject ) + { + aLine.append("/Metadata "); + aLine.append( nMetadataObject ); + aLine.append( " 0 R" ); + } +//<---- + aLine.append( ">>\n" + "endobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + + return true; +} + +sal_Int32 PDFWriterImpl::emitInfoDict( ) +{ + sal_Int32 nObject = createObject(); + + if( updateObject( nObject ) ) + { + OStringBuffer aLine( 1024 ); + aLine.append( nObject ); + aLine.append( " 0 obj\n" + "<<" ); + if( m_aDocInfo.Title.Len() ) + { + aLine.append( "/Title" ); + appendUnicodeTextStringEncrypt( m_aDocInfo.Title, nObject, aLine ); + aLine.append( "\n" ); + } + if( m_aDocInfo.Author.Len() ) + { + aLine.append( "/Author" ); + appendUnicodeTextStringEncrypt( m_aDocInfo.Author, nObject, aLine ); + aLine.append( "\n" ); + } + if( m_aDocInfo.Subject.Len() ) + { + aLine.append( "/Subject" ); + appendUnicodeTextStringEncrypt( m_aDocInfo.Subject, nObject, aLine ); + aLine.append( "\n" ); + } + if( m_aDocInfo.Keywords.Len() ) + { + aLine.append( "/Keywords" ); + appendUnicodeTextStringEncrypt( m_aDocInfo.Keywords, nObject, aLine ); + aLine.append( "\n" ); + } + if( m_aDocInfo.Creator.Len() ) + { + aLine.append( "/Creator" ); + appendUnicodeTextStringEncrypt( m_aDocInfo.Creator, nObject, aLine ); + aLine.append( "\n" ); + } + if( m_aDocInfo.Producer.Len() ) + { + aLine.append( "/Producer" ); + appendUnicodeTextStringEncrypt( m_aDocInfo.Producer, nObject, aLine ); + aLine.append( "\n" ); + } + + aLine.append( "/CreationDate" ); + appendLiteralStringEncrypt( m_aCreationDateString, nObject, aLine ); + aLine.append( ">>\nendobj\n\n" ); + if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) ) + nObject = 0; + } + else + nObject = 0; + + return nObject; +} + +//--->i56629 +// Part of this function may be shared with method appendDest. +// +sal_Int32 PDFWriterImpl::emitNamedDestinations() +{ + sal_Int32 nCount = m_aNamedDests.size(); + if( nCount <= 0 ) + return 0;//define internal error + +//get the object number for all the destinations + sal_Int32 nObject = createObject(); + + if( updateObject( nObject ) ) + { +//emit the dictionary + OStringBuffer aLine( 1024 ); + aLine.append( nObject ); + aLine.append( " 0 obj\n" + "<<" ); + + sal_Int32 nDestID; + for( nDestID = 0; nDestID < nCount; nDestID++ ) + { + const PDFNamedDest& rDest = m_aNamedDests[ nDestID ]; +// In order to correctly function both under an Internet browser and +// directly with a reader (provided the reader has the feature) we +// need to set the name of the destination the same way it will be encoded +// in an Internet link + INetURLObject aLocalURL( + OUString( RTL_CONSTASCII_USTRINGPARAM( "http://ahost.ax" ) ) ); //dummy location, won't be used + aLocalURL.SetMark( rDest.m_aDestName ); + + const rtl::OUString aName = aLocalURL.GetMark( INetURLObject::NO_DECODE ); //same coding as + // in link creation ( see PDFWriterImpl::emitLinkAnnotations ) + const PDFPage& rDestPage = m_aPages[ rDest.m_nPage ]; + + aLine.append( '/' ); + appendDestinationName( aName, aLine ); // this conversion must be done when forming the link to target ( see in emitCatalog ) + aLine.append( '[' ); // the '[' can be emitted immediately, because the appendDestinationName function + //maps the preceeding character properly + aLine.append( rDestPage.m_nPageObject ); + aLine.append( " 0 R" ); + + switch( rDest.m_eType ) + { + case PDFWriter::XYZ: + default: + aLine.append( "/XYZ " ); + appendFixedInt( rDest.m_aRect.Left(), aLine ); + aLine.append( ' ' ); + appendFixedInt( rDest.m_aRect.Bottom(), aLine ); + aLine.append( " 0" ); + break; + case PDFWriter::Fit: + aLine.append( "/Fit" ); + break; + case PDFWriter::FitRectangle: + aLine.append( "/FitR " ); + appendFixedInt( rDest.m_aRect.Left(), aLine ); + aLine.append( ' ' ); + appendFixedInt( rDest.m_aRect.Top(), aLine ); + aLine.append( ' ' ); + appendFixedInt( rDest.m_aRect.Right(), aLine ); + aLine.append( ' ' ); + appendFixedInt( rDest.m_aRect.Bottom(), aLine ); + break; + case PDFWriter::FitHorizontal: + aLine.append( "/FitH " ); + appendFixedInt( rDest.m_aRect.Bottom(), aLine ); + break; + case PDFWriter::FitVertical: + aLine.append( "/FitV " ); + appendFixedInt( rDest.m_aRect.Left(), aLine ); + break; + case PDFWriter::FitPageBoundingBox: + aLine.append( "/FitB" ); + break; + case PDFWriter::FitPageBoundingBoxHorizontal: + aLine.append( "/FitBH " ); + appendFixedInt( rDest.m_aRect.Bottom(), aLine ); + break; + case PDFWriter::FitPageBoundingBoxVertical: + aLine.append( "/FitBV " ); + appendFixedInt( rDest.m_aRect.Left(), aLine ); + break; + } + aLine.append( "]\n" ); + } +//close + + aLine.append( ">>\nendobj\n\n" ); + if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) ) + nObject = 0; + } + else + nObject = 0; + + return nObject; +} +//<--- i56629 + +//--->i59651 +// emits the output intent dictionary + +sal_Int32 PDFWriterImpl::emitOutputIntent() +{ + if( !m_bIsPDF_A1 ) + return 0; + +//emit the sRGB standard profile, in ICC format, in a stream, per IEC61966-2.1 + + OStringBuffer aLine( 1024 ); + sal_Int32 nICCObject = createObject(); + sal_Int32 nStreamLengthObject = createObject(); + + aLine.append( nICCObject ); +// sRGB has 3 colors, hence /N 3 below (PDF 1.4 table 4.16) + aLine.append( " 0 obj\n<</N 3/Length " ); + aLine.append( nStreamLengthObject ); + aLine.append( " 0 R" ); +#ifndef DEBUG_DISABLE_PDFCOMPRESSION + aLine.append( "/Filter/FlateDecode" ); +#endif + aLine.append( ">>\nstream\n" ); + CHECK_RETURN( updateObject( nICCObject ) ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); +//get file position + sal_uInt64 nBeginStreamPos = 0; + osl_getFilePos( m_aFile, &nBeginStreamPos ); + beginCompression(); + checkAndEnableStreamEncryption( nICCObject ); + sal_Int32 nStreamSize = writeBuffer( nsRGB_ICC_profile, (sal_Int32) sizeof( nsRGB_ICC_profile ) ); + disableStreamEncryption(); + endCompression(); + sal_uInt64 nEndStreamPos = 0; + osl_getFilePos( m_aFile, &nEndStreamPos ); + + if( nStreamSize == 0 ) + return 0; + if( ! writeBuffer( "\nendstream\nendobj\n\n", 19 ) ) + return 0 ; + aLine.setLength( 0 ); + +//emit the stream length object + CHECK_RETURN( updateObject( nStreamLengthObject ) ); + aLine.setLength( 0 ); + aLine.append( nStreamLengthObject ); + aLine.append( " 0 obj\n" ); + aLine.append( (sal_Int64)(nEndStreamPos-nBeginStreamPos) ); + aLine.append( "\nendobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + aLine.setLength( 0 ); + +//emit the OutputIntent dictionary + sal_Int32 nOIObject = createObject(); + CHECK_RETURN( updateObject( nOIObject ) ); + aLine.append( nOIObject ); + aLine.append( " 0 obj\n" + "<</Type/OutputIntent/S/GTS_PDFA1/OutputConditionIdentifier"); + + rtl::OUString aComment( RTL_CONSTASCII_USTRINGPARAM( "sRGB IEC61966-2.1" ) ); + appendLiteralStringEncrypt( aComment ,nOIObject, aLine ); + aLine.append("/DestOutputProfile "); + aLine.append( nICCObject ); + aLine.append( " 0 R>>\nendobj\n\n" );; + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + + return nOIObject; +} + +// formats the string for the XML stream +static void escapeStringXML( const rtl::OUString& rStr, rtl::OUString &rValue) +{ + const sal_Unicode* pUni = rStr.getStr(); + int nLen = rStr.getLength(); + for( ; nLen; nLen--, pUni++ ) + { + switch( *pUni ) + { + case sal_Unicode('&'): + rValue += rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "&" ) ); + break; + case sal_Unicode('<'): + rValue += rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "<" ) ); + break; + case sal_Unicode('>'): + rValue += rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( ">" ) ); + break; + case sal_Unicode('\''): + rValue += rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "'" ) ); + break; + case sal_Unicode('"'): + rValue += rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( """ ) ); + break; + default: + rValue += rtl::OUString( *pUni ); + break; + } + } +} + +// emits the document metadata +// +sal_Int32 PDFWriterImpl::emitDocumentMetadata() +{ + if( !m_bIsPDF_A1 ) + return 0; + + //get the object number for all the destinations + sal_Int32 nObject = createObject(); + + if( updateObject( nObject ) ) + { +// the following string are written in UTF-8 unicode + OStringBuffer aMetadataStream( 8192 ); + + aMetadataStream.append( "<?xpacket begin=\"" ); +// this lines writes Unicode “zero width non-breaking space character” (U+FEFF) (aka byte-order mark ) used +// as a byte-order marker. + aMetadataStream.append( OUStringToOString( OUString( sal_Unicode( 0xFEFF ) ), RTL_TEXTENCODING_UTF8 ) ); + aMetadataStream.append( "\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n" ); + aMetadataStream.append( "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\">\n" ); + aMetadataStream.append( " <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n" ); +//PDF/A part ( ISO 19005-1:2005 - 6.7.11 ) + aMetadataStream.append( " <rdf:Description rdf:about=\"\"\n" ); + aMetadataStream.append( " xmlns:pdfaid=\"http://www.aiim.org/pdfa/ns/id/\">\n" ); + aMetadataStream.append( " <pdfaid:part>1</pdfaid:part>\n" ); + aMetadataStream.append( " <pdfaid:conformance>A</pdfaid:conformance>\n" ); + aMetadataStream.append( " </rdf:Description>\n" ); +//... Dublin Core properties go here + if( m_aDocInfo.Title.Len() || + m_aDocInfo.Author.Len() || + m_aDocInfo.Subject.Len() ) + { + aMetadataStream.append( " <rdf:Description rdf:about=\"\"\n" ); + aMetadataStream.append( " xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n" ); + if( m_aDocInfo.Title.Len() ) + { +// this is according to PDF/A-1, technical corrigendum 1 (2007-04-01) + aMetadataStream.append( " <dc:title>\n" ); + aMetadataStream.append( " <rdf:Alt>\n" ); + aMetadataStream.append( " <rdf:li xml:lang=\"x-default\">" ); + rtl::OUString aTitle; + escapeStringXML( m_aDocInfo.Title, aTitle ); + aMetadataStream.append( OUStringToOString( aTitle, RTL_TEXTENCODING_UTF8 ) ); + aMetadataStream.append( "</rdf:li>\n" ); + aMetadataStream.append( " </rdf:Alt>\n" ); + aMetadataStream.append( " </dc:title>\n" ); + } + if( m_aDocInfo.Author.Len() ) + { + aMetadataStream.append( " <dc:creator>\n" ); + aMetadataStream.append( " <rdf:Seq>\n" ); + aMetadataStream.append( " <rdf:li>" ); + rtl::OUString aAuthor; + escapeStringXML( m_aDocInfo.Author, aAuthor ); + aMetadataStream.append( OUStringToOString( aAuthor , RTL_TEXTENCODING_UTF8 ) ); + aMetadataStream.append( "</rdf:li>\n" ); + aMetadataStream.append( " </rdf:Seq>\n" ); + aMetadataStream.append( " </dc:creator>\n" ); + } + if( m_aDocInfo.Subject.Len() ) + { +// this is according to PDF/A-1, technical corrigendum 1 (2007-04-01) + aMetadataStream.append( " <dc:description>\n" ); + aMetadataStream.append( " <rdf:Alt>\n" ); + aMetadataStream.append( " <rdf:li xml:lang=\"x-default\">" ); + rtl::OUString aSubject; + escapeStringXML( m_aDocInfo.Subject, aSubject ); + aMetadataStream.append( OUStringToOString( aSubject , RTL_TEXTENCODING_UTF8 ) ); + aMetadataStream.append( "</rdf:li>\n" ); + aMetadataStream.append( " </rdf:Alt>\n" ); + aMetadataStream.append( " </dc:description>\n" ); + } + aMetadataStream.append( " </rdf:Description>\n" ); + } + +//... PDF properties go here + if( m_aDocInfo.Producer.Len() || + m_aDocInfo.Keywords.Len() ) + { + aMetadataStream.append( " <rdf:Description rdf:about=\"\"\n" ); + aMetadataStream.append( " xmlns:pdf=\"http://ns.adobe.com/pdf/1.3/\">\n" ); + if( m_aDocInfo.Producer.Len() ) + { + aMetadataStream.append( " <pdf:Producer>" ); + rtl::OUString aProducer; + escapeStringXML( m_aDocInfo.Producer, aProducer ); + aMetadataStream.append( OUStringToOString( aProducer , RTL_TEXTENCODING_UTF8 ) ); + aMetadataStream.append( "</pdf:Producer>\n" ); + } + if( m_aDocInfo.Keywords.Len() ) + { + aMetadataStream.append( " <pdf:Keywords>" ); + rtl::OUString aKeywords; + escapeStringXML( m_aDocInfo.Keywords, aKeywords ); + aMetadataStream.append( OUStringToOString( aKeywords , RTL_TEXTENCODING_UTF8 ) ); + aMetadataStream.append( "</pdf:Keywords>\n" ); + } + aMetadataStream.append( " </rdf:Description>\n" ); + } + + aMetadataStream.append( " <rdf:Description rdf:about=\"\"\n" ); + aMetadataStream.append( " xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\">\n" ); + if( m_aDocInfo.Creator.Len() ) + { + aMetadataStream.append( " <xmp:CreatorTool>" ); + rtl::OUString aCreator; + escapeStringXML( m_aDocInfo.Creator, aCreator ); + aMetadataStream.append( OUStringToOString( aCreator , RTL_TEXTENCODING_UTF8 ) ); + aMetadataStream.append( "</xmp:CreatorTool>\n" ); + } +//creation date + aMetadataStream.append( " <xmp:CreateDate>" ); + aMetadataStream.append( m_aCreationMetaDateString ); + aMetadataStream.append( "</xmp:CreateDate>\n" ); + + aMetadataStream.append( " </rdf:Description>\n" ); + aMetadataStream.append( " </rdf:RDF>\n" ); + aMetadataStream.append( "</x:xmpmeta>\n" ); + +//add the padding + for( sal_Int32 nSpaces = 1; nSpaces <= 2100; nSpaces++ ) + { + aMetadataStream.append( " " ); + if( nSpaces % 100 == 0 ) + aMetadataStream.append( "\n" ); + } + + aMetadataStream.append( "<?xpacket end=\"w\"?>\n" ); + + OStringBuffer aMetadataObj( 1024 ); + + aMetadataObj.append( nObject ); + aMetadataObj.append( " 0 obj\n" ); + + aMetadataObj.append( "<</Type/Metadata/Subtype/XML/Length " ); + + aMetadataObj.append( (sal_Int32) aMetadataStream.getLength() ); + aMetadataObj.append( ">>\nstream\n" ); + CHECK_RETURN( writeBuffer( aMetadataObj.getStr(), aMetadataObj.getLength() ) ); +//emit the stream + CHECK_RETURN( writeBuffer( aMetadataStream.getStr(), aMetadataStream.getLength() ) ); + + aMetadataObj.setLength( 0 ); + aMetadataObj.append( "\nendstream\nendobj\n\n" ); + if( ! writeBuffer( aMetadataObj.getStr(), aMetadataObj.getLength() ) ) + nObject = 0; + } + else + nObject = 0; + + return nObject; +} +//<---i59651 + +bool PDFWriterImpl::emitTrailer() +{ + // emit doc info + OString aInfoValuesOut; + sal_Int32 nDocInfoObject = emitInfoDict( ); + + sal_Int32 nSecObject = 0; + + if( m_aContext.Encrypt == true ) + { +//emit the security information +//must be emitted as indirect dictionary object, since +//Acrobat Reader 5 works only with this kind of implementation + nSecObject = createObject(); + + if( updateObject( nSecObject ) ) + { + OStringBuffer aLineS( 1024 ); + aLineS.append( nSecObject ); + aLineS.append( " 0 obj\n" + "<</Filter/Standard/V " ); + // check the version + if( m_aContext.Security128bit == true ) + aLineS.append( "2/Length 128/R 3" ); + else + aLineS.append( "1/R 2" ); + + // emit the owner password, must not be encrypted + aLineS.append( "/O(" ); + appendLiteralString( (const sal_Char*)m_nEncryptedOwnerPassword, 32, aLineS ); + aLineS.append( ")/U(" ); + appendLiteralString( (const sal_Char*)m_nEncryptedUserPassword, 32, aLineS ); + aLineS.append( ")/P " );// the permission set + aLineS.append( m_nAccessPermissions ); + aLineS.append( ">>\nendobj\n\n" ); + if( !writeBuffer( aLineS.getStr(), aLineS.getLength() ) ) + nSecObject = 0; + } + else + nSecObject = 0; + } + // emit xref table + // remember start + sal_uInt64 nXRefOffset = 0; + CHECK_RETURN( (osl_File_E_None == osl_getFilePos( m_aFile, &nXRefOffset )) ); + CHECK_RETURN( writeBuffer( "xref\n", 5 ) ); + + sal_Int32 nObjects = m_aObjects.size(); + OStringBuffer aLine; + aLine.append( "0 " ); + aLine.append( (sal_Int32)(nObjects+1) ); + aLine.append( "\n" ); + aLine.append( "0000000000 65535 f \n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + + for( sal_Int32 i = 0; i < nObjects; i++ ) + { + aLine.setLength( 0 ); + OString aOffset = OString::valueOf( (sal_Int64)m_aObjects[i] ); + for( sal_Int32 j = 0; j < (10-aOffset.getLength()); j++ ) + aLine.append( '0' ); + aLine.append( aOffset ); + aLine.append( " 00000 n \n" ); + DBG_ASSERT( aLine.getLength() == 20, "invalid xref entry" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + } + + // prepare document checksum + OStringBuffer aDocChecksum( 2*RTL_DIGEST_LENGTH_MD5+1 ); + if( m_aDocDigest ) + { + sal_uInt8 nMD5Sum[ RTL_DIGEST_LENGTH_MD5 ]; + rtl_digest_getMD5( m_aDocDigest, nMD5Sum, sizeof(nMD5Sum) ); + for( unsigned int i = 0; i < RTL_DIGEST_LENGTH_MD5; i++ ) + appendHex( nMD5Sum[i], aDocChecksum ); + } + // document id set in setDocInfo method + // emit trailer + aLine.setLength( 0 ); + aLine.append( "trailer\n" + "<</Size " ); + aLine.append( (sal_Int32)(nObjects+1) ); + aLine.append( "/Root " ); + aLine.append( m_nCatalogObject ); + aLine.append( " 0 R\n" ); + if( nSecObject |= 0 ) + { + aLine.append( "/Encrypt "); + aLine.append( nSecObject ); + aLine.append( " 0 R\n" ); + } + if( nDocInfoObject ) + { + aLine.append( "/Info " ); + aLine.append( nDocInfoObject ); + aLine.append( " 0 R\n" ); + } + if( m_aDocID.getLength() ) + { + aLine.append( "/ID [ <" ); + aLine.append( m_aDocID.getStr(), m_aDocID.getLength() ); + aLine.append( ">\n" + "<" ); + aLine.append( m_aDocID.getStr(), m_aDocID.getLength() ); + aLine.append( "> ]\n" ); + } + if( aDocChecksum.getLength() ) + { + aLine.append( "/DocChecksum /" ); + aLine.append( aDocChecksum ); + aLine.append( "\n" ); + } + if( m_aAdditionalStreams.size() > 0 ) + { + aLine.append( "/AdditionalStreams [" ); + for( unsigned int i = 0; i < m_aAdditionalStreams.size(); i++ ) + { + aLine.append( "/" ); + appendName( m_aAdditionalStreams[i].m_aMimeType, aLine ); + aLine.append( " " ); + aLine.append( m_aAdditionalStreams[i].m_nStreamObject ); + aLine.append( " 0 R\n" ); + } + aLine.append( "]\n" ); + } + aLine.append( ">>\n" + "startxref\n" ); + aLine.append( (sal_Int64)nXRefOffset ); + aLine.append( "\n" + "%%EOF\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + + return true; +} + +struct AnnotationSortEntry +{ + sal_Int32 nTabOrder; + sal_Int32 nObject; + sal_Int32 nWidgetIndex; + + AnnotationSortEntry( sal_Int32 nTab, sal_Int32 nObj, sal_Int32 nI ) : + nTabOrder( nTab ), + nObject( nObj ), + nWidgetIndex( nI ) + {} +}; + +struct AnnotSortContainer +{ + std::set< sal_Int32 > aObjects; + std::vector< AnnotationSortEntry > aSortedAnnots; +}; + +struct AnnotSorterLess +{ + std::vector< PDFWriterImpl::PDFWidget >& m_rWidgets; + + AnnotSorterLess( std::vector< PDFWriterImpl::PDFWidget >& rWidgets ) : m_rWidgets( rWidgets ) {} + + bool operator()( const AnnotationSortEntry& rLeft, const AnnotationSortEntry& rRight ) + { + if( rLeft.nTabOrder < rRight.nTabOrder ) + return true; + if( rRight.nTabOrder < rLeft.nTabOrder ) + return false; + if( rLeft.nWidgetIndex < 0 && rRight.nWidgetIndex < 0 ) + return false; + if( rRight.nWidgetIndex < 0 ) + return true; + if( rLeft.nWidgetIndex < 0 ) + return false; + // remember: widget rects are in PDF coordinates, so they are ordered down up + if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() > + m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() ) + return true; + if( m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Top() > + m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Top() ) + return false; + if( m_rWidgets[ rLeft.nWidgetIndex ].m_aRect.Left() < + m_rWidgets[ rRight.nWidgetIndex ].m_aRect.Left() ) + return true; + return false; + } +}; + +void PDFWriterImpl::sortWidgets() +{ + // sort widget annotations on each page as per their + // TabOrder attribute + std::hash_map< sal_Int32, AnnotSortContainer > sorted; + int nWidgets = m_aWidgets.size(); + for( int nW = 0; nW < nWidgets; nW++ ) + { + const PDFWidget& rWidget = m_aWidgets[nW]; + if( rWidget.m_nPage >= 0 ) + { + AnnotSortContainer& rCont = sorted[ rWidget.m_nPage ]; + // optimize vector allocation + if( rCont.aSortedAnnots.empty() ) + rCont.aSortedAnnots.reserve( m_aPages[ rWidget.m_nPage ].m_aAnnotations.size() ); + // insert widget to tab sorter + // RadioButtons are not page annotations, only their individual check boxes are + if( rWidget.m_eType != PDFWriter::RadioButton ) + { + rCont.aObjects.insert( rWidget.m_nObject ); + rCont.aSortedAnnots.push_back( AnnotationSortEntry( rWidget.m_nTabOrder, rWidget.m_nObject, nW ) ); + } + } + } + for( std::hash_map< sal_Int32, AnnotSortContainer >::iterator it = sorted.begin(); it != sorted.end(); ++it ) + { + // append entries for non widget annotations + PDFPage& rPage = m_aPages[ it->first ]; + unsigned int nAnnots = rPage.m_aAnnotations.size(); + for( unsigned int nA = 0; nA < nAnnots; nA++ ) + if( it->second.aObjects.find( rPage.m_aAnnotations[nA] ) == it->second.aObjects.end()) + it->second.aSortedAnnots.push_back( AnnotationSortEntry( 10000, rPage.m_aAnnotations[nA], -1 ) ); + + AnnotSorterLess aLess( m_aWidgets ); + std::stable_sort( it->second.aSortedAnnots.begin(), it->second.aSortedAnnots.end(), aLess ); + // sanity check + if( it->second.aSortedAnnots.size() == nAnnots) + { + for( unsigned int nA = 0; nA < nAnnots; nA++ ) + rPage.m_aAnnotations[nA] = it->second.aSortedAnnots[nA].nObject; + } + else + { + DBG_ASSERT( 0, "wrong number of sorted annotations" ); + #if OSL_DEBUG_LEVEL > 0 + fprintf( stderr, "PDFWriterImpl::sortWidgets(): wrong number of sorted assertions on page nr %ld\n" + " %ld sorted and %ld unsorted\n", (long int)it->first, (long int)it->second.aSortedAnnots.size(), (long int)nAnnots ); + #endif + } + } + + // FIXME: implement tab order in structure tree for PDF 1.5 +} + +namespace vcl { +class PDFStreamIf : + public cppu::WeakImplHelper1< com::sun::star::io::XOutputStream > +{ + PDFWriterImpl* m_pWriter; + bool m_bWrite; + public: + PDFStreamIf( PDFWriterImpl* pWriter ) : m_pWriter( pWriter ), m_bWrite( true ) {} + virtual ~PDFStreamIf(); + + virtual void SAL_CALL writeBytes( const com::sun::star::uno::Sequence< sal_Int8 >& aData ) throw(); + virtual void SAL_CALL flush() throw(); + virtual void SAL_CALL closeOutput() throw(); +}; +} + +PDFStreamIf::~PDFStreamIf() +{ +} + +void SAL_CALL PDFStreamIf::writeBytes( const com::sun::star::uno::Sequence< sal_Int8 >& aData ) throw() +{ + if( m_bWrite ) + { + sal_Int32 nBytes = aData.getLength(); + if( nBytes > 0 ) + m_pWriter->writeBuffer( aData.getConstArray(), nBytes ); + } +} + +void SAL_CALL PDFStreamIf::flush() throw() +{ +} + +void SAL_CALL PDFStreamIf::closeOutput() throw() +{ + m_bWrite = false; +} + +bool PDFWriterImpl::emitAdditionalStreams() +{ + unsigned int nStreams = m_aAdditionalStreams.size(); + for( unsigned int i = 0; i < nStreams; i++ ) + { + PDFAddStream& rStream = m_aAdditionalStreams[i]; + rStream.m_nStreamObject = createObject(); + sal_Int32 nSizeObject = createObject(); + + if( ! updateObject( rStream.m_nStreamObject ) ) + return false; + + OStringBuffer aLine; + aLine.append( rStream.m_nStreamObject ); + aLine.append( " 0 obj\n<</Length " ); + aLine.append( nSizeObject ); + aLine.append( " 0 R" ); + if( rStream.m_bCompress ) + aLine.append( "/Filter/FlateDecode" ); + aLine.append( ">>\nstream\n" ); + if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) ) + return false; + sal_uInt64 nBeginStreamPos = 0, nEndStreamPos = 0; + if( osl_File_E_None != osl_getFilePos( m_aFile, &nBeginStreamPos ) ) + { + osl_closeFile( m_aFile ); + m_bOpen = false; + } + if( rStream.m_bCompress ) + beginCompression(); + + checkAndEnableStreamEncryption( rStream.m_nStreamObject ); + com::sun::star::uno::Reference< com::sun::star::io::XOutputStream > xStream( new PDFStreamIf( this ) ); + rStream.m_pStream->write( xStream ); + xStream.clear(); + delete rStream.m_pStream; + rStream.m_pStream = NULL; + disableStreamEncryption(); + + if( rStream.m_bCompress ) + endCompression(); + + if( osl_File_E_None != osl_getFilePos( m_aFile, &nEndStreamPos ) ) + { + osl_closeFile( m_aFile ); + m_bOpen = false; + return false; + } + if( ! writeBuffer( "\nendstream\nendobj\n\n", 19 ) ) + return false ; + // emit stream length object + if( ! updateObject( nSizeObject ) ) + return false; + aLine.setLength( 0 ); + aLine.append( nSizeObject ); + aLine.append( " 0 obj\n" ); + aLine.append( (sal_Int64)(nEndStreamPos-nBeginStreamPos) ); + aLine.append( "\nendobj\n\n" ); + if( ! writeBuffer( aLine.getStr(), aLine.getLength() ) ) + return false; + } + return true; +} + +bool PDFWriterImpl::emit() +{ + endPage(); + + // resort structure tree and annotations if necessary + // needed for widget tab order + sortWidgets(); + + // emit additional streams + CHECK_RETURN( emitAdditionalStreams() ); + + // emit catalog + CHECK_RETURN( emitCatalog() ); + + // emit trailer + CHECK_RETURN( emitTrailer() ); + + osl_closeFile( m_aFile ); + m_bOpen = false; + + return true; +} + +std::set< PDFWriter::ErrorCode > PDFWriterImpl::getErrors() +{ + return m_aErrors; +} + +sal_Int32 PDFWriterImpl::getSystemFont( const Font& i_rFont ) +{ + getReferenceDevice()->Push(); + getReferenceDevice()->SetFont( i_rFont ); + getReferenceDevice()->ImplNewFont(); + + const ImplFontData* pDevFont = m_pReferenceDevice->mpFontEntry->maFontSelData.mpFontData; + sal_Int32 nFontID = 0; + FontEmbedData::iterator it = m_aSystemFonts.find( pDevFont ); + if( it != m_aSystemFonts.end() ) + nFontID = it->second.m_nNormalFontID; + else + { + nFontID = m_nNextFID++; + m_aSystemFonts[ pDevFont ] = EmbedFont(); + m_aSystemFonts[ pDevFont ].m_nNormalFontID = nFontID; + } + + getReferenceDevice()->Pop(); + getReferenceDevice()->ImplNewFont(); + + return nFontID; +} + +void PDFWriterImpl::registerGlyphs( int nGlyphs, + sal_GlyphId* pGlyphs, + sal_Int32* pGlyphWidths, + sal_Ucs* pUnicodes, + sal_Int32* pUnicodesPerGlyph, + sal_uInt8* pMappedGlyphs, + sal_Int32* pMappedFontObjects, + const ImplFontData* pFallbackFonts[] ) +{ + const ImplFontData* pDevFont = m_pReferenceDevice->mpFontEntry->maFontSelData.mpFontData; + sal_Ucs* pCurUnicode = pUnicodes; + for( int i = 0; i < nGlyphs; pCurUnicode += pUnicodesPerGlyph[i] , i++ ) + { + const int nFontGlyphId = pGlyphs[i] & (GF_IDXMASK | GF_ISCHAR | GF_GSUB); + const ImplFontData* pCurrentFont = pFallbackFonts[i] ? pFallbackFonts[i] : pDevFont; + + if( isBuiltinFont( pCurrentFont ) ) + { + sal_Int32 nFontID = 0; + FontEmbedData::iterator it = m_aEmbeddedFonts.find( pCurrentFont ); + if( it != m_aEmbeddedFonts.end() ) + nFontID = it->second.m_nNormalFontID; + else + { + nFontID = m_nNextFID++; + m_aEmbeddedFonts[ pCurrentFont ] = EmbedFont(); + m_aEmbeddedFonts[ pCurrentFont ].m_nNormalFontID = nFontID; + } + + pGlyphWidths[ i ] = 0; + pMappedGlyphs[ i ] = sal::static_int_cast<sal_Int8>( nFontGlyphId ); + pMappedFontObjects[ i ] = nFontID; + const ImplPdfBuiltinFontData* pFD = GetPdfFontData( pCurrentFont ); + if( pFD ) + { + const BuiltinFont* pBuiltinFont = pFD->GetBuiltinFont(); + pGlyphWidths[i] = pBuiltinFont->m_aWidths[ nFontGlyphId & 0x00ff ]; + } + } + else if( pCurrentFont->mbSubsettable ) + { + FontSubset& rSubset = m_aSubsets[ pCurrentFont ]; + // search for font specific glyphID + FontMapping::iterator it = rSubset.m_aMapping.find( nFontGlyphId ); + if( it != rSubset.m_aMapping.end() ) + { + pMappedFontObjects[i] = it->second.m_nFontID; + pMappedGlyphs[i] = it->second.m_nSubsetGlyphID; + } + else + { + // create new subset if necessary + if( rSubset.m_aSubsets.empty() + || (rSubset.m_aSubsets.back().m_aMapping.size() > 254) ) + { + rSubset.m_aSubsets.push_back( FontEmit( m_nNextFID++ ) ); + } + + // copy font id + pMappedFontObjects[i] = rSubset.m_aSubsets.back().m_nFontID; + // create new glyph in subset + sal_uInt8 nNewId = sal::static_int_cast<sal_uInt8>(rSubset.m_aSubsets.back().m_aMapping.size()+1); + pMappedGlyphs[i] = nNewId; + + // add new glyph to emitted font subset + GlyphEmit& rNewGlyphEmit = rSubset.m_aSubsets.back().m_aMapping[ nFontGlyphId ]; + rNewGlyphEmit.setGlyphId( nNewId ); + for( sal_Int32 n = 0; n < pUnicodesPerGlyph[i]; n++ ) + rNewGlyphEmit.addCode( pCurUnicode[n] ); + + // add new glyph to font mapping + Glyph& rNewGlyph = rSubset.m_aMapping[ nFontGlyphId ]; + rNewGlyph.m_nFontID = pMappedFontObjects[i]; + rNewGlyph.m_nSubsetGlyphID = nNewId; + } + getReferenceDevice()->ImplGetGraphics(); + const bool bVertical = ((pGlyphs[i] & GF_ROTMASK) != 0); + pGlyphWidths[i] = m_aFontCache.getGlyphWidth( pCurrentFont, + nFontGlyphId, + bVertical, + m_pReferenceDevice->mpGraphics ); + } + else if( pCurrentFont->IsEmbeddable() ) + { + sal_Int32 nFontID = 0; + FontEmbedData::iterator it = m_aEmbeddedFonts.find( pCurrentFont ); + if( it != m_aEmbeddedFonts.end() ) + nFontID = it->second.m_nNormalFontID; + else + { + nFontID = m_nNextFID++; + m_aEmbeddedFonts[ pCurrentFont ] = EmbedFont(); + m_aEmbeddedFonts[ pCurrentFont ].m_nNormalFontID = nFontID; + } + EmbedFont& rEmbedFont = m_aEmbeddedFonts[pCurrentFont]; + + const Ucs2SIntMap* pEncoding = NULL; + const Ucs2OStrMap* pNonEncoded = NULL; + getReferenceDevice()->ImplGetGraphics(); + pEncoding = m_pReferenceDevice->mpGraphics->GetFontEncodingVector( pCurrentFont, &pNonEncoded ); + + Ucs2SIntMap::const_iterator enc_it; + Ucs2OStrMap::const_iterator nonenc_it; + + sal_Int32 nCurFontID = nFontID; + sal_Ucs cChar = *pCurUnicode; + if( pEncoding ) + { + enc_it = pEncoding->find( cChar ); + if( enc_it != pEncoding->end() && enc_it->second > 0 ) + { + DBG_ASSERT( (enc_it->second & 0xffffff00) == 0, "Invalid character code" ); + cChar = (sal_Ucs)enc_it->second; + } + else if( (enc_it == pEncoding->end() || enc_it->second == -1) && + pNonEncoded && + (nonenc_it = pNonEncoded->find( cChar )) != pNonEncoded->end() ) + { + nCurFontID = 0; + // find non encoded glyph + for( std::list< EmbedEncoding >::iterator nec_it = rEmbedFont.m_aExtendedEncodings.begin(); nec_it != rEmbedFont.m_aExtendedEncodings.end(); ++nec_it ) + { + if( nec_it->m_aCMap.find( cChar ) != nec_it->m_aCMap.end() ) + { + nCurFontID = nec_it->m_nFontID; + cChar = (sal_Ucs)nec_it->m_aCMap[ cChar ]; + break; + } + } + if( nCurFontID == 0 ) // new nonencoded glyph + { + if( rEmbedFont.m_aExtendedEncodings.empty() || rEmbedFont.m_aExtendedEncodings.back().m_aEncVector.size() == 255 ) + { + rEmbedFont.m_aExtendedEncodings.push_back( EmbedEncoding() ); + rEmbedFont.m_aExtendedEncodings.back().m_nFontID = m_nNextFID++; + } + EmbedEncoding& rEncoding = rEmbedFont.m_aExtendedEncodings.back(); + rEncoding.m_aEncVector.push_back( EmbedCode() ); + rEncoding.m_aEncVector.back().m_aUnicode = cChar; + rEncoding.m_aEncVector.back().m_aName = nonenc_it->second; + rEncoding.m_aCMap[ cChar ] = (sal_Int8)(rEncoding.m_aEncVector.size()-1); + nCurFontID = rEncoding.m_nFontID; + cChar = (sal_Ucs)rEncoding.m_aCMap[ cChar ]; + } + } + else + pEncoding = NULL; + } + if( ! pEncoding ) + { + if( cChar & 0xff00 ) + { + // some characters can be used by conversion + if( cChar >= 0xf000 && cChar <= 0xf0ff ) // symbol encoding in private use area + cChar -= 0xf000; + else + { + String aString(cChar); + ByteString aChar( aString, RTL_TEXTENCODING_MS_1252 ); + cChar = ((sal_Ucs)aChar.GetChar( 0 )) & 0x00ff; + } + } + } + + pMappedGlyphs[ i ] = (sal_Int8)cChar; + pMappedFontObjects[ i ] = nCurFontID; + pGlyphWidths[ i ] = m_aFontCache.getGlyphWidth( pCurrentFont, + (pEncoding ? *pCurUnicode : cChar) | GF_ISCHAR, + false, + m_pReferenceDevice->mpGraphics ); + } + } +} + +void PDFWriterImpl::drawRelief( SalLayout& rLayout, const String& rText, bool bTextLines ) +{ + push( PUSH_ALL ); + + FontRelief eRelief = m_aCurrentPDFState.m_aFont.GetRelief(); + + Color aTextColor = m_aCurrentPDFState.m_aFont.GetColor(); + Color aTextLineColor = m_aCurrentPDFState.m_aTextLineColor; + Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor; + Color aReliefColor( COL_LIGHTGRAY ); + if( aTextColor == COL_BLACK ) + aTextColor = Color( COL_WHITE ); + if( aTextLineColor == COL_BLACK ) + aTextLineColor = Color( COL_WHITE ); + if( aOverlineColor == COL_BLACK ) + aOverlineColor = Color( COL_WHITE ); + if( aTextColor == COL_WHITE ) + aReliefColor = Color( COL_BLACK ); + + Font aSetFont = m_aCurrentPDFState.m_aFont; + aSetFont.SetRelief( RELIEF_NONE ); + aSetFont.SetShadow( FALSE ); + + aSetFont.SetColor( aReliefColor ); + setTextLineColor( aReliefColor ); + setOverlineColor( aReliefColor ); + setFont( aSetFont ); + long nOff = 1 + getReferenceDevice()->mnDPIX/300; + if( eRelief == RELIEF_ENGRAVED ) + nOff = -nOff; + + rLayout.DrawOffset() += Point( nOff, nOff ); + updateGraphicsState(); + drawLayout( rLayout, rText, bTextLines ); + + rLayout.DrawOffset() -= Point( nOff, nOff ); + setTextLineColor( aTextLineColor ); + setOverlineColor( aOverlineColor ); + aSetFont.SetColor( aTextColor ); + setFont( aSetFont ); + updateGraphicsState(); + drawLayout( rLayout, rText, bTextLines ); + + // clean up the mess + pop(); +} + +void PDFWriterImpl::drawShadow( SalLayout& rLayout, const String& rText, bool bTextLines ) +{ + Font aSaveFont = m_aCurrentPDFState.m_aFont; + Color aSaveTextLineColor = m_aCurrentPDFState.m_aTextLineColor; + Color aSaveOverlineColor = m_aCurrentPDFState.m_aOverlineColor; + + Font& rFont = m_aCurrentPDFState.m_aFont; + if( rFont.GetColor() == Color( COL_BLACK ) || rFont.GetColor().GetLuminance() < 8 ) + rFont.SetColor( Color( COL_LIGHTGRAY ) ); + else + rFont.SetColor( Color( COL_BLACK ) ); + rFont.SetShadow( FALSE ); + rFont.SetOutline( FALSE ); + setFont( rFont ); + setTextLineColor( rFont.GetColor() ); + setOverlineColor( rFont.GetColor() ); + updateGraphicsState(); + + long nOff = 1 + ((m_pReferenceDevice->mpFontEntry->mnLineHeight-24)/24); + if( rFont.IsOutline() ) + nOff++; + rLayout.DrawBase() += Point( nOff, nOff ); + drawLayout( rLayout, rText, bTextLines ); + rLayout.DrawBase() -= Point( nOff, nOff ); + + setFont( aSaveFont ); + setTextLineColor( aSaveTextLineColor ); + setOverlineColor( aSaveOverlineColor ); + updateGraphicsState(); +} + +void PDFWriterImpl::drawVerticalGlyphs( + const std::vector<PDFWriterImpl::PDFGlyph>& rGlyphs, + OStringBuffer& rLine, + const Point& rAlignOffset, + const Matrix3& rRotScale, + double fAngle, + double fXScale, + double fSkew, + sal_Int32 nFontHeight ) +{ + long nXOffset = 0; + Point aCurPos( rGlyphs[0].m_aPos ); + aCurPos = m_pReferenceDevice->PixelToLogic( aCurPos ); + aCurPos += rAlignOffset; + for( size_t i = 0; i < rGlyphs.size(); i++ ) + { + // have to emit each glyph on its own + double fDeltaAngle = 0.0; + double fYScale = 1.0; + double fTempXScale = fXScale; + double fSkewB = fSkew; + double fSkewA = 0.0; + + Point aDeltaPos; + if( ( rGlyphs[i].m_nGlyphId & GF_ROTMASK ) == GF_ROTL ) + { + fDeltaAngle = M_PI/2.0; + aDeltaPos.X() = m_pReferenceDevice->GetFontMetric().GetAscent(); + aDeltaPos.Y() = (int)((double)m_pReferenceDevice->GetFontMetric().GetDescent() * fXScale); + fYScale = fXScale; + fTempXScale = 1.0; + fSkewA = -fSkewB; + fSkewB = 0.0; + } + else if( ( rGlyphs[i].m_nGlyphId & GF_ROTMASK ) == GF_ROTR ) + { + fDeltaAngle = -M_PI/2.0; + aDeltaPos.X() = (int)((double)m_pReferenceDevice->GetFontMetric().GetDescent()*fXScale); + aDeltaPos.Y() = -m_pReferenceDevice->GetFontMetric().GetAscent(); + fYScale = fXScale; + fTempXScale = 1.0; + fSkewA = fSkewB; + fSkewB = 0.0; + } + aDeltaPos += (m_pReferenceDevice->PixelToLogic( Point( (int)((double)nXOffset/fXScale), 0 ) ) - m_pReferenceDevice->PixelToLogic( Point() ) ); + if( i < rGlyphs.size()-1 ) + nXOffset += rGlyphs[i+1].m_aPos.Y() - rGlyphs[i].m_aPos.Y(); + if( ! rGlyphs[i].m_nGlyphId ) + continue; + + aDeltaPos = rRotScale.transform( aDeltaPos ); + + Matrix3 aMat; + if( fSkewB != 0.0 || fSkewA != 0.0 ) + aMat.skew( fSkewA, fSkewB ); + aMat.scale( fTempXScale, fYScale ); + aMat.rotate( fAngle+fDeltaAngle ); + aMat.translate( aCurPos.X()+aDeltaPos.X(), aCurPos.Y()+aDeltaPos.Y() ); + aMat.append( m_aPages.back(), rLine ); + rLine.append( " Tm" ); + if( i == 0 || rGlyphs[i-1].m_nMappedFontId != rGlyphs[i].m_nMappedFontId ) + { + rLine.append( " /F" ); + rLine.append( rGlyphs[i].m_nMappedFontId ); + rLine.append( ' ' ); + m_aPages.back().appendMappedLength( nFontHeight, rLine, true ); + rLine.append( " Tf" ); + } + rLine.append( "<" ); + appendHex( rGlyphs[i].m_nMappedGlyphId, rLine ); + rLine.append( ">Tj\n" ); + } +} + +void PDFWriterImpl::drawHorizontalGlyphs( + const std::vector<PDFWriterImpl::PDFGlyph>& rGlyphs, + OStringBuffer& rLine, + const Point& rAlignOffset, + double fAngle, + double fXScale, + double fSkew, + sal_Int32 nFontHeight, + sal_Int32 nPixelFontHeight + ) +{ + // horizontal (= normal) case + + // fill in run end indices + // end is marked by index of the first glyph of the next run + // a run is marked by same mapped font id and same Y position + std::vector< sal_uInt32 > aRunEnds; + aRunEnds.reserve( rGlyphs.size() ); + for( size_t i = 1; i < rGlyphs.size(); i++ ) + { + if( rGlyphs[i].m_nMappedFontId != rGlyphs[i-1].m_nMappedFontId || + rGlyphs[i].m_aPos.Y() != rGlyphs[i-1].m_aPos.Y() ) + { + aRunEnds.push_back(i); + } + } + // last run ends at last glyph + aRunEnds.push_back( rGlyphs.size() ); + + // loop over runs of the same font + sal_uInt32 nBeginRun = 0; + for( size_t nRun = 0; nRun < aRunEnds.size(); nRun++ ) + { + // setup text matrix + Point aCurPos = rGlyphs[nBeginRun].m_aPos; + // back transformation to current coordinate system + aCurPos = m_pReferenceDevice->PixelToLogic( aCurPos ); + aCurPos += rAlignOffset; + // the first run can be set with "Td" operator + // subsequent use of that operator would move + // the texline matrix relative to what was set before + // making use of that would drive us into rounding issues + Matrix3 aMat; + if( nRun == 0 && fAngle == 0.0 && fXScale == 1.0 && fSkew == 0.0 ) + { + m_aPages.back().appendPoint( aCurPos, rLine, false ); + rLine.append( " Td " ); + } + else + { + if( fSkew != 0.0 ) + aMat.skew( 0.0, fSkew ); + aMat.scale( fXScale, 1.0 ); + aMat.rotate( fAngle ); + aMat.translate( aCurPos.X(), aCurPos.Y() ); + aMat.append( m_aPages.back(), rLine ); + rLine.append( " Tm\n" ); + } + // set up correct font + rLine.append( "/F" ); + rLine.append( rGlyphs[nBeginRun].m_nMappedFontId ); + rLine.append( ' ' ); + m_aPages.back().appendMappedLength( nFontHeight, rLine, true ); + rLine.append( " Tf" ); + + // output glyphs using Tj or TJ + OStringBuffer aKernedLine( 256 ), aUnkernedLine( 256 ); + aKernedLine.append( "[<" ); + aUnkernedLine.append( '<' ); + appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aKernedLine ); + appendHex( rGlyphs[nBeginRun].m_nMappedGlyphId, aUnkernedLine ); + + aMat.invert(); + bool bNeedKern = false; + for( sal_uInt32 nPos = nBeginRun+1; nPos < aRunEnds[nRun]; nPos++ ) + { + appendHex( rGlyphs[nPos].m_nMappedGlyphId, aUnkernedLine ); + // check if default glyph positioning is sufficient + const Point aThisPos = aMat.transform( rGlyphs[nPos].m_aPos ); + const Point aPrevPos = aMat.transform( rGlyphs[nPos-1].m_aPos ); + double fAdvance = aThisPos.X() - aPrevPos.X(); + fAdvance *= 1000.0 / nPixelFontHeight; + const sal_Int32 nAdjustment = (sal_Int32)(rGlyphs[nPos-1].m_nNativeWidth - fAdvance + 0.5); + if( nAdjustment != 0 ) + { + // apply individual glyph positioning + bNeedKern = true; + aKernedLine.append( ">" ); + aKernedLine.append( nAdjustment ); + aKernedLine.append( "<" ); + } + appendHex( rGlyphs[nPos].m_nMappedGlyphId, aKernedLine ); + } + aKernedLine.append( ">]TJ\n" ); + aUnkernedLine.append( ">Tj\n" ); + rLine.append( bNeedKern ? aKernedLine : aUnkernedLine ); + + // set beginning of next run + nBeginRun = aRunEnds[nRun]; + } +} + +void PDFWriterImpl::drawLayout( SalLayout& rLayout, const String& rText, bool bTextLines ) +{ + // relief takes precedence over shadow (see outdev3.cxx) + if( m_aCurrentPDFState.m_aFont.GetRelief() != RELIEF_NONE ) + { + drawRelief( rLayout, rText, bTextLines ); + return; + } + else if( m_aCurrentPDFState.m_aFont.IsShadow() ) + drawShadow( rLayout, rText, bTextLines ); + + OStringBuffer aLine( 512 ); + + const int nMaxGlyphs = 256; + + sal_GlyphId pGlyphs[nMaxGlyphs]; + sal_Int32 pGlyphWidths[nMaxGlyphs]; + sal_uInt8 pMappedGlyphs[nMaxGlyphs]; + sal_Int32 pMappedFontObjects[nMaxGlyphs]; + std::vector<sal_Ucs> aUnicodes; + aUnicodes.reserve( nMaxGlyphs ); + sal_Int32 pUnicodesPerGlyph[nMaxGlyphs]; + int pCharPosAry[nMaxGlyphs]; + sal_Int32 nAdvanceWidths[nMaxGlyphs]; + const ImplFontData* pFallbackFonts[nMaxGlyphs]; + bool bVertical = m_aCurrentPDFState.m_aFont.IsVertical(); + int nGlyphs; + int nIndex = 0; + int nMinCharPos = 0, nMaxCharPos = rText.Len()-1; + double fXScale = 1.0; + double fSkew = 0.0; + sal_Int32 nPixelFontHeight = m_pReferenceDevice->mpFontEntry->maFontSelData.mnHeight; + TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlign(); + + // transform font height back to current units + // note: the layout calculates in outdevs device pixel !! + sal_Int32 nFontHeight = m_pReferenceDevice->ImplDevicePixelToLogicHeight( nPixelFontHeight ); + if( m_aCurrentPDFState.m_aFont.GetWidth() ) + { + Font aFont( m_aCurrentPDFState.m_aFont ); + aFont.SetWidth( 0 ); + FontMetric aMetric = m_pReferenceDevice->GetFontMetric( aFont ); + if( aMetric.GetWidth() != m_aCurrentPDFState.m_aFont.GetWidth() ) + { + fXScale = + (double)m_aCurrentPDFState.m_aFont.GetWidth() / + (double)aMetric.GetWidth(); + } + // force state before GetFontMetric + m_pReferenceDevice->ImplNewFont(); + } + + // perform artificial italics if necessary + if( ( m_aCurrentPDFState.m_aFont.GetItalic() == ITALIC_NORMAL || + m_aCurrentPDFState.m_aFont.GetItalic() == ITALIC_OBLIQUE ) && + !( m_pReferenceDevice->mpFontEntry->maFontSelData.mpFontData->GetSlant() == ITALIC_NORMAL || + m_pReferenceDevice->mpFontEntry->maFontSelData.mpFontData->GetSlant() == ITALIC_OBLIQUE ) + ) + { + fSkew = M_PI/12.0; + } + + // if the mapmode is distorted we need to adjust for that also + if( m_aCurrentPDFState.m_aMapMode.GetScaleX() != m_aCurrentPDFState.m_aMapMode.GetScaleY() ) + { + fXScale *= double(m_aCurrentPDFState.m_aMapMode.GetScaleX()) / double(m_aCurrentPDFState.m_aMapMode.GetScaleY()); + } + + int nAngle = m_aCurrentPDFState.m_aFont.GetOrientation(); + // normalize angles + while( nAngle < 0 ) + nAngle += 3600; + nAngle = nAngle % 3600; + double fAngle = (double)nAngle * M_PI / 1800.0; + + Matrix3 aRotScale; + aRotScale.scale( fXScale, 1.0 ); + if( fAngle != 0.0 ) + aRotScale.rotate( -fAngle ); + + bool bPop = false; + bool bABold = false; + // artificial bold necessary ? + if( m_pReferenceDevice->mpFontEntry->maFontSelData.mpFontData->GetWeight() <= WEIGHT_MEDIUM && + m_pReferenceDevice->mpFontEntry->maFontSelData.GetWeight() > WEIGHT_MEDIUM ) + { + if( ! bPop ) + aLine.append( "q " ); + bPop = true; + bABold = true; + } + // setup text colors (if necessary) + Color aStrokeColor( COL_TRANSPARENT ); + Color aNonStrokeColor( COL_TRANSPARENT ); + + if( m_aCurrentPDFState.m_aFont.IsOutline() ) + { + aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor(); + aNonStrokeColor = Color( COL_WHITE ); + } + else + aNonStrokeColor = m_aCurrentPDFState.m_aFont.GetColor(); + if( bABold ) + aStrokeColor = m_aCurrentPDFState.m_aFont.GetColor(); + + if( aStrokeColor != Color( COL_TRANSPARENT ) && aStrokeColor != m_aCurrentPDFState.m_aLineColor ) + { + if( ! bPop ) + aLine.append( "q " ); + bPop = true; + appendStrokingColor( aStrokeColor, aLine ); + aLine.append( "\n" ); + } + if( aNonStrokeColor != Color( COL_TRANSPARENT ) && aNonStrokeColor != m_aCurrentPDFState.m_aFillColor ) + { + if( ! bPop ) + aLine.append( "q " ); + bPop = true; + appendNonStrokingColor( aNonStrokeColor, aLine ); + aLine.append( "\n" ); + } + + // begin text object + aLine.append( "BT\n" ); + // outline attribute ? + if( m_aCurrentPDFState.m_aFont.IsOutline() || bABold ) + { + // set correct text mode, set stroke width + aLine.append( "2 Tr " ); // fill, then stroke + + if( m_aCurrentPDFState.m_aFont.IsOutline() ) + { + // unclear what to do in case of outline and artificial bold + // for the time being outline wins + aLine.append( "0.25 w \n" ); + } + else + { + double fW = (double)m_aCurrentPDFState.m_aFont.GetHeight() / 30.0; + m_aPages.back().appendMappedLength( fW, aLine ); + aLine.append ( " w\n" ); + } + } + + FontMetric aRefDevFontMetric = m_pReferenceDevice->GetFontMetric(); + + // collect the glyphs into a single array + const int nTmpMaxGlyphs = rLayout.GetOrientation() ? 1 : nMaxGlyphs; // #i97991# temporary workaround for #i87686# + std::vector< PDFGlyph > aGlyphs; + aGlyphs.reserve( nTmpMaxGlyphs ); + // first get all the glyphs and register them; coordinates still in Pixel + Point aGNGlyphPos; + while( (nGlyphs = rLayout.GetNextGlyphs( nTmpMaxGlyphs, pGlyphs, aGNGlyphPos, nIndex, nAdvanceWidths, pCharPosAry )) != 0 ) + { + aUnicodes.clear(); + for( int i = 0; i < nGlyphs; i++ ) + { + pFallbackFonts[i] = rLayout.GetFallbackFontData( pGlyphs[i] ); + + // default case: 1 glyph is one unicode + pUnicodesPerGlyph[i] = 1; + if( (pGlyphs[i] & GF_ISCHAR) ) + { + aUnicodes.push_back( static_cast<sal_Ucs>(pGlyphs[i] & GF_IDXMASK) ); + } + else if( pCharPosAry[i] >= nMinCharPos && pCharPosAry[i] <= nMaxCharPos ) + { + int nChars = 1; + aUnicodes.push_back( rText.GetChar( sal::static_int_cast<xub_StrLen>(pCharPosAry[i]) ) ); + pUnicodesPerGlyph[i] = 1; + // try to handle ligatures and such + if( i < nGlyphs-1 ) + { + pUnicodesPerGlyph[i] = nChars = pCharPosAry[i+1] - pCharPosAry[i]; + for( int n = 1; n < nChars; n++ ) + aUnicodes.push_back( rText.GetChar( sal::static_int_cast<xub_StrLen>(pCharPosAry[i]+n) ) ); + } + // #i36691# hack that is needed because currently the pGlyphs[] + // argument is ignored for embeddable fonts and so the layout + // engine's glyph work is ignored (i.e. char mirroring) + // TODO: a real solution would be to map the layout engine's + // glyphid (i.e. FreeType's synthetic glyphid for a Type1 font) + // back to unicode and then to embeddable font's encoding + if( getReferenceDevice()->GetLayoutMode() & TEXT_LAYOUT_BIDI_RTL ) + { + size_t nI = aUnicodes.size()-1; + for( int n = 0; n < nChars; n++, nI-- ) + aUnicodes[nI] = static_cast<sal_Ucs>(GetMirroredChar(aUnicodes[nI])); + } + } + else + aUnicodes.push_back( 0 ); + // note: in case of ctl one character may result + // in multiple glyphs. The current SalLayout + // implementations set -1 then to indicate that no direct + // mapping is possible + } + + registerGlyphs( nGlyphs, pGlyphs, pGlyphWidths, &aUnicodes[0], pUnicodesPerGlyph, pMappedGlyphs, pMappedFontObjects, pFallbackFonts ); + + for( int i = 0; i < nGlyphs; i++ ) + { + aGlyphs.push_back( PDFGlyph( aGNGlyphPos, + pGlyphWidths[i], + pGlyphs[i], + pMappedFontObjects[i], + pMappedGlyphs[i] ) ); + if( bVertical ) + aGNGlyphPos.Y() += nAdvanceWidths[i]/rLayout.GetUnitsPerPixel(); + else + aGNGlyphPos.X() += nAdvanceWidths[i]/rLayout.GetUnitsPerPixel(); + } + } + + Point aAlignOffset; + if ( eAlign == ALIGN_BOTTOM ) + aAlignOffset.Y() -= aRefDevFontMetric.GetDescent(); + else if ( eAlign == ALIGN_TOP ) + aAlignOffset.Y() += aRefDevFontMetric.GetAscent(); + if( aAlignOffset.X() || aAlignOffset.Y() ) + aAlignOffset = aRotScale.transform( aAlignOffset ); + + /* #159153# do not emit an empty glyph vector; this can happen if e.g. the original + string contained only on of the UTF16 BOMs + */ + if( ! aGlyphs.empty() ) + { + if( bVertical ) + drawVerticalGlyphs( aGlyphs, aLine, aAlignOffset, aRotScale, fAngle, fXScale, fSkew, nFontHeight ); + else + drawHorizontalGlyphs( aGlyphs, aLine, aAlignOffset, fAngle, fXScale, fSkew, nFontHeight, nPixelFontHeight ); + } + + // end textobject + aLine.append( "ET\n" ); + if( bPop ) + aLine.append( "Q\n" ); + + writeBuffer( aLine.getStr(), aLine.getLength() ); + + // draw eventual textlines + FontStrikeout eStrikeout = m_aCurrentPDFState.m_aFont.GetStrikeout(); + FontUnderline eUnderline = m_aCurrentPDFState.m_aFont.GetUnderline(); + FontUnderline eOverline = m_aCurrentPDFState.m_aFont.GetOverline(); + if( bTextLines && + ( + ( eUnderline != UNDERLINE_NONE && eUnderline != UNDERLINE_DONTKNOW ) || + ( eOverline != UNDERLINE_NONE && eOverline != UNDERLINE_DONTKNOW ) || + ( eStrikeout != STRIKEOUT_NONE && eStrikeout != STRIKEOUT_DONTKNOW ) + ) + ) + { + BOOL bUnderlineAbove = OutputDevice::ImplIsUnderlineAbove( m_aCurrentPDFState.m_aFont ); + if( m_aCurrentPDFState.m_aFont.IsWordLineMode() ) + { + Point aPos, aStartPt; + sal_Int32 nWidth = 0, nAdvance=0; + for( int nStart = 0;;) + { + sal_GlyphId nGlyphIndex; + if( !rLayout.GetNextGlyphs( 1, &nGlyphIndex, aPos, nStart, &nAdvance ) ) + break; + + if( !rLayout.IsSpacingGlyph( nGlyphIndex ) ) + { + if( !nWidth ) + aStartPt = aPos; + + nWidth += nAdvance; + } + else if( nWidth > 0 ) + { + drawTextLine( m_pReferenceDevice->PixelToLogic( aStartPt ), + m_pReferenceDevice->ImplDevicePixelToLogicWidth( nWidth ), + eStrikeout, eUnderline, eOverline, bUnderlineAbove ); + nWidth = 0; + } + } + + if( nWidth > 0 ) + { + drawTextLine( m_pReferenceDevice->PixelToLogic( aStartPt ), + m_pReferenceDevice->ImplDevicePixelToLogicWidth( nWidth ), + eStrikeout, eUnderline, eOverline, bUnderlineAbove ); + } + } + else + { + Point aStartPt = rLayout.GetDrawPosition(); + int nWidth = rLayout.GetTextWidth() / rLayout.GetUnitsPerPixel(); + drawTextLine( m_pReferenceDevice->PixelToLogic( aStartPt ), + m_pReferenceDevice->ImplDevicePixelToLogicWidth( nWidth ), + eStrikeout, eUnderline, eOverline, bUnderlineAbove ); + } + } + + // write eventual emphasis marks + if( m_aCurrentPDFState.m_aFont.GetEmphasisMark() & EMPHASISMARK_STYLE ) + { + PolyPolygon aEmphPoly; + Rectangle aEmphRect1; + Rectangle aEmphRect2; + long nEmphYOff; + long nEmphWidth; + long nEmphHeight; + BOOL bEmphPolyLine; + FontEmphasisMark nEmphMark; + + push( PUSH_ALL ); + + aLine.setLength( 0 ); + aLine.append( "q\n" ); + + nEmphMark = m_pReferenceDevice->ImplGetEmphasisMarkStyle( m_aCurrentPDFState.m_aFont ); + if ( nEmphMark & EMPHASISMARK_POS_BELOW ) + nEmphHeight = m_pReferenceDevice->mnEmphasisDescent; + else + nEmphHeight = m_pReferenceDevice->mnEmphasisAscent; + m_pReferenceDevice->ImplGetEmphasisMark( aEmphPoly, + bEmphPolyLine, + aEmphRect1, + aEmphRect2, + nEmphYOff, + nEmphWidth, + nEmphMark, + m_pReferenceDevice->ImplDevicePixelToLogicWidth(nEmphHeight), + m_pReferenceDevice->mpFontEntry->mnOrientation ); + if ( bEmphPolyLine ) + { + setLineColor( m_aCurrentPDFState.m_aFont.GetColor() ); + setFillColor( Color( COL_TRANSPARENT ) ); + } + else + { + setFillColor( m_aCurrentPDFState.m_aFont.GetColor() ); + setLineColor( Color( COL_TRANSPARENT ) ); + } + writeBuffer( aLine.getStr(), aLine.getLength() ); + + Point aOffset = Point(0,0); + + if ( nEmphMark & EMPHASISMARK_POS_BELOW ) + aOffset.Y() += m_pReferenceDevice->mpFontEntry->maMetric.mnDescent + nEmphYOff; + else + aOffset.Y() -= m_pReferenceDevice->mpFontEntry->maMetric.mnAscent + nEmphYOff; + + long nEmphWidth2 = nEmphWidth / 2; + long nEmphHeight2 = nEmphHeight / 2; + aOffset += Point( nEmphWidth2, nEmphHeight2 ); + + if ( eAlign == ALIGN_BOTTOM ) + aOffset.Y() -= m_pReferenceDevice->mpFontEntry->maMetric.mnDescent; + else if ( eAlign == ALIGN_TOP ) + aOffset.Y() += m_pReferenceDevice->mpFontEntry->maMetric.mnAscent; + + for( int nStart = 0;;) + { + Point aPos; + sal_GlyphId nGlyphIndex; + sal_Int32 nAdvance; + if( !rLayout.GetNextGlyphs( 1, &nGlyphIndex, aPos, nStart, &nAdvance ) ) + break; + + if( !rLayout.IsSpacingGlyph( nGlyphIndex ) ) + { + Point aAdjOffset = aOffset; + aAdjOffset.X() += (nAdvance - nEmphWidth) / 2; + aAdjOffset = aRotScale.transform( aAdjOffset ); + + aAdjOffset -= Point( nEmphWidth2, nEmphHeight2 ); + + aPos += aAdjOffset; + aPos = m_pReferenceDevice->PixelToLogic( aPos ); + drawEmphasisMark( aPos.X(), aPos.Y(), + aEmphPoly, bEmphPolyLine, + aEmphRect1, aEmphRect2 ); + } + } + + writeBuffer( "Q\n", 2 ); + pop(); + } + +} + +void PDFWriterImpl::drawEmphasisMark( long nX, long nY, + const PolyPolygon& rPolyPoly, BOOL bPolyLine, + const Rectangle& rRect1, const Rectangle& rRect2 ) +{ + // TODO: pass nWidth as width of this mark + // long nWidth = 0; + + if ( rPolyPoly.Count() ) + { + if ( bPolyLine ) + { + Polygon aPoly = rPolyPoly.GetObject( 0 ); + aPoly.Move( nX, nY ); + drawPolyLine( aPoly ); + } + else + { + PolyPolygon aPolyPoly = rPolyPoly; + aPolyPoly.Move( nX, nY ); + drawPolyPolygon( aPolyPoly ); + } + } + + if ( !rRect1.IsEmpty() ) + { + Rectangle aRect( Point( nX+rRect1.Left(), + nY+rRect1.Top() ), rRect1.GetSize() ); + drawRectangle( aRect ); + } + + if ( !rRect2.IsEmpty() ) + { + Rectangle aRect( Point( nX+rRect2.Left(), + nY+rRect2.Top() ), rRect2.GetSize() ); + + drawRectangle( aRect ); + } +} + +void PDFWriterImpl::drawText( const Point& rPos, const String& rText, xub_StrLen nIndex, xub_StrLen nLen, bool bTextLines ) +{ + MARK( "drawText" ); + + updateGraphicsState(); + + // get a layout from the OuputDevice's SalGraphics + // this also enforces font substitution and sets the font on SalGraphics + SalLayout* pLayout = m_pReferenceDevice->ImplLayout( rText, nIndex, nLen, rPos ); + if( pLayout ) + { + drawLayout( *pLayout, rText, bTextLines ); + pLayout->Release(); + } +} + +void PDFWriterImpl::drawTextArray( const Point& rPos, const String& rText, const sal_Int32* pDXArray, xub_StrLen nIndex, xub_StrLen nLen, bool bTextLines ) +{ + MARK( "drawText with array" ); + + updateGraphicsState(); + + // get a layout from the OuputDevice's SalGraphics + // this also enforces font substitution and sets the font on SalGraphics + SalLayout* pLayout = m_pReferenceDevice->ImplLayout( rText, nIndex, nLen, rPos, 0, pDXArray ); + if( pLayout ) + { + drawLayout( *pLayout, rText, bTextLines ); + pLayout->Release(); + } +} + +void PDFWriterImpl::drawStretchText( const Point& rPos, ULONG nWidth, const String& rText, xub_StrLen nIndex, xub_StrLen nLen, bool bTextLines ) +{ + MARK( "drawStretchText" ); + + updateGraphicsState(); + + // get a layout from the OuputDevice's SalGraphics + // this also enforces font substitution and sets the font on SalGraphics + SalLayout* pLayout = m_pReferenceDevice->ImplLayout( rText, nIndex, nLen, rPos, nWidth ); + if( pLayout ) + { + drawLayout( *pLayout, rText, bTextLines ); + pLayout->Release(); + } +} + +void PDFWriterImpl::drawText( const Rectangle& rRect, const String& rOrigStr, USHORT nStyle, bool bTextLines ) +{ + long nWidth = rRect.GetWidth(); + long nHeight = rRect.GetHeight(); + + if ( nWidth <= 0 || nHeight <= 0 ) + return; + + MARK( "drawText with rectangle" ); + + updateGraphicsState(); + + // clip with rectangle + OStringBuffer aLine; + aLine.append( "q " ); + m_aPages.back().appendRect( rRect, aLine ); + aLine.append( " W* n\n" ); + writeBuffer( aLine.getStr(), aLine.getLength() ); + + // if disabled text is needed, put in here + + Point aPos = rRect.TopLeft(); + + long nTextHeight = m_pReferenceDevice->GetTextHeight(); + xub_StrLen nMnemonicPos = STRING_NOTFOUND; + + String aStr = rOrigStr; + if ( nStyle & TEXT_DRAW_MNEMONIC ) + aStr = m_pReferenceDevice->GetNonMnemonicString( aStr, nMnemonicPos ); + + // multiline text + if ( nStyle & TEXT_DRAW_MULTILINE ) + { + XubString aLastLine; + ImplMultiTextLineInfo aMultiLineInfo; + ImplTextLineInfo* pLineInfo; + long nMaxTextWidth; + xub_StrLen i; + xub_StrLen nLines; + xub_StrLen nFormatLines; + + if ( nTextHeight ) + { + ::vcl::DefaultTextLayout aLayout( *m_pReferenceDevice ); + nMaxTextWidth = OutputDevice::ImplGetTextLines( aMultiLineInfo, nWidth, aStr, nStyle, aLayout ); + nLines = (xub_StrLen)(nHeight/nTextHeight); + nFormatLines = aMultiLineInfo.Count(); + if ( !nLines ) + nLines = 1; + if ( nFormatLines > nLines ) + { + if ( nStyle & TEXT_DRAW_ENDELLIPSIS ) + { + // handle last line + nFormatLines = nLines-1; + + pLineInfo = aMultiLineInfo.GetLine( nFormatLines ); + aLastLine = aStr.Copy( pLineInfo->GetIndex() ); + aLastLine.ConvertLineEnd( LINEEND_LF ); + // replace line feed by space + xub_StrLen nLastLineLen = aLastLine.Len(); + for ( i = 0; i < nLastLineLen; i++ ) + { + if ( aLastLine.GetChar( i ) == _LF ) + aLastLine.SetChar( i, ' ' ); + } + aLastLine = m_pReferenceDevice->GetEllipsisString( aLastLine, nWidth, nStyle ); + nStyle &= ~(TEXT_DRAW_VCENTER | TEXT_DRAW_BOTTOM); + nStyle |= TEXT_DRAW_TOP; + } + } + + // vertical alignment + if ( nStyle & TEXT_DRAW_BOTTOM ) + aPos.Y() += nHeight-(nFormatLines*nTextHeight); + else if ( nStyle & TEXT_DRAW_VCENTER ) + aPos.Y() += (nHeight-(nFormatLines*nTextHeight))/2; + + // draw all lines excluding the last + for ( i = 0; i < nFormatLines; i++ ) + { + pLineInfo = aMultiLineInfo.GetLine( i ); + if ( nStyle & TEXT_DRAW_RIGHT ) + aPos.X() += nWidth-pLineInfo->GetWidth(); + else if ( nStyle & TEXT_DRAW_CENTER ) + aPos.X() += (nWidth-pLineInfo->GetWidth())/2; + xub_StrLen nIndex = pLineInfo->GetIndex(); + xub_StrLen nLineLen = pLineInfo->GetLen(); + drawText( aPos, aStr, nIndex, nLineLen, bTextLines ); + // mnemonics should not appear in documents, + // if the need arises, put them in here + aPos.Y() += nTextHeight; + aPos.X() = rRect.Left(); + } + + + // output last line left adjusted since it was shortened + if ( aLastLine.Len() ) + drawText( aPos, aLastLine, 0, STRING_LEN, bTextLines ); + } + } + else + { + long nTextWidth = m_pReferenceDevice->GetTextWidth( aStr ); + + // Evt. Text kuerzen + if ( nTextWidth > nWidth ) + { + if ( nStyle & (TEXT_DRAW_ENDELLIPSIS | TEXT_DRAW_PATHELLIPSIS | TEXT_DRAW_NEWSELLIPSIS) ) + { + aStr = m_pReferenceDevice->GetEllipsisString( aStr, nWidth, nStyle ); + nStyle &= ~(TEXT_DRAW_CENTER | TEXT_DRAW_RIGHT); + nStyle |= TEXT_DRAW_LEFT; + nTextWidth = m_pReferenceDevice->GetTextWidth( aStr ); + } + } + + // vertical alignment + if ( nStyle & TEXT_DRAW_RIGHT ) + aPos.X() += nWidth-nTextWidth; + else if ( nStyle & TEXT_DRAW_CENTER ) + aPos.X() += (nWidth-nTextWidth)/2; + + if ( nStyle & TEXT_DRAW_BOTTOM ) + aPos.Y() += nHeight-nTextHeight; + else if ( nStyle & TEXT_DRAW_VCENTER ) + aPos.Y() += (nHeight-nTextHeight)/2; + + // mnemonics should be inserted here if the need arises + + // draw the actual text + drawText( aPos, aStr, 0, STRING_LEN, bTextLines ); + } + + // reset clip region to original value + aLine.setLength( 0 ); + aLine.append( "Q\n" ); + writeBuffer( aLine.getStr(), aLine.getLength() ); +} + +void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop ) +{ + MARK( "drawLine" ); + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) ) + return; + + OStringBuffer aLine; + m_aPages.back().appendPoint( rStart, aLine ); + aLine.append( " m " ); + m_aPages.back().appendPoint( rStop, aLine ); + aLine.append( " l S\n" ); + + writeBuffer( aLine.getStr(), aLine.getLength() ); +} + +void PDFWriterImpl::drawLine( const Point& rStart, const Point& rStop, const LineInfo& rInfo ) +{ + MARK( "drawLine with LineInfo" ); + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) ) + return; + + if( rInfo.GetStyle() == LINE_SOLID && rInfo.GetWidth() < 2 ) + { + drawLine( rStart, rStop ); + return; + } + + OStringBuffer aLine; + + aLine.append( "q " ); + if( m_aPages.back().appendLineInfo( rInfo, aLine ) ) + { + m_aPages.back().appendPoint( rStart, aLine ); + aLine.append( " m " ); + m_aPages.back().appendPoint( rStop, aLine ); + aLine.append( " l S Q\n" ); + + writeBuffer( aLine.getStr(), aLine.getLength() ); + } + else + { + PDFWriter::ExtLineInfo aInfo; + convertLineInfoToExtLineInfo( rInfo, aInfo ); + Point aPolyPoints[2] = { rStart, rStop }; + Polygon aPoly( 2, aPolyPoints ); + drawPolyLine( aPoly, aInfo ); + } +} + +void PDFWriterImpl::drawWaveLine( const Point& rStart, const Point& rStop, sal_Int32 nDelta, sal_Int32 nLineWidth ) +{ + Point aDiff( rStop-rStart ); + double fLen = sqrt( (double)(aDiff.X()*aDiff.X() + aDiff.Y()*aDiff.Y()) ); + if( fLen < 1.0 ) + return; + + MARK( "drawWaveLine" ); + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) ) + return; + + OStringBuffer aLine( 512 ); + aLine.append( "q " ); + m_aPages.back().appendMappedLength( nLineWidth, aLine, true ); + aLine.append( " w " ); + + appendDouble( (double)aDiff.X()/fLen, aLine ); + aLine.append( ' ' ); + appendDouble( -(double)aDiff.Y()/fLen, aLine ); + aLine.append( ' ' ); + appendDouble( (double)aDiff.Y()/fLen, aLine ); + aLine.append( ' ' ); + appendDouble( (double)aDiff.X()/fLen, aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( rStart, aLine ); + aLine.append( " cm " ); + m_aPages.back().appendWaveLine( (sal_Int32)fLen, 0, nDelta, aLine ); + aLine.append( "Q\n" ); + writeBuffer( aLine.getStr(), aLine.getLength() ); +} + +#define WCONV( x ) m_pReferenceDevice->ImplDevicePixelToLogicWidth( x ) +#define HCONV( x ) m_pReferenceDevice->ImplDevicePixelToLogicHeight( x ) + +void PDFWriterImpl::drawWaveTextLine( OStringBuffer& aLine, long nWidth, FontUnderline eTextLine, Color aColor, bool bIsAbove ) +{ + // note: units in pFontEntry are ref device pixel + ImplFontEntry* pFontEntry = m_pReferenceDevice->mpFontEntry; + long nLineHeight = 0; + long nLinePos = 0; + + appendStrokingColor( aColor, aLine ); + aLine.append( "\n" ); + + if ( bIsAbove ) + { + if ( !pFontEntry->maMetric.mnAboveWUnderlineSize ) + m_pReferenceDevice->ImplInitAboveTextLineSize(); + nLineHeight = HCONV( pFontEntry->maMetric.mnAboveWUnderlineSize ); + nLinePos = HCONV( pFontEntry->maMetric.mnAboveWUnderlineOffset ); + } + else + { + if ( !pFontEntry->maMetric.mnWUnderlineSize ) + m_pReferenceDevice->ImplInitTextLineSize(); + nLineHeight = HCONV( pFontEntry->maMetric.mnWUnderlineSize ); + nLinePos = HCONV( pFontEntry->maMetric.mnWUnderlineOffset ); + } + if ( (eTextLine == UNDERLINE_SMALLWAVE) && (nLineHeight > 3) ) + nLineHeight = 3; + + long nLineWidth = getReferenceDevice()->mnDPIX/450; + if ( ! nLineWidth ) + nLineWidth = 1; + + if ( eTextLine == UNDERLINE_BOLDWAVE ) + nLineWidth = 3*nLineWidth; + + m_aPages.back().appendMappedLength( (sal_Int32)nLineWidth, aLine ); + aLine.append( " w " ); + + if ( eTextLine == UNDERLINE_DOUBLEWAVE ) + { + long nOrgLineHeight = nLineHeight; + nLineHeight /= 3; + if ( nLineHeight < 2 ) + { + if ( nOrgLineHeight > 1 ) + nLineHeight = 2; + else + nLineHeight = 1; + } + long nLineDY = nOrgLineHeight-(nLineHeight*2); + if ( nLineDY < nLineWidth ) + nLineDY = nLineWidth; + long nLineDY2 = nLineDY/2; + if ( !nLineDY2 ) + nLineDY2 = 1; + + nLinePos -= nLineWidth-nLineDY2; + + m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine ); + + nLinePos += nLineWidth+nLineDY; + m_aPages.back().appendWaveLine( nWidth, -nLinePos, 2*nLineHeight, aLine ); + } + else + { + if ( eTextLine != UNDERLINE_BOLDWAVE ) + nLinePos -= nLineWidth/2; + m_aPages.back().appendWaveLine( nWidth, -nLinePos, nLineHeight, aLine ); + } +} + +void PDFWriterImpl::drawStraightTextLine( OStringBuffer& aLine, long nWidth, FontUnderline eTextLine, Color aColor, bool bIsAbove ) +{ + // note: units in pFontEntry are ref device pixel + ImplFontEntry* pFontEntry = m_pReferenceDevice->mpFontEntry; + long nLineHeight = 0; + long nLinePos = 0; + long nLinePos2 = 0; + + if ( eTextLine > UNDERLINE_BOLDWAVE ) + eTextLine = UNDERLINE_SINGLE; + + switch ( eTextLine ) + { + case UNDERLINE_SINGLE: + case UNDERLINE_DOTTED: + case UNDERLINE_DASH: + case UNDERLINE_LONGDASH: + case UNDERLINE_DASHDOT: + case UNDERLINE_DASHDOTDOT: + if ( bIsAbove ) + { + if ( !pFontEntry->maMetric.mnAboveUnderlineSize ) + m_pReferenceDevice->ImplInitAboveTextLineSize(); + nLineHeight = HCONV( pFontEntry->maMetric.mnAboveUnderlineSize ); + nLinePos = HCONV( pFontEntry->maMetric.mnAboveUnderlineOffset ); + } + else + { + if ( !pFontEntry->maMetric.mnUnderlineSize ) + m_pReferenceDevice->ImplInitTextLineSize(); + nLineHeight = HCONV( pFontEntry->maMetric.mnUnderlineSize ); + nLinePos = HCONV( pFontEntry->maMetric.mnUnderlineOffset ); + } + break; + case UNDERLINE_BOLD: + case UNDERLINE_BOLDDOTTED: + case UNDERLINE_BOLDDASH: + case UNDERLINE_BOLDLONGDASH: + case UNDERLINE_BOLDDASHDOT: + case UNDERLINE_BOLDDASHDOTDOT: + if ( bIsAbove ) + { + if ( !pFontEntry->maMetric.mnAboveBUnderlineSize ) + m_pReferenceDevice->ImplInitAboveTextLineSize(); + nLineHeight = HCONV( pFontEntry->maMetric.mnAboveBUnderlineSize ); + nLinePos = HCONV( pFontEntry->maMetric.mnAboveBUnderlineOffset ); + } + else + { + if ( !pFontEntry->maMetric.mnBUnderlineSize ) + m_pReferenceDevice->ImplInitTextLineSize(); + nLineHeight = HCONV( pFontEntry->maMetric.mnBUnderlineSize ); + nLinePos = HCONV( pFontEntry->maMetric.mnBUnderlineOffset ); + nLinePos += nLineHeight/2; + } + break; + case UNDERLINE_DOUBLE: + if ( bIsAbove ) + { + if ( !pFontEntry->maMetric.mnAboveDUnderlineSize ) + m_pReferenceDevice->ImplInitAboveTextLineSize(); + nLineHeight = HCONV( pFontEntry->maMetric.mnAboveDUnderlineSize ); + nLinePos = HCONV( pFontEntry->maMetric.mnAboveDUnderlineOffset1 ); + nLinePos2 = HCONV( pFontEntry->maMetric.mnAboveDUnderlineOffset2 ); + } + else + { + if ( !pFontEntry->maMetric.mnDUnderlineSize ) + m_pReferenceDevice->ImplInitTextLineSize(); + nLineHeight = HCONV( pFontEntry->maMetric.mnDUnderlineSize ); + nLinePos = HCONV( pFontEntry->maMetric.mnDUnderlineOffset1 ); + nLinePos2 = HCONV( pFontEntry->maMetric.mnDUnderlineOffset2 ); + } + default: + break; + } + + if ( nLineHeight ) + { + m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine, true ); + aLine.append( " w " ); + appendStrokingColor( aColor, aLine ); + aLine.append( "\n" ); + + switch ( eTextLine ) + { + case UNDERLINE_DOTTED: + case UNDERLINE_BOLDDOTTED: + aLine.append( "[ " ); + m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine, false ); + aLine.append( " ] 0 d\n" ); + break; + case UNDERLINE_DASH: + case UNDERLINE_LONGDASH: + case UNDERLINE_BOLDDASH: + case UNDERLINE_BOLDLONGDASH: + { + sal_Int32 nDashLength = 4*nLineHeight; + sal_Int32 nVoidLength = 2*nLineHeight; + if ( ( eTextLine == UNDERLINE_LONGDASH ) || ( eTextLine == UNDERLINE_BOLDLONGDASH ) ) + nDashLength = 8*nLineHeight; + + aLine.append( "[ " ); + m_aPages.back().appendMappedLength( nDashLength, aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); + aLine.append( " ] 0 d\n" ); + } + break; + case UNDERLINE_DASHDOT: + case UNDERLINE_BOLDDASHDOT: + { + sal_Int32 nDashLength = 4*nLineHeight; + sal_Int32 nVoidLength = 2*nLineHeight; + aLine.append( "[ " ); + m_aPages.back().appendMappedLength( nDashLength, aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); + aLine.append( " ] 0 d\n" ); + } + break; + case UNDERLINE_DASHDOTDOT: + case UNDERLINE_BOLDDASHDOTDOT: + { + sal_Int32 nDashLength = 4*nLineHeight; + sal_Int32 nVoidLength = 2*nLineHeight; + aLine.append( "[ " ); + m_aPages.back().appendMappedLength( nDashLength, aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( nVoidLength, aLine, false ); + aLine.append( " ] 0 d\n" ); + } + break; + default: + break; + } + + aLine.append( "0 " ); + m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos), aLine, true ); + aLine.append( " m " ); + m_aPages.back().appendMappedLength( (sal_Int32)nWidth, aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos), aLine, true ); + aLine.append( " l S\n" ); + if ( eTextLine == UNDERLINE_DOUBLE ) + { + aLine.append( "0 " ); + m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos2-nLineHeight), aLine, true ); + aLine.append( " m " ); + m_aPages.back().appendMappedLength( (sal_Int32)nWidth, aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos2-nLineHeight), aLine, true ); + aLine.append( " l S\n" ); + } + } +} + +void PDFWriterImpl::drawStrikeoutLine( OStringBuffer& aLine, long nWidth, FontStrikeout eStrikeout, Color aColor ) +{ + // note: units in pFontEntry are ref device pixel + ImplFontEntry* pFontEntry = m_pReferenceDevice->mpFontEntry; + long nLineHeight = 0; + long nLinePos = 0; + long nLinePos2 = 0; + + if ( eStrikeout > STRIKEOUT_X ) + eStrikeout = STRIKEOUT_SINGLE; + + switch ( eStrikeout ) + { + case STRIKEOUT_SINGLE: + if ( !pFontEntry->maMetric.mnStrikeoutSize ) + m_pReferenceDevice->ImplInitTextLineSize(); + nLineHeight = HCONV( pFontEntry->maMetric.mnStrikeoutSize ); + nLinePos = HCONV( pFontEntry->maMetric.mnStrikeoutOffset ); + break; + case STRIKEOUT_BOLD: + if ( !pFontEntry->maMetric.mnBStrikeoutSize ) + m_pReferenceDevice->ImplInitTextLineSize(); + nLineHeight = HCONV( pFontEntry->maMetric.mnBStrikeoutSize ); + nLinePos = HCONV( pFontEntry->maMetric.mnBStrikeoutOffset ); + break; + case STRIKEOUT_DOUBLE: + if ( !pFontEntry->maMetric.mnDStrikeoutSize ) + m_pReferenceDevice->ImplInitTextLineSize(); + nLineHeight = HCONV( pFontEntry->maMetric.mnDStrikeoutSize ); + nLinePos = HCONV( pFontEntry->maMetric.mnDStrikeoutOffset1 ); + nLinePos2 = HCONV( pFontEntry->maMetric.mnDStrikeoutOffset2 ); + break; + default: + break; + } + + if ( nLineHeight ) + { + m_aPages.back().appendMappedLength( (sal_Int32)nLineHeight, aLine, true ); + aLine.append( " w " ); + appendStrokingColor( aColor, aLine ); + aLine.append( "\n" ); + + aLine.append( "0 " ); + m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos), aLine, true ); + aLine.append( " m " ); + m_aPages.back().appendMappedLength( (sal_Int32)nWidth, aLine, true ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos), aLine, true ); + aLine.append( " l S\n" ); + + if ( eStrikeout == STRIKEOUT_DOUBLE ) + { + aLine.append( "0 " ); + m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos2-nLineHeight), aLine, true ); + aLine.append( " m " ); + m_aPages.back().appendMappedLength( (sal_Int32)nWidth, aLine, true ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( (sal_Int32)(-nLinePos2-nLineHeight), aLine, true ); + aLine.append( " l S\n" ); + } + } +} + +void PDFWriterImpl::drawStrikeoutChar( const Point& rPos, long nWidth, FontStrikeout eStrikeout ) +{ + String aStrikeoutChar = String::CreateFromAscii( eStrikeout == STRIKEOUT_SLASH ? "/" : "X" ); + String aStrikeout = aStrikeoutChar; + while( m_pReferenceDevice->GetTextWidth( aStrikeout ) < nWidth ) + aStrikeout.Append( aStrikeout ); + + // do not get broader than nWidth modulo 1 character + while( m_pReferenceDevice->GetTextWidth( aStrikeout ) >= nWidth ) + aStrikeout.Erase( 0, 1 ); + aStrikeout.Append( aStrikeoutChar ); + BOOL bShadow = m_aCurrentPDFState.m_aFont.IsShadow(); + if ( bShadow ) + { + Font aFont = m_aCurrentPDFState.m_aFont; + aFont.SetShadow( FALSE ); + setFont( aFont ); + updateGraphicsState(); + } + + // strikeout string is left aligned non-CTL text + ULONG nOrigTLM = m_pReferenceDevice->GetLayoutMode(); + m_pReferenceDevice->SetLayoutMode( TEXT_LAYOUT_BIDI_STRONG|TEXT_LAYOUT_COMPLEX_DISABLED ); + drawText( rPos, aStrikeout, 0, aStrikeout.Len(), false ); + m_pReferenceDevice->SetLayoutMode( nOrigTLM ); + + if ( bShadow ) + { + Font aFont = m_aCurrentPDFState.m_aFont; + aFont.SetShadow( TRUE ); + setFont( aFont ); + updateGraphicsState(); + } +} + +void PDFWriterImpl::drawTextLine( const Point& rPos, long nWidth, FontStrikeout eStrikeout, FontUnderline eUnderline, FontUnderline eOverline, bool bUnderlineAbove ) +{ + if ( !nWidth || + ( ((eStrikeout == STRIKEOUT_NONE)||(eStrikeout == STRIKEOUT_DONTKNOW)) && + ((eUnderline == UNDERLINE_NONE)||(eUnderline == UNDERLINE_DONTKNOW)) && + ((eOverline == UNDERLINE_NONE)||(eOverline == UNDERLINE_DONTKNOW)) ) ) + return; + + MARK( "drawTextLine" ); + updateGraphicsState(); + + // note: units in pFontEntry are ref device pixel + ImplFontEntry* pFontEntry = m_pReferenceDevice->mpFontEntry; + Color aUnderlineColor = m_aCurrentPDFState.m_aTextLineColor; + Color aOverlineColor = m_aCurrentPDFState.m_aOverlineColor; + Color aStrikeoutColor = m_aCurrentPDFState.m_aFont.GetColor(); + bool bStrikeoutDone = false; + bool bUnderlineDone = false; + bool bOverlineDone = false; + + if ( (eStrikeout == STRIKEOUT_SLASH) || (eStrikeout == STRIKEOUT_X) ) + { + drawStrikeoutChar( rPos, nWidth, eStrikeout ); + bStrikeoutDone = true; + } + + Point aPos( rPos ); + TextAlign eAlign = m_aCurrentPDFState.m_aFont.GetAlign(); + if( eAlign == ALIGN_TOP ) + aPos.Y() += HCONV( pFontEntry->maMetric.mnAscent ); + else if( eAlign == ALIGN_BOTTOM ) + aPos.Y() -= HCONV( pFontEntry->maMetric.mnDescent ); + + OStringBuffer aLine( 512 ); + // save GS + aLine.append( "q " ); + + // rotate and translate matrix + double fAngle = (double)m_aCurrentPDFState.m_aFont.GetOrientation() * M_PI / 1800.0; + Matrix3 aMat; + aMat.rotate( fAngle ); + aMat.translate( aPos.X(), aPos.Y() ); + aMat.append( m_aPages.back(), aLine ); + aLine.append( " cm\n" ); + + if ( aUnderlineColor.GetTransparency() != 0 ) + aUnderlineColor = aStrikeoutColor; + + if ( (eUnderline == UNDERLINE_SMALLWAVE) || + (eUnderline == UNDERLINE_WAVE) || + (eUnderline == UNDERLINE_DOUBLEWAVE) || + (eUnderline == UNDERLINE_BOLDWAVE) ) + { + drawWaveTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove ); + bUnderlineDone = true; + } + + if ( (eOverline == UNDERLINE_SMALLWAVE) || + (eOverline == UNDERLINE_WAVE) || + (eOverline == UNDERLINE_DOUBLEWAVE) || + (eOverline == UNDERLINE_BOLDWAVE) ) + { + drawWaveTextLine( aLine, nWidth, eOverline, aOverlineColor, true ); + bOverlineDone = true; + } + + if ( !bUnderlineDone ) + { + drawStraightTextLine( aLine, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove ); + } + + if ( !bOverlineDone ) + { + drawStraightTextLine( aLine, nWidth, eOverline, aOverlineColor, true ); + } + + if ( !bStrikeoutDone ) + { + drawStrikeoutLine( aLine, nWidth, eStrikeout, aStrikeoutColor ); + } + + aLine.append( "Q\n" ); + writeBuffer( aLine.getStr(), aLine.getLength() ); +} + +void PDFWriterImpl::drawPolygon( const Polygon& rPoly ) +{ + MARK( "drawPolygon" ); + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) && + m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) ) + return; + + int nPoints = rPoly.GetSize(); + OStringBuffer aLine( 20 * nPoints ); + m_aPages.back().appendPolygon( rPoly, aLine ); + if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) && + m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) ) + aLine.append( "B*\n" ); + else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) ) + aLine.append( "S\n" ); + else + aLine.append( "f*\n" ); + + writeBuffer( aLine.getStr(), aLine.getLength() ); +} + +void PDFWriterImpl::drawPolyPolygon( const PolyPolygon& rPolyPoly ) +{ + MARK( "drawPolyPolygon" ); + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) && + m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) ) + return; + + int nPolygons = rPolyPoly.Count(); + + OStringBuffer aLine( 40 * nPolygons ); + m_aPages.back().appendPolyPolygon( rPolyPoly, aLine ); + if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) && + m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) ) + aLine.append( "B*\n" ); + else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) ) + aLine.append( "S\n" ); + else + aLine.append( "f*\n" ); + + writeBuffer( aLine.getStr(), aLine.getLength() ); +} + +void PDFWriterImpl::drawTransparent( const PolyPolygon& rPolyPoly, sal_uInt32 nTransparentPercent ) +{ + DBG_ASSERT( nTransparentPercent <= 100, "invalid alpha value" ); + nTransparentPercent = nTransparentPercent % 100; + + MARK( "drawTransparent" ); + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) && + m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) ) + return; + + if( m_bIsPDF_A1 || m_aContext.Version < PDFWriter::PDF_1_4 ) + { + m_aErrors.insert( m_bIsPDF_A1 ? + PDFWriter::Warning_Transparency_Omitted_PDFA : + PDFWriter::Warning_Transparency_Omitted_PDF13 ); + + drawPolyPolygon( rPolyPoly ); + return; + } + + // create XObject + m_aTransparentObjects.push_back( TransparencyEmit() ); + // FIXME: polygons with beziers may yield incorrect bound rect + m_aTransparentObjects.back().m_aBoundRect = rPolyPoly.GetBoundRect(); + // convert rectangle to default user space + m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect ); + m_aTransparentObjects.back().m_nObject = createObject(); + m_aTransparentObjects.back().m_nExtGStateObject = createObject(); + m_aTransparentObjects.back().m_fAlpha = (double)(100-nTransparentPercent) / 100.0; + m_aTransparentObjects.back().m_pContentStream = new SvMemoryStream( 256, 256 ); + // create XObject's content stream + OStringBuffer aContent( 256 ); + m_aPages.back().appendPolyPolygon( rPolyPoly, aContent ); + if( m_aCurrentPDFState.m_aLineColor != Color( COL_TRANSPARENT ) && + m_aCurrentPDFState.m_aFillColor != Color( COL_TRANSPARENT ) ) + aContent.append( " B*\n" ); + else if( m_aCurrentPDFState.m_aLineColor != Color( COL_TRANSPARENT ) ) + aContent.append( " S\n" ); + else + aContent.append( " f*\n" ); + m_aTransparentObjects.back().m_pContentStream->Write( aContent.getStr(), aContent.getLength() ); + + OStringBuffer aObjName( 16 ); + aObjName.append( "Tr" ); + aObjName.append( m_aTransparentObjects.back().m_nObject ); + OString aTrName( aObjName.makeStringAndClear() ); + aObjName.append( "EGS" ); + aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject ); + OString aExtName( aObjName.makeStringAndClear() ); + + OStringBuffer aLine( 80 ); + // insert XObject + aLine.append( "q /" ); + aLine.append( aExtName ); + aLine.append( " gs /" ); + aLine.append( aTrName ); + aLine.append( " Do Q\n" ); + writeBuffer( aLine.getStr(), aLine.getLength() ); + + pushResource( ResXObject, aTrName, m_aTransparentObjects.back().m_nObject ); + pushResource( ResExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject ); +} + +void PDFWriterImpl::pushResource( ResourceKind eKind, const OString& rResource, sal_Int32 nObject ) +{ + if( nObject >= 0 ) + { + switch( eKind ) + { + case ResXObject: + m_aGlobalResourceDict.m_aXObjects[ rResource ] = nObject; + if( ! m_aOutputStreams.empty() ) + m_aOutputStreams.front().m_aResourceDict.m_aXObjects[ rResource ] = nObject; + break; + case ResExtGState: + m_aGlobalResourceDict.m_aExtGStates[ rResource ] = nObject; + if( ! m_aOutputStreams.empty() ) + m_aOutputStreams.front().m_aResourceDict.m_aExtGStates[ rResource ] = nObject; + break; + case ResShading: + m_aGlobalResourceDict.m_aShadings[ rResource ] = nObject; + if( ! m_aOutputStreams.empty() ) + m_aOutputStreams.front().m_aResourceDict.m_aShadings[ rResource ] = nObject; + break; + case ResPattern: + m_aGlobalResourceDict.m_aPatterns[ rResource ] = nObject; + if( ! m_aOutputStreams.empty() ) + m_aOutputStreams.front().m_aResourceDict.m_aPatterns[ rResource ] = nObject; + break; + } + } +} + +void PDFWriterImpl::beginRedirect( SvStream* pStream, const Rectangle& rTargetRect ) +{ + push( PUSH_ALL ); + + // force reemitting clip region + clearClipRegion(); + updateGraphicsState(); + + m_aOutputStreams.push_front( StreamRedirect() ); + m_aOutputStreams.front().m_pStream = pStream; + m_aOutputStreams.front().m_aMapMode = m_aMapMode; + + if( !rTargetRect.IsEmpty() ) + { + m_aOutputStreams.front().m_aTargetRect = + lcl_convert( m_aGraphicsStack.front().m_aMapMode, + m_aMapMode, + getReferenceDevice(), + rTargetRect ); + Point aDelta = m_aOutputStreams.front().m_aTargetRect.BottomLeft(); + long nPageHeight = pointToPixel(m_aPages[m_nCurrentPage].getHeight()); + aDelta.Y() = -(nPageHeight - m_aOutputStreams.front().m_aTargetRect.Bottom()); + 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 ); +} + +Rectangle PDFWriterImpl::getRedirectTargetRect() const +{ + return m_aOutputStreams.empty() ? Rectangle() : m_aOutputStreams.front().m_aTargetRect; +} + +SvStream* PDFWriterImpl::endRedirect() +{ + SvStream* pStream = NULL; + if( ! m_aOutputStreams.empty() ) + { + pStream = m_aOutputStreams.front().m_pStream; + m_aMapMode = m_aOutputStreams.front().m_aMapMode; + m_aOutputStreams.pop_front(); + } + + pop(); + // force reemitting colors and clip region + clearClipRegion(); + m_aCurrentPDFState.m_bClipRegion = m_aGraphicsStack.front().m_bClipRegion; + m_aCurrentPDFState.m_aClipRegion = m_aGraphicsStack.front().m_aClipRegion; + m_aCurrentPDFState.m_aLineColor = Color( COL_TRANSPARENT ); + m_aCurrentPDFState.m_aFillColor = Color( COL_TRANSPARENT ); + + updateGraphicsState(); + + return pStream; +} + +void PDFWriterImpl::beginTransparencyGroup() +{ + updateGraphicsState(); + if( m_aContext.Version >= PDFWriter::PDF_1_4 ) + beginRedirect( new SvMemoryStream( 1024, 1024 ), Rectangle() ); +} + +void PDFWriterImpl::endTransparencyGroup( const Rectangle& rBoundingBox, sal_uInt32 nTransparentPercent ) +{ + DBG_ASSERT( nTransparentPercent <= 100, "invalid alpha value" ); + nTransparentPercent = nTransparentPercent % 100; + + if( m_aContext.Version >= PDFWriter::PDF_1_4 ) + { + // create XObject + m_aTransparentObjects.push_back( TransparencyEmit() ); + m_aTransparentObjects.back().m_aBoundRect = rBoundingBox; + // convert rectangle to default user space + m_aPages.back().convertRect( m_aTransparentObjects.back().m_aBoundRect ); + m_aTransparentObjects.back().m_nObject = createObject(); + m_aTransparentObjects.back().m_fAlpha = (double)(100-nTransparentPercent) / 100.0; + // get XObject's content stream + m_aTransparentObjects.back().m_pContentStream = static_cast<SvMemoryStream*>(endRedirect()); + m_aTransparentObjects.back().m_nExtGStateObject = createObject(); + + OStringBuffer aObjName( 16 ); + aObjName.append( "Tr" ); + aObjName.append( m_aTransparentObjects.back().m_nObject ); + OString aTrName( aObjName.makeStringAndClear() ); + aObjName.append( "EGS" ); + aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject ); + OString aExtName( aObjName.makeStringAndClear() ); + + OStringBuffer aLine( 80 ); + // insert XObject + aLine.append( "q /" ); + aLine.append( aExtName ); + aLine.append( " gs /" ); + aLine.append( aTrName ); + aLine.append( " Do Q\n" ); + writeBuffer( aLine.getStr(), aLine.getLength() ); + + pushResource( ResXObject, aTrName, m_aTransparentObjects.back().m_nObject ); + pushResource( ResExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject ); + } +} + +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 aObjName( 16 ); + aObjName.append( "Tr" ); + aObjName.append( m_aTransparentObjects.back().m_nObject ); + OString aTrName( aObjName.makeStringAndClear() ); + aObjName.append( "EGS" ); + aObjName.append( m_aTransparentObjects.back().m_nExtGStateObject ); + OString aExtName( aObjName.makeStringAndClear() ); + + OStringBuffer aLine( 80 ); + // insert XObject + aLine.append( "q /" ); + aLine.append( aExtName ); + aLine.append( " gs /" ); + aLine.append( aTrName ); + aLine.append( " Do Q\n" ); + writeBuffer( aLine.getStr(), aLine.getLength() ); + + pushResource( ResXObject, aTrName, m_aTransparentObjects.back().m_nObject ); + pushResource( ResExtGState, aExtName, m_aTransparentObjects.back().m_nExtGStateObject ); + } +} + +void PDFWriterImpl::drawRectangle( const Rectangle& rRect ) +{ + MARK( "drawRectangle" ); + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) && + m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) ) + return; + + OStringBuffer aLine( 40 ); + m_aPages.back().appendRect( rRect, aLine ); + + if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) && + m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) ) + aLine.append( " B*\n" ); + else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) ) + aLine.append( " S\n" ); + else + aLine.append( " f*\n" ); + + writeBuffer( aLine.getStr(), aLine.getLength() ); +} + +void PDFWriterImpl::drawRectangle( const Rectangle& rRect, sal_uInt32 nHorzRound, sal_uInt32 nVertRound ) +{ + MARK( "drawRectangle with rounded edges" ); + + if( !nHorzRound && !nVertRound ) + drawRectangle( rRect ); + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) && + m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) ) + return; + + if( nHorzRound > (sal_uInt32)rRect.GetWidth()/2 ) + nHorzRound = rRect.GetWidth()/2; + if( nVertRound > (sal_uInt32)rRect.GetHeight()/2 ) + nVertRound = rRect.GetHeight()/2; + + Point aPoints[16]; + const double kappa = 0.5522847498; + const sal_uInt32 kx = (sal_uInt32)((kappa*(double)nHorzRound)+0.5); + const sal_uInt32 ky = (sal_uInt32)((kappa*(double)nVertRound)+0.5); + + aPoints[1] = Point( rRect.TopLeft().X() + nHorzRound, rRect.TopLeft().Y() ); + aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() ); + aPoints[2] = Point( rRect.TopRight().X()+1 - nHorzRound, aPoints[1].Y() ); + aPoints[3] = Point( aPoints[2].X()+kx, aPoints[2].Y() ); + + aPoints[5] = Point( rRect.TopRight().X()+1, rRect.TopRight().Y()+nVertRound ); + aPoints[4] = Point( aPoints[5].X(), aPoints[5].Y()-ky ); + aPoints[6] = Point( aPoints[5].X(), rRect.BottomRight().Y()+1 - nVertRound ); + aPoints[7] = Point( aPoints[6].X(), aPoints[6].Y()+ky ); + + aPoints[9] = Point( rRect.BottomRight().X()+1-nHorzRound, rRect.BottomRight().Y()+1 ); + aPoints[8] = Point( aPoints[9].X()+kx, aPoints[9].Y() ); + aPoints[10] = Point( rRect.BottomLeft().X() + nHorzRound, aPoints[9].Y() ); + aPoints[11] = Point( aPoints[10].X()-kx, aPoints[10].Y() ); + + aPoints[13] = Point( rRect.BottomLeft().X(), rRect.BottomLeft().Y()+1-nVertRound ); + aPoints[12] = Point( aPoints[13].X(), aPoints[13].Y()+ky ); + aPoints[14] = Point( rRect.TopLeft().X(), rRect.TopLeft().Y()+nVertRound ); + aPoints[15] = Point( aPoints[14].X(), aPoints[14].Y()-ky ); + + + OStringBuffer aLine( 80 ); + m_aPages.back().appendPoint( aPoints[1], aLine ); + aLine.append( " m " ); + m_aPages.back().appendPoint( aPoints[2], aLine ); + aLine.append( " l " ); + m_aPages.back().appendPoint( aPoints[3], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[4], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[5], aLine ); + aLine.append( " c\n" ); + m_aPages.back().appendPoint( aPoints[6], aLine ); + aLine.append( " l " ); + m_aPages.back().appendPoint( aPoints[7], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[8], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[9], aLine ); + aLine.append( " c\n" ); + m_aPages.back().appendPoint( aPoints[10], aLine ); + aLine.append( " l " ); + m_aPages.back().appendPoint( aPoints[11], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[12], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[13], aLine ); + aLine.append( " c\n" ); + m_aPages.back().appendPoint( aPoints[14], aLine ); + aLine.append( " l " ); + m_aPages.back().appendPoint( aPoints[15], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[0], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[1], aLine ); + aLine.append( " c " ); + + if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) && + m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) ) + aLine.append( "b*\n" ); + else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) ) + aLine.append( "s\n" ); + else + aLine.append( "f*\n" ); + + writeBuffer( aLine.getStr(), aLine.getLength() ); +} + +void PDFWriterImpl::drawEllipse( const Rectangle& rRect ) +{ + MARK( "drawEllipse" ); + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) && + m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) ) + return; + + Point aPoints[12]; + const double kappa = 0.5522847498; + const sal_uInt32 kx = (sal_uInt32)((kappa*(double)rRect.GetWidth()/2.0)+0.5); + const sal_uInt32 ky = (sal_uInt32)((kappa*(double)rRect.GetHeight()/2.0)+0.5); + + aPoints[1] = Point( rRect.TopLeft().X() + rRect.GetWidth()/2, rRect.TopLeft().Y() ); + aPoints[0] = Point( aPoints[1].X() - kx, aPoints[1].Y() ); + aPoints[2] = Point( aPoints[1].X() + kx, aPoints[1].Y() ); + + aPoints[4] = Point( rRect.TopRight().X()+1, rRect.TopRight().Y() + rRect.GetHeight()/2 ); + aPoints[3] = Point( aPoints[4].X(), aPoints[4].Y() - ky ); + aPoints[5] = Point( aPoints[4].X(), aPoints[4].Y() + ky ); + + aPoints[7] = Point( rRect.BottomLeft().X() + rRect.GetWidth()/2, rRect.BottomLeft().Y()+1 ); + aPoints[6] = Point( aPoints[7].X() + kx, aPoints[7].Y() ); + aPoints[8] = Point( aPoints[7].X() - kx, aPoints[7].Y() ); + + aPoints[10] = Point( rRect.TopLeft().X(), rRect.TopLeft().Y() + rRect.GetHeight()/2 ); + aPoints[9] = Point( aPoints[10].X(), aPoints[10].Y() + ky ); + aPoints[11] = Point( aPoints[10].X(), aPoints[10].Y() - ky ); + + OStringBuffer aLine( 80 ); + m_aPages.back().appendPoint( aPoints[1], aLine ); + aLine.append( " m " ); + m_aPages.back().appendPoint( aPoints[2], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[3], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[4], aLine ); + aLine.append( " c\n" ); + m_aPages.back().appendPoint( aPoints[5], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[6], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[7], aLine ); + aLine.append( " c\n" ); + m_aPages.back().appendPoint( aPoints[8], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[9], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[10], aLine ); + aLine.append( " c\n" ); + m_aPages.back().appendPoint( aPoints[11], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[0], aLine ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( aPoints[1], aLine ); + aLine.append( " c " ); + + if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) && + m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) ) + aLine.append( "b*\n" ); + else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) ) + aLine.append( "s\n" ); + else + aLine.append( "f*\n" ); + + writeBuffer( aLine.getStr(), aLine.getLength() ); +} + +static double calcAngle( const Rectangle& rRect, const Point& rPoint ) +{ + Point aOrigin((rRect.Left()+rRect.Right()+1)/2, + (rRect.Top()+rRect.Bottom()+1)/2); + Point aPoint = rPoint - aOrigin; + + double fX = (double)aPoint.X(); + double fY = (double)-aPoint.Y(); + + if( rRect.GetWidth() > rRect.GetHeight() ) + fY = fY*((double)rRect.GetWidth()/(double)rRect.GetHeight()); + else if( rRect.GetHeight() > rRect.GetWidth() ) + fX = fX*((double)rRect.GetHeight()/(double)rRect.GetWidth()); + return atan2( fY, fX ); +} + +void PDFWriterImpl::drawArc( const Rectangle& rRect, const Point& rStart, const Point& rStop, bool bWithPie, bool bWithChord ) +{ + MARK( "drawArc" ); + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) && + m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) ) + return; + + // calculate start and stop angles + const double fStartAngle = calcAngle( rRect, rStart ); + double fStopAngle = calcAngle( rRect, rStop ); + while( fStopAngle < fStartAngle ) + fStopAngle += 2.0*M_PI; + const int nFragments = (int)((fStopAngle-fStartAngle)/(M_PI/2.0))+1; + const double fFragmentDelta = (fStopAngle-fStartAngle)/(double)nFragments; + const double kappa = fabs( 4.0 * (1.0-cos(fFragmentDelta/2.0))/sin(fFragmentDelta/2.0) / 3.0); + const double halfWidth = (double)rRect.GetWidth()/2.0; + const double halfHeight = (double)rRect.GetHeight()/2.0; + + const Point aCenter( (rRect.Left()+rRect.Right()+1)/2, + (rRect.Top()+rRect.Bottom()+1)/2 ); + + OStringBuffer aLine( 30*nFragments ); + Point aPoint( (int)(halfWidth * cos(fStartAngle) ), + -(int)(halfHeight * sin(fStartAngle) ) ); + aPoint += aCenter; + m_aPages.back().appendPoint( aPoint, aLine ); + aLine.append( " m " ); + if( !basegfx::fTools::equal(fStartAngle, fStopAngle) ) + { + for( int i = 0; i < nFragments; i++ ) + { + const double fStartFragment = fStartAngle + (double)i*fFragmentDelta; + const double fStopFragment = fStartFragment + fFragmentDelta; + aPoint = Point( (int)(halfWidth * (cos(fStartFragment) - kappa*sin(fStartFragment) ) ), + -(int)(halfHeight * (sin(fStartFragment) + kappa*cos(fStartFragment) ) ) ); + aPoint += aCenter; + m_aPages.back().appendPoint( aPoint, aLine ); + aLine.append( ' ' ); + + aPoint = Point( (int)(halfWidth * (cos(fStopFragment) + kappa*sin(fStopFragment) ) ), + -(int)(halfHeight * (sin(fStopFragment) - kappa*cos(fStopFragment) ) ) ); + aPoint += aCenter; + m_aPages.back().appendPoint( aPoint, aLine ); + aLine.append( ' ' ); + + aPoint = Point( (int)(halfWidth * cos(fStopFragment) ), + -(int)(halfHeight * sin(fStopFragment) ) ); + aPoint += aCenter; + m_aPages.back().appendPoint( aPoint, aLine ); + aLine.append( " c\n" ); + } + } + if( bWithChord || bWithPie ) + { + if( bWithPie ) + { + m_aPages.back().appendPoint( aCenter, aLine ); + aLine.append( " l " ); + } + aLine.append( "h " ); + } + if( ! bWithChord && ! bWithPie ) + aLine.append( "S\n" ); + else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) && + m_aGraphicsStack.front().m_aFillColor != Color( COL_TRANSPARENT ) ) + aLine.append( "B*\n" ); + else if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) ) + aLine.append( "S\n" ); + else + aLine.append( "f*\n" ); + + writeBuffer( aLine.getStr(), aLine.getLength() ); +} + +void PDFWriterImpl::drawPolyLine( const Polygon& rPoly ) +{ + MARK( "drawPolyLine" ); + + USHORT nPoints = rPoly.GetSize(); + if( nPoints < 2 ) + return; + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) ) + return; + + OStringBuffer aLine( 20 * nPoints ); + m_aPages.back().appendPolygon( rPoly, aLine, rPoly[0] == rPoly[nPoints-1] ); + aLine.append( "S\n" ); + + writeBuffer( aLine.getStr(), aLine.getLength() ); +} + +void PDFWriterImpl::drawPolyLine( const Polygon& rPoly, const LineInfo& rInfo ) +{ + MARK( "drawPolyLine with LineInfo" ); + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) ) + return; + + OStringBuffer aLine; + aLine.append( "q " ); + if( m_aPages.back().appendLineInfo( rInfo, aLine ) ) + { + writeBuffer( aLine.getStr(), aLine.getLength() ); + drawPolyLine( rPoly ); + writeBuffer( "Q\n", 2 ); + } + else + { + PDFWriter::ExtLineInfo aInfo; + convertLineInfoToExtLineInfo( rInfo, aInfo ); + drawPolyLine( rPoly, aInfo ); + } +} + +void PDFWriterImpl::convertLineInfoToExtLineInfo( const LineInfo& rIn, PDFWriter::ExtLineInfo& rOut ) +{ + DBG_ASSERT( rIn.GetStyle() == LINE_DASH, "invalid conversion" ); + rOut.m_fLineWidth = rIn.GetWidth(); + rOut.m_fTransparency = 0.0; + rOut.m_eCap = PDFWriter::capButt; + rOut.m_eJoin = PDFWriter::joinMiter; + rOut.m_fMiterLimit = 10; + rOut.m_aDashArray.clear(); + + int nDashes = rIn.GetDashCount(); + int nDashLen = rIn.GetDashLen(); + int nDistance = rIn.GetDistance(); + for( int n = 0; n < nDashes; n++ ) + { + rOut.m_aDashArray.push_back( nDashLen ); + rOut.m_aDashArray.push_back( nDistance ); + } + int nDots = rIn.GetDotCount(); + int nDotLen = rIn.GetDotLen(); + for( int n = 0; n < nDots; n++ ) + { + rOut.m_aDashArray.push_back( nDotLen ); + rOut.m_aDashArray.push_back( nDistance ); + } +} + +void PDFWriterImpl::drawPolyLine( const Polygon& rPoly, const PDFWriter::ExtLineInfo& rInfo ) +{ + MARK( "drawPolyLine with ExtLineInfo" ); + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) ) + return; + + if( rInfo.m_fTransparency >= 1.0 ) + return; + + if( rInfo.m_fTransparency != 0.0 ) + beginTransparencyGroup(); + + OStringBuffer aLine; + aLine.append( "q " ); + m_aPages.back().appendMappedLength( rInfo.m_fLineWidth, aLine ); + aLine.append( " w" ); + if( rInfo.m_aDashArray.size() < 10 ) // implmentation limit of acrobat reader + { + switch( rInfo.m_eCap ) + { + default: + case PDFWriter::capButt: aLine.append( " 0 J" );break; + case PDFWriter::capRound: aLine.append( " 1 J" );break; + case PDFWriter::capSquare: aLine.append( " 2 J" );break; + } + switch( rInfo.m_eJoin ) + { + default: + case PDFWriter::joinMiter: + { + double fLimit = rInfo.m_fMiterLimit; + if( rInfo.m_fLineWidth < rInfo.m_fMiterLimit ) + fLimit = fLimit / rInfo.m_fLineWidth; + if( fLimit < 1.0 ) + fLimit = 1.0; + aLine.append( " 0 j " ); + appendDouble( fLimit, aLine ); + aLine.append( " M" ); + } + break; + case PDFWriter::joinRound: aLine.append( " 1 j" );break; + case PDFWriter::joinBevel: aLine.append( " 2 j" );break; + } + if( rInfo.m_aDashArray.size() > 0 ) + { + aLine.append( " [ " ); + for( std::vector<double>::const_iterator it = rInfo.m_aDashArray.begin(); + it != rInfo.m_aDashArray.end(); ++it ) + { + m_aPages.back().appendMappedLength( *it, aLine ); + aLine.append( ' ' ); + } + aLine.append( "] 0 d" ); + } + aLine.append( "\n" ); + writeBuffer( aLine.getStr(), aLine.getLength() ); + drawPolyLine( rPoly ); + } + else + { + basegfx::B2DPolygon aPoly(rPoly.getB2DPolygon()); + basegfx::B2DPolyPolygon aPolyPoly; + + basegfx::tools::applyLineDashing(aPoly, rInfo.m_aDashArray, &aPolyPoly); + + // Old applyLineDashing subdivided the polygon. New one will create bezier curve segments. + // To mimic old behaviour, apply subdivide here. If beziers shall be written (better quality) + // this line needs to be removed and the loop below adapted accordingly + aPolyPoly = basegfx::tools::adaptiveSubdivideByAngle(aPolyPoly); + + const sal_uInt32 nPolygonCount(aPolyPoly.count()); + + for( sal_uInt32 nPoly = 0; nPoly < nPolygonCount; nPoly++ ) + { + aLine.append( (nPoly != 0 && (nPoly & 7) == 0) ? "\n" : " " ); + aPoly = aPolyPoly.getB2DPolygon( nPoly ); + const sal_uInt32 nPointCount(aPoly.count()); + + if(nPointCount) + { + const sal_uInt32 nEdgeCount(aPoly.isClosed() ? nPointCount : nPointCount - 1); + basegfx::B2DPoint aCurrent(aPoly.getB2DPoint(0)); + + for(sal_uInt32 a(0); a < nEdgeCount; a++) + { + if( a > 0 ) + aLine.append( " " ); + const sal_uInt32 nNextIndex((a + 1) % nPointCount); + const basegfx::B2DPoint aNext(aPoly.getB2DPoint(nNextIndex)); + + m_aPages.back().appendPoint( Point( FRound(aCurrent.getX()), + FRound(aCurrent.getY()) ), + aLine ); + aLine.append( " m " ); + m_aPages.back().appendPoint( Point( FRound(aNext.getX()), + FRound(aNext.getY()) ), + aLine ); + aLine.append( " l" ); + + // prepare next edge + aCurrent = aNext; + } + } + } + aLine.append( " S " ); + writeBuffer( aLine.getStr(), aLine.getLength() ); + } + writeBuffer( "Q\n", 2 ); + + if( rInfo.m_fTransparency != 0.0 ) + { + // FIXME: actually this may be incorrect with bezier polygons + Rectangle aBoundRect( rPoly.GetBoundRect() ); + // avoid clipping with thick lines + if( rInfo.m_fLineWidth > 0.0 ) + { + sal_Int32 nLW = sal_Int32(rInfo.m_fLineWidth); + aBoundRect.Top() -= nLW; + aBoundRect.Left() -= nLW; + aBoundRect.Right() += nLW; + aBoundRect.Bottom() += nLW; + } + endTransparencyGroup( aBoundRect, (USHORT)(100.0*rInfo.m_fTransparency) ); + } +} + +void PDFWriterImpl::drawPixel( const Point& rPoint, const Color& rColor ) +{ + MARK( "drawPixel" ); + + Color aColor = ( rColor == Color( COL_TRANSPARENT ) ? m_aGraphicsStack.front().m_aLineColor : rColor ); + + if( aColor == Color( COL_TRANSPARENT ) ) + return; + + // pixels are drawn in line color, so have to set + // the nonstroking color to line color + Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor; + setFillColor( aColor ); + + updateGraphicsState(); + + OStringBuffer aLine( 20 ); + m_aPages.back().appendPoint( rPoint, aLine ); + aLine.append( ' ' ); + appendDouble( 1.0/double(getReferenceDevice()->ImplGetDPIX()), aLine ); + aLine.append( ' ' ); + appendDouble( 1.0/double(getReferenceDevice()->ImplGetDPIY()), aLine ); + aLine.append( " re f\n" ); + writeBuffer( aLine.getStr(), aLine.getLength() ); + + setFillColor( aOldFillColor ); +} + +void PDFWriterImpl::drawPixel( const Polygon& rPoints, const Color* pColors ) +{ + MARK( "drawPixel with Polygon" ); + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) && ! pColors ) + return; + + USHORT nPoints = rPoints.GetSize(); + OStringBuffer aLine( nPoints*40 ); + aLine.append( "q " ); + if( ! pColors ) + { + appendNonStrokingColor( m_aGraphicsStack.front().m_aLineColor, aLine ); + aLine.append( ' ' ); + } + + OStringBuffer aPixel(32); + aPixel.append( ' ' ); + appendDouble( 1.0/double(getReferenceDevice()->ImplGetDPIX()), aPixel ); + aPixel.append( ' ' ); + appendDouble( 1.0/double(getReferenceDevice()->ImplGetDPIY()), aPixel ); + OString aPixelStr = aPixel.makeStringAndClear(); + for( USHORT i = 0; i < nPoints; i++ ) + { + if( pColors ) + { + if( pColors[i] == Color( COL_TRANSPARENT ) ) + continue; + + appendNonStrokingColor( pColors[i], aLine ); + aLine.append( ' ' ); + } + m_aPages.back().appendPoint( rPoints[i], aLine ); + aLine.append( aPixelStr ); + aLine.append( " re f\n" ); + } + aLine.append( "Q\n" ); + writeBuffer( aLine.getStr(), aLine.getLength() ); +} + +class AccessReleaser +{ + BitmapReadAccess* m_pAccess; +public: + AccessReleaser( BitmapReadAccess* pAccess ) : m_pAccess( pAccess ){} + ~AccessReleaser() { delete m_pAccess; } +}; + +bool PDFWriterImpl::writeTransparentObject( TransparencyEmit& rObject ) +{ + CHECK_RETURN( updateObject( rObject.m_nObject ) ); + + bool bFlateFilter = compressStream( rObject.m_pContentStream ); + rObject.m_pContentStream->Seek( STREAM_SEEK_TO_END ); + ULONG nSize = rObject.m_pContentStream->Tell(); + rObject.m_pContentStream->Seek( STREAM_SEEK_TO_BEGIN ); +#if OSL_DEBUG_LEVEL > 1 + { + OStringBuffer aLine( " PDFWriterImpl::writeTransparentObject" ); + emitComment( aLine.getStr() ); + } +#endif + OStringBuffer aLine( 512 ); + CHECK_RETURN( updateObject( rObject.m_nObject ) ); + aLine.append( rObject.m_nObject ); + aLine.append( " 0 obj\n" + "<</Type/XObject\n" + "/Subtype/Form\n" + "/BBox[ " ); + appendFixedInt( rObject.m_aBoundRect.Left(), aLine ); + aLine.append( ' ' ); + appendFixedInt( rObject.m_aBoundRect.Top(), aLine ); + aLine.append( ' ' ); + appendFixedInt( rObject.m_aBoundRect.Right(), aLine ); + aLine.append( ' ' ); + appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aLine ); + aLine.append( " ]\n" ); + if( ! rObject.m_pSoftMaskStream ) + { + if( ! m_bIsPDF_A1 ) + { + aLine.append( "/Group<</S/Transparency/CS/DeviceRGB/K true>>\n" ); + } + } + /* #i42884# the PDF reference recommends that each Form XObject + * should have a resource dict; alas if that is the same object + * as the one of the page it triggers an endless recursion in + * acroread 5 (6 and up have that fixed). Since we have only one + * resource dict anyway, let's use the one from the page by NOT + * emitting a Resources entry. + */ + #if 0 + aLine.append( " /Resources " ); + aLine.append( getResourceDictObj() ); + aLine.append( " 0 R\n" ); + #endif + + aLine.append( "/Length " ); + aLine.append( (sal_Int32)(nSize) ); + aLine.append( "\n" ); + if( bFlateFilter ) + aLine.append( "/Filter/FlateDecode\n" ); + aLine.append( ">>\n" + "stream\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + checkAndEnableStreamEncryption( rObject.m_nObject ); + CHECK_RETURN( writeBuffer( rObject.m_pContentStream->GetData(), nSize ) ); + disableStreamEncryption(); + aLine.setLength( 0 ); + aLine.append( "\n" + "endstream\n" + "endobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + + // write ExtGState dict for this XObject + aLine.setLength( 0 ); + aLine.append( rObject.m_nExtGStateObject ); + aLine.append( " 0 obj\n" + "<<" ); + if( ! rObject.m_pSoftMaskStream ) + { +//i59651 + if( m_bIsPDF_A1 ) + { + aLine.append( "/CA 1.0/ca 1.0" ); + m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA ); + } + else + { + aLine.append( "/CA " ); + appendDouble( rObject.m_fAlpha, aLine ); + aLine.append( "\n" + " /ca " ); + appendDouble( rObject.m_fAlpha, aLine ); + } + aLine.append( "\n" ); + } + else + { + if( m_bIsPDF_A1 ) + { + aLine.append( "/SMask/None" ); + m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA ); + } + else + { + rObject.m_pSoftMaskStream->Seek( STREAM_SEEK_TO_END ); + sal_Int32 nMaskSize = (sal_Int32)rObject.m_pSoftMaskStream->Tell(); + rObject.m_pSoftMaskStream->Seek( STREAM_SEEK_TO_BEGIN ); + sal_Int32 nMaskObject = createObject(); + aLine.append( "/SMask<</Type/Mask/S/Luminosity/G " ); + aLine.append( nMaskObject ); + aLine.append( " 0 R>>\n" ); + + OStringBuffer aMask; + aMask.append( nMaskObject ); + aMask.append( " 0 obj\n" + "<</Type/XObject\n" + "/Subtype/Form\n" + "/BBox[" ); + appendFixedInt( rObject.m_aBoundRect.Left(), aMask ); + aMask.append( ' ' ); + appendFixedInt( rObject.m_aBoundRect.Top(), aMask ); + aMask.append( ' ' ); + appendFixedInt( rObject.m_aBoundRect.Right(), aMask ); + aMask.append( ' ' ); + appendFixedInt( rObject.m_aBoundRect.Bottom()+1, aMask ); + aMask.append( "]\n" ); + + /* #i42884# see above */ +#if 0 + aLine.append( "/Resources " ); + aMask.append( getResourceDictObj() ); + aMask.append( " 0 R\n" ); +#endif + + aMask.append( "/Group<</S/Transparency/CS/DeviceRGB>>\n" ); + aMask.append( "/Length " ); + aMask.append( nMaskSize ); + aMask.append( ">>\n" + "stream\n" ); + CHECK_RETURN( updateObject( nMaskObject ) ); + checkAndEnableStreamEncryption( nMaskObject ); + CHECK_RETURN( writeBuffer( aMask.getStr(), aMask.getLength() ) ); + CHECK_RETURN( writeBuffer( rObject.m_pSoftMaskStream->GetData(), nMaskSize ) ); + disableStreamEncryption(); + aMask.setLength( 0 ); + aMask.append( "\nendstream\n" + "endobj\n\n" ); + CHECK_RETURN( writeBuffer( aMask.getStr(), aMask.getLength() ) ); + } + } + aLine.append( ">>\n" + "endobj\n\n" ); + CHECK_RETURN( updateObject( rObject.m_nExtGStateObject ) ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + + return true; +} + +bool PDFWriterImpl::writeGradientFunction( GradientEmit& rObject ) +{ + sal_Int32 nFunctionObject = createObject(); + CHECK_RETURN( updateObject( nFunctionObject ) ); + + OutputDevice* pRefDevice = getReferenceDevice(); + pRefDevice->Push( PUSH_ALL ); + if( rObject.m_aSize.Width() > pRefDevice->GetOutputSizePixel().Width() ) + rObject.m_aSize.Width() = pRefDevice->GetOutputSizePixel().Width(); + if( rObject.m_aSize.Height() > pRefDevice->GetOutputSizePixel().Height() ) + rObject.m_aSize.Height() = pRefDevice->GetOutputSizePixel().Height(); + pRefDevice->SetMapMode( MapMode( MAP_PIXEL ) ); + pRefDevice->DrawGradient( Rectangle( Point( 0, 0 ), rObject.m_aSize ), rObject.m_aGradient ); + + Bitmap aSample = pRefDevice->GetBitmap( Point( 0, 0 ), rObject.m_aSize ); + BitmapReadAccess* pAccess = aSample.AcquireReadAccess(); + AccessReleaser aReleaser( pAccess ); + + Size aSize = aSample.GetSizePixel(); + + sal_Int32 nStreamLengthObject = createObject(); +#if OSL_DEBUG_LEVEL > 1 + { + OStringBuffer aLine( " PDFWriterImpl::writeGradientFunction" ); + emitComment( aLine.getStr() ); + } +#endif + OStringBuffer aLine( 120 ); + aLine.append( nFunctionObject ); + aLine.append( " 0 obj\n" + "<</FunctionType 0\n" + "/Domain[ 0 1 0 1 ]\n" + "/Size[ " ); + aLine.append( (sal_Int32)aSize.Width() ); + aLine.append( ' ' ); + aLine.append( (sal_Int32)aSize.Height() ); + aLine.append( " ]\n" + "/BitsPerSample 8\n" + "/Range[ 0 1 0 1 0 1 ]\n" + "/Length " ); + aLine.append( nStreamLengthObject ); + aLine.append( " 0 R\n" +#ifndef DEBUG_DISABLE_PDFCOMPRESSION + "/Filter/FlateDecode" +#endif + ">>\n" + "stream\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + + sal_uInt64 nStartStreamPos = 0; + CHECK_RETURN( (osl_File_E_None == osl_getFilePos( m_aFile, &nStartStreamPos )) ); + + checkAndEnableStreamEncryption( nFunctionObject ); + beginCompression(); + for( int y = 0; y < aSize.Height(); y++ ) + { + for( int x = 0; x < aSize.Width(); x++ ) + { + sal_uInt8 aCol[3]; + BitmapColor aColor = pAccess->GetColor( y, x ); + aCol[0] = aColor.GetRed(); + aCol[1] = aColor.GetGreen(); + aCol[2] = aColor.GetBlue(); + CHECK_RETURN( writeBuffer( aCol, 3 ) ); + } + } + endCompression(); + disableStreamEncryption(); + + sal_uInt64 nEndStreamPos = 0; + CHECK_RETURN( (osl_File_E_None == osl_getFilePos( m_aFile, &nEndStreamPos )) ); + + aLine.setLength( 0 ); + aLine.append( "\nendstream\nendobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + + // write stream length + CHECK_RETURN( updateObject( nStreamLengthObject ) ); + aLine.setLength( 0 ); + aLine.append( nStreamLengthObject ); + aLine.append( " 0 obj\n" ); + aLine.append( (sal_Int64)(nEndStreamPos-nStartStreamPos) ); + aLine.append( "\nendobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + + CHECK_RETURN( updateObject( rObject.m_nObject ) ); + aLine.setLength( 0 ); + aLine.append( rObject.m_nObject ); + aLine.append( " 0 obj\n" + "<</ShadingType 1\n" + "/ColorSpace/DeviceRGB\n" + "/AntiAlias true\n" + "/Domain[ 0 1 0 1 ]\n" + "/Matrix[ " ); + aLine.append( (sal_Int32)aSize.Width() ); + aLine.append( " 0 0 " ); + aLine.append( (sal_Int32)aSize.Height() ); + aLine.append( " 0 0 ]\n" + "/Function " ); + aLine.append( nFunctionObject ); + aLine.append( " 0 R\n" + ">>\n" + "endobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + + pRefDevice->Pop(); + + return true; +} + +bool PDFWriterImpl::writeJPG( JPGEmit& rObject ) +{ + CHECK_RETURN( rObject.m_pStream ); + CHECK_RETURN( updateObject( rObject.m_nObject ) ); + + sal_Int32 nLength = 0; + rObject.m_pStream->Seek( STREAM_SEEK_TO_END ); + nLength = rObject.m_pStream->Tell(); + rObject.m_pStream->Seek( STREAM_SEEK_TO_BEGIN ); + + sal_Int32 nMaskObject = 0; + if( !!rObject.m_aMask ) + { + if( rObject.m_aMask.GetBitCount() == 1 || + ( rObject.m_aMask.GetBitCount() == 8 && m_aContext.Version >= PDFWriter::PDF_1_4 && !m_bIsPDF_A1 )//i59651 + ) + { + nMaskObject = createObject(); + } + else if( m_bIsPDF_A1 ) + m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA ); + else if( m_aContext.Version < PDFWriter::PDF_1_4 ) + m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDF13 ); + + } +#if OSL_DEBUG_LEVEL > 1 + { + OStringBuffer aLine( " PDFWriterImpl::writeJPG" ); + emitComment( aLine.getStr() ); + } +#endif + + OStringBuffer aLine(200); + aLine.append( rObject.m_nObject ); + aLine.append( " 0 obj\n" + "<</Type/XObject/Subtype/Image/Width " ); + aLine.append( (sal_Int32)rObject.m_aID.m_aPixelSize.Width() ); + aLine.append( " /Height " ); + aLine.append( (sal_Int32)rObject.m_aID.m_aPixelSize.Height() ); + aLine.append( " /BitsPerComponent 8 " ); + if( rObject.m_bTrueColor ) + aLine.append( "/ColorSpace/DeviceRGB" ); + else + aLine.append( "/ColorSpace/DeviceGray" ); + aLine.append( "/Filter/DCTDecode/Length " ); + aLine.append( nLength ); + if( nMaskObject ) + { + aLine.append( rObject.m_aMask.GetBitCount() == 1 ? " /Mask " : " /SMask " ); + aLine.append( nMaskObject ); + aLine.append( " 0 R " ); + } + aLine.append( ">>\nstream\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + + checkAndEnableStreamEncryption( rObject.m_nObject ); + CHECK_RETURN( writeBuffer( rObject.m_pStream->GetData(), nLength ) ); + disableStreamEncryption(); + + aLine.setLength( 0 ); + aLine.append( "\nendstream\nendobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + + if( nMaskObject ) + { + BitmapEmit aEmit; + aEmit.m_nObject = nMaskObject; + if( rObject.m_aMask.GetBitCount() == 1 ) + aEmit.m_aBitmap = BitmapEx( rObject.m_aMask, rObject.m_aMask ); + else if( rObject.m_aMask.GetBitCount() == 8 ) + aEmit.m_aBitmap = BitmapEx( rObject.m_aMask, AlphaMask( rObject.m_aMask ) ); + writeBitmapObject( aEmit, true ); + } + + return true; +} + +bool PDFWriterImpl::writeBitmapObject( BitmapEmit& rObject, bool bMask ) +{ + CHECK_RETURN( updateObject( rObject.m_nObject ) ); + + Bitmap aBitmap; + Color aTransparentColor( COL_TRANSPARENT ); + bool bWriteMask = false; + if( ! bMask ) + { + aBitmap = rObject.m_aBitmap.GetBitmap(); + if( rObject.m_aBitmap.IsAlpha() ) + { + if( m_aContext.Version >= PDFWriter::PDF_1_4 ) + bWriteMask = true; + // else draw without alpha channel + } + else + { + switch( rObject.m_aBitmap.GetTransparentType() ) + { + case TRANSPARENT_NONE: + // comes from drawMask function + if( aBitmap.GetBitCount() == 1 && rObject.m_bDrawMask ) + bMask = true; + break; + case TRANSPARENT_COLOR: + aTransparentColor = rObject.m_aBitmap.GetTransparentColor(); + break; + case TRANSPARENT_BITMAP: + bWriteMask = true; + break; + } + } + } + else + { + if( m_aContext.Version < PDFWriter::PDF_1_4 || ! rObject.m_aBitmap.IsAlpha() ) + { + aBitmap = rObject.m_aBitmap.GetMask(); + aBitmap.Convert( BMP_CONVERSION_1BIT_THRESHOLD ); + DBG_ASSERT( aBitmap.GetBitCount() == 1, "mask conversion failed" ); + } + else if( aBitmap.GetBitCount() != 8 ) + { + aBitmap = rObject.m_aBitmap.GetAlpha().GetBitmap(); + aBitmap.Convert( BMP_CONVERSION_8BIT_GREYS ); + DBG_ASSERT( aBitmap.GetBitCount() == 8, "alpha mask conversion failed" ); + } + } + + BitmapReadAccess* pAccess = aBitmap.AcquireReadAccess(); + AccessReleaser aReleaser( pAccess ); + + bool bTrueColor; + sal_Int32 nBitsPerComponent; + switch( aBitmap.GetBitCount() ) + { + case 1: + case 2: + case 4: + case 8: + bTrueColor = false; + nBitsPerComponent = aBitmap.GetBitCount(); + break; + default: + bTrueColor = true; + nBitsPerComponent = 8; + break; + } + + sal_Int32 nStreamLengthObject = createObject(); + sal_Int32 nMaskObject = 0; + +#if OSL_DEBUG_LEVEL > 1 + { + OStringBuffer aLine( " PDFWriterImpl::writeBitmapObject" ); + emitComment( aLine.getStr() ); + } +#endif + OStringBuffer aLine(1024); + aLine.append( rObject.m_nObject ); + aLine.append( " 0 obj\n" + "<</Type/XObject/Subtype/Image/Width " ); + aLine.append( (sal_Int32)aBitmap.GetSizePixel().Width() ); + aLine.append( " /Height " ); + aLine.append( (sal_Int32)aBitmap.GetSizePixel().Height() ); + aLine.append( " /BitsPerComponent " ); + aLine.append( nBitsPerComponent ); + aLine.append( " /Length " ); + aLine.append( nStreamLengthObject ); + aLine.append( " 0 R\n" ); +#ifndef DEBUG_DISABLE_PDFCOMPRESSION + aLine.append( "/Filter/FlateDecode" ); +#endif + if( ! bMask ) + { + aLine.append( "/ColorSpace" ); + if( bTrueColor ) + aLine.append( "/DeviceRGB\n" ); + else if( aBitmap.HasGreyPalette() ) + { + aLine.append( "/DeviceGray\n" ); + if( aBitmap.GetBitCount() == 1 ) + { + // #i47395# 1 bit bitmaps occasionally have an inverted grey palette + sal_Int32 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( Color( COL_BLACK ) ) ); + DBG_ASSERT( nBlackIndex == 0 || nBlackIndex == 1, "wrong black index" ); + if( nBlackIndex == 1 ) + aLine.append( "/Decode[1 0]\n" ); + } + } + else + { + aLine.append( "[ /Indexed/DeviceRGB " ); + aLine.append( (sal_Int32)(pAccess->GetPaletteEntryCount()-1) ); + aLine.append( "\n<" ); + if( m_aContext.Encrypt ) + { + enableStringEncryption( rObject.m_nObject ); + //check encryption buffer size + if( checkEncryptionBufferSize( pAccess->GetPaletteEntryCount()*3 ) ) + { + int nChar = 0; + //fill the encryption buffer + for( USHORT i = 0; i < pAccess->GetPaletteEntryCount(); i++ ) + { + const BitmapColor& rColor = pAccess->GetPaletteColor( i ); + m_pEncryptionBuffer[nChar++] = rColor.GetRed(); + m_pEncryptionBuffer[nChar++] = rColor.GetGreen(); + m_pEncryptionBuffer[nChar++] = rColor.GetBlue(); + } + //encrypt the colorspace lookup table + rtl_cipher_encodeARCFOUR( m_aCipher, m_pEncryptionBuffer, nChar, m_pEncryptionBuffer, nChar ); + //now queue the data for output + nChar = 0; + for( USHORT i = 0; i < pAccess->GetPaletteEntryCount(); i++ ) + { + appendHex(m_pEncryptionBuffer[nChar++], aLine ); + appendHex(m_pEncryptionBuffer[nChar++], aLine ); + appendHex(m_pEncryptionBuffer[nChar++], aLine ); + } + } + } + else //no encryption requested (PDF/A-1a program flow drops here) + { + for( USHORT i = 0; i < pAccess->GetPaletteEntryCount(); i++ ) + { + const BitmapColor& rColor = pAccess->GetPaletteColor( i ); + appendHex( rColor.GetRed(), aLine ); + appendHex( rColor.GetGreen(), aLine ); + appendHex( rColor.GetBlue(), aLine ); + } + } + aLine.append( ">\n]\n" ); + } + } + else + { + if( aBitmap.GetBitCount() == 1 ) + { + aLine.append( " /ImageMask true\n" ); + sal_Int32 nBlackIndex = pAccess->GetBestPaletteIndex( BitmapColor( Color( COL_BLACK ) ) ); + DBG_ASSERT( nBlackIndex == 0 || nBlackIndex == 1, "wrong black index" ); + if( nBlackIndex ) + aLine.append( "/Decode[ 1 0 ]\n" ); + else + aLine.append( "/Decode[ 0 1 ]\n" ); + } + else if( aBitmap.GetBitCount() == 8 ) + { + aLine.append( "/ColorSpace/DeviceGray\n" + "/Decode [ 1 0 ]\n" ); + } + } + + if( ! bMask && m_aContext.Version > PDFWriter::PDF_1_2 && !m_bIsPDF_A1 )//i59651 + { + if( bWriteMask ) + { + nMaskObject = createObject(); + if( rObject.m_aBitmap.IsAlpha() && m_aContext.Version > PDFWriter::PDF_1_3 ) + aLine.append( "/SMask " ); + else + aLine.append( "/Mask " ); + aLine.append( nMaskObject ); + aLine.append( " 0 R\n" ); + } + else if( aTransparentColor != Color( COL_TRANSPARENT ) ) + { + aLine.append( "/Mask[ " ); + if( bTrueColor ) + { + aLine.append( (sal_Int32)aTransparentColor.GetRed() ); + aLine.append( ' ' ); + aLine.append( (sal_Int32)aTransparentColor.GetRed() ); + aLine.append( ' ' ); + aLine.append( (sal_Int32)aTransparentColor.GetGreen() ); + aLine.append( ' ' ); + aLine.append( (sal_Int32)aTransparentColor.GetGreen() ); + aLine.append( ' ' ); + aLine.append( (sal_Int32)aTransparentColor.GetBlue() ); + aLine.append( ' ' ); + aLine.append( (sal_Int32)aTransparentColor.GetBlue() ); + } + else + { + sal_Int32 nIndex = pAccess->GetBestPaletteIndex( BitmapColor( aTransparentColor ) ); + aLine.append( nIndex ); + } + aLine.append( " ]\n" ); + } + } + else if( m_bIsPDF_A1 && (bWriteMask || aTransparentColor != Color( COL_TRANSPARENT )) ) + m_aErrors.insert( PDFWriter::Warning_Transparency_Omitted_PDFA ); + + aLine.append( ">>\n" + "stream\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + sal_uInt64 nStartPos = 0; + CHECK_RETURN( (osl_File_E_None == osl_getFilePos( m_aFile, &nStartPos )) ); + + checkAndEnableStreamEncryption( rObject.m_nObject ); + beginCompression(); + if( ! bTrueColor || pAccess->GetScanlineFormat() == BMP_FORMAT_24BIT_TC_RGB ) + { + const int nScanLineBytes = 1 + ( pAccess->GetBitCount() * ( pAccess->Width() - 1 ) / 8U ); + + for( int i = 0; i < pAccess->Height(); i++ ) + { + CHECK_RETURN( writeBuffer( pAccess->GetScanline( i ), nScanLineBytes ) ); + } + } + else + { + const int nScanLineBytes = pAccess->Width()*3; + boost::shared_array<sal_uInt8> pCol( new sal_uInt8[ nScanLineBytes ] ); + for( int y = 0; y < pAccess->Height(); y++ ) + { + for( int x = 0; x < pAccess->Width(); x++ ) + { + BitmapColor aColor = pAccess->GetColor( y, x ); + pCol[3*x+0] = aColor.GetRed(); + pCol[3*x+1] = aColor.GetGreen(); + pCol[3*x+2] = aColor.GetBlue(); + } + CHECK_RETURN( writeBuffer( pCol.get(), nScanLineBytes ) ); + } + } + endCompression(); + disableStreamEncryption(); + + sal_uInt64 nEndPos = 0; + CHECK_RETURN( (osl_File_E_None == osl_getFilePos( m_aFile, &nEndPos )) ); + aLine.setLength( 0 ); + aLine.append( "\nendstream\nendobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + CHECK_RETURN( updateObject( nStreamLengthObject ) ); + aLine.setLength( 0 ); + aLine.append( nStreamLengthObject ); + aLine.append( " 0 obj\n" ); + aLine.append( (sal_Int64)(nEndPos-nStartPos) ); + aLine.append( "\nendobj\n\n" ); + CHECK_RETURN( writeBuffer( aLine.getStr(), aLine.getLength() ) ); + + if( nMaskObject ) + { + BitmapEmit aEmit; + aEmit.m_nObject = nMaskObject; + aEmit.m_aBitmap = rObject.m_aBitmap; + return writeBitmapObject( aEmit, true ); + } + + return true; +} + +void PDFWriterImpl::drawJPGBitmap( SvStream& rDCTData, bool bIsTrueColor, const Size& rSizePixel, const Rectangle& rTargetArea, const Bitmap& rMask ) +{ + MARK( "drawJPGBitmap" ); + + OStringBuffer aLine( 80 ); + updateGraphicsState(); + + // #i40055# sanity check + if( ! (rTargetArea.GetWidth() && rTargetArea.GetHeight() ) ) + return; + if( ! (rSizePixel.Width() && rSizePixel.Height()) ) + return; + + SvMemoryStream* pStream = new SvMemoryStream; + rDCTData.Seek( 0 ); + *pStream << rDCTData; + pStream->Seek( STREAM_SEEK_TO_END ); + + BitmapID aID; + aID.m_aPixelSize = rSizePixel; + aID.m_nSize = pStream->Tell(); + pStream->Seek( STREAM_SEEK_TO_BEGIN ); + aID.m_nChecksum = rtl_crc32( 0, pStream->GetData(), aID.m_nSize ); + if( ! rMask.IsEmpty() ) + aID.m_nMaskChecksum = rMask.GetChecksum(); + + std::list< JPGEmit >::const_iterator it; + for( it = m_aJPGs.begin(); it != m_aJPGs.end() && ! (aID == it->m_aID); ++it ) + ; + if( it == m_aJPGs.end() ) + { + m_aJPGs.push_front( JPGEmit() ); + JPGEmit& rEmit = m_aJPGs.front(); + rEmit.m_nObject = createObject(); + rEmit.m_aID = aID; + rEmit.m_pStream = pStream; + rEmit.m_bTrueColor = bIsTrueColor; + if( !! rMask && rMask.GetSizePixel() == rSizePixel ) + rEmit.m_aMask = rMask; + + it = m_aJPGs.begin(); + } + else + delete pStream; + + aLine.append( "q " ); + sal_Int32 nCheckWidth = 0; + m_aPages.back().appendMappedLength( (sal_Int32)rTargetArea.GetWidth(), aLine, false, &nCheckWidth ); + aLine.append( " 0 0 " ); + sal_Int32 nCheckHeight = 0; + m_aPages.back().appendMappedLength( (sal_Int32)rTargetArea.GetHeight(), aLine, true, &nCheckHeight ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( rTargetArea.BottomLeft(), aLine ); + aLine.append( " cm\n/Im" ); + aLine.append( it->m_nObject ); + aLine.append( " Do Q\n" ); + if( nCheckWidth == 0 || nCheckHeight == 0 ) + { + // #i97512# avoid invalid current matrix + aLine.setLength( 0 ); + aLine.append( "\n%jpeg image /Im" ); + aLine.append( it->m_nObject ); + aLine.append( " scaled to zero size, omitted\n" ); + } + writeBuffer( aLine.getStr(), aLine.getLength() ); + + OStringBuffer aObjName( 16 ); + aObjName.append( "Im" ); + aObjName.append( it->m_nObject ); + pushResource( ResXObject, aObjName.makeStringAndClear(), it->m_nObject ); + +} + +void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEmit& rBitmap, const Color& rFillColor ) +{ + OStringBuffer aLine( 80 ); + updateGraphicsState(); + + aLine.append( "q " ); + if( rFillColor != Color( COL_TRANSPARENT ) ) + { + appendNonStrokingColor( rFillColor, aLine ); + aLine.append( ' ' ); + } + sal_Int32 nCheckWidth = 0; + m_aPages.back().appendMappedLength( (sal_Int32)rDestSize.Width(), aLine, false, &nCheckWidth ); + aLine.append( " 0 0 " ); + sal_Int32 nCheckHeight = 0; + m_aPages.back().appendMappedLength( (sal_Int32)rDestSize.Height(), aLine, true, &nCheckHeight ); + aLine.append( ' ' ); + m_aPages.back().appendPoint( rDestPoint + Point( 0, rDestSize.Height()-1 ), aLine ); + aLine.append( " cm\n/Im" ); + aLine.append( rBitmap.m_nObject ); + aLine.append( " Do Q\n" ); + if( nCheckWidth == 0 || nCheckHeight == 0 ) + { + // #i97512# avoid invalid current matrix + aLine.setLength( 0 ); + aLine.append( "\n%bitmap image /Im" ); + aLine.append( rBitmap.m_nObject ); + aLine.append( " scaled to zero size, omitted\n" ); + } + writeBuffer( aLine.getStr(), aLine.getLength() ); +} + +const PDFWriterImpl::BitmapEmit& PDFWriterImpl::createBitmapEmit( const BitmapEx& rBitmap, bool bDrawMask ) +{ + BitmapID aID; + aID.m_aPixelSize = rBitmap.GetSizePixel(); + aID.m_nSize = rBitmap.GetBitCount(); + aID.m_nChecksum = rBitmap.GetBitmap().GetChecksum(); + aID.m_nMaskChecksum = 0; + if( rBitmap.IsAlpha() ) + aID.m_nMaskChecksum = rBitmap.GetAlpha().GetChecksum(); + else + { + Bitmap aMask = rBitmap.GetMask(); + if( ! aMask.IsEmpty() ) + aID.m_nMaskChecksum = aMask.GetChecksum(); + } + std::list< BitmapEmit >::const_iterator it; + for( it = m_aBitmaps.begin(); it != m_aBitmaps.end(); ++it ) + { + if( aID == it->m_aID ) + break; + } + if( it == m_aBitmaps.end() ) + { + m_aBitmaps.push_front( BitmapEmit() ); + m_aBitmaps.front().m_aID = aID; + m_aBitmaps.front().m_aBitmap = rBitmap; + m_aBitmaps.front().m_nObject = createObject(); + m_aBitmaps.front().m_bDrawMask = bDrawMask; + it = m_aBitmaps.begin(); + } + + OStringBuffer aObjName( 16 ); + aObjName.append( "Im" ); + aObjName.append( it->m_nObject ); + pushResource( ResXObject, aObjName.makeStringAndClear(), it->m_nObject ); + + return *it; +} + +void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap ) +{ + MARK( "drawBitmap (Bitmap)" ); + + // #i40055# sanity check + if( ! (rDestSize.Width() && rDestSize.Height()) ) + return; + + const BitmapEmit& rEmit = createBitmapEmit( BitmapEx( rBitmap ) ); + drawBitmap( rDestPoint, rDestSize, rEmit, Color( COL_TRANSPARENT ) ); +} + +void PDFWriterImpl::drawBitmap( const Point& rDestPoint, const Size& rDestSize, const BitmapEx& rBitmap ) +{ + MARK( "drawBitmap (BitmapEx)" ); + + // #i40055# sanity check + if( ! (rDestSize.Width() && rDestSize.Height()) ) + return; + + const BitmapEmit& rEmit = createBitmapEmit( rBitmap ); + drawBitmap( rDestPoint, rDestSize, rEmit, Color( COL_TRANSPARENT ) ); +} + +void PDFWriterImpl::drawMask( const Point& rDestPoint, const Size& rDestSize, const Bitmap& rBitmap, const Color& rFillColor ) +{ + MARK( "drawMask" ); + + // #i40055# sanity check + if( ! (rDestSize.Width() && rDestSize.Height()) ) + return; + + Bitmap aBitmap( rBitmap ); + if( aBitmap.GetBitCount() > 1 ) + aBitmap.Convert( BMP_CONVERSION_1BIT_THRESHOLD ); + DBG_ASSERT( aBitmap.GetBitCount() == 1, "mask conversion failed" ); + + const BitmapEmit& rEmit = createBitmapEmit( BitmapEx( aBitmap ), true ); + drawBitmap( rDestPoint, rDestSize, rEmit, rFillColor ); +} + +sal_Int32 PDFWriterImpl::createGradient( const Gradient& rGradient, const Size& rSize ) +{ + Size aPtSize( lcl_convert( m_aGraphicsStack.front().m_aMapMode, + MapMode( MAP_POINT ), + getReferenceDevice(), + rSize ) ); + // check if we already have this gradient + std::list<GradientEmit>::iterator it; + for( it = m_aGradients.begin(); it != m_aGradients.end(); ++it ) + { + if( it->m_aGradient == rGradient ) + { + if( it->m_aSize.Width() < aPtSize.Width() ) + it->m_aSize.Width() = aPtSize.Width(); + if( it->m_aSize.Height() <= aPtSize.Height() ) + it->m_aSize.Height() = aPtSize.Height(); + break; + } + } + if( it == m_aGradients.end() ) + { + m_aGradients.push_front( GradientEmit() ); + m_aGradients.front().m_aGradient = rGradient; + m_aGradients.front().m_nObject = createObject(); + m_aGradients.front().m_aSize = aPtSize; + it = m_aGradients.begin(); + } + + OStringBuffer aObjName( 16 ); + aObjName.append( 'P' ); + aObjName.append( it->m_nObject ); + pushResource( ResShading, aObjName.makeStringAndClear(), it->m_nObject ); + + return it->m_nObject; +} + +void PDFWriterImpl::drawGradient( const Rectangle& rRect, const Gradient& rGradient ) +{ + MARK( "drawGradient (Rectangle)" ); + + if( m_aContext.Version == PDFWriter::PDF_1_2 ) + { + drawRectangle( rRect ); + return; + } + + sal_Int32 nGradient = createGradient( rGradient, rRect.GetSize() ); + + Point aTranslate( rRect.BottomLeft() ); + aTranslate += Point( 0, 1 ); + + updateGraphicsState(); + + OStringBuffer aLine( 80 ); + aLine.append( "q 1 0 0 1 " ); + m_aPages.back().appendPoint( aTranslate, aLine ); + aLine.append( " cm " ); + // if a stroke is appended reset the clip region before stroke + if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) ) + aLine.append( "q " ); + aLine.append( "0 0 " ); + m_aPages.back().appendMappedLength( (sal_Int32)rRect.GetWidth(), aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( (sal_Int32)rRect.GetHeight(), aLine, true ); + aLine.append( " re W n\n" ); + + aLine.append( "/P" ); + aLine.append( nGradient ); + aLine.append( " sh " ); + if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) ) + { + aLine.append( "Q 0 0 " ); + m_aPages.back().appendMappedLength( (sal_Int32)rRect.GetWidth(), aLine, false ); + aLine.append( ' ' ); + m_aPages.back().appendMappedLength( (sal_Int32)rRect.GetHeight(), aLine, true ); + aLine.append( " re S " ); + } + aLine.append( "Q\n" ); + writeBuffer( aLine.getStr(), aLine.getLength() ); +} + +void PDFWriterImpl::drawGradient( const PolyPolygon& rPolyPoly, const Gradient& rGradient ) +{ + MARK( "drawGradient (PolyPolygon)" ); + + if( m_aContext.Version == PDFWriter::PDF_1_2 ) + { + drawPolyPolygon( rPolyPoly ); + return; + } + + sal_Int32 nGradient = createGradient( rGradient, rPolyPoly.GetBoundRect().GetSize() ); + + updateGraphicsState(); + + Rectangle aBoundRect = rPolyPoly.GetBoundRect(); + Point aTranslate = aBoundRect.BottomLeft() + Point( 0, 1 ); + int nPolygons = rPolyPoly.Count(); + + OStringBuffer aLine( 80*nPolygons ); + aLine.append( "q " ); + // set PolyPolygon as clip path + m_aPages.back().appendPolyPolygon( rPolyPoly, aLine ); + aLine.append( "W* n\n" ); + aLine.append( "1 0 0 1 " ); + m_aPages.back().appendPoint( aTranslate, aLine ); + aLine.append( " cm\n" ); + aLine.append( "/P" ); + aLine.append( nGradient ); + aLine.append( " sh Q\n" ); + if( m_aGraphicsStack.front().m_aLineColor != Color( COL_TRANSPARENT ) ) + { + // and draw the surrounding path + m_aPages.back().appendPolyPolygon( rPolyPoly, aLine ); + aLine.append( "S\n" ); + } + writeBuffer( aLine.getStr(), aLine.getLength() ); +} + +void PDFWriterImpl::drawHatch( const PolyPolygon& rPolyPoly, const Hatch& rHatch ) +{ + MARK( "drawHatch" ); + + updateGraphicsState(); + + if( m_aGraphicsStack.front().m_aLineColor == Color( COL_TRANSPARENT ) && + m_aGraphicsStack.front().m_aFillColor == Color( COL_TRANSPARENT ) ) + return; + if( rPolyPoly.Count() ) + { + PolyPolygon aPolyPoly( rPolyPoly ); + + aPolyPoly.Optimize( POLY_OPTIMIZE_NO_SAME ); + push( PUSH_LINECOLOR ); + setLineColor( rHatch.GetColor() ); + getReferenceDevice()->ImplDrawHatch( aPolyPoly, rHatch, FALSE ); + pop(); + } +} + +void PDFWriterImpl::drawWallpaper( const Rectangle& rRect, const Wallpaper& rWall ) +{ + MARK( "drawWallpaper" ); + + bool bDrawColor = false; + bool bDrawGradient = false; + bool bDrawBitmap = false; + + BitmapEx aBitmap; + Point aBmpPos = rRect.TopLeft(); + Size aBmpSize; + if( rWall.IsBitmap() ) + { + aBitmap = rWall.GetBitmap(); + aBmpSize = lcl_convert( aBitmap.GetPrefMapMode(), + getMapMode(), + getReferenceDevice(), + aBitmap.GetPrefSize() ); + Rectangle aRect( rRect ); + if( rWall.IsRect() ) + { + aRect = rWall.GetRect(); + aBmpPos = aRect.TopLeft(); + aBmpSize = aRect.GetSize(); + } + if( rWall.GetStyle() != WALLPAPER_SCALE ) + { + if( rWall.GetStyle() != WALLPAPER_TILE ) + { + bDrawBitmap = true; + if( rWall.IsGradient() ) + bDrawGradient = true; + else + bDrawColor = true; + switch( rWall.GetStyle() ) + { + case WALLPAPER_TOPLEFT: + break; + case WALLPAPER_TOP: + aBmpPos.X() += (aRect.GetWidth()-aBmpSize.Width())/2; + break; + case WALLPAPER_LEFT: + aBmpPos.Y() += (aRect.GetHeight()-aBmpSize.Height())/2; + break; + case WALLPAPER_TOPRIGHT: + aBmpPos.X() += aRect.GetWidth()-aBmpSize.Width(); + break; + case WALLPAPER_CENTER: + aBmpPos.X() += (aRect.GetWidth()-aBmpSize.Width())/2; + aBmpPos.Y() += (aRect.GetHeight()-aBmpSize.Height())/2; + break; + case WALLPAPER_RIGHT: + aBmpPos.X() += aRect.GetWidth()-aBmpSize.Width(); + aBmpPos.Y() += (aRect.GetHeight()-aBmpSize.Height())/2; + break; + case WALLPAPER_BOTTOMLEFT: + aBmpPos.Y() += aRect.GetHeight()-aBmpSize.Height(); + break; + case WALLPAPER_BOTTOM: + aBmpPos.X() += (aRect.GetWidth()-aBmpSize.Width())/2; + aBmpPos.Y() += aRect.GetHeight()-aBmpSize.Height(); + break; + case WALLPAPER_BOTTOMRIGHT: + aBmpPos.X() += aRect.GetWidth()-aBmpSize.Width(); + aBmpPos.Y() += aRect.GetHeight()-aBmpSize.Height(); + break; + default: ; + } + } + else + { + // push the bitmap + const BitmapEmit& rEmit = createBitmapEmit( BitmapEx( aBitmap ) ); + + // convert to page coordinates; this needs to be done here + // since the emit does not know the page anymore + Rectangle aConvertRect( aBmpPos, aBmpSize ); + m_aPages.back().convertRect( aConvertRect ); + + OStringBuffer aNameBuf(16); + aNameBuf.append( "Im" ); + aNameBuf.append( rEmit.m_nObject ); + OString aImageName( aNameBuf.makeStringAndClear() ); + + // push the pattern + OStringBuffer aTilingStream( 32 ); + appendFixedInt( aConvertRect.GetWidth(), aTilingStream ); + aTilingStream.append( " 0 0 " ); + appendFixedInt( aConvertRect.GetHeight(), aTilingStream ); + aTilingStream.append( " 0 0 cm\n/" ); + aTilingStream.append( aImageName ); + aTilingStream.append( " Do\n" ); + + m_aTilings.push_back( TilingEmit() ); + m_aTilings.back().m_nObject = createObject(); + m_aTilings.back().m_aRectangle = Rectangle( Point( 0, 0 ), aConvertRect.GetSize() ); + m_aTilings.back().m_pTilingStream = new SvMemoryStream(); + m_aTilings.back().m_pTilingStream->Write( aTilingStream.getStr(), aTilingStream.getLength() ); + // phase the tiling so wallpaper begins on upper left + m_aTilings.back().m_aTransform.matrix[2] = double(aConvertRect.Left() % aConvertRect.GetWidth()) / fDivisor; + m_aTilings.back().m_aTransform.matrix[5] = double(aConvertRect.Top() % aConvertRect.GetHeight()) / fDivisor; + m_aTilings.back().m_aResources.m_aXObjects[aImageName] = rEmit.m_nObject; + + updateGraphicsState(); + + OStringBuffer aObjName( 16 ); + aObjName.append( 'P' ); + aObjName.append( m_aTilings.back().m_nObject ); + OString aPatternName( aObjName.makeStringAndClear() ); + pushResource( ResPattern, aPatternName, m_aTilings.back().m_nObject ); + + // fill a rRect with the pattern + OStringBuffer aLine( 100 ); + aLine.append( "q /Pattern cs /" ); + aLine.append( aPatternName ); + aLine.append( " scn " ); + m_aPages.back().appendRect( rRect, aLine ); + aLine.append( " f Q\n" ); + writeBuffer( aLine.getStr(), aLine.getLength() ); + } + } + else + { + aBmpPos = aRect.TopLeft(); + aBmpSize = aRect.GetSize(); + bDrawBitmap = true; + } + + if( aBitmap.IsTransparent() ) + { + if( rWall.IsGradient() ) + bDrawGradient = true; + else + bDrawColor = true; + } + } + else if( rWall.IsGradient() ) + bDrawGradient = true; + else + bDrawColor = true; + + if( bDrawGradient ) + { + drawGradient( rRect, rWall.GetGradient() ); + } + if( bDrawColor ) + { + Color aOldLineColor = m_aGraphicsStack.front().m_aLineColor; + Color aOldFillColor = m_aGraphicsStack.front().m_aFillColor; + setLineColor( Color( COL_TRANSPARENT ) ); + setFillColor( rWall.GetColor() ); + drawRectangle( rRect ); + setLineColor( aOldLineColor ); + setFillColor( aOldFillColor ); + } + if( bDrawBitmap ) + { + // set temporary clip region since aBmpPos and aBmpSize + // may be outside rRect + OStringBuffer aLine( 20 ); + aLine.append( "q " ); + m_aPages.back().appendRect( rRect, aLine ); + aLine.append( " W n\n" ); + writeBuffer( aLine.getStr(), aLine.getLength() ); + drawBitmap( aBmpPos, aBmpSize, aBitmap ); + writeBuffer( "Q\n", 2 ); + } +} + +void PDFWriterImpl::beginPattern( const Rectangle& rCellRect ) +{ + beginRedirect( new SvMemoryStream(), rCellRect ); +} + +sal_Int32 PDFWriterImpl::endPattern( const SvtGraphicFill::Transform& rTransform ) +{ + Rectangle aConvertRect( getRedirectTargetRect() ); + DBG_ASSERT( aConvertRect.GetWidth() != 0 && aConvertRect.GetHeight() != 0, "empty cell rectangle in pattern" ); + + // get scaling between current mapmode and PDF output + Size aScaling( lcl_convert( m_aGraphicsStack.front().m_aMapMode, m_aMapMode, getReferenceDevice(), Size( 10000, 10000 ) ) ); + double fSX = (double(aScaling.Width()) / 10000.0); + double fSY = (double(aScaling.Height()) / 10000.0); + + // transform translation part of matrix + Size aTranslation( (long)rTransform.matrix[2], (long)rTransform.matrix[5] ); + aTranslation = lcl_convert( m_aGraphicsStack.front().m_aMapMode, m_aMapMode, getReferenceDevice(), aTranslation ); + + sal_Int32 nTilingId = m_aTilings.size(); + m_aTilings.push_back( TilingEmit() ); + TilingEmit& rTile = m_aTilings.back(); + rTile.m_nObject = createObject(); + rTile.m_aResources = m_aOutputStreams.front().m_aResourceDict; + rTile.m_aTransform.matrix[0] = rTransform.matrix[0] * fSX; + rTile.m_aTransform.matrix[1] = rTransform.matrix[1] * fSY; + rTile.m_aTransform.matrix[2] = aTranslation.Width(); + rTile.m_aTransform.matrix[3] = rTransform.matrix[3] * fSX; + rTile.m_aTransform.matrix[4] = rTransform.matrix[4] * fSY; + rTile.m_aTransform.matrix[5] = -aTranslation.Height(); + // caution: endRedirect pops the stream, so do this last + rTile.m_pTilingStream = dynamic_cast<SvMemoryStream*>(endRedirect()); + // FIXME: bound rect will not work with rotated matrix + rTile.m_aRectangle = Rectangle( Point(0,0), aConvertRect.GetSize() ); + rTile.m_aCellSize = aConvertRect.GetSize(); + + OStringBuffer aObjName( 16 ); + aObjName.append( 'P' ); + aObjName.append( rTile.m_nObject ); + pushResource( ResPattern, aObjName.makeStringAndClear(), rTile.m_nObject ); + return nTilingId; +} + +void PDFWriterImpl::drawPolyPolygon( const PolyPolygon& rPolyPoly, sal_Int32 nPattern, bool bEOFill ) +{ + if( nPattern < 0 || nPattern >= (sal_Int32)m_aTilings.size() ) + return; + + m_aPages.back().endStream(); + sal_Int32 nXObject = createObject(); + OStringBuffer aNameBuf( 16 ); + aNameBuf.append( "Pol" ); + aNameBuf.append( nXObject ); + OString aObjName( aNameBuf.makeStringAndClear() ); + Rectangle aObjRect; + if( updateObject( nXObject ) ) + { + // get bounding rect of object + PolyPolygon aSubDiv; + rPolyPoly.AdaptiveSubdivide( aSubDiv ); + aObjRect = aSubDiv.GetBoundRect(); + Rectangle aConvObjRect( aObjRect ); + m_aPages.back().convertRect( aConvObjRect ); + + // move polypolygon to bottom left of page + PolyPolygon aLocalPath( rPolyPoly ); + sal_Int32 nPgWd = getReferenceDevice()->ImplGetDPIX() * m_aPages.back().getWidth() / 72; + sal_Int32 nPgHt = getReferenceDevice()->ImplGetDPIY() * m_aPages.back().getHeight() / 72; + Size aLogicPgSz = getReferenceDevice()->PixelToLogic( Size( nPgWd, nPgHt ), m_aGraphicsStack.front().m_aMapMode ); + sal_Int32 nXOff = aObjRect.Left(); + sal_Int32 nYOff = aLogicPgSz.Height() - aObjRect.Bottom(); + aLocalPath.Move( -nXOff, nYOff ); + + // prepare XObject's content stream + OStringBuffer aStream( 512 ); + aStream.append( "/Pattern cs /P" ); + aStream.append( m_aTilings[ nPattern ].m_nObject ); + aStream.append( " scn\n" ); + m_aPages.back().appendPolyPolygon( aLocalPath, aStream ); + aStream.append( bEOFill ? "f*" : "f" ); + SvMemoryStream aMemStream( aStream.getLength() ); + aMemStream.Write( aStream.getStr(), aStream.getLength() ); + bool bDeflate = compressStream( &aMemStream ); + aMemStream.Seek( STREAM_SEEK_TO_END ); + sal_Int32 nStreamLen = (sal_Int32)aMemStream.Tell(); + aMemStream.Seek( STREAM_SEEK_TO_BEGIN ); + + // add new XObject to global resource dict + m_aGlobalResourceDict.m_aXObjects[ aObjName ] = nXObject; + + // write XObject + OStringBuffer aLine( 512 ); + aLine.append( nXObject ); + aLine.append( " 0 obj\n" + "<</Type/XObject/Subtype/Form/BBox[0 0 " ); + appendFixedInt( aConvObjRect.GetWidth(), aLine ); + aLine.append( ' ' ); + appendFixedInt( aConvObjRect.GetHeight(), aLine ); + aLine.append( "]/Length " ); + aLine.append( nStreamLen ); + if( bDeflate ) + aLine.append( "/Filter/FlateDecode" ); + aLine.append( ">>\n" + "stream\n" ); + writeBuffer( aLine.getStr(), aLine.getLength() ); + checkAndEnableStreamEncryption( nXObject ); + writeBuffer( aMemStream.GetData(), nStreamLen ); + disableStreamEncryption(); + writeBuffer( "\nendstream\nendobj\n\n", 19 ); + } + m_aPages.back().beginStream(); + OStringBuffer aLine( 80 ); + aLine.append( "q 1 0 0 1 " ); + m_aPages.back().appendPoint( aObjRect.BottomLeft(), aLine ); + aLine.append( " cm/" ); + aLine.append( aObjName ); + aLine.append( " Do Q\n" ); + writeBuffer( aLine.getStr(), aLine.getLength() ); +} + +void PDFWriterImpl::updateGraphicsState() +{ + OStringBuffer aLine( 256 ); + GraphicsState& rNewState = m_aGraphicsStack.front(); + // first set clip region since it might invalidate everything else + + if( (rNewState.m_nUpdateFlags & GraphicsState::updateClipRegion) ) + { + rNewState.m_nUpdateFlags &= ~GraphicsState::updateClipRegion; + + if( m_aCurrentPDFState.m_bClipRegion != rNewState.m_bClipRegion || + ( rNewState.m_bClipRegion && m_aCurrentPDFState.m_aClipRegion != rNewState.m_aClipRegion ) ) + { + if( m_aCurrentPDFState.m_bClipRegion && m_aCurrentPDFState.m_aClipRegion.count() ) + { + aLine.append( "Q " ); + // invalidate everything but the clip region + m_aCurrentPDFState = GraphicsState(); + rNewState.m_nUpdateFlags = sal::static_int_cast<sal_uInt16>(~GraphicsState::updateClipRegion); + } + if( rNewState.m_bClipRegion && rNewState.m_aClipRegion.count() ) + { + // clip region is always stored in private PDF mapmode + MapMode aNewMapMode = rNewState.m_aMapMode; + rNewState.m_aMapMode = m_aMapMode; + getReferenceDevice()->SetMapMode( rNewState.m_aMapMode ); + m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode; + + aLine.append( "q " ); + m_aPages.back().appendPolyPolygon( rNewState.m_aClipRegion, aLine ); + aLine.append( "W* n\n" ); + rNewState.m_aMapMode = aNewMapMode; + getReferenceDevice()->SetMapMode( rNewState.m_aMapMode ); + m_aCurrentPDFState.m_aMapMode = rNewState.m_aMapMode; + } + } + } + + if( (rNewState.m_nUpdateFlags & GraphicsState::updateMapMode) ) + { + rNewState.m_nUpdateFlags &= ~GraphicsState::updateMapMode; + getReferenceDevice()->SetMapMode( rNewState.m_aMapMode ); + } + + if( (rNewState.m_nUpdateFlags & GraphicsState::updateFont) ) + { + rNewState.m_nUpdateFlags &= ~GraphicsState::updateFont; + getReferenceDevice()->SetFont( rNewState.m_aFont ); + getReferenceDevice()->ImplNewFont(); + } + + if( (rNewState.m_nUpdateFlags & GraphicsState::updateLayoutMode) ) + { + rNewState.m_nUpdateFlags &= ~GraphicsState::updateLayoutMode; + getReferenceDevice()->SetLayoutMode( rNewState.m_nLayoutMode ); + } + + if( (rNewState.m_nUpdateFlags & GraphicsState::updateDigitLanguage) ) + { + rNewState.m_nUpdateFlags &= ~GraphicsState::updateDigitLanguage; + getReferenceDevice()->SetDigitLanguage( rNewState.m_aDigitLanguage ); + } + + if( (rNewState.m_nUpdateFlags & GraphicsState::updateLineColor) ) + { + rNewState.m_nUpdateFlags &= ~GraphicsState::updateLineColor; + if( m_aCurrentPDFState.m_aLineColor != rNewState.m_aLineColor && + rNewState.m_aLineColor != Color( COL_TRANSPARENT ) ) + { + appendStrokingColor( rNewState.m_aLineColor, aLine ); + aLine.append( "\n" ); + } + } + + if( (rNewState.m_nUpdateFlags & GraphicsState::updateFillColor) ) + { + rNewState.m_nUpdateFlags &= ~GraphicsState::updateFillColor; + if( m_aCurrentPDFState.m_aFillColor != rNewState.m_aFillColor && + rNewState.m_aFillColor != Color( COL_TRANSPARENT ) ) + { + appendNonStrokingColor( rNewState.m_aFillColor, aLine ); + aLine.append( "\n" ); + } + } + + if( (rNewState.m_nUpdateFlags & GraphicsState::updateTransparentPercent) ) + { + rNewState.m_nUpdateFlags &= ~GraphicsState::updateTransparentPercent; + if( m_aContext.Version >= PDFWriter::PDF_1_4 && m_aCurrentPDFState.m_nTransparentPercent != rNewState.m_nTransparentPercent ) + { + // TODO: switch extended graphicsstate + } + } + + // everything is up to date now + m_aCurrentPDFState = m_aGraphicsStack.front(); + if( aLine.getLength() ) + writeBuffer( aLine.getStr(), aLine.getLength() ); +} + +/* #i47544# imitate OutputDevice behaviour: +* if a font with a nontransparent color is set, it overwrites the current +* text color. OTOH setting the text color will overwrite the color of the font. +*/ +void PDFWriterImpl::setFont( const Font& rFont ) +{ + Color aColor = rFont.GetColor(); + if( aColor == Color( COL_TRANSPARENT ) ) + aColor = m_aGraphicsStack.front().m_aFont.GetColor(); + m_aGraphicsStack.front().m_aFont = rFont; + m_aGraphicsStack.front().m_aFont.SetColor( aColor ); + m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsState::updateFont; +} + +void PDFWriterImpl::push( sal_uInt16 nFlags ) +{ + OSL_ENSURE( m_aGraphicsStack.size() > 0, "invalid graphics stack" ); + m_aGraphicsStack.push_front( m_aGraphicsStack.front() ); + m_aGraphicsStack.front().m_nFlags = nFlags; +} + +void PDFWriterImpl::pop() +{ + OSL_ENSURE( m_aGraphicsStack.size() > 1, "pop without push" ); + if( m_aGraphicsStack.size() < 2 ) + return; + + GraphicsState aState = m_aGraphicsStack.front(); + m_aGraphicsStack.pop_front(); + GraphicsState& rOld = m_aGraphicsStack.front(); + + // move those parameters back that were not pushed + // in the first place + if( ! (aState.m_nFlags & PUSH_LINECOLOR) ) + setLineColor( aState.m_aLineColor ); + if( ! (aState.m_nFlags & PUSH_FILLCOLOR) ) + setFillColor( aState.m_aFillColor ); + if( ! (aState.m_nFlags & PUSH_FONT) ) + setFont( aState.m_aFont ); + if( ! (aState.m_nFlags & PUSH_TEXTCOLOR) ) + setTextColor( aState.m_aFont.GetColor() ); + if( ! (aState.m_nFlags & PUSH_MAPMODE) ) + setMapMode( aState.m_aMapMode ); + if( ! (aState.m_nFlags & PUSH_CLIPREGION) ) + { + // do not use setClipRegion here + // it would convert again assuming the current mapmode + rOld.m_aClipRegion = aState.m_aClipRegion; + rOld.m_bClipRegion = aState.m_bClipRegion; + } + if( ! (aState.m_nFlags & PUSH_TEXTLINECOLOR ) ) + setTextLineColor( aState.m_aTextLineColor ); + if( ! (aState.m_nFlags & PUSH_OVERLINECOLOR ) ) + setOverlineColor( aState.m_aOverlineColor ); + if( ! (aState.m_nFlags & PUSH_TEXTALIGN ) ) + setTextAlign( aState.m_aFont.GetAlign() ); + if( ! (aState.m_nFlags & PUSH_TEXTFILLCOLOR) ) + setTextFillColor( aState.m_aFont.GetFillColor() ); + if( ! (aState.m_nFlags & PUSH_REFPOINT) ) + { + // what ? + } + // invalidate graphics state + m_aGraphicsStack.front().m_nUpdateFlags = sal::static_int_cast<sal_uInt16>(~0U); +} + +void PDFWriterImpl::setMapMode( const MapMode& rMapMode ) +{ + m_aGraphicsStack.front().m_aMapMode = rMapMode; + getReferenceDevice()->SetMapMode( rMapMode ); + m_aCurrentPDFState.m_aMapMode = rMapMode; +} + +void PDFWriterImpl::setClipRegion( const basegfx::B2DPolyPolygon& rRegion ) +{ + basegfx::B2DPolyPolygon aRegion = getReferenceDevice()->LogicToPixel( rRegion, m_aGraphicsStack.front().m_aMapMode ); + aRegion = getReferenceDevice()->PixelToLogic( aRegion, m_aMapMode ); + m_aGraphicsStack.front().m_aClipRegion = aRegion; + m_aGraphicsStack.front().m_bClipRegion = true; + m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsState::updateClipRegion; +} + +void PDFWriterImpl::moveClipRegion( sal_Int32 nX, sal_Int32 nY ) +{ + if( m_aGraphicsStack.front().m_bClipRegion && m_aGraphicsStack.front().m_aClipRegion.count() ) + { + Point aPoint( lcl_convert( m_aGraphicsStack.front().m_aMapMode, + m_aMapMode, + getReferenceDevice(), + Point( nX, nY ) ) ); + aPoint -= lcl_convert( m_aGraphicsStack.front().m_aMapMode, + m_aMapMode, + getReferenceDevice(), + Point() ); + basegfx::B2DHomMatrix aMat; + aMat.translate( aPoint.X(), aPoint.Y() ); + m_aGraphicsStack.front().m_aClipRegion.transform( aMat ); + m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsState::updateClipRegion; + } +} + +bool PDFWriterImpl::intersectClipRegion( const Rectangle& rRect ) +{ + basegfx::B2DPolyPolygon aRect( basegfx::tools::createPolygonFromRect( + basegfx::B2DRectangle( rRect.Left(), rRect.Top(), rRect.Right(), rRect.Bottom() ) ) ); + return intersectClipRegion( aRect ); +} + + +bool PDFWriterImpl::intersectClipRegion( const basegfx::B2DPolyPolygon& rRegion ) +{ + basegfx::B2DPolyPolygon aRegion( getReferenceDevice()->LogicToPixel( rRegion, m_aGraphicsStack.front().m_aMapMode ) ); + aRegion = getReferenceDevice()->PixelToLogic( aRegion, m_aMapMode ); + m_aGraphicsStack.front().m_nUpdateFlags |= GraphicsState::updateClipRegion; + if( m_aGraphicsStack.front().m_bClipRegion ) + { + basegfx::B2DPolyPolygon aOld( basegfx::tools::prepareForPolygonOperation( m_aGraphicsStack.front().m_aClipRegion ) ); + aRegion = basegfx::tools::prepareForPolygonOperation( aRegion ); + m_aGraphicsStack.front().m_aClipRegion = basegfx::tools::solvePolygonOperationAnd( aOld, aRegion ); + } + else + { + m_aGraphicsStack.front().m_aClipRegion = aRegion; + m_aGraphicsStack.front().m_bClipRegion = true; + } + return true; +} + +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; +} + +//--->i56629 +sal_Int32 PDFWriterImpl::createNamedDest( const rtl::OUString& sDestName, 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_aNamedDests.size(); + + m_aNamedDests.push_back( PDFNamedDest() ); + m_aNamedDests.back().m_aDestName = sDestName; + m_aNamedDests.back().m_nPage = nPageNr; + m_aNamedDests.back().m_eType = eType; + m_aNamedDests.back().m_aRect = rRect; + // convert to default user space now, since the mapmode may change + m_aPages[nPageNr].convertRect( m_aNamedDests.back().m_aRect ); + + return nRet; +} +//<---i56629 + +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; + + using namespace ::com::sun::star; + + if (!m_xTrans.is()) + { + uno::Reference< lang::XMultiServiceFactory > xFact( comphelper::getProcessServiceFactory() ); + if( xFact.is() ) + { + m_xTrans = uno::Reference < util::XURLTransformer >( + xFact->createInstance( OUString( RTL_CONSTASCII_USTRINGPARAM( "com.sun.star.util.URLTransformer" ) ) ), uno::UNO_QUERY ); + } + } + + util::URL aURL; + aURL.Complete = rURL; + + if (m_xTrans.is()) + m_xTrans->parseStrict( aURL ); + + m_aLinks[ nLinkId ].m_aURL = aURL.Complete; + + 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 = psp::WhitespaceToSpace( 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( "/" ); + if( rEle.m_aAlias.getLength() > 0 ) + aLine.append( rEle.m_aAlias ); + else + aLine.append( getStructureTag( rEle.m_eType ) ); + aLine.append( "<</MCID " ); + aLine.append( nMCID ); + aLine.append( ">>BDC\n" ); + writeBuffer( aLine.getStr(), aLine.getLength() ); + + // update the element's content list +#if OSL_DEBUG_LEVEL > 1 + fprintf( stderr, "beginning marked content id %" SAL_PRIdINT32 " on page object %" SAL_PRIdINT32 ", structure first page = %" SAL_PRIdINT32 "\n", + nMCID, + m_aPages[ m_nCurrentPage ].m_nPageObject, + rEle.m_nFirstPageObject ); +#endif + rEle.m_aKids.push_back( PDFStructureElementKid( nMCID, m_aPages[m_nCurrentPage].m_nPageObject ) ); + // update the page's mcid parent list + m_aPages[ m_nCurrentPage ].m_aMCIDParents.push_back( rEle.m_nObject ); + // mark element MC sequence as open + rEle.m_bOpenMCSeq = true; + } + // handle artifacts + else if( ! m_bEmitStructure && m_aContext.Tagged && + m_nCurrentStructElement > 0 && + m_aStructure[ m_nCurrentStructElement ].m_eType == PDFWriter::NonStructElement && + ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence + ) + { + OStringBuffer aLine( 128 ); + aLine.append( "/Artifact BMC\n" ); + writeBuffer( aLine.getStr(), aLine.getLength() ); + // mark element MC sequence as open + m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = true; + } +} + +void PDFWriterImpl::endStructureElementMCSeq() +{ + if( m_nCurrentStructElement > 0 && // StructTreeRoot + ( m_bEmitStructure || m_aStructure[ m_nCurrentStructElement ].m_eType == PDFWriter::NonStructElement ) && + m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // must have an opened MC sequence + ) + { + writeBuffer( "EMC\n", 4 ); + m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq = false; + } +} + +bool PDFWriterImpl::checkEmitStructure() +{ + bool bEmit = false; + if( m_aContext.Tagged ) + { + bEmit = true; + sal_Int32 nEle = m_nCurrentStructElement; + while( nEle > 0 && nEle < sal_Int32(m_aStructure.size()) ) + { + if( m_aStructure[ nEle ].m_eType == PDFWriter::NonStructElement ) + { + bEmit = false; + break; + } + nEle = m_aStructure[ nEle ].m_nParentElement; + } + } + return bEmit; +} + +sal_Int32 PDFWriterImpl::beginStructureElement( PDFWriter::StructElement eType, const rtl::OUString& rAlias ) +{ + if( m_nCurrentPage < 0 ) + return -1; + + if( ! m_aContext.Tagged ) + return -1; + + // close eventual current MC sequence + endStructureElementMCSeq(); + + if( m_nCurrentStructElement == 0 && + eType != PDFWriter::Document && eType != PDFWriter::NonStructElement ) + { + // struct tree root hit, but not beginning document + // this might happen with setCurrentStructureElement + // silently insert structure into document again if one properly exists + if( ! m_aStructure[ 0 ].m_aChildren.empty() ) + { + PDFWriter::StructElement childType = PDFWriter::NonStructElement; + sal_Int32 nNewCurElement = 0; + const std::list< sal_Int32 >& rRootChildren = m_aStructure[0].m_aChildren; + for( std::list< sal_Int32 >::const_iterator it = rRootChildren.begin(); + childType != PDFWriter::Document && it != rRootChildren.end(); ++it ) + { + nNewCurElement = *it; + childType = m_aStructure[ nNewCurElement ].m_eType; + } + if( childType == PDFWriter::Document ) + { + m_nCurrentStructElement = nNewCurElement; + DBG_ASSERT( 0, "Structure element inserted to StructTreeRoot that is not a document" ); + } + else { + DBG_ERROR( "document structure in disorder !" ); + } + } + else { + DBG_ERROR( "PDF document structure MUST be contained in a Document element" ); + } + } + + 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; + + // handle alias names + if( rAlias.getLength() && eType != PDFWriter::NonStructElement ) + { + OStringBuffer aNameBuf( rAlias.getLength() ); + appendName( rAlias, aNameBuf ); + OString aAliasName( aNameBuf.makeStringAndClear() ); + rEle.m_aAlias = aAliasName; + m_aRoleMap[ aAliasName ] = getStructureTag( eType ); + } + +#if OSL_DEBUG_LEVEL > 1 + OStringBuffer aLine( "beginStructureElement " ); + aLine.append( m_nCurrentStructElement ); + aLine.append( ": " ); + aLine.append( getStructureTag( eType ) ); + if( rEle.m_aAlias.getLength() ) + { + aLine.append( " aliased as \"" ); + aLine.append( rEle.m_aAlias ); + aLine.append( '\"' ); + } + emitComment( aLine.getStr() ); +#endif + + // check whether to emit structure henceforth + m_bEmitStructure = checkEmitStructure(); + + if( m_bEmitStructure ) // don't create nonexistant objects + { + rEle.m_nObject = createObject(); + // update parent's kids list + m_aStructure[ rEle.m_nParentElement ].m_aKids.push_back( rEle.m_nObject ); + } + return nNewId; +} + +void PDFWriterImpl::endStructureElement() +{ + if( m_nCurrentPage < 0 ) + return; + + if( ! m_aContext.Tagged ) + return; + + if( m_nCurrentStructElement == 0 ) + { + // hit the struct tree root, that means there is an endStructureElement + // without corresponding beginStructureElement + return; + } + + // end the marked content sequence + endStructureElementMCSeq(); + +#if OSL_DEBUG_LEVEL > 1 + OStringBuffer aLine( "endStructureElement " ); + aLine.append( m_nCurrentStructElement ); + aLine.append( ": " ); + aLine.append( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) ); + if( m_aStructure[ m_nCurrentStructElement ].m_aAlias.getLength() ) + { + aLine.append( " aliased as \"" ); + aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias ); + aLine.append( '\"' ); + } +#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 +} + +//---> i94258 +/* + * This function adds an internal structure list container to overcome the 8191 elements array limitation + * in kids element emission. + * Recursive function + * + */ +void PDFWriterImpl::addInternalStructureContainer( PDFStructureElement& rEle ) +{ + if( rEle.m_eType == PDFWriter::NonStructElement && + rEle.m_nOwnElement != rEle.m_nParentElement ) + return; + + 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 ) + { + //triggered when a child of the rEle element is found + if( rChild.m_nParentElement == rEle.m_nOwnElement ) + addInternalStructureContainer( rChild );//examine the child + else + { + DBG_ERROR( "PDFWriterImpl::addInternalStructureContainer: invalid child structure element" ); +#if OSL_DEBUG_LEVEL > 1 + fprintf( stderr, "PDFWriterImpl::addInternalStructureContainer: invalid child structure elemnt with id %" SAL_PRIdINT32 "\n", *it ); +#endif + } + } + } + else + { + DBG_ERROR( "PDFWriterImpl::emitStructure: invalid child structure id" ); +#if OSL_DEBUG_LEVEL > 1 + fprintf( stderr, "PDFWriterImpl::addInternalStructureContainer: invalid child structure id %" SAL_PRIdINT32 "\n", *it ); +#endif + } + } + + if( rEle.m_nOwnElement != rEle.m_nParentElement ) + { + if( !rEle.m_aKids.empty() ) + { + if( rEle.m_aKids.size() > ncMaxPDFArraySize ) { + //then we need to add the containers for the kids elements + // a list to be used for the new kid element + std::list< PDFStructureElementKid > aNewKids; + std::list< sal_Int32 > aNewChildren; + + // add Div in RoleMap, in case no one else did (TODO: is it needed? Is it dangerous?) + OStringBuffer aNameBuf( "Div" ); + OString aAliasName( aNameBuf.makeStringAndClear() ); + m_aRoleMap[ aAliasName ] = getStructureTag( PDFWriter::Division ); + + while( rEle.m_aKids.size() > ncMaxPDFArraySize ) + { + sal_Int32 nCurrentStructElement = rEle.m_nOwnElement; + sal_Int32 nNewId = sal_Int32(m_aStructure.size()); + m_aStructure.push_back( PDFStructureElement() ); + PDFStructureElement& rEleNew = m_aStructure.back(); + rEleNew.m_aAlias = aAliasName; + rEleNew.m_eType = PDFWriter::Division; // a new Div type container + rEleNew.m_nOwnElement = nNewId; + rEleNew.m_nParentElement = nCurrentStructElement; + //inherit the same page as the first child to be reparented + rEleNew.m_nFirstPageObject = m_aStructure[ rEle.m_aChildren.front() ].m_nFirstPageObject; + rEleNew.m_nObject = createObject();//assign a PDF object number + //add the object to the kid list of the parent + aNewKids.push_back( PDFStructureElementKid( rEleNew.m_nObject ) ); + aNewChildren.push_back( nNewId ); + + std::list< sal_Int32 >::iterator aChildEndIt( rEle.m_aChildren.begin() ); + std::list< PDFStructureElementKid >::iterator aKidEndIt( rEle.m_aKids.begin() ); + advance( aChildEndIt, ncMaxPDFArraySize ); + advance( aKidEndIt, ncMaxPDFArraySize ); + + rEleNew.m_aKids.splice( rEleNew.m_aKids.begin(), + rEle.m_aKids, + rEle.m_aKids.begin(), + aKidEndIt ); + rEleNew.m_aChildren.splice( rEleNew.m_aChildren.begin(), + rEle.m_aChildren, + rEle.m_aChildren.begin(), + aChildEndIt ); + // set the kid's new parent + for( std::list< sal_Int32 >::const_iterator it = rEleNew.m_aChildren.begin(); + it != rEleNew.m_aChildren.end(); ++it ) + { + m_aStructure[ *it ].m_nParentElement = nNewId; + } + } + //finally add the new kids resulting from the container added + rEle.m_aKids.insert( rEle.m_aKids.begin(), aNewKids.begin(), aNewKids.end() ); + rEle.m_aChildren.insert( rEle.m_aChildren.begin(), aNewChildren.begin(), aNewChildren.end() ); + } + } + } +} +//<--- i94258 + +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_aStructure[ m_nCurrentStructElement ].m_aAlias.getLength() ) + { + aLine.append( " aliased as \"" ); + aLine.append( m_aStructure[ m_nCurrentStructElement ].m_aAlias ); + aLine.append( '\"' ); + } + 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 (%s) element\n", + getAttributeTag( eAttr ), + getAttributeValueTag( eVal ), + getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ), + m_aStructure[ m_nCurrentStructElement ].m_aAlias.getStr() + ); +#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 ) + { + if( eAttr == PDFWriter::Language ) + { + m_aStructure[ m_nCurrentStructElement ].m_aLocale = MsLangId::convertLanguageToLocale( (LanguageType)nValue ); + return true; + } + + 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 (%s) element\n", + getAttributeTag( eAttr ), + (int)nValue, + getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ), + m_aStructure[ m_nCurrentStructElement ].m_aAlias.getStr() ); +#endif + + return bInsert; +} + +void PDFWriterImpl::setStructureBoundingBox( const Rectangle& rRect ) +{ + sal_Int32 nPageNr = m_nCurrentPage; + if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() || !m_aContext.Tagged ) + return; + + + if( m_nCurrentStructElement > 0 && m_bEmitStructure ) + { + PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType; + if( eType == PDFWriter::Figure || + eType == PDFWriter::Formula || + eType == PDFWriter::Form || + eType == PDFWriter::Table ) + { + m_aStructure[ m_nCurrentStructElement ].m_aBBox = rRect; + // convert to default user space now, since the mapmode may change + m_aPages[nPageNr].convertRect( m_aStructure[ m_nCurrentStructElement ].m_aBBox ); + } + } +} + +void PDFWriterImpl::setActualText( const String& rText ) +{ + if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure ) + { + m_aStructure[ m_nCurrentStructElement ].m_aActualText = rText; + } +} + +void PDFWriterImpl::setAlternateText( const String& rText ) +{ + if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure ) + { + m_aStructure[ m_nCurrentStructElement ].m_aAltText = rText; + } +} + +void PDFWriterImpl::setAutoAdvanceTime( sal_uInt32 nSeconds, sal_Int32 nPageNr ) +{ + if( nPageNr < 0 ) + nPageNr = m_nCurrentPage; + + if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() ) + return; + + m_aPages[ nPageNr ].m_nDuration = nSeconds; +} + +void PDFWriterImpl::setPageTransition( PDFWriter::PageTransition eType, sal_uInt32 nMilliSec, sal_Int32 nPageNr ) +{ + if( nPageNr < 0 ) + nPageNr = m_nCurrentPage; + + if( nPageNr < 0 || nPageNr >= (sal_Int32)m_aPages.size() ) + return; + + m_aPages[ nPageNr ].m_eTransition = eType; + m_aPages[ nPageNr ].m_nTransTime = nMilliSec; +} + +void PDFWriterImpl::ensureUniqueRadioOnValues() +{ + // loop over radio groups + for( std::map<sal_Int32,sal_Int32>::const_iterator group = m_aRadioGroupWidgets.begin(); + group != m_aRadioGroupWidgets.end(); ++group ) + { + PDFWidget& rGroupWidget = m_aWidgets[ group->second ]; + // check whether all kids have a unique OnValue + std::hash_map< OUString, sal_Int32, OUStringHash > aOnValues; + int nChildren = rGroupWidget.m_aKidsIndex.size(); + bool bIsUnique = true; + for( int nKid = 0; nKid < nChildren && bIsUnique; nKid++ ) + { + int nKidIndex = rGroupWidget.m_aKidsIndex[nKid]; + const OUString& rVal = m_aWidgets[nKidIndex].m_aOnValue; + #if OSL_DEBUG_LEVEL > 1 + fprintf( stderr, "OnValue: %s\n", OUStringToOString( rVal, RTL_TEXTENCODING_UTF8 ).getStr() ); + #endif + if( aOnValues.find( rVal ) == aOnValues.end() ) + { + aOnValues[ rVal ] = 1; + } + else + { + bIsUnique = false; + } + } + if( ! bIsUnique ) + { + #if OSL_DEBUG_LEVEL > 1 + fprintf( stderr, "enforcing unique OnValues\n" ); + #endif + // make unique by using ascending OnValues + for( int nKid = 0; nKid < nChildren; nKid++ ) + { + int nKidIndex = rGroupWidget.m_aKidsIndex[nKid]; + PDFWidget& rKid = m_aWidgets[nKidIndex]; + rKid.m_aOnValue = OUString::valueOf( sal_Int32(nKid+1) ); + if( ! rKid.m_aValue.equalsAscii( "Off" ) ) + rKid.m_aValue = rKid.m_aOnValue; + } + } + // finally move the "Yes" appearance to the OnValue appearance + for( int nKid = 0; nKid < nChildren; nKid++ ) + { + int nKidIndex = rGroupWidget.m_aKidsIndex[nKid]; + PDFWidget& rKid = m_aWidgets[nKidIndex]; + PDFAppearanceMap::iterator app_it = rKid.m_aAppearances.find( "N" ); + if( app_it != rKid.m_aAppearances.end() ) + { + PDFAppearanceStreams::iterator stream_it = app_it->second.find( "Yes" ); + if( stream_it != app_it->second.end() ) + { + SvMemoryStream* pStream = stream_it->second; + app_it->second.erase( stream_it ); + OStringBuffer aBuf( rKid.m_aOnValue.getLength()*2 ); + appendName( rKid.m_aOnValue, aBuf ); + (app_it->second)[ aBuf.makeStringAndClear() ] = pStream; + } + #if OSL_DEBUG_LEVEL > 1 + else + fprintf( stderr, "error: RadioButton without \"Yes\" stream\n" ); + #endif + } + // update selected radio button + if( ! rKid.m_aValue.equalsAscii( "Off" ) ) + { + rGroupWidget.m_aValue = rKid.m_aValue; + } + } + } +} + +sal_Int32 PDFWriterImpl::findRadioGroupWidget( const PDFWriter::RadioButtonWidget& rBtn ) +{ + sal_Int32 nRadioGroupWidget = -1; + + std::map< sal_Int32, sal_Int32 >::const_iterator it = m_aRadioGroupWidgets.find( rBtn.RadioGroup ); + + if( it == m_aRadioGroupWidgets.end() ) + { + m_aRadioGroupWidgets[ rBtn.RadioGroup ] = nRadioGroupWidget = + sal_Int32(m_aWidgets.size()); + + // new group, insert the radiobutton + m_aWidgets.push_back( PDFWidget() ); + m_aWidgets.back().m_nObject = createObject(); + m_aWidgets.back().m_nPage = m_nCurrentPage; + m_aWidgets.back().m_eType = PDFWriter::RadioButton; + m_aWidgets.back().m_nRadioGroup = rBtn.RadioGroup; + m_aWidgets.back().m_nFlags |= 0x0000C000; // NoToggleToOff and Radio bits + + createWidgetFieldName( sal_Int32(m_aWidgets.size()-1), rBtn ); + } + 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; + + sal_Int32 nNewWidget = m_aWidgets.size(); + m_aWidgets.push_back( PDFWidget() ); + + m_aWidgets.back().m_nObject = createObject(); + m_aWidgets.back().m_aRect = rControl.Location; + m_aWidgets.back().m_nPage = nPageNr; + m_aWidgets.back().m_eType = rControl.getType(); + + sal_Int32 nRadioGroupWidget = -1; + // for unknown reasons the radio buttons of a radio group must not have a + // field name, else the buttons are in fact check boxes - + // that is multiple buttons of the radio group can be selected + if( rControl.getType() == PDFWriter::RadioButton ) + nRadioGroupWidget = findRadioGroupWidget( static_cast<const PDFWriter::RadioButtonWidget&>(rControl) ); + else + { + createWidgetFieldName( nNewWidget, rControl ); + } + + // caution: m_aWidgets must not be changed after here or rNewWidget may be invalid + PDFWidget& rNewWidget = m_aWidgets[nNewWidget]; + rNewWidget.m_aDescription = rControl.Description; + rNewWidget.m_aText = rControl.Text; + rNewWidget.m_nTextStyle = rControl.TextStyle & + ( TEXT_DRAW_LEFT | TEXT_DRAW_CENTER | TEXT_DRAW_RIGHT | TEXT_DRAW_TOP | + TEXT_DRAW_VCENTER | TEXT_DRAW_BOTTOM | + TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK ); + rNewWidget.m_nTabOrder = rControl.TabOrder; + + // various properties are set via the flags (/Ff) property of the field dict + if( rControl.ReadOnly ) + rNewWidget.m_nFlags |= 1; + if( rControl.getType() == PDFWriter::PushButton ) + { + const PDFWriter::PushButtonWidget& rBtn = static_cast<const PDFWriter::PushButtonWidget&>(rControl); + if( rNewWidget.m_nTextStyle == 0 ) + rNewWidget.m_nTextStyle = + TEXT_DRAW_CENTER | TEXT_DRAW_VCENTER | + TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK; + + rNewWidget.m_nFlags |= 0x00010000; + if( rBtn.URL.getLength() ) + rNewWidget.m_aListEntries.push_back( rBtn.URL ); + rNewWidget.m_bSubmit = rBtn.Submit; + rNewWidget.m_bSubmitGet = rBtn.SubmitGet; + rNewWidget.m_nDest = rBtn.Dest; + createDefaultPushButtonAppearance( rNewWidget, rBtn ); + } + else if( rControl.getType() == PDFWriter::RadioButton ) + { + const PDFWriter::RadioButtonWidget& rBtn = static_cast<const PDFWriter::RadioButtonWidget&>(rControl); + if( rNewWidget.m_nTextStyle == 0 ) + rNewWidget.m_nTextStyle = + TEXT_DRAW_VCENTER | TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK; + /* PDF sees a RadioButton group as one radio button with + * children which are in turn check boxes + * + * so we need to create a radio button on demand for a new group + * and insert a checkbox for each RadioButtonWidget as its child + */ + rNewWidget.m_eType = PDFWriter::CheckBox; + rNewWidget.m_nRadioGroup = rBtn.RadioGroup; + + DBG_ASSERT( nRadioGroupWidget >= 0 && nRadioGroupWidget < (sal_Int32)m_aWidgets.size(), "no radio group parent" ); + + PDFWidget& rRadioButton = m_aWidgets[nRadioGroupWidget]; + rRadioButton.m_aKids.push_back( rNewWidget.m_nObject ); + rRadioButton.m_aKidsIndex.push_back( nNewWidget ); + rNewWidget.m_nParent = rRadioButton.m_nObject; + + rNewWidget.m_aValue = OUString( RTL_CONSTASCII_USTRINGPARAM( "Off" ) ); + rNewWidget.m_aOnValue = rBtn.OnValue; + if( ! rRadioButton.m_aValue.getLength() && rBtn.Selected ) + { + rNewWidget.m_aValue = rNewWidget.m_aOnValue; + rRadioButton.m_aValue = rNewWidget.m_aOnValue; + } + createDefaultRadioButtonAppearance( rNewWidget, rBtn ); + + // union rect of radio group + Rectangle aRect = rNewWidget.m_aRect; + m_aPages[ nPageNr ].convertRect( aRect ); + rRadioButton.m_aRect.Union( aRect ); + } + else if( rControl.getType() == PDFWriter::CheckBox ) + { + const PDFWriter::CheckBoxWidget& rBox = static_cast<const PDFWriter::CheckBoxWidget&>(rControl); + if( rNewWidget.m_nTextStyle == 0 ) + rNewWidget.m_nTextStyle = + TEXT_DRAW_VCENTER | TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK; + + rNewWidget.m_aValue = OUString::createFromAscii( rBox.Checked ? "Yes" : "Off" ); + // create default appearance before m_aRect gets transformed + createDefaultCheckBoxAppearance( rNewWidget, rBox ); + } + else if( rControl.getType() == PDFWriter::ListBox ) + { + if( rNewWidget.m_nTextStyle == 0 ) + rNewWidget.m_nTextStyle = TEXT_DRAW_VCENTER; + + const PDFWriter::ListBoxWidget& rLstBox = static_cast<const PDFWriter::ListBoxWidget&>(rControl); + rNewWidget.m_aListEntries = rLstBox.Entries; + rNewWidget.m_aSelectedEntries = rLstBox.SelectedEntries; + rNewWidget.m_aValue = rLstBox.Text; + if( rLstBox.DropDown ) + rNewWidget.m_nFlags |= 0x00020000; + if( rLstBox.Sort ) + rNewWidget.m_nFlags |= 0x00080000; + if( rLstBox.MultiSelect && !rLstBox.DropDown && (int)m_aContext.Version > (int)PDFWriter::PDF_1_3 ) + 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; + + PDFWriter::ListBoxWidget aLBox; + aLBox.Name = rBox.Name; + aLBox.Description = rBox.Description; + aLBox.Text = rBox.Text; + aLBox.TextStyle = rBox.TextStyle; + aLBox.ReadOnly = rBox.ReadOnly; + aLBox.Border = rBox.Border; + aLBox.BorderColor = rBox.BorderColor; + aLBox.Background = rBox.Background; + aLBox.BackgroundColor = rBox.BackgroundColor; + aLBox.TextFont = rBox.TextFont; + aLBox.TextColor = rBox.TextColor; + aLBox.DropDown = true; + aLBox.Sort = rBox.Sort; + aLBox.MultiSelect = false; + aLBox.Entries = rBox.Entries; + + createDefaultListBoxAppearance( rNewWidget, aLBox ); + } + else if( rControl.getType() == PDFWriter::Edit ) + { + if( rNewWidget.m_nTextStyle == 0 ) + rNewWidget.m_nTextStyle = TEXT_DRAW_LEFT | TEXT_DRAW_VCENTER; + + const PDFWriter::EditWidget& rEdit = static_cast<const PDFWriter::EditWidget&>(rControl); + if( rEdit.MultiLine ) + { + rNewWidget.m_nFlags |= 0x00001000; + rNewWidget.m_nTextStyle |= TEXT_DRAW_MULTILINE | TEXT_DRAW_WORDBREAK; + } + if( rEdit.Password ) + rNewWidget.m_nFlags |= 0x00002000; + if( rEdit.FileSelect && m_aContext.Version > PDFWriter::PDF_1_3 ) + rNewWidget.m_nFlags |= 0x00100000; + rNewWidget.m_nMaxLen = rEdit.MaxLen; + rNewWidget.m_aValue = rEdit.Text; + + createDefaultEditAppearance( rNewWidget, rEdit ); + } + + // convert to default user space now, since the mapmode may change + // note: create default appearances before m_aRect gets transformed + m_aPages[ nPageNr ].convertRect( rNewWidget.m_aRect ); + + // insert widget to page's annotation list + m_aPages[ nPageNr ].m_aAnnotations.push_back( rNewWidget.m_nObject ); + + // mark page as having widgets + m_aPages[ nPageNr ].m_bHasWidgets = true; + + return nNewWidget; +} + +void PDFWriterImpl::beginControlAppearance( sal_Int32 nControl ) +{ + if( nControl < 0 || nControl >= (sal_Int32)m_aWidgets.size() ) + return; + + PDFWidget& rWidget = m_aWidgets[ nControl ]; + m_nCurrentControl = nControl; + + SvMemoryStream* pControlStream = new SvMemoryStream( 1024, 1024 ); + // back conversion of control rect to current MapMode; necessary because + // MapMode between createControl and beginControlAppearance + // could have changed; therefore the widget rectangle is + // already converted + Rectangle aBack( Point( rWidget.m_aRect.Left(), pointToPixel(m_aPages[m_nCurrentPage].getHeight()) - rWidget.m_aRect.Top() - rWidget.m_aRect.GetHeight() ), + rWidget.m_aRect.GetSize() ); + aBack = lcl_convert( m_aMapMode, + m_aGraphicsStack.front().m_aMapMode, + getReferenceDevice(), + aBack ); + beginRedirect( pControlStream, aBack ); + writeBuffer( "/Tx BMC\n", 8 ); +} + +bool PDFWriterImpl::endControlAppearance( PDFWriter::WidgetState eState ) +{ + bool bRet = false; + if( ! m_aOutputStreams.empty() ) + writeBuffer( "\nEMC\n", 5 ); + SvMemoryStream* pAppearance = static_cast<SvMemoryStream*>(endRedirect()); + if( pAppearance && m_nCurrentControl >= 0 && m_nCurrentControl < (sal_Int32)m_aWidgets.size() ) + { + PDFWidget& rWidget = m_aWidgets[ m_nCurrentControl ]; + OString aState, aStyle; + switch( rWidget.m_eType ) + { + case PDFWriter::PushButton: + if( eState == PDFWriter::Up || eState == PDFWriter::Down ) + { + aState = (eState == PDFWriter::Up) ? "N" : "D"; + aStyle = "Standard"; + } + break; + case PDFWriter::CheckBox: + if( eState == PDFWriter::Up || eState == PDFWriter::Down ) + { + aState = "N"; + aStyle = (eState == PDFWriter::Up) ? "Off" : "Yes"; + /* cf PDFReference 3rd ed. V1.4 p539: + recommended name for on state is "Yes", + recommended name for off state is "Off" + */ + } + break; + case PDFWriter::RadioButton: + if( eState == PDFWriter::Up || eState == PDFWriter::Down ) + { + aState = "N"; + if( eState == PDFWriter::Up ) + aStyle = "Off"; + else + { + OStringBuffer aBuf( rWidget.m_aOnValue.getLength()*2 ); + appendName( rWidget.m_aOnValue, aBuf ); + aStyle = aBuf.makeStringAndClear(); + } + } + break; + case PDFWriter::Edit: + aState = "N"; + aStyle = "Standard"; + break; + case PDFWriter::ListBox: + case PDFWriter::ComboBox: + case PDFWriter::Hierarchy: + 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; +} + +void PDFWriterImpl::addStream( const String& rMimeType, PDFOutputStream* pStream, bool bCompress ) +{ + if( pStream ) + { + m_aAdditionalStreams.push_back( PDFAddStream() ); + PDFAddStream& rStream = m_aAdditionalStreams.back(); + rStream.m_aMimeType = rMimeType.Len() + ? OUString( rMimeType ) + : OUString( RTL_CONSTASCII_USTRINGPARAM( "application/octet-stream" ) ); + rStream.m_pStream = pStream; + rStream.m_bCompress = bCompress; + } +} + +/************************************************************* +begin i12626 methods + +Implements Algorithm 3.2, step 1 only +*/ +void PDFWriterImpl::padPassword( rtl::OUString aPassword, sal_uInt8 *paPasswordTarget ) +{ +// get ansi-1252 version of the password string CHECKIT ! i12626 + rtl::OString aString = rtl::OUStringToOString( aPassword, RTL_TEXTENCODING_MS_1252 ); + +//copy the string to the target + sal_Int32 nToCopy = ( aString.getLength() < 32 ) ? aString.getLength() : 32; + sal_Int32 nCurrentChar; + + for( nCurrentChar = 0; nCurrentChar < nToCopy; nCurrentChar++ ) + paPasswordTarget[nCurrentChar] = (sal_uInt8)( aString.getStr()[nCurrentChar] ); + +//pad it + if( nCurrentChar < 32 ) + {//fill with standard byte string + sal_Int32 i,y; + for( i = nCurrentChar, y = 0 ; i < 32; i++, y++ ) + paPasswordTarget[i] = m_nPadString[y]; + } +} + +/********************************** +Algorithm 3.2 Compute the encryption key used + +step 1 should already be done before calling, the paThePaddedPassword parameter should contain +the padded password and must be 32 byte long, the encryption key is returned into the paEncryptionKey parameter, +it will be 16 byte long for 128 bit security; for 40 bit security only the first 5 bytes are used + +TODO: in pdf ver 1.5 and 1.6 the step 6 is different, should be implemented. See spec. + +*/ +void PDFWriterImpl::computeEncryptionKey(sal_uInt8 *paThePaddedPassword, sal_uInt8 *paEncryptionKey ) +{ +//step 2 + if( m_aDigest ) + { + rtlDigestError nError = rtl_digest_updateMD5( m_aDigest, paThePaddedPassword, ENCRYPTED_PWD_SIZE ); +//step 3 + if( nError == rtl_Digest_E_None ) + nError = rtl_digest_updateMD5( m_aDigest, m_nEncryptedOwnerPassword , sizeof( m_nEncryptedOwnerPassword ) ); +//Step 4 + sal_uInt8 nPerm[4]; + + nPerm[0] = (sal_uInt8)m_nAccessPermissions; + nPerm[1] = (sal_uInt8)( m_nAccessPermissions >> 8 ); + nPerm[2] = (sal_uInt8)( m_nAccessPermissions >> 16 ); + nPerm[3] = (sal_uInt8)( m_nAccessPermissions >> 24 ); + + if( nError == rtl_Digest_E_None ) + nError = rtl_digest_updateMD5( m_aDigest, nPerm , sizeof( nPerm ) ); + +//step 5, get the document ID, binary form + if( nError == rtl_Digest_E_None ) + nError = rtl_digest_updateMD5( m_aDigest, m_nDocID , sizeof( m_nDocID ) ); +//get the digest + sal_uInt8 nMD5Sum[ RTL_DIGEST_LENGTH_MD5 ]; + if( nError == rtl_Digest_E_None ) + { + rtl_digest_getMD5( m_aDigest, nMD5Sum, sizeof( nMD5Sum ) ); + +//step 6, only if 128 bit + if( m_aContext.Security128bit ) + { + for( sal_Int32 i = 0; i < 50; i++ ) + { + nError = rtl_digest_updateMD5( m_aDigest, &nMD5Sum, sizeof( nMD5Sum ) ); + if( nError != rtl_Digest_E_None ) + break; + rtl_digest_getMD5( m_aDigest, nMD5Sum, sizeof( nMD5Sum ) ); + } + } + } +//Step 7 + for( sal_Int32 i = 0; i < MD5_DIGEST_SIZE; i++ ) + paEncryptionKey[i] = nMD5Sum[i]; + } +} + +/********************************** +Algorithm 3.3 Compute the encryption dictionary /O value, save into the class data member +the step numbers down here correspond to the ones in PDF v.1.4 specfication +*/ +void PDFWriterImpl::computeODictionaryValue() +{ +//step 1 already done, data is in m_nPaddedOwnerPassword +//step 2 + if( m_aDigest ) + { + rtlDigestError nError = rtl_digest_updateMD5( m_aDigest, &m_nPaddedOwnerPassword, sizeof( m_nPaddedOwnerPassword ) ); + if( nError == rtl_Digest_E_None ) + { + sal_uInt8 nMD5Sum[ RTL_DIGEST_LENGTH_MD5 ]; + + rtl_digest_getMD5( m_aDigest, nMD5Sum, sizeof(nMD5Sum) ); +//step 3, only if 128 bit + if( m_aContext.Security128bit ) + { + sal_Int32 i; + for( i = 0; i < 50; i++ ) + { + nError = rtl_digest_updateMD5( m_aDigest, nMD5Sum, sizeof( nMD5Sum ) ); + if( nError != rtl_Digest_E_None ) + break; + rtl_digest_getMD5( m_aDigest, nMD5Sum, sizeof( nMD5Sum ) ); + } + } +//Step 4, the key is in nMD5Sum +//step 5 already done, data is in m_nPaddedUserPassword +//step 6 + rtl_cipher_initARCFOUR( m_aCipher, rtl_Cipher_DirectionEncode, + nMD5Sum, m_nKeyLength , NULL, 0 ); +// encrypt the user password using the key set above + rtl_cipher_encodeARCFOUR( m_aCipher, m_nPaddedUserPassword, sizeof( m_nPaddedUserPassword ), // the data to be encrypted + m_nEncryptedOwnerPassword, sizeof( m_nEncryptedOwnerPassword ) ); //encrypted data, stored in class data member +//Step 7, only if 128 bit + if( m_aContext.Security128bit ) + { + sal_uInt32 i, y; + sal_uInt8 nLocalKey[ SECUR_128BIT_KEY ]; // 16 = 128 bit key + + for( i = 1; i <= 19; i++ ) // do it 19 times, start with 1 + { + for( y = 0; y < sizeof( nLocalKey ); y++ ) + nLocalKey[y] = (sal_uInt8)( nMD5Sum[y] ^ i ); + + rtl_cipher_initARCFOUR( m_aCipher, rtl_Cipher_DirectionEncode, + nLocalKey, SECUR_128BIT_KEY, NULL, 0 ); //destination data area, on init can be NULL + rtl_cipher_encodeARCFOUR( m_aCipher, m_nEncryptedOwnerPassword, sizeof( m_nEncryptedOwnerPassword ), // the data to be encrypted + m_nEncryptedOwnerPassword, sizeof( m_nEncryptedOwnerPassword ) ); // encrypted data, can be the same as the input, encrypt "in place" +//step 8, store in class data member + } + } + } + } +} + +/********************************** +Algorithms 3.4 and 3.5 Compute the encryption dictionary /U value, save into the class data member, revision 2 (40 bit) or 3 (128 bit) +*/ +void PDFWriterImpl::computeUDictionaryValue() +{ +//step 1, common to both 3.4 and 3.5 + computeEncryptionKey( m_nPaddedUserPassword , m_nEncryptionKey ); + + if( m_aContext.Security128bit == false ) + { +//3.4 +//step 2 and 3 + rtl_cipher_initARCFOUR( m_aCipher, rtl_Cipher_DirectionEncode, + m_nEncryptionKey, 5 , // key and key length + NULL, 0 ); //destination data area +// encrypt the user password using the key set above, save for later use + rtl_cipher_encodeARCFOUR( m_aCipher, m_nPadString, sizeof( m_nPadString ), // the data to be encrypted + m_nEncryptedUserPassword, sizeof( m_nEncryptedUserPassword ) ); //encrypted data, stored in class data member + } + else + { +//or 3.5, for 128 bit security +//step6, initilize the last 16 bytes of the encrypted user password to 0 + for(sal_uInt32 i = MD5_DIGEST_SIZE; i < sizeof( m_nEncryptedUserPassword ); i++) + m_nEncryptedUserPassword[i] = 0; +//step 2 + if( m_aDigest ) + { + rtlDigestError nError = rtl_digest_updateMD5( m_aDigest, m_nPadString, sizeof( m_nPadString ) ); +//step 3 + if( nError == rtl_Digest_E_None ) + nError = rtl_digest_updateMD5( m_aDigest, m_nDocID , sizeof(m_nDocID) ); + + sal_uInt8 nMD5Sum[ RTL_DIGEST_LENGTH_MD5 ]; + rtl_digest_getMD5( m_aDigest, nMD5Sum, sizeof(nMD5Sum) ); +//Step 4 + rtl_cipher_initARCFOUR( m_aCipher, rtl_Cipher_DirectionEncode, + m_nEncryptionKey, SECUR_128BIT_KEY, NULL, 0 ); //destination data area + rtl_cipher_encodeARCFOUR( m_aCipher, nMD5Sum, sizeof( nMD5Sum ), // the data to be encrypted + m_nEncryptedUserPassword, sizeof( nMD5Sum ) ); //encrypted data, stored in class data member +//step 5 + sal_uInt32 i, y; + sal_uInt8 nLocalKey[SECUR_128BIT_KEY]; + + for( i = 1; i <= 19; i++ ) // do it 19 times, start with 1 + { + for( y = 0; y < sizeof( nLocalKey ) ; y++ ) + nLocalKey[y] = (sal_uInt8)( m_nEncryptionKey[y] ^ i ); + + rtl_cipher_initARCFOUR( m_aCipher, rtl_Cipher_DirectionEncode, + nLocalKey, SECUR_128BIT_KEY, // key and key length + NULL, 0 ); //destination data area, on init can be NULL + rtl_cipher_encodeARCFOUR( m_aCipher, m_nEncryptedUserPassword, SECUR_128BIT_KEY, // the data to be encrypted + m_nEncryptedUserPassword, SECUR_128BIT_KEY ); // encrypted data, can be the same as the input, encrypt "in place" + } + } + } +} + +/* init the encryption engine +1. init the document id, used both for building the document id and for building the encryption key(s) +2. build the encryption key following algorithms described in the PDF specification + */ +void PDFWriterImpl::initEncryption() +{ + m_aOwnerPassword = m_aContext.OwnerPassword; + m_aUserPassword = m_aContext.UserPassword; +/* password stuff computing, before sending out anything */ + DBG_ASSERT( m_aCipher != NULL, "PDFWriterImpl::initEncryption: a cipher (ARCFOUR) object is not available !" ); + DBG_ASSERT( m_aDigest != NULL, "PDFWriterImpl::initEncryption: a digest (MD5) object is not available !" ); + + if( m_aCipher && m_aDigest ) + { +//if there is no owner password, force it to the user password + if( m_aOwnerPassword.getLength() == 0 ) + m_aOwnerPassword = m_aUserPassword; + + initPadString(); +/* +1) pad passwords +*/ + padPassword( m_aOwnerPassword, m_nPaddedOwnerPassword ); + padPassword( m_aUserPassword, m_nPaddedUserPassword ); +/* +2) compute the access permissions, in numerical form + +the default value depends on the revision 2 (40 bit) or 3 (128 bit security): +- for 40 bit security the unused bit must be set to 1, since they are not used +- for 128 bit security the same bit must be preset to 0 and set later if needed +according to the table 3.15, pdf v 1.4 */ + m_nAccessPermissions = ( m_aContext.Security128bit ) ? 0xfffff0c0 : 0xffffffc0 ; + +/* check permissions for 40 bit security case */ + m_nAccessPermissions |= ( m_aContext.AccessPermissions.CanPrintTheDocument ) ? 1 << 2 : 0; + m_nAccessPermissions |= ( m_aContext.AccessPermissions.CanModifyTheContent ) ? 1 << 3 : 0; + m_nAccessPermissions |= ( m_aContext.AccessPermissions.CanCopyOrExtract ) ? 1 << 4 : 0; + m_nAccessPermissions |= ( m_aContext.AccessPermissions.CanAddOrModify ) ? 1 << 5 : 0; + m_nKeyLength = SECUR_40BIT_KEY; + m_nRC4KeyLength = SECUR_40BIT_KEY+5; // for this value see PDF spec v 1.4, algorithm 3.1 step 4, where n is 5 + + if( m_aContext.Security128bit ) + { + m_nKeyLength = SECUR_128BIT_KEY; + m_nRC4KeyLength = 16; // for this value see PDF spec v 1.4, algorithm 3.1 step 4, where n is 16, thus maximum + // permitted value is 16 + m_nAccessPermissions |= ( m_aContext.AccessPermissions.CanFillInteractive ) ? 1 << 8 : 0; + m_nAccessPermissions |= ( m_aContext.AccessPermissions.CanExtractForAccessibility ) ? 1 << 9 : 0; + m_nAccessPermissions |= ( m_aContext.AccessPermissions.CanAssemble ) ? 1 << 10 : 0; + m_nAccessPermissions |= ( m_aContext.AccessPermissions.CanPrintFull ) ? 1 << 11 : 0; + } + computeODictionaryValue(); + computeUDictionaryValue(); + +//clear out exceding key values, prepares for generation number default to 0 as well +// see checkAndEnableStreamEncryption in pdfwriter_impl.hxx + sal_Int32 i, y; + for( i = m_nKeyLength, y = 0; y < 5 ; y++ ) + m_nEncryptionKey[i++] = 0; + } + else //either no cipher or no digest or both, something is wrong with memory or something else + m_aContext.Encrypt = false; //then turn the encryption off +} +/* end i12626 methods */ + |