/**************************************************************
 * 
 * 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_cppcanvas.hxx"

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

#include <rtl/logfile.hxx>

#include <com/sun/star/rendering/PathCapType.hpp>
#include <com/sun/star/rendering/PathJoinType.hpp>
#include <com/sun/star/rendering/XCanvas.hpp>
#include <com/sun/star/rendering/XCanvasFont.hpp>

#include <basegfx/numeric/ftools.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/range/b2drectangle.hxx>
#include <basegfx/vector/b2dsize.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>

#include <tools/gen.hxx>
#include <vcl/canvastools.hxx>
#include <vcl/virdev.hxx>

#include <basegfx/tools/canvastools.hxx>
#include <canvas/canvastools.hxx>

#include <boost/scoped_array.hpp>
#include <boost/bind.hpp>
#include <boost/utility.hpp>

#include "textaction.hxx"
#include "outdevstate.hxx"
#include "mtftools.hxx"


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

namespace cppcanvas
{
    namespace internal
    {
        namespace
        {
            void init( rendering::RenderState&					o_rRenderState,
                       const ::basegfx::B2DPoint&				rStartPoint, 
                       const OutDevState& 						rState,
                       const CanvasSharedPtr& 					rCanvas		 )
            {
                tools::initRenderState(o_rRenderState,rState);

                // #i36950# Offset clip back to origin (as it's also moved
                // by rStartPoint)
                // #i53964# Also take VCL font rotation into account,
                // since this, opposed to the FontMatrix rotation
                // elsewhere, _does_ get incorporated into the render
                // state transform.
                tools::modifyClip( o_rRenderState, 
                                   rState, 
                                   rCanvas, 
                                   rStartPoint, 
                                   NULL,
                                   &rState.fontRotation );

                basegfx::B2DHomMatrix aLocalTransformation(basegfx::tools::createRotateB2DHomMatrix(rState.fontRotation));
                aLocalTransformation.translate( rStartPoint.getX(),
                                                rStartPoint.getY() );
                ::canvas::tools::appendToRenderState( o_rRenderState,
                                                      aLocalTransformation );

                o_rRenderState.DeviceColor = rState.textColor;
            }

            void init( rendering::RenderState&					o_rRenderState,
                       const ::basegfx::B2DPoint&				rStartPoint, 
                       const OutDevState& 						rState,
                       const CanvasSharedPtr& 					rCanvas,
                       const ::basegfx::B2DHomMatrix&			rTextTransform	)
            {
                init( o_rRenderState, rStartPoint, rState, rCanvas );

                // TODO(F2): Also inversely-transform clip with
                // rTextTransform (which is actually rather hard, as the
                // text transform is _prepended_ to the render state)!

                // prepend extra font transform to render state
                // (prepend it, because it's interpreted in the unit
                // rect coordinate space)
                ::canvas::tools::prependToRenderState( o_rRenderState,
                                                       rTextTransform );
            }

            void init( rendering::RenderState&						o_rRenderState,
                       uno::Reference< rendering::XCanvasFont >&	o_rFont,
                       const ::basegfx::B2DPoint&					rStartPoint, 
                       const OutDevState& 							rState,
                       const CanvasSharedPtr& 						rCanvas		 )
            {
                // ensure that o_rFont is valid. It is possible that
                // text actions are generated without previously
                // setting a font. Then, just take a default font
                if( !o_rFont.is() )
                {
                    // Use completely default FontRequest
                    const rendering::FontRequest aFontRequest;

                    geometry::Matrix2D aFontMatrix;
                    ::canvas::tools::setIdentityMatrix2D( aFontMatrix );

                    o_rFont = rCanvas->getUNOCanvas()->createFont( 
                        aFontRequest,
                        uno::Sequence< beans::PropertyValue >(),
                        aFontMatrix );
                }

                init( o_rRenderState,
                      rStartPoint, 
                      rState,
                      rCanvas );
            }

            void init( rendering::RenderState&						o_rRenderState,
                       uno::Reference< rendering::XCanvasFont >&	o_rFont,
                       const ::basegfx::B2DPoint&					rStartPoint, 
                       const OutDevState& 							rState,
                       const CanvasSharedPtr& 						rCanvas,
                       const ::basegfx::B2DHomMatrix&				rTextTransform	)
            {
                init( o_rRenderState, o_rFont, rStartPoint, rState, rCanvas );

                // TODO(F2): Also inversely-transform clip with
                // rTextTransform (which is actually rather hard, as the
                // text transform is _prepended_ to the render state)!

                // prepend extra font transform to render state
                // (prepend it, because it's interpreted in the unit
                // rect coordinate space)
                ::canvas::tools::prependToRenderState( o_rRenderState,
                                                       rTextTransform );
            }

            ::basegfx::B2DPolyPolygon textLinesFromLogicalOffsets( const uno::Sequence< double >&	rOffsets,
                                                                   const tools::TextLineInfo&		rTextLineInfo )
            {
                return tools::createTextLinesPolyPolygon( 
                    0.0, 
                    // extract character cell furthest to the right
                    *(::std::max_element( 
                          rOffsets.getConstArray(),
                          rOffsets.getConstArray() + rOffsets.getLength() )), 
                    rTextLineInfo );
            }

            uno::Sequence< double > setupDXArray( const sal_Int32*	 pCharWidths,
                                                  sal_Int32			 nLen,
                                                  const OutDevState& rState )
            {
                // convert character widths from logical units
                uno::Sequence< double > aCharWidthSeq( nLen );
                double*					pOutputWidths( aCharWidthSeq.getArray() );

                // #143885# maintain (nearly) full precision of DX
                // array, by circumventing integer-based
                // OutDev-mapping
                const double nScale( rState.mapModeTransform.get(0,0) );
                for( int i = 0; i < nLen; ++i )
                {
                    // TODO(F2): use correct scale direction
                    *pOutputWidths++ = *pCharWidths++ * nScale;
                }
                
                return aCharWidthSeq;
            }

            uno::Sequence< double > setupDXArray( const ::String& 	 rText,
                                                  sal_Int32			 nStartPos,
                                                  sal_Int32			 nLen,
                                                  VirtualDevice&	 rVDev,
                                                  const OutDevState& rState )
            {
                // no external DX array given, create one from given
                // string
                ::boost::scoped_array< sal_Int32 > pCharWidths( new sal_Int32[nLen] );

                rVDev.GetTextArray( rText, pCharWidths.get(), 
                                    static_cast<sal_uInt16>(nStartPos), 
                                    static_cast<sal_uInt16>(nLen) );

                return setupDXArray( pCharWidths.get(), nLen, rState );
            }

            ::basegfx::B2DPoint adaptStartPoint( const ::basegfx::B2DPoint&		rStartPoint,
                                                 const OutDevState& 			rState, 
                                                 const uno::Sequence< double >& rOffsets )
            {
                ::basegfx::B2DPoint aLocalPoint( rStartPoint );

                if( rState.textAlignment )
                {
                    // text origin is right, not left. Modify start point
                    // accordingly, because XCanvas::drawTextLayout()
                    // always aligns left!

                    const double nOffset( rOffsets[ rOffsets.getLength()-1 ] );

                    // correct start point for rotated text: rotate around
                    // former start point
                    aLocalPoint.setX( aLocalPoint.getX() + cos( rState.fontRotation )*nOffset );
                    aLocalPoint.setY( aLocalPoint.getY() + sin( rState.fontRotation )*nOffset );
                }

                return aLocalPoint;
            }
            
            /** Perform common setup for array text actions

            	This method creates the XTextLayout object and
            	initializes it, e.g. with the logical advancements.
             */
            void initArrayAction( rendering::RenderState&					o_rRenderState,
                                  uno::Reference< rendering::XTextLayout >& o_rTextLayout,	
                                  const ::basegfx::B2DPoint&				rStartPoint,
                                  const ::rtl::OUString&					rText,
                                  sal_Int32 								nStartPos,
                                  sal_Int32 								nLen,
                                  const uno::Sequence< double >&			rOffsets,
                                  const CanvasSharedPtr&					rCanvas,
                                  const OutDevState&						rState,
                                  const ::basegfx::B2DHomMatrix*			pTextTransform )
            {
                ENSURE_OR_THROW( rOffsets.getLength(),
                                  "::cppcanvas::internal::initArrayAction(): zero-length DX array" );

                const ::basegfx::B2DPoint aLocalStartPoint( 
                    adaptStartPoint( rStartPoint, rState, rOffsets ) );

                uno::Reference< rendering::XCanvasFont > xFont( rState.xFont );

                if( pTextTransform )
                    init( o_rRenderState, xFont, aLocalStartPoint, rState, rCanvas, *pTextTransform );
                else
                    init( o_rRenderState, xFont, aLocalStartPoint, rState, rCanvas );

                o_rTextLayout = xFont->createTextLayout( 
                    rendering::StringContext( rText, nStartPos, nLen ), 
                    rState.textDirection, 
                    0 );

                ENSURE_OR_THROW( o_rTextLayout.is(),
                                  "::cppcanvas::internal::initArrayAction(): Invalid font" );

                o_rTextLayout->applyLogicalAdvancements( rOffsets );
            }

            double getLineWidth( ::VirtualDevice&                rVDev, 
                                 const OutDevState&              rState, 
                                 const rendering::StringContext& rStringContext )
            {
                // TODO(F2): use correct scale direction
                const ::basegfx::B2DSize aSize( rVDev.GetTextWidth( rStringContext.Text, 
                                                                    static_cast<sal_uInt16>(rStringContext.StartPosition), 
                                                                    static_cast<sal_uInt16>(rStringContext.Length) ),
                                    0 );

                return (rState.mapModeTransform * aSize).getX();
            }

            uno::Sequence< double > 
            	calcSubsetOffsets( rendering::RenderState&							io_rRenderState,
                                   double&											o_rMinPos,
                                   double&											o_rMaxPos,
                                   const uno::Reference< rendering::XTextLayout >&	rOrigTextLayout,
                                   const ::cppcanvas::internal::Action::Subset&		rSubset )
            {
                ENSURE_OR_THROW( rSubset.mnSubsetEnd > rSubset.mnSubsetBegin,
                                  "::cppcanvas::internal::calcSubsetOffsets(): invalid subset range range" );

                uno::Sequence< double > aOrigOffsets( rOrigTextLayout->queryLogicalAdvancements() );
                const double*			pOffsets( aOrigOffsets.getConstArray() );

                ENSURE_OR_THROW( aOrigOffsets.getLength() >= rSubset.mnSubsetEnd,
                                  "::cppcanvas::internal::calcSubsetOffsets(): invalid subset range range" );

                // TODO(F3): It currently seems that for RTL text, the
                // DX offsets are nevertheless increasing in logical
                // text order (I'd expect they are decreasing,
                // mimicking the fact that the text is output
                // right-to-left). This breaks text effects for ALL
                // RTL languages.

                // determine leftmost position in given subset range -
                // as the DX array contains the output positions
                // starting with the second character (the first is
                // assumed to have output position 0), correct begin
                // iterator.
                const double nMinPos( rSubset.mnSubsetBegin <= 0 ? 0 :
                                      *(::std::min_element( pOffsets+rSubset.mnSubsetBegin-1,
                                                            pOffsets+rSubset.mnSubsetEnd )) );

                // determine rightmost position in given subset range
                // - as the DX array contains the output positions
                // starting with the second character (the first is
                // assumed to have output position 0), correct begin
                // iterator.
                const double nMaxPos( 
                    *(::std::max_element( pOffsets + (rSubset.mnSubsetBegin <= 0 ? 
                                                      0 : rSubset.mnSubsetBegin-1),
                                          pOffsets + rSubset.mnSubsetEnd )) );
                    

                // adapt render state, to move text output to given offset
                // -------------------------------------------------------

                // TODO(F1): Strictly speaking, we also have to adapt
                // the clip here, which normally should _not_ move
                // with the output offset. Neglected for now, as it
                // does not matter for drawing layer output

                if( rSubset.mnSubsetBegin > 0 )
                {
                    ::basegfx::B2DHomMatrix aTranslation;
                    if( rOrigTextLayout->getFont()->getFontRequest().FontDescription.IsVertical )
                    {
                        // vertical text -> offset in y direction
                        aTranslation.translate( 0.0, nMinPos );
                    }
                    else
                    {
                        // horizontal text -> offset in x direction
                        aTranslation.translate( nMinPos, 0.0 );
                    }

                    ::canvas::tools::appendToRenderState( io_rRenderState,
                                                          aTranslation );
                }


                // reduce DX array to given substring
                // ----------------------------------
                
                const sal_Int32			nNewElements( rSubset.mnSubsetEnd - rSubset.mnSubsetBegin );
                uno::Sequence< double > aAdaptedOffsets( nNewElements );
                double*					pAdaptedOffsets( aAdaptedOffsets.getArray() );

                // move to new output position (subtract nMinPos,
                // which is the new '0' position), copy only the range
                // as given by rSubset.
                ::std::transform( pOffsets + rSubset.mnSubsetBegin,
                                  pOffsets + rSubset.mnSubsetEnd,
                                  pAdaptedOffsets,
                                  ::boost::bind( ::std::minus<double>(),
                                                 _1, 
                                                 nMinPos ) );

                o_rMinPos = nMinPos;
                o_rMaxPos = nMaxPos;

                return aAdaptedOffsets;
            }

            uno::Reference< rendering::XTextLayout > 
	            createSubsetLayout( const rendering::StringContext&					rOrigContext,
                                    const ::cppcanvas::internal::Action::Subset&	rSubset,
                                    const uno::Reference< rendering::XTextLayout >&	rOrigTextLayout )
            {
                // create temporary new text layout with subset string
                // ---------------------------------------------------

                const sal_Int32 nNewStartPos( rOrigContext.StartPosition + ::std::min( 
                                                  rSubset.mnSubsetBegin, rOrigContext.Length-1 ) );
                const sal_Int32 nNewLength( ::std::max( 
                                                ::std::min( 
                                                    rSubset.mnSubsetEnd - rSubset.mnSubsetBegin,
                                                    rOrigContext.Length ),
                                                sal_Int32( 0 ) ) );

                const rendering::StringContext aContext( rOrigContext.Text,
                                                         nNewStartPos,
                                                         nNewLength );

                uno::Reference< rendering::XTextLayout > xTextLayout( 
                    rOrigTextLayout->getFont()->createTextLayout( aContext,
                                                                  rOrigTextLayout->getMainTextDirection(),
                                                                  0 ),
                    uno::UNO_QUERY_THROW );

                return xTextLayout;
            }

            /** Setup subset text layout

            	@param io_rTextLayout
                Must contain original (full set) text layout on input,
                will contain subsetted text layout (or empty
                reference, for empty subsets) on output.

                @param io_rRenderState
                Must contain original render state on input, will
                contain shifted render state concatenated with
                rTransformation on output.

                @param rTransformation
                Additional transformation, to be prepended to render
                state

                @param rSubset
                Subset to prepare
             */
            void createSubsetLayout( uno::Reference< rendering::XTextLayout >&	io_rTextLayout,
                                     rendering::RenderState&					io_rRenderState,
                                     double&									o_rMinPos,
                                     double&									o_rMaxPos,
                                     const ::basegfx::B2DHomMatrix&				rTransformation,
                                     const Action::Subset&						rSubset )
            {
                ::canvas::tools::prependToRenderState(io_rRenderState, rTransformation);

                if( rSubset.mnSubsetBegin == rSubset.mnSubsetEnd )
                {
                     // empty range, empty layout
                    io_rTextLayout.clear();

                    return;
                }

                ENSURE_OR_THROW( io_rTextLayout.is(),
                                  "createSubsetLayout(): Invalid input layout" );

                const rendering::StringContext& rOrigContext( io_rTextLayout->getText() );

                if( rSubset.mnSubsetBegin == 0 &&
                    rSubset.mnSubsetEnd == rOrigContext.Length )
                {
                    // full range, no need for subsetting
                    return;
                }

                uno::Reference< rendering::XTextLayout > xTextLayout(
                    createSubsetLayout( rOrigContext, rSubset, io_rTextLayout ) );

                if( xTextLayout.is() )
                {
                    xTextLayout->applyLogicalAdvancements( 
                        calcSubsetOffsets( io_rRenderState,
                                           o_rMinPos,
                                           o_rMaxPos,
                                           io_rTextLayout,
                                           rSubset ) );
                }

                io_rTextLayout = xTextLayout;
            }


            /** Interface for renderEffectText functor below.

            	This is interface is used from the renderEffectText()
            	method below, to call the client implementation.
             */
            class TextRenderer
            {
            public:
                virtual ~TextRenderer() {}

                /// Render text with given RenderState
                virtual bool operator()( const rendering::RenderState& rRenderState ) const = 0;
            };

            /** Render effect text.

            	@param rRenderer
                Functor object, will be called to render the actual
                part of the text effect (the text itself and the means
                to render it are unknown to this method)
             */
            bool renderEffectText( const TextRenderer& 							rRenderer,
                                   const rendering::RenderState&				rRenderState,
                                   const rendering::ViewState&			 		/*rViewState*/,
                                   const uno::Reference< rendering::XCanvas >&	xCanvas,
                                   const ::Color&								rShadowColor,
                                   const ::basegfx::B2DSize&					rShadowOffset,
                                   const ::Color&								rReliefColor,
                                   const ::basegfx::B2DSize&					rReliefOffset )
            {
                ::Color aEmptyColor( COL_AUTO );
                uno::Reference<rendering::XColorSpace> xColorSpace(
                    xCanvas->getDevice()->getDeviceColorSpace() );

                // draw shadow text, if enabled
                if( rShadowColor != aEmptyColor )
                {
                    rendering::RenderState aShadowState( rRenderState );
                    ::basegfx::B2DHomMatrix aTranslate;

                    aTranslate.translate( rShadowOffset.getX(),
                                          rShadowOffset.getY() );

                    ::canvas::tools::appendToRenderState(aShadowState, aTranslate);

                    aShadowState.DeviceColor = 
                        ::vcl::unotools::colorToDoubleSequence( rShadowColor,
                                                                xColorSpace );

                    rRenderer( aShadowState );
                }

                // draw relief text, if enabled
                if( rReliefColor != aEmptyColor )
                {
                    rendering::RenderState aReliefState( rRenderState );
                    ::basegfx::B2DHomMatrix aTranslate;

                    aTranslate.translate( rReliefOffset.getX(),
                                          rReliefOffset.getY() );

                    ::canvas::tools::appendToRenderState(aReliefState, aTranslate);

                    aReliefState.DeviceColor = 
                        ::vcl::unotools::colorToDoubleSequence( rReliefColor,
                                                                xColorSpace );

                    rRenderer( aReliefState );
                }

                // draw normal text
                rRenderer( rRenderState );

                return true;
            }


            ::basegfx::B2DRange calcEffectTextBounds( const ::basegfx::B2DRange& 	rTextBounds,
                                                      const ::basegfx::B2DRange& 	rLineBounds,
                                                      const ::basegfx::B2DSize&		rReliefOffset,
                                                      const ::basegfx::B2DSize&		rShadowOffset,
                                                      const rendering::RenderState&	rRenderState,
                                                      const rendering::ViewState&   rViewState )
            {
                ::basegfx::B2DRange aBounds( rTextBounds );

                // add extends of text lines
                aBounds.expand( rLineBounds );
                
                // TODO(Q3): Provide this functionality at the B2DRange
                ::basegfx::B2DRange aTotalBounds( aBounds );
                aTotalBounds.expand( 
                    ::basegfx::B2DRange( aBounds.getMinX() + rReliefOffset.getX(),
                                         aBounds.getMinY() + rReliefOffset.getY(),
                                         aBounds.getMaxX() + rReliefOffset.getX(),
                                         aBounds.getMaxY() + rReliefOffset.getY() ) );
                aTotalBounds.expand( 
                    ::basegfx::B2DRange( aBounds.getMinX() + rShadowOffset.getX(),
                                         aBounds.getMinY() + rShadowOffset.getY(),
                                         aBounds.getMaxX() + rShadowOffset.getX(),
                                         aBounds.getMaxY() + rShadowOffset.getY() ) );

                return tools::calcDevicePixelBounds( aTotalBounds,
                                                     rViewState,
                                                     rRenderState );
            }

            void initEffectLinePolyPolygon( ::basegfx::B2DSize& 							o_rOverallSize,
                                            uno::Reference< rendering::XPolyPolygon2D >&	o_rTextLines,
                                            const CanvasSharedPtr&							rCanvas,
                                            const uno::Sequence< double >&					rOffsets,
                                            const tools::TextLineInfo						rLineInfo	)
            {
                const ::basegfx::B2DPolyPolygon aPoly( 
                    textLinesFromLogicalOffsets(
                        rOffsets,
                        rLineInfo ) );

                o_rOverallSize = ::basegfx::tools::getRange( aPoly ).getRange();

                o_rTextLines = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( 
                    rCanvas->getUNOCanvas()->getDevice(),
                    aPoly );
            }

            void initEffectLinePolyPolygon( ::basegfx::B2DSize& 							o_rOverallSize,
                                            uno::Reference< rendering::XPolyPolygon2D >&	o_rTextLines,
                                            const CanvasSharedPtr&							rCanvas,
                                            double                                          nLineWidth,
                                            const tools::TextLineInfo						rLineInfo	)
            {
                const ::basegfx::B2DPolyPolygon aPoly( 
                    tools::createTextLinesPolyPolygon( 0.0, nLineWidth, 
                                                       rLineInfo ) );

                o_rOverallSize = ::basegfx::tools::getRange( aPoly ).getRange();

                o_rTextLines = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( 
                    rCanvas->getUNOCanvas()->getDevice(),
                    aPoly );
            }


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

            class TextAction : public Action, private ::boost::noncopyable
            { 
            public: 
                TextAction( const ::basegfx::B2DPoint& 	rStartPoint,
                            const ::rtl::OUString&		rString,
                            sal_Int32 					nStartPos,
                            sal_Int32 					nLen,
                            const CanvasSharedPtr&		rCanvas,
                            const OutDevState&			rState );

                TextAction( const ::basegfx::B2DPoint& 		rStartPoint,
                            const ::rtl::OUString&			rString,
                            sal_Int32 						nStartPos,
                            sal_Int32 						nLen,
                            const CanvasSharedPtr&			rCanvas,
                            const OutDevState&				rState,
                            const ::basegfx::B2DHomMatrix&	rTextTransform );

                virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation ) const;
                virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation,
                                     const Subset&					rSubset ) const;

                virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const;
                virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix&	rTransformation,
                                                       const Subset&					rSubset ) const;

                virtual sal_Int32 getActionCount() const;

            private:
                // TODO(P2): This is potentially a real mass object
                // (every character might be a separate TextAction),
                // thus, make it as lightweight as possible. For
                // example, share common RenderState among several
                // TextActions, maybe using maOffsets for the
                // translation.

                uno::Reference< rendering::XCanvasFont >	mxFont;
                const rendering::StringContext			  	maStringContext;
                const CanvasSharedPtr						mpCanvas;
                rendering::RenderState						maState;
                const sal_Int8								maTextDirection;
            };

            TextAction::TextAction( const ::basegfx::B2DPoint& 	rStartPoint,
                                    const ::rtl::OUString&		rString,
                                    sal_Int32 					nStartPos,
                                    sal_Int32 					nLen,
                                    const CanvasSharedPtr&		rCanvas,
                                    const OutDevState&			rState	) :
                mxFont( rState.xFont ),
                maStringContext( rString, nStartPos, nLen ),
                mpCanvas( rCanvas ),
                maState(),
                maTextDirection( rState.textDirection )
            {
                init( maState, mxFont, 
                      rStartPoint, 
                      rState, rCanvas );

                ENSURE_OR_THROW( mxFont.is(),
                                  "::cppcanvas::internal::TextAction(): Invalid font" );
            }

            TextAction::TextAction( const ::basegfx::B2DPoint& 		rStartPoint,
                                    const ::rtl::OUString&			rString,
                                    sal_Int32 						nStartPos,
                                    sal_Int32 						nLen,
                                    const CanvasSharedPtr&			rCanvas,
                                    const OutDevState&				rState,
                                    const ::basegfx::B2DHomMatrix&	rTextTransform ) :
                mxFont( rState.xFont ),
                maStringContext( rString, nStartPos, nLen ),
                mpCanvas( rCanvas ),
                maState(),
                maTextDirection( rState.textDirection )
            {
                init( maState, mxFont, 
                      rStartPoint, 
                      rState, rCanvas, rTextTransform );

                ENSURE_OR_THROW( mxFont.is(),
                                  "::cppcanvas::internal::TextAction(): Invalid font" );
            }

            bool TextAction::render( const ::basegfx::B2DHomMatrix& rTransformation ) const
            {
                RTL_LOGFILE_CONTEXT( aLog, "::cppcanvas::internal::TextAction::render()" );
                RTL_LOGFILE_CONTEXT_TRACE1( aLog, "::cppcanvas::internal::TextAction: 0x%X", this );

                rendering::RenderState aLocalState( maState );
                ::canvas::tools::prependToRenderState(aLocalState, rTransformation);

                mpCanvas->getUNOCanvas()->drawText( maStringContext, mxFont,
                                                    mpCanvas->getViewState(), aLocalState, maTextDirection );

                return true;
            }

            bool TextAction::render( const ::basegfx::B2DHomMatrix&	rTransformation,
                                     const Subset&					/*rSubset*/ ) const
            {
                OSL_ENSURE( false,
                            "TextAction::render(): Subset not supported by this object" );

                // TODO(P1): Retrieve necessary font metric info for
                // TextAction from XCanvas. Currently, the
                // TextActionFactory does not generate this object for
                // _subsettable_ text
                return render( rTransformation );
            }

            ::basegfx::B2DRange TextAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const
            {
                // create XTextLayout, to have the
                // XTextLayout::queryTextBounds() method available
                uno::Reference< rendering::XTextLayout > xTextLayout(
                    mxFont->createTextLayout( 
                        maStringContext,
                        maTextDirection, 
                        0 ) );

                rendering::RenderState aLocalState( maState );
                ::canvas::tools::prependToRenderState(aLocalState, rTransformation);

                return tools::calcDevicePixelBounds( ::basegfx::unotools::b2DRectangleFromRealRectangle2D(
                                                         xTextLayout->queryTextBounds() ),
                                                     mpCanvas->getViewState(),
                                                     aLocalState );
            }

            ::basegfx::B2DRange TextAction::getBounds( const ::basegfx::B2DHomMatrix&	rTransformation,
                                                       const Subset&					/*rSubset*/ ) const
            {
                OSL_ENSURE( false,
                            "TextAction::getBounds(): Subset not supported by this object" );

                // TODO(P1): Retrieve necessary font metric info for
                // TextAction from XCanvas. Currently, the
                // TextActionFactory does not generate this object for
                // _subsettable_ text
                return getBounds( rTransformation );
            }

            sal_Int32 TextAction::getActionCount() const
            {
                // TODO(P1): Retrieve necessary font metric info for
                // TextAction from XCanvas. Currently, the
                // TextActionFactory does not generate this object for
                // _subsettable_ text
                return 1;
            }


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

            class EffectTextAction : 
                public Action, 
                public TextRenderer,
                private ::boost::noncopyable
            { 
            public: 
                EffectTextAction( const ::basegfx::B2DPoint& rStartPoint,  
                                  const ::basegfx::B2DSize&	 rReliefOffset,  
                                  const ::Color&			 rReliefColor,
                                  const ::basegfx::B2DSize&	 rShadowOffset,
                                  const ::Color&			 rShadowColor,
                                  const ::rtl::OUString& 	 rText,
                                  sal_Int32 				 nStartPos,
                                  sal_Int32 				 nLen,
                                  VirtualDevice&			 rVDev,
                                  const CanvasSharedPtr&	 rCanvas, 
                                  const OutDevState& 		 rState ); 

                EffectTextAction( const ::basegfx::B2DPoint&        rStartPoint,  
                                  const ::basegfx::B2DSize&			rReliefOffset,  
                                  const ::Color&					rReliefColor,
                                  const ::basegfx::B2DSize&			rShadowOffset,
                                  const ::Color&					rShadowColor,
                                  const ::rtl::OUString& 			rText,
                                  sal_Int32 						nStartPos,
                                  sal_Int32 						nLen,
                                  VirtualDevice&					rVDev,
                                  const CanvasSharedPtr&			rCanvas, 
                                  const OutDevState& 				rState,
                                  const ::basegfx::B2DHomMatrix&	rTextTransform ); 

                virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation ) const;
                virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation,
                                     const Subset&					rSubset ) const;

                virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const;
                virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix&	rTransformation,
                                                       const Subset&					rSubset ) const;

                virtual sal_Int32 getActionCount() const;

            private:
                /// Interface TextRenderer 
                virtual bool operator()( const rendering::RenderState& rRenderState ) const;

                // TODO(P2): This is potentially a real mass object
                // (every character might be a separate TextAction),
                // thus, make it as lightweight as possible. For
                // example, share common RenderState among several
                // TextActions, maybe using maOffsets for the
                // translation.

                uno::Reference< rendering::XCanvasFont >	mxFont;
                const rendering::StringContext			  	maStringContext;
                const CanvasSharedPtr						mpCanvas;
                rendering::RenderState						maState;
                const tools::TextLineInfo					maTextLineInfo;
                ::basegfx::B2DSize							maLinesOverallSize;
                const double								mnLineWidth;
                uno::Reference< rendering::XPolyPolygon2D >	mxTextLines;
                const ::basegfx::B2DSize					maReliefOffset;
                const ::Color								maReliefColor;
                const ::basegfx::B2DSize					maShadowOffset;
                const ::Color								maShadowColor;
                const sal_Int8								maTextDirection;
            };

            EffectTextAction::EffectTextAction( const ::basegfx::B2DPoint& rStartPoint,  
                                                const ::basegfx::B2DSize&  rReliefOffset,  
                                                const ::Color&             rReliefColor,
                                                const ::basegfx::B2DSize&  rShadowOffset,
                                                const ::Color&             rShadowColor,
                                                const ::rtl::OUString&     rText,
                                                sal_Int32                  nStartPos,
                                                sal_Int32                  nLen,
                                                VirtualDevice&             rVDev,
                                                const CanvasSharedPtr&     rCanvas, 
                                                const OutDevState&         rState ) :
                mxFont( rState.xFont ),
                maStringContext( rText, nStartPos, nLen ),
                mpCanvas( rCanvas ),
                maState(),
                maTextLineInfo( tools::createTextLineInfo( rVDev, rState ) ),
                maLinesOverallSize(),
                mnLineWidth( getLineWidth( rVDev, rState, maStringContext ) ),
                mxTextLines(),
                maReliefOffset( rReliefOffset ),
                maReliefColor( rReliefColor ),
                maShadowOffset( rShadowOffset ),
                maShadowColor( rShadowColor ),
                maTextDirection( rState.textDirection )
            {
                initEffectLinePolyPolygon( maLinesOverallSize,
                                           mxTextLines,
                                           rCanvas,
                                           mnLineWidth,
                                           maTextLineInfo );

                init( maState, mxFont, 
                      rStartPoint, 
                      rState, rCanvas );

                ENSURE_OR_THROW( mxFont.is() && mxTextLines.is(),
                                  "::cppcanvas::internal::EffectTextAction(): Invalid font or lines" );
            }

            EffectTextAction::EffectTextAction( const ::basegfx::B2DPoint&		rStartPoint,  
                                                const ::basegfx::B2DSize&		rReliefOffset,  
                                                const ::Color&					rReliefColor,
                                                const ::basegfx::B2DSize&		rShadowOffset,
                                                const ::Color&					rShadowColor,
                                                const ::rtl::OUString& 			rText,
                                                sal_Int32 						nStartPos,
                                                sal_Int32 						nLen,
                                                VirtualDevice&					rVDev,
                                                const CanvasSharedPtr&			rCanvas, 
                                                const OutDevState& 				rState,
                                                const ::basegfx::B2DHomMatrix&	rTextTransform ) :
                mxFont( rState.xFont ),
                maStringContext( rText, nStartPos, nLen ),
                mpCanvas( rCanvas ),
                maState(),
                maTextLineInfo( tools::createTextLineInfo( rVDev, rState ) ),
                maLinesOverallSize(),
                mnLineWidth( getLineWidth( rVDev, rState, maStringContext ) ),
                mxTextLines(),
                maReliefOffset( rReliefOffset ),
                maReliefColor( rReliefColor ),
                maShadowOffset( rShadowOffset ),
                maShadowColor( rShadowColor ),
                maTextDirection( rState.textDirection )
            {
                initEffectLinePolyPolygon( maLinesOverallSize,
                                           mxTextLines,
                                           rCanvas,
                                           mnLineWidth,
                                           maTextLineInfo );

                init( maState, mxFont, 
                      rStartPoint, 
                      rState, rCanvas, rTextTransform );

                ENSURE_OR_THROW( mxFont.is() && mxTextLines.is(),
                                  "::cppcanvas::internal::EffectTextAction(): Invalid font or lines" );
            }

            bool EffectTextAction::operator()( const rendering::RenderState& rRenderState ) const
            {
                const rendering::ViewState& rViewState( mpCanvas->getViewState() );
                const uno::Reference< rendering::XCanvas >& rCanvas( mpCanvas->getUNOCanvas() );

                rCanvas->fillPolyPolygon( mxTextLines,
                                          rViewState,
                                          rRenderState );

                rCanvas->drawText( maStringContext, mxFont,
                                   rViewState,
                                   rRenderState, 
                                   maTextDirection );

                return true;
            }

            bool EffectTextAction::render( const ::basegfx::B2DHomMatrix& rTransformation ) const
            {
                RTL_LOGFILE_CONTEXT( aLog, "::cppcanvas::internal::EffectTextAction::render()" );
                RTL_LOGFILE_CONTEXT_TRACE1( aLog, "::cppcanvas::internal::EffectTextAction: 0x%X", this );

                rendering::RenderState aLocalState( maState );
                ::canvas::tools::prependToRenderState(aLocalState, rTransformation);

                return renderEffectText( *this,
                                         aLocalState,
                                         mpCanvas->getViewState(),
                                         mpCanvas->getUNOCanvas(),
                                         maShadowColor,
                                         maShadowOffset,
                                         maReliefColor,
                                         maReliefOffset );
            }

            bool EffectTextAction::render( const ::basegfx::B2DHomMatrix&	rTransformation,
                                           const Subset&					/*rSubset*/ ) const
            {
                OSL_ENSURE( false,
                            "EffectTextAction::render(): Subset not supported by this object" );

                // TODO(P1): Retrieve necessary font metric info for
                // TextAction from XCanvas. Currently, the
                // TextActionFactory does not generate this object for
                // subsettable text
                return render( rTransformation );
            }

            ::basegfx::B2DRange EffectTextAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const
            {
                // create XTextLayout, to have the
                // XTextLayout::queryTextBounds() method available
                uno::Reference< rendering::XTextLayout > xTextLayout(
                    mxFont->createTextLayout( 
                        maStringContext,
                        maTextDirection, 
                        0 ) );

                rendering::RenderState aLocalState( maState );
                ::canvas::tools::prependToRenderState(aLocalState, rTransformation);

                return calcEffectTextBounds( ::basegfx::unotools::b2DRectangleFromRealRectangle2D(
                                                 xTextLayout->queryTextBounds() ),
                                             ::basegfx::B2DRange( 0,0,
                                                                  maLinesOverallSize.getX(),
                                                                  maLinesOverallSize.getY() ),
                                             maReliefOffset,
                                             maShadowOffset,
                                             aLocalState,
                                             mpCanvas->getViewState() );
            }

            ::basegfx::B2DRange EffectTextAction::getBounds( const ::basegfx::B2DHomMatrix&	rTransformation,
                                                             const Subset&					/*rSubset*/ ) const
            {
                OSL_ENSURE( false,
                            "EffectTextAction::getBounds(): Subset not supported by this object" );

                // TODO(P1): Retrieve necessary font metric info for
                // TextAction from XCanvas. Currently, the
                // TextActionFactory does not generate this object for
                // _subsettable_ text
                return getBounds( rTransformation );
            }

            sal_Int32 EffectTextAction::getActionCount() const
            {
                // TODO(P1): Retrieve necessary font metric info for
                // TextAction from XCanvas. Currently, the
                // TextActionFactory does not generate this object for
                // subsettable text
                return 1;
            }


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

            class TextArrayAction : public Action, private ::boost::noncopyable
            { 
            public: 
                TextArrayAction( const ::basegfx::B2DPoint& 	rStartPoint,
                                 const ::rtl::OUString&			rString,
                                 sal_Int32 						nStartPos,
                                 sal_Int32 						nLen,
                                 const uno::Sequence< double >&	rOffsets,
                                 const CanvasSharedPtr&			rCanvas,
                                 const OutDevState&				rState );

                TextArrayAction( const ::basegfx::B2DPoint& 	rStartPoint,
                                 const ::rtl::OUString&			rString,
                                 sal_Int32 						nStartPos,
                                 sal_Int32 						nLen,
                                 const uno::Sequence< double >&	rOffsets,
                                 const CanvasSharedPtr&			rCanvas,
                                 const OutDevState&				rState,
                                 const ::basegfx::B2DHomMatrix&	rTextTransform );

                virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation ) const;
                virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation,
                                     const Subset&					rSubset ) const;

                virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const;
                virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix&	rTransformation,
                                                       const Subset&					rSubset ) const;

                virtual sal_Int32 getActionCount() const;

            private:
                // TODO(P2): This is potentially a real mass object
                // (every character might be a separate TextAction),
                // thus, make it as lightweight as possible. For
                // example, share common RenderState among several
                // TextActions, maybe using maOffsets for the
                // translation.

                uno::Reference< rendering::XTextLayout >	mxTextLayout;
                const CanvasSharedPtr						mpCanvas;
                rendering::RenderState						maState;
            };

            TextArrayAction::TextArrayAction( const ::basegfx::B2DPoint& 		rStartPoint,
                                              const ::rtl::OUString&			rString,
                                              sal_Int32 						nStartPos,
                                              sal_Int32 						nLen,
                                              const uno::Sequence< double >&	rOffsets,
                                              const CanvasSharedPtr&			rCanvas,
                                              const OutDevState&				rState ) :
                mxTextLayout(),
                mpCanvas( rCanvas ),
                maState()
            {
                initArrayAction( maState, 
                                 mxTextLayout,
                                 rStartPoint,
                                 rString,
                                 nStartPos,
                                 nLen,
                                 rOffsets,
                                 rCanvas,
                                 rState, NULL );
            }

            TextArrayAction::TextArrayAction( const ::basegfx::B2DPoint& 		rStartPoint,
                                              const ::rtl::OUString&			rString,
                                              sal_Int32 						nStartPos,
                                              sal_Int32 						nLen,
                                              const uno::Sequence< double >&	rOffsets,
                                              const CanvasSharedPtr&			rCanvas,
                                              const OutDevState&				rState,
                                              const ::basegfx::B2DHomMatrix&	rTextTransform ) :
                mxTextLayout(),
                mpCanvas( rCanvas ),
                maState()
            {
                initArrayAction( maState, 
                                 mxTextLayout,
                                 rStartPoint,
                                 rString,
                                 nStartPos,
                                 nLen,
                                 rOffsets,
                                 rCanvas,
                                 rState,
                                 &rTextTransform );
            }

            bool TextArrayAction::render( const ::basegfx::B2DHomMatrix& rTransformation ) const
            {
                RTL_LOGFILE_CONTEXT( aLog, "::cppcanvas::internal::TextArrayAction::render()" );
                RTL_LOGFILE_CONTEXT_TRACE1( aLog, "::cppcanvas::internal::TextArrayAction: 0x%X", this );

                rendering::RenderState aLocalState( maState );
                ::canvas::tools::prependToRenderState(aLocalState, rTransformation);

#ifdef SPECIAL_DEBUG
                aLocalState.Clip.clear();
                aLocalState.DeviceColor = 
                    ::vcl::unotools::colorToDoubleSequence( mpCanvas->getUNOCanvas()->getDevice(),
                                                            ::Color( 0x80FF0000 ) );

                if( maState.Clip.is() )
                    mpCanvas->getUNOCanvas()->drawPolyPolygon( maState.Clip, 
                                                               mpCanvas->getViewState(), 
                                                               aLocalState );

                aLocalState.DeviceColor = maState.DeviceColor;
#endif

                mpCanvas->getUNOCanvas()->drawTextLayout( mxTextLayout, 
                                                          mpCanvas->getViewState(), 
                                                          aLocalState );

                return true;
            }

            bool TextArrayAction::render( const ::basegfx::B2DHomMatrix&	rTransformation,
                                          const Subset&						rSubset ) const
            {
                RTL_LOGFILE_CONTEXT( aLog, "::cppcanvas::internal::TextArrayAction::render( subset )" );
                RTL_LOGFILE_CONTEXT_TRACE1( aLog, "::cppcanvas::internal::TextArrayAction: 0x%X", this );

                rendering::RenderState 						aLocalState( maState );
                uno::Reference< rendering::XTextLayout >	xTextLayout( mxTextLayout );

                double nDummy0, nDummy1;
                createSubsetLayout( xTextLayout,
                                    aLocalState,
                                    nDummy0, 
                                    nDummy1,
                                    rTransformation,
                                    rSubset );

                if( !xTextLayout.is() )
                    return true; // empty layout, render nothing

                mpCanvas->getUNOCanvas()->drawTextLayout( xTextLayout, 
                                                          mpCanvas->getViewState(), 
                                                          aLocalState );

                return true;
            }

            ::basegfx::B2DRange TextArrayAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const
            {
                rendering::RenderState aLocalState( maState );
                ::canvas::tools::prependToRenderState(aLocalState, rTransformation);

                return tools::calcDevicePixelBounds( ::basegfx::unotools::b2DRectangleFromRealRectangle2D(
                                                         mxTextLayout->queryTextBounds() ),
                                                     mpCanvas->getViewState(),
                                                     aLocalState );
            }

            ::basegfx::B2DRange TextArrayAction::getBounds( const ::basegfx::B2DHomMatrix&	rTransformation,
                                                            const Subset&					rSubset ) const
            {
                RTL_LOGFILE_CONTEXT( aLog, "::cppcanvas::internal::TextArrayAction::getBounds( subset )" );
                RTL_LOGFILE_CONTEXT_TRACE1( aLog, "::cppcanvas::internal::TextArrayAction: 0x%X", this );

                rendering::RenderState 						aLocalState( maState );
                uno::Reference< rendering::XTextLayout >	xTextLayout( mxTextLayout );

                double nDummy0, nDummy1;
                createSubsetLayout( xTextLayout,
                                    aLocalState,
                                    nDummy0, 
                                    nDummy1,
                                    rTransformation,
                                    rSubset );

                if( !xTextLayout.is() )
                    return ::basegfx::B2DRange(); // empty layout, empty bounds

                return tools::calcDevicePixelBounds( ::basegfx::unotools::b2DRectangleFromRealRectangle2D(
                                                         xTextLayout->queryTextBounds() ),
                                                     mpCanvas->getViewState(),
                                                     aLocalState );
            }

            sal_Int32 TextArrayAction::getActionCount() const
            {
                const rendering::StringContext& rOrigContext( mxTextLayout->getText() );

                return rOrigContext.Length;
            }


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

            class EffectTextArrayAction : 
                public Action, 
                public TextRenderer,
                private ::boost::noncopyable
            { 
            public: 
                EffectTextArrayAction( const ::basegfx::B2DPoint&		rStartPoint,  
                                       const ::basegfx::B2DSize&		rReliefOffset,  
                                       const ::Color&					rReliefColor,
                                       const ::basegfx::B2DSize&		rShadowOffset,
                                       const ::Color&					rShadowColor,
                                       const ::rtl::OUString& 			rText,
                                       sal_Int32 						nStartPos,
                                       sal_Int32 						nLen,
                                       const uno::Sequence< double >&	rOffsets,
                                       VirtualDevice&					rVDev,
                                       const CanvasSharedPtr&			rCanvas, 
                                       const OutDevState& 				rState	);
                EffectTextArrayAction( const ::basegfx::B2DPoint&		rStartPoint,  
                                       const ::basegfx::B2DSize&		rReliefOffset,  
                                       const ::Color&					rReliefColor,
                                       const ::basegfx::B2DSize&		rShadowOffset,
                                       const ::Color&					rShadowColor,
                                       const ::rtl::OUString& 			rText,
                                       sal_Int32 						nStartPos,
                                       sal_Int32 						nLen,
                                       const uno::Sequence< double >&	rOffsets,
                                       VirtualDevice&					rVDev,
                                       const CanvasSharedPtr&			rCanvas, 
                                       const OutDevState& 				rState,
                                       const ::basegfx::B2DHomMatrix&	rTextTransform ); 

                virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation ) const;
                virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation,
                                     const Subset&					rSubset ) const;

                virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const;
                virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix&	rTransformation,
                                                       const Subset&					rSubset ) const;

                virtual sal_Int32 getActionCount() const;

            private:
                // TextRenderer interface
                virtual bool operator()( const rendering::RenderState& rRenderState ) const;
                    
                // TODO(P2): This is potentially a real mass object
                // (every character might be a separate TextAction),
                // thus, make it as lightweight as possible. For
                // example, share common RenderState among several
                // TextActions, maybe using maOffsets for the
                // translation.

                uno::Reference< rendering::XTextLayout >		mxTextLayout;
                const CanvasSharedPtr							mpCanvas;
                rendering::RenderState							maState;
                const tools::TextLineInfo						maTextLineInfo;
                ::basegfx::B2DSize								maLinesOverallSize;
                uno::Reference< rendering::XPolyPolygon2D >		mxTextLines;
                const ::basegfx::B2DSize						maReliefOffset;
                const ::Color									maReliefColor;
                const ::basegfx::B2DSize						maShadowOffset;
                const ::Color									maShadowColor;
            };

            EffectTextArrayAction::EffectTextArrayAction( const ::basegfx::B2DPoint&		rStartPoint,  
                                                          const ::basegfx::B2DSize&			rReliefOffset,  
                                                          const ::Color&					rReliefColor,
                                                          const ::basegfx::B2DSize&			rShadowOffset,
                                                          const ::Color&					rShadowColor,
                                                          const ::rtl::OUString& 			rText,
                                                          sal_Int32 						nStartPos,
                                                          sal_Int32 						nLen,
                                                          const uno::Sequence< double >&	rOffsets,
                                                          VirtualDevice&					rVDev,
                                                          const CanvasSharedPtr&			rCanvas, 
                                                          const OutDevState& 				rState	) :
                mxTextLayout(),
                mpCanvas( rCanvas ),
                maState(),
                maTextLineInfo( tools::createTextLineInfo( rVDev, rState ) ),
                maLinesOverallSize(),
                mxTextLines(),
                maReliefOffset( rReliefOffset ),
                maReliefColor( rReliefColor ),
                maShadowOffset( rShadowOffset ),
                maShadowColor( rShadowColor )
            {
                initEffectLinePolyPolygon( maLinesOverallSize,
                                           mxTextLines,
                                           rCanvas,
                                           rOffsets,
                                           maTextLineInfo );

                initArrayAction( maState, 
                                 mxTextLayout,
                                 rStartPoint,
                                 rText,
                                 nStartPos,
                                 nLen,
                                 rOffsets,
                                 rCanvas,
                                 rState, NULL );
            }

            EffectTextArrayAction::EffectTextArrayAction( const ::basegfx::B2DPoint&		rStartPoint,  
                                                          const ::basegfx::B2DSize&			rReliefOffset,  
                                                          const ::Color&					rReliefColor,
                                                          const ::basegfx::B2DSize&			rShadowOffset,
                                                          const ::Color&					rShadowColor,
                                                          const ::rtl::OUString& 			rText,
                                                          sal_Int32 						nStartPos,
                                                          sal_Int32 						nLen,
                                                          const uno::Sequence< double >&	rOffsets,
                                                          VirtualDevice&					rVDev,
                                                          const CanvasSharedPtr&			rCanvas, 
                                                          const OutDevState& 				rState,
                                                          const ::basegfx::B2DHomMatrix&	rTextTransform ) :
                mxTextLayout(),
                mpCanvas( rCanvas ),
                maState(),
                maTextLineInfo( tools::createTextLineInfo( rVDev, rState ) ),
                maLinesOverallSize(),
                mxTextLines(),
                maReliefOffset( rReliefOffset ),
                maReliefColor( rReliefColor ),
                maShadowOffset( rShadowOffset ),
                maShadowColor( rShadowColor )
            {
                initEffectLinePolyPolygon( maLinesOverallSize,
                                           mxTextLines,
                                           rCanvas,
                                           rOffsets,
                                           maTextLineInfo );

                initArrayAction( maState, 
                                 mxTextLayout,
                                 rStartPoint,
                                 rText,
                                 nStartPos,
                                 nLen,
                                 rOffsets,
                                 rCanvas,
                                 rState, 
                                 &rTextTransform );
            }

            bool EffectTextArrayAction::operator()( const rendering::RenderState& rRenderState ) const
            {
                const rendering::ViewState& rViewState( mpCanvas->getViewState() );
                const uno::Reference< rendering::XCanvas >& rCanvas( mpCanvas->getUNOCanvas() );

                rCanvas->fillPolyPolygon( mxTextLines,
                                          rViewState,
                                          rRenderState );

                rCanvas->drawTextLayout( mxTextLayout, 
                                         rViewState,
                                         rRenderState );

                return true;
            }

            bool EffectTextArrayAction::render( const ::basegfx::B2DHomMatrix& rTransformation ) const
            {
                RTL_LOGFILE_CONTEXT( aLog, "::cppcanvas::internal::EffectTextArrayAction::render()" );
                RTL_LOGFILE_CONTEXT_TRACE1( aLog, "::cppcanvas::internal::EffectTextArrayAction: 0x%X", this );

                rendering::RenderState aLocalState( maState );
                ::canvas::tools::prependToRenderState(aLocalState, rTransformation);

                return renderEffectText( *this,
                                         aLocalState,
                                         mpCanvas->getViewState(),
                                         mpCanvas->getUNOCanvas(),
                                         maShadowColor,
                                         maShadowOffset,
                                         maReliefColor,
                                         maReliefOffset );
            }

            class EffectTextArrayRenderHelper : public TextRenderer
            {
            public:
                EffectTextArrayRenderHelper( const uno::Reference< rendering::XCanvas >&		rCanvas,
                                             const uno::Reference< rendering::XTextLayout >& 	rTextLayout,
                                             const uno::Reference< rendering::XPolyPolygon2D >& rLinePolygon,
                                             const rendering::ViewState&			 			rViewState ) :
                    mrCanvas( rCanvas ),
                    mrTextLayout( rTextLayout ),
                    mrLinePolygon( rLinePolygon ),
                    mrViewState( rViewState )
                {
                }

                // TextRenderer interface
                virtual bool operator()( const rendering::RenderState& rRenderState ) const
                {
                    mrCanvas->fillPolyPolygon( mrLinePolygon,
                                               mrViewState,
                                               rRenderState );
                    
                    mrCanvas->drawTextLayout( mrTextLayout, 
                                              mrViewState,
                                              rRenderState );

                    return true;
                }

            private:
                const uno::Reference< rendering::XCanvas >&			mrCanvas;
                const uno::Reference< rendering::XTextLayout >&		mrTextLayout;
                const uno::Reference< rendering::XPolyPolygon2D >&	mrLinePolygon;
                const rendering::ViewState&			 				mrViewState;
            };

            bool EffectTextArrayAction::render( const ::basegfx::B2DHomMatrix&	rTransformation,
                                                const Subset&					rSubset ) const
            {
                RTL_LOGFILE_CONTEXT( aLog, "::cppcanvas::internal::EffectTextArrayAction::render( subset )" );
                RTL_LOGFILE_CONTEXT_TRACE1( aLog, "::cppcanvas::internal::EffectTextArrayAction: 0x%X", this );

                rendering::RenderState 					 aLocalState( maState );
                uno::Reference< rendering::XTextLayout > xTextLayout( mxTextLayout );
                const geometry::RealRectangle2D          aTextBounds( mxTextLayout->queryTextBounds() );

                double nMinPos(0.0);
                double nMaxPos(aTextBounds.X2 - aTextBounds.X1);

                createSubsetLayout( xTextLayout,
                                    aLocalState,
                                    nMinPos, 
                                    nMaxPos,
                                    rTransformation,
                                    rSubset );

                if( !xTextLayout.is() )
                    return true; // empty layout, render nothing


                // create and setup local line polygon
                // ===================================

                uno::Reference< rendering::XCanvas > xCanvas( mpCanvas->getUNOCanvas() );
                const rendering::ViewState&			 rViewState( mpCanvas->getViewState() );
                
                uno::Reference< rendering::XPolyPolygon2D > xTextLines(
                    ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( 
                        xCanvas->getDevice(),
                        tools::createTextLinesPolyPolygon( 
                            0.0, nMaxPos - nMinPos,
                            maTextLineInfo ) ) );
                

                // render everything
                // =================

                return renderEffectText( 
                    EffectTextArrayRenderHelper( xCanvas,
                                                 xTextLayout,
                                                 xTextLines,
                                                 rViewState ),
                    aLocalState,
                    rViewState,
                    xCanvas,
                    maShadowColor,
                    maShadowOffset,
                    maReliefColor,
                    maReliefOffset );
            }

            ::basegfx::B2DRange EffectTextArrayAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const
            {
                rendering::RenderState aLocalState( maState );
                ::canvas::tools::prependToRenderState(aLocalState, rTransformation);

                return calcEffectTextBounds( ::basegfx::unotools::b2DRectangleFromRealRectangle2D(
                                                 mxTextLayout->queryTextBounds() ),
                                             ::basegfx::B2DRange( 0,0,
                                                                  maLinesOverallSize.getX(),
                                                                  maLinesOverallSize.getY() ),
                                             maReliefOffset,
                                             maShadowOffset,
                                             aLocalState,
                                             mpCanvas->getViewState() );
            }

            ::basegfx::B2DRange EffectTextArrayAction::getBounds( const ::basegfx::B2DHomMatrix&	rTransformation,
                                                                  const Subset&						rSubset ) const
            {
                RTL_LOGFILE_CONTEXT( aLog, "::cppcanvas::internal::EffectTextArrayAction::getBounds( subset )" );
                RTL_LOGFILE_CONTEXT_TRACE1( aLog, "::cppcanvas::internal::EffectTextArrayAction: 0x%X", this );

                rendering::RenderState 					 aLocalState( maState );
                uno::Reference< rendering::XTextLayout > xTextLayout( mxTextLayout );
                const geometry::RealRectangle2D          aTextBounds( mxTextLayout->queryTextBounds() );

                double nMinPos(0.0);
                double nMaxPos(aTextBounds.X2 - aTextBounds.X1);

                createSubsetLayout( xTextLayout,
                                    aLocalState,
                                    nMinPos, 
                                    nMaxPos,
                                    rTransformation,
                                    rSubset );

                if( !xTextLayout.is() )
                    return ::basegfx::B2DRange(); // empty layout, empty bounds


                // create and setup local line polygon
                // ===================================

                const ::basegfx::B2DPolyPolygon aPoly(
                    tools::createTextLinesPolyPolygon( 
                        0.0, nMaxPos - nMinPos,
                        maTextLineInfo ) );
                
                return calcEffectTextBounds( ::basegfx::unotools::b2DRectangleFromRealRectangle2D(
                                                 xTextLayout->queryTextBounds() ),
                                             ::basegfx::tools::getRange( aPoly ),
                                             maReliefOffset,
                                             maShadowOffset,
                                             aLocalState,
                                             mpCanvas->getViewState() );
            }

            sal_Int32 EffectTextArrayAction::getActionCount() const
            {
                const rendering::StringContext& rOrigContext( mxTextLayout->getText() );

                return rOrigContext.Length;
            }


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

            class OutlineAction : 
                public Action, 
                public TextRenderer,
                private ::boost::noncopyable
            { 
            public: 
                OutlineAction( const ::basegfx::B2DPoint&							rStartPoint,  
                               const ::basegfx::B2DSize&							rReliefOffset,  
                               const ::Color&										rReliefColor,
                               const ::basegfx::B2DSize&							rShadowOffset,
                               const ::Color&										rShadowColor,
                               const ::basegfx::B2DRectangle&						rOutlineBounds,
                               const uno::Reference< rendering::XPolyPolygon2D >&	rTextPoly,
                               const ::std::vector< sal_Int32 >& 					rPolygonGlyphMap,
                               const uno::Sequence< double >&						rOffsets,
                               VirtualDevice&										rVDev,
                               const CanvasSharedPtr&								rCanvas, 
                               const OutDevState& 									rState	);
                OutlineAction( const ::basegfx::B2DPoint&							rStartPoint,  
                               const ::basegfx::B2DSize&							rReliefOffset,  
                               const ::Color&										rReliefColor,
                               const ::basegfx::B2DSize&							rShadowOffset,
                               const ::Color&										rShadowColor,
                               const ::basegfx::B2DRectangle&						rOutlineBounds,
                               const uno::Reference< rendering::XPolyPolygon2D >&	rTextPoly,
                               const ::std::vector< sal_Int32 >& 					rPolygonGlyphMap,
                               const uno::Sequence< double >&						rOffsets,
                               VirtualDevice&										rVDev,
                               const CanvasSharedPtr&								rCanvas, 
                               const OutDevState& 									rState,
                               const ::basegfx::B2DHomMatrix&						rTextTransform ); 

                virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation ) const;
                virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation,
                                     const Subset&					rSubset ) const;

                virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const;
                virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix&	rTransformation,
                                                       const Subset&					rSubset ) const;

                virtual sal_Int32 getActionCount() const;

            private:
                // TextRenderer interface
                virtual bool operator()( const rendering::RenderState& rRenderState ) const;
                    
                // TODO(P2): This is potentially a real mass object
                // (every character might be a separate TextAction),
                // thus, make it as lightweight as possible. For
                // example, share common RenderState among several
                // TextActions, maybe using maOffsets for the
                // translation.

                uno::Reference< rendering::XPolyPolygon2D >			mxTextPoly;

                /** This vector denotes the index of the start polygon
                    for the respective glyph sequence.

                    To get a polygon index range for a given character
                    index i, take [ maPolygonGlyphMap[i],
                    maPolygonGlyphMap[i+1] ). Note that this is wrong
                    for BiDi
                 */
                const ::std::vector< sal_Int32 > 					maPolygonGlyphMap;
                const uno::Sequence< double >						maOffsets;
                const CanvasSharedPtr								mpCanvas;
                rendering::RenderState								maState;
                double												mnOutlineWidth;
                const uno::Sequence< double >						maFillColor;
                const tools::TextLineInfo							maTextLineInfo;
                ::basegfx::B2DSize									maLinesOverallSize;
                const ::basegfx::B2DRectangle						maOutlineBounds;
                uno::Reference< rendering::XPolyPolygon2D >			mxTextLines;
                const ::basegfx::B2DSize							maReliefOffset;
                const ::Color										maReliefColor;
                const ::basegfx::B2DSize							maShadowOffset;
                const ::Color										maShadowColor;
            };

            double calcOutlineWidth( const OutDevState& rState,
                                     VirtualDevice&     rVDev )
            {
                const ::basegfx::B2DSize aFontSize( 0,
                                                    rVDev.GetFont().GetHeight() / 64.0 );

                const double nOutlineWidth( 
                    (rState.mapModeTransform * aFontSize).getY() );

                return nOutlineWidth < 1.0 ? 1.0 : nOutlineWidth;
            }

            OutlineAction::OutlineAction( const ::basegfx::B2DPoint&							rStartPoint,  
                                          const ::basegfx::B2DSize&								rReliefOffset,  
                                          const ::Color&										rReliefColor,
                                          const ::basegfx::B2DSize&								rShadowOffset,
                                          const ::Color&										rShadowColor,
                                          const ::basegfx::B2DRectangle&						rOutlineBounds,
                                          const uno::Reference< rendering::XPolyPolygon2D >& 	rTextPoly,
                                          const ::std::vector< sal_Int32 >& 					rPolygonGlyphMap,
                                          const uno::Sequence< double >&						rOffsets,
                                          VirtualDevice&										rVDev,
                                          const CanvasSharedPtr&								rCanvas, 
                                          const OutDevState& 									rState	) :
                mxTextPoly( rTextPoly ),
                maPolygonGlyphMap( rPolygonGlyphMap ),
                maOffsets( rOffsets ),
                mpCanvas( rCanvas ),
                maState(),
                mnOutlineWidth( calcOutlineWidth(rState,rVDev) ),
                maFillColor( 
                    ::vcl::unotools::colorToDoubleSequence( 
                        ::Color( COL_WHITE ),
                        rCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() )),
                maTextLineInfo( tools::createTextLineInfo( rVDev, rState ) ),
                maLinesOverallSize(),
                maOutlineBounds( rOutlineBounds ),
                mxTextLines(),
                maReliefOffset( rReliefOffset ),
                maReliefColor( rReliefColor ),
                maShadowOffset( rShadowOffset ),
                maShadowColor( rShadowColor )
            {
                initEffectLinePolyPolygon( maLinesOverallSize,
                                           mxTextLines,
                                           rCanvas,
                                           rOffsets,
                                           maTextLineInfo );

                init( maState,
                      rStartPoint, 
                      rState,
                      rCanvas );
            }

            OutlineAction::OutlineAction( const ::basegfx::B2DPoint&							rStartPoint,  
                                          const ::basegfx::B2DSize&								rReliefOffset,  
                                          const ::Color&										rReliefColor,
                                          const ::basegfx::B2DSize&								rShadowOffset,
                                          const ::Color&										rShadowColor,
                                          const ::basegfx::B2DRectangle&						rOutlineBounds,
                                          const uno::Reference< rendering::XPolyPolygon2D >& 	rTextPoly,
                                          const ::std::vector< sal_Int32 >& 					rPolygonGlyphMap,
                                          const uno::Sequence< double >&						rOffsets,
                                          VirtualDevice&										rVDev,
                                          const CanvasSharedPtr&								rCanvas, 
                                          const OutDevState& 									rState,
                                          const ::basegfx::B2DHomMatrix&						rTextTransform ) :
                mxTextPoly( rTextPoly ),
                maPolygonGlyphMap( rPolygonGlyphMap ),
                maOffsets( rOffsets ),
                mpCanvas( rCanvas ),
                maState(),
                mnOutlineWidth( calcOutlineWidth(rState,rVDev) ),
                maFillColor( 
                    ::vcl::unotools::colorToDoubleSequence(
                        ::Color( COL_WHITE ),
                        rCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() )),
                maTextLineInfo( tools::createTextLineInfo( rVDev, rState ) ),
                maLinesOverallSize(),
                maOutlineBounds( rOutlineBounds ),
                mxTextLines(),
                maReliefOffset( rReliefOffset ),
                maReliefColor( rReliefColor ),
                maShadowOffset( rShadowOffset ),
                maShadowColor( rShadowColor )
            {
                initEffectLinePolyPolygon( maLinesOverallSize,
                                           mxTextLines,
                                           rCanvas,
                                           rOffsets,
                                           maTextLineInfo );

                init( maState,
                      rStartPoint, 
                      rState,
                      rCanvas,
                      rTextTransform );
            }

            bool OutlineAction::operator()( const rendering::RenderState& rRenderState ) const
            {
                const rendering::ViewState& 				rViewState( mpCanvas->getViewState() );
                const uno::Reference< rendering::XCanvas >& rCanvas( mpCanvas->getUNOCanvas() );

                rendering::StrokeAttributes aStrokeAttributes;

                aStrokeAttributes.StrokeWidth  = mnOutlineWidth;
                aStrokeAttributes.MiterLimit   = 1.0;
                aStrokeAttributes.StartCapType = rendering::PathCapType::BUTT;
                aStrokeAttributes.EndCapType   = rendering::PathCapType::BUTT;
                aStrokeAttributes.JoinType     = rendering::PathJoinType::MITER;

                rendering::RenderState aLocalState( rRenderState );
                aLocalState.DeviceColor = maFillColor;

                // TODO(P1): implement caching

                // background of text
                rCanvas->fillPolyPolygon( mxTextPoly, 
                                          rViewState,
                                          aLocalState );
                
                // border line of text
                rCanvas->strokePolyPolygon( mxTextPoly, 
                                            rViewState,
                                            rRenderState,
                                            aStrokeAttributes );

                // underlines/strikethrough - background
                rCanvas->fillPolyPolygon( mxTextLines,
                                          rViewState,
                                          aLocalState );
                // underlines/strikethrough - border
                rCanvas->strokePolyPolygon( mxTextLines,
                                            rViewState,
                                            rRenderState,
                                            aStrokeAttributes );

                return true;
            }

            bool OutlineAction::render( const ::basegfx::B2DHomMatrix& rTransformation ) const
            {
                RTL_LOGFILE_CONTEXT( aLog, "::cppcanvas::internal::EffectTextArrayAction::render()" );
                RTL_LOGFILE_CONTEXT_TRACE1( aLog, "::cppcanvas::internal::EffectTextArrayAction: 0x%X", this );

                rendering::RenderState aLocalState( maState );
                ::canvas::tools::prependToRenderState(aLocalState, rTransformation);

                return renderEffectText( *this,
                                         aLocalState,
                                         mpCanvas->getViewState(),
                                         mpCanvas->getUNOCanvas(),
                                         maShadowColor,
                                         maShadowOffset,
                                         maReliefColor,
                                         maReliefOffset );
            }

            class OutlineTextArrayRenderHelper : public TextRenderer
            {
            public:
                OutlineTextArrayRenderHelper( const uno::Reference< rendering::XCanvas >&		 rCanvas,
                                              const uno::Reference< rendering::XPolyPolygon2D >& rTextPolygon,
                                              const uno::Reference< rendering::XPolyPolygon2D >& rLinePolygon,
                                              const rendering::ViewState&			 			 rViewState,
                                              double											 nOutlineWidth ) :
                    maFillColor( 
                        ::vcl::unotools::colorToDoubleSequence( 
                            ::Color( COL_WHITE ),
                            rCanvas->getDevice()->getDeviceColorSpace() )),
                    mnOutlineWidth( nOutlineWidth ),
                    mrCanvas( rCanvas ),
                    mrTextPolygon( rTextPolygon ),
                    mrLinePolygon( rLinePolygon ),
                    mrViewState( rViewState )
                {
                }

                // TextRenderer interface
                virtual bool operator()( const rendering::RenderState& rRenderState ) const
                {
                    rendering::StrokeAttributes aStrokeAttributes;

                    aStrokeAttributes.StrokeWidth  = mnOutlineWidth;
                    aStrokeAttributes.MiterLimit   = 1.0;
                    aStrokeAttributes.StartCapType = rendering::PathCapType::BUTT;
                    aStrokeAttributes.EndCapType   = rendering::PathCapType::BUTT;
                    aStrokeAttributes.JoinType     = rendering::PathJoinType::MITER;

                    rendering::RenderState aLocalState( rRenderState );
                    aLocalState.DeviceColor = maFillColor;

                    // TODO(P1): implement caching

                    // background of text
                    mrCanvas->fillPolyPolygon( mrTextPolygon, 
                                               mrViewState,
                                               aLocalState );
                
                    // border line of text
                    mrCanvas->strokePolyPolygon( mrTextPolygon, 
                                                 mrViewState,
                                                 rRenderState,
                                                 aStrokeAttributes );

                    // underlines/strikethrough - background
                    mrCanvas->fillPolyPolygon( mrLinePolygon,
                                               mrViewState,
                                               aLocalState );
                    // underlines/strikethrough - border
                    mrCanvas->strokePolyPolygon( mrLinePolygon,
                                                 mrViewState,
                                                 rRenderState,
                                                 aStrokeAttributes );

                    return true;
                }

            private:
                const uno::Sequence< double >						maFillColor;
                double												mnOutlineWidth;
                const uno::Reference< rendering::XCanvas >&			mrCanvas;
                const uno::Reference< rendering::XPolyPolygon2D >&	mrTextPolygon;
                const uno::Reference< rendering::XPolyPolygon2D >&	mrLinePolygon;
                const rendering::ViewState&			 				mrViewState;
            };

            bool OutlineAction::render( const ::basegfx::B2DHomMatrix&	rTransformation,
                                        const Subset&					rSubset ) const
            {
                RTL_LOGFILE_CONTEXT( aLog, "::cppcanvas::internal::OutlineAction::render( subset )" );
                RTL_LOGFILE_CONTEXT_TRACE1( aLog, "::cppcanvas::internal::OutlineAction: 0x%X", this );

                if( rSubset.mnSubsetBegin == rSubset.mnSubsetEnd )
                    return true; // empty range, render nothing

#if 1
                // TODO(F3): Subsetting NYI for outline text!
                return render( rTransformation );
#else
                const rendering::StringContext rOrigContext( mxTextLayout->getText() );

                if( rSubset.mnSubsetBegin == 0 &&
                    rSubset.mnSubsetEnd == rOrigContext.Length )
                {
                    // full range, no need for subsetting
                    return render( rTransformation );
                }

                rendering::RenderState aLocalState( maState );
                ::canvas::tools::prependToRenderState(aLocalState, rTransformation);


                // create and setup local Text polygon
                // ===================================

                uno::Reference< rendering::XPolyPolygon2D > xTextPolygon();

                // TODO(P3): Provide an API method for that!

                if( !xTextLayout.is() )
                    return false;

                // render everything
                // =================

                return renderEffectText( 
                    OutlineTextArrayRenderHelper( 
                        xCanvas,
                        mnOutlineWidth,
                        xTextLayout,
                        xTextLines,
                        rViewState ),
                    aLocalState,
                    rViewState,
                    xCanvas,
                    maShadowColor,
                    maShadowOffset,
                    maReliefColor,
                    maReliefOffset );
#endif
            }

            ::basegfx::B2DRange OutlineAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const
            {
                rendering::RenderState aLocalState( maState );
                ::canvas::tools::prependToRenderState(aLocalState, rTransformation);

                return calcEffectTextBounds( maOutlineBounds,
                                             ::basegfx::B2DRange( 0,0,
                                                                  maLinesOverallSize.getX(),
                                                                  maLinesOverallSize.getY() ),
                                             maReliefOffset,
                                             maShadowOffset,
                                             aLocalState,
                                             mpCanvas->getViewState() );
            }

            ::basegfx::B2DRange OutlineAction::getBounds( const ::basegfx::B2DHomMatrix&	rTransformation,
                                                          const Subset&						/*rSubset*/ ) const
            {
                OSL_ENSURE( false,
                            "OutlineAction::getBounds(): Subset not yet supported by this object" );

                return getBounds( rTransformation );
            }

            sal_Int32 OutlineAction::getActionCount() const
            {
                // TODO(F3): Subsetting NYI for outline text!
                return maOffsets.getLength();
            }


            // ======================================================================
            //
            // Action factory methods
            //
            // ======================================================================

            /** Create an outline action

            	This method extracts the polygonal outline from the
            	text, and creates a properly setup OutlineAction from
            	it.
             */
            ActionSharedPtr createOutline( const ::basegfx::B2DPoint&		rStartPoint,  
                                           const ::basegfx::B2DSize&		rReliefOffset,  
                                           const ::Color&					rReliefColor,
                                           const ::basegfx::B2DSize&        rShadowOffset,
                                           const ::Color&					rShadowColor,
                                           const String& 					rText,
                                           sal_Int32 						nStartPos,
                                           sal_Int32 						nLen,
                                           const sal_Int32*					pDXArray,
                                           VirtualDevice&					rVDev,
                                           const CanvasSharedPtr&			rCanvas, 
                                           const OutDevState& 				rState,
                                           const Renderer::Parameters& 		rParms	)
            {
                // operate on raw DX array here (in logical coordinate
                // system), to have a higher resolution
                // PolyPolygon. That polygon is then converted to
                // device coordinate system.

                // #i68512# Temporarily switch off font rotation
                // (which is already contained in the render state
                // transformation matrix - otherwise, glyph polygons
                // will be rotated twice)
                const ::Font aOrigFont( rVDev.GetFont() );
                ::Font       aUnrotatedFont( aOrigFont );
                aUnrotatedFont.SetOrientation(0);
                rVDev.SetFont( aUnrotatedFont );
                
                // TODO(F3): Don't understand parameter semantics of
                // GetTextOutlines()
                ::basegfx::B2DPolyPolygon aResultingPolyPolygon;
                PolyPolyVector aVCLPolyPolyVector;
                const bool bHaveOutlines( rVDev.GetTextOutlines( aVCLPolyPolyVector, rText,
                                                                 static_cast<sal_uInt16>(nStartPos),
                                                                 static_cast<sal_uInt16>(nStartPos), 
                                                                 static_cast<sal_uInt16>(nLen),
                                                                 sal_True, 0, pDXArray ) );
                rVDev.SetFont(aOrigFont);

                if( !bHaveOutlines )
                    return ActionSharedPtr();

                ::std::vector< sal_Int32 > aPolygonGlyphMap;

                // first glyph starts at polygon index 0
                aPolygonGlyphMap.push_back( 0 );

                // remove offsetting from mapmode transformation
                // (outline polygons must stay at origin, only need to
                // be scaled)
                ::basegfx::B2DHomMatrix aMapModeTransform( 
                    rState.mapModeTransform );
                aMapModeTransform.set(0,2, 0.0);
                aMapModeTransform.set(1,2, 0.0);

                PolyPolyVector::const_iterator 		 aIter( aVCLPolyPolyVector.begin() );
                const PolyPolyVector::const_iterator aEnd( aVCLPolyPolyVector.end() );
                for( ; aIter!= aEnd; ++aIter )
                {
                    ::basegfx::B2DPolyPolygon aPolyPolygon;

                    aPolyPolygon = aIter->getB2DPolyPolygon();
                    aPolyPolygon.transform( aMapModeTransform );

                    // append result to collecting polypoly
                    for( sal_uInt32 i=0; i<aPolyPolygon.count(); ++i )
                    {
                        // #i47795# Ensure closed polygons (since
                        // FreeType returns the glyph outlines
                        // open)
                        const ::basegfx::B2DPolygon& rPoly( aPolyPolygon.getB2DPolygon( i ) );
                        const sal_uInt32 nCount( rPoly.count() );
                        if( nCount<3 ||
                            rPoly.isClosed() )
                        {
                            // polygon either degenerate, or
                            // already closed.
                            aResultingPolyPolygon.append( rPoly );
                        }
                        else
                        {
                            ::basegfx::B2DPolygon aPoly(rPoly);
                            aPoly.setClosed(true);

                            aResultingPolyPolygon.append( aPoly );
                        }
                    }

                    // TODO(F3): Depending on the semantics of
                    // GetTextOutlines(), this here is wrong!

                    // calc next glyph index
                    aPolygonGlyphMap.push_back( aResultingPolyPolygon.count() );
                }

                const uno::Sequence< double > aCharWidthSeq(
                    pDXArray ?
                    setupDXArray( pDXArray, nLen, rState ) :
                    setupDXArray( rText,
                                  nStartPos,
                                  nLen,
                                  rVDev,
                                  rState ));
                const uno::Reference< rendering::XPolyPolygon2D > xTextPoly(
                    ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( 
                        rCanvas->getUNOCanvas()->getDevice(), 
                        aResultingPolyPolygon ) );

                if( rParms.maTextTransformation.is_initialized() )
                {
                    return ActionSharedPtr( 
                        new OutlineAction( 
                            rStartPoint,
                            rReliefOffset,  
                            rReliefColor,
                            rShadowOffset,
                            rShadowColor,
                            ::basegfx::tools::getRange(aResultingPolyPolygon),
                            xTextPoly,
                            aPolygonGlyphMap,
                            aCharWidthSeq,
                            rVDev,
                            rCanvas, 
                            rState,
                            *rParms.maTextTransformation ) );
                }
                else
                {
                    return ActionSharedPtr( 
                        new OutlineAction( 
                            rStartPoint,
                            rReliefOffset,  
                            rReliefColor,
                            rShadowOffset,
                            rShadowColor,
                            ::basegfx::tools::getRange(aResultingPolyPolygon),
                            xTextPoly,
                            aPolygonGlyphMap,
                            aCharWidthSeq,
                            rVDev,
                            rCanvas, 
                            rState	) );
                }
            }

        } // namespace

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

		ActionSharedPtr TextActionFactory::createTextAction( const ::Point&					rStartPoint,  
                                                             const ::Size&					rReliefOffset,  
                                                             const ::Color&					rReliefColor,
                                                             const ::Size&					rShadowOffset,
                                                             const ::Color&					rShadowColor,
                                                             const String& 					rText,
                                                             sal_Int32 						nStartPos,
                                                             sal_Int32 						nLen,
                                                             const sal_Int32*				pDXArray,
                                                             VirtualDevice&					rVDev,
                                                             const CanvasSharedPtr&			rCanvas, 
                                                             const OutDevState& 			rState,
                                                             const Renderer::Parameters& 	rParms,
                                                             bool							bSubsettable	)
		{
            const ::Size  aBaselineOffset( tools::getBaselineOffset( rState,
                                                                     rVDev ) );
            // #143885# maintain (nearly) full precision positioning,
            // by circumventing integer-based OutDev-mapping
            const ::basegfx::B2DPoint aStartPoint( 
                rState.mapModeTransform * 
                ::basegfx::B2DPoint(rStartPoint.X() + aBaselineOffset.Width(),
                                    rStartPoint.Y() + aBaselineOffset.Height()) );

            const ::basegfx::B2DSize aReliefOffset( 
                rState.mapModeTransform * ::vcl::unotools::b2DSizeFromSize( rReliefOffset ) );
            const ::basegfx::B2DSize aShadowOffset( 
                rState.mapModeTransform * ::vcl::unotools::b2DSizeFromSize( rShadowOffset ) );

            if( rState.isTextOutlineModeSet )
            {
                return createOutline(
                    		aStartPoint,  
                            aReliefOffset,  
                            rReliefColor,
                            aShadowOffset,
                            rShadowColor,
                            rText,
                            nStartPos,
                            nLen,
                            pDXArray,
                            rVDev,
                            rCanvas, 
                            rState,
                            rParms );
            }

            // convert DX array to device coordinate system (and
            // create it in the first place, if pDXArray is NULL)
            const uno::Sequence< double > aCharWidths(
                pDXArray ?
                setupDXArray( pDXArray, nLen, rState ) :
                setupDXArray( rText,
                              nStartPos,
                              nLen,
                              rVDev,
                              rState ));

            // determine type of text action to create
            // =======================================

            const ::Color aEmptyColor( COL_AUTO );

            // no DX array, and no need to subset - no need to store
            // DX array, then.
            if( !pDXArray && !bSubsettable )
            {
                // effects, or not?
                if( !rState.textOverlineStyle &&
                    !rState.textUnderlineStyle &&
                    !rState.textStrikeoutStyle &&
                    rReliefColor == aEmptyColor &&
                    rShadowColor == aEmptyColor )
                {
                    // nope
                    if( rParms.maTextTransformation.is_initialized() )
                    {
                        return ActionSharedPtr( new TextAction( 
                                                    aStartPoint,
                                                    rText,
                                                    nStartPos,
                                                    nLen,
                                                    rCanvas,
                                                    rState,
                                                    *rParms.maTextTransformation ) );
                    }
                    else
                    {
                        return ActionSharedPtr( new TextAction( 
                                                    aStartPoint,
                                                    rText,
                                                    nStartPos,
                                                    nLen,
                                                    rCanvas,
                                                    rState ) );
                    }
                }
                else
                {
                    // at least one of the effects requested
                    if( rParms.maTextTransformation.is_initialized() )
                        return ActionSharedPtr( new EffectTextAction( 
                                                    aStartPoint,
                                                    aReliefOffset,  
                                                    rReliefColor,
                                                    aShadowOffset,
                                                    rShadowColor,
                                                    rText,
                                                    nStartPos,
                                                    nLen,
                                                    rVDev,
                                                    rCanvas,
                                                    rState,
                                                    *rParms.maTextTransformation ) );
                    else
                        return ActionSharedPtr( new EffectTextAction( 
                                                    aStartPoint,
                                                    aReliefOffset,  
                                                    rReliefColor,
                                                    aShadowOffset,
                                                    rShadowColor,
                                                    rText,
                                                    nStartPos,
                                                    nLen,
                                                    rVDev,
                                                    rCanvas,
                                                    rState ) );
                }
            }
            else
            {
                // DX array necessary - any effects?
                if( !rState.textOverlineStyle &&
                    !rState.textUnderlineStyle &&
                    !rState.textStrikeoutStyle &&
                    rReliefColor == aEmptyColor &&
                    rShadowColor == aEmptyColor )
                {
                    // nope
                    if( rParms.maTextTransformation.is_initialized() )
                        return ActionSharedPtr( new TextArrayAction( 
                                                    aStartPoint,
                                                    rText,
                                                    nStartPos,
                                                    nLen,
                                                    aCharWidths,
                                                    rCanvas,
                                                    rState,
                                                    *rParms.maTextTransformation ) );
                    else
                        return ActionSharedPtr( new TextArrayAction( 
                                                    aStartPoint,
                                                    rText,
                                                    nStartPos,
                                                    nLen,
                                                    aCharWidths,
                                                    rCanvas,
                                                    rState ) );
                }
                else
                {
                    // at least one of the effects requested
                    if( rParms.maTextTransformation.is_initialized() )
                        return ActionSharedPtr( new EffectTextArrayAction( 
                                                    aStartPoint,  
                                                    aReliefOffset,  
                                                    rReliefColor,
                                                    aShadowOffset,
                                                    rShadowColor,
                                                    rText,
                                                    nStartPos,
                                                    nLen,
                                                    aCharWidths,
                                                    rVDev,
                                                    rCanvas,
                                                    rState,
                                                    *rParms.maTextTransformation ) );
                    else
                        return ActionSharedPtr( new EffectTextArrayAction( 
                                                    aStartPoint,
                                                    aReliefOffset,  
                                                    rReliefColor,
                                                    aShadowOffset,
                                                    rShadowColor,
                                                    rText,
                                                    nStartPos,
                                                    nLen,
                                                    aCharWidths,
                                                    rVDev,
                                                    rCanvas,
                                                    rState ) );
                }
            }
#if defined __GNUC__
#if __GNUC__ == 4 && __GNUC_MINOR__ >= 1
            // Unreachable; to avoid bogus warning:
            return ActionSharedPtr();
#endif
#endif
        }
    }
}
