/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the 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/math.hxx>

#include <com/sun/star/rendering/CompositeOperation.hpp>
#include <com/sun/star/util/Endianness.hpp>
#include <com/sun/star/rendering/TextDirection.hpp>
#include <com/sun/star/rendering/TexturingMode.hpp>
#include <com/sun/star/rendering/PathCapType.hpp>
#include <com/sun/star/rendering/PathJoinType.hpp>
#include <com/sun/star/drawing/LineCap.hpp>

#include <tools/poly.hxx>
#include <vcl/window.hxx>
#include <vcl/bitmapex.hxx>
#include <vcl/bmpacc.hxx>
#include <vcl/canvastools.hxx>

#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/range/b2drectangle.hxx>
#include <basegfx/point/b2dpoint.hxx>
#include <basegfx/vector/b2dsize.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/polygon/b2dlinegeometry.hxx>
#include <basegfx/tools/canvastools.hxx>
#include <basegfx/numeric/ftools.hxx>

#include <utility>

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

#include "textlayout.hxx"
#include "canvashelper.hxx"
#include "canvasbitmap.hxx"
#include "impltools.hxx"
#include "canvasfont.hxx"


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

namespace vclcanvas
{
    namespace
    {
        basegfx::B2DLineJoin b2DJoineFromJoin( sal_Int8 nJoinType )
        {
            switch( nJoinType )
            {                
                case rendering::PathJoinType::NONE:
                    return basegfx::B2DLINEJOIN_NONE;

                case rendering::PathJoinType::MITER:
                    return basegfx::B2DLINEJOIN_MITER;

                case rendering::PathJoinType::ROUND:
                    return basegfx::B2DLINEJOIN_ROUND;

                case rendering::PathJoinType::BEVEL:
                    return basegfx::B2DLINEJOIN_BEVEL;

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

            return basegfx::B2DLINEJOIN_NONE;
        }

        drawing::LineCap unoCapeFromCap( sal_Int8 nCapType)
        {
            switch ( nCapType)
            {
                case rendering::PathCapType::BUTT:
                    return drawing::LineCap_BUTT;

                case rendering::PathCapType::ROUND:
                    return drawing::LineCap_ROUND;

                case rendering::PathCapType::SQUARE:
                    return drawing::LineCap_SQUARE;

                default:
                    ENSURE_OR_THROW( false,
                                      "unoCapeFromCap(): Unexpected cap type" );
            }
            return drawing::LineCap_BUTT;
        }
    }

    CanvasHelper::CanvasHelper() :
        mpDevice(),
        mpProtectedOutDev(),
        mpOutDev(),
        mp2ndOutDev(),
        mbHaveAlpha( false )
    {
    }

    void CanvasHelper::disposing()
    {
        mpDevice = NULL;
        mpProtectedOutDev.reset();
        mpOutDev.reset();
        mp2ndOutDev.reset();
    }

    void CanvasHelper::init( rendering::XGraphicDevice&     rDevice,
                             const OutDevProviderSharedPtr&	rOutDev, 
                             bool 							bProtect,
                             bool 							bHaveAlpha )
    {
        // cast away const, need to change refcount (as this is
        // ~invisible to client code, still logically const)
        mpDevice	= &rDevice;
        mbHaveAlpha = bHaveAlpha;

        setOutDev( rOutDev, bProtect );
    }

    void CanvasHelper::setOutDev( const OutDevProviderSharedPtr& rOutDev, 
                                  bool 							 bProtect )
    {
        if( bProtect )
            mpProtectedOutDev = rOutDev;
        else
            mpProtectedOutDev.reset();
            
        mpOutDev = rOutDev;
    }

    void CanvasHelper::setBackgroundOutDev( const OutDevProviderSharedPtr& rOutDev )
    {
        mp2ndOutDev = rOutDev;
        mp2ndOutDev->getOutDev().EnableMapMode( sal_False );
        mp2ndOutDev->getOutDev().SetAntialiasing( ANTIALIASING_ENABLE_B2DDRAW );
    }

    void CanvasHelper::clear()
    {
        // are we disposed?
        if( mpOutDev )
        {
            OutputDevice& rOutDev( mpOutDev->getOutDev() );
            tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDev );

            rOutDev.EnableMapMode( sal_False );
            rOutDev.SetAntialiasing( ANTIALIASING_ENABLE_B2DDRAW );
            rOutDev.SetLineColor( COL_WHITE );
            rOutDev.SetFillColor( COL_WHITE );
            rOutDev.DrawRect( Rectangle( Point(),
                                         rOutDev.GetOutputSizePixel()) );

            if( mp2ndOutDev )
            {
                OutputDevice& rOutDev2( mp2ndOutDev->getOutDev() );
                
                rOutDev2.SetDrawMode( DRAWMODE_DEFAULT );
                rOutDev2.EnableMapMode( sal_False );
                rOutDev2.SetAntialiasing( ANTIALIASING_ENABLE_B2DDRAW );
                rOutDev2.SetLineColor( COL_WHITE );
                rOutDev2.SetFillColor( COL_WHITE );
                rOutDev2.DrawRect( Rectangle( Point(),
                                              rOutDev2.GetOutputSizePixel()) );
                rOutDev2.SetDrawMode( DRAWMODE_BLACKLINE | DRAWMODE_BLACKFILL | DRAWMODE_BLACKTEXT |
                                      DRAWMODE_BLACKGRADIENT | DRAWMODE_BLACKBITMAP );
            }
        }
    }

    void CanvasHelper::drawPoint( const rendering::XCanvas* 	, 
                                  const geometry::RealPoint2D& 	aPoint,
                                  const rendering::ViewState& 	viewState,
                                  const rendering::RenderState&	renderState )
    {
        // are we disposed?
        if( mpOutDev )
        {
            // nope, render
            tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDev );
            setupOutDevState( viewState, renderState, LINE_COLOR );

            const Point aOutPoint( tools::mapRealPoint2D( aPoint,
                                                          viewState, renderState ) );
            // TODO(F1): alpha
            mpOutDev->getOutDev().DrawPixel( aOutPoint );

            if( mp2ndOutDev )
                mp2ndOutDev->getOutDev().DrawPixel( aOutPoint );
        }
    }

    void CanvasHelper::drawLine( const rendering::XCanvas* 		, 
                                 const geometry::RealPoint2D& 	aStartRealPoint2D,
                                 const geometry::RealPoint2D& 	aEndRealPoint2D,
                                 const rendering::ViewState& 	viewState,
                                 const rendering::RenderState& 	renderState )
    {
        // are we disposed?
        if( mpOutDev )
        {
            // nope, render
            tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDev );
            setupOutDevState( viewState, renderState, LINE_COLOR );

            const Point aStartPoint( tools::mapRealPoint2D( aStartRealPoint2D,
                                                            viewState, renderState ) );
            const Point aEndPoint( tools::mapRealPoint2D( aEndRealPoint2D,
                                                          viewState, renderState ) );
            // TODO(F2): alpha
            mpOutDev->getOutDev().DrawLine( aStartPoint, aEndPoint );

            if( mp2ndOutDev )
                mp2ndOutDev->getOutDev().DrawLine( aStartPoint, aEndPoint );
        }
    }

    void CanvasHelper::drawBezier( const rendering::XCanvas* 			, 
                                   const geometry::RealBezierSegment2D&	aBezierSegment,
                                   const geometry::RealPoint2D& 		_aEndPoint,
                                   const rendering::ViewState& 			viewState,
                                   const rendering::RenderState& 		renderState )
    {
        if( mpOutDev )
        {
            tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDev );
            setupOutDevState( viewState, renderState, LINE_COLOR );

            const Point& rStartPoint( tools::mapRealPoint2D( geometry::RealPoint2D(aBezierSegment.Px, 
                                                                                   aBezierSegment.Py),
                                                            viewState, renderState ) );
            const Point& rCtrlPoint1( tools::mapRealPoint2D( geometry::RealPoint2D(aBezierSegment.C1x, 
                                                                                   aBezierSegment.C1y),
                                                            viewState, renderState ) );
            const Point& rCtrlPoint2( tools::mapRealPoint2D( geometry::RealPoint2D(aBezierSegment.C2x, 
                                                                                   aBezierSegment.C2y),
                                                             viewState, renderState ) );
            const Point& rEndPoint( tools::mapRealPoint2D( _aEndPoint,
                                                           viewState, renderState ) );

            ::Polygon aPoly(4);
            aPoly.SetPoint( rStartPoint, 0 );
            aPoly.SetFlags( 0, POLY_NORMAL ); 
            aPoly.SetPoint( rCtrlPoint1, 1 );
            aPoly.SetFlags( 1, POLY_CONTROL ); 
            aPoly.SetPoint( rCtrlPoint2, 2 );
            aPoly.SetFlags( 2, POLY_CONTROL ); 
            aPoly.SetPoint( rEndPoint, 3 );
            aPoly.SetFlags( 3, POLY_NORMAL ); 

            // TODO(F2): alpha
            mpOutDev->getOutDev().DrawPolygon( aPoly );
            if( mp2ndOutDev )
                mp2ndOutDev->getOutDev().DrawPolygon( aPoly );
        }
    }

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

        if( mpOutDev )
        {
            tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDev );
            setupOutDevState( viewState, renderState, LINE_COLOR );

            const ::basegfx::B2DPolyPolygon& rPolyPoly( 
                ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon) );
            const PolyPolygon aPolyPoly( tools::mapPolyPolygon( rPolyPoly, viewState, renderState ) );

            if( rPolyPoly.isClosed() )
            {
                mpOutDev->getOutDev().DrawPolyPolygon( aPolyPoly );
                
                if( mp2ndOutDev )
                    mp2ndOutDev->getOutDev().DrawPolyPolygon( aPolyPoly );
            }
            else
            {
                // mixed open/closed state. Cannot render open polygon
                // via DrawPolyPolygon(), since that implicitley
                // closed every polygon. OTOH, no need to distinguish
                // further and render closed polygons via
                // DrawPolygon(), and open ones via DrawPolyLine():
                // closed polygons will simply already contain the
                // closing segment.
                sal_uInt16 nSize( aPolyPoly.Count() );

                for( sal_uInt16 i=0; i<nSize; ++i )
                {
                    mpOutDev->getOutDev().DrawPolyLine( aPolyPoly[i] );
                    
                    if( mp2ndOutDev )
                        mp2ndOutDev->getOutDev().DrawPolyLine( aPolyPoly[i] );
                }
            }
        }

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

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

        if( mpOutDev )
        {
            tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDev );

            ::basegfx::B2DHomMatrix aMatrix;
            ::canvas::tools::mergeViewAndRenderTransform(aMatrix, viewState, renderState);

            ::basegfx::B2DSize aLinePixelSize(strokeAttributes.StrokeWidth,
                                              strokeAttributes.StrokeWidth);
            aLinePixelSize *= aMatrix;

            ::basegfx::B2DPolyPolygon aPolyPoly( 
                ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon) );

            if( aPolyPoly.areControlPointsUsed() )
            {
                // AW: Not needed for ApplyLineDashing anymore; should be removed
                aPolyPoly = ::basegfx::tools::adaptiveSubdivideByAngle(aPolyPoly);
            }

            // apply dashing, if any
            if( strokeAttributes.DashArray.getLength() )
            {
                const ::std::vector<double>& aDashArray(
                    ::comphelper::sequenceToContainer< ::std::vector<double> >(strokeAttributes.DashArray) );

                ::basegfx::B2DPolyPolygon aDashedPolyPoly;

                for( sal_uInt32 i=0; i<aPolyPoly.count(); ++i )
                {
                    // AW: new interface; You may also get gaps in the same run now
                    basegfx::tools::applyLineDashing(aPolyPoly.getB2DPolygon(i), aDashArray, &aDashedPolyPoly);
                    //aDashedPolyPoly.append( 
                    //    ::basegfx::tools::applyLineDashing( aPolyPoly.getB2DPolygon(i),
                    //                                        aDashArray ) );
                }
                
                aPolyPoly = aDashedPolyPoly;
            }

            ::basegfx::B2DPolyPolygon aStrokedPolyPoly;
            if( aLinePixelSize.getLength() < 1.42 )
            {
                // line width < 1.0 in device pixel, thus, output as a
                // simple hairline poly-polygon
                setupOutDevState( viewState, renderState, LINE_COLOR );

                aStrokedPolyPoly = aPolyPoly;
            }
            else
            {
                // render as a 'thick' line
                setupOutDevState( viewState, renderState, FILL_COLOR );
                
                for( sal_uInt32 i=0; i<aPolyPoly.count(); ++i )
                {
                    // TODO(F2): Use MiterLimit from StrokeAttributes,
                    // need to convert it here to angle.

                    // TODO(F2): Also use Cap settings from
                    // StrokeAttributes, the
                    // createAreaGeometryForLineStartEnd() method does not
                    // seem to fit very well here

                    // AW: New interface, will create bezier polygons now
                    aStrokedPolyPoly.append(basegfx::tools::createAreaGeometry(
                        aPolyPoly.getB2DPolygon(i), 
                        strokeAttributes.StrokeWidth*0.5, 
                        b2DJoineFromJoin(strokeAttributes.JoinType),
                        unoCapeFromCap(strokeAttributes.StartCapType)));
                    //aStrokedPolyPoly.append( 
                    //    ::basegfx::tools::createAreaGeometryForPolygon( aPolyPoly.getB2DPolygon(i),
                    //                                                    strokeAttributes.StrokeWidth*0.5,
                    //                                                    b2DJoineFromJoin(strokeAttributes.JoinType) ) );
                }
            }

            // transform only _now_, all the StrokeAttributes are in
            // user coordinates.
            aStrokedPolyPoly.transform( aMatrix );
            
            const PolyPolygon aVCLPolyPoly( aStrokedPolyPoly );

            // TODO(F2): When using alpha here, must handle that via
            // temporary surface or somesuch.

            // Note: the generated stroke poly-polygon is NOT free of
            // self-intersections. Therefore, if we would render it
            // via OutDev::DrawPolyPolygon(), on/off fill would
            // generate off areas on those self-intersections.
            sal_uInt16 nSize( aVCLPolyPoly.Count() );
            
            for( sal_uInt16 i=0; i<nSize; ++i )
            {
                if( aStrokedPolyPoly.getB2DPolygon( i ).isClosed() ) {
                    mpOutDev->getOutDev().DrawPolygon( aVCLPolyPoly[i] );
                    if( mp2ndOutDev )
                        mp2ndOutDev->getOutDev().DrawPolygon( aVCLPolyPoly[i] );
                } else {
                    const sal_uInt16 nPolySize = aVCLPolyPoly[i].GetSize();
                    if( nPolySize ) {
                        Point rPrevPoint = aVCLPolyPoly[i].GetPoint( 0 );
                        Point rPoint;

                        for( sal_uInt16 j=1; j<nPolySize; j++ ) {
                            rPoint = aVCLPolyPoly[i].GetPoint( j );
                            mpOutDev->getOutDev().DrawLine( rPrevPoint, rPoint );
                            if( mp2ndOutDev )
                                mp2ndOutDev->getOutDev().DrawLine( rPrevPoint, rPoint );
                            rPrevPoint = rPoint;
                        }
                    }
                }
            }
        }

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

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTexturedPolyPolygon( const rendering::XCanvas* 							, 
                                                                                           const uno::Reference< rendering::XPolyPolygon2D >& 	,
                                                                                           const rendering::ViewState& 							,
                                                                                           const rendering::RenderState& 						,
                                                                                           const uno::Sequence< rendering::Texture >& 			,
                                                                                           const rendering::StrokeAttributes& 					 )
    {
        return uno::Reference< rendering::XCachedPrimitive >(NULL);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTextureMappedPolyPolygon( const rendering::XCanvas* 							, 
                                                                                                const uno::Reference< rendering::XPolyPolygon2D >& 	,
                                                                                                const rendering::ViewState& 						,
                                                                                                const rendering::RenderState& 						,
                                                                                                const uno::Sequence< rendering::Texture >& 			,
                                                                                                const uno::Reference< geometry::XMapping2D >& 		,
                                                                                                const rendering::StrokeAttributes& 					 )
    {
        return uno::Reference< rendering::XCachedPrimitive >(NULL);
    }

    uno::Reference< rendering::XPolyPolygon2D >   CanvasHelper::queryStrokeShapes( const rendering::XCanvas* 							, 
                                                                                   const uno::Reference< rendering::XPolyPolygon2D >& 	,
                                                                                   const rendering::ViewState& 							,
                                                                                   const rendering::RenderState& 						,
                                                                                   const rendering::StrokeAttributes& 					 )
    {
        return uno::Reference< rendering::XPolyPolygon2D >(NULL);
    }

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

        if( mpOutDev )
        {
            tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDev );

            const int nTransparency( setupOutDevState( viewState, renderState, FILL_COLOR ) );
            const int nTransPercent( (nTransparency * 100 + 128) / 255 );  // normal rounding, no truncation here
            ::basegfx::B2DPolyPolygon aB2DPolyPoly(
                ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon));
            aB2DPolyPoly.setClosed(true); // ensure closed poly, otherwise VCL does not fill
            const PolyPolygon aPolyPoly( tools::mapPolyPolygon( 
                                             aB2DPolyPoly,
                                             viewState, renderState ) );
            const bool bSourceAlpha( renderState.CompositeOperation == rendering::CompositeOperation::SOURCE );
            if( !nTransparency || bSourceAlpha )
            {
                mpOutDev->getOutDev().DrawPolyPolygon( aPolyPoly );
            }
            else
            {
                mpOutDev->getOutDev().DrawTransparent( aPolyPoly, (sal_uInt16)nTransPercent );
            }

            if( mp2ndOutDev )
            {
                if( !nTransparency || bSourceAlpha )
                {
                    // HACK. Normally, CanvasHelper does not care
                    // about actually what mp2ndOutDev is...
                    if( bSourceAlpha && nTransparency == 255 )
                    {
                        mp2ndOutDev->getOutDev().SetDrawMode( DRAWMODE_WHITELINE | DRAWMODE_WHITEFILL | DRAWMODE_WHITETEXT |
                                                              DRAWMODE_WHITEGRADIENT | DRAWMODE_WHITEBITMAP );
                        mp2ndOutDev->getOutDev().SetFillColor( COL_WHITE );
                        mp2ndOutDev->getOutDev().DrawPolyPolygon( aPolyPoly );
                        mp2ndOutDev->getOutDev().SetDrawMode( DRAWMODE_BLACKLINE | DRAWMODE_BLACKFILL | DRAWMODE_BLACKTEXT |
                                                              DRAWMODE_BLACKGRADIENT | DRAWMODE_BLACKBITMAP );
                    }
                    else
                    {
                        mp2ndOutDev->getOutDev().DrawPolyPolygon( aPolyPoly );
                    }
                }
                else
                {
                    mp2ndOutDev->getOutDev().DrawTransparent( aPolyPoly, (sal_uInt16)nTransPercent );
                }
            }
        }

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

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTextureMappedPolyPolygon( const rendering::XCanvas* 							, 
                                                                                              const uno::Reference< rendering::XPolyPolygon2D >& 	,
                                                                                              const rendering::ViewState& 							,
                                                                                              const rendering::RenderState& 						,
                                                                                              const uno::Sequence< rendering::Texture >& 			,
                                                                                              const uno::Reference< geometry::XMapping2D >& 			 )
    {
        return uno::Reference< rendering::XCachedPrimitive >(NULL);
    }

    uno::Reference< rendering::XCanvasFont > CanvasHelper::createFont( const rendering::XCanvas* 						, 
                                                                       const rendering::FontRequest& 					fontRequest,
                                                                       const uno::Sequence< beans::PropertyValue >& 	extraFontProperties,
                                                                       const geometry::Matrix2D& 						fontMatrix )
    {
        if( mpOutDev && mpDevice )
        {
            // TODO(F2): font properties and font matrix
            return uno::Reference< rendering::XCanvasFont >(
                    new CanvasFont(fontRequest, extraFontProperties, fontMatrix, 
                                   *mpDevice, mpOutDev) );
        }

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

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

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

        if( mpOutDev )
        {
            tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDev );

            ::Point aOutpos;
            if( !setupTextOutput( aOutpos, viewState, renderState, xFont ) )
                return uno::Reference< rendering::XCachedPrimitive >(NULL); // no output necessary

            // change text direction and layout mode
            sal_uIntPtr nLayoutMode(0);
            switch( textDirection )
            {
                case rendering::TextDirection::WEAK_LEFT_TO_RIGHT:
                    nLayoutMode |= TEXT_LAYOUT_BIDI_LTR;
                    // FALLTHROUGH intended
                case rendering::TextDirection::STRONG_LEFT_TO_RIGHT:
                    nLayoutMode |= TEXT_LAYOUT_BIDI_LTR | TEXT_LAYOUT_BIDI_STRONG;
                    nLayoutMode |= TEXT_LAYOUT_TEXTORIGIN_LEFT;
                    break;

                case rendering::TextDirection::WEAK_RIGHT_TO_LEFT:
                    nLayoutMode |= TEXT_LAYOUT_BIDI_RTL;
                    // FALLTHROUGH intended
                case rendering::TextDirection::STRONG_RIGHT_TO_LEFT:
                    nLayoutMode |= TEXT_LAYOUT_BIDI_RTL | TEXT_LAYOUT_BIDI_STRONG;
                    nLayoutMode |= TEXT_LAYOUT_TEXTORIGIN_RIGHT;
                    break;
            }

            // TODO(F2): alpha
            mpOutDev->getOutDev().SetLayoutMode( nLayoutMode );
            mpOutDev->getOutDev().DrawText( aOutpos,
                                            text.Text,
                                            ::canvas::tools::numeric_cast<sal_uInt16>(text.StartPosition),
                                            ::canvas::tools::numeric_cast<sal_uInt16>(text.Length) );

            if( mp2ndOutDev )
            {
                mp2ndOutDev->getOutDev().SetLayoutMode( nLayoutMode );
                mp2ndOutDev->getOutDev().DrawText( aOutpos,
                                                   text.Text,
                                                   ::canvas::tools::numeric_cast<sal_uInt16>(text.StartPosition),
                                                   ::canvas::tools::numeric_cast<sal_uInt16>(text.Length) );
            }
        }

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

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

        TextLayout* pTextLayout = dynamic_cast< TextLayout* >( xLayoutedText.get() );

        if( pTextLayout )
        {
            if( mpOutDev )
            {
                tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDev );

                // TODO(T3): Race condition. We're taking the font
                // from xLayoutedText, and then calling draw() at it,
                // without exclusive access. Move setupTextOutput(),
                // e.g. to impltools?

                ::Point aOutpos;
                if( !setupTextOutput( aOutpos, viewState, renderState, xLayoutedText->getFont() ) )
                    return uno::Reference< rendering::XCachedPrimitive >(NULL); // no output necessary

                // TODO(F2): What about the offset scalings?
                // TODO(F2): alpha
                pTextLayout->draw( mpOutDev->getOutDev(), aOutpos, viewState, renderState );

                if( mp2ndOutDev )
                    pTextLayout->draw( mp2ndOutDev->getOutDev(), aOutpos, viewState, renderState );
            }
        }
        else
        {
            ENSURE_ARG_OR_THROW( false,
                                 "TextLayout not compatible with this canvas" );
        }

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

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

        ::canvas::tools::verifyInput( renderState,
                                      BOOST_CURRENT_FUNCTION,
                                      mpDevice,
                                      4,
                                      bModulateColors ? 3 : 0 );

        if( mpOutDev )
        {
            tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDev );
            setupOutDevState( viewState, renderState, IGNORE_COLOR );

            ::basegfx::B2DHomMatrix aMatrix;
            ::canvas::tools::mergeViewAndRenderTransform(aMatrix, viewState, renderState);

            ::basegfx::B2DPoint aOutputPos( 0.0, 0.0 );
            aOutputPos *= aMatrix;

            BitmapEx aBmpEx( tools::bitmapExFromXBitmap(xBitmap) );

            // TODO(F2): Implement modulation again for other color
            // channels (currently, works only for alpha). Note: this
            // is already implemented in transformBitmap()
            if( bModulateColors &&
                renderState.DeviceColor.getLength() > 3 )
            {
                // optimize away the case where alpha modulation value
                // is 1.0 - we then simply switch off modulation at all
                bModulateColors = !::rtl::math::approxEqual(
                    renderState.DeviceColor[3], 1.0);
            }

            // check whether we can render bitmap as-is: must not
            // modulate colors, matrix must either be the identity
            // transform (that's clear), _or_ contain only
            // translational components.
			if( !bModulateColors &&
                (aMatrix.isIdentity() ||
                 (::basegfx::fTools::equalZero( aMatrix.get(0,1) ) &&
                  ::basegfx::fTools::equalZero( aMatrix.get(1,0) ) &&
                  ::rtl::math::approxEqual(aMatrix.get(0,0), 1.0) &&
                  ::rtl::math::approxEqual(aMatrix.get(1,1), 1.0)) ) )
            {
                // optimized case: identity matrix, or only
                // translational components.
                mpOutDev->getOutDev().DrawBitmapEx( ::vcl::unotools::pointFromB2DPoint( aOutputPos ),
                                                    aBmpEx );

                if( mp2ndOutDev )
                    mp2ndOutDev->getOutDev().DrawBitmapEx( ::vcl::unotools::pointFromB2DPoint( aOutputPos ),
                                                           aBmpEx );

                // Returning a cache object is not useful, the XBitmap
                // itself serves this purpose
                return uno::Reference< rendering::XCachedPrimitive >(NULL);
            }
            else
            {
                // Matrix contains non-trivial transformation (or
                // color modulation is requested), decompose to check
                // whether GraphicObject suffices
                ::basegfx::B2DVector aScale;
                double				 nRotate;
                double				 nShearX;
                aMatrix.decompose( aScale, aOutputPos, nRotate, nShearX );
                
                GraphicAttr 			aGrfAttr;
                GraphicObjectSharedPtr 	pGrfObj;

                ::Size aBmpSize( aBmpEx.GetSizePixel() );

                // setup alpha modulation
                if( bModulateColors )
                {
                    const double nAlphaModulation( renderState.DeviceColor[3] );

                    // TODO(F1): Note that the GraphicManager has a
                    // subtle difference in how it calculates the
                    // resulting alpha value: it's using the inverse
                    // alpha values (i.e. 'transparency'), and
                    // calculates transOrig + transModulate, instead
                    // of transOrig + transModulate -
                    // transOrig*transModulate (which would be
                    // equivalent to the origAlpha*modulateAlpha the
                    // DX canvas performs)
                    aGrfAttr.SetTransparency( 
                        static_cast< sal_uInt8 >( 
                            ::basegfx::fround( 255.0*( 1.0 - nAlphaModulation ) ) ) );
                }

                if( ::basegfx::fTools::equalZero( nShearX ) )
                {
                    // no shear, GraphicObject is enough (the
                    // GraphicObject only supports scaling, rotation
                    // and translation)

                    // #i75339# don't apply mirror flags, having
                    // negative size values is enough to make
                    // GraphicObject flip the bitmap

                    // The angle has to be mapped from radian to tenths of
                    // degress with the orientation reversed: [0,2Pi) ->
                    // (3600,0].  Note that the original angle may have
                    // values outside the [0,2Pi) interval.
                    const double nAngleInTenthOfDegrees (3600.0 - nRotate * 3600.0 / (2*M_PI));
                    aGrfAttr.SetRotation( static_cast< sal_uInt16 >(::basegfx::fround(nAngleInTenthOfDegrees)) );

                    pGrfObj.reset( new GraphicObject( aBmpEx ) );
                }
                else
                {
                    // modify output position, to account for the fact
                    // that transformBitmap() always normalizes its output
                    // bitmap into the smallest enclosing box.
                    ::basegfx::B2DRectangle	aDestRect;            
                    ::canvas::tools::calcTransformedRectBounds( aDestRect, 
                                                                ::basegfx::B2DRectangle(0,
                                                                                        0,
                                                                                        aBmpSize.Width(),
                                                                                        aBmpSize.Height()),
                                                                aMatrix );

                    aOutputPos.setX( aDestRect.getMinX() );
                    aOutputPos.setY( aDestRect.getMinY() );

                    // complex transformation, use generic affine bitmap
                    // transformation
                    aBmpEx = tools::transformBitmap( aBmpEx,
                                                     aMatrix, 
                                                     renderState.DeviceColor,
                                                     tools::MODULATE_NONE );

                    pGrfObj.reset( new GraphicObject( aBmpEx ) );

                    // clear scale values, generated bitmap already
                    // contains scaling
                    aScale.setX( 1.0 ); aScale.setY( 1.0 );

                    // update bitmap size, bitmap has changed above.
                    aBmpSize = aBmpEx.GetSizePixel();
                }

                // output GraphicObject
                const ::Point aPt( ::vcl::unotools::pointFromB2DPoint( aOutputPos ) );
                const ::Size  aSz( ::basegfx::fround( aScale.getX() * aBmpSize.Width() ),
                                   ::basegfx::fround( aScale.getY() * aBmpSize.Height() ) );

                pGrfObj->Draw( &mpOutDev->getOutDev(),
                               aPt,
                               aSz,
                               &aGrfAttr );

                if( mp2ndOutDev )
                    pGrfObj->Draw( &mp2ndOutDev->getOutDev(),
                                   aPt,
                                   aSz,
                                   &aGrfAttr );
                
                // created GraphicObject, which possibly cached
                // display bitmap - return cache object, to retain
                // that information.
                return uno::Reference< rendering::XCachedPrimitive >(
                    new CachedBitmap( pGrfObj,
                                      aPt,
                                      aSz,
                                      aGrfAttr,
                                      viewState,
                                      renderState,
                                      // cast away const, need to
                                      // change refcount (as this is
                                      // ~invisible to client code,
                                      // still logically const)
                                      const_cast< rendering::XCanvas* >(pCanvas)) );
            }
        }

        // Nothing rendered
        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 )
    {
        return implDrawBitmap( pCanvas, 
                               xBitmap,
                               viewState,
                               renderState,
                               false );
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmapModulated( const rendering::XCanvas* 						pCanvas, 
                                                                                     const uno::Reference< rendering::XBitmap >& 	xBitmap,
                                                                                     const rendering::ViewState& 					viewState,
                                                                                     const rendering::RenderState& 					renderState )
    {
        return implDrawBitmap( pCanvas, 
                               xBitmap,
                               viewState,
                               renderState,
                               true );
    }

    uno::Reference< rendering::XGraphicDevice > CanvasHelper::getDevice()
    {
        // cast away const, need to change refcount (as this is
        // ~invisible to client code, still logically const)
        return uno::Reference< rendering::XGraphicDevice >(mpDevice);
    }

    void CanvasHelper::copyRect( const rendering::XCanvas* 							, 
                                 const uno::Reference< rendering::XBitmapCanvas >& 	, 
                                 const geometry::RealRectangle2D& 					, 
                                 const rendering::ViewState& 						, 
                                 const rendering::RenderState& 						, 
                                 const geometry::RealRectangle2D& 					, 
                                 const rendering::ViewState& 						, 
                                 const rendering::RenderState& 						 )
    {
        // TODO(F1)
    }
            
    geometry::IntegerSize2D CanvasHelper::getSize()
    {
        if( !mpOutDev.get() )
            return geometry::IntegerSize2D(); // we're disposed

        return ::vcl::unotools::integerSize2DFromSize( mpOutDev->getOutDev().GetOutputSizePixel() );
    }

    uno::Reference< rendering::XBitmap > CanvasHelper::getScaledBitmap( const geometry::RealSize2D& newSize, 
                                                                        sal_Bool 					beFast )
    {
        if( !mpOutDev.get() || !mpDevice )
            return uno::Reference< rendering::XBitmap >(); // we're disposed

        OutputDevice& rOutDev( mpOutDev->getOutDev() );

        tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDev );
        rOutDev.EnableMapMode( sal_False );
        rOutDev.SetAntialiasing( ANTIALIASING_ENABLE_B2DDRAW );

        // TODO(F2): Support alpha vdev canvas here
        const Point aEmptyPoint(0,0);
        const Size  aBmpSize( rOutDev.GetOutputSizePixel() );

        Bitmap aBitmap( rOutDev.GetBitmap(aEmptyPoint, aBmpSize) );

        aBitmap.Scale( ::vcl::unotools::sizeFromRealSize2D(newSize), 
                       beFast ? BMP_SCALE_FASTESTINTERPOLATE : BMP_SCALE_INTERPOLATE );

        return uno::Reference< rendering::XBitmap >( 
            new CanvasBitmap( aBitmap, *mpDevice, mpOutDev ) );
    }

    uno::Sequence< sal_Int8 > CanvasHelper::getData( rendering::IntegerBitmapLayout& 	 rLayout,
                                                     const geometry::IntegerRectangle2D& rect )
    {
        if( !mpOutDev.get() )
            return uno::Sequence< sal_Int8 >(); // we're disposed

        rLayout = getMemoryLayout();

        // TODO(F2): Support alpha canvas here
        const Rectangle	aRect( ::vcl::unotools::rectangleFromIntegerRectangle2D(rect) );

        OutputDevice& rOutDev( mpOutDev->getOutDev() );

        tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDev );
        rOutDev.EnableMapMode( sal_False );
        rOutDev.SetAntialiasing( ANTIALIASING_ENABLE_B2DDRAW );

        Bitmap aBitmap( rOutDev.GetBitmap(aRect.TopLeft(), 
                                          aRect.GetSize()) );

        ScopedBitmapReadAccess pReadAccess( aBitmap.AcquireReadAccess(),
                                            aBitmap );

        ENSURE_OR_THROW( pReadAccess.get() != NULL,
                         "Could not acquire read access to OutDev bitmap" );

        const sal_Int32 nWidth( rect.X2 - rect.X1 );
        const sal_Int32 nHeight( rect.Y2 - rect.Y1 );

        rLayout.ScanLines = nHeight;
        rLayout.ScanLineBytes = nWidth*4;
        rLayout.ScanLineStride = rLayout.ScanLineBytes;        

        uno::Sequence< sal_Int8 > aRes( 4*nWidth*nHeight );
        sal_Int8* pRes = aRes.getArray();

        int nCurrPos(0);
        for( int y=0; y<nHeight; ++y )
        {
            for( int x=0; x<nWidth; ++x )
            {
                pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetRed();
                pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetGreen();
                pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetBlue();
                pRes[ nCurrPos++ ] = -1;
            }
        }

        return aRes;
    }

    void CanvasHelper::setData( const uno::Sequence< sal_Int8 >& 		data, 
                                const rendering::IntegerBitmapLayout& 	aLayout, 
                                const geometry::IntegerRectangle2D&		rect )
    {
        if( !mpOutDev.get() )
            return; // we're disposed

        const rendering::IntegerBitmapLayout aRefLayout( getMemoryLayout() );
        ENSURE_ARG_OR_THROW( aRefLayout.PlaneStride != aLayout.PlaneStride ||
                             aRefLayout.ColorSpace  != aLayout.ColorSpace ||
                             aRefLayout.Palette     != aLayout.Palette ||
                             aRefLayout.IsMsbFirst  != aLayout.IsMsbFirst,
                             "Mismatching memory layout" );

        OutputDevice& rOutDev( mpOutDev->getOutDev() );

        tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDev );
        rOutDev.EnableMapMode( sal_False );
        rOutDev.SetAntialiasing( ANTIALIASING_ENABLE_B2DDRAW );

        const Rectangle aRect( ::vcl::unotools::rectangleFromIntegerRectangle2D(rect) );
        const sal_uInt16 	nBitCount( ::std::min( (sal_uInt16)24U, 
                                               (sal_uInt16)rOutDev.GetBitCount() ) );
        const BitmapPalette* pPalette = NULL;

        if( nBitCount <= 8 )
        {
            // TODO(Q1): Extract this to a common place, e.g. GraphicDevice

            // try to determine palette from output device (by
            // extracting a 1,1 bitmap, and querying it)
            const Point aEmptyPoint;
            const Size  aSize(1,1);
            Bitmap aTmpBitmap( rOutDev.GetBitmap( aEmptyPoint,
                                                  aSize ) );
            
            ScopedBitmapReadAccess pReadAccess( aTmpBitmap.AcquireReadAccess(),
                                                aTmpBitmap );

            pPalette = &pReadAccess->GetPalette();
        }

        // TODO(F2): Support alpha canvas here
        Bitmap aBitmap( aRect.GetSize(), nBitCount, pPalette );

        bool bCopyBack( false ); // only copy something back, if we
                                 // actually changed some pixel
        {
            ScopedBitmapWriteAccess pWriteAccess( aBitmap.AcquireWriteAccess(),
                                                  aBitmap );
            
            ENSURE_OR_THROW( pWriteAccess.get() != NULL,
                             "Could not acquire write access to OutDev bitmap" );

            // for the time being, always read as RGB 
            const sal_Int32 nWidth( rect.X2 - rect.X1 );
            const sal_Int32 nHeight( rect.Y2 - rect.Y1 );
            int x, y, nCurrPos(0);
            for( y=0; y<nHeight; ++y )
            {
                switch( pWriteAccess->GetScanlineFormat() )
                {
                    case BMP_FORMAT_8BIT_PAL:
                    {
                        Scanline pScan = pWriteAccess->GetScanline( y );

                        for( x=0; x<nWidth; ++x )
                        {
                            *pScan++ = (sal_uInt8)pWriteAccess->GetBestPaletteIndex(
                                BitmapColor( data[ nCurrPos ],
                                             data[ nCurrPos+1 ],
                                             data[ nCurrPos+2 ] ) );

                            nCurrPos += 4;
                        }
                    }
                    break;

                    case BMP_FORMAT_24BIT_TC_BGR:
                    {
                        Scanline pScan = pWriteAccess->GetScanline( y );

                        for( x=0; x<nWidth; ++x )
                        {
                            *pScan++ = data[ nCurrPos+2 ];
                            *pScan++ = data[ nCurrPos+1 ];
                            *pScan++ = data[ nCurrPos   ];

                            nCurrPos += 4;
                        }
                    }
                    break;

                    case BMP_FORMAT_24BIT_TC_RGB:
                    {
                        Scanline pScan = pWriteAccess->GetScanline( y );

                        for( x=0; x<nWidth; ++x )
                        {
                            *pScan++ = data[ nCurrPos   ];
                            *pScan++ = data[ nCurrPos+1 ];
                            *pScan++ = data[ nCurrPos+2 ];

                            nCurrPos += 4;
                        }
                    }
                    break;

                    default:
                    {
                        for( x=0; x<nWidth; ++x )
                        {
                            pWriteAccess->SetPixel( y, x, BitmapColor( data[ nCurrPos   ],
                                                                       data[ nCurrPos+1 ],
                                                                       data[ nCurrPos+2 ] ) );
                            nCurrPos += 4;
                        }
                    }
                    break;
                }
            }

            bCopyBack = true;
        }

        // copy back only here, since the BitmapAccessors must be
        // destroyed beforehand
        if( bCopyBack )
        {
            // TODO(F2): Support alpha canvas here
            rOutDev.DrawBitmap(aRect.TopLeft(), aBitmap);
        }
    }

    void CanvasHelper::setPixel( const uno::Sequence< sal_Int8 >& 		color, 
                                 const rendering::IntegerBitmapLayout&	rLayout, 
                                 const geometry::IntegerPoint2D& 		pos )
    {
        if( !mpOutDev.get() )
            return; // we're disposed

        OutputDevice& rOutDev( mpOutDev->getOutDev() );

        tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDev );
        rOutDev.EnableMapMode( sal_False );
        rOutDev.SetAntialiasing( ANTIALIASING_ENABLE_B2DDRAW );

        const Size aBmpSize( rOutDev.GetOutputSizePixel() );

        ENSURE_ARG_OR_THROW( pos.X >= 0 && pos.X < aBmpSize.Width(), 
                             "X coordinate out of bounds" );
        ENSURE_ARG_OR_THROW( pos.Y >= 0 && pos.Y < aBmpSize.Height(), 
                             "Y coordinate out of bounds" );
        ENSURE_ARG_OR_THROW( color.getLength() > 3, 
                             "not enough color components" );

        const rendering::IntegerBitmapLayout aRefLayout( getMemoryLayout() );
        ENSURE_ARG_OR_THROW( aRefLayout.PlaneStride != rLayout.PlaneStride ||
                             aRefLayout.ColorSpace  != rLayout.ColorSpace ||
                             aRefLayout.Palette     != rLayout.Palette ||
                             aRefLayout.IsMsbFirst  != rLayout.IsMsbFirst,
                             "Mismatching memory layout" );

        // TODO(F2): Support alpha canvas here
        rOutDev.DrawPixel( ::vcl::unotools::pointFromIntegerPoint2D( pos ), 
                           ::canvas::tools::stdIntSequenceToColor( color ));
    }

    uno::Sequence< sal_Int8 > CanvasHelper::getPixel( rendering::IntegerBitmapLayout& rLayout, 
                                                      const geometry::IntegerPoint2D& pos )
    {
        if( !mpOutDev.get() )
            return uno::Sequence< sal_Int8 >(); // we're disposed

        rLayout = getMemoryLayout();
        rLayout.ScanLines = 1;
        rLayout.ScanLineBytes = 4;
        rLayout.ScanLineStride = rLayout.ScanLineBytes;        

        OutputDevice& rOutDev( mpOutDev->getOutDev() );

        tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDev );
        rOutDev.EnableMapMode( sal_False );
        rOutDev.SetAntialiasing( ANTIALIASING_ENABLE_B2DDRAW );

        const Size aBmpSize( rOutDev.GetOutputSizePixel() );

        ENSURE_ARG_OR_THROW( pos.X >= 0 && pos.X < aBmpSize.Width(), 
                             "X coordinate out of bounds" );
        ENSURE_ARG_OR_THROW( pos.Y >= 0 && pos.Y < aBmpSize.Height(), 
                             "Y coordinate out of bounds" );

        // TODO(F2): Support alpha canvas here
        return ::canvas::tools::colorToStdIntSequence(
            rOutDev.GetPixel( 
                ::vcl::unotools::pointFromIntegerPoint2D( pos ))); 
    }

    rendering::IntegerBitmapLayout CanvasHelper::getMemoryLayout()
    {
        if( !mpOutDev.get() )
            return rendering::IntegerBitmapLayout(); // we're disposed

        return ::canvas::tools::getStdMemoryLayout(getSize());
    }

    int CanvasHelper::setupOutDevState( const rendering::ViewState& 	viewState,
                                        const rendering::RenderState& 	renderState,
                                        ColorType						eColorType ) const
    {
        ENSURE_OR_THROW( mpOutDev.get(), 
                         "outdev null. Are we disposed?" );

        ::canvas::tools::verifyInput( renderState,
                                      BOOST_CURRENT_FUNCTION,
                                      mpDevice,
                                      2,
                                      eColorType == IGNORE_COLOR ? 0 : 3 );

        OutputDevice& rOutDev( mpOutDev->getOutDev() );
        OutputDevice* p2ndOutDev = NULL;

        rOutDev.EnableMapMode( sal_False );
        rOutDev.SetAntialiasing( ANTIALIASING_ENABLE_B2DDRAW );

        if( mp2ndOutDev )
            p2ndOutDev = &mp2ndOutDev->getOutDev();

        int nTransparency(0);

        // TODO(P2): Don't change clipping all the time, maintain current clip
        // state and change only when update is necessary

        // accumulate non-empty clips into one region
        // ==========================================

        Region aClipRegion(true);

        if( viewState.Clip.is() )
        {
            ::basegfx::B2DPolyPolygon aClipPoly( 
                ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(viewState.Clip) );

            if( aClipPoly.count() )
            {
                // setup non-empty clipping
                ::basegfx::B2DHomMatrix aMatrix;
                aClipPoly.transform(
                    ::basegfx::unotools::homMatrixFromAffineMatrix( aMatrix, 
                                                                    viewState.AffineTransform ) );
                
                aClipRegion = Region::GetRegionFromPolyPolygon( ::PolyPolygon( aClipPoly ) );
            }
            else
            {
                // clip polygon is empty
                aClipRegion.SetEmpty();
            }
        }

        if( renderState.Clip.is() )
        {
            ::basegfx::B2DPolyPolygon aClipPoly( 
                ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(renderState.Clip) );

            ::basegfx::B2DHomMatrix aMatrix;
            aClipPoly.transform(
                ::canvas::tools::mergeViewAndRenderTransform( aMatrix, 
                                                              viewState,
                                                              renderState ) );

            if( aClipPoly.count() )
            {
                // setup non-empty clipping
                Region aRegion = Region::GetRegionFromPolyPolygon( ::PolyPolygon( aClipPoly ) );
                aClipRegion.Intersect( aRegion );
            }
            else
            {
                // clip polygon is empty
                aClipRegion.SetEmpty();
            }
        }

        // setup accumulated clip region. Note that setting an
        // empty clip region denotes "clip everything" on the
        // OutputDevice (which is why we translate that into
        // SetClipRegion() here). When both view and render clip
        // are empty, aClipRegion remains default-constructed,
        // i.e. empty, too.
        if( aClipRegion.IsNull() )
        {
            rOutDev.SetClipRegion();
                
            if( p2ndOutDev )
                p2ndOutDev->SetClipRegion();
        }
        else
        {
            rOutDev.SetClipRegion( aClipRegion );
                
            if( p2ndOutDev )
                p2ndOutDev->SetClipRegion( aClipRegion );
        }

        if( eColorType != IGNORE_COLOR )
        {
            Color aColor( COL_WHITE );

            if( renderState.DeviceColor.getLength() > 2 )
            {
                aColor = ::vcl::unotools::stdColorSpaceSequenceToColor(
                    renderState.DeviceColor );
            }

            // extract alpha, and make color opaque
            // afterwards. Otherwise, OutputDevice won't draw anything
            nTransparency = aColor.GetTransparency();
            aColor.SetTransparency(0);

            switch( eColorType )
            {
                case LINE_COLOR:
                    rOutDev.SetLineColor( aColor );
                    rOutDev.SetFillColor();

                    if( p2ndOutDev )
                    {
                        p2ndOutDev->SetLineColor( aColor );
                        p2ndOutDev->SetFillColor();
                    }
                    break;

                case FILL_COLOR:
                    rOutDev.SetFillColor( aColor );
                    rOutDev.SetLineColor();

                    if( p2ndOutDev )
                    {
                        p2ndOutDev->SetFillColor( aColor );
                        p2ndOutDev->SetLineColor();
                    }
                    break;

                case TEXT_COLOR:
                    rOutDev.SetTextColor( aColor );

                    if( p2ndOutDev )
                        p2ndOutDev->SetTextColor( aColor );
                    break;

                default:
                    ENSURE_OR_THROW( false, 
                                     "Unexpected color type");
                    break;
            }
        }

        return nTransparency;
    }

    bool CanvasHelper::setupTextOutput( ::Point&										o_rOutPos,
                                        const rendering::ViewState& 					viewState,
                                        const rendering::RenderState& 					renderState,
                                        const uno::Reference< rendering::XCanvasFont >&	xFont	) const
    {
        ENSURE_OR_THROW( mpOutDev.get(), 
                         "outdev null. Are we disposed?" );

        setupOutDevState( viewState, renderState, TEXT_COLOR );

        OutputDevice& rOutDev( mpOutDev->getOutDev() );

        ::Font aVCLFont;

        CanvasFont* pFont = dynamic_cast< CanvasFont* >( xFont.get() );
        
        ENSURE_ARG_OR_THROW( pFont,
                             "Font not compatible with this canvas" );

        aVCLFont = pFont->getVCLFont();

        Color aColor( COL_BLACK );

        if( renderState.DeviceColor.getLength() > 2 )
        {
            aColor = ::vcl::unotools::stdColorSpaceSequenceToColor(
                renderState.DeviceColor );
        }

        // setup font color
        aVCLFont.SetColor( aColor );
        aVCLFont.SetFillColor( aColor );

        // no need to replicate this for mp2ndOutDev, we're modifying only aVCLFont here.
        if( !tools::setupFontTransform( o_rOutPos, aVCLFont, viewState, renderState, rOutDev ) )
            return false;

        rOutDev.SetFont( aVCLFont );

        if( mp2ndOutDev )
            mp2ndOutDev->getOutDev().SetFont( aVCLFont );

        return true;
    }

    bool CanvasHelper::repaint( const GraphicObjectSharedPtr&	rGrf,
                                const rendering::ViewState&     viewState,
                                const rendering::RenderState&   renderState,
                                const ::Point& 					rPt, 
                                const ::Size& 					rSz,
                                const GraphicAttr&				rAttr ) const
    {
        ENSURE_OR_RETURN_FALSE( rGrf,
                          "Invalid Graphic" );

        if( !mpOutDev )
            return false; // disposed
        else
        {
            tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDev );
            setupOutDevState( viewState, renderState, IGNORE_COLOR );

            if( !rGrf->Draw( &mpOutDev->getOutDev(), rPt, rSz, &rAttr ) )
                return false;

            // #i80779# Redraw also into mask outdev
            if( mp2ndOutDev )
                return rGrf->Draw( &mp2ndOutDev->getOutDev(), rPt, rSz, &rAttr );

            return true;
        }
    }
    
    void CanvasHelper::flush() const
    {
        if( mpOutDev && mpOutDev->getOutDev().GetOutDevType() == OUTDEV_WINDOW )
        {
            // TODO(Q3): Evil downcast. And what's more, Window::Flush is
            // not even const. Wah.
            static_cast<Window&>(mpOutDev->getOutDev()).Flush();
        }

        if( mp2ndOutDev && mp2ndOutDev->getOutDev().GetOutDevType() == OUTDEV_WINDOW )
        {
            // TODO(Q3): Evil downcast. And what's more, Window::Flush is
            // not even const. Wah.
            static_cast<Window&>(mp2ndOutDev->getOutDev()).Flush();
        }
    }

}
