summaryrefslogtreecommitdiffstats
path: root/cppcanvas/source/mtfrenderer/textaction.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /cppcanvas/source/mtfrenderer/textaction.cxx
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'cppcanvas/source/mtfrenderer/textaction.cxx')
-rw-r--r--cppcanvas/source/mtfrenderer/textaction.cxx2323
1 files changed, 2323 insertions, 0 deletions
diff --git a/cppcanvas/source/mtfrenderer/textaction.cxx b/cppcanvas/source/mtfrenderer/textaction.cxx
new file mode 100644
index 0000000000..2f2148c44d
--- /dev/null
+++ b/cppcanvas/source/mtfrenderer/textaction.cxx
@@ -0,0 +1,2323 @@
+/* -*- 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& 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,
+ 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 = 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& rViewState( mpCanvas->getViewState() );
+ const uno::Reference< rendering::XCanvas >& rCanvas( mpCanvas->getUNOCanvas() );
+
+ //rhbz#1589029 non-transparent text fill background support
+ if (rTextFillColor != COL_AUTO)
+ {
+ rendering::RenderState aLocalState( rRenderState );
+ aLocalState.DeviceColor = vcl::unotools::colorToDoubleSequence(
+ rTextFillColor, rCanvas->getDevice()->getDeviceColorSpace());
+ auto xTextBounds = queryTextBounds(rCanvas);
+ // background of text
+ rCanvas->fillPolyPolygon(xTextBounds, rViewState, aLocalState);
+ }
+
+ // under/over lines
+ rCanvas->fillPolyPolygon( mxTextLines,
+ rViewState,
+ rRenderState );
+
+ rCanvas->drawText( maStringContext, mxFont,
+ rViewState,
+ 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& rOrigContext( mxTextLayout->getText() );
+
+ return rOrigContext.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& rViewState( mpCanvas->getViewState() );
+ const uno::Reference< rendering::XCanvas >& rCanvas( mpCanvas->getUNOCanvas() );
+
+ //rhbz#1589029 non-transparent text fill background support
+ if (rTextFillColor != COL_AUTO)
+ {
+ rendering::RenderState aLocalState(rRenderState);
+ aLocalState.DeviceColor = vcl::unotools::colorToDoubleSequence(
+ rTextFillColor, rCanvas->getDevice()->getDeviceColorSpace());
+ auto xTextBounds = queryTextBounds(rCanvas);
+ // background of text
+ rCanvas->fillPolyPolygon(xTextBounds, rViewState, aLocalState);
+ }
+
+ // under/over lines
+ maTextLinesHelper.render(rRenderState, bNormalText);
+
+ rCanvas->drawTextLayout( mxTextLayout,
+ rViewState,
+ 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& rViewState( mpCanvas->getViewState() );
+
+ TextLinesHelper aHelper = maTextLinesHelper;
+ aHelper.init(nMaxPos - nMinPos, maTextLineInfo);
+
+
+ // render everything
+ // =================
+
+ return renderEffectText(
+ EffectTextArrayRenderHelper( xCanvas,
+ xTextLayout,
+ aHelper,
+ rViewState ),
+ 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& rOrigContext( mxTextLayout->getText() );
+
+ return rOrigContext.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 ::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 ::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;
+ 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;
+ };
+
+ 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 ::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() )),
+ maTextLineInfo( tools::createTextLineInfo( rVDev, rState ) ),
+ maOutlineBounds( rOutlineBounds ),
+ maReliefOffset( rReliefOffset ),
+ maReliefColor( rReliefColor ),
+ maShadowOffset( rShadowOffset ),
+ maShadowColor( rShadowColor )
+ {
+ 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 ::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() )),
+ maTextLineInfo( tools::createTextLineInfo( rVDev, rState ) ),
+ maOutlineBounds( rOutlineBounds ),
+ maReliefOffset( rReliefOffset ),
+ maReliefColor( rReliefColor ),
+ maShadowOffset( rShadowOffset ),
+ maShadowColor( rShadowColor )
+ {
+ 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& 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
+ {
+ 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 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 ) );
+
+ if( rParms.maTextTransformation )
+ {
+ return std::make_shared<OutlineAction>(
+ rStartPoint,
+ rReliefOffset,
+ rReliefColor,
+ rShadowOffset,
+ rShadowColor,
+ ::basegfx::utils::getRange(aResultingPolyPolygon),
+ xTextPoly,
+ aCharWidthSeq,
+ rVDev,
+ rCanvas,
+ rState,
+ *rParms.maTextTransformation );
+ }
+ else
+ {
+ return std::make_shared<OutlineAction>(
+ rStartPoint,
+ rReliefOffset,
+ rReliefColor,
+ rShadowOffset,
+ rShadowColor,
+ ::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,
+ 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: */