/************************************************************************* * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright 2000, 2010 Oracle and/or its affiliates. * * OpenOffice.org - a multi-platform office productivity suite * * This file is part of OpenOffice.org. * * OpenOffice.org is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 * only, as published by the Free Software Foundation. * * OpenOffice.org is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License version 3 for more details * (a copy is included in the LICENSE file that accompanied this code). * * You should have received a copy of the GNU Lesser General Public License * version 3 along with OpenOffice.org. If not, see * * for a copy of the LGPLv3 License. * ************************************************************************/ // MARKER(update_precomp.py): autogen include statement, do not remove #include "precompiled_cppcanvas.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(nStartPos), static_cast(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(rStringContext.StartPosition), static_cast(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(), _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 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(nStartPos), static_cast(nStartPos), static_cast(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 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 } } }