summaryrefslogtreecommitdiffstats
path: root/cppcanvas/source/mtfrenderer
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /cppcanvas/source/mtfrenderer
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'cppcanvas/source/mtfrenderer')
-rw-r--r--cppcanvas/source/mtfrenderer/bitmapaction.cxx217
-rw-r--r--cppcanvas/source/mtfrenderer/bitmapaction.hxx61
-rw-r--r--cppcanvas/source/mtfrenderer/cachedprimitivebase.cxx80
-rw-r--r--cppcanvas/source/mtfrenderer/cachedprimitivebase.hxx83
-rw-r--r--cppcanvas/source/mtfrenderer/implrenderer.cxx3066
-rw-r--r--cppcanvas/source/mtfrenderer/lineaction.cxx154
-rw-r--r--cppcanvas/source/mtfrenderer/lineaction.hxx54
-rw-r--r--cppcanvas/source/mtfrenderer/mtftools.cxx671
-rw-r--r--cppcanvas/source/mtfrenderer/mtftools.hxx212
-rw-r--r--cppcanvas/source/mtfrenderer/pointaction.cxx172
-rw-r--r--cppcanvas/source/mtfrenderer/pointaction.hxx59
-rw-r--r--cppcanvas/source/mtfrenderer/polypolyaction.cxx500
-rw-r--r--cppcanvas/source/mtfrenderer/polypolyaction.hxx82
-rw-r--r--cppcanvas/source/mtfrenderer/textaction.cxx2323
-rw-r--r--cppcanvas/source/mtfrenderer/textaction.hxx84
-rw-r--r--cppcanvas/source/mtfrenderer/textlineshelper.cxx125
-rw-r--r--cppcanvas/source/mtfrenderer/textlineshelper.hxx79
-rw-r--r--cppcanvas/source/mtfrenderer/transparencygroupaction.cxx522
-rw-r--r--cppcanvas/source/mtfrenderer/transparencygroupaction.hxx85
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: */