/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*************************************************************************
 *
 * 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_canvas.hxx"

#include <canvas/debug.hxx>
#include <tools/diagnose_ex.h>

#include <rtl/logfile.hxx>
#include <rtl/math.hxx>

#include <com/sun/star/rendering/TexturingMode.hpp>
#include <com/sun/star/rendering/CompositeOperation.hpp>
#include <com/sun/star/rendering/RepaintResult.hpp>
#include <com/sun/star/rendering/PathCapType.hpp>
#include <com/sun/star/rendering/PathJoinType.hpp>

#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/point/b2dpoint.hxx>
#include <basegfx/tools/canvastools.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>

#include <comphelper/sequence.hxx>
#include <canvas/canvastools.hxx>

#include "dx_spritecanvas.hxx"
#include "dx_impltools.hxx"
#include "dx_vcltools.hxx"
#include "dx_canvasfont.hxx"
#include "dx_textlayout.hxx"
#include "dx_canvashelper.hxx"

#include <algorithm>


using namespace ::com::sun::star;

namespace dxcanvas
{
    namespace
    {
        Gdiplus::LineCap gdiCapFromCap( sal_Int8 nCapType )
        {
            switch( nCapType )
            {
                case rendering::PathCapType::BUTT:
                    return Gdiplus::LineCapFlat;

                case rendering::PathCapType::ROUND:
                    return Gdiplus::LineCapRound;

                case rendering::PathCapType::SQUARE:
                    return Gdiplus::LineCapSquare;

                default:
                    ENSURE_OR_THROW( false,
                                      "gdiCapFromCap(): Unexpected cap type" );
            }

            return Gdiplus::LineCapFlat;
        }

        Gdiplus::LineJoin gdiJoinFromJoin( sal_Int8 nJoinType )
        {
            switch( nJoinType )
            {
                case rendering::PathJoinType::NONE:
                    OSL_FAIL( "gdiJoinFromJoin(): Join NONE not possible, mapping to MITER" );
                    // FALLTHROUGH intended
                case rendering::PathJoinType::MITER:
                    return Gdiplus::LineJoinMiter;

                case rendering::PathJoinType::ROUND:
                    return Gdiplus::LineJoinRound;

                case rendering::PathJoinType::BEVEL:
                    return Gdiplus::LineJoinBevel;

                default:
                    ENSURE_OR_THROW( false,
                                      "gdiJoinFromJoin(): Unexpected join type" );
            }

            return Gdiplus::LineJoinMiter;
        }
    }

    CanvasHelper::CanvasHelper() :
        mpGdiPlusUser( GDIPlusUser::createInstance() ),
        mpDevice( NULL ),
        mpGraphicsProvider(),
        maOutputOffset()
    {
    }

    void CanvasHelper::disposing()
    {
        mpGraphicsProvider.reset();
        mpDevice = NULL;
        mpGdiPlusUser.reset();
    }

    void CanvasHelper::setDevice( rendering::XGraphicDevice& rDevice )
    {
        mpDevice = &rDevice;
    }

    void CanvasHelper::setTarget( const GraphicsProviderSharedPtr& rTarget )
    {
        ENSURE_OR_THROW( rTarget,
                          "CanvasHelper::setTarget(): Invalid target" );
        ENSURE_OR_THROW( !mpGraphicsProvider.get(),
                          "CanvasHelper::setTarget(): target set, old target would be overwritten" );

        mpGraphicsProvider = rTarget;
    }

    void CanvasHelper::setTarget( const GraphicsProviderSharedPtr& rTarget,
                                  const ::basegfx::B2ISize& 	   rOutputOffset )
    {
        ENSURE_OR_THROW( rTarget,
                         "CanvasHelper::setTarget(): invalid target" );
        ENSURE_OR_THROW( !mpGraphicsProvider.get(),
                         "CanvasHelper::setTarget(): target set, old target would be overwritten" );

        mpGraphicsProvider = rTarget;
        maOutputOffset = rOutputOffset;
    }

    void CanvasHelper::clear()
    {
        if( needOutput() )
        {
            GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );
            Gdiplus::Color aClearColor = Gdiplus::Color((Gdiplus::ARGB)Gdiplus::Color::White);

            ENSURE_OR_THROW(
                Gdiplus::Ok == pGraphics->SetCompositingMode( 
                    Gdiplus::CompositingModeSourceCopy ), // force set, don't blend
                "CanvasHelper::clear(): GDI+ SetCompositingMode call failed" );
            ENSURE_OR_THROW(
                Gdiplus::Ok == pGraphics->Clear( aClearColor ),
                "CanvasHelper::clear(): GDI+ Clear call failed" );
        }
    }

    void CanvasHelper::drawPoint( const rendering::XCanvas* 	/*pCanvas*/,
                                  const geometry::RealPoint2D& 	aPoint,
                                  const rendering::ViewState& 	viewState,
                                  const rendering::RenderState&	renderState )
    {
        if( needOutput() )
        {
            GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );

            setupGraphicsState( pGraphics, viewState, renderState );
        
            Gdiplus::SolidBrush aBrush( 
                Gdiplus::Color( 
                    tools::sequenceToArgb(renderState.DeviceColor)) );

            // determine size of one-by-one device pixel ellipse
            Gdiplus::Matrix aMatrix;
            pGraphics->GetTransform(&aMatrix);
            aMatrix.Invert();
            Gdiplus::PointF vector(1, 1);
            aMatrix.TransformVectors(&vector);

            // paint a one-by-one circle, with the given point
            // in the middle (rounded to float)
            ENSURE_OR_THROW(
                Gdiplus::Ok == pGraphics->FillEllipse( &aBrush,
                                                       // disambiguate call
                                                       Gdiplus::REAL(aPoint.X),
                                                       Gdiplus::REAL(aPoint.Y), 
                                                       Gdiplus::REAL(vector.X), 
                                                       Gdiplus::REAL(vector.Y) ),
                "CanvasHelper::drawPoint(): GDI+ call failed" );
        }
    }

    void CanvasHelper::drawLine( const rendering::XCanvas* 		/*pCanvas*/,
                                 const geometry::RealPoint2D& 	aStartPoint,
                                 const geometry::RealPoint2D& 	aEndPoint,
                                 const rendering::ViewState& 	viewState,
                                 const rendering::RenderState& 	renderState )
    {
        if( needOutput() )
        {
            GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );

            setupGraphicsState( pGraphics, viewState, renderState );
        
            Gdiplus::Pen aPen( 
                Gdiplus::Color( 
                    tools::sequenceToArgb(renderState.DeviceColor)),
                Gdiplus::REAL(0.0) );

            // #122683# Switched precedence of pixel offset
            // mode. Seemingly, polygon stroking needs
            // PixelOffsetModeNone to achieve visually pleasing
            // results, whereas all other operations (e.g. polygon
            // fills, bitmaps) look better with PixelOffsetModeHalf.
            const Gdiplus::PixelOffsetMode aOldMode(
                pGraphics->GetPixelOffsetMode() );
            pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone );

            Gdiplus::Status hr = pGraphics->DrawLine( &aPen, 
                                                      Gdiplus::REAL(aStartPoint.X), // disambiguate call
                                                      Gdiplus::REAL(aStartPoint.Y), 
                                                      Gdiplus::REAL(aEndPoint.X), 
                                                      Gdiplus::REAL(aEndPoint.Y) );
            pGraphics->SetPixelOffsetMode( aOldMode );

            ENSURE_OR_THROW(
                Gdiplus::Ok == hr,
                "CanvasHelper::drawLine(): GDI+ call failed" );
        }
    }

    void CanvasHelper::drawBezier( const rendering::XCanvas* 			/*pCanvas*/, 
                                   const geometry::RealBezierSegment2D&	aBezierSegment, 
                                   const geometry::RealPoint2D& 		aEndPoint,
                                   const rendering::ViewState& 			viewState, 
                                   const rendering::RenderState& 		renderState )
    {
        if( needOutput() )
        {
            GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );

            setupGraphicsState( pGraphics, viewState, renderState );
        
            Gdiplus::Pen aPen( 
                Gdiplus::Color( 
                    tools::sequenceToArgb(renderState.DeviceColor)),
                Gdiplus::REAL(0.0) );

            // #122683# Switched precedence of pixel offset
            // mode. Seemingly, polygon stroking needs
            // PixelOffsetModeNone to achieve visually pleasing
            // results, whereas all other operations (e.g. polygon
            // fills, bitmaps) look better with PixelOffsetModeHalf.
            const Gdiplus::PixelOffsetMode aOldMode( 
                pGraphics->GetPixelOffsetMode() );
            pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone );

            Gdiplus::Status hr = pGraphics->DrawBezier( &aPen,
                                                        Gdiplus::REAL(aBezierSegment.Px), // disambiguate call
                                                        Gdiplus::REAL(aBezierSegment.Py),
                                                        Gdiplus::REAL(aBezierSegment.C1x),
                                                        Gdiplus::REAL(aBezierSegment.C1y),
                                                        Gdiplus::REAL(aEndPoint.X),
                                                        Gdiplus::REAL(aEndPoint.Y),
                                                        Gdiplus::REAL(aBezierSegment.C2x),
                                                        Gdiplus::REAL(aBezierSegment.C2y) );

            pGraphics->SetPixelOffsetMode( aOldMode );

            ENSURE_OR_THROW(
                Gdiplus::Ok == hr,
                "CanvasHelper::drawBezier(): GDI+ call failed" );
        }
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawPolyPolygon( const rendering::XCanvas* 							/*pCanvas*/, 
                                                                                 const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, 
                                                                                 const rendering::ViewState& 						viewState, 
                                                                                 const rendering::RenderState& 						renderState )
    {
        ENSURE_OR_THROW( xPolyPolygon.is(), 
                          "CanvasHelper::drawPolyPolygon: polygon is NULL");

        if( needOutput() )
        {
            GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );

            setupGraphicsState( pGraphics, viewState, renderState );
        
            Gdiplus::Pen aPen( 
                Gdiplus::Color( 
                    tools::sequenceToArgb(renderState.DeviceColor)),
                Gdiplus::REAL(0.0) );
        
            // #122683# Switched precedence of pixel offset
            // mode. Seemingly, polygon stroking needs
            // PixelOffsetModeNone to achieve visually pleasing
            // results, whereas all other operations (e.g. polygon
            // fills, bitmaps) look better with PixelOffsetModeHalf.
            const Gdiplus::PixelOffsetMode aOldMode( 
                pGraphics->GetPixelOffsetMode() );
            pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone );

            GraphicsPathSharedPtr pPath( tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon ) );

            // TODO(E1): Return value
            Gdiplus::Status hr = pGraphics->DrawPath( &aPen, pPath.get() );

            pGraphics->SetPixelOffsetMode( aOldMode );

            ENSURE_OR_THROW(
                Gdiplus::Ok == hr,
                "CanvasHelper::drawPolyPolygon(): GDI+ call failed" );
        }

        // TODO(P1): Provide caching here.
        return uno::Reference< rendering::XCachedPrimitive >(NULL);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokePolyPolygon( const rendering::XCanvas* 							/*pCanvas*/, 
                                                                                   const uno::Reference< rendering::XPolyPolygon2D >& 	xPolyPolygon, 
                                                                                   const rendering::ViewState& 							viewState, 
                                                                                   const rendering::RenderState& 						renderState, 
                                                                                   const rendering::StrokeAttributes& 					strokeAttributes )
    {
        ENSURE_OR_THROW( xPolyPolygon.is(), 
                          "CanvasHelper::drawPolyPolygon: polygon is NULL");

        if( needOutput() )
        {
            GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );

            setupGraphicsState( pGraphics, viewState, renderState );
        

            // Setup stroke pen
            // ----------------

            Gdiplus::Pen aPen( 
                Gdiplus::Color( 
                    tools::sequenceToArgb(renderState.DeviceColor)),
                static_cast< Gdiplus::REAL >(strokeAttributes.StrokeWidth) );

            // #122683# Switched precedence of pixel offset
            // mode. Seemingly, polygon stroking needs
            // PixelOffsetModeNone to achieve visually pleasing
            // results, whereas all other operations (e.g. polygon
            // fills, bitmaps) look better with PixelOffsetModeHalf.
            const Gdiplus::PixelOffsetMode aOldMode( 
                pGraphics->GetPixelOffsetMode() );
            pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone );

            const bool bIsMiter(rendering::PathJoinType::MITER == strokeAttributes.JoinType);
            const bool bIsNone(rendering::PathJoinType::NONE == strokeAttributes.JoinType);
            
            if(bIsMiter)
                aPen.SetMiterLimit( static_cast< Gdiplus::REAL >(strokeAttributes.MiterLimit) );

            const ::std::vector< Gdiplus::REAL >& rDashArray( 
                ::comphelper::sequenceToContainer< ::std::vector< Gdiplus::REAL > >( 
                    strokeAttributes.DashArray ) );
            if( !rDashArray.empty() )
            {
                aPen.SetDashPattern( &rDashArray[0],
                                     rDashArray.size() );
            }
            aPen.SetLineCap( gdiCapFromCap(strokeAttributes.StartCapType),
                             gdiCapFromCap(strokeAttributes.EndCapType),
                             Gdiplus::DashCapFlat );
            if(!bIsNone)
                aPen.SetLineJoin( gdiJoinFromJoin(strokeAttributes.JoinType) );

            GraphicsPathSharedPtr pPath( tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon, bIsNone ) );

            // TODO(E1): Return value
            Gdiplus::Status hr = pGraphics->DrawPath( &aPen, pPath.get() );

            pGraphics->SetPixelOffsetMode( aOldMode );

            ENSURE_OR_THROW(
                Gdiplus::Ok == hr,
                "CanvasHelper::strokePolyPolygon(): GDI+ call failed" );
        }

        // TODO(P1): Provide caching here.
        return uno::Reference< rendering::XCachedPrimitive >(NULL);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTexturedPolyPolygon( const rendering::XCanvas* 							/*pCanvas*/, 
                                                                                           const uno::Reference< rendering::XPolyPolygon2D >& 	/*xPolyPolygon*/, 
                                                                                           const rendering::ViewState& 							/*viewState*/, 
                                                                                           const rendering::RenderState& 						/*renderState*/, 
                                                                                           const uno::Sequence< rendering::Texture >& 			/*textures*/, 
                                                                                           const rendering::StrokeAttributes& 					/*strokeAttributes*/ )
    {
        // TODO
        return uno::Reference< rendering::XCachedPrimitive >(NULL);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTextureMappedPolyPolygon( const rendering::XCanvas* 							/*pCanvas*/, 
                                                                                                const uno::Reference< rendering::XPolyPolygon2D >&	/*xPolyPolygon*/, 
                                                                                                const rendering::ViewState& 						/*viewState*/, 
                                                                                                const rendering::RenderState& 						/*renderState*/, 
                                                                                                const uno::Sequence< rendering::Texture >& 			/*textures*/, 
                                                                                                const uno::Reference< geometry::XMapping2D >& 		/*xMapping*/, 
                                                                                                const rendering::StrokeAttributes& 					/*strokeAttributes*/ )
    {
        // TODO
        return uno::Reference< rendering::XCachedPrimitive >(NULL);
    }

    uno::Reference< rendering::XPolyPolygon2D >   CanvasHelper::queryStrokeShapes( const rendering::XCanvas* 							/*pCanvas*/, 
                                                                                   const uno::Reference< rendering::XPolyPolygon2D >& 	/*xPolyPolygon*/, 
                                                                                   const rendering::ViewState& 							/*viewState*/, 
                                                                                   const rendering::RenderState& 						/*renderState*/, 
                                                                                   const rendering::StrokeAttributes& 					/*strokeAttributes*/ )
    {
        // TODO
        return uno::Reference< rendering::XPolyPolygon2D >(NULL);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillPolyPolygon( const rendering::XCanvas* 							/*pCanvas*/, 
                                                                                 const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, 
                                                                                 const rendering::ViewState& 						viewState, 
                                                                                 const rendering::RenderState& 						renderState )
    {
        ENSURE_OR_THROW( xPolyPolygon.is(), 
                          "CanvasHelper::fillPolyPolygon: polygon is NULL");

        if( needOutput() )
        {
            GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );

            setupGraphicsState( pGraphics, viewState, renderState );

            Gdiplus::SolidBrush aBrush( 
                tools::sequenceToArgb(renderState.DeviceColor));
                
            GraphicsPathSharedPtr pPath( tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon ) );

            // TODO(F1): FillRule
            ENSURE_OR_THROW( Gdiplus::Ok == pGraphics->FillPath( &aBrush, pPath.get() ),
                             "CanvasHelper::fillPolyPolygon(): GDI+ call failed  " );
        }

        // TODO(P1): Provide caching here.
        return uno::Reference< rendering::XCachedPrimitive >(NULL);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTextureMappedPolyPolygon( const rendering::XCanvas* 							/*pCanvas*/, 
                                                                                              const uno::Reference< rendering::XPolyPolygon2D >& 	/*xPolyPolygon*/, 
                                                                                              const rendering::ViewState& 							/*viewState*/, 
                                                                                              const rendering::RenderState& 						/*renderState*/, 
                                                                                              const uno::Sequence< rendering::Texture >& 			/*textures*/, 
                                                                                              const uno::Reference< geometry::XMapping2D >& 		/*xMapping*/ )
    {
        // TODO
        return uno::Reference< rendering::XCachedPrimitive >(NULL);
    }

    uno::Reference< rendering::XCanvasFont > CanvasHelper::createFont( const rendering::XCanvas* 					/*pCanvas*/, 
                                                                       const rendering::FontRequest& 				fontRequest, 
                                                                       const uno::Sequence< beans::PropertyValue >& extraFontProperties, 
                                                                       const geometry::Matrix2D& 					fontMatrix )
    {
        if( needOutput() )
        {
            return uno::Reference< rendering::XCanvasFont >( 
                    new CanvasFont(fontRequest, extraFontProperties, fontMatrix ) );
        }

        return uno::Reference< rendering::XCanvasFont >();
    }

    uno::Sequence< rendering::FontInfo > CanvasHelper::queryAvailableFonts( const rendering::XCanvas* 						/*pCanvas*/, 
                                                                            const rendering::FontInfo& 						/*aFilter*/, 
                                                                            const uno::Sequence< beans::PropertyValue >& 	/*aFontProperties*/ )
    {
        // TODO
        return uno::Sequence< rendering::FontInfo >();
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawText( const rendering::XCanvas* 						/*pCanvas*/, 
                                                                          const rendering::StringContext& 					text, 
                                                                          const uno::Reference< rendering::XCanvasFont >& 	xFont, 
                                                                          const rendering::ViewState& 						viewState, 
                                                                          const rendering::RenderState& 					renderState, 
                                                                          sal_Int8				 							/*textDirection*/ )
    {
        ENSURE_OR_THROW( xFont.is(), 
                          "CanvasHelper::drawText: font is NULL");

        if( needOutput() )
        {
            GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );

            setupGraphicsState( pGraphics, viewState, renderState );
        
            Gdiplus::SolidBrush aBrush( 
                Gdiplus::Color( 
                    tools::sequenceToArgb(renderState.DeviceColor)));

            CanvasFont::ImplRef pFont( 
                tools::canvasFontFromXFont(xFont) );

            // Move glyphs up, such that output happens at the font
            // baseline.
            Gdiplus::PointF aPoint( 0.0,
                                    static_cast<Gdiplus::REAL>(-(pFont->getFont()->GetSize()*
                                                                 pFont->getCellAscent() /
                                                                 pFont->getEmHeight())) );
            
            // TODO(F1): According to
            // http://support.microsoft.com/default.aspx?scid=kb;EN-US;Q307208,
            // we might have to revert to GDI and ExTextOut here,
            // since GDI+ takes the scalability a little bit too
            // far...

            // TODO(F2): Proper layout (BiDi, CTL)! IMHO must use
            // DrawDriverString here, and perform layouting myself...
            ENSURE_OR_THROW(
                Gdiplus::Ok == pGraphics->DrawString( reinterpret_cast<LPCWSTR>(
                                                          text.Text.copy( text.StartPosition,
                                                                          text.Length ).getStr()),
                                                      text.Length,
                                                      pFont->getFont().get(),
                                                      aPoint,
                                                      &aBrush ),
                "CanvasHelper::drawText(): GDI+ call failed" );
        }

        return uno::Reference< rendering::XCachedPrimitive >(NULL);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawTextLayout( const rendering::XCanvas* 						/*pCanvas*/, 
                                                                                const uno::Reference< rendering::XTextLayout >& xLayoutetText, 
                                                                                const rendering::ViewState& 					viewState, 
                                                                                const rendering::RenderState& 					renderState )
    {
        ENSURE_OR_THROW( xLayoutetText.is(), 
                          "CanvasHelper::drawTextLayout: layout is NULL");

        if( needOutput() )
        {
            TextLayout* pTextLayout = 
                dynamic_cast< TextLayout* >( xLayoutetText.get() );

            ENSURE_OR_THROW( pTextLayout,
                                "CanvasHelper::drawTextLayout(): TextLayout not compatible with this canvas" );

            pTextLayout->draw( mpGraphicsProvider->getGraphics(),
                               viewState, 
                               renderState, 
                               maOutputOffset, 
                               mpDevice,
                               false );
        }

        return uno::Reference< rendering::XCachedPrimitive >(NULL);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmap( const rendering::XCanvas* 					/*pCanvas*/, 
                                                                            const uno::Reference< rendering::XBitmap >& xBitmap, 
                                                                            const rendering::ViewState& 				viewState, 
                                                                            const rendering::RenderState& 				renderState )
    {
        ENSURE_OR_THROW( xBitmap.is(), 
                          "CanvasHelper::drawBitmap: bitmap is NULL");

        if( needOutput() )
        {
            // check whether one of our own objects - need to retrieve
            // bitmap _before_ calling
            // GraphicsProvider::getGraphics(), to avoid locking our
            // own surface.
            BitmapSharedPtr pGdiBitmap;
            BitmapProvider* pBitmap = dynamic_cast< BitmapProvider* >(xBitmap.get());
            if( pBitmap )
            {
                IBitmapSharedPtr pDXBitmap( pBitmap->getBitmap() );
                if( pDXBitmap )
                    pGdiBitmap = pDXBitmap->getBitmap();
            }

            GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );
            setupGraphicsState( pGraphics, viewState, renderState );

            if( pGdiBitmap )
                tools::drawGdiPlusBitmap(pGraphics,pGdiBitmap);
            else
                tools::drawVCLBitmapFromXBitmap(pGraphics,
                                                xBitmap);
        }

        // TODO(P1): Provide caching here.
        return uno::Reference< rendering::XCachedPrimitive >(NULL);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmapModulated( const rendering::XCanvas* 						pCanvas, 
                                                                                     const uno::Reference< rendering::XBitmap >& 	xBitmap, 
                                                                                     const rendering::ViewState& 					viewState, 
                                                                                     const rendering::RenderState& 					renderState )
    {
        ENSURE_OR_THROW( xBitmap.is(), 
                          "CanvasHelper::drawBitmap: bitmap is NULL");

        // no color set -> this is equivalent to a plain drawBitmap(), then
        if( renderState.DeviceColor.getLength() < 3 )
            return drawBitmap( pCanvas, xBitmap, viewState, renderState );

        if( needOutput() )
        {            
            GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );

            setupGraphicsState( pGraphics, viewState, renderState );

            BitmapSharedPtr pBitmap( tools::bitmapFromXBitmap( xBitmap ) );
            Gdiplus::Rect aRect( 0, 0, 
                                 pBitmap->GetWidth(),
                                 pBitmap->GetHeight() );

            // Setup an ImageAttributes with an alpha-modulating
            // color matrix.
            const rendering::ARGBColor& rARGBColor(
                mpDevice->getDeviceColorSpace()->convertToARGB(renderState.DeviceColor)[0]);

            Gdiplus::ImageAttributes aImgAttr;
            tools::setModulateImageAttributes( aImgAttr,
                                               rARGBColor.Red,
                                               rARGBColor.Green,
                                               rARGBColor.Blue,
                                               rARGBColor.Alpha );

            ENSURE_OR_THROW( 
                Gdiplus::Ok == pGraphics->DrawImage( pBitmap.get(), 
                                                     aRect,
                                                     0, 0,
                                                     pBitmap->GetWidth(),
                                                     pBitmap->GetHeight(),
                                                     Gdiplus::UnitPixel,
                                                     &aImgAttr,
                                                     NULL,
                                                     NULL ),
                "CanvasHelper::drawBitmapModulated(): GDI+ call failed" );
        }

        // TODO(P1): Provide caching here.
        return uno::Reference< rendering::XCachedPrimitive >(NULL);
    }

    uno::Reference< rendering::XGraphicDevice > CanvasHelper::getDevice()
    {
        return uno::Reference< rendering::XGraphicDevice >(mpDevice);
    }

    // private helper
    // --------------------------------------------------

    Gdiplus::CompositingMode CanvasHelper::calcCompositingMode( sal_Int8 nMode )
    {
        Gdiplus::CompositingMode aRet( Gdiplus::CompositingModeSourceOver );

        switch( nMode )
        {
            case rendering::CompositeOperation::OVER:
                // FALLTHROUGH intended
            case rendering::CompositeOperation::CLEAR:
                aRet = Gdiplus::CompositingModeSourceOver;
                break;

            case rendering::CompositeOperation::SOURCE:
                aRet = Gdiplus::CompositingModeSourceCopy;
                break;

            case rendering::CompositeOperation::DESTINATION:
                // FALLTHROUGH intended
            case rendering::CompositeOperation::UNDER:
                // FALLTHROUGH intended
            case rendering::CompositeOperation::INSIDE:
                // FALLTHROUGH intended
            case rendering::CompositeOperation::INSIDE_REVERSE:
                // FALLTHROUGH intended
            case rendering::CompositeOperation::OUTSIDE:
                // FALLTHROUGH intended
            case rendering::CompositeOperation::OUTSIDE_REVERSE:
                // FALLTHROUGH intended
            case rendering::CompositeOperation::ATOP:
                // FALLTHROUGH intended
            case rendering::CompositeOperation::ATOP_REVERSE:
                // FALLTHROUGH intended
            case rendering::CompositeOperation::XOR:
                // FALLTHROUGH intended
            case rendering::CompositeOperation::ADD:
                // FALLTHROUGH intended
            case rendering::CompositeOperation::SATURATE:
                // TODO(F2): Problem, because GDI+ only knows about two compositing modes
                aRet = Gdiplus::CompositingModeSourceOver;
                break;
                
            default:
                ENSURE_OR_THROW( false, "CanvasHelper::calcCompositingMode: unexpected mode" );
                break;
        }

        return aRet;
    }

    void CanvasHelper::setupGraphicsState( GraphicsSharedPtr&            rGraphics,
                                           const rendering::ViewState& 	 viewState, 
                                           const rendering::RenderState& renderState )
    {
        ENSURE_OR_THROW( needOutput(), 
                          "CanvasHelper::setupGraphicsState: primary graphics invalid" );
        ENSURE_OR_THROW( mpDevice,
                          "CanvasHelper::setupGraphicsState: reference device invalid" );

        // setup view transform first. Clipping e.g. depends on it
        ::basegfx::B2DHomMatrix aTransform;
        ::canvas::tools::getViewStateTransform(aTransform, viewState);

        // add output offset
        if( !maOutputOffset.equalZero() )
        {
            const basegfx::B2DHomMatrix aOutputOffset(basegfx::tools::createTranslateB2DHomMatrix(
                maOutputOffset.getX(), maOutputOffset.getY()));
            aTransform = aOutputOffset * aTransform;
        }

        Gdiplus::Matrix aMatrix;
        tools::gdiPlusMatrixFromB2DHomMatrix( aMatrix, aTransform );

        ENSURE_OR_THROW( 
            Gdiplus::Ok == rGraphics->SetTransform( &aMatrix ),
            "CanvasHelper::setupGraphicsState(): Failed to set GDI+ transformation" );

        // setup view and render state clipping
        ENSURE_OR_THROW( 
            Gdiplus::Ok == rGraphics->ResetClip(),
            "CanvasHelper::setupGraphicsState(): Failed to reset GDI+ clip" );

        if( viewState.Clip.is() )
        {
            GraphicsPathSharedPtr aClipPath( tools::graphicsPathFromXPolyPolygon2D( viewState.Clip ) );

            // TODO(P3): Cache clip. SetClip( GraphicsPath ) performs abyssmally on GDI+. 
            // Try SetClip( Rect ) or similar for simple clip paths (need some support in 
            // LinePolyPolygon, then)
            ENSURE_OR_THROW( 
                Gdiplus::Ok == rGraphics->SetClip( aClipPath.get(), 
                                                   Gdiplus::CombineModeIntersect ),
                "CanvasHelper::setupGraphicsState(): Cannot set GDI+ clip" );
        }

        // setup overall transform only now. View clip above was relative to 
        // view transform
        ::canvas::tools::mergeViewAndRenderTransform(aTransform,
                                                     viewState,
                                                     renderState);

        // add output offset
        if( !maOutputOffset.equalZero() )
        {
            const basegfx::B2DHomMatrix aOutputOffset(basegfx::tools::createTranslateB2DHomMatrix(
                maOutputOffset.getX(), maOutputOffset.getY()));
            aTransform = aOutputOffset * aTransform;
        }

        tools::gdiPlusMatrixFromB2DHomMatrix( aMatrix, aTransform );

        ENSURE_OR_THROW( 
            Gdiplus::Ok == rGraphics->SetTransform( &aMatrix ),
            "CanvasHelper::setupGraphicsState(): Cannot set GDI+ transformation" );

        if( renderState.Clip.is() )
        {
            GraphicsPathSharedPtr aClipPath( tools::graphicsPathFromXPolyPolygon2D( renderState.Clip ) );

            // TODO(P3): Cache clip. SetClip( GraphicsPath ) performs abyssmally on GDI+. 
            // Try SetClip( Rect ) or similar for simple clip paths (need some support in 
            // LinePolyPolygon, then)
            ENSURE_OR_THROW( 
                Gdiplus::Ok == rGraphics->SetClip( aClipPath.get(), 
                                                   Gdiplus::CombineModeIntersect ),
                "CanvasHelper::setupGraphicsState(): Cannot set GDI+ clip" );
        }

        // setup compositing
        const Gdiplus::CompositingMode eCompositing( calcCompositingMode( renderState.CompositeOperation ) );
        ENSURE_OR_THROW( 
            Gdiplus::Ok == rGraphics->SetCompositingMode( eCompositing ),
            "CanvasHelper::setupGraphicsState(): Cannot set GDI* compositing mode)" );
    }

    void CanvasHelper::flush() const
    {
        if( needOutput() )
            mpGraphicsProvider->getGraphics()->Flush( Gdiplus::FlushIntentionSync );
    }
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
