/*************************************************************************
 *
 * 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.
 *
 ************************************************************************/

#include "precompiled_vcl.hxx"

#include "pdfwriter_impl.hxx"

#include "vcl/pdfextoutdevdata.hxx"
#include "vcl/virdev.hxx"
#include "vcl/gdimtf.hxx"
#include "vcl/metaact.hxx"
#include "vcl/bmpacc.hxx"
#include "vcl/graph.hxx"
#include "vcl/rendergraphicrasterizer.hxx"

#include "svdata.hxx"

#include "unotools/streamwrap.hxx"
#include "unotools/processfactory.hxx"

#include "comphelper/processfactory.hxx"

#include "com/sun/star/beans/PropertyValue.hpp"
#include "com/sun/star/io/XSeekable.hpp"
#include "com/sun/star/graphic/XGraphicProvider.hpp"

#include "cppuhelper/implbase1.hxx"

#include <rtl/digest.h>

#undef USE_PDFGRADIENTS

using namespace vcl;
using namespace rtl;
using namespace com::sun::star;
using namespace com::sun::star::uno;
using namespace com::sun::star::beans;

// -----------------------------------------------------------------------------

void PDFWriterImpl::implWriteGradient( const PolyPolygon& i_rPolyPoly, const Gradient& i_rGradient,
                                       VirtualDevice* i_pDummyVDev, const vcl::PDFWriter::PlayMetafileContext& i_rContext )
{
    GDIMetaFile        aTmpMtf;

    i_pDummyVDev->AddGradientActions( i_rPolyPoly.GetBoundRect(), i_rGradient, aTmpMtf );

    m_rOuterFace.Push();
    m_rOuterFace.IntersectClipRegion( i_rPolyPoly.getB2DPolyPolygon() );
    playMetafile( aTmpMtf, NULL, i_rContext, i_pDummyVDev );
    m_rOuterFace.Pop();
}

// -----------------------------------------------------------------------------

void PDFWriterImpl::implWriteBitmapEx( const Point& i_rPoint, const Size& i_rSize, const BitmapEx& i_rBitmapEx,
                                       VirtualDevice* i_pDummyVDev, const vcl::PDFWriter::PlayMetafileContext& i_rContext )
{
	if ( !i_rBitmapEx.IsEmpty() && i_rSize.Width() && i_rSize.Height() )
	{
		BitmapEx		aBitmapEx( i_rBitmapEx );
        Point			aPoint( i_rPoint );
        Size			aSize( i_rSize );

        // #i19065# Negative sizes have mirror semantics on
        // OutputDevice. BitmapEx and co. have no idea about that, so
        // perform that _before_ doing anything with aBitmapEx.
        sal_uLong nMirrorFlags(BMP_MIRROR_NONE);
        if( aSize.Width() < 0 )
        {
            aSize.Width() *= -1;
            aPoint.X() -= aSize.Width();
            nMirrorFlags |= BMP_MIRROR_HORZ;
        }
        if( aSize.Height() < 0 )
        {
            aSize.Height() *= -1;
            aPoint.Y() -= aSize.Height();
            nMirrorFlags |= BMP_MIRROR_VERT;
        }

        if( nMirrorFlags != BMP_MIRROR_NONE )
        {
            aBitmapEx.Mirror( nMirrorFlags );
        }
		if( i_rContext.m_nMaxImageResolution > 50 )
		{
			// do downsampling if neccessary
			const Size      aDstSizeTwip( i_pDummyVDev->PixelToLogic( i_pDummyVDev->LogicToPixel( aSize ), MAP_TWIP ) );
			const Size      aBmpSize( aBitmapEx.GetSizePixel() );
			const double    fBmpPixelX = aBmpSize.Width();
			const double    fBmpPixelY = aBmpSize.Height();
			const double    fMaxPixelX = aDstSizeTwip.Width() * i_rContext.m_nMaxImageResolution / 1440.0;
			const double    fMaxPixelY = aDstSizeTwip.Height() * i_rContext.m_nMaxImageResolution / 1440.0;

			// check, if the bitmap DPI exceeds the maximum DPI (allow 4 pixel rounding tolerance)
			if( ( ( fBmpPixelX > ( fMaxPixelX + 4 ) ) ||
				( fBmpPixelY > ( fMaxPixelY + 4 ) ) ) &&
				( fBmpPixelY > 0.0 ) && ( fMaxPixelY > 0.0 ) )
			{
				// do scaling
				Size            aNewBmpSize;
				const double    fBmpWH = fBmpPixelX / fBmpPixelY;
				const double    fMaxWH = fMaxPixelX / fMaxPixelY;

				if( fBmpWH < fMaxWH )
				{
					aNewBmpSize.Width() = FRound( fMaxPixelY * fBmpWH );
					aNewBmpSize.Height() = FRound( fMaxPixelY );
				}
				else if( fBmpWH > 0.0 )
				{
					aNewBmpSize.Width() = FRound( fMaxPixelX );
					aNewBmpSize.Height() = FRound( fMaxPixelX / fBmpWH);
				}
				if( aNewBmpSize.Width() && aNewBmpSize.Height() )
					aBitmapEx.Scale( aNewBmpSize );
				else
					aBitmapEx.SetEmpty();
			}
		}

		const Size aSizePixel( aBitmapEx.GetSizePixel() );
		if ( aSizePixel.Width() && aSizePixel.Height() )
		{
            if( m_aContext.ColorMode == PDFWriter::DrawGreyscale )
            {
                BmpConversion eConv = BMP_CONVERSION_8BIT_GREYS;
                int nDepth = aBitmapEx.GetBitmap().GetBitCount();
                if( nDepth <= 4 )
                    eConv = BMP_CONVERSION_4BIT_GREYS;
                if( nDepth > 1 )
                    aBitmapEx.Convert( eConv );
            }
			sal_Bool bUseJPGCompression = !i_rContext.m_bOnlyLosslessCompression;
			if ( ( aSizePixel.Width() < 32 ) || ( aSizePixel.Height() < 32 ) )
				bUseJPGCompression = sal_False;

			SvMemoryStream	aStrm;
			Bitmap			aMask;

			bool bTrueColorJPG = true;
			if ( bUseJPGCompression )
			{
				sal_uInt32 nZippedFileSize;		// sj: we will calculate the filesize of a zipped bitmap
				{								// to determine if jpeg compression is usefull
					SvMemoryStream aTemp;
					aTemp.SetCompressMode( aTemp.GetCompressMode() | COMPRESSMODE_ZBITMAP );
					aTemp.SetVersion( SOFFICE_FILEFORMAT_40 );	// sj: up from version 40 our bitmap stream operator
					aTemp << aBitmapEx;							// is capable of zlib stream compression
					aTemp.Seek( STREAM_SEEK_TO_END );
					nZippedFileSize = aTemp.Tell();
				}
				if ( aBitmapEx.IsTransparent() )
				{
					if ( aBitmapEx.IsAlpha() )
						aMask = aBitmapEx.GetAlpha().GetBitmap();
					else
						aMask = aBitmapEx.GetMask();
				}
				Graphic			aGraphic( aBitmapEx.GetBitmap() );
				sal_Int32		nColorMode = 0;

				Sequence< PropertyValue > aFilterData( 2 );
				aFilterData[ 0 ].Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "Quality" ) );
				aFilterData[ 0 ].Value <<= sal_Int32(i_rContext.m_nJPEGQuality);
				aFilterData[ 1 ].Name = OUString( RTL_CONSTASCII_USTRINGPARAM( "ColorMode" ) );
				aFilterData[ 1 ].Value <<= nColorMode;

				try
				{
					uno::Reference < io::XStream > xStream = new utl::OStreamWrapper( aStrm );
					uno::Reference< io::XSeekable > xSeekable( xStream, UNO_QUERY_THROW );
					uno::Reference< graphic::XGraphicProvider > xGraphicProvider( ImplGetSVData()->maAppData.mxMSF->createInstance(
						OUString::createFromAscii( "com.sun.star.graphic.GraphicProvider" ) ), UNO_QUERY );
					if ( xGraphicProvider.is() )
					{
						uno::Reference< graphic::XGraphic > xGraphic( aGraphic.GetXGraphic() );
						uno::Reference < io::XOutputStream > xOut( xStream->getOutputStream() );
						rtl::OUString aMimeType( ::rtl::OUString::createFromAscii( "image/jpeg" ) );
						uno::Sequence< beans::PropertyValue > aOutMediaProperties( 3 );
						aOutMediaProperties[0].Name = ::rtl::OUString::createFromAscii( "OutputStream" );
						aOutMediaProperties[0].Value <<= xOut;
						aOutMediaProperties[1].Name = ::rtl::OUString::createFromAscii( "MimeType" );
						aOutMediaProperties[1].Value <<= aMimeType;
						aOutMediaProperties[2].Name = ::rtl::OUString::createFromAscii( "FilterData" );
						aOutMediaProperties[2].Value <<= aFilterData;
						xGraphicProvider->storeGraphic( xGraphic, aOutMediaProperties );
						xOut->flush();
						if ( xSeekable->getLength() > nZippedFileSize )
						{
							bUseJPGCompression = sal_False;
						}
						else
						{
                            aStrm.Seek( STREAM_SEEK_TO_END );

                            xSeekable->seek( 0 );
                            Sequence< PropertyValue > aArgs( 1 );
                            aArgs[ 0 ].Name = ::rtl::OUString::createFromAscii( "InputStream" );
                            aArgs[ 0 ].Value <<= xStream;
                            uno::Reference< XPropertySet > xPropSet( xGraphicProvider->queryGraphicDescriptor( aArgs ) );
                            if ( xPropSet.is() )
                            {
                                sal_Int16 nBitsPerPixel = 24;
                                if ( xPropSet->getPropertyValue( ::rtl::OUString::createFromAscii( "BitsPerPixel" ) ) >>= nBitsPerPixel )
                                {
                                    bTrueColorJPG = nBitsPerPixel != 8;
                                }
                            }
                        }
					}
					else
					    bUseJPGCompression = sal_False;
				}
				catch( uno::Exception& )
				{
				    bUseJPGCompression = sal_False;
				}
			}
			if ( bUseJPGCompression )
				m_rOuterFace.DrawJPGBitmap( aStrm, bTrueColorJPG, aSizePixel, Rectangle( aPoint, aSize ), aMask );
			else if ( aBitmapEx.IsTransparent() )
				m_rOuterFace.DrawBitmapEx( aPoint, aSize, aBitmapEx );
			else
				m_rOuterFace.DrawBitmap( aPoint, aSize, aBitmapEx.GetBitmap() );
		}
	}
}


// -----------------------------------------------------------------------------

void PDFWriterImpl::playMetafile( const GDIMetaFile& i_rMtf, vcl::PDFExtOutDevData* i_pOutDevData, const vcl::PDFWriter::PlayMetafileContext& i_rContext, VirtualDevice* pDummyVDev )
{
    bool bAssertionFired( false );

    VirtualDevice* pPrivateDevice = NULL;
    if( ! pDummyVDev )
    {
        pPrivateDevice = pDummyVDev = new VirtualDevice();
        pDummyVDev->EnableOutput( sal_False );
        pDummyVDev->SetMapMode( i_rMtf.GetPrefMapMode() );
    }
    GDIMetaFile aMtf( i_rMtf );

	for( sal_uInt32 i = 0, nCount = aMtf.GetActionCount(); i < nCount; )
	{
		if ( !i_pOutDevData || !i_pOutDevData->PlaySyncPageAct( m_rOuterFace, i ) )
		{
			const MetaAction*	pAction = aMtf.GetAction( i );
			const sal_uInt16		nType = pAction->GetType();

			switch( nType )
			{
				case( META_PIXEL_ACTION	):
				{
					const MetaPixelAction* pA = (const MetaPixelAction*) pAction;
					m_rOuterFace.DrawPixel( pA->GetPoint(), pA->GetColor() );
				}
				break;

				case( META_POINT_ACTION	):
				{
					const MetaPointAction* pA = (const MetaPointAction*) pAction;
					m_rOuterFace.DrawPixel( pA->GetPoint() );
				}
				break;

				case( META_LINE_ACTION ):
				{
					const MetaLineAction* pA = (const MetaLineAction*) pAction;
					if ( pA->GetLineInfo().IsDefault() )
						m_rOuterFace.DrawLine( pA->GetStartPoint(), pA->GetEndPoint() );
					else
						m_rOuterFace.DrawLine( pA->GetStartPoint(), pA->GetEndPoint(), pA->GetLineInfo() );
				}
				break;

				case( META_RECT_ACTION ):
				{
					const MetaRectAction* pA = (const MetaRectAction*) pAction;
					m_rOuterFace.DrawRect( pA->GetRect() );
				}
				break;

				case( META_ROUNDRECT_ACTION	):
				{
					const MetaRoundRectAction* pA = (const MetaRoundRectAction*) pAction;
					m_rOuterFace.DrawRect( pA->GetRect(), pA->GetHorzRound(), pA->GetVertRound() );
				}
				break;

				case( META_ELLIPSE_ACTION ):
				{
					const MetaEllipseAction* pA = (const MetaEllipseAction*) pAction;
					m_rOuterFace.DrawEllipse( pA->GetRect() );
				}
				break;

				case( META_ARC_ACTION ):
				{
					const MetaArcAction* pA = (const MetaArcAction*) pAction;
					m_rOuterFace.DrawArc( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint() );
				}
				break;

				case( META_PIE_ACTION ):
				{
					const MetaArcAction* pA = (const MetaArcAction*) pAction;
					m_rOuterFace.DrawPie( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint() );
				}
				break;

				case( META_CHORD_ACTION	):
				{
					const MetaChordAction* pA = (const MetaChordAction*) pAction;
					m_rOuterFace.DrawChord( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint() );
				}
				break;

				case( META_POLYGON_ACTION ):
				{
					const MetaPolygonAction* pA = (const MetaPolygonAction*) pAction;
					m_rOuterFace.DrawPolygon( pA->GetPolygon() );
				}
				break;

				case( META_POLYLINE_ACTION ):
    			{
					const MetaPolyLineAction* pA = (const MetaPolyLineAction*) pAction;
					if ( pA->GetLineInfo().IsDefault() )
						m_rOuterFace.DrawPolyLine( pA->GetPolygon() );
					else
						m_rOuterFace.DrawPolyLine( pA->GetPolygon(), pA->GetLineInfo() );
				}
				break;

				case( META_POLYPOLYGON_ACTION ):
				{
					const MetaPolyPolygonAction* pA = (const MetaPolyPolygonAction*) pAction;
					m_rOuterFace.DrawPolyPolygon( pA->GetPolyPolygon() );
				}
				break;

				case( META_GRADIENT_ACTION ):
				{
					const MetaGradientAction* pA = (const MetaGradientAction*) pAction;
					#ifdef USE_PDFGRADIENTS
					m_rOuterFace.DrawGradient( pA->GetRect(), pA->GetGradient() );
					#else
					const PolyPolygon         aPolyPoly( pA->GetRect() );
					implWriteGradient( aPolyPoly, pA->GetGradient(), pDummyVDev, i_rContext );
					#endif
				}
				break;

				case( META_GRADIENTEX_ACTION ):
				{
					const MetaGradientExAction*	pA = (const MetaGradientExAction*) pAction;
					#ifdef USE_PDFGRADIENTS
					m_rOuterFace.DrawGradient( pA->GetPolyPolygon(), pA->GetGradient() );
					#else
					implWriteGradient( pA->GetPolyPolygon(), pA->GetGradient(), pDummyVDev, i_rContext );
					#endif
				}
				break;

				case META_HATCH_ACTION:
				{
					const MetaHatchAction*	pA = (const MetaHatchAction*) pAction;
					m_rOuterFace.DrawHatch( pA->GetPolyPolygon(), pA->GetHatch() );
				}
				break;

				case( META_TRANSPARENT_ACTION ):
				{
					const MetaTransparentAction* pA = (const MetaTransparentAction*) pAction;
					m_rOuterFace.DrawTransparent( pA->GetPolyPolygon(), pA->GetTransparence() );
				}
				break;

				case( META_FLOATTRANSPARENT_ACTION ):
				{
					const MetaFloatTransparentAction* pA = (const MetaFloatTransparentAction*) pAction;

					GDIMetaFile		aTmpMtf( pA->GetGDIMetaFile() );
					const Point&	rPos = pA->GetPoint();
					const Size&		rSize= pA->GetSize();
					const Gradient&	rTransparenceGradient = pA->GetGradient();

                    // special case constant alpha value
                    if( rTransparenceGradient.GetStartColor() == rTransparenceGradient.GetEndColor() )
                    {
                        const Color aTransCol( rTransparenceGradient.GetStartColor() );
                        const sal_uInt16 nTransPercent = aTransCol.GetLuminance() * 100 / 255;
                        m_rOuterFace.BeginTransparencyGroup();
                        playMetafile( aTmpMtf, NULL, i_rContext, pDummyVDev );
                        m_rOuterFace.EndTransparencyGroup( Rectangle( rPos, rSize ), nTransPercent );
                    }
                    else
                    {
                        const Size	aDstSizeTwip( pDummyVDev->PixelToLogic( pDummyVDev->LogicToPixel( rSize ), MAP_TWIP ) );
                        sal_Int32	nMaxBmpDPI = i_rContext.m_bOnlyLosslessCompression ? 300 : 72;
                        if( i_rContext.m_nMaxImageResolution > 50 )
                        {
                            if ( nMaxBmpDPI > i_rContext.m_nMaxImageResolution )
                                nMaxBmpDPI = i_rContext.m_nMaxImageResolution;
                        }
                        const sal_Int32	nPixelX = (sal_Int32)((double)aDstSizeTwip.Width() * (double)nMaxBmpDPI / 1440.0);
                        const sal_Int32 nPixelY = (sal_Int32)((double)aDstSizeTwip.Height() * (double)nMaxBmpDPI / 1440.0);
                        if ( nPixelX && nPixelY )
                        {
                            Size aDstSizePixel( nPixelX, nPixelY );
                            VirtualDevice* pVDev = new VirtualDevice;
                            if( pVDev->SetOutputSizePixel( aDstSizePixel ) )
                            {
                                Bitmap			aPaint, aMask;
                                AlphaMask		aAlpha;
                                Point			aPoint;

                                MapMode aMapMode( pDummyVDev->GetMapMode() );
                                aMapMode.SetOrigin( aPoint );
                                pVDev->SetMapMode( aMapMode );
                                Size aDstSize( pVDev->PixelToLogic( aDstSizePixel ) );

                                Point	aMtfOrigin( aTmpMtf.GetPrefMapMode().GetOrigin() );
                                if ( aMtfOrigin.X() || aMtfOrigin.Y() )
                                    aTmpMtf.Move( -aMtfOrigin.X(), -aMtfOrigin.Y() );
                                double	fScaleX = (double)aDstSize.Width() / (double)aTmpMtf.GetPrefSize().Width();
                                double	fScaleY = (double)aDstSize.Height() / (double)aTmpMtf.GetPrefSize().Height();
                                if( fScaleX != 1.0 || fScaleY != 1.0 )
                                    aTmpMtf.Scale( fScaleX, fScaleY );
                                aTmpMtf.SetPrefMapMode( aMapMode );

                                // create paint bitmap
                                aTmpMtf.WindStart();
                                aTmpMtf.Play( pVDev, aPoint, aDstSize );
                                aTmpMtf.WindStart();
    
                                pVDev->EnableMapMode( sal_False );
                                aPaint = pVDev->GetBitmap( aPoint, aDstSizePixel );
                                pVDev->EnableMapMode( sal_True );
    
                                // create mask bitmap
                                pVDev->SetLineColor( COL_BLACK );
                                pVDev->SetFillColor( COL_BLACK );
                                pVDev->DrawRect( Rectangle( aPoint, aDstSize ) );
                                pVDev->SetDrawMode( DRAWMODE_WHITELINE | DRAWMODE_WHITEFILL | DRAWMODE_WHITETEXT |
                                                    DRAWMODE_WHITEBITMAP | DRAWMODE_WHITEGRADIENT );
                                aTmpMtf.WindStart();
                                aTmpMtf.Play( pVDev, aPoint, aDstSize );
                                aTmpMtf.WindStart();
                                pVDev->EnableMapMode( sal_False );
                                aMask = pVDev->GetBitmap( aPoint, aDstSizePixel );
                                pVDev->EnableMapMode( sal_True );
    
                                // create alpha mask from gradient
                                pVDev->SetDrawMode( DRAWMODE_GRAYGRADIENT );
                                pVDev->DrawGradient( Rectangle( aPoint, aDstSize ), rTransparenceGradient );
                                pVDev->SetDrawMode( DRAWMODE_DEFAULT );
                                pVDev->EnableMapMode( sal_False );
                                pVDev->DrawMask( aPoint, aDstSizePixel, aMask, Color( COL_WHITE ) );
                                aAlpha = pVDev->GetBitmap( aPoint, aDstSizePixel );
                                implWriteBitmapEx( rPos, rSize, BitmapEx( aPaint, aAlpha ), pDummyVDev, i_rContext );
                            }
                            delete pVDev;
                        }
                    }
				}
				break;

				case( META_EPS_ACTION ):
				{
					const MetaEPSAction*	pA = (const MetaEPSAction*) pAction;
					const GDIMetaFile		aSubstitute( pA->GetSubstitute() );

					m_rOuterFace.Push();
					pDummyVDev->Push();

					MapMode	aMapMode( aSubstitute.GetPrefMapMode() );
					Size aOutSize( pDummyVDev->LogicToLogic( pA->GetSize(), pDummyVDev->GetMapMode(), aMapMode ) );
					aMapMode.SetScaleX( Fraction( aOutSize.Width(), aSubstitute.GetPrefSize().Width() ) );
					aMapMode.SetScaleY( Fraction( aOutSize.Height(), aSubstitute.GetPrefSize().Height() ) );
					aMapMode.SetOrigin( pDummyVDev->LogicToLogic( pA->GetPoint(), pDummyVDev->GetMapMode(), aMapMode ) );

					m_rOuterFace.SetMapMode( aMapMode );
					pDummyVDev->SetMapMode( aMapMode );
					playMetafile( aSubstitute, NULL, i_rContext, pDummyVDev );
					pDummyVDev->Pop();
					m_rOuterFace.Pop();
				}
				break;

				case( META_COMMENT_ACTION ):
                if( ! i_rContext.m_bTransparenciesWereRemoved )
				{
					const MetaCommentAction*	pA = (const MetaCommentAction*) pAction;
					String						aSkipComment;

					if( pA->GetComment().CompareIgnoreCaseToAscii( "XGRAD_SEQ_BEGIN" ) == COMPARE_EQUAL )
					{
						const MetaGradientExAction*	pGradAction = NULL;
						sal_Bool					bDone = sal_False;

						while( !bDone && ( ++i < nCount ) )
						{
							pAction = aMtf.GetAction( i );

							if( pAction->GetType() == META_GRADIENTEX_ACTION )
								pGradAction = (const MetaGradientExAction*) pAction;
							else if( ( pAction->GetType() == META_COMMENT_ACTION ) &&
									( ( (const MetaCommentAction*) pAction )->GetComment().CompareIgnoreCaseToAscii( "XGRAD_SEQ_END" ) == COMPARE_EQUAL ) )
							{
								bDone = sal_True;
							}
						}

						if( pGradAction )
						{
						    #if USE_PDFGRADIENTS
							m_rOuterFace.DrawGradient( pGradAction->GetPolyPolygon(), pGradAction->GetGradient() );
							#else
							implWriteGradient( pGradAction->GetPolyPolygon(), pGradAction->GetGradient(), pDummyVDev, i_rContext );
							#endif
						}
					}
					else
					{
						const sal_uInt8* pData = pA->GetData();
						if ( pData )
						{
							SvMemoryStream	aMemStm( (void*)pData, pA->GetDataSize(), STREAM_READ );
							sal_Bool		bSkipSequence = sal_False;
							ByteString		sSeqEnd;

							if( pA->GetComment().Equals( "XPATHSTROKE_SEQ_BEGIN" ) )
							{
								sSeqEnd = ByteString( "XPATHSTROKE_SEQ_END" );
								SvtGraphicStroke aStroke;
								aMemStm >> aStroke;

								Polygon aPath;
								aStroke.getPath( aPath );

								PolyPolygon aStartArrow;
								PolyPolygon aEndArrow;
								double fTransparency( aStroke.getTransparency() );
								double fStrokeWidth( aStroke.getStrokeWidth() );
								SvtGraphicStroke::DashArray aDashArray;

								aStroke.getStartArrow( aStartArrow );
								aStroke.getEndArrow( aEndArrow );
								aStroke.getDashArray( aDashArray );

								bSkipSequence = sal_True;
								if ( aStartArrow.Count() || aEndArrow.Count() )
									bSkipSequence = sal_False;
								if ( aDashArray.size() && ( fStrokeWidth != 0.0 ) && ( fTransparency == 0.0 ) )
									bSkipSequence = sal_False;
								if ( bSkipSequence )
								{
									PDFWriter::ExtLineInfo aInfo;
                                    aInfo.m_fLineWidth      = fStrokeWidth;
                                    aInfo.m_fTransparency   = fTransparency;
                                    aInfo.m_fMiterLimit     = aStroke.getMiterLimit();
                                    switch( aStroke.getCapType() )
                                    {
                                        default:
                                        case SvtGraphicStroke::capButt:   aInfo.m_eCap = PDFWriter::capButt;break;
                                        case SvtGraphicStroke::capRound:  aInfo.m_eCap = PDFWriter::capRound;break;
                                        case SvtGraphicStroke::capSquare: aInfo.m_eCap = PDFWriter::capSquare;break;
                                    }
                                    switch( aStroke.getJoinType() )
                                    {
                                        default:
                                        case SvtGraphicStroke::joinMiter: aInfo.m_eJoin = PDFWriter::joinMiter;break;
                                        case SvtGraphicStroke::joinRound: aInfo.m_eJoin = PDFWriter::joinRound;break;
                                        case SvtGraphicStroke::joinBevel: aInfo.m_eJoin = PDFWriter::joinBevel;break;
                                        case SvtGraphicStroke::joinNone:
                                            aInfo.m_eJoin = PDFWriter::joinMiter;
                                            aInfo.m_fMiterLimit = 0.0;
                                            break;
                                    }
                                    aInfo.m_aDashArray = aDashArray;

                                    if(SvtGraphicStroke::joinNone == aStroke.getJoinType()
                                        && fStrokeWidth > 0.0)
                                    {
                                        // emulate no edge rounding by handling single edges
                                        const sal_uInt16 nPoints(aPath.GetSize());
                                        const bool bCurve(aPath.HasFlags());

                                        for(sal_uInt16 a(0); a + 1 < nPoints; a++)
                                        {
                                            if(bCurve
                                                && POLY_NORMAL != aPath.GetFlags(a + 1)
                                                && a + 2 < nPoints
                                                && POLY_NORMAL != aPath.GetFlags(a + 2)
                                                && a + 3 < nPoints)
                                            {
                								const Polygon aSnippet(4,
                                                    aPath.GetConstPointAry() + a,
                                                    aPath.GetConstFlagAry() + a);
                                                m_rOuterFace.DrawPolyLine( aSnippet, aInfo );
                                                a += 2;
                                            }
                                            else
                                            {
                								const Polygon aSnippet(2,
                                                    aPath.GetConstPointAry() + a);
                                                m_rOuterFace.DrawPolyLine( aSnippet, aInfo );
                                            }
                                        }
                                    }
                                    else
                                    {
                                        m_rOuterFace.DrawPolyLine( aPath, aInfo );
                                    }
								}
							}
							else if ( pA->GetComment().Equals( "XPATHFILL_SEQ_BEGIN" ) )
							{
								sSeqEnd = ByteString( "XPATHFILL_SEQ_END" );
								SvtGraphicFill aFill;
								aMemStm >> aFill;

								if ( ( aFill.getFillType() == SvtGraphicFill::fillSolid ) && ( aFill.getFillRule() == SvtGraphicFill::fillEvenOdd ) )
								{
									double fTransparency = aFill.getTransparency();
									if ( fTransparency == 0.0 )
									{
										PolyPolygon aPath;
										aFill.getPath( aPath );

										bSkipSequence = sal_True;
										m_rOuterFace.DrawPolyPolygon( aPath );
									}
									else if ( fTransparency == 1.0 )
										bSkipSequence = sal_True;
								}
/* #i81548# removing optimization for fill textures, because most of the texture settings are not
   exported properly. In OpenOffice 3.1 the drawing layer will support graphic primitives, then it
   will not be a problem to optimize the filltexture export. But for wysiwyg is more important than
   filesize.
                                else if( aFill.getFillType() == SvtGraphicFill::fillTexture && aFill.isTiling() )
                                {
                                    sal_Int32 nPattern = mnCachePatternId;
                                    Graphic aPatternGraphic;
                                    aFill.getGraphic( aPatternGraphic );
                                    bool bUseCache = false;
                                    SvtGraphicFill::Transform aPatTransform;
                                    aFill.getTransform( aPatTransform );

                                    if(  mnCachePatternId >= 0 )
                                    {
                                        SvtGraphicFill::Transform aCacheTransform;
                                        maCacheFill.getTransform( aCacheTransform );
                                        if( aCacheTransform.matrix[0] == aPatTransform.matrix[0] &&
                                            aCacheTransform.matrix[1] == aPatTransform.matrix[1] &&
                                            aCacheTransform.matrix[2] == aPatTransform.matrix[2] &&
                                            aCacheTransform.matrix[3] == aPatTransform.matrix[3] &&
                                            aCacheTransform.matrix[4] == aPatTransform.matrix[4] &&
                                            aCacheTransform.matrix[5] == aPatTransform.matrix[5]
                                            )
                                        {
                                            Graphic aCacheGraphic;
                                            maCacheFill.getGraphic( aCacheGraphic );
                                            if( aCacheGraphic == aPatternGraphic )
                                                bUseCache = true;
                                        }
                                    }

                                    if( ! bUseCache )
                                    {

                                        // paint graphic to metafile
                                        GDIMetaFile aPattern;
                                        pDummyVDev->SetConnectMetaFile( &aPattern );
                                        pDummyVDev->Push();
                                        pDummyVDev->SetMapMode( aPatternGraphic.GetPrefMapMode() );

                                        aPatternGraphic.Draw( &rDummyVDev, Point( 0, 0 ) );
                                        pDummyVDev->Pop();
                                        pDummyVDev->SetConnectMetaFile( NULL );
                                        aPattern.WindStart();

                                        MapMode	aPatternMapMode( aPatternGraphic.GetPrefMapMode() );
                                        // prepare pattern from metafile
                                        Size aPrefSize( aPatternGraphic.GetPrefSize() );
                                        // FIXME: this magic -1 shouldn't be necessary
                                        aPrefSize.Width() -= 1;
                                        aPrefSize.Height() -= 1;
                                        aPrefSize = m_rOuterFace.GetReferenceDevice()->
                                            LogicToLogic( aPrefSize,
                                                          &aPatternMapMode,
                                                          &m_rOuterFace.GetReferenceDevice()->GetMapMode() );
                                        // build bounding rectangle of pattern
                                        Rectangle aBound( Point( 0, 0 ), aPrefSize );
                                        m_rOuterFace.BeginPattern( aBound );
                                        m_rOuterFace.Push();
                                        pDummyVDev->Push();
                                        m_rOuterFace.SetMapMode( aPatternMapMode );
                                        pDummyVDev->SetMapMode( aPatternMapMode );
                                        ImplWriteActions( m_rOuterFace, NULL, aPattern, rDummyVDev );
                                        pDummyVDev->Pop();
                                        m_rOuterFace.Pop();

                                        nPattern = m_rOuterFace.EndPattern( aPatTransform );

                                        // try some caching and reuse pattern
                                        mnCachePatternId = nPattern;
                                        maCacheFill = aFill;
                                    }

                                    // draw polypolygon with pattern fill
                                    PolyPolygon aPath;
                                    aFill.getPath( aPath );
                                    m_rOuterFace.DrawPolyPolygon( aPath, nPattern, aFill.getFillRule() == SvtGraphicFill::fillEvenOdd );

                                    bSkipSequence = sal_True;
                                }
*/
							}
							if ( bSkipSequence )
							{
								while( ++i < nCount )
								{
									pAction = aMtf.GetAction( i );
									if ( pAction->GetType() == META_COMMENT_ACTION )
									{
										ByteString sComment( ((MetaCommentAction*)pAction)->GetComment() );
										if ( sComment.Equals( sSeqEnd ) )
											break;
									}
                                    // #i44496#
                                    // the replacement action for stroke is a filled rectangle
                                    // the set fillcolor of the replacement is part of the graphics
                                    // state and must not be skipped
                                    else if( pAction->GetType() == META_FILLCOLOR_ACTION )
                                    {
                                        const MetaFillColorAction* pMA = (const MetaFillColorAction*) pAction;
                                        if( pMA->IsSetting() )
                                            m_rOuterFace.SetFillColor( pMA->GetColor() );
                                        else
                                            m_rOuterFace.SetFillColor();
                                    }
								}
							}
						}
					}
				}
				break;

				case( META_BMP_ACTION ):
				{
					const MetaBmpAction* pA = (const MetaBmpAction*) pAction;
					BitmapEx aBitmapEx( pA->GetBitmap() );
					Size aSize( OutputDevice::LogicToLogic( aBitmapEx.GetPrefSize(),
							aBitmapEx.GetPrefMapMode(), pDummyVDev->GetMapMode() ) );
                    if( ! ( aSize.Width() && aSize.Height() ) )
                        aSize = pDummyVDev->PixelToLogic( aBitmapEx.GetSizePixel() );
					implWriteBitmapEx( pA->GetPoint(), aSize, aBitmapEx, pDummyVDev, i_rContext );
				}
				break;

				case( META_BMPSCALE_ACTION ):
				{
					const MetaBmpScaleAction* pA = (const MetaBmpScaleAction*) pAction;
					implWriteBitmapEx( pA->GetPoint(), pA->GetSize(), BitmapEx( pA->GetBitmap() ), pDummyVDev, i_rContext );
				}
				break;

				case( META_BMPSCALEPART_ACTION ):
				{
					const MetaBmpScalePartAction* pA = (const MetaBmpScalePartAction*) pAction;
					BitmapEx aBitmapEx( pA->GetBitmap() );
					aBitmapEx.Crop( Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) );
					implWriteBitmapEx( pA->GetDestPoint(), pA->GetDestSize(), aBitmapEx, pDummyVDev, i_rContext );
				}
				break;

				case( META_BMPEX_ACTION	):
				{
					const MetaBmpExAction*	pA = (const MetaBmpExAction*) pAction;
					BitmapEx aBitmapEx( pA->GetBitmapEx() );
					Size aSize( OutputDevice::LogicToLogic( aBitmapEx.GetPrefSize(),
							aBitmapEx.GetPrefMapMode(), pDummyVDev->GetMapMode() ) );
					implWriteBitmapEx( pA->GetPoint(), aSize, aBitmapEx, pDummyVDev, i_rContext );
				}
				break;

				case( META_BMPEXSCALE_ACTION ):
				{
					const MetaBmpExScaleAction* pA = (const MetaBmpExScaleAction*) pAction;
					implWriteBitmapEx( pA->GetPoint(), pA->GetSize(), pA->GetBitmapEx(), pDummyVDev, i_rContext );
				}
				break;

				case( META_BMPEXSCALEPART_ACTION ):
				{
					const MetaBmpExScalePartAction* pA = (const MetaBmpExScalePartAction*) pAction;
					BitmapEx aBitmapEx( pA->GetBitmapEx() );
					aBitmapEx.Crop( Rectangle( pA->GetSrcPoint(), pA->GetSrcSize() ) );
					implWriteBitmapEx( pA->GetDestPoint(), pA->GetDestSize(), aBitmapEx, pDummyVDev, i_rContext );
				}
				break;

				case( META_MASK_ACTION ):
				case( META_MASKSCALE_ACTION	):
				case( META_MASKSCALEPART_ACTION	):
				{
					DBG_ERROR( "MetaMask...Action not supported yet" );
				}
				break;

				case( META_TEXT_ACTION ):
				{
					const MetaTextAction* pA = (const MetaTextAction*) pAction;
					m_rOuterFace.DrawText( pA->GetPoint(), String( pA->GetText(), pA->GetIndex(), pA->GetLen() ) );
				}
				break;

				case( META_TEXTRECT_ACTION ):
				{
					const MetaTextRectAction* pA = (const MetaTextRectAction*) pAction;
					m_rOuterFace.DrawText( pA->GetRect(), String( pA->GetText() ), pA->GetStyle() );
				}
				break;

				case( META_TEXTARRAY_ACTION	):
				{
					const MetaTextArrayAction* pA = (const MetaTextArrayAction*) pAction;
					m_rOuterFace.DrawTextArray( pA->GetPoint(), pA->GetText(), pA->GetDXArray(), pA->GetIndex(), pA->GetLen() );
				}
				break;

				case( META_STRETCHTEXT_ACTION ):
				{
					const MetaStretchTextAction* pA = (const MetaStretchTextAction*) pAction;
					m_rOuterFace.DrawStretchText( pA->GetPoint(), pA->GetWidth(), pA->GetText(), pA->GetIndex(), pA->GetLen() );
				}
				break;


				case( META_TEXTLINE_ACTION ):
				{
					const MetaTextLineAction* pA = (const MetaTextLineAction*) pAction;
					m_rOuterFace.DrawTextLine( pA->GetStartPoint(), pA->GetWidth(), pA->GetStrikeout(), pA->GetUnderline(), pA->GetOverline() );

				}
				break;

				case( META_CLIPREGION_ACTION ):
				{
					const MetaClipRegionAction* pA = (const MetaClipRegionAction*) pAction;

					if( pA->IsClipping() )
					{
					    if( pA->GetRegion().IsEmpty() )
					        m_rOuterFace.SetClipRegion( basegfx::B2DPolyPolygon() );
					    else
					    {
					        Region aReg( pA->GetRegion() );
					        m_rOuterFace.SetClipRegion( aReg.ConvertToB2DPolyPolygon() );
					    }
					}
					else
						m_rOuterFace.SetClipRegion();
				}
				break;

				case( META_ISECTRECTCLIPREGION_ACTION ):
				{
					const MetaISectRectClipRegionAction* pA = (const MetaISectRectClipRegionAction*) pAction;
					m_rOuterFace.IntersectClipRegion( pA->GetRect() );
				}
				break;

				case( META_ISECTREGIONCLIPREGION_ACTION	):
				{
				    const MetaISectRegionClipRegionAction* pA = (const MetaISectRegionClipRegionAction*) pAction;
				    Region aReg( pA->GetRegion() );
				    m_rOuterFace.IntersectClipRegion( aReg.ConvertToB2DPolyPolygon() );
				}
				break;

				case( META_MOVECLIPREGION_ACTION ):
				{
					const MetaMoveClipRegionAction* pA = (const MetaMoveClipRegionAction*) pAction;
					m_rOuterFace.MoveClipRegion( pA->GetHorzMove(), pA->GetVertMove() );
				}
				break;

				case( META_MAPMODE_ACTION ):
				{
					const_cast< MetaAction* >( pAction )->Execute( pDummyVDev );
					m_rOuterFace.SetMapMode( pDummyVDev->GetMapMode() );
				}
				break;

				case( META_LINECOLOR_ACTION	):
				{
					const MetaLineColorAction* pA = (const MetaLineColorAction*) pAction;

					if( pA->IsSetting() )
						m_rOuterFace.SetLineColor( pA->GetColor() );
					else
						m_rOuterFace.SetLineColor();
				}
				break;

				case( META_FILLCOLOR_ACTION	):
				{
					const MetaFillColorAction* pA = (const MetaFillColorAction*) pAction;

					if( pA->IsSetting() )
						m_rOuterFace.SetFillColor( pA->GetColor() );
					else
						m_rOuterFace.SetFillColor();
				}
				break;

				case( META_TEXTLINECOLOR_ACTION ):
				{
					const MetaTextLineColorAction* pA = (const MetaTextLineColorAction*) pAction;

					if( pA->IsSetting() )
						m_rOuterFace.SetTextLineColor( pA->GetColor() );
					else
						m_rOuterFace.SetTextLineColor();
				}
				break;

				case( META_OVERLINECOLOR_ACTION ):
				{
					const MetaOverlineColorAction* pA = (const MetaOverlineColorAction*) pAction;

					if( pA->IsSetting() )
						m_rOuterFace.SetOverlineColor( pA->GetColor() );
					else
						m_rOuterFace.SetOverlineColor();
				}
				break;

				case( META_TEXTFILLCOLOR_ACTION	):
				{
					const MetaTextFillColorAction* pA = (const MetaTextFillColorAction*) pAction;

					if( pA->IsSetting() )
						m_rOuterFace.SetTextFillColor( pA->GetColor() );
					else
						m_rOuterFace.SetTextFillColor();
				}
				break;

				case( META_TEXTCOLOR_ACTION	):
				{
					const MetaTextColorAction* pA = (const MetaTextColorAction*) pAction;
					m_rOuterFace.SetTextColor( pA->GetColor() );
				}
				break;

				case( META_TEXTALIGN_ACTION	):
				{
					const MetaTextAlignAction* pA = (const MetaTextAlignAction*) pAction;
					m_rOuterFace.SetTextAlign( pA->GetTextAlign() );
				}
				break;

				case( META_FONT_ACTION ):
				{
					const MetaFontAction* pA = (const MetaFontAction*) pAction;
					m_rOuterFace.SetFont( pA->GetFont() );
				}
				break;

				case( META_PUSH_ACTION ):
				{
					const MetaPushAction* pA = (const MetaPushAction*) pAction;

					pDummyVDev->Push( pA->GetFlags() );
					m_rOuterFace.Push( pA->GetFlags() );
				}
				break;

				case( META_POP_ACTION ):
				{
					pDummyVDev->Pop();
					m_rOuterFace.Pop();
				}
				break;

				case( META_LAYOUTMODE_ACTION ):
				{
					const MetaLayoutModeAction* pA = (const MetaLayoutModeAction*) pAction;
					m_rOuterFace.SetLayoutMode( pA->GetLayoutMode() );
				}
				break;

				case META_TEXTLANGUAGE_ACTION:
				{
					const  MetaTextLanguageAction* pA = (const MetaTextLanguageAction*) pAction;
                    m_rOuterFace.SetDigitLanguage( pA->GetTextLanguage() );
				}
				break;

				case( META_WALLPAPER_ACTION	):
				{
					const MetaWallpaperAction* pA = (const MetaWallpaperAction*) pAction;
					m_rOuterFace.DrawWallpaper( pA->GetRect(), pA->GetWallpaper() );
				}
				break;

				case( META_RASTEROP_ACTION ):
				{
					// !!! >>> we don't want to support this actions
				}
				break;

				case( META_REFPOINT_ACTION ):
				{
					// !!! >>> we don't want to support this actions
				}
				break;

				case( META_RENDERGRAPHIC_ACTION ):
				{
					const MetaRenderGraphicAction* pA = static_cast< const MetaRenderGraphicAction* >( pAction );
                    const ::vcl::RenderGraphicRasterizer aRasterizer( pA->GetRenderGraphic() );

					implWriteBitmapEx( pA->GetPoint(), pA->GetSize(),
                                       aRasterizer.Rasterize( pDummyVDev->LogicToPixel( pA->GetSize() ) ),
                                       pDummyVDev, i_rContext );
				}
				break;

				default:
					// #i24604# Made assertion fire only once per
					// metafile. The asserted actions here are all
					// deprecated
					if( !bAssertionFired )
					{
						bAssertionFired = true;
						DBG_ERROR( "PDFExport::ImplWriteActions: deprecated and unsupported MetaAction encountered" );
					}
				break;
			}
			i++;
		}
	}

    delete pPrivateDevice;
}

// Encryption methods

/* a crutch to transport an rtlDigest safely though UNO API
   this is needed for the PDF export dialog, which otherwise would have to pass
   clear text passwords down till they can be used in PDFWriter. Unfortunately
   the MD5 sum of the password (which is needed to create the PDF encryption key)
   is not sufficient, since an rtl MD5 digest cannot be created in an arbitrary state
   which would be needed in PDFWriterImpl::computeEncryptionKey.
*/
class EncHashTransporter : public cppu::WeakImplHelper1 < com::sun::star::beans::XMaterialHolder >
{
    rtlDigest                   maUDigest;
    sal_IntPtr                  maID;
    std::vector< sal_uInt8 >    maOValue;

    static std::map< sal_IntPtr, EncHashTransporter* >      sTransporters;
public:
    EncHashTransporter()
    : maUDigest( rtl_digest_createMD5() )
    {
        maID = reinterpret_cast< sal_IntPtr >(this);
        while( sTransporters.find( maID ) != sTransporters.end() ) // paranoia mode
            maID++;
        sTransporters[ maID ] = this;
    }

    virtual ~EncHashTransporter()
    {
        sTransporters.erase( maID );
        if( maUDigest )
            rtl_digest_destroyMD5( maUDigest );
        OSL_TRACE( "EncHashTransporter freed\n" );
    }

    rtlDigest getUDigest() const { return maUDigest; };
    std::vector< sal_uInt8 >& getOValue() { return maOValue; }
    void invalidate()
    {
        if( maUDigest )
        {
            rtl_digest_destroyMD5( maUDigest );
            maUDigest = NULL;
        }
    }

    // XMaterialHolder
    virtual uno::Any SAL_CALL getMaterial() throw()
    {
        return uno::makeAny( sal_Int64(maID) );
    }

    static EncHashTransporter* getEncHashTransporter( const uno::Reference< beans::XMaterialHolder >& );

};

std::map< sal_IntPtr, EncHashTransporter* > EncHashTransporter::sTransporters;

EncHashTransporter* EncHashTransporter::getEncHashTransporter( const uno::Reference< beans::XMaterialHolder >& xRef )
{
    EncHashTransporter* pResult = NULL;
    if( xRef.is() )
    {
        uno::Any aMat( xRef->getMaterial() );
        sal_Int64 nMat = 0;
        if( aMat >>= nMat )
        {
            std::map< sal_IntPtr, EncHashTransporter* >::iterator it = sTransporters.find( static_cast<sal_IntPtr>(nMat) );
            if( it != sTransporters.end() )
                pResult = it->second;
        }
    }
    return pResult;
}

sal_Bool PDFWriterImpl::checkEncryptionBufferSize( register sal_Int32 newSize )
{
    if( m_nEncryptionBufferSize < newSize )
    {
        /* reallocate the buffer, the used function allocate as rtl_allocateMemory
        if the pointer parameter is NULL */
        m_pEncryptionBuffer = (sal_uInt8*)rtl_reallocateMemory( m_pEncryptionBuffer, newSize );
        if( m_pEncryptionBuffer )
            m_nEncryptionBufferSize = newSize;
        else
            m_nEncryptionBufferSize = 0;
    }
    return ( m_nEncryptionBufferSize != 0 );
}

void PDFWriterImpl::checkAndEnableStreamEncryption( register sal_Int32 nObject )
{
    if( m_aContext.Encryption.Encrypt() )
    {
        m_bEncryptThisStream = true;
        sal_Int32 i = m_nKeyLength;
        m_aContext.Encryption.EncryptionKey[i++] = (sal_uInt8)nObject;
        m_aContext.Encryption.EncryptionKey[i++] = (sal_uInt8)( nObject >> 8 );
        m_aContext.Encryption.EncryptionKey[i++] = (sal_uInt8)( nObject >> 16 );
        //the other location of m_nEncryptionKey are already set to 0, our fixed generation number
        // do the MD5 hash
        sal_uInt8 nMD5Sum[ RTL_DIGEST_LENGTH_MD5 ];
        // the i+2 to take into account the generation number, always zero
        rtl_digest_MD5( &m_aContext.Encryption.EncryptionKey[0], i+2, nMD5Sum, sizeof(nMD5Sum) );
        // initialize the RC4 with the key
        // key legth: see algoritm 3.1, step 4: (N+5) max 16
        rtl_cipher_initARCFOUR( m_aCipher, rtl_Cipher_DirectionEncode, nMD5Sum, m_nRC4KeyLength, NULL, 0 );
    }
}

void PDFWriterImpl::enableStringEncryption( register sal_Int32 nObject )
{
    if( m_aContext.Encryption.Encrypt() )
    {
        sal_Int32 i = m_nKeyLength;
        m_aContext.Encryption.EncryptionKey[i++] = (sal_uInt8)nObject;
        m_aContext.Encryption.EncryptionKey[i++] = (sal_uInt8)( nObject >> 8 );
        m_aContext.Encryption.EncryptionKey[i++] = (sal_uInt8)( nObject >> 16 );
        //the other location of m_nEncryptionKey are already set to 0, our fixed generation number
        // do the MD5 hash
        sal_uInt8 nMD5Sum[ RTL_DIGEST_LENGTH_MD5 ];
        // the i+2 to take into account the generation number, always zero
        rtl_digest_MD5( &m_aContext.Encryption.EncryptionKey[0], i+2, nMD5Sum, sizeof(nMD5Sum) );
        // initialize the RC4 with the key
        // key legth: see algoritm 3.1, step 4: (N+5) max 16
        rtl_cipher_initARCFOUR( m_aCipher, rtl_Cipher_DirectionEncode, nMD5Sum, m_nRC4KeyLength, NULL, 0 );
    }
}

/* 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
 */
uno::Reference< beans::XMaterialHolder > PDFWriterImpl::initEncryption( const rtl::OUString& i_rOwnerPassword,
                                                                        const rtl::OUString& i_rUserPassword,
                                                                        bool b128Bit
                                                                        )
{
    uno::Reference< beans::XMaterialHolder > xResult;
    if( i_rOwnerPassword.getLength() || i_rUserPassword.getLength() )
    {
        EncHashTransporter* pTransporter = new EncHashTransporter;
        xResult = pTransporter;

        // get padded passwords
        sal_uInt8 aPadUPW[ENCRYPTED_PWD_SIZE], aPadOPW[ENCRYPTED_PWD_SIZE];
        padPassword( i_rOwnerPassword.getLength() ? i_rOwnerPassword : i_rUserPassword, aPadOPW );
        padPassword( i_rUserPassword, aPadUPW );
        sal_Int32 nKeyLength = SECUR_40BIT_KEY;
        if( b128Bit )
            nKeyLength = SECUR_128BIT_KEY;

        if( computeODictionaryValue( aPadOPW, aPadUPW, pTransporter->getOValue(), nKeyLength ) )
        {
            rtlDigest aDig = pTransporter->getUDigest();
            if( rtl_digest_updateMD5( aDig, aPadUPW, ENCRYPTED_PWD_SIZE ) != rtl_Digest_E_None )
                xResult.clear();
        }
        else
            xResult.clear();

        // trash temporary padded cleartext PWDs
        rtl_zeroMemory( aPadOPW, sizeof(aPadOPW) );
        rtl_zeroMemory( aPadUPW, sizeof(aPadUPW) );

    }
    return xResult;
}

bool PDFWriterImpl::prepareEncryption( const uno::Reference< beans::XMaterialHolder >& xEnc )
{
    bool bSuccess = false;
    EncHashTransporter* pTransporter = EncHashTransporter::getEncHashTransporter( xEnc );
    if( pTransporter )
    {
        sal_Int32 nKeyLength = 0, nRC4KeyLength = 0;
        sal_Int32 nAccessPermissions = computeAccessPermissions( m_aContext.Encryption, nKeyLength, nRC4KeyLength );
        m_aContext.Encryption.OValue = pTransporter->getOValue();
        bSuccess = computeUDictionaryValue( pTransporter, m_aContext.Encryption, nKeyLength, nAccessPermissions );
    }
    if( ! bSuccess )
    {
        m_aContext.Encryption.OValue.clear();
        m_aContext.Encryption.UValue.clear();
        m_aContext.Encryption.EncryptionKey.clear();
    }
    return bSuccess;
}

sal_Int32 PDFWriterImpl::computeAccessPermissions( const vcl::PDFWriter::PDFEncryptionProperties& i_rProperties,
	                                               sal_Int32& o_rKeyLength, sal_Int32& o_rRC4KeyLength )
{
    /*
    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 */
    sal_Int32 nAccessPermissions = ( i_rProperties.Security128bit ) ? 0xfffff0c0 : 0xffffffc0 ;

    /* check permissions for 40 bit security case */
    nAccessPermissions |= ( i_rProperties.CanPrintTheDocument ) ?  1 << 2 : 0;
    nAccessPermissions |= ( i_rProperties.CanModifyTheContent ) ? 1 << 3 : 0;
    nAccessPermissions |= ( i_rProperties.CanCopyOrExtract ) ?   1 << 4 : 0;
    nAccessPermissions |= ( i_rProperties.CanAddOrModify ) ? 1 << 5 : 0;
    o_rKeyLength = SECUR_40BIT_KEY;
    o_rRC4KeyLength = SECUR_40BIT_KEY+5; // for this value see PDF spec v 1.4, algorithm 3.1 step 4, where n is 5

    if( i_rProperties.Security128bit )
    {
        o_rKeyLength = SECUR_128BIT_KEY;
        o_rRC4KeyLength = 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
        nAccessPermissions |= ( i_rProperties.CanFillInteractive ) ?         1 << 8 : 0;
        nAccessPermissions |= ( i_rProperties.CanExtractForAccessibility ) ? 1 << 9 : 0;
        nAccessPermissions |= ( i_rProperties.CanAssemble ) ?                1 << 10 : 0;
        nAccessPermissions |= ( i_rProperties.CanPrintFull ) ?               1 << 11 : 0;
    }
    return nAccessPermissions;
}

/*************************************************************
begin i12626 methods

Implements Algorithm 3.2, step 1 only
*/
void PDFWriterImpl::padPassword( const rtl::OUString& i_rPassword, sal_uInt8* o_pPaddedPW )
{
    // get ansi-1252 version of the password string CHECKIT ! i12626
    rtl::OString aString( rtl::OUStringToOString( i_rPassword, RTL_TEXTENCODING_MS_1252 ) );

    //copy the string to the target
    sal_Int32 nToCopy = ( aString.getLength() < ENCRYPTED_PWD_SIZE ) ? aString.getLength() : ENCRYPTED_PWD_SIZE;
    sal_Int32 nCurrentChar;

    for( nCurrentChar = 0; nCurrentChar < nToCopy; nCurrentChar++ )
        o_pPaddedPW[nCurrentChar] = (sal_uInt8)( aString.getStr()[nCurrentChar] );

    //pad it with standard byte string
    sal_Int32 i,y;
    for( i = nCurrentChar, y = 0 ; i < ENCRYPTED_PWD_SIZE; i++, y++ )
        o_pPaddedPW[i] = s_nPadString[y];

    // trash memory of temporary clear text password
    rtl_zeroMemory( (sal_Char*)aString.getStr(), aString.getLength() );
}

/**********************************
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.

*/
bool PDFWriterImpl::computeEncryptionKey( EncHashTransporter* i_pTransporter, vcl::PDFWriter::PDFEncryptionProperties& io_rProperties, sal_Int32 i_nAccessPermissions )
{
    bool bSuccess = true;
    sal_uInt8 nMD5Sum[ RTL_DIGEST_LENGTH_MD5 ];

    // transporter contains an MD5 digest with the padded user password already
    rtlDigest aDigest = i_pTransporter->getUDigest();
    rtlDigestError nError = rtl_Digest_E_None;
    if( aDigest )
    {
        //step 3
        if( ! io_rProperties.OValue.empty() )
            nError = rtl_digest_updateMD5( aDigest, &io_rProperties.OValue[0] , sal_Int32(io_rProperties.OValue.size()) );
        else
            bSuccess = false;
        //Step 4
        sal_uInt8 nPerm[4];

        nPerm[0] = (sal_uInt8)i_nAccessPermissions;
        nPerm[1] = (sal_uInt8)( i_nAccessPermissions >> 8 );
        nPerm[2] = (sal_uInt8)( i_nAccessPermissions >> 16 );
        nPerm[3] = (sal_uInt8)( i_nAccessPermissions >> 24 );

        if( nError == rtl_Digest_E_None )
            nError = rtl_digest_updateMD5( aDigest, nPerm , sizeof( nPerm ) );

        //step 5, get the document ID, binary form
        if( nError == rtl_Digest_E_None )
            nError = rtl_digest_updateMD5( aDigest, &io_rProperties.DocumentIdentifier[0], sal_Int32(io_rProperties.DocumentIdentifier.size()) );
        //get the digest
        if( nError == rtl_Digest_E_None )
        {
            rtl_digest_getMD5( aDigest, nMD5Sum, sizeof( nMD5Sum ) );

            //step 6, only if 128 bit
            if( io_rProperties.Security128bit )
            {
                for( sal_Int32 i = 0; i < 50; i++ )
                {
                    nError = rtl_digest_updateMD5( aDigest, &nMD5Sum, sizeof( nMD5Sum ) );
                    if( nError != rtl_Digest_E_None )
                    {
                        bSuccess =  false;
                        break;
                    }
                    rtl_digest_getMD5( aDigest, nMD5Sum, sizeof( nMD5Sum ) );
                }
            }
        }
    }
    else
        bSuccess = false;

    i_pTransporter->invalidate();

    //Step 7
    if( bSuccess )
    {
        io_rProperties.EncryptionKey.resize( MAXIMUM_RC4_KEY_LENGTH );
        for( sal_Int32 i = 0; i < MD5_DIGEST_SIZE; i++ )
            io_rProperties.EncryptionKey[i] = nMD5Sum[i];
    }
    else
        io_rProperties.EncryptionKey.clear();

    return bSuccess;
}

/**********************************
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
*/
bool PDFWriterImpl::computeODictionaryValue( const sal_uInt8* i_pPaddedOwnerPassword,
                                             const sal_uInt8* i_pPaddedUserPassword,
                                             std::vector< sal_uInt8 >& io_rOValue,
                                             sal_Int32 i_nKeyLength
                                             )
{
    bool bSuccess = true;

    io_rOValue.resize( ENCRYPTED_PWD_SIZE );

    rtlDigest aDigest = rtl_digest_createMD5();
    rtlCipher aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream );
    if( aDigest && aCipher)
    {
        //step 1 already done, data is in i_pPaddedOwnerPassword
        //step 2

        rtlDigestError nError = rtl_digest_updateMD5( aDigest, i_pPaddedOwnerPassword, ENCRYPTED_PWD_SIZE );
        if( nError == rtl_Digest_E_None )
        {
            sal_uInt8 nMD5Sum[ RTL_DIGEST_LENGTH_MD5 ];

            rtl_digest_getMD5( aDigest, nMD5Sum, sizeof(nMD5Sum) );
//step 3, only if 128 bit
            if( i_nKeyLength == SECUR_128BIT_KEY )
            {
                sal_Int32 i;
                for( i = 0; i < 50; i++ )
                {
                    nError = rtl_digest_updateMD5( aDigest, nMD5Sum, sizeof( nMD5Sum ) );
                    if( nError != rtl_Digest_E_None )
                    {
                        bSuccess = false;
                        break;
                    }
                    rtl_digest_getMD5( aDigest, nMD5Sum, sizeof( nMD5Sum ) );
                }
            }
            //Step 4, the key is in nMD5Sum
            //step 5 already done, data is in i_pPaddedUserPassword
            //step 6
            rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode,
                                     nMD5Sum, i_nKeyLength , NULL, 0 );
            // encrypt the user password using the key set above
            rtl_cipher_encodeARCFOUR( aCipher, i_pPaddedUserPassword, ENCRYPTED_PWD_SIZE, // the data to be encrypted
                                      &io_rOValue[0], sal_Int32(io_rOValue.size()) ); //encrypted data
            //Step 7, only if 128 bit
            if( i_nKeyLength == SECUR_128BIT_KEY )
            {
                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( aCipher, rtl_Cipher_DirectionEncode,
                                            nLocalKey, SECUR_128BIT_KEY, NULL, 0 ); //destination data area, on init can be NULL
                    rtl_cipher_encodeARCFOUR( aCipher, &io_rOValue[0], sal_Int32(io_rOValue.size()), // the data to be encrypted
                                              &io_rOValue[0], sal_Int32(io_rOValue.size()) ); // encrypted data, can be the same as the input, encrypt "in place"
                    //step 8, store in class data member
                }
            }
        }
        else
            bSuccess = false;
    }
    else
        bSuccess = false;

    if( aDigest )
        rtl_digest_destroyMD5( aDigest );
    if( aCipher )
        rtl_cipher_destroyARCFOUR( aCipher );

    if( ! bSuccess )
        io_rOValue.clear();
    return bSuccess;
}

/**********************************
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)
*/
bool PDFWriterImpl::computeUDictionaryValue( EncHashTransporter* i_pTransporter,
                                             vcl::PDFWriter::PDFEncryptionProperties& io_rProperties,
                                             sal_Int32 i_nKeyLength,
                                             sal_Int32 i_nAccessPermissions
                                             )
{
    bool bSuccess = true;

    io_rProperties.UValue.resize( ENCRYPTED_PWD_SIZE );

    rtlDigest aDigest = rtl_digest_createMD5();
    rtlCipher aCipher = rtl_cipher_createARCFOUR( rtl_Cipher_ModeStream );
    if( aDigest && aCipher )
    {
        //step 1, common to both 3.4 and 3.5
        if( computeEncryptionKey( i_pTransporter, io_rProperties, i_nAccessPermissions ) )
        {
            // prepare encryption key for object
            for( sal_Int32 i = i_nKeyLength, y = 0; y < 5 ; y++ )
                io_rProperties.EncryptionKey[i++] = 0;

            if( io_rProperties.Security128bit == false )
            {
                //3.4
                //step 2 and 3
                rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode,
                                        &io_rProperties.EncryptionKey[0], 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( aCipher, s_nPadString, sizeof( s_nPadString ), // the data to be encrypted
                                          &io_rProperties.UValue[0], sal_Int32(io_rProperties.UValue.size()) ); //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 < sal_uInt32(io_rProperties.UValue.size()); i++)
                    io_rProperties.UValue[i] = 0;
                //step 2
                rtlDigestError nError = rtl_digest_updateMD5( aDigest, s_nPadString, sizeof( s_nPadString ) );
                //step 3
                if( nError == rtl_Digest_E_None )
                    nError = rtl_digest_updateMD5( aDigest, &io_rProperties.DocumentIdentifier[0], sal_Int32(io_rProperties.DocumentIdentifier.size()) );
                else
                    bSuccess = false;

                sal_uInt8 nMD5Sum[ RTL_DIGEST_LENGTH_MD5 ];
                rtl_digest_getMD5( aDigest, nMD5Sum, sizeof(nMD5Sum) );
                //Step 4
                rtl_cipher_initARCFOUR( aCipher, rtl_Cipher_DirectionEncode,
                                        &io_rProperties.EncryptionKey[0], SECUR_128BIT_KEY, NULL, 0 ); //destination data area
                rtl_cipher_encodeARCFOUR( aCipher, nMD5Sum, sizeof( nMD5Sum ), // the data to be encrypted
                                          &io_rProperties.UValue[0], 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)( io_rProperties.EncryptionKey[y] ^ i );

                    rtl_cipher_initARCFOUR( 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( aCipher, &io_rProperties.UValue[0], SECUR_128BIT_KEY, // the data to be encrypted
                                              &io_rProperties.UValue[0], SECUR_128BIT_KEY ); // encrypted data, can be the same as the input, encrypt "in place"
                }
            }
        }
        else
            bSuccess = false;
    }
    else
        bSuccess = false;

    if( aDigest )
        rtl_digest_destroyMD5( aDigest );
    if( aCipher )
        rtl_cipher_destroyARCFOUR( aCipher );

    if( ! bSuccess )
        io_rProperties.UValue.clear();
    return bSuccess;
}

/* end i12626 methods */

static const long unsetRun[256] =
{
    8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4,	/* 0x00 - 0x0f */
    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,	/* 0x10 - 0x1f */
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,	/* 0x20 - 0x2f */
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,	/* 0x30 - 0x3f */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,	/* 0x40 - 0x4f */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,	/* 0x50 - 0x5f */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,	/* 0x60 - 0x6f */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,	/* 0x70 - 0x7f */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	/* 0x80 - 0x8f */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	/* 0x90 - 0x9f */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	/* 0xa0 - 0xaf */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	/* 0xb0 - 0xbf */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	/* 0xc0 - 0xcf */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	/* 0xd0 - 0xdf */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	/* 0xe0 - 0xef */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	/* 0xf0 - 0xff */
};

static const long setRun[256] =
{
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	/* 0x00 - 0x0f */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	/* 0x10 - 0x1f */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	/* 0x20 - 0x2f */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	/* 0x30 - 0x3f */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	/* 0x40 - 0x4f */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	/* 0x50 - 0x5f */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	/* 0x60 - 0x6f */
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	/* 0x70 - 0x7f */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,	/* 0x80 - 0x8f */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,	/* 0x90 - 0x9f */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,	/* 0xa0 - 0xaf */
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,	/* 0xb0 - 0xbf */
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,	/* 0xc0 - 0xcf */
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,	/* 0xd0 - 0xdf */
    3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,	/* 0xe0 - 0xef */
    4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8,	/* 0xf0 - 0xff */
};

inline bool isSet( const Scanline i_pLine, long i_nIndex )
{
    return (i_pLine[ i_nIndex/8 ] & (0x80 >> (i_nIndex&7))) != 0;
}

long findBitRun( const Scanline i_pLine, long i_nStartIndex, long i_nW, bool i_bSet )
{
    if( i_nStartIndex < 0 )
        return i_nW;
    
    long nIndex = i_nStartIndex;
    if( nIndex < i_nW )
    {
        const sal_uInt8 * pByte = static_cast<sal_uInt8*>(i_pLine) + (nIndex/8);
        sal_uInt8 nByte = *pByte;

        // run up to byte boundary
        long nBitInByte = (nIndex & 7);
        if( nBitInByte )
        {
            sal_uInt8 nMask = 0x80 >> nBitInByte;
            while( nBitInByte != 8 )
            {
                if( (nByte & nMask) != (i_bSet ? nMask : 0) )
                    return nIndex < i_nW ? nIndex : i_nW;
                nMask = nMask >> 1;
                nBitInByte++;
                nIndex++;
            }
            if( nIndex < i_nW )
            {
                pByte++;
                nByte = *pByte;
            }
        }
        
        sal_uInt8 nRunByte;
        const long* pRunTable;
        if( i_bSet )
        {
            nRunByte = 0xff;
            pRunTable = setRun;
        }
        else
        {
            nRunByte = 0;
            pRunTable = unsetRun;
        }
        
        while( nByte == nRunByte && nIndex < i_nW )
        {
            nIndex += 8;
            pByte++;
            nByte = *pByte;
        }
        if( nIndex < i_nW )
        {
            nIndex += pRunTable[nByte];
        }
    }
    return nIndex < i_nW ? nIndex : i_nW;
}

struct BitStreamState
{
    sal_uInt8       mnBuffer;
    sal_uInt32      mnNextBitPos;
    
    BitStreamState()
    : mnBuffer( 0 )
    , mnNextBitPos( 8 )
    {
    }
    
    const sal_uInt8* getByte() const { return &mnBuffer; }
    void flush() { mnNextBitPos = 8; mnBuffer = 0; }
};

void PDFWriterImpl::putG4Bits( sal_uInt32 i_nLength, sal_uInt32 i_nCode, BitStreamState& io_rState )
{
    while( i_nLength > io_rState.mnNextBitPos )
    {
        io_rState.mnBuffer |= static_cast<sal_uInt8>( i_nCode >> (i_nLength - io_rState.mnNextBitPos) );
        i_nLength -= io_rState.mnNextBitPos;
        writeBuffer( io_rState.getByte(), 1 );
        io_rState.flush();
    }
    OSL_ASSERT( i_nLength < 9 );
    static const unsigned int msbmask[9] = { 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff };
    io_rState.mnBuffer |= static_cast<sal_uInt8>( (i_nCode & msbmask[i_nLength]) << (io_rState.mnNextBitPos - i_nLength) );
    io_rState.mnNextBitPos -= i_nLength;
    if( io_rState.mnNextBitPos == 0 )
    {
        writeBuffer( io_rState.getByte(), 1 );
        io_rState.flush();
    }
}

struct PixelCode
{
    sal_uInt32      mnEncodedPixels;
    sal_uInt32      mnCodeBits;
    sal_uInt32      mnCode;
};

static const PixelCode WhitePixelCodes[] =
{
    { 0, 8, 0x35 },     // 0011 0101
    { 1, 6, 0x7 },      // 0001 11
    { 2, 4, 0x7 },      // 0111
    { 3, 4, 0x8 },      // 1000
    { 4, 4, 0xB },      // 1011
    { 5, 4, 0xC },      // 1100
    { 6, 4, 0xE },      // 1110
    { 7, 4, 0xF },      // 1111
    { 8, 5, 0x13 },     // 1001 1
    { 9, 5, 0x14 },     // 1010 0
    { 10, 5, 0x7 },     // 0011 1
    { 11, 5, 0x8 },     // 0100 0
    { 12, 6, 0x8 },     // 0010 00
    { 13, 6, 0x3 },     // 0000 11
    { 14, 6, 0x34 },    // 1101 00
    { 15, 6, 0x35 },    // 1101 01
    { 16, 6, 0x2A },    // 1010 10
    { 17, 6, 0x2B },    // 1010 11
    { 18, 7, 0x27 },    // 0100 111
    { 19, 7, 0xC },     // 0001 100
    { 20, 7, 0x8 },     // 0001 000
    { 21, 7, 0x17 },    // 0010 111
    { 22, 7, 0x3 },     // 0000 011
    { 23, 7, 0x4 },     // 0000 100
    { 24, 7, 0x28 },    // 0101 000
    { 25, 7, 0x2B },    // 0101 011
    { 26, 7, 0x13 },    // 0010 011
    { 27, 7, 0x24 },    // 0100 100
    { 28, 7, 0x18 },    // 0011 000
    { 29, 8, 0x2 },     // 0000 0010
    { 30, 8, 0x3 },     // 0000 0011
    { 31, 8, 0x1A },    // 0001 1010
    { 32, 8, 0x1B },    // 0001 1011
    { 33, 8, 0x12 },    // 0001 0010
    { 34, 8, 0x13 },    // 0001 0011
    { 35, 8, 0x14 },    // 0001 0100
    { 36, 8, 0x15 },    // 0001 0101
    { 37, 8, 0x16 },    // 0001 0110
    { 38, 8, 0x17 },    // 0001 0111
    { 39, 8, 0x28 },    // 0010 1000
    { 40, 8, 0x29 },    // 0010 1001
    { 41, 8, 0x2A },    // 0010 1010
    { 42, 8, 0x2B },    // 0010 1011
    { 43, 8, 0x2C },    // 0010 1100
    { 44, 8, 0x2D },    // 0010 1101
    { 45, 8, 0x4 },     // 0000 0100
    { 46, 8, 0x5 },     // 0000 0101
    { 47, 8, 0xA },     // 0000 1010
    { 48, 8, 0xB },     // 0000 1011
    { 49, 8, 0x52 },    // 0101 0010
    { 50, 8, 0x53 },    // 0101 0011
    { 51, 8, 0x54 },    // 0101 0100
    { 52, 8, 0x55 },    // 0101 0101
    { 53, 8, 0x24 },    // 0010 0100
    { 54, 8, 0x25 },    // 0010 0101
    { 55, 8, 0x58 },    // 0101 1000
    { 56, 8, 0x59 },    // 0101 1001
    { 57, 8, 0x5A },    // 0101 1010
    { 58, 8, 0x5B },    // 0101 1011
    { 59, 8, 0x4A },    // 0100 1010
    { 60, 8, 0x4B },    // 0100 1011
    { 61, 8, 0x32 },    // 0011 0010
    { 62, 8, 0x33 },    // 0011 0011
    { 63, 8, 0x34 },    // 0011 0100
    { 64, 5, 0x1B },    // 1101 1
    { 128, 5, 0x12 },   // 1001 0
    { 192, 6, 0x17 },   // 0101 11
    { 256, 7, 0x37 },   // 0110 111
    { 320, 8, 0x36 },   // 0011 0110
    { 384, 8, 0x37 },   // 0011 0111
    { 448, 8, 0x64 },   // 0110 0100
    { 512, 8, 0x65 },   // 0110 0101
    { 576, 8, 0x68 },   // 0110 1000
    { 640, 8, 0x67 },   // 0110 0111
    { 704, 9, 0xCC },   // 0110 0110 0
    { 768, 9, 0xCD },   // 0110 0110 1
    { 832, 9, 0xD2 },   // 0110 1001 0
    { 896, 9, 0xD3 },   // 0110 1001 1
    { 960, 9, 0xD4 },   // 0110 1010 0
    { 1024, 9, 0xD5 },  // 0110 1010 1
    { 1088, 9, 0xD6 },  // 0110 1011 0
    { 1152, 9, 0xD7 },  // 0110 1011 1
    { 1216, 9, 0xD8 },  // 0110 1100 0
    { 1280, 9, 0xD9 },  // 0110 1100 1
    { 1344, 9, 0xDA },  // 0110 1101 0
    { 1408, 9, 0xDB },  // 0110 1101 1
    { 1472, 9, 0x98 },  // 0100 1100 0
    { 1536, 9, 0x99 },  // 0100 1100 1
    { 1600, 9, 0x9A },  // 0100 1101 0
    { 1664, 6, 0x18 },  // 0110 00
    { 1728, 9, 0x9B },  // 0100 1101 1
    { 1792, 11, 0x8 },  // 0000 0001 000
    { 1856, 11, 0xC },  // 0000 0001 100
    { 1920, 11, 0xD },  // 0000 0001 101
    { 1984, 12, 0x12 }, // 0000 0001 0010
    { 2048, 12, 0x13 }, // 0000 0001 0011
    { 2112, 12, 0x14 }, // 0000 0001 0100
    { 2176, 12, 0x15 }, // 0000 0001 0101
    { 2240, 12, 0x16 }, // 0000 0001 0110
    { 2304, 12, 0x17 }, // 0000 0001 0111
    { 2368, 12, 0x1C }, // 0000 0001 1100
    { 2432, 12, 0x1D }, // 0000 0001 1101
    { 2496, 12, 0x1E }, // 0000 0001 1110
    { 2560, 12, 0x1F }  // 0000 0001 1111
};

static const PixelCode BlackPixelCodes[] =
{
    { 0, 10, 0x37 },    // 0000 1101 11
    { 1, 3, 0x2 },      // 010
    { 2, 2, 0x3 },      // 11
    { 3, 2, 0x2 },      // 10
    { 4, 3, 0x3 },      // 011
    { 5, 4, 0x3 },      // 0011
    { 6, 4, 0x2 },      // 0010
    { 7, 5, 0x3 },      // 0001 1
    { 8, 6, 0x5 },      // 0001 01
    { 9, 6, 0x4 },      // 0001 00
    { 10, 7, 0x4 },     // 0000 100
    { 11, 7, 0x5 },     // 0000 101
    { 12, 7, 0x7 },     // 0000 111
    { 13, 8, 0x4 },     // 0000 0100
    { 14, 8, 0x7 },     // 0000 0111
    { 15, 9, 0x18 },    // 0000 1100 0
    { 16, 10, 0x17 },   // 0000 0101 11
    { 17, 10, 0x18 },   // 0000 0110 00
    { 18, 10, 0x8 },    // 0000 0010 00
    { 19, 11, 0x67 },   // 0000 1100 111
    { 20, 11, 0x68 },   // 0000 1101 000
    { 21, 11, 0x6C },   // 0000 1101 100
    { 22, 11, 0x37 },   // 0000 0110 111
    { 23, 11, 0x28 },   // 0000 0101 000
    { 24, 11, 0x17 },   // 0000 0010 111
    { 25, 11, 0x18 },   // 0000 0011 000
    { 26, 12, 0xCA },   // 0000 1100 1010
    { 27, 12, 0xCB },   // 0000 1100 1011
    { 28, 12, 0xCC },   // 0000 1100 1100
    { 29, 12, 0xCD },   // 0000 1100 1101
    { 30, 12, 0x68 },   // 0000 0110 1000
    { 31, 12, 0x69 },   // 0000 0110 1001
    { 32, 12, 0x6A },   // 0000 0110 1010
    { 33, 12, 0x6B },   // 0000 0110 1011
    { 34, 12, 0xD2 },   // 0000 1101 0010
    { 35, 12, 0xD3 },   // 0000 1101 0011
    { 36, 12, 0xD4 },   // 0000 1101 0100
    { 37, 12, 0xD5 },   // 0000 1101 0101
    { 38, 12, 0xD6 },   // 0000 1101 0110
    { 39, 12, 0xD7 },   // 0000 1101 0111
    { 40, 12, 0x6C },   // 0000 0110 1100
    { 41, 12, 0x6D },   // 0000 0110 1101
    { 42, 12, 0xDA },   // 0000 1101 1010
    { 43, 12, 0xDB },   // 0000 1101 1011
    { 44, 12, 0x54 },   // 0000 0101 0100
    { 45, 12, 0x55 },   // 0000 0101 0101
    { 46, 12, 0x56 },   // 0000 0101 0110
    { 47, 12, 0x57 },   // 0000 0101 0111
    { 48, 12, 0x64 },   // 0000 0110 0100
    { 49, 12, 0x65 },   // 0000 0110 0101
    { 50, 12, 0x52 },   // 0000 0101 0010
    { 51, 12, 0x53 },   // 0000 0101 0011
    { 52, 12, 0x24 },   // 0000 0010 0100
    { 53, 12, 0x37 },   // 0000 0011 0111
    { 54, 12, 0x38 },   // 0000 0011 1000
    { 55, 12, 0x27 },   // 0000 0010 0111
    { 56, 12, 0x28 },   // 0000 0010 1000
    { 57, 12, 0x58 },   // 0000 0101 1000
    { 58, 12, 0x59 },   // 0000 0101 1001
    { 59, 12, 0x2B },   // 0000 0010 1011
    { 60, 12, 0x2C },   // 0000 0010 1100
    { 61, 12, 0x5A },   // 0000 0101 1010
    { 62, 12, 0x66 },   // 0000 0110 0110
    { 63, 12, 0x67 },   // 0000 0110 0111
    { 64, 10, 0xF },    // 0000 0011 11
    { 128, 12, 0xC8 },  // 0000 1100 1000
    { 192, 12, 0xC9 },  // 0000 1100 1001
    { 256, 12, 0x5B },  // 0000 0101 1011
    { 320, 12, 0x33 },  // 0000 0011 0011
    { 384, 12, 0x34 },  // 0000 0011 0100
    { 448, 12, 0x35 },  // 0000 0011 0101
    { 512, 13, 0x6C },  // 0000 0011 0110 0
    { 576, 13, 0x6D },  // 0000 0011 0110 1
    { 640, 13, 0x4A },  // 0000 0010 0101 0
    { 704, 13, 0x4B },  // 0000 0010 0101 1
    { 768, 13, 0x4C },  // 0000 0010 0110 0
    { 832, 13, 0x4D },  // 0000 0010 0110 1
    { 896, 13, 0x72 },  // 0000 0011 1001 0
    { 960, 13, 0x73 },  // 0000 0011 1001 1
    { 1024, 13, 0x74 }, // 0000 0011 1010 0
    { 1088, 13, 0x75 }, // 0000 0011 1010 1
    { 1152, 13, 0x76 }, // 0000 0011 1011 0
    { 1216, 13, 0x77 }, // 0000 0011 1011 1
    { 1280, 13, 0x52 }, // 0000 0010 1001 0
    { 1344, 13, 0x53 }, // 0000 0010 1001 1
    { 1408, 13, 0x54 }, // 0000 0010 1010 0
    { 1472, 13, 0x55 }, // 0000 0010 1010 1
    { 1536, 13, 0x5A }, // 0000 0010 1101 0
    { 1600, 13, 0x5B }, // 0000 0010 1101 1
    { 1664, 13, 0x64 }, // 0000 0011 0010 0
    { 1728, 13, 0x65 }, // 0000 0011 0010 1
    { 1792, 11, 0x8 },  // 0000 0001 000
    { 1856, 11, 0xC },  // 0000 0001 100
    { 1920, 11, 0xD },  // 0000 0001 101
    { 1984, 12, 0x12 }, // 0000 0001 0010
    { 2048, 12, 0x13 }, // 0000 0001 0011
    { 2112, 12, 0x14 }, // 0000 0001 0100
    { 2176, 12, 0x15 }, // 0000 0001 0101
    { 2240, 12, 0x16 }, // 0000 0001 0110
    { 2304, 12, 0x17 }, // 0000 0001 0111
    { 2368, 12, 0x1C }, // 0000 0001 1100
    { 2432, 12, 0x1D }, // 0000 0001 1101
    { 2496, 12, 0x1E }, // 0000 0001 1110
    { 2560, 12, 0x1F }  // 0000 0001 1111
};


void PDFWriterImpl::putG4Span( long i_nSpan, bool i_bWhitePixel, BitStreamState& io_rState )
{
    const PixelCode* pTable = i_bWhitePixel ? WhitePixelCodes : BlackPixelCodes;
    // maximum encoded span is 2560 consecutive pixels
    while( i_nSpan > 2623 )
    {
        // write 2560 bits, that is entry (63 + (2560 >> 6)) == 103 in the appropriate table
        putG4Bits( pTable[103].mnCodeBits, pTable[103].mnCode, io_rState );
        i_nSpan -= pTable[103].mnEncodedPixels;
    }
    // write multiples of 64 pixels up to 2560
    if( i_nSpan > 63 )
    {
        sal_uInt32 nTabIndex = 63 + (i_nSpan >> 6);
        OSL_ASSERT( pTable[nTabIndex].mnEncodedPixels == static_cast<sal_uInt32>(64*(i_nSpan >> 6)) );
        putG4Bits( pTable[nTabIndex].mnCodeBits, pTable[nTabIndex].mnCode, io_rState );
        i_nSpan -= pTable[nTabIndex].mnEncodedPixels;
    }
    putG4Bits( pTable[i_nSpan].mnCodeBits, pTable[i_nSpan].mnCode, io_rState );
}

void PDFWriterImpl::writeG4Stream( BitmapReadAccess* i_pBitmap )
{
    long nW = i_pBitmap->Width();
    long nH = i_pBitmap->Height();
    if( nW <= 0 || nH <= 0 )
        return;
    if( i_pBitmap->GetBitCount() != 1 )
        return;
    
    BitStreamState aBitState;
    
    // the first reference line is virtual and completely empty
    const Scanline pFirstRefLine = (Scanline)rtl_allocateZeroMemory( nW/8 + 1 );
    Scanline pRefLine = pFirstRefLine; 
    for( long nY = 0; nY < nH; nY++ )
    {
        const Scanline pCurLine = i_pBitmap->GetScanline( nY );
        long nLineIndex = 0;
        bool bRunSet = (*pCurLine & 0x80) ? true : false;
        bool bRefSet = (*pRefLine & 0x80) ? true : false;
        long nRunIndex1 = bRunSet ? 0 : findBitRun( pCurLine, 0, nW, bRunSet );
        long nRefIndex1 = bRefSet ? 0 : findBitRun( pRefLine, 0, nW, bRefSet );
        for( ; nLineIndex < nW; )
        {
            long nRefIndex2 = findBitRun( pRefLine, nRefIndex1, nW, isSet( pRefLine, nRefIndex1 ) );
            if( nRefIndex2 >= nRunIndex1 )
            {
                long nDiff = nRefIndex1 - nRunIndex1;
                if( -3 <= nDiff && nDiff <= 3 )
                {   // vertical coding
                    static const struct
                    {
                        sal_uInt32 mnCodeBits;
                        sal_uInt32 mnCode;
                    } VerticalCodes[7] = {
                        { 7, 0x03 },    // 0000 011
                        { 6, 0x03 },    // 0000 11
                        { 3, 0x03 },    // 011
                        { 1, 0x1 },     // 1
                        { 3, 0x2 },     // 010
                        { 6, 0x02 },    // 0000 10
                        { 7, 0x02 }     // 0000 010
                    };
                    // convert to index
                    nDiff += 3;

                    // emit diff code
                    putG4Bits( VerticalCodes[nDiff].mnCodeBits, VerticalCodes[nDiff].mnCode, aBitState );
                    nLineIndex = nRunIndex1;
                }
                else
                {   // difference too large, horizontal coding
                    // emit horz code 001
                    putG4Bits( 3, 0x1, aBitState );
                    long nRunIndex2 = findBitRun( pCurLine, nRunIndex1, nW, isSet( pCurLine, nRunIndex1 ) );
                    bool bWhiteFirst = ( nLineIndex + nRunIndex1 == 0 || ! isSet( pCurLine, nLineIndex ) );
                    putG4Span( nRunIndex1 - nLineIndex, bWhiteFirst, aBitState );
                    putG4Span( nRunIndex2 - nRunIndex1, ! bWhiteFirst, aBitState );
                    nLineIndex = nRunIndex2;
                }
            }
            else
            {   // emit pass code 0001
                putG4Bits( 4, 0x1, aBitState );
                nLineIndex = nRefIndex2;
            }
            if( nLineIndex < nW )
            {
                bool bSet = isSet( pCurLine, nLineIndex );
                nRunIndex1 = findBitRun( pCurLine, nLineIndex, nW, bSet );
                nRefIndex1 = findBitRun( pRefLine, nLineIndex, nW, ! bSet );
                nRefIndex1 = findBitRun( pRefLine, nRefIndex1, nW, bSet );
            }
        }
        
        // the current line is the reference for the next line
        pRefLine = pCurLine;
    }
    // terminate strip with EOFB
    putG4Bits( 12, 1, aBitState );
    putG4Bits( 12, 1, aBitState );
    if( aBitState.mnNextBitPos != 8 )
    {
        writeBuffer( aBitState.getByte(), 1 );
        aBitState.flush();
    }
    
    rtl_freeMemory( pFirstRefLine );
}