diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /cppcanvas/source/mtfrenderer/textaction.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-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.cxx | 2323 |
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: */ |