/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <comphelper/diagnose_ex.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/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 <utility>
#include <vcl/canvastools.hxx>
#include <vcl/virdev.hxx>
#include <basegfx/utils/canvastools.hxx>
#include <canvas/canvastools.hxx>
#include <memory>
#include <sal/log.hxx>
#include "textaction.hxx"
#include "textlineshelper.hxx"
#include <outdevstate.hxx>
#include "mtftools.hxx"
using namespace ::com::sun::star;
namespace cppcanvas::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,
nullptr,
&rState.fontRotation );
basegfx::B2DHomMatrix aLocalTransformation(basegfx::utils::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 );
}
void initLayoutWidth(double& rLayoutWidth, const uno::Sequence<double>& rOffsets)
{
ENSURE_OR_THROW(rOffsets.hasElements(),
"::cppcanvas::internal::initLayoutWidth(): zero-length array" );
rLayoutWidth = *(std::max_element(rOffsets.begin(), rOffsets.end()));
}
uno::Sequence< double > setupDXArray( KernArraySpan rCharWidths,
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++ = rCharWidths[i] * nScale;
}
return aCharWidthSeq;
}
uno::Sequence< double > setupDXArray( const OUString& rText,
sal_Int32 nStartPos,
sal_Int32 nLen,
VirtualDevice const & rVDev,
const OutDevState& rState )
{
// no external DX array given, create one from given
// string
KernArray aCharWidths;
rVDev.GetTextArray( rText, &aCharWidths, nStartPos, nLen );
return setupDXArray( aCharWidths, 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 OUString& rText,
sal_Int32 nStartPos,
sal_Int32 nLen,
const uno::Sequence< double >& rOffsets,
const uno::Sequence< sal_Bool >& rKashidas,
const CanvasSharedPtr& rCanvas,
const OutDevState& rState,
const ::basegfx::B2DHomMatrix* pTextTransform )
{
ENSURE_OR_THROW( rOffsets.hasElements(),
"::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 );
o_rTextLayout->applyKashidaPositions( rKashidas );
}
double getLineWidth( ::VirtualDevice const & rVDev,
const OutDevState& rState,
const rendering::StringContext& rStringContext )
{
// TODO(F2): use correct scale direction
const ::basegfx::B2DSize aSize( rVDev.GetTextWidth( rStringContext.Text,
static_cast<sal_uInt16>(rStringContext.StartPosition),
static_cast<sal_uInt16>(rStringContext.Length) ),
0 );
return (rState.mapModeTransform * aSize).getWidth();
}
uno::Sequence< double >
calcSubsetOffsets( rendering::RenderState& io_rRenderState,
double& o_rMinPos,
double& o_rMaxPos,
const uno::Reference< rendering::XTextLayout >& rOrigTextLayout,
double nLayoutWidth,
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" );
// 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 )) );
// Logical advancements always increase in logical text order.
// For RTL text, nMaxPos is the distance from the right edge to
// the leftmost position in the subset, so we have to convert
// it to the offset from the origin (i.e. left edge ).
// LTR: |---- min --->|---- max --->| |
// RTL: | |<--- max ----|<--- min ---|
// |<- nOffset ->| |
const double nOffset = rOrigTextLayout->getMainTextDirection()
? nLayoutWidth - nMaxPos : nMinPos;
// 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 (nOffset > 0.0)
{
::basegfx::B2DHomMatrix aTranslation;
if( rOrigTextLayout->getFont()->getFontRequest().FontDescription.IsVertical == css::util::TriState_YES )
{
// vertical text -> offset in y direction
aTranslation.translate(0.0, nOffset);
}
else
{
// horizontal text -> offset in x direction
aTranslation.translate(nOffset, 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,
[nMinPos](double aPos) { return aPos - 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_SET_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,
double nLayoutWidth,
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 aOrigContext( io_rTextLayout->getText() );
if( rSubset.mnSubsetBegin == 0 &&
rSubset.mnSubsetEnd == aOrigContext.Length )
{
// full range, no need for subsetting
return;
}
uno::Reference< rendering::XTextLayout > xTextLayout(
createSubsetLayout( aOrigContext, rSubset, io_rTextLayout ) );
if( xTextLayout.is() )
{
xTextLayout->applyLogicalAdvancements(
calcSubsetOffsets( io_rRenderState,
o_rMinPos,
o_rMaxPos,
io_rTextLayout,
nLayoutWidth,
rSubset ) );
uno::Sequence< sal_Bool > aOrigKashidaPositions(io_rTextLayout->queryKashidaPositions());
uno::Sequence< sal_Bool > aKashidaPositions(aOrigKashidaPositions.getArray() + rSubset.mnSubsetBegin,
rSubset.mnSubsetEnd - rSubset.mnSubsetBegin);
xTextLayout->applyKashidaPositions(aKashidaPositions);
}
io_rTextLayout = std::move(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 ::Color& rTextFillColor, bool bNormalText ) 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 uno::Reference< rendering::XCanvas >& xCanvas,
const ::Color& rShadowColor,
const ::basegfx::B2DSize& rShadowOffset,
const ::Color& rReliefColor,
const ::basegfx::B2DSize& rReliefOffset,
const ::Color& rTextFillColor )
{
::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.getWidth(),
rShadowOffset.getHeight());
::canvas::tools::appendToRenderState(aShadowState, aTranslate);
aShadowState.DeviceColor =
vcl::unotools::colorToDoubleSequence( rShadowColor,
xColorSpace );
rRenderer( aShadowState, rTextFillColor, false );
}
// draw relief text, if enabled
if( rReliefColor != aEmptyColor )
{
rendering::RenderState aReliefState( rRenderState );
::basegfx::B2DHomMatrix aTranslate;
aTranslate.translate(rReliefOffset.getWidth(),
rReliefOffset.getHeight());
::canvas::tools::appendToRenderState(aReliefState, aTranslate);
aReliefState.DeviceColor =
vcl::unotools::colorToDoubleSequence( rReliefColor,
xColorSpace );
rRenderer( aReliefState, rTextFillColor, false );
}
// draw normal text
rRenderer( rRenderState, rTextFillColor, true );
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.getWidth(),
aBounds.getMinY() + rReliefOffset.getHeight(),
aBounds.getMaxX() + rReliefOffset.getWidth(),
aBounds.getMaxY() + rReliefOffset.getHeight() ) );
aTotalBounds.expand(
::basegfx::B2DRange( aBounds.getMinX() + rShadowOffset.getWidth(),
aBounds.getMinY() + rShadowOffset.getHeight(),
aBounds.getMaxX() + rShadowOffset.getWidth(),
aBounds.getMaxY() + rShadowOffset.getHeight() ) );
return tools::calcDevicePixelBounds( aTotalBounds,
rViewState,
rRenderState );
}
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 ) );
auto aRange = basegfx::utils::getRange( aPoly ).getRange();
o_rOverallSize = basegfx::B2DSize(aRange.getX(), aRange.getY());
o_rTextLines = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
rCanvas->getUNOCanvas()->getDevice(),
aPoly );
}
class TextAction : public Action
{
public:
TextAction( const ::basegfx::B2DPoint& rStartPoint,
const OUString& rString,
sal_Int32 nStartPos,
sal_Int32 nLen,
const CanvasSharedPtr& rCanvas,
const OutDevState& rState );
TextAction( const ::basegfx::B2DPoint& rStartPoint,
const OUString& rString,
sal_Int32 nStartPos,
sal_Int32 nLen,
const CanvasSharedPtr& rCanvas,
const OutDevState& rState,
const ::basegfx::B2DHomMatrix& rTextTransform );
TextAction(const TextAction&) = delete;
const TextAction& operator=(const TextAction&) = delete;
virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation ) const override;
virtual bool renderSubset( const ::basegfx::B2DHomMatrix& rTransformation,
const Subset& rSubset ) const override;
virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const override;
virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation,
const Subset& rSubset ) const override;
virtual sal_Int32 getActionCount() const override;
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 OUString& rString,
sal_Int32 nStartPos,
sal_Int32 nLen,
const CanvasSharedPtr& rCanvas,
const OutDevState& rState ) :
mxFont( rState.xFont ),
maStringContext( rString, nStartPos, nLen ),
mpCanvas( rCanvas ),
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 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 ),
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
{
SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TextAction::render()" );
SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TextAction: 0x" << std::hex << this );
rendering::RenderState aLocalState( maState );
::canvas::tools::prependToRenderState(aLocalState, rTransformation);
mpCanvas->getUNOCanvas()->drawText( maStringContext, mxFont,
mpCanvas->getViewState(), aLocalState, maTextDirection );
return true;
}
bool TextAction::renderSubset( const ::basegfx::B2DHomMatrix& rTransformation,
const Subset& /*rSubset*/ ) const
{
SAL_WARN( "cppcanvas.emf", "TextAction::renderSubset(): 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
{
SAL_WARN( "cppcanvas.emf", "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
{
public:
EffectTextAction( const ::basegfx::B2DPoint& rStartPoint,
const ::basegfx::B2DSize& rReliefOffset,
const ::Color& rReliefColor,
const ::basegfx::B2DSize& rShadowOffset,
const ::Color& rShadowColor,
const ::Color& rTextFillColor,
const OUString& rText,
sal_Int32 nStartPos,
sal_Int32 nLen,
VirtualDevice const & 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 ::Color& rTextFillColor,
const OUString& rText,
sal_Int32 nStartPos,
sal_Int32 nLen,
VirtualDevice const & rVDev,
const CanvasSharedPtr& rCanvas,
const OutDevState& rState,
const ::basegfx::B2DHomMatrix& rTextTransform );
EffectTextAction(const EffectTextAction&) = delete;
const EffectTextAction& operator=(const EffectTextAction&) = delete;
virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation ) const override;
virtual bool renderSubset( const ::basegfx::B2DHomMatrix& rTransformation,
const Subset& rSubset ) const override;
virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const override;
virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation,
const Subset& rSubset ) const override;
virtual sal_Int32 getActionCount() const override;
private:
/// Interface TextRenderer
virtual bool operator()( const rendering::RenderState& rRenderState, const ::Color& rTextFillColor, bool bNormalText ) const override;
geometry::RealRectangle2D queryTextBounds() const;
css::uno::Reference<css::rendering::XPolyPolygon2D> queryTextBounds(const uno::Reference<rendering::XCanvas>& rCanvas) 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;
uno::Reference< rendering::XPolyPolygon2D > mxTextLines;
const ::basegfx::B2DSize maReliefOffset;
const ::Color maReliefColor;
const ::basegfx::B2DSize maShadowOffset;
const ::Color maShadowColor;
const ::Color maTextFillColor;
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 ::Color& rTextFillColor,
const OUString& rText,
sal_Int32 nStartPos,
sal_Int32 nLen,
VirtualDevice const & rVDev,
const CanvasSharedPtr& rCanvas,
const OutDevState& rState ) :
mxFont( rState.xFont ),
maStringContext( rText, nStartPos, nLen ),
mpCanvas( rCanvas ),
maTextLineInfo( tools::createTextLineInfo( rVDev, rState ) ),
maReliefOffset( rReliefOffset ),
maReliefColor( rReliefColor ),
maShadowOffset( rShadowOffset ),
maShadowColor( rShadowColor ),
maTextFillColor( rTextFillColor ),
maTextDirection( rState.textDirection )
{
const double nLineWidth(getLineWidth( rVDev, rState, maStringContext ));
initEffectLinePolyPolygon( maLinesOverallSize,
mxTextLines,
rCanvas,
nLineWidth,
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 ::Color& rTextFillColor,
const OUString& rText,
sal_Int32 nStartPos,
sal_Int32 nLen,
VirtualDevice const & rVDev,
const CanvasSharedPtr& rCanvas,
const OutDevState& rState,
const ::basegfx::B2DHomMatrix& rTextTransform ) :
mxFont( rState.xFont ),
maStringContext( rText, nStartPos, nLen ),
mpCanvas( rCanvas ),
maTextLineInfo( tools::createTextLineInfo( rVDev, rState ) ),
maReliefOffset( rReliefOffset ),
maReliefColor( rReliefColor ),
maShadowOffset( rShadowOffset ),
maShadowColor( rShadowColor ),
maTextFillColor( rTextFillColor ),
maTextDirection( rState.textDirection )
{
const double nLineWidth( getLineWidth( rVDev, rState, maStringContext ) );
initEffectLinePolyPolygon( maLinesOverallSize,
mxTextLines,
rCanvas,
nLineWidth,
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 ::Color& rTextFillColor, bool /*bNormalText*/ ) const
{
const rendering::ViewState aViewState( mpCanvas->getViewState() );
const uno::Reference< rendering::XCanvas > aCanvas( mpCanvas->getUNOCanvas() );
//rhbz#1589029 non-transparent text fill background support
if (rTextFillColor != COL_AUTO)
{
rendering::RenderState aLocalState( rRenderState );
aLocalState.DeviceColor = vcl::unotools::colorToDoubleSequence(
rTextFillColor, aCanvas->getDevice()->getDeviceColorSpace());
auto xTextBounds = queryTextBounds(aCanvas);
// background of text
aCanvas->fillPolyPolygon(xTextBounds, aViewState, aLocalState);
}
// under/over lines
aCanvas->fillPolyPolygon( mxTextLines,
aViewState,
rRenderState );
aCanvas->drawText( maStringContext, mxFont,
aViewState,
rRenderState,
maTextDirection );
return true;
}
bool EffectTextAction::render( const ::basegfx::B2DHomMatrix& rTransformation ) const
{
SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::EffectTextAction::render()" );
SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::EffectTextAction: 0x" << std::hex << this );
rendering::RenderState aLocalState( maState );
::canvas::tools::prependToRenderState(aLocalState, rTransformation);
return renderEffectText( *this,
aLocalState,
mpCanvas->getUNOCanvas(),
maShadowColor,
maShadowOffset,
maReliefColor,
maReliefOffset,
maTextFillColor);
}
bool EffectTextAction::renderSubset( const ::basegfx::B2DHomMatrix& rTransformation,
const Subset& /*rSubset*/ ) const
{
SAL_WARN( "cppcanvas.emf", "EffectTextAction::renderSubset(): 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 );
}
geometry::RealRectangle2D EffectTextAction::queryTextBounds() const
{
// create XTextLayout, to have the
// XTextLayout::queryTextBounds() method available
uno::Reference< rendering::XTextLayout > xTextLayout(
mxFont->createTextLayout(
maStringContext,
maTextDirection,
0 ) );
return xTextLayout->queryTextBounds();
}
css::uno::Reference<css::rendering::XPolyPolygon2D> EffectTextAction::queryTextBounds(const uno::Reference<rendering::XCanvas>& rCanvas) const
{
auto aTextBounds = queryTextBounds();
auto aB2DBounds = ::basegfx::unotools::b2DRectangleFromRealRectangle2D(aTextBounds);
auto aTextBoundsPoly = ::basegfx::utils::createPolygonFromRect(aB2DBounds);
return ::basegfx::unotools::xPolyPolygonFromB2DPolygon(rCanvas->getDevice(), aTextBoundsPoly);
}
::basegfx::B2DRange EffectTextAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const
{
rendering::RenderState aLocalState( maState );
::canvas::tools::prependToRenderState(aLocalState, rTransformation);
return calcEffectTextBounds( ::basegfx::unotools::b2DRectangleFromRealRectangle2D(
queryTextBounds() ),
::basegfx::B2DRange( 0,0,
maLinesOverallSize.getWidth(),
maLinesOverallSize.getHeight() ),
maReliefOffset,
maShadowOffset,
aLocalState,
mpCanvas->getViewState() );
}
::basegfx::B2DRange EffectTextAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation,
const Subset& /*rSubset*/ ) const
{
SAL_WARN( "cppcanvas.emf", "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
{
public:
TextArrayAction( const ::basegfx::B2DPoint& rStartPoint,
const OUString& rString,
sal_Int32 nStartPos,
sal_Int32 nLen,
const uno::Sequence< double >& rOffsets,
const uno::Sequence< sal_Bool >& rKashidas,
const CanvasSharedPtr& rCanvas,
const OutDevState& rState );
TextArrayAction( const ::basegfx::B2DPoint& rStartPoint,
const OUString& rString,
sal_Int32 nStartPos,
sal_Int32 nLen,
const uno::Sequence< double >& rOffsets,
const uno::Sequence< sal_Bool >& rKashidas,
const CanvasSharedPtr& rCanvas,
const OutDevState& rState,
const ::basegfx::B2DHomMatrix& rTextTransform );
TextArrayAction(const TextArrayAction&) = delete;
const TextArrayAction& operator=(const TextArrayAction&) = delete;
virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation ) const override;
virtual bool renderSubset( const ::basegfx::B2DHomMatrix& rTransformation,
const Subset& rSubset ) const override;
virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const override;
virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation,
const Subset& rSubset ) const override;
virtual sal_Int32 getActionCount() const override;
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;
double mnLayoutWidth;
};
TextArrayAction::TextArrayAction( const ::basegfx::B2DPoint& rStartPoint,
const OUString& rString,
sal_Int32 nStartPos,
sal_Int32 nLen,
const uno::Sequence< double >& rOffsets,
const uno::Sequence< sal_Bool >& rKashidas,
const CanvasSharedPtr& rCanvas,
const OutDevState& rState ) :
mpCanvas( rCanvas )
{
initLayoutWidth(mnLayoutWidth, rOffsets);
initArrayAction( maState,
mxTextLayout,
rStartPoint,
rString,
nStartPos,
nLen,
rOffsets,
rKashidas,
rCanvas,
rState, nullptr );
}
TextArrayAction::TextArrayAction( const ::basegfx::B2DPoint& rStartPoint,
const OUString& rString,
sal_Int32 nStartPos,
sal_Int32 nLen,
const uno::Sequence< double >& rOffsets,
const uno::Sequence< sal_Bool >& rKashidas,
const CanvasSharedPtr& rCanvas,
const OutDevState& rState,
const ::basegfx::B2DHomMatrix& rTextTransform ) :
mpCanvas( rCanvas )
{
initLayoutWidth(mnLayoutWidth, rOffsets);
initArrayAction( maState,
mxTextLayout,
rStartPoint,
rString,
nStartPos,
nLen,
rOffsets,
rKashidas,
rCanvas,
rState,
&rTextTransform );
}
bool TextArrayAction::render( const ::basegfx::B2DHomMatrix& rTransformation ) const
{
SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TextArrayAction::render()" );
SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TextArrayAction: 0x" << std::hex << this );
rendering::RenderState aLocalState( maState );
::canvas::tools::prependToRenderState(aLocalState, rTransformation);
mpCanvas->getUNOCanvas()->drawTextLayout( mxTextLayout,
mpCanvas->getViewState(),
aLocalState );
return true;
}
bool TextArrayAction::renderSubset( const ::basegfx::B2DHomMatrix& rTransformation,
const Subset& rSubset ) const
{
SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TextArrayAction::renderSubset()" );
SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TextArrayAction: 0x" << std::hex << this );
rendering::RenderState aLocalState( maState );
uno::Reference< rendering::XTextLayout > xTextLayout( mxTextLayout );
double nDummy0, nDummy1;
createSubsetLayout( xTextLayout,
mnLayoutWidth,
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
{
SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TextArrayAction::getBounds( subset )" );
SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TextArrayAction: 0x" << std::hex << this );
rendering::RenderState aLocalState( maState );
uno::Reference< rendering::XTextLayout > xTextLayout( mxTextLayout );
double nDummy0, nDummy1;
createSubsetLayout( xTextLayout,
mnLayoutWidth,
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 aOrigContext( mxTextLayout->getText() );
return aOrigContext.Length;
}
class EffectTextArrayAction :
public Action,
public TextRenderer
{
public:
EffectTextArrayAction( const ::basegfx::B2DPoint& rStartPoint,
const ::basegfx::B2DSize& rReliefOffset,
const ::Color& rReliefColor,
const ::basegfx::B2DSize& rShadowOffset,
const ::Color& rShadowColor,
const ::Color& rTextFillColor,
const OUString& rText,
sal_Int32 nStartPos,
sal_Int32 nLen,
const uno::Sequence< double >& rOffsets,
const uno::Sequence< sal_Bool >& rKashidas,
VirtualDevice const & 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 ::Color& rTextFillColor,
const OUString& rText,
sal_Int32 nStartPos,
sal_Int32 nLen,
const uno::Sequence< double >& rOffsets,
const uno::Sequence< sal_Bool >& rKashidas,
VirtualDevice const & rVDev,
const CanvasSharedPtr& rCanvas,
const OutDevState& rState,
const ::basegfx::B2DHomMatrix& rTextTransform );
EffectTextArrayAction(const EffectTextArrayAction&) = delete;
const EffectTextArrayAction& operator=(const EffectTextArrayAction&) = delete;
virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation ) const override;
virtual bool renderSubset( const ::basegfx::B2DHomMatrix& rTransformation,
const Subset& rSubset ) const override;
virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const override;
virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation,
const Subset& rSubset ) const override;
virtual sal_Int32 getActionCount() const override;
private:
// TextRenderer interface
virtual bool operator()( const rendering::RenderState& rRenderState, const ::Color& rTextFillColor, bool bNormalText ) const override;
css::uno::Reference<css::rendering::XPolyPolygon2D> queryTextBounds(const uno::Reference<rendering::XCanvas>& rCanvas) 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;
TextLinesHelper maTextLinesHelper;
const ::basegfx::B2DSize maReliefOffset;
const ::Color maReliefColor;
const ::basegfx::B2DSize maShadowOffset;
const ::Color maShadowColor;
const ::Color maTextFillColor;
double mnLayoutWidth;
};
EffectTextArrayAction::EffectTextArrayAction( const ::basegfx::B2DPoint& rStartPoint,
const ::basegfx::B2DSize& rReliefOffset,
const ::Color& rReliefColor,
const ::basegfx::B2DSize& rShadowOffset,
const ::Color& rShadowColor,
const ::Color& rTextFillColor,
const OUString& rText,
sal_Int32 nStartPos,
sal_Int32 nLen,
const uno::Sequence< double >& rOffsets,
const uno::Sequence< sal_Bool >& rKashidas,
VirtualDevice const & rVDev,
const CanvasSharedPtr& rCanvas,
const OutDevState& rState ) :
mpCanvas( rCanvas ),
maTextLineInfo( tools::createTextLineInfo( rVDev, rState ) ),
maTextLinesHelper(mpCanvas, rState),
maReliefOffset( rReliefOffset ),
maReliefColor( rReliefColor ),
maShadowOffset( rShadowOffset ),
maShadowColor( rShadowColor ),
maTextFillColor( rTextFillColor )
{
initLayoutWidth(mnLayoutWidth, rOffsets);
maTextLinesHelper.init(mnLayoutWidth, maTextLineInfo);
initArrayAction( maState,
mxTextLayout,
rStartPoint,
rText,
nStartPos,
nLen,
rOffsets,
rKashidas,
rCanvas,
rState, nullptr );
}
EffectTextArrayAction::EffectTextArrayAction( const ::basegfx::B2DPoint& rStartPoint,
const ::basegfx::B2DSize& rReliefOffset,
const ::Color& rReliefColor,
const ::basegfx::B2DSize& rShadowOffset,
const ::Color& rShadowColor,
const ::Color& rTextFillColor,
const OUString& rText,
sal_Int32 nStartPos,
sal_Int32 nLen,
const uno::Sequence< double >& rOffsets,
const uno::Sequence< sal_Bool >& rKashidas,
VirtualDevice const & rVDev,
const CanvasSharedPtr& rCanvas,
const OutDevState& rState,
const ::basegfx::B2DHomMatrix& rTextTransform ) :
mpCanvas( rCanvas ),
maTextLineInfo( tools::createTextLineInfo( rVDev, rState ) ),
maTextLinesHelper(mpCanvas, rState),
maReliefOffset( rReliefOffset ),
maReliefColor( rReliefColor ),
maShadowOffset( rShadowOffset ),
maShadowColor( rShadowColor ),
maTextFillColor( rTextFillColor )
{
initLayoutWidth(mnLayoutWidth, rOffsets);
maTextLinesHelper.init(mnLayoutWidth, maTextLineInfo);
initArrayAction( maState,
mxTextLayout,
rStartPoint,
rText,
nStartPos,
nLen,
rOffsets,
rKashidas,
rCanvas,
rState,
&rTextTransform );
}
css::uno::Reference<css::rendering::XPolyPolygon2D> EffectTextArrayAction::queryTextBounds(const uno::Reference<rendering::XCanvas>& rCanvas) const
{
const geometry::RealRectangle2D aTextBounds(mxTextLayout->queryTextBounds());
auto aB2DBounds = ::basegfx::unotools::b2DRectangleFromRealRectangle2D(aTextBounds);
auto aTextBoundsPoly = ::basegfx::utils::createPolygonFromRect(aB2DBounds);
return ::basegfx::unotools::xPolyPolygonFromB2DPolygon(rCanvas->getDevice(), aTextBoundsPoly);
}
bool EffectTextArrayAction::operator()( const rendering::RenderState& rRenderState, const ::Color& rTextFillColor, bool bNormalText) const
{
const rendering::ViewState aViewState( mpCanvas->getViewState() );
const uno::Reference< rendering::XCanvas > aCanvas( mpCanvas->getUNOCanvas() );
//rhbz#1589029 non-transparent text fill background support
if (rTextFillColor != COL_AUTO)
{
rendering::RenderState aLocalState(rRenderState);
aLocalState.DeviceColor = vcl::unotools::colorToDoubleSequence(
rTextFillColor, aCanvas->getDevice()->getDeviceColorSpace());
auto xTextBounds = queryTextBounds(aCanvas);
// background of text
aCanvas->fillPolyPolygon(xTextBounds, aViewState, aLocalState);
}
// under/over lines
maTextLinesHelper.render(rRenderState, bNormalText);
aCanvas->drawTextLayout( mxTextLayout,
aViewState,
rRenderState );
return true;
}
bool EffectTextArrayAction::render( const ::basegfx::B2DHomMatrix& rTransformation ) const
{
SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::EffectTextArrayAction::render()" );
SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::EffectTextArrayAction: 0x" << std::hex << this );
rendering::RenderState aLocalState( maState );
::canvas::tools::prependToRenderState(aLocalState, rTransformation);
return renderEffectText( *this,
aLocalState,
mpCanvas->getUNOCanvas(),
maShadowColor,
maShadowOffset,
maReliefColor,
maReliefOffset,
maTextFillColor);
}
class EffectTextArrayRenderHelper : public TextRenderer
{
public:
EffectTextArrayRenderHelper( const uno::Reference< rendering::XCanvas >& rCanvas,
const uno::Reference< rendering::XTextLayout >& rTextLayout,
const TextLinesHelper& rTextLinesHelper,
const rendering::ViewState& rViewState ) :
mrCanvas( rCanvas ),
mrTextLayout( rTextLayout ),
mrTextLinesHelper( rTextLinesHelper ),
mrViewState( rViewState )
{
}
// TextRenderer interface
virtual bool operator()( const rendering::RenderState& rRenderState, const ::Color& rTextFillColor,bool bNormalText) const override
{
mrTextLinesHelper.render(rRenderState, bNormalText);
//rhbz#1589029 non-transparent text fill background support
if (rTextFillColor != COL_AUTO)
{
rendering::RenderState aLocalState(rRenderState);
aLocalState.DeviceColor = vcl::unotools::colorToDoubleSequence(
rTextFillColor, mrCanvas->getDevice()->getDeviceColorSpace());
auto xTextBounds = queryTextBounds();
// background of text
mrCanvas->fillPolyPolygon(xTextBounds, mrViewState, aLocalState);
}
mrCanvas->drawTextLayout( mrTextLayout,
mrViewState,
rRenderState );
return true;
}
private:
css::uno::Reference<css::rendering::XPolyPolygon2D> queryTextBounds() const
{
const geometry::RealRectangle2D aTextBounds(mrTextLayout->queryTextBounds());
auto aB2DBounds = ::basegfx::unotools::b2DRectangleFromRealRectangle2D(aTextBounds);
auto aTextBoundsPoly = ::basegfx::utils::createPolygonFromRect(aB2DBounds);
return ::basegfx::unotools::xPolyPolygonFromB2DPolygon(mrCanvas->getDevice(), aTextBoundsPoly);
}
const uno::Reference< rendering::XCanvas >& mrCanvas;
const uno::Reference< rendering::XTextLayout >& mrTextLayout;
const TextLinesHelper& mrTextLinesHelper;
const rendering::ViewState& mrViewState;
};
bool EffectTextArrayAction::renderSubset( const ::basegfx::B2DHomMatrix& rTransformation,
const Subset& rSubset ) const
{
SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::EffectTextArrayAction::renderSubset()" );
SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::EffectTextArrayAction: 0x" << std::hex << 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,
mnLayoutWidth,
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 aViewState( mpCanvas->getViewState() );
TextLinesHelper aHelper = maTextLinesHelper;
aHelper.init(nMaxPos - nMinPos, maTextLineInfo);
// render everything
// =================
return renderEffectText(
EffectTextArrayRenderHelper( xCanvas,
xTextLayout,
aHelper,
aViewState ),
aLocalState,
xCanvas,
maShadowColor,
maShadowOffset,
maReliefColor,
maReliefOffset,
maTextFillColor);
}
::basegfx::B2DRange EffectTextArrayAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const
{
rendering::RenderState aLocalState( maState );
::canvas::tools::prependToRenderState(aLocalState, rTransformation);
::basegfx::B2DSize aSize = maTextLinesHelper.getOverallSize();
return calcEffectTextBounds( ::basegfx::unotools::b2DRectangleFromRealRectangle2D(
mxTextLayout->queryTextBounds() ),
basegfx::B2DRange(0, 0,
aSize.getWidth(),
aSize.getHeight()),
maReliefOffset,
maShadowOffset,
aLocalState,
mpCanvas->getViewState() );
}
::basegfx::B2DRange EffectTextArrayAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation,
const Subset& rSubset ) const
{
SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::EffectTextArrayAction::getBounds( subset )" );
SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::EffectTextArrayAction: 0x" << std::hex << 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,
mnLayoutWidth,
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::utils::getRange( aPoly ),
maReliefOffset,
maShadowOffset,
aLocalState,
mpCanvas->getViewState() );
}
sal_Int32 EffectTextArrayAction::getActionCount() const
{
const rendering::StringContext aOrigContext( mxTextLayout->getText() );
return aOrigContext.Length;
}
class OutlineAction :
public Action,
public TextRenderer
{
public:
OutlineAction( const ::basegfx::B2DPoint& rStartPoint,
const ::basegfx::B2DSize& rReliefOffset,
const ::Color& rReliefColor,
const ::basegfx::B2DSize& rShadowOffset,
const ::Color& rShadowColor,
const ::Color& rFillColor,
uno::Reference< rendering::XPolyPolygon2D > xFillPoly,
const ::basegfx::B2DRectangle& rOutlineBounds,
uno::Reference< rendering::XPolyPolygon2D > xTextPoly,
const uno::Sequence< double >& rOffsets,
VirtualDevice const & 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 ::Color& rFillColor,
uno::Reference< rendering::XPolyPolygon2D > xFillPoly,
const ::basegfx::B2DRectangle& rOutlineBounds,
uno::Reference< rendering::XPolyPolygon2D > xTextPoly,
const uno::Sequence< double >& rOffsets,
VirtualDevice const & rVDev,
const CanvasSharedPtr& rCanvas,
const OutDevState& rState,
const ::basegfx::B2DHomMatrix& rTextTransform );
OutlineAction(const OutlineAction&) = delete;
const OutlineAction& operator=(const OutlineAction&) = delete;
virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation ) const override;
virtual bool renderSubset( const ::basegfx::B2DHomMatrix& rTransformation,
const Subset& rSubset ) const override;
virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const override;
virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation,
const Subset& rSubset ) const override;
virtual sal_Int32 getActionCount() const override;
private:
// TextRenderer interface
virtual bool operator()( const rendering::RenderState& rRenderState, const ::Color& rTextFillColor, bool bNormalText ) const override;
// 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;
const uno::Sequence< double > maOffsets;
const CanvasSharedPtr mpCanvas;
rendering::RenderState maState;
double mnOutlineWidth;
const uno::Sequence< double > maFillColor;
uno::Reference< rendering::XPolyPolygon2D > mxBackgroundFillPoly;
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;
const ::Color maTextFillColor;
const ::Color maBackgroundFillColor;
};
double calcOutlineWidth( const OutDevState& rState,
VirtualDevice const & rVDev )
{
const ::basegfx::B2DSize aFontSize( 0,
rVDev.GetFont().GetFontHeight() / 64.0 );
const double nOutlineWidth(
(rState.mapModeTransform * aFontSize).getHeight() );
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 ::Color& rFillColor,
uno::Reference< rendering::XPolyPolygon2D > xFillPoly,
const ::basegfx::B2DRectangle& rOutlineBounds,
uno::Reference< rendering::XPolyPolygon2D > xTextPoly,
const uno::Sequence< double >& rOffsets,
VirtualDevice const & rVDev,
const CanvasSharedPtr& rCanvas,
const OutDevState& rState ) :
mxTextPoly(std::move( xTextPoly )),
maOffsets( rOffsets ),
mpCanvas( rCanvas ),
mnOutlineWidth( calcOutlineWidth(rState,rVDev) ),
maFillColor(
vcl::unotools::colorToDoubleSequence(
COL_WHITE,
rCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() )),
mxBackgroundFillPoly(std::move( xFillPoly )),
maTextLineInfo( tools::createTextLineInfo( rVDev, rState ) ),
maOutlineBounds( rOutlineBounds ),
maReliefOffset( rReliefOffset ),
maReliefColor( rReliefColor ),
maShadowOffset( rShadowOffset ),
maShadowColor( rShadowColor ),
maBackgroundFillColor( rFillColor )
{
double nLayoutWidth = 0.0;
initLayoutWidth(nLayoutWidth, rOffsets);
initEffectLinePolyPolygon( maLinesOverallSize,
mxTextLines,
rCanvas,
nLayoutWidth,
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 ::Color& rFillColor,
uno::Reference< rendering::XPolyPolygon2D > xFillPoly,
const ::basegfx::B2DRectangle& rOutlineBounds,
uno::Reference< rendering::XPolyPolygon2D > xTextPoly,
const uno::Sequence< double >& rOffsets,
VirtualDevice const & rVDev,
const CanvasSharedPtr& rCanvas,
const OutDevState& rState,
const ::basegfx::B2DHomMatrix& rTextTransform ) :
mxTextPoly(std::move( xTextPoly )),
maOffsets( rOffsets ),
mpCanvas( rCanvas ),
mnOutlineWidth( calcOutlineWidth(rState,rVDev) ),
maFillColor(
vcl::unotools::colorToDoubleSequence(
COL_WHITE,
rCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() )),
mxBackgroundFillPoly(std::move( xFillPoly )),
maTextLineInfo( tools::createTextLineInfo( rVDev, rState ) ),
maOutlineBounds( rOutlineBounds ),
maReliefOffset( rReliefOffset ),
maReliefColor( rReliefColor ),
maShadowOffset( rShadowOffset ),
maShadowColor( rShadowColor ),
maBackgroundFillColor( rFillColor )
{
double nLayoutWidth = 0.0;
initLayoutWidth(nLayoutWidth, rOffsets);
initEffectLinePolyPolygon( maLinesOverallSize,
mxTextLines,
rCanvas,
nLayoutWidth,
maTextLineInfo );
init( maState,
rStartPoint,
rState,
rCanvas,
rTextTransform );
}
bool OutlineAction::operator()( const rendering::RenderState& rRenderState, const ::Color& /*rTextFillColor*/, bool /*bNormalText*/ ) const
{
const rendering::ViewState aViewState( mpCanvas->getViewState() );
const uno::Reference< rendering::XCanvas > xCanvas( mpCanvas->getUNOCanvas() );
if (mxBackgroundFillPoly.is())
{
rendering::RenderState aLocalState( rRenderState );
aLocalState.DeviceColor = vcl::unotools::colorToDoubleSequence(
maBackgroundFillColor, xCanvas->getDevice()->getDeviceColorSpace());
xCanvas->fillPolyPolygon(mxBackgroundFillPoly, aViewState, aLocalState);
}
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
xCanvas->fillPolyPolygon( mxTextPoly,
aViewState,
aLocalState );
// border line of text
xCanvas->strokePolyPolygon( mxTextPoly,
aViewState,
rRenderState,
aStrokeAttributes );
// underlines/strikethrough - background
xCanvas->fillPolyPolygon( mxTextLines,
aViewState,
aLocalState );
// underlines/strikethrough - border
xCanvas->strokePolyPolygon( mxTextLines,
aViewState,
rRenderState,
aStrokeAttributes );
return true;
}
bool OutlineAction::render( const ::basegfx::B2DHomMatrix& rTransformation ) const
{
SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::EffectTextArrayAction::render()" );
SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::EffectTextArrayAction: 0x" << std::hex << this );
rendering::RenderState aLocalState( maState );
::canvas::tools::prependToRenderState(aLocalState, rTransformation);
return renderEffectText( *this,
aLocalState,
mpCanvas->getUNOCanvas(),
maShadowColor,
maShadowOffset,
maReliefColor,
maReliefOffset,
maTextFillColor);
}
#if 0 // see #if'ed out use in OutlineAction::renderSubset below:
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(
::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;
};
#endif
bool OutlineAction::renderSubset( const ::basegfx::B2DHomMatrix& rTransformation,
const Subset& rSubset ) const
{
SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::OutlineAction::renderSubset()" );
SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::OutlineAction: 0x" << std::hex << 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.getWidth(),
maLinesOverallSize.getHeight()),
maReliefOffset,
maShadowOffset,
aLocalState,
mpCanvas->getViewState() );
}
::basegfx::B2DRange OutlineAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation,
const Subset& /*rSubset*/ ) const
{
SAL_WARN( "cppcanvas.emf", "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.
*/
std::shared_ptr<Action> createOutline( const ::basegfx::B2DPoint& rStartPoint,
const ::basegfx::B2DSize& rReliefOffset,
const ::Color& rReliefColor,
const ::basegfx::B2DSize& rShadowOffset,
const ::Color& rShadowColor,
const ::Color& rTextFillColor,
const OUString& rText,
sal_Int32 nStartPos,
sal_Int32 nLen,
KernArraySpan pDXArray,
std::span<const sal_Bool> pKashidaArray,
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 vcl::Font aOrigFont( rVDev.GetFont() );
vcl::Font aUnrotatedFont( aOrigFont );
aUnrotatedFont.SetOrientation(0_deg10);
rVDev.SetFont( aUnrotatedFont );
// TODO(F3): Don't understand parameter semantics of
// GetTextOutlines()
::basegfx::B2DPolyPolygon aResultingPolyPolygon;
PolyPolyVector aVCLPolyPolyVector;
const bool bHaveOutlines( rVDev.GetTextOutlines( aVCLPolyPolyVector, rText,
static_cast<sal_uInt16>(nStartPos),
static_cast<sal_uInt16>(nStartPos),
static_cast<sal_uInt16>(nLen),
0, pDXArray, pKashidaArray ) );
rVDev.SetFont(aOrigFont);
if( !bHaveOutlines )
return std::shared_ptr<Action>();
// 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);
for( const auto& rVCLPolyPolygon : aVCLPolyPolyVector )
{
::basegfx::B2DPolyPolygon aPolyPolygon = rVCLPolyPolygon.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 );
}
}
}
const uno::Sequence< double > aCharWidthSeq(
!pDXArray.empty() ?
setupDXArray( pDXArray, nLen, rState ) :
setupDXArray( rText,
nStartPos,
nLen,
rVDev,
rState ));
const uno::Reference< rendering::XPolyPolygon2D > xTextPoly(
::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
rCanvas->getUNOCanvas()->getDevice(),
aResultingPolyPolygon ) );
// create background color fill polygon?
css::uno::Reference<css::rendering::XPolyPolygon2D> xTextBoundsPoly;
if (rTextFillColor != COL_AUTO)
{
rendering::StringContext aStringContext( rText, nStartPos, nLen );
uno::Reference< rendering::XTextLayout > xTextLayout(
rState.xFont->createTextLayout(
aStringContext,
rState.textDirection,
0 ) );
auto aTextBounds = xTextLayout->queryTextBounds();
auto aB2DBounds = ::basegfx::unotools::b2DRectangleFromRealRectangle2D(aTextBounds);
auto aTextBoundsPoly = ::basegfx::utils::createPolygonFromRect(aB2DBounds);
xTextBoundsPoly = ::basegfx::unotools::xPolyPolygonFromB2DPolygon(
rCanvas->getUNOCanvas()->getDevice(),
aTextBoundsPoly);
}
if( rParms.maTextTransformation )
{
return std::make_shared<OutlineAction>(
rStartPoint,
rReliefOffset,
rReliefColor,
rShadowOffset,
rShadowColor,
rTextFillColor,
xTextBoundsPoly,
::basegfx::utils::getRange(aResultingPolyPolygon),
xTextPoly,
aCharWidthSeq,
rVDev,
rCanvas,
rState,
*rParms.maTextTransformation );
}
else
{
return std::make_shared<OutlineAction>(
rStartPoint,
rReliefOffset,
rReliefColor,
rShadowOffset,
rShadowColor,
rTextFillColor,
xTextBoundsPoly,
::basegfx::utils::getRange(aResultingPolyPolygon),
xTextPoly,
aCharWidthSeq,
rVDev,
rCanvas,
rState );
}
}
} // namespace
std::shared_ptr<Action> TextActionFactory::createTextAction( const ::Point& rStartPoint,
const ::Size& rReliefOffset,
const ::Color& rReliefColor,
const ::Size& rShadowOffset,
const ::Color& rShadowColor,
const ::Color& rTextFillColor,
const OUString& rText,
sal_Int32 nStartPos,
sal_Int32 nLen,
KernArraySpan pDXArray,
std::span<const sal_Bool> pKashidaArray,
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,
rTextFillColor,
rText,
nStartPos,
nLen,
pDXArray,
pKashidaArray,
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.empty() ?
setupDXArray( pDXArray, nLen, rState ) :
setupDXArray( rText,
nStartPos,
nLen,
rVDev,
rState ));
const uno::Sequence< sal_Bool > aKashidas(pKashidaArray.data(), pKashidaArray.size());
// determine type of text action to create
// =======================================
const ::Color aEmptyColor( COL_AUTO );
std::shared_ptr<Action> ret;
// no DX array, and no need to subset - no need to store
// DX array, then.
if( pDXArray.empty() && !bSubsettable )
{
// effects, or not?
if( !rState.textOverlineStyle &&
!rState.textUnderlineStyle &&
!rState.textStrikeoutStyle &&
rReliefColor == aEmptyColor &&
rShadowColor == aEmptyColor &&
rTextFillColor == aEmptyColor )
{
// nope
if( rParms.maTextTransformation )
{
ret = std::make_shared<TextAction>(
aStartPoint,
rText,
nStartPos,
nLen,
rCanvas,
rState,
*rParms.maTextTransformation );
}
else
{
ret = std::make_shared<TextAction>(
aStartPoint,
rText,
nStartPos,
nLen,
rCanvas,
rState );
}
}
else
{
// at least one of the effects requested
if( rParms.maTextTransformation )
ret = std::make_shared<EffectTextAction>(
aStartPoint,
aReliefOffset,
rReliefColor,
aShadowOffset,
rShadowColor,
rTextFillColor,
rText,
nStartPos,
nLen,
rVDev,
rCanvas,
rState,
*rParms.maTextTransformation );
else
ret = std::make_shared<EffectTextAction>(
aStartPoint,
aReliefOffset,
rReliefColor,
aShadowOffset,
rShadowColor,
rTextFillColor,
rText,
nStartPos,
nLen,
rVDev,
rCanvas,
rState );
}
}
else
{
// DX array necessary - any effects?
if( !rState.textOverlineStyle &&
!rState.textUnderlineStyle &&
!rState.textStrikeoutStyle &&
rReliefColor == aEmptyColor &&
rShadowColor == aEmptyColor &&
rTextFillColor == aEmptyColor )
{
// nope
if( rParms.maTextTransformation )
ret = std::make_shared<TextArrayAction>(
aStartPoint,
rText,
nStartPos,
nLen,
aCharWidths,
aKashidas,
rCanvas,
rState,
*rParms.maTextTransformation );
else
ret = std::make_shared<TextArrayAction>(
aStartPoint,
rText,
nStartPos,
nLen,
aCharWidths,
aKashidas,
rCanvas,
rState );
}
else
{
// at least one of the effects requested
if( rParms.maTextTransformation )
ret = std::make_shared<EffectTextArrayAction>(
aStartPoint,
aReliefOffset,
rReliefColor,
aShadowOffset,
rShadowColor,
rTextFillColor,
rText,
nStartPos,
nLen,
aCharWidths,
aKashidas,
rVDev,
rCanvas,
rState,
*rParms.maTextTransformation );
else
ret = std::make_shared<EffectTextArrayAction>(
aStartPoint,
aReliefOffset,
rReliefColor,
aShadowOffset,
rShadowColor,
rTextFillColor,
rText,
nStartPos,
nLen,
aCharWidths,
aKashidas,
rVDev,
rCanvas,
rState );
}
}
return ret;
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V547 Expression is always false.