2316 lines
117 KiB
C++
2316 lines
117 KiB
C++
/*************************************************************************
|
|
*
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* Copyright 2000, 2010 Oracle and/or its affiliates.
|
|
*
|
|
* OpenOffice.org - a multi-platform office productivity suite
|
|
*
|
|
* This file is part of OpenOffice.org.
|
|
*
|
|
* OpenOffice.org is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License version 3
|
|
* only, as published by the Free Software Foundation.
|
|
*
|
|
* OpenOffice.org is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License version 3 for more details
|
|
* (a copy is included in the LICENSE file that accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* version 3 along with OpenOffice.org. If not, see
|
|
* <http://www.openoffice.org/license.html>
|
|
* for a copy of the LGPLv3 License.
|
|
*
|
|
************************************************************************/
|
|
|
|
// MARKER(update_precomp.py): autogen include statement, do not remove
|
|
#include "precompiled_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<USHORT>(nStartPos),
|
|
static_cast<USHORT>(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<USHORT>(rStringContext.StartPosition),
|
|
static_cast<USHORT>(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<USHORT>(nStartPos),
|
|
static_cast<USHORT>(nStartPos),
|
|
static_cast<USHORT>(nLen),
|
|
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.isValid() )
|
|
{
|
|
return ActionSharedPtr(
|
|
new OutlineAction(
|
|
rStartPoint,
|
|
rReliefOffset,
|
|
rReliefColor,
|
|
rShadowOffset,
|
|
rShadowColor,
|
|
::basegfx::tools::getRange(aResultingPolyPolygon),
|
|
xTextPoly,
|
|
aPolygonGlyphMap,
|
|
aCharWidthSeq,
|
|
rVDev,
|
|
rCanvas,
|
|
rState,
|
|
rParms.maTextTransformation.getValue() ) );
|
|
}
|
|
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.isValid() )
|
|
{
|
|
return ActionSharedPtr( new TextAction(
|
|
aStartPoint,
|
|
rText,
|
|
nStartPos,
|
|
nLen,
|
|
rCanvas,
|
|
rState,
|
|
rParms.maTextTransformation.getValue() ) );
|
|
}
|
|
else
|
|
{
|
|
return ActionSharedPtr( new TextAction(
|
|
aStartPoint,
|
|
rText,
|
|
nStartPos,
|
|
nLen,
|
|
rCanvas,
|
|
rState ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// at least one of the effects requested
|
|
if( rParms.maTextTransformation.isValid() )
|
|
return ActionSharedPtr( new EffectTextAction(
|
|
aStartPoint,
|
|
aReliefOffset,
|
|
rReliefColor,
|
|
aShadowOffset,
|
|
rShadowColor,
|
|
rText,
|
|
nStartPos,
|
|
nLen,
|
|
rVDev,
|
|
rCanvas,
|
|
rState,
|
|
rParms.maTextTransformation.getValue() ) );
|
|
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.isValid() )
|
|
return ActionSharedPtr( new TextArrayAction(
|
|
aStartPoint,
|
|
rText,
|
|
nStartPos,
|
|
nLen,
|
|
aCharWidths,
|
|
rCanvas,
|
|
rState,
|
|
rParms.maTextTransformation.getValue() ) );
|
|
else
|
|
return ActionSharedPtr( new TextArrayAction(
|
|
aStartPoint,
|
|
rText,
|
|
nStartPos,
|
|
nLen,
|
|
aCharWidths,
|
|
rCanvas,
|
|
rState ) );
|
|
}
|
|
else
|
|
{
|
|
// at least one of the effects requested
|
|
if( rParms.maTextTransformation.isValid() )
|
|
return ActionSharedPtr( new EffectTextArrayAction(
|
|
aStartPoint,
|
|
aReliefOffset,
|
|
rReliefColor,
|
|
aShadowOffset,
|
|
rShadowColor,
|
|
rText,
|
|
nStartPos,
|
|
nLen,
|
|
aCharWidths,
|
|
rVDev,
|
|
rCanvas,
|
|
rState,
|
|
rParms.maTextTransformation.getValue() ) );
|
|
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
|
|
}
|
|
}
|
|
}
|