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 | |
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')
19 files changed, 8629 insertions, 0 deletions
diff --git a/cppcanvas/source/mtfrenderer/bitmapaction.cxx b/cppcanvas/source/mtfrenderer/bitmapaction.cxx new file mode 100644 index 0000000000..24e1f46fae --- /dev/null +++ b/cppcanvas/source/mtfrenderer/bitmapaction.cxx @@ -0,0 +1,217 @@ +/* -*- 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 <com/sun/star/rendering/XBitmap.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/XCachedPrimitive.hpp> +#include <vcl/bitmapex.hxx> +#include <tools/gen.hxx> +#include <vcl/canvastools.hxx> +#include <canvas/canvastools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/range/b2drange.hxx> +#include <sal/log.hxx> +#include "cachedprimitivebase.hxx" +#include "bitmapaction.hxx" +#include <outdevstate.hxx> +#include "mtftools.hxx" +#include <basegfx/matrix/b2dhommatrixtools.hxx> + + +using namespace ::com::sun::star; + +namespace cppcanvas::internal +{ + namespace + { + + class BitmapAction : public CachedPrimitiveBase + { + public: + BitmapAction( const ::BitmapEx&, + const ::basegfx::B2DPoint& rDstPoint, + const CanvasSharedPtr&, + const OutDevState& ); + BitmapAction( const ::BitmapEx&, + const ::basegfx::B2DPoint& rDstPoint, + const ::basegfx::B2DVector& rDstSize, + const CanvasSharedPtr&, + const OutDevState& ); + + 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: + using Action::render; + virtual bool renderPrimitive( uno::Reference< rendering::XCachedPrimitive >& rCachedPrimitive, + const ::basegfx::B2DHomMatrix& rTransformation ) const override; + + uno::Reference< rendering::XBitmap > mxBitmap; + CanvasSharedPtr mpCanvas; + rendering::RenderState maState; + }; + + + BitmapAction::BitmapAction( const ::BitmapEx& rBmpEx, + const ::basegfx::B2DPoint& rDstPoint, + const CanvasSharedPtr& rCanvas, + const OutDevState& rState ) : + CachedPrimitiveBase( rCanvas, true ), + mxBitmap( vcl::unotools::xBitmapFromBitmapEx( rBmpEx ) ), + mpCanvas( rCanvas ) + { + tools::initRenderState(maState,rState); + + // Setup transformation such that the next render call is + // moved rPoint away. + const basegfx::B2DHomMatrix aLocalTransformation(basegfx::utils::createTranslateB2DHomMatrix(rDstPoint)); + ::canvas::tools::appendToRenderState( maState, + aLocalTransformation ); + + // correct clip (which is relative to original transform) + tools::modifyClip( maState, + rState, + rCanvas, + rDstPoint, + nullptr, + nullptr ); + } + + BitmapAction::BitmapAction( const ::BitmapEx& rBmpEx, + const ::basegfx::B2DPoint& rDstPoint, + const ::basegfx::B2DVector& rDstSize, + const CanvasSharedPtr& rCanvas, + const OutDevState& rState ) : + CachedPrimitiveBase( rCanvas, true ), + mxBitmap( vcl::unotools::xBitmapFromBitmapEx( rBmpEx ) ), + mpCanvas( rCanvas ) + { + tools::initRenderState(maState,rState); + + // Setup transformation such that the next render call is + // moved rPoint away, and scaled according to the ratio + // given by src and dst size. + const ::Size aBmpSize( rBmpEx.GetSizePixel() ); + + const ::basegfx::B2DVector aScale( rDstSize.getX() / aBmpSize.Width(), + rDstSize.getY() / aBmpSize.Height() ); + const basegfx::B2DHomMatrix aLocalTransformation(basegfx::utils::createScaleTranslateB2DHomMatrix( + aScale, rDstPoint)); + ::canvas::tools::appendToRenderState( maState, aLocalTransformation ); + + // correct clip (which is relative to original transform) + tools::modifyClip( maState, + rState, + rCanvas, + rDstPoint, + &aScale, + nullptr ); + } + + bool BitmapAction::renderPrimitive( uno::Reference< rendering::XCachedPrimitive >& rCachedPrimitive, + const ::basegfx::B2DHomMatrix& rTransformation ) const + { + SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::BitmapAction::renderPrimitive()" ); + SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::BitmapAction: 0x" << std::hex << this ); + + rendering::RenderState aLocalState( maState ); + ::canvas::tools::prependToRenderState(aLocalState, rTransformation); + + rCachedPrimitive = mpCanvas->getUNOCanvas()->drawBitmap( mxBitmap, + mpCanvas->getViewState(), + aLocalState ); + + return true; + } + + bool BitmapAction::renderSubset( const ::basegfx::B2DHomMatrix& rTransformation, + const Subset& rSubset ) const + { + // bitmap only contains a single action, fail if subset + // requests different range + if( rSubset.mnSubsetBegin != 0 || + rSubset.mnSubsetEnd != 1 ) + return false; + + return CachedPrimitiveBase::render( rTransformation ); + } + + ::basegfx::B2DRange BitmapAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const + { + rendering::RenderState aLocalState( maState ); + ::canvas::tools::prependToRenderState(aLocalState, rTransformation); + + const geometry::IntegerSize2D aSize( mxBitmap->getSize() ); + + return tools::calcDevicePixelBounds( ::basegfx::B2DRange( 0,0, + aSize.Width, + aSize.Height ), + mpCanvas->getViewState(), + aLocalState ); + } + + ::basegfx::B2DRange BitmapAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation, + const Subset& rSubset ) const + { + // bitmap only contains a single action, empty bounds + // if subset requests different range + if( rSubset.mnSubsetBegin != 0 || + rSubset.mnSubsetEnd != 1 ) + return ::basegfx::B2DRange(); + + return getBounds( rTransformation ); + } + + sal_Int32 BitmapAction::getActionCount() const + { + return 1; + } + } + + std::shared_ptr<Action> BitmapActionFactory::createBitmapAction( const ::BitmapEx& rBmpEx, + const ::basegfx::B2DPoint& rDstPoint, + const CanvasSharedPtr& rCanvas, + const OutDevState& rState ) + { + return std::make_shared<BitmapAction>(rBmpEx, rDstPoint, rCanvas, rState ); + } + + std::shared_ptr<Action> BitmapActionFactory::createBitmapAction( const ::BitmapEx& rBmpEx, + const ::basegfx::B2DPoint& rDstPoint, + const ::basegfx::B2DVector& rDstSize, + const CanvasSharedPtr& rCanvas, + const OutDevState& rState ) + { + return std::make_shared<BitmapAction>(rBmpEx, + rDstPoint, + rDstSize, + rCanvas, + rState ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cppcanvas/source/mtfrenderer/bitmapaction.hxx b/cppcanvas/source/mtfrenderer/bitmapaction.hxx new file mode 100644 index 0000000000..59bf841dab --- /dev/null +++ b/cppcanvas/source/mtfrenderer/bitmapaction.hxx @@ -0,0 +1,61 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppcanvas/canvas.hxx> +#include <action.hxx> + +namespace basegfx { + class B2DPoint; + class B2DVector; +} +class BitmapEx; + +/* Definition of internal::BitmapActionFactory */ + +namespace cppcanvas::internal +{ + struct OutDevState; + + /** Creates encapsulated converters between GDIMetaFile and + XCanvas. The Canvas argument is deliberately placed at the + constructor, to force reconstruction of this object for a + new canvas. This considerably eases internal state + handling, since a lot of the internal state (e.g. fonts, + text layout) is Canvas-dependent. + */ + namespace BitmapActionFactory + { + /// Unscaled bitmap action, only references destination point + std::shared_ptr<Action> createBitmapAction( const ::BitmapEx&, + const ::basegfx::B2DPoint& rDstPoint, + const CanvasSharedPtr&, + const OutDevState& ); + + /// Scaled bitmap action, dest point and dest size + std::shared_ptr<Action> createBitmapAction( const ::BitmapEx&, + const ::basegfx::B2DPoint& rDstPoint, + const ::basegfx::B2DVector& rDstSize, + const CanvasSharedPtr&, + const OutDevState& ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cppcanvas/source/mtfrenderer/cachedprimitivebase.cxx b/cppcanvas/source/mtfrenderer/cachedprimitivebase.cxx new file mode 100644 index 0000000000..e5664cabfc --- /dev/null +++ b/cppcanvas/source/mtfrenderer/cachedprimitivebase.cxx @@ -0,0 +1,80 @@ +/* -*- 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 <com/sun/star/rendering/RepaintResult.hpp> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <canvas/canvastools.hxx> +#include <cppcanvas/canvas.hxx> + +#include "cachedprimitivebase.hxx" +#include <sal/log.hxx> +#include <utility> + +using namespace ::com::sun::star; + +namespace cppcanvas::internal +{ + CachedPrimitiveBase::CachedPrimitiveBase( CanvasSharedPtr xCanvas, + bool bOnlyRedrawWithSameTransform ) : + mpCanvas(std::move( xCanvas )), + mbOnlyRedrawWithSameTransform( bOnlyRedrawWithSameTransform ) + { + // TODO(F2): also store last view transform, and refuse to + // redraw if changed. + } + + bool CachedPrimitiveBase::render( const ::basegfx::B2DHomMatrix& rTransformation ) const + { + SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::CachedPrimitiveBase::render()" ); + SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::CachedPrimitiveBase: 0x" << std::hex << this ); + + const rendering::ViewState& rViewState( mpCanvas->getViewState() ); + ::basegfx::B2DHomMatrix aTotalTransform; + + ::canvas::tools::getViewStateTransform( aTotalTransform, + rViewState ); + aTotalTransform *= rTransformation; + + // can we use the cached primitive? For that, it must be + // present in the first place, and, if + // mbOnlyRedrawWithSameTransform is true, the overall + // transformation must be the same. + if( mxCachedPrimitive.is() && + (!mbOnlyRedrawWithSameTransform || + maLastTransformation == aTotalTransform) ) + { + if( mxCachedPrimitive->redraw( rViewState ) == + rendering::RepaintResult::REDRAWN ) + { + // cached repaint succeeded, done. + return true; + } + } + + maLastTransformation = aTotalTransform; + + // delegate rendering to derived classes + return renderPrimitive( mxCachedPrimitive, + rTransformation ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cppcanvas/source/mtfrenderer/cachedprimitivebase.hxx b/cppcanvas/source/mtfrenderer/cachedprimitivebase.hxx new file mode 100644 index 0000000000..48a31db5d2 --- /dev/null +++ b/cppcanvas/source/mtfrenderer/cachedprimitivebase.hxx @@ -0,0 +1,83 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/rendering/XCachedPrimitive.hpp> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <cppcanvas/canvas.hxx> + +#include <action.hxx> + +namespace basegfx { class B2DHomMatrix; } + + +/* Definition of internal::CachedPrimitiveBase class */ + +namespace cppcanvas::internal + { + /** Base class providing cached re-rendering, if XCanvas + returns XCachedPrimitive + + Derive from this class and implement private render() + method to perform the actual primitive rendering. Return + cached primitive into given reference. Next time this + class' public render() method gets called, the cached + representation is taken. + */ + class CachedPrimitiveBase : public Action + { + public: + /** Constructor + + @param rCanvas + Canvas on which this primitive is to appear + + @param bOnlyRedrawWithSameTransform + When true, this class only reuses the cached + primitive, if the overall transformation stays the + same. Otherwise, repaints are always performed via the + cached primitive. + */ + CachedPrimitiveBase( CanvasSharedPtr xCanvas, + bool bOnlyRedrawWithSameTransform ); + + CachedPrimitiveBase(const CachedPrimitiveBase&) = delete; + const CachedPrimitiveBase& operator=(const CachedPrimitiveBase&) = delete; + + virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation ) const override; + + protected: + using Action::render; + + private: + virtual bool renderPrimitive( css::uno::Reference< css::rendering::XCachedPrimitive >& rCachedPrimitive, + const ::basegfx::B2DHomMatrix& rTransformation ) const = 0; + + CanvasSharedPtr mpCanvas; + mutable css::uno::Reference< css::rendering::XCachedPrimitive > mxCachedPrimitive; + mutable ::basegfx::B2DHomMatrix maLastTransformation; + const bool mbOnlyRedrawWithSameTransform; + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cppcanvas/source/mtfrenderer/implrenderer.cxx b/cppcanvas/source/mtfrenderer/implrenderer.cxx new file mode 100644 index 0000000000..d3cfe793f4 --- /dev/null +++ b/cppcanvas/source/mtfrenderer/implrenderer.cxx @@ -0,0 +1,3066 @@ +/* -*- 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 <tools/debug.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/propertyvalue.hxx> +#include <cppcanvas/canvas.hxx> +#include <com/sun/star/rendering/XGraphicDevice.hpp> +#include <com/sun/star/rendering/TexturingMode.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/rendering/PanoseProportion.hpp> +#include <com/sun/star/rendering/XCanvasFont.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/PathCapType.hpp> +#include <com/sun/star/rendering/PathJoinType.hpp> +#include <basegfx/utils/canvastools.hxx> +#include <basegfx/utils/gradienttools.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/vector/b2dsize.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/tuple/b2dtuple.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <canvas/canvastools.hxx> +#include <rtl/ustrbuf.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/metaact.hxx> +#include <vcl/virdev.hxx> +#include <vcl/metric.hxx> +#include <vcl/graphictools.hxx> +#include <vcl/BitmapPalette.hxx> +#include <tools/poly.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <implrenderer.hxx> +#include <tools.hxx> +#include <outdevstate.hxx> +#include <action.hxx> +#include <sal/log.hxx> +#include "bitmapaction.hxx" +#include "lineaction.hxx" +#include "pointaction.hxx" +#include "polypolyaction.hxx" +#include "textaction.hxx" +#include "transparencygroupaction.hxx" +#include <vector> +#include <algorithm> +#include <memory> +#include <string_view> +#include "mtftools.hxx" + +using namespace ::com::sun::star; + + +// free support functions +// ====================== +namespace +{ + template < class MetaActionType > void setStateColor( MetaActionType* pAct, + bool& rIsColorSet, + uno::Sequence< double >& rColorSequence, + const cppcanvas::CanvasSharedPtr& rCanvas ) + { + rIsColorSet = pAct->IsSetting(); + if (!rIsColorSet) + return; + + ::Color aColor( pAct->GetColor() ); + + // force alpha part of color to + // opaque. transparent painting is done + // explicitly via MetaActionType::Transparent + aColor.SetAlpha(255); + //aColor.SetTransparency(128); + + rColorSequence = vcl::unotools::colorToDoubleSequence( + aColor, + rCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() ); + } + + void setupStrokeAttributes( rendering::StrokeAttributes& o_rStrokeAttributes, + const ::cppcanvas::internal::ActionFactoryParameters& rParms, + const LineInfo& rLineInfo ) + { + const ::basegfx::B2DSize aWidth( rLineInfo.GetWidth(), 0 ); + o_rStrokeAttributes.StrokeWidth = + (rParms.mrStates.getState().mapModeTransform * aWidth).getLength(); + + // setup reasonable defaults + o_rStrokeAttributes.MiterLimit = 15.0; // 1.0 was no good default; GDI+'s limit is 10.0, our's is 15.0 + o_rStrokeAttributes.StartCapType = rendering::PathCapType::BUTT; + o_rStrokeAttributes.EndCapType = rendering::PathCapType::BUTT; + + switch (rLineInfo.GetLineJoin()) + { + case basegfx::B2DLineJoin::NONE: + o_rStrokeAttributes.JoinType = rendering::PathJoinType::NONE; + break; + case basegfx::B2DLineJoin::Bevel: + o_rStrokeAttributes.JoinType = rendering::PathJoinType::BEVEL; + break; + case basegfx::B2DLineJoin::Miter: + o_rStrokeAttributes.JoinType = rendering::PathJoinType::MITER; + break; + case basegfx::B2DLineJoin::Round: + o_rStrokeAttributes.JoinType = rendering::PathJoinType::ROUND; + break; + } + + switch(rLineInfo.GetLineCap()) + { + default: /* css::drawing::LineCap_BUTT */ + { + o_rStrokeAttributes.StartCapType = rendering::PathCapType::BUTT; + o_rStrokeAttributes.EndCapType = rendering::PathCapType::BUTT; + break; + } + case css::drawing::LineCap_ROUND: + { + o_rStrokeAttributes.StartCapType = rendering::PathCapType::ROUND; + o_rStrokeAttributes.EndCapType = rendering::PathCapType::ROUND; + break; + } + case css::drawing::LineCap_SQUARE: + { + o_rStrokeAttributes.StartCapType = rendering::PathCapType::SQUARE; + o_rStrokeAttributes.EndCapType = rendering::PathCapType::SQUARE; + break; + } + } + + if( LineStyle::Dash != rLineInfo.GetStyle() ) + return; + + const ::cppcanvas::internal::OutDevState& rState( rParms.mrStates.getState() ); + + // TODO(F1): Interpret OutDev::GetRefPoint() for the start of the dashing. + + // interpret dash info only if explicitly enabled as + // style + const ::basegfx::B2DSize aDistance( rLineInfo.GetDistance(), 0 ); + const double nDistance( (rState.mapModeTransform * aDistance).getLength() ); + + const ::basegfx::B2DSize aDashLen( rLineInfo.GetDashLen(), 0 ); + const double nDashLen( (rState.mapModeTransform * aDashLen).getLength() ); + + const ::basegfx::B2DSize aDotLen( rLineInfo.GetDotLen(), 0 ); + const double nDotLen( (rState.mapModeTransform * aDotLen).getLength() ); + + const sal_Int32 nNumArryEntries( 2*rLineInfo.GetDashCount() + + 2*rLineInfo.GetDotCount() ); + + o_rStrokeAttributes.DashArray.realloc( nNumArryEntries ); + double* pDashArray = o_rStrokeAttributes.DashArray.getArray(); + + + // iteratively fill dash array, first with dashes, then + // with dots. + + + sal_Int32 nCurrEntry=0; + + for( sal_Int32 i=0; i<rLineInfo.GetDashCount(); ++i ) + { + pDashArray[nCurrEntry++] = nDashLen; + pDashArray[nCurrEntry++] = nDistance; + } + for( sal_Int32 i=0; i<rLineInfo.GetDotCount(); ++i ) + { + pDashArray[nCurrEntry++] = nDotLen; + pDashArray[nCurrEntry++] = nDistance; + } + } + + + /** Create masked BitmapEx, where the white areas of rBitmap are + transparent, and the other appear in rMaskColor. + */ + BitmapEx createMaskBmpEx( const Bitmap& rBitmap, + const ::Color& rMaskColor ) + { + const ::Color aWhite( COL_WHITE ); + BitmapPalette aBiLevelPalette{ + aWhite, rMaskColor + }; + + AlphaMask aMask( rBitmap.CreateAlphaMask( aWhite )); + Bitmap aSolid( rBitmap.GetSizePixel(), + vcl::PixelFormat::N8_BPP, + &aBiLevelPalette ); + aSolid.Erase( rMaskColor ); + + return BitmapEx( aSolid, aMask ); + } + + OUString convertToLocalizedNumerals(std::u16string_view rStr, + LanguageType eTextLanguage) + { + OUStringBuffer aBuf(rStr); + for (sal_Int32 i = 0; i < aBuf.getLength(); ++i) + { + sal_Unicode nChar = aBuf[i]; + if (nChar >= '0' && nChar <= '9') + aBuf[i] = GetLocalizedChar(nChar, eTextLanguage); + } + return aBuf.makeStringAndClear(); + } +} + +namespace cppcanvas::internal +{ + // state stack manipulators + + void VectorOfOutDevStates::clearStateStack() + { + m_aStates.clear(); + const OutDevState aDefaultState; + m_aStates.push_back(aDefaultState); + } + + OutDevState& VectorOfOutDevStates::getState() + { + return m_aStates.back(); + } + + const OutDevState& VectorOfOutDevStates::getState() const + { + return m_aStates.back(); + } + + void VectorOfOutDevStates::pushState(vcl::PushFlags nFlags) + { + m_aStates.push_back( getState() ); + getState().pushFlags = nFlags; + } + + void VectorOfOutDevStates::popState() + { + if( getState().pushFlags != vcl::PushFlags::ALL ) + { + // a state is pushed which is incomplete, i.e. does not + // restore everything to the previous stack level when + // popped. + // That means, we take the old state, and restore every + // OutDevState member whose flag is set, from the new to the + // old state. Then the new state gets overwritten by the + // calculated state + + // preset to-be-calculated new state with old state + OutDevState aCalculatedNewState( getState() ); + + // selectively copy to-be-restored content over saved old + // state + m_aStates.pop_back(); + + const OutDevState& rNewState( getState() ); + + if( aCalculatedNewState.pushFlags & vcl::PushFlags::LINECOLOR ) + { + aCalculatedNewState.lineColor = rNewState.lineColor; + aCalculatedNewState.isLineColorSet = rNewState.isLineColorSet; + } + + if( aCalculatedNewState.pushFlags & vcl::PushFlags::FILLCOLOR ) + { + aCalculatedNewState.fillColor = rNewState.fillColor; + aCalculatedNewState.isFillColorSet = rNewState.isFillColorSet; + } + + if( aCalculatedNewState.pushFlags & vcl::PushFlags::FONT ) + { + aCalculatedNewState.xFont = rNewState.xFont; + aCalculatedNewState.fontRotation = rNewState.fontRotation; + aCalculatedNewState.textReliefStyle = rNewState.textReliefStyle; + aCalculatedNewState.textOverlineStyle = rNewState.textOverlineStyle; + aCalculatedNewState.textUnderlineStyle = rNewState.textUnderlineStyle; + aCalculatedNewState.textStrikeoutStyle = rNewState.textStrikeoutStyle; + aCalculatedNewState.textEmphasisMark = rNewState.textEmphasisMark; + aCalculatedNewState.isTextEffectShadowSet = rNewState.isTextEffectShadowSet; + aCalculatedNewState.isTextWordUnderlineSet = rNewState.isTextWordUnderlineSet; + aCalculatedNewState.isTextOutlineModeSet = rNewState.isTextOutlineModeSet; + } + + if( aCalculatedNewState.pushFlags & vcl::PushFlags::TEXTCOLOR ) + { + aCalculatedNewState.textColor = rNewState.textColor; + } + + if( aCalculatedNewState.pushFlags & vcl::PushFlags::MAPMODE ) + { + aCalculatedNewState.mapModeTransform = rNewState.mapModeTransform; + } + + if( aCalculatedNewState.pushFlags & vcl::PushFlags::CLIPREGION ) + { + aCalculatedNewState.clip = rNewState.clip; + aCalculatedNewState.clipRect = rNewState.clipRect; + aCalculatedNewState.xClipPoly = rNewState.xClipPoly; + } + + // TODO(F2): Raster ops NYI + // if( (aCalculatedNewState.pushFlags & vcl::PushFlags::RASTEROP) ) + // { + // } + + if( aCalculatedNewState.pushFlags & vcl::PushFlags::TEXTFILLCOLOR ) + { + aCalculatedNewState.textFillColor = rNewState.textFillColor; + aCalculatedNewState.isTextFillColorSet = rNewState.isTextFillColorSet; + } + + if( aCalculatedNewState.pushFlags & vcl::PushFlags::TEXTALIGN ) + { + aCalculatedNewState.textReferencePoint = rNewState.textReferencePoint; + } + + // TODO(F1): Refpoint handling NYI + // if( (aCalculatedNewState.pushFlags & vcl::PushFlags::REFPOINT) ) + // { + // } + + if( aCalculatedNewState.pushFlags & vcl::PushFlags::TEXTLINECOLOR ) + { + aCalculatedNewState.textLineColor = rNewState.textLineColor; + aCalculatedNewState.isTextLineColorSet = rNewState.isTextLineColorSet; + } + + if( aCalculatedNewState.pushFlags & vcl::PushFlags::OVERLINECOLOR ) + { + aCalculatedNewState.textOverlineColor = rNewState.textOverlineColor; + aCalculatedNewState.isTextOverlineColorSet = rNewState.isTextOverlineColorSet; + } + + if( aCalculatedNewState.pushFlags & vcl::PushFlags::TEXTLAYOUTMODE ) + { + aCalculatedNewState.textAlignment = rNewState.textAlignment; + aCalculatedNewState.textDirection = rNewState.textDirection; + } + + // TODO(F2): Text language handling NYI + // if( (aCalculatedNewState.pushFlags & vcl::PushFlags::TEXTLANGUAGE) ) + // { + // } + + // always copy push mode + aCalculatedNewState.pushFlags = rNewState.pushFlags; + + // flush to stack + getState() = aCalculatedNewState; + } + else + { + m_aStates.pop_back(); + } + } + + bool ImplRenderer::createFillAndStroke( const ::basegfx::B2DPolyPolygon& rPolyPoly, + const ActionFactoryParameters& rParms ) + { + const OutDevState& rState( rParms.mrStates.getState() ); + if( (!rState.isLineColorSet && + !rState.isFillColorSet) || + (!rState.lineColor.hasElements() && + !rState.fillColor.hasElements()) ) + { + return false; + } + + std::shared_ptr<Action> pPolyAction( + internal::PolyPolyActionFactory::createPolyPolyAction( + rPolyPoly, rParms.mrCanvas, rState ) ); + + if( pPolyAction ) + { + maActions.emplace_back( + pPolyAction, + rParms.mrCurrActionIndex ); + + rParms.mrCurrActionIndex += pPolyAction->getActionCount()-1; + } + + return true; + } + + bool ImplRenderer::createFillAndStroke( const ::basegfx::B2DPolygon& rPoly, + const ActionFactoryParameters& rParms ) + { + return createFillAndStroke( ::basegfx::B2DPolyPolygon( rPoly ), + rParms ); + } + + void ImplRenderer::skipContent( GDIMetaFile& rMtf, + const char* pCommentString, + sal_Int32& io_rCurrActionIndex ) + { + ENSURE_OR_THROW( pCommentString, + "ImplRenderer::skipContent(): NULL string given" ); + + MetaAction* pCurrAct; + while( (pCurrAct=rMtf.NextAction()) != nullptr ) + { + // increment action index, we've skipped an action. + ++io_rCurrActionIndex; + + if( pCurrAct->GetType() == MetaActionType::COMMENT && + static_cast<MetaCommentAction*>(pCurrAct)->GetComment().equalsIgnoreAsciiCase( + pCommentString) ) + { + // requested comment found, done + return; + } + } + + // EOF + } + + bool ImplRenderer::isActionContained( GDIMetaFile& rMtf, + const char* pCommentString, + MetaActionType nType ) + { + ENSURE_OR_THROW( pCommentString, + "ImplRenderer::isActionContained(): NULL string given" ); + + bool bRet( false ); + + // at least _one_ call to GDIMetaFile::NextAction() is + // executed + size_t nPos( 1 ); + + MetaAction* pCurrAct; + while( (pCurrAct=rMtf.NextAction()) != nullptr ) + { + if( pCurrAct->GetType() == nType ) + { + bRet = true; // action type found + break; + } + + if( pCurrAct->GetType() == MetaActionType::COMMENT && + static_cast<MetaCommentAction*>(pCurrAct)->GetComment().equalsIgnoreAsciiCase( + pCommentString) ) + { + // delimiting end comment found, done + bRet = false; // not yet found + break; + } + + ++nPos; + } + + // rewind metafile to previous position (this method must + // not change the current metaaction) + while( nPos-- ) + rMtf.WindPrev(); + + if( !pCurrAct ) + { + // EOF, and not yet found + bRet = false; + } + + return bRet; + } + + void ImplRenderer::createGradientAction( const ::tools::PolyPolygon& rPoly, + const ::Gradient& rGradient, + const ActionFactoryParameters& rParms, + bool bIsPolygonRectangle, + bool bSubsettableActions ) + { + DBG_TESTSOLARMUTEX(); + + ::basegfx::B2DPolyPolygon aDevicePoly( rPoly.getB2DPolyPolygon() ); + aDevicePoly.transform( rParms.mrStates.getState().mapModeTransform ); + + // decide, whether this gradient can be rendered natively + // by the canvas, or must be emulated via VCL gradient + // action extraction. + const sal_uInt16 nSteps( rGradient.GetSteps() ); + + if( // step count is infinite, can use native canvas + // gradients here + nSteps == 0 || + // step count is sufficiently high, such that no + // discernible difference should be visible. + nSteps > 64 ) + { + uno::Reference< lang::XMultiServiceFactory> xFactory( + rParms.mrCanvas->getUNOCanvas()->getDevice()->getParametricPolyPolygonFactory() ); + + if( xFactory.is() ) + { + rendering::Texture aTexture; + + aTexture.RepeatModeX = rendering::TexturingMode::CLAMP; + aTexture.RepeatModeY = rendering::TexturingMode::CLAMP; + aTexture.Alpha = 1.0; + + + // setup start/end color values + + + // scale color coefficients with gradient intensities + const sal_uInt16 nStartIntensity( rGradient.GetStartIntensity() ); + ::Color aVCLStartColor( rGradient.GetStartColor() ); + aVCLStartColor.SetRed( static_cast<sal_uInt8>(aVCLStartColor.GetRed() * nStartIntensity / 100) ); + aVCLStartColor.SetGreen( static_cast<sal_uInt8>(aVCLStartColor.GetGreen() * nStartIntensity / 100) ); + aVCLStartColor.SetBlue( static_cast<sal_uInt8>(aVCLStartColor.GetBlue() * nStartIntensity / 100) ); + + const sal_uInt16 nEndIntensity( rGradient.GetEndIntensity() ); + ::Color aVCLEndColor( rGradient.GetEndColor() ); + aVCLEndColor.SetRed( static_cast<sal_uInt8>(aVCLEndColor.GetRed() * nEndIntensity / 100) ); + aVCLEndColor.SetGreen( static_cast<sal_uInt8>(aVCLEndColor.GetGreen() * nEndIntensity / 100) ); + aVCLEndColor.SetBlue( static_cast<sal_uInt8>(aVCLEndColor.GetBlue() * nEndIntensity / 100) ); + + uno::Reference<rendering::XColorSpace> xColorSpace( + rParms.mrCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace()); + const uno::Sequence< double > aStartColor( + vcl::unotools::colorToDoubleSequence( aVCLStartColor, + xColorSpace )); + const uno::Sequence< double > aEndColor( + vcl::unotools::colorToDoubleSequence( aVCLEndColor, + xColorSpace )); + + uno::Sequence< uno::Sequence < double > > aColors; + uno::Sequence< double > aStops; + + if( rGradient.GetStyle() == css::awt::GradientStyle_AXIAL ) + { + aStops = { 0.0, 0.5, 1.0 }; + aColors = { aEndColor, aStartColor, aEndColor }; + } + else + { + aStops = { 0.0, 1.0 }; + aColors = { aStartColor, aEndColor }; + } + + const ::basegfx::B2DRectangle aBounds( + ::basegfx::utils::getRange(aDevicePoly) ); + const ::basegfx::B2DVector aOffset( + rGradient.GetOfsX() / 100.0, + rGradient.GetOfsY() / 100.0); + double fRotation = toRadians( rGradient.GetAngle() ); + const double fBorder( rGradient.GetBorder() / 100.0 ); + + basegfx::B2DHomMatrix aRot90; + aRot90.rotate(M_PI_2); + + basegfx::ODFGradientInfo aGradInfo; + OUString aGradientService; + switch( rGradient.GetStyle() ) + { + case css::awt::GradientStyle_LINEAR: + aGradInfo = basegfx::utils::createLinearODFGradientInfo( + aBounds, + nSteps, + fBorder, + fRotation); + // map ODF to svg gradient orientation - x + // instead of y direction + aGradInfo.setTextureTransform(aGradInfo.getTextureTransform() * aRot90); + aGradientService = "LinearGradient"; + break; + + case css::awt::GradientStyle_AXIAL: + { + // Adapt the border so that it is suitable + // for the axial gradient. An axial + // gradient consists of two linear + // gradients. Each of those covers half + // of the total size. In order to + // compensate for the condensed display of + // the linear gradients, we have to + // enlarge the area taken up by the actual + // gradient (1-fBorder). After that we + // have to turn the result back into a + // border value, hence the second (left + // most 1-... + const double fAxialBorder (1-2*(1-fBorder)); + aGradInfo = basegfx::utils::createAxialODFGradientInfo( + aBounds, + nSteps, + fAxialBorder, + fRotation); + // map ODF to svg gradient orientation - x + // instead of y direction + aGradInfo.setTextureTransform(aGradInfo.getTextureTransform() * aRot90); + + // map ODF axial gradient to 3-stop linear + // gradient - shift left by 0.5 + basegfx::B2DHomMatrix aShift; + + aShift.translate(-0.5,0); + aGradInfo.setTextureTransform(aGradInfo.getTextureTransform() * aShift); + aGradientService = "LinearGradient"; + break; + } + + case css::awt::GradientStyle_RADIAL: + aGradInfo = basegfx::utils::createRadialODFGradientInfo( + aBounds, + aOffset, + nSteps, + fBorder); + aGradientService = "EllipticalGradient"; + break; + + case css::awt::GradientStyle_ELLIPTICAL: + aGradInfo = basegfx::utils::createEllipticalODFGradientInfo( + aBounds, + aOffset, + nSteps, + fBorder, + fRotation); + aGradientService = "EllipticalGradient"; + break; + + case css::awt::GradientStyle_SQUARE: + aGradInfo = basegfx::utils::createSquareODFGradientInfo( + aBounds, + aOffset, + nSteps, + fBorder, + fRotation); + aGradientService = "RectangularGradient"; + break; + + case css::awt::GradientStyle_RECT: + aGradInfo = basegfx::utils::createRectangularODFGradientInfo( + aBounds, + aOffset, + nSteps, + fBorder, + fRotation); + aGradientService = "RectangularGradient"; + break; + + default: + ENSURE_OR_THROW( false, + "ImplRenderer::createGradientAction(): Unexpected gradient type" ); + break; + } + + ::basegfx::unotools::affineMatrixFromHomMatrix( aTexture.AffineTransform, + aGradInfo.getTextureTransform() ); + + uno::Sequence<uno::Any> args(comphelper::InitAnyPropertySequence( + { + {"Colors", uno::Any(aColors)}, + {"Stops", uno::Any(aStops)}, + {"AspectRatio", uno::Any(aGradInfo.getAspectRatio())}, + })); + aTexture.Gradient.set( + xFactory->createInstanceWithArguments(aGradientService, + args), + uno::UNO_QUERY); + if( aTexture.Gradient.is() ) + { + std::shared_ptr<Action> pPolyAction( + internal::PolyPolyActionFactory::createPolyPolyAction( + aDevicePoly, + rParms.mrCanvas, + rParms.mrStates.getState(), + aTexture ) ); + + if( pPolyAction ) + { + maActions.emplace_back( + pPolyAction, + rParms.mrCurrActionIndex ); + + rParms.mrCurrActionIndex += pPolyAction->getActionCount()-1; + } + + // done, using native gradients + return; + } + } + } + + // cannot currently use native canvas gradients, as a + // finite step size is given (this funny feature is not + // supported by the XCanvas API) + rParms.mrStates.pushState(vcl::PushFlags::ALL); + + if( !bIsPolygonRectangle ) + { + // only clip, if given polygon is not a rectangle in + // the first place (the gradient is always limited to + // the given bound rect) + updateClipping( + aDevicePoly, + rParms, + true ); + } + + GDIMetaFile aTmpMtf; + Gradient aGradient(rGradient); + aGradient.AddGradientActions( rPoly.GetBoundRect(), aTmpMtf ); + + createActions( aTmpMtf, rParms, bSubsettableActions ); + + rParms.mrStates.popState(); + } + + uno::Reference< rendering::XCanvasFont > ImplRenderer::createFont( double& o_rFontRotation, + const vcl::Font& rFont, + const ActionFactoryParameters& rParms ) + { + rendering::FontRequest aFontRequest; + + if( rParms.mrParms.maFontName ) + aFontRequest.FontDescription.FamilyName = *rParms.mrParms.maFontName; + else + aFontRequest.FontDescription.FamilyName = rFont.GetFamilyName(); + + aFontRequest.FontDescription.StyleName = rFont.GetStyleName(); + + aFontRequest.FontDescription.IsSymbolFont = (rFont.GetCharSet() == RTL_TEXTENCODING_SYMBOL) ? util::TriState_YES : util::TriState_NO; + aFontRequest.FontDescription.IsVertical = rFont.IsVertical() ? util::TriState_YES : util::TriState_NO; + + // TODO(F2): improve vclenum->panose conversion + aFontRequest.FontDescription.FontDescription.Weight = + rParms.mrParms.maFontWeight ? + *rParms.mrParms.maFontWeight : + ::canvas::tools::numeric_cast<sal_Int8>( ::basegfx::fround( rFont.GetWeight() ) ); + aFontRequest.FontDescription.FontDescription.Letterform = + rParms.mrParms.maFontLetterForm ? + *rParms.mrParms.maFontLetterForm : + (rFont.GetItalic() == ITALIC_NONE) ? 0 : 9; + aFontRequest.FontDescription.FontDescription.Proportion = + (rFont.GetPitch() == PITCH_FIXED) + ? rendering::PanoseProportion::MONO_SPACED + : rendering::PanoseProportion::ANYTHING; + + LanguageType aLang = rFont.GetLanguage(); + aFontRequest.Locale = LanguageTag::convertToLocale( aLang, false); + + // setup state-local text transformation, + // if the font be rotated + const auto nFontAngle( rFont.GetOrientation() ); + if( nFontAngle ) + { + // set to unity transform rotated by font angle + const double nAngle( toRadians(nFontAngle) ); + o_rFontRotation = -nAngle; + } + else + { + o_rFontRotation = 0.0; + } + + geometry::Matrix2D aFontMatrix; + ::canvas::tools::setIdentityMatrix2D( aFontMatrix ); + + // TODO(F2): use correct scale direction, font + // height might be width or anything else + + // TODO(Q3): This code smells of programming by + // coincidence (the next two if statements) + + ::Size rFontSizeLog( rFont.GetFontSize() ); + + if (rFontSizeLog.Height() == 0) + { + // guess 16 pixel (as in VCL) + rFontSizeLog = ::Size(0, 16); + + // convert to target MapUnit if not pixels + rFontSizeLog = OutputDevice::LogicToLogic(rFontSizeLog, MapMode(MapUnit::MapPixel), rParms.mrVDev.GetMapMode()); + } + + const sal_Int32 nFontWidthLog = rFontSizeLog.Width(); + if( nFontWidthLog != 0 ) + { + vcl::Font aTestFont = rFont; + aTestFont.SetAverageFontWidth( 0 ); + sal_Int32 nNormalWidth = rParms.mrVDev.GetFontMetric( aTestFont ).GetAverageFontWidth(); + if( nNormalWidth != nFontWidthLog ) + if( nNormalWidth ) + aFontMatrix.m00 = static_cast<double>(nFontWidthLog) / nNormalWidth; + } + + // #i52608# apply map mode scale also to font matrix - an + // anisotrophic mapmode must be reflected in an + // anisotrophic font matrix scale. + const OutDevState& rState( rParms.mrStates.getState() ); + if( !::basegfx::fTools::equal( + rState.mapModeTransform.get(0,0), + rState.mapModeTransform.get(1,1)) ) + { + const double nScaleX( rState.mapModeTransform.get(0,0) ); + const double nScaleY( rState.mapModeTransform.get(1,1) ); + + // note: no reason to check for division by zero, we + // always have the value closer (or equal) to zero as + // the nominator. + if( fabs(nScaleX) < fabs(nScaleY) ) + aFontMatrix.m00 *= nScaleX / nScaleY; + else + aFontMatrix.m11 *= nScaleY / nScaleX; + } + aFontRequest.CellSize = (rState.mapModeTransform * vcl::unotools::b2DSizeFromSize(rFontSizeLog)).getHeight(); + + if (rFont.GetEmphasisMark() != FontEmphasisMark::NONE) + { + uno::Sequence< beans::PropertyValue > aProperties{ comphelper::makePropertyValue( + "EmphasisMark", sal_uInt32(rFont.GetEmphasisMark())) }; + return rParms.mrCanvas->getUNOCanvas()->createFont(aFontRequest, + aProperties, + aFontMatrix); + } + + return rParms.mrCanvas->getUNOCanvas()->createFont( aFontRequest, + uno::Sequence< beans::PropertyValue >(), + aFontMatrix ); + } + + // create text effects such as shadow/relief/embossed + void ImplRenderer::createTextAction( const ::Point& rStartPoint, + const OUString& rString, + int nIndex, + int nLength, + KernArraySpan pCharWidths, + std::span<const sal_Bool> pKashidaArray, + const ActionFactoryParameters& rParms, + bool bSubsettableActions ) + { + ENSURE_OR_THROW( nIndex >= 0 && nLength <= rString.getLength() + nIndex, + "ImplRenderer::createTextWithEffectsAction(): Invalid text index" ); + + if( !nLength ) + return; // zero-length text, no visible output + + const OutDevState& rState( rParms.mrStates.getState() ); + + // TODO(F2): implement all text effects + // if( rState.textAlignment ); // TODO(F2): NYI + + ::Color aTextFillColor( COL_AUTO ); + ::Color aShadowColor( COL_AUTO ); + ::Color aReliefColor( COL_AUTO ); + ::Size aShadowOffset; + ::Size aReliefOffset; + + uno::Reference<rendering::XColorSpace> xColorSpace( + rParms.mrCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() ); + + if( rState.isTextEffectShadowSet ) + { + // calculate shadow offset (similar to outdev3.cxx) + // TODO(F3): better match with outdev3.cxx + sal_Int32 nShadowOffset = static_cast<sal_Int32>(1.5 + ((rParms.mrVDev.GetFont().GetFontHeight()-24.0)/24.0)); + if( nShadowOffset < 1 ) + nShadowOffset = 1; + + aShadowOffset.setWidth( nShadowOffset ); + aShadowOffset.setHeight( nShadowOffset ); + + // determine shadow color (from outdev3.cxx) + ::Color aTextColor = vcl::unotools::doubleSequenceToColor( + rState.textColor, xColorSpace ); + bool bIsDark = (aTextColor == COL_BLACK) + || (aTextColor.GetLuminance() < 8); + + aShadowColor = bIsDark ? COL_LIGHTGRAY : COL_BLACK; + aShadowColor.SetAlpha( aTextColor.GetAlpha() ); + } + + if( rState.textReliefStyle != FontRelief::NONE ) + { + // calculate relief offset (similar to outdev3.cxx) + sal_Int32 nReliefOffset = rParms.mrVDev.PixelToLogic( Size( 1, 1 ) ).Height(); + nReliefOffset += nReliefOffset/2; + if( nReliefOffset < 1 ) + nReliefOffset = 1; + + if( rState.textReliefStyle == FontRelief::Engraved ) + nReliefOffset = -nReliefOffset; + + aReliefOffset.setWidth( nReliefOffset ); + aReliefOffset.setHeight( nReliefOffset ); + + // determine relief color (from outdev3.cxx) + ::Color aTextColor = vcl::unotools::doubleSequenceToColor( + rState.textColor, xColorSpace ); + + aReliefColor = COL_LIGHTGRAY; + + // we don't have an automatic color, so black is always + // drawn on white (literally copied from + // vcl/source/gdi/outdev3.cxx) + if( aTextColor == COL_BLACK ) + { + aTextColor = COL_WHITE; + rParms.mrStates.getState().textColor = + vcl::unotools::colorToDoubleSequence( + aTextColor, xColorSpace ); + } + + if( aTextColor == COL_WHITE ) + aReliefColor = COL_BLACK; + aReliefColor.SetAlpha( aTextColor.GetAlpha() ); + } + + if (rState.isTextFillColorSet) + aTextFillColor = vcl::unotools::doubleSequenceToColor(rState.textFillColor, xColorSpace); + + // create the actual text action + std::shared_ptr<Action> pTextAction( + TextActionFactory::createTextAction( + rStartPoint, + aReliefOffset, + aReliefColor, + aShadowOffset, + aShadowColor, + aTextFillColor, + rString, + nIndex, + nLength, + pCharWidths, + pKashidaArray, + rParms.mrVDev, + rParms.mrCanvas, + rState, + rParms.mrParms, + bSubsettableActions ) ); + + std::shared_ptr<Action> pStrikeoutTextAction; + + if ( rState.textStrikeoutStyle == STRIKEOUT_X || rState.textStrikeoutStyle == STRIKEOUT_SLASH ) + { + ::tools::Long nWidth = rParms.mrVDev.GetTextWidth( rString,nIndex,nLength ); + + sal_Unicode pChars[4]; + if ( rState.textStrikeoutStyle == STRIKEOUT_X ) + pChars[0] = 'X'; + else + pChars[0] = '/'; + pChars[3]=pChars[2]=pChars[1]=pChars[0]; + + ::tools::Long nStrikeoutWidth = (rParms.mrVDev.GetTextWidth( + OUString(pChars, std::size(pChars))) + 2) / 4; + + if( nStrikeoutWidth <= 0 ) + nStrikeoutWidth = 1; + + ::tools::Long nMaxWidth = nStrikeoutWidth/2; + if ( nMaxWidth < 2 ) + nMaxWidth = 2; + nMaxWidth += nWidth + 1; + + ::tools::Long nFullStrikeoutWidth = 0; + OUStringBuffer aStrikeoutText; + while( (nFullStrikeoutWidth+=nStrikeoutWidth ) < nMaxWidth+1 ) + aStrikeoutText.append(pChars[0]); + + sal_Int32 nLen = aStrikeoutText.getLength(); + + if( nLen ) + { + ::tools::Long nInterval = ( nWidth - nStrikeoutWidth * nLen ) / nLen; + nStrikeoutWidth += nInterval; + KernArray aStrikeoutCharWidths; + + for ( int i = 0;i<nLen; i++) + { + aStrikeoutCharWidths.push_back(nStrikeoutWidth); + } + + for ( int i = 1;i< nLen; i++ ) + { + aStrikeoutCharWidths.adjust(i, aStrikeoutCharWidths[i - 1]); + } + + pStrikeoutTextAction = + TextActionFactory::createTextAction( + rStartPoint, + aReliefOffset, + aReliefColor, + aShadowOffset, + aShadowColor, + aTextFillColor, + aStrikeoutText.makeStringAndClear(), + 0/*nStartPos*/, + nLen, + aStrikeoutCharWidths, + pKashidaArray, + rParms.mrVDev, + rParms.mrCanvas, + rState, + rParms.mrParms, + bSubsettableActions ) ; + } + } + + if( !pTextAction ) + return; + + maActions.emplace_back( + pTextAction, + rParms.mrCurrActionIndex ); + + if ( pStrikeoutTextAction ) + { + maActions.emplace_back( + pStrikeoutTextAction, + rParms.mrCurrActionIndex ); + } + + rParms.mrCurrActionIndex += pTextAction->getActionCount()-1; + } + + void ImplRenderer::updateClipping( const ::basegfx::B2DPolyPolygon& rClipPoly, + const ActionFactoryParameters& rParms, + bool bIntersect ) + { + ::cppcanvas::internal::OutDevState& rState( rParms.mrStates.getState() ); + + const bool bEmptyClipRect( rState.clipRect.IsEmpty() ); + const bool bEmptyClipPoly( rState.clip.count() == 0 ); + + ENSURE_OR_THROW( bEmptyClipPoly || bEmptyClipRect, + "ImplRenderer::updateClipping(): Clip rect and polygon are both set!" ); + + if( !bIntersect || + (bEmptyClipRect && bEmptyClipPoly) ) + { + rState.clip = rClipPoly; + } + else + { + if( !bEmptyClipRect ) + { + // TODO(P3): Use Liang-Barsky polygon clip here, + // after all, one object is just a rectangle! + + // convert rect to polygon beforehand, must revert + // to general polygon clipping here. + ::tools::Rectangle aRect = rState.clipRect; + // #121100# VCL rectangular clips always + // include one more pixel to the right + // and the bottom + aRect.AdjustRight(1); + aRect.AdjustBottom(1); + rState.clip = ::basegfx::B2DPolyPolygon( + ::basegfx::utils::createPolygonFromRect( + vcl::unotools::b2DRectangleFromRectangle(aRect) ) ); + } + + // AW: Simplified + rState.clip = basegfx::utils::clipPolyPolygonOnPolyPolygon( + rClipPoly, rState.clip, true, false); + } + + // by now, our clip resides in the OutDevState::clip + // poly-polygon. + rState.clipRect.SetEmpty(); + + if( rState.clip.count() == 0 ) + { + if( rState.clipRect.IsEmpty() ) + { + rState.xClipPoly.clear(); + } + else + { + ::tools::Rectangle aRect = rState.clipRect; + // #121100# VCL rectangular clips + // always include one more pixel to + // the right and the bottom + aRect.AdjustRight(1); + aRect.AdjustBottom(1); + rState.xClipPoly = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( + rParms.mrCanvas->getUNOCanvas()->getDevice(), + ::basegfx::B2DPolyPolygon( + ::basegfx::utils::createPolygonFromRect( + vcl::unotools::b2DRectangleFromRectangle(aRect) ) ) ); + } + } + else + { + rState.xClipPoly = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( + rParms.mrCanvas->getUNOCanvas()->getDevice(), + rState.clip ); + } + } + + void ImplRenderer::updateClipping( const ::tools::Rectangle& rClipRect, + const ActionFactoryParameters& rParms, + bool bIntersect ) + { + ::cppcanvas::internal::OutDevState& rState( rParms.mrStates.getState() ); + + const bool bEmptyClipRect( rState.clipRect.IsEmpty() ); + const bool bEmptyClipPoly( rState.clip.count() == 0 ); + + ENSURE_OR_THROW( bEmptyClipPoly || bEmptyClipRect, + "ImplRenderer::updateClipping(): Clip rect and polygon are both set!" ); + + if( !bIntersect || + (bEmptyClipRect && bEmptyClipPoly) ) + { + rState.clipRect = rClipRect; + rState.clip.clear(); + } + else if( bEmptyClipPoly ) + { + rState.clipRect.Intersection( rClipRect ); + rState.clip.clear(); + } + else + { + // TODO(P3): Handle a fourth case here, when all clip + // polygons are rectangular, once B2DMultiRange's + // sweep line implementation is done. + + // general case: convert to polygon and clip + + + // convert rect to polygon beforehand, must revert + // to general polygon clipping here. + ::basegfx::B2DPolyPolygon aClipPoly( + ::basegfx::utils::createPolygonFromRect( + vcl::unotools::b2DRectangleFromRectangle(rClipRect) ) ); + + rState.clipRect.SetEmpty(); + + // AW: Simplified + rState.clip = basegfx::utils::clipPolyPolygonOnPolyPolygon( + aClipPoly, rState.clip, true, false); + } + + if( rState.clip.count() == 0 ) + { + if( rState.clipRect.IsEmpty() ) + { + rState.xClipPoly.clear(); + } + else + { + // #121100# VCL rectangular clips + // always include one more pixel to + // the right and the bottom + ::tools::Rectangle aRect = rState.clipRect; + aRect.AdjustRight(1); + aRect.AdjustBottom(1); + rState.xClipPoly = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( + rParms.mrCanvas->getUNOCanvas()->getDevice(), + ::basegfx::B2DPolyPolygon( + ::basegfx::utils::createPolygonFromRect( + vcl::unotools::b2DRectangleFromRectangle(aRect) ) ) ); + } + } + else + { + rState.xClipPoly = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( + rParms.mrCanvas->getUNOCanvas()->getDevice(), + rState.clip ); + } + } + + void ImplRenderer::createActions( GDIMetaFile& rMtf, + const ActionFactoryParameters& rFactoryParms, + bool bSubsettableActions ) + { + /* TODO(P2): interpret mtf-comments + ================================ + + - gradient fillings (do that via comments) + + - think about mapping. _If_ we do everything in logical + coordinates (which would solve the probs for stroke + widths and text offsets), then we would have to + recalc scaling for every drawing operation. This is + because the outdev map mode might change at any time. + Also keep in mind, that, although we've double precision + float arithmetic now, different offsets might still + generate different roundings (aka + 'OutputDevice::SetPixelOffset()) + + */ + + // alias common parameters + VectorOfOutDevStates& rStates(rFactoryParms.mrStates); + const CanvasSharedPtr& rCanvas(rFactoryParms.mrCanvas); + ::VirtualDevice& rVDev(rFactoryParms.mrVDev); + const Parameters& rParms(rFactoryParms.mrParms); + sal_Int32& io_rCurrActionIndex(rFactoryParms.mrCurrActionIndex); + + + // Loop over every metaaction + // ========================== + MetaAction* pCurrAct; + + // TODO(P1): think about caching + for( pCurrAct=rMtf.FirstAction(); + pCurrAct; + pCurrAct = rMtf.NextAction() ) + { + // execute every action, to keep VDev state up-to-date + // currently used only for + // - the map mode + // - the line/fill color when processing a MetaActionType::Transparent + // - SetFont to process font metric specific actions + pCurrAct->Execute( &rVDev ); + + SAL_INFO("cppcanvas.emf", "MTF\trecord type: 0x" << static_cast<sal_uInt16>(pCurrAct->GetType()) << " (" << static_cast<sal_uInt16>(pCurrAct->GetType()) << ")"); + + switch( pCurrAct->GetType() ) + { + + + // In the first part of this monster-switch, we + // handle all state-changing meta actions. These + // are all handled locally. + + + case MetaActionType::PUSH: + { + MetaPushAction* pPushAction = static_cast<MetaPushAction*>(pCurrAct); + rStates.pushState(pPushAction->GetFlags()); + } + break; + + case MetaActionType::POP: + rStates.popState(); + break; + + case MetaActionType::TEXTLANGUAGE: + case MetaActionType::REFPOINT: + // handled via pCurrAct->Execute( &rVDev ) + break; + + case MetaActionType::MAPMODE: + // modify current mapModeTransformation + // transformation, such that subsequent + // coordinates map correctly + tools::calcLogic2PixelAffineTransform( rStates.getState().mapModeTransform, + rVDev ); + break; + + // monitor clip regions, to assemble clip polygon on our own + case MetaActionType::CLIPREGION: + { + MetaClipRegionAction* pClipAction = static_cast<MetaClipRegionAction*>(pCurrAct); + + if( !pClipAction->IsClipping() ) + { + // clear clipping + rStates.getState().clip.clear(); + } + else + { + if( !pClipAction->GetRegion().HasPolyPolygonOrB2DPolyPolygon() ) + { + SAL_INFO( "cppcanvas.emf", "ImplRenderer::createActions(): non-polygonal clip " + "region encountered, falling back to bounding box!" ); + + // #121806# explicitly kept integer + ::tools::Rectangle aClipRect( + rVDev.LogicToPixel( + pClipAction->GetRegion().GetBoundRect() ) ); + + // intersect current clip with given rect + updateClipping( + aClipRect, + rFactoryParms, + false ); + } + else + { + // set new clip polygon (don't intersect + // with old one, just set it) + + // #121806# explicitly kept integer + basegfx::B2DPolyPolygon aPolyPolygon(pClipAction->GetRegion().GetAsB2DPolyPolygon()); + + aPolyPolygon.transform(rVDev.GetViewTransformation()); + updateClipping( + aPolyPolygon, + rFactoryParms, + false ); + } + } + + break; + } + + case MetaActionType::ISECTRECTCLIPREGION: + { + MetaISectRectClipRegionAction* pClipAction = static_cast<MetaISectRectClipRegionAction*>(pCurrAct); + + // #121806# explicitly kept integer + ::tools::Rectangle aClipRect( + rVDev.LogicToPixel( pClipAction->GetRect() ) ); + + // intersect current clip with given rect + updateClipping( + aClipRect, + rFactoryParms, + true ); + + break; + } + + case MetaActionType::ISECTREGIONCLIPREGION: + { + MetaISectRegionClipRegionAction* pClipAction = static_cast<MetaISectRegionClipRegionAction*>(pCurrAct); + + if( !pClipAction->GetRegion().HasPolyPolygonOrB2DPolyPolygon() ) + { + SAL_INFO( "cppcanvas.emf", "ImplRenderer::createActions(): non-polygonal clip " + "region encountered, falling back to bounding box!" ); + + // #121806# explicitly kept integer + ::tools::Rectangle aClipRect( + rVDev.LogicToPixel( pClipAction->GetRegion().GetBoundRect() ) ); + + // intersect current clip with given rect + updateClipping( + aClipRect, + rFactoryParms, + true ); + } + else + { + // intersect current clip with given clip polygon + + // #121806# explicitly kept integer + basegfx::B2DPolyPolygon aPolyPolygon(pClipAction->GetRegion().GetAsB2DPolyPolygon()); + + aPolyPolygon.transform(rVDev.GetViewTransformation()); + updateClipping( + aPolyPolygon, + rFactoryParms, + true ); + } + + break; + } + + case MetaActionType::MOVECLIPREGION: + // TODO(F2): NYI + break; + + case MetaActionType::LINECOLOR: + if( !rParms.maLineColor ) + { + setStateColor( static_cast<MetaLineColorAction*>(pCurrAct), + rStates.getState().isLineColorSet, + rStates.getState().lineColor, + rCanvas ); + } + else + { + // #120994# Do switch on/off LineColor, even when an overriding one is set + bool bSetting(static_cast<MetaLineColorAction*>(pCurrAct)->IsSetting()); + + rStates.getState().isLineColorSet = bSetting; + } + break; + + case MetaActionType::FILLCOLOR: + if( !rParms.maFillColor ) + { + setStateColor( static_cast<MetaFillColorAction*>(pCurrAct), + rStates.getState().isFillColorSet, + rStates.getState().fillColor, + rCanvas ); + } + else + { + // #120994# Do switch on/off FillColor, even when an overriding one is set + bool bSetting(static_cast<MetaFillColorAction*>(pCurrAct)->IsSetting()); + + rStates.getState().isFillColorSet = bSetting; + } + break; + + case MetaActionType::TEXTCOLOR: + { + if( !rParms.maTextColor ) + { + // Text color is set unconditionally, thus, no + // use of setStateColor here + ::Color aColor( static_cast<MetaTextColorAction*>(pCurrAct)->GetColor() ); + + // force alpha part of color to + // opaque. transparent painting is done + // explicitly via MetaActionType::Transparent + aColor.SetAlpha(255); + + rStates.getState().textColor = + vcl::unotools::colorToDoubleSequence( + aColor, + rCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() ); + } + } + break; + + case MetaActionType::TEXTFILLCOLOR: + if( !rParms.maTextColor ) + { + setStateColor( static_cast<MetaTextFillColorAction*>(pCurrAct), + rStates.getState().isTextFillColorSet, + rStates.getState().textFillColor, + rCanvas ); + } + else + { + // #120994# Do switch on/off TextFillColor, even when an overriding one is set + bool bSetting(static_cast<MetaTextFillColorAction*>(pCurrAct)->IsSetting()); + + rStates.getState().isTextFillColorSet = bSetting; + } + break; + + case MetaActionType::TEXTLINECOLOR: + if( !rParms.maTextColor ) + { + setStateColor( static_cast<MetaTextLineColorAction*>(pCurrAct), + rStates.getState().isTextLineColorSet, + rStates.getState().textLineColor, + rCanvas ); + } + else + { + // #120994# Do switch on/off TextLineColor, even when an overriding one is set + bool bSetting(static_cast<MetaTextLineColorAction*>(pCurrAct)->IsSetting()); + + rStates.getState().isTextLineColorSet = bSetting; + } + break; + + case MetaActionType::OVERLINECOLOR: + if( !rParms.maTextColor ) + { + setStateColor( static_cast<MetaOverlineColorAction*>(pCurrAct), + rStates.getState().isTextOverlineColorSet, + rStates.getState().textOverlineColor, + rCanvas ); + } + else + { + bool bSetting(static_cast<MetaOverlineColorAction*>(pCurrAct)->IsSetting()); + + rStates.getState().isTextOverlineColorSet = bSetting; + } + break; + + case MetaActionType::TEXTALIGN: + { + ::cppcanvas::internal::OutDevState& rState = rStates.getState(); + const TextAlign eTextAlign( static_cast<MetaTextAlignAction*>(pCurrAct)->GetTextAlign() ); + + rState.textReferencePoint = eTextAlign; + } + break; + + case MetaActionType::FONT: + { + ::cppcanvas::internal::OutDevState& rState = rStates.getState(); + const vcl::Font& rFont( static_cast<MetaFontAction*>(pCurrAct)->GetFont() ); + + rState.xFont = createFont( rState.fontRotation, + rFont, + rFactoryParms ); + + // TODO(Q2): define and use appropriate enumeration types + rState.textReliefStyle = rFont.GetRelief(); + rState.textOverlineStyle = static_cast<sal_Int8>(rFont.GetOverline()); + rState.textUnderlineStyle = rParms.maFontUnderline.has_value() ? + (*rParms.maFontUnderline ? sal_Int8(LINESTYLE_SINGLE) : sal_Int8(LINESTYLE_NONE)) : + static_cast<sal_Int8>(rFont.GetUnderline()); + rState.textStrikeoutStyle = static_cast<sal_Int8>(rFont.GetStrikeout()); + rState.textEmphasisMark = rFont.GetEmphasisMark(); + rState.isTextEffectShadowSet = rFont.IsShadow(); + rState.isTextWordUnderlineSet = rFont.IsWordLineMode(); + rState.isTextOutlineModeSet = rFont.IsOutline(); + } + break; + + case MetaActionType::RASTEROP: + // TODO(F2): NYI + break; + + case MetaActionType::LAYOUTMODE: + { + // TODO(F2): A lot is missing here + vcl::text::ComplexTextLayoutFlags nLayoutMode = static_cast<MetaLayoutModeAction*>(pCurrAct)->GetLayoutMode(); + ::cppcanvas::internal::OutDevState& rState = rStates.getState(); + + vcl::text::ComplexTextLayoutFlags nBidiLayoutMode = nLayoutMode & (vcl::text::ComplexTextLayoutFlags::BiDiRtl|vcl::text::ComplexTextLayoutFlags::BiDiStrong); + if( nBidiLayoutMode == vcl::text::ComplexTextLayoutFlags::Default) + rState.textDirection = rendering::TextDirection::WEAK_LEFT_TO_RIGHT; + else if( nBidiLayoutMode == vcl::text::ComplexTextLayoutFlags::BiDiStrong) + rState.textDirection = rendering::TextDirection::STRONG_LEFT_TO_RIGHT; + else if( nBidiLayoutMode == vcl::text::ComplexTextLayoutFlags::BiDiRtl) + rState.textDirection = rendering::TextDirection::WEAK_RIGHT_TO_LEFT; + else if( nBidiLayoutMode == (vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::BiDiStrong)) + rState.textDirection = rendering::TextDirection::STRONG_RIGHT_TO_LEFT; + + rState.textAlignment = 0; // TODO(F2): rendering::TextAlignment::LEFT_ALIGNED; + if( (nLayoutMode & (vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::TextOriginRight) ) + && !(nLayoutMode & vcl::text::ComplexTextLayoutFlags::TextOriginLeft ) ) + { + rState.textAlignment = 1; // TODO(F2): rendering::TextAlignment::RIGHT_ALIGNED; + } + } + break; + + + // In the second part of this monster-switch, we + // handle all recursing meta actions. These are the + // ones generating a metafile by themselves, which is + // then processed by recursively calling this method. + + + case MetaActionType::GRADIENT: + { + MetaGradientAction* pGradAct = static_cast<MetaGradientAction*>(pCurrAct); + createGradientAction( ::tools::PolyPolygon( pGradAct->GetRect() ), + pGradAct->GetGradient(), + rFactoryParms, + true, + bSubsettableActions ); + } + break; + + case MetaActionType::HATCH: + { + // TODO(F2): use native Canvas hatches here + GDIMetaFile aTmpMtf; + + rVDev.AddHatchActions( static_cast<MetaHatchAction*>(pCurrAct)->GetPolyPolygon(), + static_cast<MetaHatchAction*>(pCurrAct)->GetHatch(), + aTmpMtf ); + createActions( aTmpMtf, rFactoryParms, + bSubsettableActions ); + } + break; + + case MetaActionType::EPS: + { + MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pCurrAct); + const GDIMetaFile& rSubstitute = pAct->GetSubstitute(); + + // #121806# explicitly kept integer + const Size aMtfSize( rSubstitute.GetPrefSize() ); + const Size aMtfSizePixPre( rVDev.LogicToPixel( aMtfSize, + rSubstitute.GetPrefMapMode() ) ); + + // #i44110# correct null-sized output - there + // are metafiles which have zero size in at + // least one dimension + + // Remark the 1L cannot be replaced, that would cause max to compare long/int + const Size aMtfSizePix( std::max( aMtfSizePixPre.Width(), ::tools::Long(1) ), + std::max( aMtfSizePixPre.Height(), ::tools::Long(1) ) ); + + // Setup local transform, such that the + // metafile renders itself into the given + // output rectangle + rStates.pushState(vcl::PushFlags::ALL); + + rVDev.Push(); + rVDev.SetMapMode( rSubstitute.GetPrefMapMode() ); + + const ::Point& rPos( rVDev.LogicToPixel( pAct->GetPoint() ) ); + const ::Size& rSize( rVDev.LogicToPixel( pAct->GetSize() ) ); + + rStates.getState().transform.translate( rPos.X(), + rPos.Y() ); + rStates.getState().transform.scale( static_cast<double>(rSize.Width()) / aMtfSizePix.Width(), + static_cast<double>(rSize.Height()) / aMtfSizePix.Height() ); + + createActions( const_cast<GDIMetaFile&>(pAct->GetSubstitute()), + rFactoryParms, + bSubsettableActions ); + + rVDev.Pop(); + rStates.popState(); + } + break; + + // handle metafile comments, to retrieve + // meta-information for gradients, fills and + // strokes. May skip actions, and may recurse. + case MetaActionType::COMMENT: + { + MetaCommentAction* pAct = static_cast<MetaCommentAction*>(pCurrAct); + + // Handle gradients + if (pAct->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN")) + { + MetaGradientExAction* pGradAction = nullptr; + bool bDone( false ); + while( !bDone ) + { + pCurrAct=rMtf.NextAction(); + if (!pCurrAct) + break; + switch( pCurrAct->GetType() ) + { + // extract gradient info + case MetaActionType::GRADIENTEX: + pGradAction = static_cast<MetaGradientExAction*>(pCurrAct); + break; + + // skip broken-down rendering, output gradient when sequence is ended + case MetaActionType::COMMENT: + if( static_cast<MetaCommentAction*>(pCurrAct)->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_END") ) + { + bDone = true; + + if( pGradAction ) + { + createGradientAction( pGradAction->GetPolyPolygon(), + pGradAction->GetGradient(), + rFactoryParms, + false, + bSubsettableActions ); + } + } + break; + default: break; + } + } + } + // TODO(P2): Handle drawing layer strokes, via + // XPATHSTROKE_SEQ_BEGIN comment + + // Handle drawing layer fills + else if( pAct->GetComment() == "XPATHFILL_SEQ_BEGIN" ) + { + const sal_uInt8* pData = pAct->GetData(); + if ( pData ) + { + SvMemoryStream aMemStm( const_cast<sal_uInt8 *>(pData), pAct->GetDataSize(), StreamMode::READ ); + + SvtGraphicFill aFill; + ReadSvtGraphicFill( aMemStm, aFill ); + + // TODO(P2): Also handle gradients and + // hatches like this + + // only evaluate comment for pure + // bitmap fills. If a transparency + // gradient is involved (denoted by + // the FloatTransparent action), take + // the normal meta actions. + if( aFill.getFillType() == SvtGraphicFill::fillTexture && + !isActionContained( rMtf, + "XPATHFILL_SEQ_END", + MetaActionType::FLOATTRANSPARENT ) ) + { + rendering::Texture aTexture; + + // TODO(F1): the SvtGraphicFill + // can also transport metafiles + // here, handle that case, too + Graphic aGraphic; + aFill.getGraphic( aGraphic ); + + BitmapEx aBmpEx( aGraphic.GetBitmapEx() ); + const ::Size aBmpSize( aBmpEx.GetSizePixel() ); + + ::SvtGraphicFill::Transform aTransform; + aFill.getTransform( aTransform ); + + ::basegfx::B2DHomMatrix aMatrix; + + // convert to basegfx matrix + aMatrix.set(0,0, aTransform.matrix[ 0 ] ); + aMatrix.set(0,1, aTransform.matrix[ 1 ] ); + aMatrix.set(0,2, aTransform.matrix[ 2 ] ); + aMatrix.set(1,0, aTransform.matrix[ 3 ] ); + aMatrix.set(1,1, aTransform.matrix[ 4 ] ); + aMatrix.set(1,2, aTransform.matrix[ 5 ] ); + + ::basegfx::B2DHomMatrix aScale; + aScale.scale( aBmpSize.Width(), + aBmpSize.Height() ); + + // post-multiply with the bitmap + // size (XCanvas' texture assumes + // the given bitmap to be + // normalized to [0,1]x[0,1] + // rectangle) + aMatrix = aMatrix * aScale; + + // pre-multiply with the + // logic-to-pixel scale factor + // (the metafile comment works in + // logical coordinates). + ::basegfx::B2DHomMatrix aLogic2PixelTransform; + aMatrix *= tools::calcLogic2PixelLinearTransform( aLogic2PixelTransform, + rVDev ); + + ::basegfx::unotools::affineMatrixFromHomMatrix( + aTexture.AffineTransform, + aMatrix ); + + aTexture.Alpha = 1.0 - aFill.getTransparency(); + aTexture.Bitmap = vcl::unotools::xBitmapFromBitmapEx( aBmpEx ); + if( aFill.isTiling() ) + { + aTexture.RepeatModeX = rendering::TexturingMode::REPEAT; + aTexture.RepeatModeY = rendering::TexturingMode::REPEAT; + } + else + { + aTexture.RepeatModeX = rendering::TexturingMode::NONE; + aTexture.RepeatModeY = rendering::TexturingMode::NONE; + } + + ::tools::PolyPolygon aPath; + aFill.getPath( aPath ); + + ::basegfx::B2DPolyPolygon aPoly( aPath.getB2DPolyPolygon() ); + aPoly.transform( rStates.getState().mapModeTransform ); + std::shared_ptr<Action> pPolyAction( + internal::PolyPolyActionFactory::createPolyPolyAction( + aPoly, + rCanvas, + rStates.getState(), + aTexture ) ); + + if( pPolyAction ) + { + maActions.emplace_back( + pPolyAction, + io_rCurrActionIndex ); + + io_rCurrActionIndex += pPolyAction->getActionCount()-1; + } + + // skip broken-down render output + skipContent( rMtf, + "XPATHFILL_SEQ_END", + io_rCurrActionIndex ); + } + } + } + // Handle drawing layer fills + else if( pAct->GetComment() == "EMF_PLUS" ) { + SAL_WARN ("cppcanvas.emf", "EMF+ code was refactored and removed"); + } else if( pAct->GetComment() == "EMF_PLUS_HEADER_INFO" ) { + SAL_INFO ("cppcanvas.emf", "EMF+ passed to canvas mtf renderer - header info, size: " << pAct->GetDataSize ()); + + SvMemoryStream rMF (const_cast<sal_uInt8 *>(pAct->GetData ()), pAct->GetDataSize (), StreamMode::READ); + + rMF.ReadInt32( nFrameLeft ).ReadInt32( nFrameTop ).ReadInt32( nFrameRight ).ReadInt32( nFrameBottom ); + SAL_INFO ("cppcanvas.emf", "EMF+ picture frame: " << nFrameLeft << "," << nFrameTop << " - " << nFrameRight << "," << nFrameBottom); + rMF.ReadInt32( nPixX ).ReadInt32( nPixY ).ReadInt32( nMmX ).ReadInt32( nMmY ); + SAL_INFO ("cppcanvas.emf", "EMF+ ref device pixel size: " << nPixX << "x" << nPixY << " mm size: " << nMmX << "x" << nMmY); + + ReadXForm( rMF, aBaseTransform ); + //aWorldTransform.Set (aBaseTransform); + } + } + break; + + + // In the third part of this monster-switch, we + // handle all 'acting' meta actions. These are all + // processed by constructing function objects for + // them, which will later ease caching. + + + case MetaActionType::POINT: + { + const OutDevState& rState( rStates.getState() ); + if( rState.lineColor.hasElements() ) + { + std::shared_ptr<Action> pPointAction( + internal::PointActionFactory::createPointAction( + rState.mapModeTransform * vcl::unotools::b2DPointFromPoint( + static_cast<MetaPointAction*>(pCurrAct)->GetPoint() ), + rCanvas, + rState ) ); + + if( pPointAction ) + { + maActions.emplace_back( + pPointAction, + io_rCurrActionIndex ); + + io_rCurrActionIndex += pPointAction->getActionCount()-1; + } + } + } + break; + + case MetaActionType::PIXEL: + { + const OutDevState& rState( rStates.getState() ); + if( rState.lineColor.hasElements() ) + { + std::shared_ptr<Action> pPointAction( + internal::PointActionFactory::createPointAction( + rState.mapModeTransform * vcl::unotools::b2DPointFromPoint( + static_cast<MetaPixelAction*>(pCurrAct)->GetPoint() ), + rCanvas, + rState, + static_cast<MetaPixelAction*>(pCurrAct)->GetColor() ) ); + + if( pPointAction ) + { + maActions.emplace_back( + pPointAction, + io_rCurrActionIndex ); + + io_rCurrActionIndex += pPointAction->getActionCount()-1; + } + } + } + break; + + case MetaActionType::LINE: + { + const OutDevState& rState( rStates.getState() ); + if( rState.lineColor.hasElements() ) + { + MetaLineAction* pLineAct = static_cast<MetaLineAction*>(pCurrAct); + + const LineInfo& rLineInfo( pLineAct->GetLineInfo() ); + + const ::basegfx::B2DPoint aStartPoint( + rState.mapModeTransform * vcl::unotools::b2DPointFromPoint( pLineAct->GetStartPoint() )); + const ::basegfx::B2DPoint aEndPoint( + rState.mapModeTransform * vcl::unotools::b2DPointFromPoint( pLineAct->GetEndPoint() )); + + std::shared_ptr<Action> pLineAction; + + if( rLineInfo.IsDefault() ) + { + // plain hair line + pLineAction = + internal::LineActionFactory::createLineAction( + aStartPoint, + aEndPoint, + rCanvas, + rState ); + + if( pLineAction ) + { + maActions.emplace_back( + pLineAction, + io_rCurrActionIndex ); + + io_rCurrActionIndex += pLineAction->getActionCount()-1; + } + } + else if( LineStyle::NONE != rLineInfo.GetStyle() ) + { + // 'thick' line + rendering::StrokeAttributes aStrokeAttributes; + + setupStrokeAttributes( aStrokeAttributes, + rFactoryParms, + rLineInfo ); + + // XCanvas can only stroke polygons, + // not simple lines - thus, handle + // this case via the polypolygon + // action + ::basegfx::B2DPolygon aPoly; + aPoly.append( aStartPoint ); + aPoly.append( aEndPoint ); + pLineAction = + internal::PolyPolyActionFactory::createPolyPolyAction( + ::basegfx::B2DPolyPolygon( aPoly ), + rCanvas, rState, aStrokeAttributes ); + + if( pLineAction ) + { + maActions.emplace_back( + pLineAction, + io_rCurrActionIndex ); + + io_rCurrActionIndex += pLineAction->getActionCount()-1; + } + } + // else: line style is default + // (i.e. invisible), don't generate action + } + } + break; + + case MetaActionType::RECT: + { + const ::tools::Rectangle& rRect( + static_cast<MetaRectAction*>(pCurrAct)->GetRect() ); + + if( rRect.IsEmpty() ) + break; + + const OutDevState& rState( rStates.getState() ); + const ::basegfx::B2DPoint aTopLeftPixel( + rState.mapModeTransform * vcl::unotools::b2DPointFromPoint( rRect.TopLeft() ) ); + const ::basegfx::B2DPoint aBottomRightPixel( + rState.mapModeTransform * vcl::unotools::b2DPointFromPoint( rRect.BottomRight() ) + + // #121100# OutputDevice::DrawRect() fills + // rectangles Apple-like, i.e. with one + // additional pixel to the right and bottom. + ::basegfx::B2DPoint(1,1) ); + + createFillAndStroke( ::basegfx::utils::createPolygonFromRect( + ::basegfx::B2DRange( aTopLeftPixel, + aBottomRightPixel )), + rFactoryParms ); + break; + } + + case MetaActionType::ROUNDRECT: + { + const ::tools::Rectangle& rRect( + static_cast<MetaRoundRectAction*>(pCurrAct)->GetRect()); + + if( rRect.IsEmpty() ) + break; + + ::basegfx::B2DPolygon aPoly( + ::basegfx::utils::createPolygonFromRect( + ::basegfx::B2DRange( + vcl::unotools::b2DPointFromPoint( rRect.TopLeft() ), + vcl::unotools::b2DPointFromPoint( rRect.BottomRight() ) + + ::basegfx::B2DPoint(1,1) ), + static_cast<double>(static_cast<MetaRoundRectAction*>(pCurrAct)->GetHorzRound()) / rRect.GetWidth(), + static_cast<double>(static_cast<MetaRoundRectAction*>(pCurrAct)->GetVertRound()) / rRect.GetHeight() ) ); + aPoly.transform( rStates.getState().mapModeTransform ); + + createFillAndStroke( aPoly, + rFactoryParms ); + } + break; + + case MetaActionType::ELLIPSE: + { + const ::tools::Rectangle& rRect( + static_cast<MetaEllipseAction*>(pCurrAct)->GetRect() ); + + if( rRect.IsEmpty() ) + break; + + const ::basegfx::B2DRange aRange( + vcl::unotools::b2DPointFromPoint( rRect.TopLeft() ), + vcl::unotools::b2DPointFromPoint( rRect.BottomRight() ) + + ::basegfx::B2DPoint(1,1) ); + + ::basegfx::B2DPolygon aPoly( + ::basegfx::utils::createPolygonFromEllipse( + aRange.getCenter(), + aRange.getWidth() / 2, // divide by 2 since createPolygonFromEllipse + aRange.getHeight() / 2 )); // expects the radius and NOT the diameter! + aPoly.transform( rStates.getState().mapModeTransform ); + + createFillAndStroke( aPoly, + rFactoryParms ); + } + break; + + case MetaActionType::ARC: + { + // TODO(F1): Missing basegfx functionality. Mind empty rects! + const ::tools::Polygon aToolsPoly( static_cast<MetaArcAction*>(pCurrAct)->GetRect(), + static_cast<MetaArcAction*>(pCurrAct)->GetStartPoint(), + static_cast<MetaArcAction*>(pCurrAct)->GetEndPoint(), PolyStyle::Arc ); + ::basegfx::B2DPolygon aPoly( aToolsPoly.getB2DPolygon() ); + aPoly.transform( rStates.getState().mapModeTransform ); + + createFillAndStroke( aPoly, + rFactoryParms ); + } + break; + + case MetaActionType::PIE: + { + // TODO(F1): Missing basegfx functionality. Mind empty rects! + const ::tools::Polygon aToolsPoly( static_cast<MetaPieAction*>(pCurrAct)->GetRect(), + static_cast<MetaPieAction*>(pCurrAct)->GetStartPoint(), + static_cast<MetaPieAction*>(pCurrAct)->GetEndPoint(), PolyStyle::Pie ); + ::basegfx::B2DPolygon aPoly( aToolsPoly.getB2DPolygon() ); + aPoly.transform( rStates.getState().mapModeTransform ); + + createFillAndStroke( aPoly, + rFactoryParms ); + } + break; + + case MetaActionType::CHORD: + { + // TODO(F1): Missing basegfx functionality. Mind empty rects! + const ::tools::Polygon aToolsPoly( static_cast<MetaChordAction*>(pCurrAct)->GetRect(), + static_cast<MetaChordAction*>(pCurrAct)->GetStartPoint(), + static_cast<MetaChordAction*>(pCurrAct)->GetEndPoint(), PolyStyle::Chord ); + ::basegfx::B2DPolygon aPoly( aToolsPoly.getB2DPolygon() ); + aPoly.transform( rStates.getState().mapModeTransform ); + + createFillAndStroke( aPoly, + rFactoryParms ); + } + break; + + case MetaActionType::POLYLINE: + { + const OutDevState& rState( rStates.getState() ); + if( rState.lineColor.hasElements() || + rState.fillColor.hasElements() ) + { + MetaPolyLineAction* pPolyLineAct = static_cast<MetaPolyLineAction*>(pCurrAct); + + const LineInfo& rLineInfo( pPolyLineAct->GetLineInfo() ); + ::basegfx::B2DPolygon aPoly( pPolyLineAct->GetPolygon().getB2DPolygon() ); + aPoly.transform( rState.mapModeTransform ); + + std::shared_ptr<Action> pLineAction; + + if( rLineInfo.IsDefault() ) + { + // plain hair line polygon + pLineAction = + internal::PolyPolyActionFactory::createLinePolyPolyAction( + ::basegfx::B2DPolyPolygon(aPoly), + rCanvas, + rState ); + + if( pLineAction ) + { + maActions.emplace_back( + pLineAction, + io_rCurrActionIndex ); + + io_rCurrActionIndex += pLineAction->getActionCount()-1; + } + } + else if( LineStyle::NONE != rLineInfo.GetStyle() ) + { + // 'thick' line polygon + rendering::StrokeAttributes aStrokeAttributes; + + setupStrokeAttributes( aStrokeAttributes, + rFactoryParms, + rLineInfo ); + + pLineAction = + internal::PolyPolyActionFactory::createPolyPolyAction( + ::basegfx::B2DPolyPolygon(aPoly), + rCanvas, + rState, + aStrokeAttributes ) ; + + if( pLineAction ) + { + maActions.emplace_back( + pLineAction, + io_rCurrActionIndex ); + + io_rCurrActionIndex += pLineAction->getActionCount()-1; + } + } + // else: line style is default + // (i.e. invisible), don't generate action + } + } + break; + + case MetaActionType::POLYGON: + { + ::basegfx::B2DPolygon aPoly( static_cast<MetaPolygonAction*>(pCurrAct)->GetPolygon().getB2DPolygon() ); + aPoly.transform( rStates.getState().mapModeTransform ); + createFillAndStroke( aPoly, + rFactoryParms ); + } + break; + + case MetaActionType::POLYPOLYGON: + { + ::basegfx::B2DPolyPolygon aPoly( static_cast<MetaPolyPolygonAction*>(pCurrAct)->GetPolyPolygon().getB2DPolyPolygon() ); + aPoly.transform( rStates.getState().mapModeTransform ); + createFillAndStroke( aPoly, + rFactoryParms ); + } + break; + + case MetaActionType::BMP: + { + MetaBmpAction* pAct = static_cast<MetaBmpAction*>(pCurrAct); + + std::shared_ptr<Action> pBmpAction( + internal::BitmapActionFactory::createBitmapAction( + BitmapEx(pAct->GetBitmap()), + rStates.getState().mapModeTransform * + vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ), + rCanvas, + rStates.getState() ) ); + + if( pBmpAction ) + { + maActions.emplace_back( + pBmpAction, + io_rCurrActionIndex ); + + io_rCurrActionIndex += pBmpAction->getActionCount()-1; + } + } + break; + + case MetaActionType::BMPSCALE: + { + MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pCurrAct); + + std::shared_ptr<Action> pBmpAction( + internal::BitmapActionFactory::createBitmapAction( + BitmapEx(pAct->GetBitmap()), + rStates.getState().mapModeTransform * vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ), + rStates.getState().mapModeTransform * vcl::unotools::b2DVectorFromSize( pAct->GetSize() ), + rCanvas, + rStates.getState() ) ); + + if( pBmpAction ) + { + maActions.emplace_back( + pBmpAction, + io_rCurrActionIndex ); + + io_rCurrActionIndex += pBmpAction->getActionCount()-1; + } + } + break; + + case MetaActionType::BMPSCALEPART: + { + MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pCurrAct); + + // crop bitmap to given source rectangle (no + // need to copy and convert the whole bitmap) + ::Bitmap aBmp( pAct->GetBitmap() ); + const ::tools::Rectangle aCropRect( pAct->GetSrcPoint(), + pAct->GetSrcSize() ); + aBmp.Crop( aCropRect ); + + std::shared_ptr<Action> pBmpAction( + internal::BitmapActionFactory::createBitmapAction( + BitmapEx(aBmp), + rStates.getState().mapModeTransform * + vcl::unotools::b2DPointFromPoint( pAct->GetDestPoint() ), + rStates.getState().mapModeTransform * + vcl::unotools::b2DVectorFromSize( pAct->GetDestSize() ), + rCanvas, + rStates.getState() ) ); + + if( pBmpAction ) + { + maActions.emplace_back( + pBmpAction, + io_rCurrActionIndex ); + + io_rCurrActionIndex += pBmpAction->getActionCount()-1; + } + } + break; + + case MetaActionType::BMPEX: + { + MetaBmpExAction* pAct = static_cast<MetaBmpExAction*>(pCurrAct); + + std::shared_ptr<Action> pBmpAction( + internal::BitmapActionFactory::createBitmapAction( + pAct->GetBitmapEx(), + rStates.getState().mapModeTransform * + vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ), + rCanvas, + rStates.getState() ) ); + + if( pBmpAction ) + { + maActions.emplace_back( + pBmpAction, + io_rCurrActionIndex ); + + io_rCurrActionIndex += pBmpAction->getActionCount()-1; + } + } + break; + + case MetaActionType::BMPEXSCALE: + { + MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pCurrAct); + + std::shared_ptr<Action> pBmpAction( + internal::BitmapActionFactory::createBitmapAction( + pAct->GetBitmapEx(), + rStates.getState().mapModeTransform * + vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ), + rStates.getState().mapModeTransform * + vcl::unotools::b2DVectorFromSize( pAct->GetSize() ), + rCanvas, + rStates.getState() ) ); + + if( pBmpAction ) + { + maActions.emplace_back( + pBmpAction, + io_rCurrActionIndex ); + + io_rCurrActionIndex += pBmpAction->getActionCount()-1; + } + } + break; + + case MetaActionType::BMPEXSCALEPART: + { + MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pCurrAct); + + // crop bitmap to given source rectangle (no + // need to copy and convert the whole bitmap) + BitmapEx aBmp( pAct->GetBitmapEx() ); + const ::tools::Rectangle aCropRect( pAct->GetSrcPoint(), + pAct->GetSrcSize() ); + aBmp.Crop( aCropRect ); + + std::shared_ptr<Action> pBmpAction( + internal::BitmapActionFactory::createBitmapAction( + aBmp, + rStates.getState().mapModeTransform * + vcl::unotools::b2DPointFromPoint( pAct->GetDestPoint() ), + rStates.getState().mapModeTransform * + vcl::unotools::b2DVectorFromSize( pAct->GetDestSize() ), + rCanvas, + rStates.getState() ) ); + + if( pBmpAction ) + { + maActions.emplace_back( + pBmpAction, + io_rCurrActionIndex ); + + io_rCurrActionIndex += pBmpAction->getActionCount()-1; + } + } + break; + + case MetaActionType::MASK: + { + MetaMaskAction* pAct = static_cast<MetaMaskAction*>(pCurrAct); + + // create masked BitmapEx right here, as the + // canvas does not provide equivalent + // functionality + BitmapEx aBmp( createMaskBmpEx( pAct->GetBitmap(), + pAct->GetColor() )); + + std::shared_ptr<Action> pBmpAction( + internal::BitmapActionFactory::createBitmapAction( + aBmp, + rStates.getState().mapModeTransform * + vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ), + rCanvas, + rStates.getState() ) ); + + if( pBmpAction ) + { + maActions.emplace_back( + pBmpAction, + io_rCurrActionIndex ); + + io_rCurrActionIndex += pBmpAction->getActionCount()-1; + } + } + break; + + case MetaActionType::MASKSCALE: + { + MetaMaskScaleAction* pAct = static_cast<MetaMaskScaleAction*>(pCurrAct); + + // create masked BitmapEx right here, as the + // canvas does not provide equivalent + // functionality + BitmapEx aBmp( createMaskBmpEx( pAct->GetBitmap(), + pAct->GetColor() )); + + std::shared_ptr<Action> pBmpAction( + internal::BitmapActionFactory::createBitmapAction( + aBmp, + rStates.getState().mapModeTransform * + vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ), + rStates.getState().mapModeTransform * + vcl::unotools::b2DVectorFromSize( pAct->GetSize() ), + rCanvas, + rStates.getState() ) ); + + if( pBmpAction ) + { + maActions.emplace_back( + pBmpAction, + io_rCurrActionIndex ); + + io_rCurrActionIndex += pBmpAction->getActionCount()-1; + } + } + break; + + case MetaActionType::MASKSCALEPART: + { + MetaMaskScalePartAction* pAct = static_cast<MetaMaskScalePartAction*>(pCurrAct); + + // create masked BitmapEx right here, as the + // canvas does not provide equivalent + // functionality + BitmapEx aBmp( createMaskBmpEx( pAct->GetBitmap(), + pAct->GetColor() )); + + // crop bitmap to given source rectangle (no + // need to copy and convert the whole bitmap) + const ::tools::Rectangle aCropRect( pAct->GetSrcPoint(), + pAct->GetSrcSize() ); + aBmp.Crop( aCropRect ); + + std::shared_ptr<Action> pBmpAction( + internal::BitmapActionFactory::createBitmapAction( + aBmp, + rStates.getState().mapModeTransform * + vcl::unotools::b2DPointFromPoint( pAct->GetDestPoint() ), + rStates.getState().mapModeTransform * + vcl::unotools::b2DVectorFromSize( pAct->GetDestSize() ), + rCanvas, + rStates.getState() ) ); + + if( pBmpAction ) + { + maActions.emplace_back( + pBmpAction, + io_rCurrActionIndex ); + + io_rCurrActionIndex += pBmpAction->getActionCount()-1; + } + } + break; + + case MetaActionType::GRADIENTEX: + // TODO(F1): use native Canvas gradients here + // action is ignored here, because redundant to MetaActionType::GRADIENT + break; + + case MetaActionType::WALLPAPER: + // TODO(F2): NYI + break; + + case MetaActionType::Transparent: + { + const OutDevState& rState( rStates.getState() ); + if( rState.lineColor.hasElements() || + rState.fillColor.hasElements() ) + { + MetaTransparentAction* pAct = static_cast<MetaTransparentAction*>(pCurrAct); + ::basegfx::B2DPolyPolygon aPoly( pAct->GetPolyPolygon().getB2DPolyPolygon() ); + aPoly.transform( rState.mapModeTransform ); + + std::shared_ptr<Action> pPolyAction( + internal::PolyPolyActionFactory::createPolyPolyAction( + aPoly, + rCanvas, + rState, + pAct->GetTransparence() ) ); + + if( pPolyAction ) + { + maActions.emplace_back( + pPolyAction, + io_rCurrActionIndex ); + + io_rCurrActionIndex += pPolyAction->getActionCount()-1; + } + } + } + break; + + case MetaActionType::FLOATTRANSPARENT: + { + MetaFloatTransparentAction* pAct = static_cast<MetaFloatTransparentAction*>(pCurrAct); + + std::unique_ptr< GDIMetaFile > pMtf( + new ::GDIMetaFile( pAct->GetGDIMetaFile() ) ); + + // TODO(P2): Use native canvas gradients here (saves a lot of UNO calls) + std::optional< Gradient > pGradient( pAct->GetGradient() ); + + DBG_TESTSOLARMUTEX(); + + std::shared_ptr<Action> pFloatTransAction( + internal::TransparencyGroupActionFactory::createTransparencyGroupAction( + std::move(pMtf), + std::move(pGradient), + rStates.getState().mapModeTransform * + vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ), + rStates.getState().mapModeTransform * + vcl::unotools::b2DVectorFromSize( pAct->GetSize() ), + rCanvas, + rStates.getState() ) ); + + if( pFloatTransAction ) + { + maActions.emplace_back( + pFloatTransAction, + io_rCurrActionIndex ); + + io_rCurrActionIndex += pFloatTransAction->getActionCount()-1; + } + } + break; + + case MetaActionType::TEXT: + { + MetaTextAction* pAct = static_cast<MetaTextAction*>(pCurrAct); + OUString sText = pAct->GetText(); + + if (rVDev.GetDigitLanguage()) + sText = convertToLocalizedNumerals(sText, rVDev.GetDigitLanguage()); + + const sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex()); + + createTextAction( + pAct->GetPoint(), + sText, + pAct->GetIndex(), + nLen, + {}, + {}, + rFactoryParms, + bSubsettableActions ); + } + break; + + case MetaActionType::TEXTARRAY: + { + MetaTextArrayAction* pAct = static_cast<MetaTextArrayAction*>(pCurrAct); + OUString sText = pAct->GetText(); + + if (rVDev.GetDigitLanguage()) + sText = convertToLocalizedNumerals(sText, rVDev.GetDigitLanguage()); + + const sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex()); + + createTextAction( + pAct->GetPoint(), + sText, + pAct->GetIndex(), + nLen, + pAct->GetDXArray(), + pAct->GetKashidaArray(), + rFactoryParms, + bSubsettableActions ); + } + break; + + case MetaActionType::TEXTLINE: + { + MetaTextLineAction* pAct = static_cast<MetaTextLineAction*>(pCurrAct); + + const OutDevState& rState( rStates.getState() ); + const ::Size aBaselineOffset( tools::getBaselineOffset( rState, + rVDev ) ); + const ::basegfx::B2DSize aSize( rState.mapModeTransform * + ::basegfx::B2DSize(pAct->GetWidth(), + 0 )); + + std::shared_ptr<Action> pPolyAction( + PolyPolyActionFactory::createPolyPolyAction( + tools::createTextLinesPolyPolygon( + rState.mapModeTransform * + ::basegfx::B2DPoint( + vcl::unotools::b2DPointFromPoint(pAct->GetStartPoint()) + + vcl::unotools::b2DVectorFromSize(aBaselineOffset)), + aSize.getWidth(), + tools::createTextLineInfo( rVDev, + rState )), + rCanvas, + rState ) ); + + if( pPolyAction ) + { + maActions.emplace_back( + pPolyAction, + io_rCurrActionIndex ); + + io_rCurrActionIndex += pPolyAction->getActionCount()-1; + } + } + break; + + case MetaActionType::TEXTRECT: + { + MetaTextRectAction* pAct = static_cast<MetaTextRectAction*>(pCurrAct); + + rStates.pushState(vcl::PushFlags::ALL); + + // use the VDev to break up the text rect + // action into readily formatted lines + GDIMetaFile aTmpMtf; + rVDev.AddTextRectActions( pAct->GetRect(), + pAct->GetText(), + pAct->GetStyle(), + aTmpMtf ); + + createActions( aTmpMtf, + rFactoryParms, + bSubsettableActions ); + + rStates.popState(); + + break; + } + + case MetaActionType::STRETCHTEXT: + { + MetaStretchTextAction* pAct = static_cast<MetaStretchTextAction*>(pCurrAct); + OUString sText = pAct->GetText(); + + if (rVDev.GetDigitLanguage()) + sText = convertToLocalizedNumerals(sText, rVDev.GetDigitLanguage()); + + const sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex()); + + // #i70897# Nothing to do, actually... + if( nLen == 0 ) + break; + + // have to fit the text into the given + // width. This is achieved by internally + // generating a DX array, and uniformly + // distributing the excess/insufficient width + // to every logical character. + KernArray aDXArray; + + rVDev.GetTextArray( pAct->GetText(), &aDXArray, + pAct->GetIndex(), pAct->GetLen() ); + + const sal_Int32 nWidthDifference( pAct->GetWidth() - aDXArray[ nLen-1 ] ); + + // Last entry of pDXArray contains total width of the text + for (sal_Int32 i = 1; i <= nLen; ++i) + { + // calc ratio for every array entry, to + // distribute rounding errors 'evenly' + // across the characters. Note that each + // entry represents the 'end' position of + // the corresponding character, thus, we + // let i run from 1 to nLen. + aDXArray.adjust(i - 1, i * nWidthDifference / nLen); + } + + createTextAction( + pAct->GetPoint(), + sText, + pAct->GetIndex(), + nLen, + aDXArray, + {}, + rFactoryParms, + bSubsettableActions ); + } + break; + + default: + SAL_WARN( "cppcanvas.emf", "Unknown meta action type encountered" ); + break; + } + + // increment action index (each mtf action counts _at + // least_ one. Some count for more, therefore, + // io_rCurrActionIndex is sometimes incremented by + // pAct->getActionCount()-1 above, the -1 being the + // correction for the unconditional increment here). + ++io_rCurrActionIndex; + } + } + + + namespace + { + class ActionRenderer + { + public: + explicit ActionRenderer( ::basegfx::B2DHomMatrix aTransformation ) : + maTransformation(std::move( aTransformation )), + mbRet( true ) + { + } + + bool result() const + { + return mbRet; + } + + void operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rAction ) + { + // ANDing the result. We want to fail if at least + // one action failed. + mbRet &= rAction.mpAction->render( maTransformation ); + } + + void operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rAction, + const Action::Subset& rSubset ) + { + // ANDing the result. We want to fail if at least + // one action failed. + mbRet &= rAction.mpAction->renderSubset( maTransformation, + rSubset ); + } + + private: + ::basegfx::B2DHomMatrix maTransformation; + bool mbRet; + }; + + class AreaQuery + { + public: + explicit AreaQuery( ::basegfx::B2DHomMatrix aTransformation ) : + maTransformation(std::move( aTransformation )) + { + } + + static bool result() + { + return true; // nothing can fail here + } + + void operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rAction ) + { + maBounds.expand( rAction.mpAction->getBounds( maTransformation ) ); + } + + void operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rAction, + const Action::Subset& rSubset ) + { + maBounds.expand( rAction.mpAction->getBounds( maTransformation, + rSubset ) ); + } + + const ::basegfx::B2DRange& getBounds() const + { + return maBounds; + } + + private: + ::basegfx::B2DHomMatrix maTransformation; + ::basegfx::B2DRange maBounds; + }; + + // Doing that via inline class. Compilers tend to not inline free + // functions. + struct UpperBoundActionIndexComparator + { + bool operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rLHS, + const ::cppcanvas::internal::ImplRenderer::MtfAction& rRHS ) + { + const sal_Int32 nLHSCount( rLHS.mpAction ? + rLHS.mpAction->getActionCount() : 0 ); + const sal_Int32 nRHSCount( rRHS.mpAction ? + rRHS.mpAction->getActionCount() : 0 ); + + // compare end of action range, to have an action selected + // by lower_bound even if the requested index points in + // the middle of the action's range + return rLHS.mnOrigIndex + nLHSCount < rRHS.mnOrigIndex + nRHSCount; + } + }; + + /** Algorithm to apply given functor to a subset range + + @tpl Functor + + Functor to call for each element of the subset + range. Must provide the following method signatures: + bool result() (returning false if operation failed) + + */ + template< typename Functor > bool + forSubsetRange( Functor& rFunctor, + ImplRenderer::ActionVector::const_iterator aRangeBegin, + const ImplRenderer::ActionVector::const_iterator& aRangeEnd, + sal_Int32 nStartIndex, + sal_Int32 nEndIndex, + const ImplRenderer::ActionVector::const_iterator& rEnd ) + { + if( aRangeBegin == aRangeEnd ) + { + // only a single action. Setup subset, and call functor + Action::Subset aSubset; + aSubset.mnSubsetBegin = std::max( sal_Int32( 0 ), + nStartIndex - aRangeBegin->mnOrigIndex ); + aSubset.mnSubsetEnd = std::min( aRangeBegin->mpAction->getActionCount(), + nEndIndex - aRangeBegin->mnOrigIndex ); + + ENSURE_OR_RETURN_FALSE( aSubset.mnSubsetBegin >= 0 && aSubset.mnSubsetEnd >= 0, + "ImplRenderer::forSubsetRange(): Invalid indices" ); + + rFunctor( *aRangeBegin, aSubset ); + } + else + { + // more than one action. + + // render partial first, full intermediate, and + // partial last action + Action::Subset aSubset; + aSubset.mnSubsetBegin = std::max( sal_Int32( 0 ), + nStartIndex - aRangeBegin->mnOrigIndex ); + aSubset.mnSubsetEnd = aRangeBegin->mpAction->getActionCount(); + + ENSURE_OR_RETURN_FALSE( aSubset.mnSubsetBegin >= 0 && aSubset.mnSubsetEnd >= 0, + "ImplRenderer::forSubsetRange(): Invalid indices" ); + + rFunctor( *aRangeBegin, aSubset ); + + // first action rendered, skip to next + ++aRangeBegin; + + // render full middle actions + while( aRangeBegin != aRangeEnd ) + rFunctor( *aRangeBegin++ ); + + if( aRangeEnd == rEnd || + aRangeEnd->mnOrigIndex > nEndIndex ) + { + // aRangeEnd denotes end of action vector, + + // or + + // nEndIndex references something _after_ + // aRangeBegin, but _before_ aRangeEnd + + // either way: no partial action left + return rFunctor.result(); + } + + aSubset.mnSubsetBegin = 0; + aSubset.mnSubsetEnd = nEndIndex - aRangeEnd->mnOrigIndex; + + ENSURE_OR_RETURN_FALSE(aSubset.mnSubsetEnd >= 0, + "ImplRenderer::forSubsetRange(): Invalid indices" ); + + rFunctor( *aRangeEnd, aSubset ); + } + + return rFunctor.result(); + } + } + + bool ImplRenderer::getSubsetIndices( sal_Int32& io_rStartIndex, + sal_Int32& io_rEndIndex, + ActionVector::const_iterator& o_rRangeBegin, + ActionVector::const_iterator& o_rRangeEnd ) const + { + ENSURE_OR_RETURN_FALSE( io_rStartIndex<=io_rEndIndex, + "ImplRenderer::getSubsetIndices(): invalid action range" ); + + ENSURE_OR_RETURN_FALSE( !maActions.empty(), + "ImplRenderer::getSubsetIndices(): no actions to render" ); + + const sal_Int32 nMinActionIndex( maActions.front().mnOrigIndex ); + const sal_Int32 nMaxActionIndex( maActions.back().mnOrigIndex + + maActions.back().mpAction->getActionCount() ); + + // clip given range to permissible values (there might be + // ranges before and behind the valid indices) + io_rStartIndex = std::max( nMinActionIndex, + io_rStartIndex ); + io_rEndIndex = std::min( nMaxActionIndex, + io_rEndIndex ); + + if( io_rStartIndex == io_rEndIndex || + io_rStartIndex > io_rEndIndex ) + { + // empty range, don't render anything. The second + // condition e.g. happens if the requested range lies + // fully before or behind the valid action indices. + return false; + } + + + const ActionVector::const_iterator aBegin( maActions.begin() ); + const ActionVector::const_iterator aEnd( maActions.end() ); + + + // find start and end action + // ========================= + o_rRangeBegin = std::lower_bound( aBegin, aEnd, + MtfAction( std::shared_ptr<Action>(), io_rStartIndex ), + UpperBoundActionIndexComparator() ); + o_rRangeEnd = std::lower_bound( aBegin, aEnd, + MtfAction( std::shared_ptr<Action>(), io_rEndIndex ), + UpperBoundActionIndexComparator() ); + return true; + } + + + // Public methods + + + ImplRenderer::ImplRenderer( const CanvasSharedPtr& rCanvas, + const GDIMetaFile& rMtf, + const Parameters& rParams ) + : CanvasGraphicHelper(rCanvas) + , nFrameLeft(0) + , nFrameTop(0) + , nFrameRight(0) + , nFrameBottom(0) + , nPixX(0) + , nPixY(0) + , nMmX(0) + , nMmY(0) + { + SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::ImplRenderer::ImplRenderer(mtf)" ); + + OSL_ENSURE( rCanvas && rCanvas->getUNOCanvas().is(), + "ImplRenderer::ImplRenderer(): Invalid canvas" ); + OSL_ENSURE( rCanvas->getUNOCanvas()->getDevice().is(), + "ImplRenderer::ImplRenderer(): Invalid graphic device" ); + + // make sure canvas and graphic device are valid; action + // creation don't check that every time + if( !rCanvas || + !rCanvas->getUNOCanvas().is() || + !rCanvas->getUNOCanvas()->getDevice().is() ) + { + // leave actions empty + return; + } + + VectorOfOutDevStates aStateStack; + + ScopedVclPtrInstance< VirtualDevice > aVDev; + aVDev->EnableOutput( false ); + + // Setup VDev for state tracking and mapping + // ========================================= + + aVDev->SetMapMode( rMtf.GetPrefMapMode() ); + + const Size aMtfSize( rMtf.GetPrefSize() ); + const Size aMtfSizePixPre( aVDev->LogicToPixel( aMtfSize, + rMtf.GetPrefMapMode() ) ); + + // #i44110# correct null-sized output - there are shapes + // which have zero size in at least one dimension + // Remark the 1L cannot be replaced, that would cause max to compare long/int + const Size aMtfSizePix( std::max( aMtfSizePixPre.Width(), ::tools::Long(1) ), + std::max( aMtfSizePixPre.Height(), ::tools::Long(1) ) ); + + sal_Int32 nCurrActions(0); + ActionFactoryParameters aParms(aStateStack, + rCanvas, + *aVDev, + rParams, + nCurrActions ); + + // init state stack + aStateStack.clearStateStack(); + + // Setup local state, such that the metafile renders + // itself into a one-by-one square at the origin for + // identity view and render transformations + aStateStack.getState().transform.scale( 1.0 / aMtfSizePix.Width(), + 1.0 / aMtfSizePix.Height() ); + + tools::calcLogic2PixelAffineTransform( aStateStack.getState().mapModeTransform, + *aVDev ); + + { + ::cppcanvas::internal::OutDevState& rState = aStateStack.getState(); + // setup default text color to black + rState.textColor = + rState.textFillColor = + rState.textOverlineColor = + rState.textLineColor = tools::intSRGBAToDoubleSequence( 0x000000FF ); + } + + // apply overrides from the Parameters struct + if( rParams.maFillColor ) + { + ::cppcanvas::internal::OutDevState& rState = aStateStack.getState(); + rState.isFillColorSet = true; + rState.fillColor = tools::intSRGBAToDoubleSequence( *rParams.maFillColor ); + } + if( rParams.maLineColor ) + { + ::cppcanvas::internal::OutDevState& rState = aStateStack.getState(); + rState.isLineColorSet = true; + rState.lineColor = tools::intSRGBAToDoubleSequence( *rParams.maLineColor ); + } + if( rParams.maTextColor ) + { + ::cppcanvas::internal::OutDevState& rState = aStateStack.getState(); + rState.isTextFillColorSet = true; + rState.isTextOverlineColorSet = true; + rState.isTextLineColorSet = true; + rState.textColor = + rState.textFillColor = + rState.textOverlineColor = + rState.textLineColor = tools::intSRGBAToDoubleSequence( *rParams.maTextColor ); + } + if( rParams.maFontName || + rParams.maFontWeight || + rParams.maFontLetterForm || + rParams.maFontUnderline.has_value() ) + { + ::cppcanvas::internal::OutDevState& rState = aStateStack.getState(); + + rState.xFont = createFont( rState.fontRotation, + vcl::Font(), // default font + aParms ); + } + + /* EMF+ */ + createActions( const_cast<GDIMetaFile&>(rMtf), // HACK(Q2): + // we're + // changing + // the + // current + // action + // in + // createActions! + aParms, + true // TODO(P1): make subsettability configurable + ); + } + + ImplRenderer::~ImplRenderer() + { + } + + bool ImplRenderer::drawSubset( sal_Int32 nStartIndex, + sal_Int32 nEndIndex ) const + { + SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::ImplRenderer::drawSubset()" ); + + ActionVector::const_iterator aRangeBegin; + ActionVector::const_iterator aRangeEnd; + + try + { + if( !getSubsetIndices( nStartIndex, nEndIndex, + aRangeBegin, aRangeEnd ) ) + return true; // nothing to render (but _that_ was successful) + + // now, aRangeBegin references the action in which the + // subset rendering must start, and aRangeEnd references + // the action in which the subset rendering must end (it + // might also end right at the start of the referenced + // action, such that zero of that action needs to be + // rendered). + + + // render subset of actions + // ======================== + + ::basegfx::B2DHomMatrix aMatrix; + ::canvas::tools::getRenderStateTransform( aMatrix, + getRenderState() ); + + ActionRenderer aRenderer( aMatrix ); + + return forSubsetRange( aRenderer, + aRangeBegin, + aRangeEnd, + nStartIndex, + nEndIndex, + maActions.end() ); + } + catch( uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("cppcanvas.emf"); + // convert error to return value + return false; + } + } + + ::basegfx::B2DRange ImplRenderer::getSubsetArea( sal_Int32 nStartIndex, + sal_Int32 nEndIndex ) const + { + SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::ImplRenderer::getSubsetArea()" ); + + ActionVector::const_iterator aRangeBegin; + ActionVector::const_iterator aRangeEnd; + + if( !getSubsetIndices( nStartIndex, nEndIndex, + aRangeBegin, aRangeEnd ) ) + return ::basegfx::B2DRange(); // nothing to render -> empty range + + // now, aRangeBegin references the action in which the + // subset querying must start, and aRangeEnd references + // the action in which the subset querying must end (it + // might also end right at the start of the referenced + // action, such that zero of that action needs to be + // queried). + + + // query bounds for subset of actions + // ================================== + + ::basegfx::B2DHomMatrix aMatrix; + ::canvas::tools::getRenderStateTransform( aMatrix, + getRenderState() ); + + AreaQuery aQuery( aMatrix ); + forSubsetRange( aQuery, + aRangeBegin, + aRangeEnd, + nStartIndex, + nEndIndex, + maActions.end() ); + + return aQuery.getBounds(); + } + + bool ImplRenderer::draw() const + { + SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::ImplRenderer::draw()" ); + + ::basegfx::B2DHomMatrix aMatrix; + ::canvas::tools::getRenderStateTransform( aMatrix, + getRenderState() ); + + try + { + return std::for_each( maActions.begin(), maActions.end(), ActionRenderer( aMatrix ) ).result(); + } + catch( uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION( "cppcanvas.emf"); + return false; + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cppcanvas/source/mtfrenderer/lineaction.cxx b/cppcanvas/source/mtfrenderer/lineaction.cxx new file mode 100644 index 0000000000..4bc55d9299 --- /dev/null +++ b/cppcanvas/source/mtfrenderer/lineaction.cxx @@ -0,0 +1,154 @@ +/* -*- 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 "lineaction.hxx" +#include <outdevstate.hxx> + +#include <com/sun/star/rendering/XCanvas.hpp> + +#include <basegfx/range/b2drange.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <canvas/canvastools.hxx> +#include <sal/log.hxx> + +#include <cppcanvas/canvas.hxx> +#include <utility> + +#include "mtftools.hxx" + + +using namespace ::com::sun::star; + +namespace cppcanvas::internal +{ + namespace + { + class LineAction : public Action + { + public: + LineAction( const ::basegfx::B2DPoint&, + const ::basegfx::B2DPoint&, + CanvasSharedPtr, + const OutDevState& ); + + LineAction(const LineAction&) = delete; + const LineAction& operator=(const LineAction&) = 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: + ::basegfx::B2DPoint maStartPoint; + ::basegfx::B2DPoint maEndPoint; + CanvasSharedPtr mpCanvas; + rendering::RenderState maState; + }; + + LineAction::LineAction( const ::basegfx::B2DPoint& rStartPoint, + const ::basegfx::B2DPoint& rEndPoint, + CanvasSharedPtr xCanvas, + const OutDevState& rState ) : + maStartPoint( rStartPoint ), + maEndPoint( rEndPoint ), + mpCanvas(std::move( xCanvas )) + { + tools::initRenderState(maState,rState); + maState.DeviceColor = rState.lineColor; + } + + bool LineAction::render( const ::basegfx::B2DHomMatrix& rTransformation ) const + { + SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::LineAction::render()" ); + SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::LineAction: 0x" << std::hex << this ); + + rendering::RenderState aLocalState( maState ); + ::canvas::tools::prependToRenderState(aLocalState, rTransformation); + + mpCanvas->getUNOCanvas()->drawLine( ::basegfx::unotools::point2DFromB2DPoint(maStartPoint), + ::basegfx::unotools::point2DFromB2DPoint(maEndPoint), + mpCanvas->getViewState(), + aLocalState ); + + return true; + } + + bool LineAction::renderSubset( const ::basegfx::B2DHomMatrix& rTransformation, + const Subset& rSubset ) const + { + // line only contains a single action, fail if subset + // requests different range + if( rSubset.mnSubsetBegin != 0 || + rSubset.mnSubsetEnd != 1 ) + return false; + + return render( rTransformation ); + } + + ::basegfx::B2DRange LineAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const + { + rendering::RenderState aLocalState( maState ); + ::canvas::tools::prependToRenderState(aLocalState, rTransformation); + + return tools::calcDevicePixelBounds( ::basegfx::B2DRange( maStartPoint, + maEndPoint ), + mpCanvas->getViewState(), + aLocalState ); + } + + ::basegfx::B2DRange LineAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation, + const Subset& rSubset ) const + { + // line only contains a single action, empty bounds + // if subset requests different range + if( rSubset.mnSubsetBegin != 0 || + rSubset.mnSubsetEnd != 1 ) + return ::basegfx::B2DRange(); + + return getBounds( rTransformation ); + } + + sal_Int32 LineAction::getActionCount() const + { + return 1; + } + } + + std::shared_ptr<Action> LineActionFactory::createLineAction( const ::basegfx::B2DPoint& rStartPoint, + const ::basegfx::B2DPoint& rEndPoint, + const CanvasSharedPtr& rCanvas, + const OutDevState& rState ) + { + return std::make_shared<LineAction>( rStartPoint, + rEndPoint, + rCanvas, + rState); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cppcanvas/source/mtfrenderer/lineaction.hxx b/cppcanvas/source/mtfrenderer/lineaction.hxx new file mode 100644 index 0000000000..2b2913cfc0 --- /dev/null +++ b/cppcanvas/source/mtfrenderer/lineaction.hxx @@ -0,0 +1,54 @@ +/* -*- 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 . + */ + +#pragma once + +#include <action.hxx> +#include <cppcanvas/canvas.hxx> + +namespace basegfx { + class B2DPoint; +} + + +/* Definition of internal::LineActionFactory */ + +namespace cppcanvas::internal + { + struct OutDevState; + + /** Creates encapsulated converters between GDIMetaFile and + XCanvas. The Canvas argument is deliberately placed at the + constructor, to force reconstruction of this object for a + new canvas. This considerably eases internal state + handling, since a lot of the internal state (e.g. fonts, + text layout) is Canvas-dependent. + */ + namespace LineActionFactory + { + /// Plain hair line from point 1 to point 2 + std::shared_ptr<Action> createLineAction( const ::basegfx::B2DPoint&, + const ::basegfx::B2DPoint&, + const CanvasSharedPtr&, + const OutDevState& ); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cppcanvas/source/mtfrenderer/mtftools.cxx b/cppcanvas/source/mtfrenderer/mtftools.cxx new file mode 100644 index 0000000000..94a83da70c --- /dev/null +++ b/cppcanvas/source/mtfrenderer/mtftools.cxx @@ -0,0 +1,671 @@ +/* -*- 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/XCanvas.hpp> +#include <basegfx/utils/canvastools.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <canvas/canvastools.hxx> +#include <rtl/math.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/virdev.hxx> +#include <vcl/metric.hxx> +#include "mtftools.hxx" +#include <outdevstate.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + + +using namespace ::com::sun::star; + +namespace cppcanvas::tools +{ + void initRenderState( rendering::RenderState& renderState, + const ::cppcanvas::internal::OutDevState& outdevState ) + { + ::canvas::tools::initRenderState( renderState ); + ::canvas::tools::setRenderStateTransform( renderState, + outdevState.transform ); + renderState.Clip = outdevState.xClipPoly; + } + + ::Size getBaselineOffset( const ::cppcanvas::internal::OutDevState& outdevState, + const VirtualDevice& rVDev ) + { + const ::FontMetric& aMetric = rVDev.GetFontMetric(); + + // calc offset for text output, the XCanvas always renders + // baseline offset. + switch( outdevState.textReferencePoint ) + { + case ALIGN_TOP: + return ::Size( 0, + aMetric.GetInternalLeading() + aMetric.GetAscent() ); + + case ALIGN_BASELINE: + return ::Size( 0, 0 ); + + case ALIGN_BOTTOM: + return ::Size( 0, + -aMetric.GetDescent() ); + + default: + throw css::uno::RuntimeException( + "tools::getBaselineOffset(): Unexpected TextAlign value" ); + } + } + + ::basegfx::B2DHomMatrix& calcLogic2PixelLinearTransform( ::basegfx::B2DHomMatrix& o_rMatrix, + const VirtualDevice& rVDev ) + { + // select size value in the middle of the available range, + // to have headroom both when map mode scales up, and when + // it scales down. + const ::Size aSizeLogic( 0x00010000L, + 0x00010000L ); + + const ::Size aSizePixel( rVDev.LogicToPixel( aSizeLogic ) ); + + o_rMatrix = basegfx::utils::createScaleB2DHomMatrix( + aSizePixel.Width() / static_cast<double>(aSizeLogic.Width()), + aSizePixel.Height() / static_cast<double>(aSizeLogic.Height()) ); + + return o_rMatrix; + } + + ::basegfx::B2DHomMatrix& calcLogic2PixelAffineTransform( ::basegfx::B2DHomMatrix& o_rMatrix, + const VirtualDevice& rVDev ) + { + // retrieves scale + calcLogic2PixelLinearTransform(o_rMatrix, rVDev); + + // translate according to curr map mode/pref map mode offset + const ::Point aEmptyPoint; + const ::Point& rTranslatedPoint( + rVDev.LogicToPixel( aEmptyPoint )); + + o_rMatrix.translate(rTranslatedPoint.X(), + rTranslatedPoint.Y()); + + return o_rMatrix; + } + + bool modifyClip( rendering::RenderState& o_rRenderState, + const struct ::cppcanvas::internal::OutDevState& rOutdevState, + const CanvasSharedPtr& rCanvas, + const ::basegfx::B2DPoint& rOffset, + const ::basegfx::B2DVector* pScaling, + const double* pRotation ) + { + const bool bOffsetting( !rOffset.equalZero() ); + const bool bScaling( pScaling && + !rtl::math::approxEqual(pScaling->getX(), 1.0) && + !rtl::math::approxEqual(pScaling->getY(), 1.0) ); + const bool bRotation( pRotation && + *pRotation != 0.0 ); + + if( !bOffsetting && !bScaling && !bRotation ) + return false; // nothing to do + + if( rOutdevState.clip.count() ) + { + // general polygon case + + ::basegfx::B2DPolyPolygon aLocalClip( rOutdevState.clip ); + ::basegfx::B2DHomMatrix aTransform; + + if( bOffsetting ) + aTransform.translate( -rOffset.getX(), + -rOffset.getY() ); + if( bScaling ) + aTransform.scale( 1.0/pScaling->getX(), 1.0/pScaling->getY() ); + + if( bRotation ) + aTransform.rotate( - *pRotation ); + + aLocalClip.transform( aTransform ); + + o_rRenderState.Clip = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( + rCanvas->getUNOCanvas()->getDevice(), + aLocalClip ); + + return true; + } + else if( !rOutdevState.clipRect.IsEmpty() ) + { + // simple rect case + + const ::tools::Rectangle aLocalClipRect( rOutdevState.clipRect ); + + if( bRotation ) + { + // rotation involved - convert to polygon first, + // then transform that + ::basegfx::B2DPolygon aLocalClip( + ::basegfx::utils::createPolygonFromRect( + vcl::unotools::b2DRectangleFromRectangle(aLocalClipRect) ) ); + ::basegfx::B2DHomMatrix aTransform; + + if( bOffsetting ) + aTransform.translate( -rOffset.getX(), + -rOffset.getY() ); + if( bScaling ) + aTransform.scale( 1.0/pScaling->getX(), 1.0/pScaling->getY() ); + + aTransform.rotate( - *pRotation ); + + aLocalClip.transform( aTransform ); + + o_rRenderState.Clip = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( + rCanvas->getUNOCanvas()->getDevice(), + ::basegfx::B2DPolyPolygon( aLocalClip ) ); + } + else if( bScaling ) + { + // scale and offset - do it on the fly, have to + // convert to float anyway. + o_rRenderState.Clip = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( + rCanvas->getUNOCanvas()->getDevice(), + ::basegfx::B2DPolyPolygon( + ::basegfx::utils::createPolygonFromRect( + ::basegfx::B2DRectangle( + (aLocalClipRect.Left() - rOffset.getX())/pScaling->getX(), + (aLocalClipRect.Top() - rOffset.getY())/pScaling->getY(), + (aLocalClipRect.Right() - rOffset.getX())/pScaling->getX(), + (aLocalClipRect.Bottom() - rOffset.getY())/pScaling->getY() ) ) ) ); + } + else + { + // offset only - do it on the fly, have to convert + // to float anyway. + o_rRenderState.Clip = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( + rCanvas->getUNOCanvas()->getDevice(), + ::basegfx::B2DPolyPolygon( + ::basegfx::utils::createPolygonFromRect( + ::basegfx::B2DRectangle( aLocalClipRect.Left() - rOffset.getX(), + aLocalClipRect.Top() - rOffset.getY(), + aLocalClipRect.Right() - rOffset.getX(), + aLocalClipRect.Bottom() - rOffset.getY() ) ) ) ); + } + + return true; + } + + // empty clip, nothing to do + return false; + } + + // create overline/underline/strikeout line info struct + TextLineInfo createTextLineInfo( const ::VirtualDevice& rVDev, + const ::cppcanvas::internal::OutDevState& rState ) + { + const bool bOldMode( rVDev.IsMapModeEnabled() ); + + // #i68512# Force metric regeneration with mapmode enabled + // (prolly OutDev bug) + rVDev.GetFontMetric(); + + // will restore map mode below + const_cast< ::VirtualDevice& >(rVDev).EnableMapMode( false ); + + const ::FontMetric aMetric = rVDev.GetFontMetric(); + + TextLineInfo aTextInfo( + (aMetric.GetDescent() + 2) / 4.0, + ((aMetric.GetInternalLeading() + 1.5) / 3.0), + (aMetric.GetInternalLeading() / 2.0) - aMetric.GetAscent(), + aMetric.GetDescent() / 2.0, + (aMetric.GetInternalLeading() - aMetric.GetAscent()) / 3.0, + rState.textOverlineStyle, + rState.textUnderlineStyle, + rState.textStrikeoutStyle ); + + const_cast< ::VirtualDevice& >(rVDev).EnableMapMode( bOldMode ); + + return aTextInfo; + } + + namespace + { + void appendWaveline( ::basegfx::B2DPolyPolygon& o_rPoly, + const ::basegfx::B2DPoint& rStartPos, + const double nStartOffset, + const double nWidth, + const double nHeight, + sal_Int8 nLineStyle) + { + const double x(rStartPos.getX()); + const double y(rStartPos.getY() + nStartOffset + nHeight); + double nWaveWidth = nHeight * 10.6 * 0.25; + // Offset for the double line. + double nOffset = 0.0; + + if (nLineStyle == LINESTYLE_DOUBLEWAVE) + nOffset = -nHeight * 0.5; + else + nWaveWidth *= 2.0; + + basegfx::B2DPolygon aLine; + aLine.append(basegfx::B2DPoint(x, y + nOffset)); + aLine.append(basegfx::B2DPoint(x + nWidth, y + nOffset)); + + o_rPoly.append(::basegfx::utils::createWaveline(aLine, nWaveWidth, nWaveWidth * 0.5)); + + if (nLineStyle == LINESTYLE_DOUBLEWAVE) + { + nOffset = nHeight * 1.2; + + basegfx::B2DPolygon aLine2; + aLine2.append(basegfx::B2DPoint(x, y + nOffset)); + aLine2.append(basegfx::B2DPoint(x + nWidth, y + nOffset)); + o_rPoly.append(::basegfx::utils::createWaveline(aLine2, nWaveWidth, nWaveWidth * 0.5)); + } + } + + void appendRect( ::basegfx::B2DPolyPolygon& o_rPoly, + const ::basegfx::B2DPoint& rStartPos, + const double nX1, + const double nY1, + const double nX2, + const double nY2 ) + { + const double x( rStartPos.getX() ); + const double y( rStartPos.getY() ); + + o_rPoly.append( + ::basegfx::utils::createPolygonFromRect( + ::basegfx::B2DRectangle( x + nX1, y + nY1, x + nX2, y + nY2 ) ) ); + } + + void appendRect( ::basegfx::B2DPolyPolygon& o_rPoly, + const double nX1, + const double nY1, + const double nX2, + const double nY2 ) + { + o_rPoly.append( + ::basegfx::utils::createPolygonFromRect( + ::basegfx::B2DRectangle( nX1, nY1, nX2, nY2 ) ) ); + } + + bool appendDashes( ::basegfx::B2DPolyPolygon& o_rPoly, + const double nX, + double nY, + const double nLineWidth, + double nLineHeight, + sal_Int8 nLineStyle, + bool bIsOverline) + { + static const int aDottedArray[] = { 1, 1, 0}; // DOTTED LINE + static const int aDotDashArray[] = { 1, 1, 4, 1, 0}; // DASHDOT + static const int aDashDotDotArray[] = { 1, 1, 1, 1, 4, 1, 0}; // DASHDOTDOT + static const int aDashedArray[] = { 5, 2, 0}; // DASHED LINE + static const int aLongDashArray[] = { 7, 2, 0}; // LONGDASH + const int *pArray = nullptr; + bool bIsBold = false; + + switch(nLineStyle) + { + case LINESTYLE_BOLDDOTTED: + bIsBold = true; + [[fallthrough]]; + case LINESTYLE_DOTTED: + pArray = aDottedArray; + break; + + case LINESTYLE_BOLDDASH: + bIsBold = true; + [[fallthrough]]; + case LINESTYLE_DASH: + pArray = aDashedArray; + break; + + case LINESTYLE_BOLDLONGDASH: + bIsBold = true; + [[fallthrough]]; + case LINESTYLE_LONGDASH: + pArray = aLongDashArray; + break; + + case LINESTYLE_BOLDDASHDOT: + bIsBold = true; + [[fallthrough]]; + case LINESTYLE_DASHDOT: + pArray = aDotDashArray; + break; + case LINESTYLE_BOLDDASHDOTDOT: + bIsBold = true; + [[fallthrough]]; + case LINESTYLE_DASHDOTDOT: + pArray = aDashDotDotArray; + break; + } + + if (!pArray) + return false; + + if (bIsBold) + { + if (bIsOverline) + nY -= nLineHeight; + + nLineHeight *= 2; + } + + const double nEnd = nX + nLineWidth; + sal_Int32 nIndex = 0; + bool bAppend = true; + double nX1 = nX; + + while(nX1 < nEnd) + { + if (pArray[nIndex] == 0) + nIndex = 0; + + const double nX2 = std::min(nEnd, nX1 + pArray[nIndex] * nLineHeight); + + if (bAppend) + appendRect(o_rPoly, nX1, nY, nX2, nY + nLineHeight); + + nX1 = nX2; + + ++nIndex; + + bAppend = !bAppend; + } + return true; + } + + // create line actions for text such as underline and + // strikeout + void createOverlinePolyPolygon(::basegfx::B2DPolyPolygon& rTextLinesPolyPoly, + const ::basegfx::B2DPoint& rStartPos, + const double& rLineWidth, + const TextLineInfo& rTextLineInfo) + { + switch( rTextLineInfo.mnOverlineStyle ) + { + case LINESTYLE_NONE: // nothing to do + case LINESTYLE_DONTKNOW: + break; + + case LINESTYLE_DOUBLEWAVE: + case LINESTYLE_SMALLWAVE: + case LINESTYLE_BOLDWAVE: + case LINESTYLE_WAVE: + appendWaveline( + rTextLinesPolyPoly, + rStartPos, + rTextLineInfo.mnOverlineOffset, + rLineWidth, + rTextLineInfo.mnOverlineHeight, + rTextLineInfo.mnOverlineStyle); + + break; + case LINESTYLE_SINGLE: + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnOverlineOffset, + rLineWidth, + rTextLineInfo.mnOverlineOffset + rTextLineInfo.mnOverlineHeight ); + break; + case LINESTYLE_BOLD: + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnOverlineOffset - rTextLineInfo.mnOverlineHeight, + rLineWidth, + rTextLineInfo.mnOverlineOffset + rTextLineInfo.mnOverlineHeight ); + break; + + case LINESTYLE_DOUBLE: + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnOverlineOffset - rTextLineInfo.mnOverlineHeight * 2.0 , + rLineWidth, + rTextLineInfo.mnOverlineOffset - rTextLineInfo.mnOverlineHeight ); + + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnOverlineOffset + rTextLineInfo.mnOverlineHeight, + rLineWidth, + rTextLineInfo.mnOverlineOffset + rTextLineInfo.mnOverlineHeight * 2.0 ); + break; + + default: + if (!appendDashes( + rTextLinesPolyPoly, + rStartPos.getX(), + rStartPos.getY() + rTextLineInfo.mnOverlineOffset, + rLineWidth, + rTextLineInfo.mnOverlineHeight, + rTextLineInfo.mnOverlineStyle, + true)) + { + ENSURE_OR_THROW( false, + "::cppcanvas::internal::createTextLinesPolyPolygon(): Unexpected overline case" ); + } + } + } + + void createUnderlinePolyPolygon(::basegfx::B2DPolyPolygon& rTextLinesPolyPoly, + const ::basegfx::B2DPoint& rStartPos, + const double& rLineWidth, + const TextLineInfo& rTextLineInfo ) + { + + switch( rTextLineInfo.mnUnderlineStyle ) + { + case LINESTYLE_NONE: // nothing to do + case LINESTYLE_DONTKNOW: + break; + + case LINESTYLE_DOUBLEWAVE: + case LINESTYLE_SMALLWAVE: + case LINESTYLE_BOLDWAVE: + case LINESTYLE_WAVE: + appendWaveline( + rTextLinesPolyPoly, + rStartPos, + rTextLineInfo.mnUnderlineOffset, + rLineWidth, + rTextLineInfo.mnLineHeight, + rTextLineInfo.mnUnderlineStyle); + break; + case LINESTYLE_SINGLE: + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnUnderlineOffset, + rLineWidth, + rTextLineInfo.mnUnderlineOffset + rTextLineInfo.mnLineHeight ); + break; + + case LINESTYLE_BOLD: + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnUnderlineOffset, + rLineWidth, + rTextLineInfo.mnUnderlineOffset + 2*rTextLineInfo.mnLineHeight ); + break; + + case LINESTYLE_DOUBLE: + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnUnderlineOffset - rTextLineInfo.mnLineHeight, + rLineWidth, + rTextLineInfo.mnUnderlineOffset ); + + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnUnderlineOffset + 2*rTextLineInfo.mnLineHeight, + rLineWidth, + rTextLineInfo.mnUnderlineOffset + 3*rTextLineInfo.mnLineHeight ); + break; + + default: + if (!appendDashes( + rTextLinesPolyPoly, + rStartPos.getX(), + rStartPos.getY() + rTextLineInfo.mnUnderlineOffset, + rLineWidth, + rTextLineInfo.mnLineHeight, + rTextLineInfo.mnUnderlineStyle, + false)) + { + ENSURE_OR_THROW( false, + "::cppcanvas::internal::createTextLinesPolyPolygon(): Unexpected underline case" ); + } + } + } + + void createStrikeoutPolyPolygon(::basegfx::B2DPolyPolygon& rTextLinesPolyPoly, + const ::basegfx::B2DPoint& rStartPos, + const double& rLineWidth, + const TextLineInfo& rTextLineInfo) + { + switch( rTextLineInfo.mnStrikeoutStyle ) + { + case STRIKEOUT_NONE: // nothing to do + case STRIKEOUT_DONTKNOW: + break; + + case STRIKEOUT_SLASH: // TODO(Q1): we should handle this in the text layer + case STRIKEOUT_X: + break; + + case STRIKEOUT_SINGLE: + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnStrikeoutOffset, + rLineWidth, + rTextLineInfo.mnStrikeoutOffset + rTextLineInfo.mnLineHeight ); + break; + + case STRIKEOUT_BOLD: + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnStrikeoutOffset, + rLineWidth, + rTextLineInfo.mnStrikeoutOffset + 2*rTextLineInfo.mnLineHeight ); + break; + + case STRIKEOUT_DOUBLE: + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnStrikeoutOffset - rTextLineInfo.mnLineHeight, + rLineWidth, + rTextLineInfo.mnStrikeoutOffset ); + + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnStrikeoutOffset + 2*rTextLineInfo.mnLineHeight, + rLineWidth, + rTextLineInfo.mnStrikeoutOffset + 3*rTextLineInfo.mnLineHeight ); + break; + + default: + ENSURE_OR_THROW( false, + "::cppcanvas::internal::createTextLinesPolyPolygon(): Unexpected strikeout case" ); + } + } + } + + ::basegfx::B2DPolyPolygon createTextLinesPolyPolygon( const ::basegfx::B2DPoint& rStartPos, + const double& rLineWidth, + const TextLineInfo& rTextLineInfo ) + { + // fill the polypolygon with all text lines + ::basegfx::B2DPolyPolygon aTextLinesPolyPoly; + + createOverlinePolyPolygon(aTextLinesPolyPoly, rStartPos, rLineWidth, rTextLineInfo); + createUnderlinePolyPolygon(aTextLinesPolyPoly, rStartPos, rLineWidth, rTextLineInfo); + createStrikeoutPolyPolygon(aTextLinesPolyPoly, rStartPos, rLineWidth, rTextLineInfo); + return aTextLinesPolyPoly; + } + + ::basegfx::B2DRange calcDevicePixelBounds( const ::basegfx::B2DRange& rBounds, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ::basegfx::B2DHomMatrix aTransform; + ::canvas::tools::mergeViewAndRenderTransform( aTransform, + viewState, + renderState ); + + ::basegfx::B2DRange aTransformedBounds; + return ::canvas::tools::calcTransformedRectBounds( aTransformedBounds, + rBounds, + aTransform ); + } + + // create line actions for text such as underline and + // strikeout + ::basegfx::B2DPolyPolygon createTextLinesPolyPolygon( const double& rStartOffset, + const double& rLineWidth, + const TextLineInfo& rTextLineInfo ) + { + return createTextLinesPolyPolygon( + ::basegfx::B2DPoint( rStartOffset, + 0.0 ), + rLineWidth, + rTextLineInfo ); + } + + void createTextLinesPolyPolygon( const double& rStartOffset, + const double& rLineWidth, + const TextLineInfo& rTextLineInfo, + ::basegfx::B2DPolyPolygon& rOverlinePolyPoly, + ::basegfx::B2DPolyPolygon& rUnderlinePolyPoly, + ::basegfx::B2DPolyPolygon& rStrikeoutPolyPoly ) + { + ::basegfx::B2DPoint aStartPos(rStartOffset, 0.0); + + createOverlinePolyPolygon(rOverlinePolyPoly, aStartPos, rLineWidth, rTextLineInfo); + createUnderlinePolyPolygon(rUnderlinePolyPoly, aStartPos, rLineWidth, rTextLineInfo); + createStrikeoutPolyPolygon(rStrikeoutPolyPoly, aStartPos, rLineWidth, rTextLineInfo); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cppcanvas/source/mtfrenderer/mtftools.hxx b/cppcanvas/source/mtfrenderer/mtftools.hxx new file mode 100644 index 0000000000..13970a5187 --- /dev/null +++ b/cppcanvas/source/mtfrenderer/mtftools.hxx @@ -0,0 +1,212 @@ +/* -*- 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 . + */ + +#pragma once + +#include <action.hxx> +#include <cppcanvas/canvas.hxx> + + +class VirtualDevice; +class Size; + +namespace basegfx +{ + class B2DVector; + class B2DPoint; +} +namespace com::sun::star::rendering +{ + struct RenderState; +} + + +namespace cppcanvas +{ + namespace internal + { + struct OutDevState; + } + + namespace tools + { + /** Init render state from OutDevState + + This method initializes the given render state object, + sets up the transformation and the clip from the + OutDevState. + */ + void initRenderState( css::rendering::RenderState& renderState, + const ::cppcanvas::internal::OutDevState& outdevState ); + + /** Calc output offset relative to baseline + + The XCanvas API always renders text relative to its + baseline. This method calculates an offset in logical + coordinates, depending on the OutDevState's + textReferencePoint and the font currently set, to offset + the text from the baseline. + + @param outdevState + State to take textReferencePoint from + + @param rVDev + VDev to obtain font metrics from. + */ + ::Size getBaselineOffset( const ::cppcanvas::internal::OutDevState& outdevState, + const VirtualDevice& rVDev ); + + /** Construct a matrix that converts from logical to pixel + coordinate system. + + This method calculates a matrix that approximates the + VirtualDevice's LogicToPixel conversion (disregarding any + offset components, thus the 'linear' in the method name - + the returned matrix is guaranteed to be linear). + + @param o_rMatrix + This matrix will receive the calculated transform, and is + also returned from this method. + + @return the calculated transformation matrix. + */ + ::basegfx::B2DHomMatrix& calcLogic2PixelLinearTransform( ::basegfx::B2DHomMatrix& o_rMatrix, + const VirtualDevice& rVDev ); + + /** Construct a matrix that converts from logical to pixel + coordinate system. + + This method calculates a matrix that approximates the + VirtualDevice's LogicToPixel conversion. + + @param o_rMatrix + This matrix will receive the calculated transform, and is + also returned from this method. + + @return the calculated transformation matrix. + */ + ::basegfx::B2DHomMatrix& calcLogic2PixelAffineTransform( ::basegfx::B2DHomMatrix& o_rMatrix, + const VirtualDevice& rVDev ); + + /** This method modifies the clip, to cancel the given + transformation. + + As the clip is relative to the render state + transformation, offsetting or scaling the render state + must modify the clip, to keep it at the same position + relative to the primitive at hand + + @param o_rRenderState + Render state to change the clip in + + @param rOutdevState + Input state. Is used to retrieve the original clip from + + @param rOffset + The clip is offsetted by the negative of this value. + + @param pScaling + The clip is inversely scaled by this value (if given) + + @param pRotation + The clip is inversely rotated by this value (if given) + + @return true, if the clip has changed, false if not + */ + bool modifyClip( css::rendering::RenderState& o_rRenderState, + const struct ::cppcanvas::internal::OutDevState& rOutdevState, + const CanvasSharedPtr& rCanvas, + const ::basegfx::B2DPoint& rOffset, + const ::basegfx::B2DVector* pScaling, + const double* pRotation ); + + struct TextLineInfo + { + TextLineInfo( const double& rLineHeight, + const double& rOverlineHeight, + const double& rOverlineOffset, + const double& rUnderlineOffset, + const double& rStrikeoutOffset, + sal_Int8 nOverlineStyle, + sal_Int8 nUnderlineStyle, + sal_Int8 nStrikeoutStyle ) : + mnLineHeight( rLineHeight ), + mnOverlineHeight( rOverlineHeight ), + mnOverlineOffset( rOverlineOffset ), + mnUnderlineOffset( rUnderlineOffset ), + mnStrikeoutOffset( rStrikeoutOffset ), + mnOverlineStyle( nOverlineStyle ), + mnUnderlineStyle( nUnderlineStyle ), + mnStrikeoutStyle( nStrikeoutStyle ) + { + } + + double mnLineHeight; + double mnOverlineHeight; + double mnOverlineOffset; + double mnUnderlineOffset; + double mnStrikeoutOffset; + sal_Int8 mnOverlineStyle; + sal_Int8 mnUnderlineStyle; + sal_Int8 mnStrikeoutStyle; + }; + + /** Transform given bounds to device coordinate system. + */ + ::basegfx::B2DRange calcDevicePixelBounds( const ::basegfx::B2DRange& rBounds, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + + /** Generate text underline/strikeout info struct from OutDev + state. + */ + TextLineInfo createTextLineInfo( const ::VirtualDevice& rVDev, + const ::cppcanvas::internal::OutDevState& rState ); + + /** Create a poly-polygon representing the given combination + of overline, strikeout and underline. + + @param rStartOffset + Offset in X direction, where the underline starts + + @param rLineWidth + Width of the line of text to overline/strikeout/underline + + @param rTextLineInfo + Common info needed for overline/strikeout/underline generation + */ + ::basegfx::B2DPolyPolygon createTextLinesPolyPolygon( const double& rStartOffset, + const double& rLineWidth, + const TextLineInfo& rTextLineInfo ); + + ::basegfx::B2DPolyPolygon createTextLinesPolyPolygon( const ::basegfx::B2DPoint& rStartPos, + const double& rLineWidth, + const TextLineInfo& rTextLineInfo ); + + void createTextLinesPolyPolygon( const double& rStartOffset, + const double& rLineWidth, + const TextLineInfo& rTextLineInfo, + ::basegfx::B2DPolyPolygon& rOverlinePolyPoly, + ::basegfx::B2DPolyPolygon& rUnderlinePolyPoly, + ::basegfx::B2DPolyPolygon& rStrikeoutPolyPoly ); + + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cppcanvas/source/mtfrenderer/pointaction.cxx b/cppcanvas/source/mtfrenderer/pointaction.cxx new file mode 100644 index 0000000000..449f4b9b42 --- /dev/null +++ b/cppcanvas/source/mtfrenderer/pointaction.cxx @@ -0,0 +1,172 @@ +/* -*- 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 <com/sun/star/rendering/XCanvas.hpp> + +#include <sal/types.h> +#include <sal/log.hxx> +#include <utility> +#include <vcl/canvastools.hxx> + +#include <basegfx/range/b2drange.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <canvas/canvastools.hxx> + +#include "pointaction.hxx" +#include <outdevstate.hxx> +#include <cppcanvas/canvas.hxx> +#include "mtftools.hxx" + + +using namespace ::com::sun::star; + +namespace cppcanvas::internal +{ + namespace + { + class PointAction : public Action + { + public: + PointAction( const ::basegfx::B2DPoint&, + CanvasSharedPtr, + const OutDevState& ); + PointAction( const ::basegfx::B2DPoint&, + const CanvasSharedPtr&, + const OutDevState&, + const ::Color& ); + + PointAction(const PointAction&) = delete; + const PointAction& operator=(const PointAction&) = 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: + ::basegfx::B2DPoint maPoint; + CanvasSharedPtr mpCanvas; + css::rendering::RenderState maState; + }; + + PointAction::PointAction( const ::basegfx::B2DPoint& rPoint, + CanvasSharedPtr xCanvas, + const OutDevState& rState ) : + maPoint( rPoint ), + mpCanvas(std::move( xCanvas )) + { + tools::initRenderState(maState,rState); + maState.DeviceColor = rState.lineColor; + } + + PointAction::PointAction( const ::basegfx::B2DPoint& rPoint, + const CanvasSharedPtr& rCanvas, + const OutDevState& rState, + const ::Color& rAltColor ) : + maPoint( rPoint ), + mpCanvas( rCanvas ) + { + tools::initRenderState(maState,rState); + maState.DeviceColor = vcl::unotools::colorToDoubleSequence( + rAltColor, + rCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() ); + } + + bool PointAction::render( const ::basegfx::B2DHomMatrix& rTransformation ) const + { + SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::PointAction::render()" ); + SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::PointAction: 0x" << std::hex << this ); + + rendering::RenderState aLocalState( maState ); + ::canvas::tools::prependToRenderState(aLocalState, rTransformation); + + mpCanvas->getUNOCanvas()->drawPoint( ::basegfx::unotools::point2DFromB2DPoint(maPoint), + mpCanvas->getViewState(), + aLocalState ); + + return true; + } + + bool PointAction::renderSubset( const ::basegfx::B2DHomMatrix& rTransformation, + const Subset& rSubset ) const + { + // point only contains a single action, fail if subset + // requests different range + if( rSubset.mnSubsetBegin != 0 || + rSubset.mnSubsetEnd != 1 ) + return false; + + return render( rTransformation ); + } + + ::basegfx::B2DRange PointAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const + { + rendering::RenderState aLocalState( maState ); + ::canvas::tools::prependToRenderState(aLocalState, rTransformation); + + return tools::calcDevicePixelBounds( ::basegfx::B2DRange( maPoint.getX()-1, + maPoint.getY()-1, + maPoint.getX()+1, + maPoint.getY()+1 ), + mpCanvas->getViewState(), + aLocalState ); + } + + ::basegfx::B2DRange PointAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation, + const Subset& rSubset ) const + { + // point only contains a single action, empty bounds + // if subset requests different range + if( rSubset.mnSubsetBegin != 0 || + rSubset.mnSubsetEnd != 1 ) + return ::basegfx::B2DRange(); + + return getBounds( rTransformation ); + } + + sal_Int32 PointAction::getActionCount() const + { + return 1; + } + } + + std::shared_ptr<Action> PointActionFactory::createPointAction( const ::basegfx::B2DPoint& rPoint, + const CanvasSharedPtr& rCanvas, + const OutDevState& rState ) + { + return std::make_shared<PointAction>( rPoint, rCanvas, rState ); + } + + std::shared_ptr<Action> PointActionFactory::createPointAction( const ::basegfx::B2DPoint& rPoint, + const CanvasSharedPtr& rCanvas, + const OutDevState& rState, + const ::Color& rColor ) + { + return std::make_shared<PointAction>( rPoint, rCanvas, rState, rColor ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cppcanvas/source/mtfrenderer/pointaction.hxx b/cppcanvas/source/mtfrenderer/pointaction.hxx new file mode 100644 index 0000000000..d51ccb5dda --- /dev/null +++ b/cppcanvas/source/mtfrenderer/pointaction.hxx @@ -0,0 +1,59 @@ +/* -*- 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 . + */ + +#pragma once + +#include <action.hxx> +#include <cppcanvas/canvas.hxx> + +class Color; +namespace basegfx { + class B2DPoint; +} + +/* Definition of internal::PointActionFactory */ + +namespace cppcanvas::internal + { + struct OutDevState; + + /** Creates encapsulated converters between GDIMetaFile and + XCanvas. The Canvas argument is deliberately placed at the + constructor, to force reconstruction of this object for a + new canvas. This considerably eases internal state + handling, since a lot of the internal state (e.g. fonts, + text layout) is Canvas-dependent. + */ + namespace PointActionFactory + { + /// Point in current color + std::shared_ptr<Action> createPointAction( const ::basegfx::B2DPoint&, + const CanvasSharedPtr&, + const OutDevState& ); + + /// Point in given color + std::shared_ptr<Action> createPointAction( const ::basegfx::B2DPoint&, + const CanvasSharedPtr&, + const OutDevState&, + const ::Color& ); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cppcanvas/source/mtfrenderer/polypolyaction.cxx b/cppcanvas/source/mtfrenderer/polypolyaction.cxx new file mode 100644 index 0000000000..955727a313 --- /dev/null +++ b/cppcanvas/source/mtfrenderer/polypolyaction.cxx @@ -0,0 +1,500 @@ +/* -*- 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 <com/sun/star/rendering/XCanvas.hpp> + +#include <sal/types.h> + +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <canvas/canvastools.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> + +#include "cachedprimitivebase.hxx" +#include "polypolyaction.hxx" +#include <outdevstate.hxx> +#include <utility> +#include "mtftools.hxx" + + +using namespace ::com::sun::star; + +namespace cppcanvas::internal +{ + namespace + { + class PolyPolyAction : public CachedPrimitiveBase + { + public: + PolyPolyAction( const ::basegfx::B2DPolyPolygon&, + const CanvasSharedPtr&, + const OutDevState&, + bool bFill, + bool bStroke ); + PolyPolyAction( const ::basegfx::B2DPolyPolygon&, + const CanvasSharedPtr&, + const OutDevState&, + bool bFill, + bool bStroke, + int nTransparency ); + + 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: + using Action::render; + virtual bool renderPrimitive( uno::Reference< rendering::XCachedPrimitive >& rCachedPrimitive, + const ::basegfx::B2DHomMatrix& rTransformation ) const override; + + const uno::Reference< rendering::XPolyPolygon2D > mxPolyPoly; + const ::basegfx::B2DRange maBounds; + const CanvasSharedPtr mpCanvas; + + // stroke color is now implicit: the maState.DeviceColor member + rendering::RenderState maState; + + uno::Sequence< double > maFillColor; + }; + + PolyPolyAction::PolyPolyAction( const ::basegfx::B2DPolyPolygon& rPolyPoly, + const CanvasSharedPtr& rCanvas, + const OutDevState& rState, + bool bFill, + bool bStroke ) : + CachedPrimitiveBase( rCanvas, false ), + mxPolyPoly( ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( rCanvas->getUNOCanvas()->getDevice(), rPolyPoly) ), + maBounds( ::basegfx::utils::getRange(rPolyPoly) ), + mpCanvas( rCanvas ) + { + tools::initRenderState(maState,rState); + + if( bFill ) + maFillColor = rState.fillColor; + + if( bStroke ) + maState.DeviceColor = rState.lineColor; + } + + PolyPolyAction::PolyPolyAction( const ::basegfx::B2DPolyPolygon& rPolyPoly, + const CanvasSharedPtr& rCanvas, + const OutDevState& rState, + bool bFill, + bool bStroke, + int nTransparency ) : + CachedPrimitiveBase( rCanvas, false ), + mxPolyPoly( ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( rCanvas->getUNOCanvas()->getDevice(), rPolyPoly) ), + maBounds( ::basegfx::utils::getRange(rPolyPoly) ), + mpCanvas( rCanvas ) + { + tools::initRenderState(maState,rState); + + if( bFill ) + { + maFillColor = rState.fillColor; + + if( maFillColor.getLength() < 4 ) + maFillColor.realloc( 4 ); + + // TODO(F1): Color management + // adapt fill color transparency + maFillColor.getArray()[3] = 1.0 - nTransparency / 100.0; + } + + if( bStroke ) + { + maState.DeviceColor = rState.lineColor; + + if( maState.DeviceColor.getLength() < 4 ) + maState.DeviceColor.realloc( 4 ); + + // TODO(F1): Color management + // adapt fill color transparency + maState.DeviceColor.getArray()[3] = 1.0 - nTransparency / 100.0; + } + } + + bool PolyPolyAction::renderPrimitive( uno::Reference< rendering::XCachedPrimitive >& rCachedPrimitive, + const ::basegfx::B2DHomMatrix& rTransformation ) const + { + SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::PolyPolyAction::renderPrimitive()" ); + SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::PolyPolyAction: 0x" << std::hex << this ); + + rendering::RenderState aLocalState( maState ); + ::canvas::tools::prependToRenderState(aLocalState, rTransformation); + + if( maFillColor.hasElements() ) + { + // TODO(E3): Use DBO's finalizer here, + // fillPolyPolygon() might throw + const uno::Sequence< double > aTmpColor( aLocalState.DeviceColor ); + aLocalState.DeviceColor = maFillColor; + + rCachedPrimitive = mpCanvas->getUNOCanvas()->fillPolyPolygon( mxPolyPoly, + mpCanvas->getViewState(), + aLocalState ); + + aLocalState.DeviceColor = aTmpColor; + } + + if( aLocalState.DeviceColor.hasElements() ) + { + rCachedPrimitive = mpCanvas->getUNOCanvas()->drawPolyPolygon( mxPolyPoly, + mpCanvas->getViewState(), + aLocalState ); + } + + return true; + } + + bool PolyPolyAction::renderSubset( const ::basegfx::B2DHomMatrix& rTransformation, + const Subset& rSubset ) const + { + // TODO(F1): Split up poly-polygon into polygons, or even + // line segments, when subsets are requested. + + // polygon only contains a single action, fail if subset + // requests different range + if( rSubset.mnSubsetBegin != 0 || + rSubset.mnSubsetEnd != 1 ) + return false; + + return CachedPrimitiveBase::render( rTransformation ); + } + + ::basegfx::B2DRange PolyPolyAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const + { + rendering::RenderState aLocalState( maState ); + ::canvas::tools::prependToRenderState(aLocalState, rTransformation); + + return tools::calcDevicePixelBounds( + maBounds, + mpCanvas->getViewState(), + aLocalState ); + } + + ::basegfx::B2DRange PolyPolyAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation, + const Subset& rSubset ) const + { + // TODO(F1): Split up poly-polygon into polygons, or even + // line segments, when subsets are requested. + + // polygon only contains a single action, empty bounds + // if subset requests different range + if( rSubset.mnSubsetBegin != 0 || + rSubset.mnSubsetEnd != 1 ) + return ::basegfx::B2DRange(); + + return getBounds( rTransformation ); + } + + sal_Int32 PolyPolyAction::getActionCount() const + { + // TODO(F1): Split up poly-polygon into polygons, or even + // line segments, when subsets are requested. + return 1; + } + + + class TexturedPolyPolyAction : public CachedPrimitiveBase + { + public: + TexturedPolyPolyAction( const ::basegfx::B2DPolyPolygon& rPoly, + const CanvasSharedPtr& rCanvas, + const OutDevState& rState, + const rendering::Texture& rTexture ); + + 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: + using Action::render; + virtual bool renderPrimitive( uno::Reference< rendering::XCachedPrimitive >& rCachedPrimitive, + const ::basegfx::B2DHomMatrix& rTransformation ) const override; + + const uno::Reference< rendering::XPolyPolygon2D > mxPolyPoly; + const ::basegfx::B2DRectangle maBounds; + const CanvasSharedPtr mpCanvas; + + // stroke color is now implicit: the maState.DeviceColor member + rendering::RenderState maState; + const rendering::Texture maTexture; + }; + + TexturedPolyPolyAction::TexturedPolyPolyAction( const ::basegfx::B2DPolyPolygon& rPolyPoly, + const CanvasSharedPtr& rCanvas, + const OutDevState& rState, + const rendering::Texture& rTexture ) : + CachedPrimitiveBase( rCanvas, true ), + mxPolyPoly( ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( rCanvas->getUNOCanvas()->getDevice(), rPolyPoly) ), + maBounds( ::basegfx::utils::getRange(rPolyPoly) ), + mpCanvas( rCanvas ), + maTexture( rTexture ) + { + tools::initRenderState(maState,rState); + } + + bool TexturedPolyPolyAction::renderPrimitive( uno::Reference< rendering::XCachedPrimitive >& rCachedPrimitive, + const ::basegfx::B2DHomMatrix& rTransformation ) const + { + SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::PolyPolyAction::renderPrimitive()" ); + SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::PolyPolyAction: 0x" << std::hex << this ); + + rendering::RenderState aLocalState( maState ); + ::canvas::tools::prependToRenderState(aLocalState, rTransformation); + + uno::Sequence< rendering::Texture > aSeq { maTexture }; + + rCachedPrimitive = mpCanvas->getUNOCanvas()->fillTexturedPolyPolygon( mxPolyPoly, + mpCanvas->getViewState(), + aLocalState, + aSeq ); + return true; + } + + bool TexturedPolyPolyAction::renderSubset( const ::basegfx::B2DHomMatrix& rTransformation, + const Subset& rSubset ) const + { + // TODO(F1): Split up poly-polygon into polygons, or even + // line segments, when subsets are requested. + + // polygon only contains a single action, fail if subset + // requests different range + if( rSubset.mnSubsetBegin != 0 || + rSubset.mnSubsetEnd != 1 ) + return false; + + return CachedPrimitiveBase::render( rTransformation ); + } + + ::basegfx::B2DRange TexturedPolyPolyAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const + { + rendering::RenderState aLocalState( maState ); + ::canvas::tools::prependToRenderState(aLocalState, rTransformation); + + return tools::calcDevicePixelBounds( + maBounds, + mpCanvas->getViewState(), + aLocalState ); + } + + ::basegfx::B2DRange TexturedPolyPolyAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation, + const Subset& rSubset ) const + { + // TODO(F1): Split up poly-polygon into polygons, or even + // line segments, when subsets are requested. + + // polygon only contains a single action, empty bounds + // if subset requests different range + if( rSubset.mnSubsetBegin != 0 || + rSubset.mnSubsetEnd != 1 ) + return ::basegfx::B2DRange(); + + return getBounds( rTransformation ); + } + + sal_Int32 TexturedPolyPolyAction::getActionCount() const + { + // TODO(F1): Split up poly-polygon into polygons, or even + // line segments, when subsets are requested. + return 1; + } + + + class StrokedPolyPolyAction : public CachedPrimitiveBase + { + public: + StrokedPolyPolyAction( const ::basegfx::B2DPolyPolygon& rPoly, + const CanvasSharedPtr& rCanvas, + const OutDevState& rState, + rendering::StrokeAttributes aStrokeAttributes ); + + 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: + using Action::render; + virtual bool renderPrimitive( uno::Reference< rendering::XCachedPrimitive >& rCachedPrimitive, + const ::basegfx::B2DHomMatrix& rTransformation ) const override; + + const uno::Reference< rendering::XPolyPolygon2D > mxPolyPoly; + const ::basegfx::B2DRectangle maBounds; + const CanvasSharedPtr mpCanvas; + rendering::RenderState maState; + const rendering::StrokeAttributes maStrokeAttributes; + }; + + StrokedPolyPolyAction::StrokedPolyPolyAction( const ::basegfx::B2DPolyPolygon& rPolyPoly, + const CanvasSharedPtr& rCanvas, + const OutDevState& rState, + rendering::StrokeAttributes aStrokeAttributes ) : + CachedPrimitiveBase( rCanvas, false ), + mxPolyPoly( ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( rCanvas->getUNOCanvas()->getDevice(), rPolyPoly) ), + maBounds( ::basegfx::utils::getRange(rPolyPoly) ), + mpCanvas( rCanvas ), + maStrokeAttributes(std::move( aStrokeAttributes )) + { + tools::initRenderState(maState,rState); + maState.DeviceColor = rState.lineColor; + } + + bool StrokedPolyPolyAction::renderPrimitive( uno::Reference< rendering::XCachedPrimitive >& rCachedPrimitive, + const ::basegfx::B2DHomMatrix& rTransformation ) const + { + SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::PolyPolyAction::renderPrimitive()" ); + SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::PolyPolyAction: 0x" << std::hex << this ); + + rendering::RenderState aLocalState( maState ); + ::canvas::tools::prependToRenderState(aLocalState, rTransformation); + + rCachedPrimitive = mpCanvas->getUNOCanvas()->strokePolyPolygon( mxPolyPoly, + mpCanvas->getViewState(), + aLocalState, + maStrokeAttributes ); + return true; + } + + bool StrokedPolyPolyAction::renderSubset( const ::basegfx::B2DHomMatrix& rTransformation, + const Subset& rSubset ) const + { + // TODO(F1): Split up poly-polygon into polygons, or even + // line segments, when subsets are requested. + + // polygon only contains a single action, fail if subset + // requests different range + if( rSubset.mnSubsetBegin != 0 || + rSubset.mnSubsetEnd != 1 ) + return false; + + return CachedPrimitiveBase::render( rTransformation ); + } + + ::basegfx::B2DRange StrokedPolyPolyAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const + { + rendering::RenderState aLocalState( maState ); + ::canvas::tools::prependToRenderState(aLocalState, rTransformation); + + return tools::calcDevicePixelBounds( + maBounds, + mpCanvas->getViewState(), + aLocalState ); + } + + ::basegfx::B2DRange StrokedPolyPolyAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation, + const Subset& rSubset ) const + { + // TODO(F1): Split up poly-polygon into polygons, or even + // line segments, when subsets are requested. + + // polygon only contains a single action, empty bounds + // if subset requests different range + if( rSubset.mnSubsetBegin != 0 || + rSubset.mnSubsetEnd != 1 ) + return ::basegfx::B2DRange(); + + return getBounds( rTransformation ); + } + + sal_Int32 StrokedPolyPolyAction::getActionCount() const + { + // TODO(F1): Split up poly-polygon into polygons, or even + // line segments, when subsets are requested. + return 1; + } + } + + std::shared_ptr<Action> PolyPolyActionFactory::createPolyPolyAction( const ::basegfx::B2DPolyPolygon& rPoly, + const CanvasSharedPtr& rCanvas, + const OutDevState& rState ) + { + OSL_ENSURE( rState.isLineColorSet || rState.isFillColorSet, + "PolyPolyActionFactory::createPolyPolyAction() with empty line and fill color" ); + return std::make_shared<PolyPolyAction>( rPoly, rCanvas, rState, + rState.isFillColorSet, + rState.isLineColorSet ); + } + + std::shared_ptr<Action> PolyPolyActionFactory::createPolyPolyAction( const ::basegfx::B2DPolyPolygon& rPoly, + const CanvasSharedPtr& rCanvas, + const OutDevState& rState, + const rendering::Texture& rTexture ) + { + return std::make_shared<TexturedPolyPolyAction>( rPoly, rCanvas, rState, rTexture ); + } + + std::shared_ptr<Action> PolyPolyActionFactory::createLinePolyPolyAction( const ::basegfx::B2DPolyPolygon& rPoly, + const CanvasSharedPtr& rCanvas, + const OutDevState& rState ) + { + OSL_ENSURE( rState.isLineColorSet, + "PolyPolyActionFactory::createLinePolyPolyAction() called with empty line color" ); + + return std::make_shared<PolyPolyAction>( rPoly, rCanvas, rState, + false, + rState.isLineColorSet ); + } + + std::shared_ptr<Action> PolyPolyActionFactory::createPolyPolyAction( const ::basegfx::B2DPolyPolygon& rPoly, + const CanvasSharedPtr& rCanvas, + const OutDevState& rState, + const rendering::StrokeAttributes& rStrokeAttributes ) + { + OSL_ENSURE( rState.isLineColorSet, + "PolyPolyActionFactory::createPolyPolyAction() for strokes called with empty line color" ); + return std::make_shared<StrokedPolyPolyAction>( rPoly, rCanvas, rState, rStrokeAttributes ); + } + + std::shared_ptr<Action> PolyPolyActionFactory::createPolyPolyAction( const ::basegfx::B2DPolyPolygon& rPoly, + const CanvasSharedPtr& rCanvas, + const OutDevState& rState, + int nTransparency ) + { + OSL_ENSURE( rState.isLineColorSet || rState.isFillColorSet, + "PolyPolyActionFactory::createPolyPolyAction() with empty line and fill color" ); + return std::make_shared<PolyPolyAction>( rPoly, rCanvas, rState, + rState.isFillColorSet, + rState.isLineColorSet, + nTransparency ); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cppcanvas/source/mtfrenderer/polypolyaction.hxx b/cppcanvas/source/mtfrenderer/polypolyaction.hxx new file mode 100644 index 0000000000..2ddfd55b4b --- /dev/null +++ b/cppcanvas/source/mtfrenderer/polypolyaction.hxx @@ -0,0 +1,82 @@ +/* -*- 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 . + */ + +#pragma once + +#include <action.hxx> +#include <cppcanvas/canvas.hxx> + + +namespace basegfx { + class B2DPolyPolygon; +} +namespace com::sun::star::rendering +{ + struct Texture; + struct StrokeAttributes; +} + + +/* Definition of internal::PolyPolyActionFactory */ + +namespace cppcanvas::internal + { + struct OutDevState; + + /** Creates encapsulated converters between GDIMetaFile and + XCanvas. The Canvas argument is deliberately placed at the + constructor, to force reconstruction of this object for a + new canvas. This considerably eases internal state + handling, since a lot of the internal state (e.g. fonts, + text layout) is Canvas-dependent. + */ + namespace PolyPolyActionFactory + { + /// Create polygon, fill/stroke according to state + std::shared_ptr<Action> createPolyPolyAction( const ::basegfx::B2DPolyPolygon&, + const CanvasSharedPtr&, + const OutDevState& ); + + /// Create texture-filled polygon + std::shared_ptr<Action> createPolyPolyAction( const ::basegfx::B2DPolyPolygon&, + const CanvasSharedPtr&, + const OutDevState&, + const css::rendering::Texture& ); + + /// Create line polygon (always stroked, not filled) + std::shared_ptr<Action> createLinePolyPolyAction( const ::basegfx::B2DPolyPolygon&, + const CanvasSharedPtr&, + const OutDevState& ); + + /// Create stroked polygon + std::shared_ptr<Action> createPolyPolyAction( const ::basegfx::B2DPolyPolygon&, + const CanvasSharedPtr&, + const OutDevState&, + const css::rendering::StrokeAttributes& ); + + /// For transparent painting of the given polygon (normally, we take the colors always opaque) + std::shared_ptr<Action> createPolyPolyAction( const ::basegfx::B2DPolyPolygon&, + const CanvasSharedPtr&, + const OutDevState&, + int nTransparency ); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 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: */ diff --git a/cppcanvas/source/mtfrenderer/textaction.hxx b/cppcanvas/source/mtfrenderer/textaction.hxx new file mode 100644 index 0000000000..9f7349cacc --- /dev/null +++ b/cppcanvas/source/mtfrenderer/textaction.hxx @@ -0,0 +1,84 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> + +#include <span> + +#include <action.hxx> +#include <cppcanvas/canvas.hxx> +#include <cppcanvas/renderer.hxx> + +class VirtualDevice; +class Point; +class Size; +class Color; + + +/* Definition of internal::TextActionFactory */ + +namespace cppcanvas::internal + { + struct OutDevState; + + /** Creates encapsulated converters between GDIMetaFile and + XCanvas. The Canvas argument is deliberately placed at the + constructor, to force reconstruction of this object for a + new canvas. This considerably eases internal state + handling, since a lot of the internal state (e.g. fonts, + text layout) is Canvas-dependent. + */ + namespace TextActionFactory + { + /** Create text action, optionally shadow/relief effect + + Note that this method accepts all coordinates in + logical coordinates. + + @param pDXArray + Pointer to array of logical character offsets (or NULL) + + @param bSubsettable + When this parameter is set to true, the generated + action might consume slightly more memory, but is + subsettable (Action::render( Subset ) works on + characters) + */ + std::shared_ptr<Action> 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 ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cppcanvas/source/mtfrenderer/textlineshelper.cxx b/cppcanvas/source/mtfrenderer/textlineshelper.cxx new file mode 100644 index 0000000000..125c3385af --- /dev/null +++ b/cppcanvas/source/mtfrenderer/textlineshelper.cxx @@ -0,0 +1,125 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/StrokeAttributes.hpp> +#include <com/sun/star/rendering/PathJoinType.hpp> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <outdevstate.hxx> +#include <utility> +#include "textlineshelper.hxx" +#include "mtftools.hxx" + +using namespace ::com::sun::star; + +namespace +{ +void initLineStyleWaveline(sal_uInt32 nLineStyle, bool& bIsWaveline, bool& bIsBold) +{ + bIsWaveline = nLineStyle == LINESTYLE_DOUBLEWAVE || nLineStyle == LINESTYLE_SMALLWAVE + || nLineStyle == LINESTYLE_BOLDWAVE || nLineStyle == LINESTYLE_WAVE; + bIsBold = nLineStyle == LINESTYLE_BOLDWAVE; +} +} + +namespace cppcanvas::internal +{ +TextLinesHelper::TextLinesHelper(CanvasSharedPtr xCanvas, const OutDevState& rState) + : mpCanvas(std::move(xCanvas)) + , mbIsOverlineColorSet(rState.isTextOverlineColorSet) + , maOverlineColor(rState.textOverlineColor) + , mbIsUnderlineColorSet(rState.isTextLineColorSet) + , maUnderlineColor(rState.textLineColor) + , mbOverlineWaveline(false) + , mbUnderlineWaveline(false) + , mbOverlineWavelineBold(false) + , mbUnderlineWavelineBold(false) +{ +} + +void TextLinesHelper::init(double nLineWidth, const tools::TextLineInfo& rLineInfo) +{ + ::basegfx::B2DRange aRange; // default is empty. + ::basegfx::B2DPolyPolygon aOverline, aUnderline, aStrikeout; + tools::createTextLinesPolyPolygon(0.0, nLineWidth, rLineInfo, aOverline, aUnderline, + aStrikeout); + + mxOverline.clear(); + mxUnderline.clear(); + mxStrikeout.clear(); + + uno::Reference<rendering::XGraphicDevice> xDevice = mpCanvas->getUNOCanvas()->getDevice(); + + if (aOverline.count()) + { + aRange.expand(::basegfx::utils::getRange(aOverline)); + mxOverline = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(xDevice, aOverline); + } + + if (aUnderline.count()) + { + aRange.expand(::basegfx::utils::getRange(aUnderline)); + mxUnderline = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(xDevice, aUnderline); + } + + if (aStrikeout.count()) + { + aRange.expand(::basegfx::utils::getRange(aStrikeout)); + mxStrikeout = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(xDevice, aStrikeout); + } + + maOverallSize = basegfx::B2DSize(aRange.getRange().getX(), aRange.getRange().getY()); + + initLineStyleWaveline(rLineInfo.mnOverlineStyle, mbOverlineWaveline, mbOverlineWavelineBold); + + initLineStyleWaveline(rLineInfo.mnUnderlineStyle, mbUnderlineWaveline, mbUnderlineWavelineBold); +} + +void TextLinesHelper::render(const rendering::RenderState& rRenderState, bool bNormalText) const +{ + const rendering::ViewState& rViewState(mpCanvas->getViewState()); + const uno::Reference<rendering::XCanvas>& xCanvas(mpCanvas->getUNOCanvas()); + rendering::StrokeAttributes aStrokeAttributes; + aStrokeAttributes.JoinType = rendering::PathJoinType::ROUND; + + if (mxOverline.is()) + { + rendering::RenderState aLocalState(rRenderState); + if (bNormalText && mbIsOverlineColorSet) + aLocalState.DeviceColor = maOverlineColor; + + if (mbOverlineWaveline) + { + aStrokeAttributes.StrokeWidth = mbOverlineWavelineBold ? 2.0 : 1.0; + xCanvas->strokePolyPolygon(mxOverline, rViewState, aLocalState, aStrokeAttributes); + } + else + xCanvas->fillPolyPolygon(mxOverline, rViewState, aLocalState); + } + + if (mxUnderline.is()) + { + rendering::RenderState aLocalState(rRenderState); + if (bNormalText && mbIsUnderlineColorSet) + aLocalState.DeviceColor = maUnderlineColor; + if (mbUnderlineWaveline) + { + aStrokeAttributes.StrokeWidth = mbUnderlineWavelineBold ? 2.0 : 1.0; + xCanvas->strokePolyPolygon(mxUnderline, rViewState, aLocalState, aStrokeAttributes); + } + else + xCanvas->fillPolyPolygon(mxUnderline, rViewState, aLocalState); + } + + if (mxStrikeout.is()) + xCanvas->fillPolyPolygon(mxStrikeout, rViewState, rRenderState); +} +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/cppcanvas/source/mtfrenderer/textlineshelper.hxx b/cppcanvas/source/mtfrenderer/textlineshelper.hxx new file mode 100644 index 0000000000..f0a53ff7a0 --- /dev/null +++ b/cppcanvas/source/mtfrenderer/textlineshelper.hxx @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#pragma once + +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/rendering/RenderState.hpp> +#include <basegfx/vector/b2dsize.hxx> +#include <cppcanvas/canvas.hxx> + +namespace com::sun::star::rendering +{ +class XPolyPolygon2D; +} + +namespace cppcanvas +{ +namespace tools +{ +struct TextLineInfo; +} + +namespace internal +{ +struct OutDevState; + +class TextLinesHelper +{ + const CanvasSharedPtr mpCanvas; + css::uno::Reference<css::rendering::XPolyPolygon2D> mxOverline; + css::uno::Reference<css::rendering::XPolyPolygon2D> mxUnderline; + css::uno::Reference<css::rendering::XPolyPolygon2D> mxStrikeout; + + ::basegfx::B2DSize maOverallSize; + + bool mbIsOverlineColorSet; + const css::uno::Sequence<double> maOverlineColor; + + bool mbIsUnderlineColorSet; + const css::uno::Sequence<double> maUnderlineColor; + + bool mbOverlineWaveline; + bool mbUnderlineWaveline; + + bool mbOverlineWavelineBold; + bool mbUnderlineWavelineBold; + +public: + TextLinesHelper(CanvasSharedPtr xCanvas, const OutDevState& rState); + + ::basegfx::B2DSize const& getOverallSize() const { return maOverallSize; } + + /** Init textlines with specified linewidth and TextLineInfo. + */ + void init(double nLineWidth, const tools::TextLineInfo& rLineInfo); + + /** Fill the textlines with colors. + OutDevState::textUnderlineColor. + + @param rRenderState + Used to invoke XCanvas::fillPolyPolygon. + + @param bNormalText + Use overline color and underline color if the value is true, ignore those + colors otherwise ( typical case is to render the shadow ). + */ + void render(const css::rendering::RenderState& rRenderState, bool bNormalText) const; +}; +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/cppcanvas/source/mtfrenderer/transparencygroupaction.cxx b/cppcanvas/source/mtfrenderer/transparencygroupaction.cxx new file mode 100644 index 0000000000..d8bf50649b --- /dev/null +++ b/cppcanvas/source/mtfrenderer/transparencygroupaction.cxx @@ -0,0 +1,522 @@ +/* -*- 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 <sal/config.h> + +#include <utility> + +#include <tools/gen.hxx> +#include <tools/debug.hxx> + +#include <canvas/canvastools.hxx> + +#include <com/sun/star/rendering/XBitmap.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> + +#include <vcl/metaact.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/svapp.hxx> +#include <vcl/virdev.hxx> +#include <vcl/gdimtf.hxx> + +#include <basegfx/range/b2drange.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/vector/b2dsize.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/tuple/b2dtuple.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <sal/log.hxx> + +#include "transparencygroupaction.hxx" +#include <outdevstate.hxx> +#include "mtftools.hxx" +#include <cppcanvas/vclfactory.hxx> + +#if OSL_DEBUG_LEVEL > 2 +#include <vcl/canvastools.hxx> +#endif + +using namespace ::com::sun::star; + +namespace cppcanvas::internal +{ + // free support functions + // ====================== + namespace + { + class TransparencyGroupAction : public Action + { + public: + /** Create new transparency group action. + + @param rGroupMtf + Metafile that groups all actions to be rendered + transparent. + + @param rAlphaGradient + VCL gradient, to be rendered into the action's alpha + channel. + + @param rDstPoint + Left, top edge of destination, in current state + coordinate system + + @param rDstSize + Size of the transparency group object, in current + state coordinate system. + */ + TransparencyGroupAction( std::unique_ptr< GDIMetaFile >&& rGroupMtf, + std::optional< Gradient >&& rAlphaGradient, + const ::basegfx::B2DPoint& rDstPoint, + const ::basegfx::B2DVector& rDstSize, + const CanvasSharedPtr& rCanvas, + const OutDevState& rState ); + + TransparencyGroupAction(const TransparencyGroupAction&) = delete; + const TransparencyGroupAction& operator=(const TransparencyGroupAction&) = 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: + std::unique_ptr< GDIMetaFile > mpGroupMtf; + std::optional< Gradient > mpAlphaGradient; + + const ::basegfx::B2DSize maDstSize; + + mutable uno::Reference< rendering::XBitmap > mxBufferBitmap; // contains last rendered version + mutable ::basegfx::B2DHomMatrix maLastTransformation; // contains last active transformation + mutable Subset maLastSubset; // contains last effective subset + + // transformation for + // mxBufferBitmap content + CanvasSharedPtr mpCanvas; + rendering::RenderState maState; + }; + + + /** Setup transformation such that the next render call is + moved rPoint away, and scaled according to the ratio + given by src and dst size. + */ + void implSetupTransform( rendering::RenderState& rRenderState, + const ::basegfx::B2DPoint& rDstPoint ) + { + ::basegfx::B2DHomMatrix aLocalTransformation; + + aLocalTransformation.translate( rDstPoint.getX(), + rDstPoint.getY() ); + ::canvas::tools::appendToRenderState( rRenderState, + aLocalTransformation ); + } + + TransparencyGroupAction::TransparencyGroupAction( std::unique_ptr< GDIMetaFile >&& rGroupMtf, + std::optional< Gradient >&& rAlphaGradient, + const ::basegfx::B2DPoint& rDstPoint, + const ::basegfx::B2DVector& rDstSize, + const CanvasSharedPtr& rCanvas, + const OutDevState& rState ) : + mpGroupMtf( std::move(rGroupMtf) ), + mpAlphaGradient( std::move(rAlphaGradient) ), + maDstSize(rDstSize.getX(), rDstSize.getY()), + mpCanvas( rCanvas ) + { + tools::initRenderState(maState,rState); + implSetupTransform( maState, rDstPoint ); + + // correct clip (which is relative to original transform) + tools::modifyClip( maState, + rState, + rCanvas, + rDstPoint, + nullptr, + nullptr ); + + maLastSubset.mnSubsetBegin = 0; + maLastSubset.mnSubsetEnd = -1; + } + + // TODO(P3): The whole float transparency handling is a mess, + // this should be refactored. What's more, the old idea of + // having only internal 'metaactions', and not the original + // GDIMetaFile now looks a lot less attractive. Try to move + // into the direction of having a direct GDIMetaFile2XCanvas + // renderer, and maybe a separate metafile XCanvas + // implementation. + bool TransparencyGroupAction::renderSubset( const ::basegfx::B2DHomMatrix& rTransformation, + const Subset& rSubset ) const + { + SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TransparencyGroupAction::renderSubset()" ); + SAL_INFO( "cppcanvas.emf", "::cppcanvas::internal::TransparencyGroupAction: 0x" << std::hex << this ); + + // determine overall transformation matrix (render, view, + // and passed transformation) + ::basegfx::B2DHomMatrix aTransform; + ::canvas::tools::getRenderStateTransform( aTransform, maState ); + aTransform = rTransformation * aTransform; + + ::basegfx::B2DHomMatrix aTotalTransform; + ::canvas::tools::getViewStateTransform( aTotalTransform, mpCanvas->getViewState() ); + aTotalTransform = aTotalTransform * aTransform; + + // since pure translational changes to the transformation + // does not matter, remove them before comparing + aTotalTransform.set( 0, 2, 0.0 ); + aTotalTransform.set( 1, 2, 0.0 ); + + // determine total scaling factor of the + // transformation matrix - need to make the bitmap + // large enough + ::basegfx::B2DTuple aScale; + ::basegfx::B2DTuple aTranslate; + double nRotate; + double nShearX; + if( !aTotalTransform.decompose( aScale, + aTranslate, + nRotate, + nShearX ) ) + { + SAL_WARN( "cppcanvas.emf", "TransparencyGroupAction::renderSubset(): non-decomposable transformation" ); + return false; + } + + ::Point aMtfOffsetPoint; + + // if there's no buffer bitmap, or as soon as the + // total transformation changes, we've got to + // re-render the bitmap + if( !mxBufferBitmap.is() || + aTotalTransform != maLastTransformation || + rSubset.mnSubsetBegin != maLastSubset.mnSubsetBegin || + rSubset.mnSubsetEnd != maLastSubset.mnSubsetEnd ) + { + DBG_TESTSOLARMUTEX(); + + // tdf#150610 fix broken rendering of text meta actions + // Even when drawing to a VirtualDevice where antialiasing + // is disabled, text will still be drawn with some + // antialiased pixels on HiDPI displays. So, expand the + // size of the VirtualDevice slightly to capture any of + // the pixels drawn past the edges of the destination + // bounds. + bool bHasTextActions = false; + MetaAction* pCurrAct; + int nCurrActionIndex; + for( nCurrActionIndex=0, pCurrAct=mpGroupMtf->FirstAction(); + pCurrAct && !bHasTextActions; + ++nCurrActionIndex, pCurrAct = mpGroupMtf->NextAction() ) + { + switch( pCurrAct->GetType() ) + { + case MetaActionType::TEXT: + case MetaActionType::TEXTARRAY: + case MetaActionType::STRETCHTEXT: + case MetaActionType::TEXTRECT: + if( ( rSubset.mnSubsetBegin == 0 && rSubset.mnSubsetEnd == -1 ) || ( rSubset.mnSubsetBegin <= nCurrActionIndex && rSubset.mnSubsetEnd > nCurrActionIndex ) ) + bHasTextActions = true; + break; + default: + break; + } + } + + // output size of metafile + ::Size aOutputSizePixel( ::basegfx::fround( aScale.getX() * maDstSize.getWidth() ), + ::basegfx::fround( aScale.getY() * maDstSize.getHeight() ) ); + + sal_Int32 nBitmapExtra; + if ( bHasTextActions ) + { + nBitmapExtra = 10; + aMtfOffsetPoint = ::Point( nBitmapExtra / 2, nBitmapExtra / 2 ); + } + else + { + // Related tdf#150610 assume antialiasing is enabled + // Although antialiasing is normally disabled in the + // VirtualDevice, lines in tdf#150610 will draw past + // the edge of the VirtualDevice when running a + // slideshow so always add an extra pixel on the + // right and bottom edges. + nBitmapExtra = 1; + } + + // pixel size of cache bitmap: round up to nearest int + ::Point aBitmapPoint; + ::Size aBitmapSizePixel( static_cast<sal_Int32>( aScale.getX() * maDstSize.getWidth() ) + nBitmapExtra, + static_cast<sal_Int32>( aScale.getY() * maDstSize.getHeight() ) + nBitmapExtra ); + + // render our content into an appropriately sized + // VirtualDevice with alpha channel + ScopedVclPtrInstance<VirtualDevice> aVDev( + *::Application::GetDefaultDevice(), DeviceFormat::WITH_ALPHA ); + aVDev->SetOutputSizePixel( aBitmapSizePixel, true, true ); + aVDev->SetMapMode(); + + if( rSubset.mnSubsetBegin != 0 || + rSubset.mnSubsetEnd != -1 ) + { + // true subset - extract referenced + // metaactions from mpGroupMtf + GDIMetaFile aMtf; + + // extract subset actions + for( nCurrActionIndex=0, + pCurrAct=mpGroupMtf->FirstAction(); + pCurrAct; + ++nCurrActionIndex, pCurrAct = mpGroupMtf->NextAction() ) + { + switch( pCurrAct->GetType() ) + { + case MetaActionType::PUSH: + case MetaActionType::POP: + case MetaActionType::CLIPREGION: + case MetaActionType::ISECTRECTCLIPREGION: + case MetaActionType::ISECTREGIONCLIPREGION: + case MetaActionType::MOVECLIPREGION: + case MetaActionType::LINECOLOR: + case MetaActionType::FILLCOLOR: + case MetaActionType::TEXTCOLOR: + case MetaActionType::TEXTFILLCOLOR: + case MetaActionType::TEXTLINECOLOR: + case MetaActionType::TEXTALIGN: + case MetaActionType::FONT: + case MetaActionType::RASTEROP: + case MetaActionType::REFPOINT: + case MetaActionType::LAYOUTMODE: + // state-changing action - copy as-is + aMtf.AddAction( pCurrAct->Clone() ); + break; + + case MetaActionType::GRADIENT: + case MetaActionType::HATCH: + case MetaActionType::EPS: + case MetaActionType::COMMENT: + case MetaActionType::POINT: + case MetaActionType::PIXEL: + case MetaActionType::LINE: + case MetaActionType::RECT: + case MetaActionType::ROUNDRECT: + case MetaActionType::ELLIPSE: + case MetaActionType::ARC: + case MetaActionType::PIE: + case MetaActionType::CHORD: + case MetaActionType::POLYLINE: + case MetaActionType::POLYGON: + case MetaActionType::POLYPOLYGON: + case MetaActionType::BMP: + case MetaActionType::BMPSCALE: + case MetaActionType::BMPSCALEPART: + case MetaActionType::BMPEX: + case MetaActionType::BMPEXSCALE: + case MetaActionType::BMPEXSCALEPART: + case MetaActionType::MASK: + case MetaActionType::MASKSCALE: + case MetaActionType::MASKSCALEPART: + case MetaActionType::GRADIENTEX: + case MetaActionType::WALLPAPER: + case MetaActionType::Transparent: + case MetaActionType::FLOATTRANSPARENT: + case MetaActionType::TEXT: + case MetaActionType::TEXTARRAY: + case MetaActionType::TEXTLINE: + case MetaActionType::TEXTRECT: + case MetaActionType::STRETCHTEXT: + // output-generating action - only + // copy, if we're within the + // requested subset + if( rSubset.mnSubsetBegin <= nCurrActionIndex && + rSubset.mnSubsetEnd > nCurrActionIndex ) + { + aMtf.AddAction( pCurrAct->Clone() ); + } + break; + + default: + SAL_WARN( "cppcanvas.emf", "Unknown meta action type encountered" ); + break; + } + } + + aVDev->DrawTransparent( aMtf, + aBitmapPoint, + aBitmapSizePixel, + aMtfOffsetPoint, + aOutputSizePixel, + *mpAlphaGradient ); + } + else + { + // no subsetting - render whole mtf + aVDev->DrawTransparent( *mpGroupMtf, + aBitmapPoint, + aBitmapSizePixel, + aMtfOffsetPoint, + aOutputSizePixel, + *mpAlphaGradient ); + } + + + // update buffered bitmap and transformation + BitmapSharedPtr aBmp( VCLFactory::createBitmap( + mpCanvas, + aVDev->GetBitmapEx( + aBitmapPoint, + aBitmapSizePixel ) ) ); + mxBufferBitmap = aBmp->getUNOBitmap(); + maLastTransformation = aTotalTransform; + maLastSubset = rSubset; + } + + // determine target transformation (we can't simply pass + // aTotalTransform as assembled above, since we must take + // the canvas' view state as is, it might contain clipping + // (which, in turn, is relative to the view + // transformation)) + + // given that aTotalTransform is the identity + // transformation, we could simply render our bitmap + // as-is. Now, since the mxBufferBitmap content already + // accounts for scale changes in the overall + // transformation, we must factor this out + // before. Generally, the transformation matrix should be + // structured like this: + // Translation*Rotation*Shear*Scale. Thus, to neutralize + // the contained scaling, we've got to right-multiply with + // the inverse. + ::basegfx::B2DHomMatrix aScaleCorrection; + aScaleCorrection.translate( -aMtfOffsetPoint.X(), -aMtfOffsetPoint.Y() ); + aScaleCorrection.scale( 1/aScale.getX(), 1/aScale.getY() ); + aTransform = aTransform * aScaleCorrection; + + rendering::RenderState aLocalState( maState ); + ::canvas::tools::setRenderStateTransform(aLocalState, aTransform); + + if(aLocalState.Clip.is()) + { + // tdf#95709 + // Adjust renderstate clip to modified scale from above + ::basegfx::B2DPolyPolygon aClip = ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(aLocalState.Clip); + aClip.transform(basegfx::utils::createScaleB2DHomMatrix(aScale)); + aLocalState.Clip = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(mpCanvas->getUNOCanvas()->getDevice(), aClip); + } + +#if OSL_DEBUG_LEVEL > 2 + aLocalState.Clip.clear(); + aLocalState.DeviceColor = + vcl::unotools::colorToDoubleSequence( + ::Color( 0x80FF0000 ), + mpCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() ); + + if( maState.Clip.is() ) + mpCanvas->getUNOCanvas()->fillPolyPolygon( maState.Clip, + mpCanvas->getViewState(), + aLocalState ); + + aLocalState.DeviceColor = maState.DeviceColor; +#endif + + // no further alpha changes necessary -> draw directly + mpCanvas->getUNOCanvas()->drawBitmap( mxBufferBitmap, + mpCanvas->getViewState(), + aLocalState ); + return true; + } + + // TODO(P3): The whole float transparency handling is a mess, + // this should be refactored. What's more, the old idea of + // having only internal 'metaactions', and not the original + // GDIMetaFile now looks a lot less attractive. Try to move + // into the direction of having a direct GDIMetaFile2XCanvas + // renderer, and maybe a separate metafile XCanvas + // implementation. + bool TransparencyGroupAction::render( const ::basegfx::B2DHomMatrix& rTransformation ) const + { + Subset aSubset; + + aSubset.mnSubsetBegin = 0; + aSubset.mnSubsetEnd = -1; + + return renderSubset( rTransformation, aSubset ); + } + + ::basegfx::B2DRange TransparencyGroupAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const + { + rendering::RenderState aLocalState( maState ); + ::canvas::tools::prependToRenderState(aLocalState, rTransformation); + + return tools::calcDevicePixelBounds( + ::basegfx::B2DRange( 0,0, + maDstSize.getWidth(), + maDstSize.getHeight() ), + mpCanvas->getViewState(), + aLocalState ); + } + + ::basegfx::B2DRange TransparencyGroupAction::getBounds( const ::basegfx::B2DHomMatrix& rTransformation, + const Subset& rSubset ) const + { + // TODO(F3): Currently, the bounds for + // TransparencyGroupAction subsets equal those of the + // full set, although this action is able to render + // true subsets. + + // polygon only contains a single action, empty bounds + // if subset requests different range + if( rSubset.mnSubsetBegin != 0 || + rSubset.mnSubsetEnd != 1 ) + return ::basegfx::B2DRange(); + + return getBounds( rTransformation ); + } + + sal_Int32 TransparencyGroupAction::getActionCount() const + { + return mpGroupMtf ? mpGroupMtf->GetActionSize() : 0; + } + + } + + std::shared_ptr<Action> TransparencyGroupActionFactory::createTransparencyGroupAction( std::unique_ptr< GDIMetaFile >&& rGroupMtf, + std::optional< Gradient >&& rAlphaGradient, + const ::basegfx::B2DPoint& rDstPoint, + const ::basegfx::B2DVector& rDstSize, + const CanvasSharedPtr& rCanvas, + const OutDevState& rState ) + { + return std::make_shared<TransparencyGroupAction>(std::move(rGroupMtf), + std::move(rAlphaGradient), + rDstPoint, + rDstSize, + rCanvas, + rState ); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/cppcanvas/source/mtfrenderer/transparencygroupaction.hxx b/cppcanvas/source/mtfrenderer/transparencygroupaction.hxx new file mode 100644 index 0000000000..ddf01ca009 --- /dev/null +++ b/cppcanvas/source/mtfrenderer/transparencygroupaction.hxx @@ -0,0 +1,85 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppcanvas/canvas.hxx> +#include <vcl/gradient.hxx> +#include <action.hxx> + +#include <memory> + +namespace basegfx { + class B2DPoint; + class B2DVector; +} + +class GDIMetaFile; + + +/* Definition of internal::TransparencyGroupActionFactory */ + +namespace cppcanvas::internal + { + struct OutDevState; + + /** Transparency group action. + + This action groups a bunch of other actions, to be + rendered with the given transparency setting against the + background. + + Creates encapsulated converters between GDIMetaFile and + XCanvas. The Canvas argument is deliberately placed at the + constructor, to force reconstruction of this object for a + new canvas. This considerably eases internal state + handling, since a lot of the internal state (e.g. fonts, + text layout) is Canvas-dependent. + */ + namespace TransparencyGroupActionFactory + { + /** Create new transparency group action. + + @param rGroupMtf + Metafile that groups all actions to be rendered + transparent. + + @param rAlphaGradient + VCL gradient, to be rendered into the action's alpha + channel. + + @param rDstPoint + Left, top edge of destination, in current state + coordinate system + + @param rDstSize + Size of the transparency group object, in current + state coordinate system. + */ + std::shared_ptr<Action> createTransparencyGroupAction( std::unique_ptr< GDIMetaFile >&& rGroupMtf, + std::optional< Gradient >&& rAlphaGradient, + const ::basegfx::B2DPoint& rDstPoint, + const ::basegfx::B2DVector& rDstSize, + const CanvasSharedPtr& rCanvas, + const OutDevState& rState ); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |