summaryrefslogtreecommitdiffstats
path: root/drawinglayer/source/processor2d
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /drawinglayer/source/processor2d
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drawinglayer/source/processor2d')
-rw-r--r--drawinglayer/source/processor2d/baseprocessor2d.cxx73
-rw-r--r--drawinglayer/source/processor2d/contourextractor2d.cxx193
-rw-r--r--drawinglayer/source/processor2d/getdigitlanguage.cxx31
-rw-r--r--drawinglayer/source/processor2d/getdigitlanguage.hxx22
-rw-r--r--drawinglayer/source/processor2d/helperwrongspellrenderer.cxx76
-rw-r--r--drawinglayer/source/processor2d/helperwrongspellrenderer.hxx47
-rw-r--r--drawinglayer/source/processor2d/hittestprocessor2d.cxx551
-rw-r--r--drawinglayer/source/processor2d/linegeometryextractor2d.cxx125
-rw-r--r--drawinglayer/source/processor2d/objectinfoextractor2d.cxx77
-rw-r--r--drawinglayer/source/processor2d/processor2dtools.cxx62
-rw-r--r--drawinglayer/source/processor2d/processorfromoutputdevice.cxx50
-rw-r--r--drawinglayer/source/processor2d/textaspolygonextractor2d.cxx231
-rw-r--r--drawinglayer/source/processor2d/vclhelperbufferdevice.cxx444
-rw-r--r--drawinglayer/source/processor2d/vclhelperbufferdevice.hxx51
-rw-r--r--drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx2571
-rw-r--r--drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx217
-rw-r--r--drawinglayer/source/processor2d/vclpixelprocessor2d.cxx1351
-rw-r--r--drawinglayer/source/processor2d/vclpixelprocessor2d.hxx116
-rw-r--r--drawinglayer/source/processor2d/vclprocessor2d.cxx1480
-rw-r--r--drawinglayer/source/processor2d/vclprocessor2d.hxx119
20 files changed, 7887 insertions, 0 deletions
diff --git a/drawinglayer/source/processor2d/baseprocessor2d.cxx b/drawinglayer/source/processor2d/baseprocessor2d.cxx
new file mode 100644
index 000000000..211569f5f
--- /dev/null
+++ b/drawinglayer/source/processor2d/baseprocessor2d.cxx
@@ -0,0 +1,73 @@
+/* -*- 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 <drawinglayer/primitive2d/Primitive2DContainer.hxx>
+#include <drawinglayer/processor2d/baseprocessor2d.hxx>
+
+
+using namespace com::sun::star;
+
+
+namespace drawinglayer::processor2d
+{
+ void BaseProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& /*rCandidate*/)
+ {
+ }
+
+ BaseProcessor2D::BaseProcessor2D(const geometry::ViewInformation2D& rViewInformation)
+ : maViewInformation2D(rViewInformation)
+ {
+ }
+
+ BaseProcessor2D::~BaseProcessor2D()
+ {
+ }
+
+ void BaseProcessor2D::process(const primitive2d::BasePrimitive2D& rCandidate)
+ {
+ // use the visitor API to avoid the cost of constructing Primitive2DContainers
+ rCandidate.get2DDecomposition(*this, getViewInformation2D());
+ }
+
+ // Primitive2DDecompositionVisitor
+ void BaseProcessor2D::visit(const primitive2d::Primitive2DReference& rCandidate)
+ {
+ processBasePrimitive2D(*rCandidate);
+ }
+ void BaseProcessor2D::visit(const primitive2d::Primitive2DContainer& rContainer)
+ {
+ process(rContainer);
+ }
+ void BaseProcessor2D::visit(primitive2d::Primitive2DContainer&& rCandidate)
+ {
+ process(rCandidate);
+ }
+
+ void BaseProcessor2D::process(const primitive2d::Primitive2DContainer& rSource)
+ {
+ for (const primitive2d::Primitive2DReference& rCandidate : rSource)
+ {
+ if (rCandidate)
+ processBasePrimitive2D(*rCandidate);
+ }
+ }
+
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/contourextractor2d.cxx b/drawinglayer/source/processor2d/contourextractor2d.cxx
new file mode 100644
index 000000000..a1d120018
--- /dev/null
+++ b/drawinglayer/source/processor2d/contourextractor2d.cxx
@@ -0,0 +1,193 @@
+/* -*- 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 <drawinglayer/processor2d/contourextractor2d.hxx>
+#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
+#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <drawinglayer/primitive2d/metafileprimitive2d.hxx>
+#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
+#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+#include <drawinglayer/primitive2d/sceneprimitive2d.hxx>
+
+
+using namespace com::sun::star;
+
+
+namespace drawinglayer::processor2d
+{
+ ContourExtractor2D::ContourExtractor2D(
+ const geometry::ViewInformation2D& rViewInformation,
+ bool bExtractFillOnly)
+ : BaseProcessor2D(rViewInformation),
+ mbExtractFillOnly(bExtractFillOnly)
+ {
+ }
+
+ ContourExtractor2D::~ContourExtractor2D()
+ {
+ }
+
+ void ContourExtractor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
+ {
+ switch(rCandidate.getPrimitive2DID())
+ {
+ case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D :
+ {
+ if(!mbExtractFillOnly)
+ {
+ // extract hairline in world coordinates
+ const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonHairlinePrimitive2D& >(rCandidate));
+ basegfx::B2DPolygon aLocalPolygon(rPolygonCandidate.getB2DPolygon());
+ aLocalPolygon.transform(getViewInformation2D().getObjectTransformation());
+
+ if(aLocalPolygon.isClosed())
+ {
+ // line polygons need to be represented as open polygons to differentiate them
+ // from filled polygons
+ basegfx::utils::openWithGeometryChange(aLocalPolygon);
+ }
+
+ maExtractedContour.emplace_back(aLocalPolygon);
+ }
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D :
+ {
+ // extract fill in world coordinates
+ const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolyPolygonColorPrimitive2D& >(rCandidate));
+ basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolygonCandidate.getB2DPolyPolygon());
+ aLocalPolyPolygon.transform(getViewInformation2D().getObjectTransformation());
+ maExtractedContour.push_back(aLocalPolyPolygon);
+ break;
+ }
+ case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D :
+ {
+ // extract BoundRect from bitmaps in world coordinates
+ const primitive2d::BitmapPrimitive2D& rBitmapCandidate(static_cast< const primitive2d::BitmapPrimitive2D& >(rCandidate));
+ basegfx::B2DHomMatrix aLocalTransform(getViewInformation2D().getObjectTransformation() * rBitmapCandidate.getTransform());
+ basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon());
+ aPolygon.transform(aLocalTransform);
+ maExtractedContour.emplace_back(aPolygon);
+ break;
+ }
+ case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D :
+ {
+ // extract BoundRect from MetaFiles in world coordinates
+ const primitive2d::MetafilePrimitive2D& rMetaCandidate(static_cast< const primitive2d::MetafilePrimitive2D& >(rCandidate));
+ basegfx::B2DHomMatrix aLocalTransform(getViewInformation2D().getObjectTransformation() * rMetaCandidate.getTransform());
+ basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon());
+ aPolygon.transform(aLocalTransform);
+ maExtractedContour.emplace_back(aPolygon);
+ break;
+ }
+ case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D :
+ {
+ // sub-transparence group. Look at children
+ const primitive2d::TransparencePrimitive2D& rTransCandidate(static_cast< const primitive2d::TransparencePrimitive2D& >(rCandidate));
+ process(rTransCandidate.getChildren());
+ break;
+ }
+ case PRIMITIVE2D_ID_MASKPRIMITIVE2D :
+ {
+ // extract mask in world coordinates, ignore content
+ const primitive2d::MaskPrimitive2D& rMaskCandidate(static_cast< const primitive2d::MaskPrimitive2D& >(rCandidate));
+ basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask());
+ aMask.transform(getViewInformation2D().getObjectTransformation());
+ maExtractedContour.push_back(aMask);
+ break;
+ }
+ case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D :
+ {
+ // remember current ViewInformation2D
+ const primitive2d::TransformPrimitive2D& rTransformCandidate(static_cast< const primitive2d::TransformPrimitive2D& >(rCandidate));
+ const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
+
+ // create new local ViewInformation2D
+ const geometry::ViewInformation2D aViewInformation2D(
+ getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation(),
+ getViewInformation2D().getViewTransformation(),
+ getViewInformation2D().getViewport(),
+ getViewInformation2D().getVisualizedPage(),
+ getViewInformation2D().getViewTime());
+ updateViewInformation(aViewInformation2D);
+
+ // process content
+ process(rTransformCandidate.getChildren());
+
+ // restore transformations
+ updateViewInformation(aLastViewInformation2D);
+
+ break;
+ }
+ case PRIMITIVE2D_ID_SCENEPRIMITIVE2D :
+ {
+ // 2D Scene primitive containing 3D stuff; extract 2D contour in world coordinates
+ const primitive2d::ScenePrimitive2D& rScenePrimitive2DCandidate(static_cast< const primitive2d::ScenePrimitive2D& >(rCandidate));
+ const primitive2d::Primitive2DContainer xExtracted2DSceneGeometry(rScenePrimitive2DCandidate.getGeometry2D());
+ const primitive2d::Primitive2DContainer xExtracted2DSceneShadow(rScenePrimitive2DCandidate.getShadow2D());
+
+ // process content
+ if(!xExtracted2DSceneGeometry.empty())
+ {
+ process(xExtracted2DSceneGeometry);
+ }
+
+ // process content
+ if(!xExtracted2DSceneShadow.empty())
+ {
+ process(xExtracted2DSceneShadow);
+ }
+
+ break;
+ }
+ case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D :
+ case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D :
+ case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D :
+ {
+ // ignorable primitives
+ break;
+ }
+ case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D :
+ case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D :
+ {
+ // primitives who's BoundRect will be added in world coordinates
+ basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
+ if (!aRange.isEmpty())
+ {
+ aRange.transform(getViewInformation2D().getObjectTransformation());
+ maExtractedContour.emplace_back(basegfx::utils::createPolygonFromRect(aRange));
+ }
+ break;
+ }
+ default :
+ {
+ // process recursively
+ process(rCandidate);
+ break;
+ }
+ }
+ }
+
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/getdigitlanguage.cxx b/drawinglayer/source/processor2d/getdigitlanguage.cxx
new file mode 100644
index 000000000..858284b23
--- /dev/null
+++ b/drawinglayer/source/processor2d/getdigitlanguage.cxx
@@ -0,0 +1,31 @@
+/* -*- 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/.
+ */
+
+#include <sal/config.h>
+
+#include <i18nlangtag/lang.h>
+#include <i18nlangtag/languagetag.hxx>
+#include <svl/ctloptions.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+
+#include "getdigitlanguage.hxx"
+
+LanguageType drawinglayer::detail::getDigitLanguage() {
+ switch (SvtCTLOptions().GetCTLTextNumerals()) {
+ case SvtCTLOptions::NUMERALS_ARABIC:
+ return LANGUAGE_ENGLISH;
+ case SvtCTLOptions::NUMERALS_HINDI:
+ return LANGUAGE_ARABIC_SAUDI_ARABIA;
+ default:
+ return Application::GetSettings().GetLanguageTag().getLanguageType();
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/getdigitlanguage.hxx b/drawinglayer/source/processor2d/getdigitlanguage.hxx
new file mode 100644
index 000000000..c22076fa7
--- /dev/null
+++ b/drawinglayer/source/processor2d/getdigitlanguage.hxx
@@ -0,0 +1,22 @@
+/* -*- 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/.
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <i18nlangtag/lang.h>
+
+namespace drawinglayer::detail
+{
+/// Get digit language derived from SvtCTLOptions
+LanguageType getDigitLanguage();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/helperwrongspellrenderer.cxx b/drawinglayer/source/processor2d/helperwrongspellrenderer.cxx
new file mode 100644
index 000000000..9f838a7e1
--- /dev/null
+++ b/drawinglayer/source/processor2d/helperwrongspellrenderer.cxx
@@ -0,0 +1,76 @@
+/* -*- 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 "helperwrongspellrenderer.hxx"
+#include <drawinglayer/primitive2d/wrongspellprimitive2d.hxx>
+#include <tools/gen.hxx>
+#include <vcl/outdev.hxx>
+#include <basegfx/color/bcolormodifier.hxx>
+#include <vcl/outdev/ScopedStates.hxx>
+
+using namespace css;
+
+namespace drawinglayer
+{
+namespace
+{
+constexpr sal_uInt32 constMinimumFontHeight = 5; // #define WRONG_SHOW_MIN 5
+}
+
+bool renderWrongSpellPrimitive2D(const primitive2d::WrongSpellPrimitive2D& rWrongSpellCandidate,
+ OutputDevice& rOutputDevice,
+ const basegfx::B2DHomMatrix& rObjectToViewTransformation,
+ const basegfx::BColorModifierStack& rBColorModifierStack)
+{
+ const basegfx::B2DHomMatrix aLocalTransform(rObjectToViewTransformation
+ * rWrongSpellCandidate.getTransformation());
+ const basegfx::B2DVector aFontVectorPixel(aLocalTransform * basegfx::B2DVector(0.0, 1.0));
+ const sal_uInt32 nFontPixelHeight(basegfx::fround(aFontVectorPixel.getLength()));
+
+ if (nFontPixelHeight <= constMinimumFontHeight)
+ return true;
+
+ const basegfx::B2DPoint aStart(aLocalTransform
+ * basegfx::B2DPoint(rWrongSpellCandidate.getStart(), 0.0));
+ const basegfx::B2DPoint aStop(aLocalTransform
+ * basegfx::B2DPoint(rWrongSpellCandidate.getStop(), 0.0));
+ const Point aVclStart(basegfx::fround(aStart.getX()), basegfx::fround(aStart.getY()));
+ const Point aVclStop(basegfx::fround(aStop.getX()), basegfx::fround(aStop.getY()));
+
+ // #i101075# draw it. Do not forget to use the evtl. offsetted origin of the target device,
+ // e.g. when used with mask/transparence buffer device
+ const Point aOrigin(rOutputDevice.GetMapMode().GetOrigin());
+
+ const basegfx::BColor aProcessedColor(
+ rBColorModifierStack.getModifiedColor(rWrongSpellCandidate.getColor()));
+ const bool bMapModeEnabledState(rOutputDevice.IsMapModeEnabled());
+
+ vcl::ScopedAntialiasing a(rOutputDevice, true);
+ rOutputDevice.EnableMapMode(false);
+ rOutputDevice.SetLineColor(Color(aProcessedColor));
+ rOutputDevice.SetFillColor();
+ rOutputDevice.DrawWaveLine(aOrigin + aVclStart, aOrigin + aVclStop);
+ rOutputDevice.EnableMapMode(bMapModeEnabledState);
+
+ // cannot really go wrong
+ return true;
+}
+} // end of namespace drawinglayer
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/helperwrongspellrenderer.hxx b/drawinglayer/source/processor2d/helperwrongspellrenderer.hxx
new file mode 100644
index 000000000..886ce99bb
--- /dev/null
+++ b/drawinglayer/source/processor2d/helperwrongspellrenderer.hxx
@@ -0,0 +1,47 @@
+/* -*- 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
+
+class OutputDevice;
+
+namespace drawinglayer::primitive2d
+{
+class WrongSpellPrimitive2D;
+}
+
+namespace basegfx
+{
+class B2DHomMatrix;
+class BColorModifierStack;
+}
+
+// support WrongSpell rendering using VCL from primitives due to VCLs nice
+// and fast solution with wavelines
+
+namespace drawinglayer
+{
+bool renderWrongSpellPrimitive2D(const primitive2d::WrongSpellPrimitive2D& rWrongSpellCandidate,
+ OutputDevice& rOutputDevice,
+ const basegfx::B2DHomMatrix& rObjectToViewTransformation,
+ const basegfx::BColorModifierStack& rBColorModifierStack);
+
+} // end of namespace drawinglayer
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/hittestprocessor2d.cxx b/drawinglayer/source/processor2d/hittestprocessor2d.cxx
new file mode 100644
index 000000000..dc7d6fa7c
--- /dev/null
+++ b/drawinglayer/source/processor2d/hittestprocessor2d.cxx
@@ -0,0 +1,551 @@
+/* -*- 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 <drawinglayer/processor2d/hittestprocessor2d.hxx>
+#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolygonMarkerPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolygonWavePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
+#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
+#include <drawinglayer/primitive2d/sceneprimitive2d.hxx>
+#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
+#include <basegfx/matrix/b3dhommatrix.hxx>
+#include <drawinglayer/processor3d/cutfindprocessor3d.hxx>
+#include <drawinglayer/primitive2d/hiddengeometryprimitive2d.hxx>
+#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
+#include <comphelper/lok.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+
+namespace drawinglayer::processor2d
+{
+ HitTestProcessor2D::HitTestProcessor2D(const geometry::ViewInformation2D& rViewInformation,
+ const basegfx::B2DPoint& rLogicHitPosition,
+ double fLogicHitTolerance,
+ bool bHitTextOnly)
+ : BaseProcessor2D(rViewInformation),
+ mfDiscreteHitTolerance(0.0),
+ mbCollectHitStack(false),
+ mbHit(false),
+ mbHitTextOnly(bHitTextOnly)
+ {
+ // init hit tolerance
+ mfDiscreteHitTolerance = fLogicHitTolerance;
+
+ if(basegfx::fTools::less(mfDiscreteHitTolerance, 0.0))
+ {
+ // ensure input parameter for hit tolerance is >= 0.0
+ mfDiscreteHitTolerance = 0.0;
+ }
+ else if(basegfx::fTools::more(mfDiscreteHitTolerance, 0.0))
+ {
+ // generate discrete hit tolerance
+ mfDiscreteHitTolerance = (getViewInformation2D().getObjectToViewTransformation()
+ * basegfx::B2DVector(mfDiscreteHitTolerance, 0.0)).getLength();
+ }
+
+ // generate discrete hit position
+ maDiscreteHitPosition = getViewInformation2D().getObjectToViewTransformation() * rLogicHitPosition;
+ }
+
+ HitTestProcessor2D::~HitTestProcessor2D()
+ {
+ }
+
+ bool HitTestProcessor2D::checkHairlineHitWithTolerance(
+ const basegfx::B2DPolygon& rPolygon,
+ double fDiscreteHitTolerance) const
+ {
+ basegfx::B2DPolygon aLocalPolygon(rPolygon);
+ aLocalPolygon.transform(getViewInformation2D().getObjectToViewTransformation());
+
+ // get discrete range
+ basegfx::B2DRange aPolygonRange(aLocalPolygon.getB2DRange());
+
+ if(basegfx::fTools::more(fDiscreteHitTolerance, 0.0))
+ {
+ aPolygonRange.grow(fDiscreteHitTolerance);
+ }
+
+ // do rough range test first
+ if(aPolygonRange.isInside(getDiscreteHitPosition()))
+ {
+ // check if a polygon edge is hit
+ return basegfx::utils::isInEpsilonRange(
+ aLocalPolygon,
+ getDiscreteHitPosition(),
+ fDiscreteHitTolerance);
+ }
+
+ return false;
+ }
+
+ bool HitTestProcessor2D::checkFillHitWithTolerance(
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ double fDiscreteHitTolerance) const
+ {
+ bool bRetval(false);
+ basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolyPolygon);
+ aLocalPolyPolygon.transform(getViewInformation2D().getObjectToViewTransformation());
+
+ // get discrete range
+ basegfx::B2DRange aPolygonRange(aLocalPolyPolygon.getB2DRange());
+ const bool bDiscreteHitToleranceUsed(basegfx::fTools::more(fDiscreteHitTolerance, 0.0));
+
+ if(bDiscreteHitToleranceUsed)
+ {
+ aPolygonRange.grow(fDiscreteHitTolerance);
+ }
+
+ // do rough range test first
+ if(aPolygonRange.isInside(getDiscreteHitPosition()))
+ {
+ // if a HitTolerance is given, check for polygon edge hit in epsilon first
+ if(bDiscreteHitToleranceUsed &&
+ basegfx::utils::isInEpsilonRange(
+ aLocalPolyPolygon,
+ getDiscreteHitPosition(),
+ fDiscreteHitTolerance))
+ {
+ bRetval = true;
+ }
+
+ // check for hit in filled polyPolygon
+ if(!bRetval && basegfx::utils::isInside(
+ aLocalPolyPolygon,
+ getDiscreteHitPosition(),
+ true))
+ {
+ bRetval = true;
+ }
+ }
+
+ return bRetval;
+ }
+
+ void HitTestProcessor2D::check3DHit(const primitive2d::ScenePrimitive2D& rCandidate)
+ {
+ // calculate relative point in unified 2D scene
+ const basegfx::B2DPoint aLogicHitPosition(getViewInformation2D().getInverseObjectToViewTransformation() * getDiscreteHitPosition());
+
+ // use bitmap check in ScenePrimitive2D
+ bool bTryFastResult(false);
+
+ if(rCandidate.tryToCheckLastVisualisationDirectHit(aLogicHitPosition, bTryFastResult))
+ {
+ mbHit = bTryFastResult;
+ }
+ else
+ {
+ basegfx::B2DHomMatrix aInverseSceneTransform(rCandidate.getObjectTransformation());
+ aInverseSceneTransform.invert();
+ const basegfx::B2DPoint aRelativePoint(aInverseSceneTransform * aLogicHitPosition);
+
+ // check if test point is inside scene's unified area at all
+ if(aRelativePoint.getX() >= 0.0 && aRelativePoint.getX() <= 1.0
+ && aRelativePoint.getY() >= 0.0 && aRelativePoint.getY() <= 1.0)
+ {
+ // get 3D view information
+ const geometry::ViewInformation3D& rObjectViewInformation3D = rCandidate.getViewInformation3D();
+
+ // create HitPoint Front and Back, transform to object coordinates
+ basegfx::B3DHomMatrix aViewToObject(rObjectViewInformation3D.getObjectToView());
+ aViewToObject.invert();
+ const basegfx::B3DPoint aFront(aViewToObject * basegfx::B3DPoint(aRelativePoint.getX(), aRelativePoint.getY(), 0.0));
+ const basegfx::B3DPoint aBack(aViewToObject * basegfx::B3DPoint(aRelativePoint.getX(), aRelativePoint.getY(), 1.0));
+
+ if(!aFront.equal(aBack))
+ {
+ const primitive3d::Primitive3DContainer& rPrimitives = rCandidate.getChildren3D();
+
+ if(!rPrimitives.empty())
+ {
+ // make BoundVolume empty and overlapping test for speedup
+ const basegfx::B3DRange aObjectRange(
+ rPrimitives.getB3DRange(rObjectViewInformation3D));
+
+ if(!aObjectRange.isEmpty())
+ {
+ const basegfx::B3DRange aFrontBackRange(aFront, aBack);
+
+ if(aObjectRange.overlaps(aFrontBackRange))
+ {
+ // bound volumes hit, geometric cut tests needed
+ drawinglayer::processor3d::CutFindProcessor aCutFindProcessor(
+ rObjectViewInformation3D,
+ aFront,
+ aBack,
+ true);
+ aCutFindProcessor.process(rPrimitives);
+
+ mbHit = (!aCutFindProcessor.getCutPoints().empty());
+ }
+ }
+ }
+ }
+ }
+
+ if(!getHit())
+ {
+ // empty 3D scene; Check for border hit
+ basegfx::B2DPolygon aOutline(basegfx::utils::createUnitPolygon());
+ aOutline.transform(rCandidate.getObjectTransformation());
+
+ mbHit = checkHairlineHitWithTolerance(aOutline, getDiscreteHitTolerance());
+ }
+ }
+ }
+
+ void HitTestProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
+ {
+ if(getHit())
+ {
+ // stop processing as soon as a hit was recognized
+ return;
+ }
+
+ switch(rCandidate.getPrimitive2DID())
+ {
+ case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D :
+ {
+ // remember current ViewInformation2D
+ const primitive2d::TransformPrimitive2D& rTransformCandidate(static_cast< const primitive2d::TransformPrimitive2D& >(rCandidate));
+ const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
+
+ // create new local ViewInformation2D containing transformation
+ const geometry::ViewInformation2D aViewInformation2D(
+ getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation(),
+ getViewInformation2D().getViewTransformation(),
+ getViewInformation2D().getViewport(),
+ getViewInformation2D().getVisualizedPage(),
+ getViewInformation2D().getViewTime());
+ updateViewInformation(aViewInformation2D);
+
+ // process child content recursively
+ process(rTransformCandidate.getChildren());
+
+ // restore transformations
+ updateViewInformation(aLastViewInformation2D);
+
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D :
+ {
+ if(!getHitTextOnly())
+ {
+ // create hairline in discrete coordinates
+ const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonHairlinePrimitive2D& >(rCandidate));
+
+ // use hairline test
+ mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance());
+ }
+
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYGONMARKERPRIMITIVE2D :
+ {
+ if(!getHitTextOnly())
+ {
+ // handle marker like hairline; no need to decompose in dashes
+ const primitive2d::PolygonMarkerPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonMarkerPrimitive2D& >(rCandidate));
+
+ // use hairline test
+ mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance());
+ }
+
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D :
+ {
+ if(!getHitTextOnly())
+ {
+ // handle stroke evtl. directly; no need to decompose to filled polygon outlines
+ const primitive2d::PolygonStrokePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonStrokePrimitive2D& >(rCandidate));
+ const attribute::LineAttribute& rLineAttribute = rPolygonCandidate.getLineAttribute();
+
+ if(basegfx::fTools::more(rLineAttribute.getWidth(), 0.0))
+ {
+ if(basegfx::B2DLineJoin::Miter == rLineAttribute.getLineJoin())
+ {
+ // if line is mitered, use decomposition since mitered line
+ // geometry may use more space than the geometry grown by half line width
+ process(rCandidate);
+ }
+ else
+ {
+ // for all other B2DLINEJOIN_* do a hairline HitTest with expanded tolerance
+ const basegfx::B2DVector aDiscreteHalfLineVector(getViewInformation2D().getObjectToViewTransformation()
+ * basegfx::B2DVector(rLineAttribute.getWidth() * 0.5, 0.0));
+ mbHit = checkHairlineHitWithTolerance(
+ rPolygonCandidate.getB2DPolygon(),
+ getDiscreteHitTolerance() + aDiscreteHalfLineVector.getLength());
+ }
+ }
+ else
+ {
+ // hairline; fallback to hairline test. Do not decompose
+ // since this may decompose the hairline to dashes
+ mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance());
+ }
+ }
+
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYGONWAVEPRIMITIVE2D :
+ {
+ if(!getHitTextOnly())
+ {
+ // do not use decompose; just handle like a line with width
+ const primitive2d::PolygonWavePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonWavePrimitive2D& >(rCandidate));
+ double fLogicHitTolerance(0.0);
+
+ // if WaveHeight, grow by it
+ if(basegfx::fTools::more(rPolygonCandidate.getWaveHeight(), 0.0))
+ {
+ fLogicHitTolerance += rPolygonCandidate.getWaveHeight();
+ }
+
+ // if line width, grow by it
+ if(basegfx::fTools::more(rPolygonCandidate.getLineAttribute().getWidth(), 0.0))
+ {
+ fLogicHitTolerance += rPolygonCandidate.getLineAttribute().getWidth() * 0.5;
+ }
+
+ const basegfx::B2DVector aDiscreteHalfLineVector(getViewInformation2D().getObjectToViewTransformation()
+ * basegfx::B2DVector(fLogicHitTolerance, 0.0));
+
+ mbHit = checkHairlineHitWithTolerance(
+ rPolygonCandidate.getB2DPolygon(),
+ getDiscreteHitTolerance() + aDiscreteHalfLineVector.getLength());
+ }
+
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D :
+ {
+ if(!getHitTextOnly())
+ {
+ // create filled polyPolygon in discrete coordinates
+ const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolyPolygonColorPrimitive2D& >(rCandidate));
+
+ // use fill hit test
+ mbHit = checkFillHitWithTolerance(rPolygonCandidate.getB2DPolyPolygon(), getDiscreteHitTolerance());
+ }
+
+ break;
+ }
+ case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D :
+ {
+ // sub-transparence group
+ const primitive2d::TransparencePrimitive2D& rTransCandidate(static_cast< const primitive2d::TransparencePrimitive2D& >(rCandidate));
+
+ // Currently the transparence content is not taken into account; only
+ // the children are recursively checked for hit. This may be refined for
+ // parts where the content is completely transparent if needed.
+ process(rTransCandidate.getChildren());
+
+ break;
+ }
+ case PRIMITIVE2D_ID_MASKPRIMITIVE2D :
+ {
+ // create mask in discrete coordinates; only recursively continue
+ // with content when HitTest position is inside the mask
+ const primitive2d::MaskPrimitive2D& rMaskCandidate(static_cast< const primitive2d::MaskPrimitive2D& >(rCandidate));
+
+ // use fill hit test
+ if(checkFillHitWithTolerance(rMaskCandidate.getMask(), getDiscreteHitTolerance()))
+ {
+ // recursively HitTest children
+ process(rMaskCandidate.getChildren());
+ }
+
+ break;
+ }
+ case PRIMITIVE2D_ID_SCENEPRIMITIVE2D :
+ {
+ if(!getHitTextOnly())
+ {
+ const primitive2d::ScenePrimitive2D& rScenePrimitive2D(
+ static_cast< const primitive2d::ScenePrimitive2D& >(rCandidate));
+ check3DHit(rScenePrimitive2D);
+ }
+
+ break;
+ }
+ case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D :
+ case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D :
+ case PRIMITIVE2D_ID_GRIDPRIMITIVE2D :
+ case PRIMITIVE2D_ID_HELPLINEPRIMITIVE2D :
+ {
+ // ignorable primitives
+ break;
+ }
+ case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D :
+ {
+ // Ignore shadows; we do not want to have shadows hittable.
+ // Remove this one to make shadows hittable on demand.
+ break;
+ }
+ case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D :
+ case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D :
+ {
+ // for text use the BoundRect of the primitive itself
+ const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
+
+ if(!aRange.isEmpty())
+ {
+ const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange));
+ mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance());
+ }
+
+ break;
+ }
+ case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D :
+ {
+ if(!getHitTextOnly())
+ {
+ // The recently added BitmapEx::GetTransparency() makes it easy to extend
+ // the BitmapPrimitive2D HitTest to take the contained BitmapEx and it's
+ // transparency into account
+ const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
+
+ if(!aRange.isEmpty())
+ {
+ const primitive2d::BitmapPrimitive2D& rBitmapCandidate(static_cast< const primitive2d::BitmapPrimitive2D& >(rCandidate));
+ const BitmapEx aBitmapEx(VCLUnoHelper::GetBitmap(rBitmapCandidate.getXBitmap()));
+ const Size& rSizePixel(aBitmapEx.GetSizePixel());
+
+ // When tiled rendering, don't bother with the pixel size of the candidate.
+ if(rSizePixel.Width() && rSizePixel.Height() && !comphelper::LibreOfficeKit::isActive())
+ {
+ basegfx::B2DHomMatrix aBackTransform(
+ getViewInformation2D().getObjectToViewTransformation() *
+ rBitmapCandidate.getTransform());
+ aBackTransform.invert();
+
+ const basegfx::B2DPoint aRelativePoint(aBackTransform * getDiscreteHitPosition());
+ const basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0);
+
+ if(aUnitRange.isInside(aRelativePoint))
+ {
+ const sal_Int32 nX(basegfx::fround(aRelativePoint.getX() * rSizePixel.Width()));
+ const sal_Int32 nY(basegfx::fround(aRelativePoint.getY() * rSizePixel.Height()));
+
+ mbHit = (0 != aBitmapEx.GetAlpha(nX, nY));
+ }
+ }
+ else
+ {
+ // fallback to standard HitTest
+ const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange));
+ mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance());
+ }
+ }
+ }
+
+ break;
+ }
+ case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D :
+ case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D :
+ case PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D :
+ case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D :
+ case PRIMITIVE2D_ID_FILLHATCHPRIMITIVE2D :
+ case PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D :
+ case PRIMITIVE2D_ID_MEDIAPRIMITIVE2D:
+ {
+ if(!getHitTextOnly())
+ {
+ // Class of primitives for which just the BoundRect of the primitive itself
+ // will be used for HitTest currently.
+ //
+ // This may be refined in the future, e.g:
+ // - For Bitmaps, the mask and/or transparence information may be used
+ // - For MetaFiles, the MetaFile content may be used
+ const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
+
+ if(!aRange.isEmpty())
+ {
+ const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange));
+ mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance());
+ }
+ }
+
+ break;
+ }
+ case PRIMITIVE2D_ID_HIDDENGEOMETRYPRIMITIVE2D :
+ {
+ // HiddenGeometryPrimitive2D; the default decomposition would return an empty sequence,
+ // so force this primitive to process its children directly if the switch is set
+ // (which is the default). Else, ignore invisible content
+ const primitive2d::HiddenGeometryPrimitive2D& rHiddenGeometry(static_cast< const primitive2d::HiddenGeometryPrimitive2D& >(rCandidate));
+ const primitive2d::Primitive2DContainer& rChildren = rHiddenGeometry.getChildren();
+
+ if(!rChildren.empty())
+ {
+ process(rChildren);
+ }
+
+ break;
+ }
+ case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D :
+ {
+ if(!getHitTextOnly())
+ {
+ const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate(static_cast< const primitive2d::PointArrayPrimitive2D& >(rCandidate));
+ const std::vector< basegfx::B2DPoint >& rPositions = rPointArrayCandidate.getPositions();
+ const sal_uInt32 nCount(rPositions.size());
+
+ for(sal_uInt32 a(0); !getHit() && a < nCount; a++)
+ {
+ const basegfx::B2DPoint aPosition(getViewInformation2D().getObjectToViewTransformation() * rPositions[a]);
+ const basegfx::B2DVector aDistance(aPosition - getDiscreteHitPosition());
+
+ if(aDistance.getLength() <= getDiscreteHitTolerance())
+ {
+ mbHit = true;
+ }
+ }
+ }
+
+ break;
+ }
+ default :
+ {
+ // process recursively
+ process(rCandidate);
+
+ break;
+ }
+ }
+
+ if (getHit() && getCollectHitStack())
+ {
+ /// push candidate to HitStack to create it. This only happens when a hit is found and
+ /// creating the HitStack was requested (see collectHitStack)
+ maHitStack.append(primitive2d::Primitive2DReference(const_cast< primitive2d::BasePrimitive2D* >(&rCandidate)));
+ }
+ }
+
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/linegeometryextractor2d.cxx b/drawinglayer/source/processor2d/linegeometryextractor2d.cxx
new file mode 100644
index 000000000..08ec1232d
--- /dev/null
+++ b/drawinglayer/source/processor2d/linegeometryextractor2d.cxx
@@ -0,0 +1,125 @@
+/* -*- 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 <drawinglayer/processor2d/linegeometryextractor2d.hxx>
+#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
+#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+
+
+using namespace com::sun::star;
+
+
+namespace drawinglayer::processor2d
+{
+ LineGeometryExtractor2D::LineGeometryExtractor2D(const geometry::ViewInformation2D& rViewInformation)
+ : BaseProcessor2D(rViewInformation),
+ mbInLineGeometry(false)
+ {
+ }
+
+ LineGeometryExtractor2D::~LineGeometryExtractor2D()
+ {
+ }
+
+ void LineGeometryExtractor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
+ {
+ switch(rCandidate.getPrimitive2DID())
+ {
+ case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D :
+ case PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D :
+ {
+ // enter a line geometry group (with or without LineEnds)
+ bool bOldState(mbInLineGeometry);
+ mbInLineGeometry = true;
+ process(rCandidate);
+ mbInLineGeometry = bOldState;
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D :
+ {
+ if(mbInLineGeometry)
+ {
+ // extract hairline line geometry in world coordinates
+ const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonHairlinePrimitive2D& >(rCandidate));
+ basegfx::B2DPolygon aLocalPolygon(rPolygonCandidate.getB2DPolygon());
+ aLocalPolygon.transform(getViewInformation2D().getObjectTransformation());
+ maExtractedHairlines.push_back(aLocalPolygon);
+ }
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D :
+ {
+ if(mbInLineGeometry)
+ {
+ // extract filled line geometry (line with width)
+ const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolyPolygonColorPrimitive2D& >(rCandidate));
+ basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolygonCandidate.getB2DPolyPolygon());
+ aLocalPolyPolygon.transform(getViewInformation2D().getObjectTransformation());
+ maExtractedLineFills.push_back(aLocalPolyPolygon);
+ }
+ break;
+ }
+ case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D :
+ {
+ // remember current transformation and ViewInformation
+ const primitive2d::TransformPrimitive2D& rTransformCandidate(static_cast< const primitive2d::TransformPrimitive2D& >(rCandidate));
+ const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
+
+ // create new transformations for CurrentTransformation and for local ViewInformation2D
+ const geometry::ViewInformation2D aViewInformation2D(
+ getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation(),
+ getViewInformation2D().getViewTransformation(),
+ getViewInformation2D().getViewport(),
+ getViewInformation2D().getVisualizedPage(),
+ getViewInformation2D().getViewTime());
+ updateViewInformation(aViewInformation2D);
+
+ // process content
+ process(rTransformCandidate.getChildren());
+
+ // restore transformations
+ updateViewInformation(aLastViewInformation2D);
+
+ break;
+ }
+ case PRIMITIVE2D_ID_SCENEPRIMITIVE2D :
+ case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D :
+ case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D :
+ case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D :
+ case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D :
+ case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D :
+ case PRIMITIVE2D_ID_MASKPRIMITIVE2D :
+ {
+ // ignorable primitives
+ break;
+ }
+ default :
+ {
+ // process recursively
+ process(rCandidate);
+ break;
+ }
+ }
+ }
+
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/objectinfoextractor2d.cxx b/drawinglayer/source/processor2d/objectinfoextractor2d.cxx
new file mode 100644
index 000000000..552406d53
--- /dev/null
+++ b/drawinglayer/source/processor2d/objectinfoextractor2d.cxx
@@ -0,0 +1,77 @@
+/* -*- 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 <drawinglayer/processor2d/objectinfoextractor2d.hxx>
+#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
+#include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx>
+
+using namespace com::sun::star;
+
+namespace drawinglayer::processor2d
+{
+ void ObjectInfoPrimitiveExtractor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
+ {
+ if(mpFound)
+ return;
+
+ switch(rCandidate.getPrimitive2DID())
+ {
+ case PRIMITIVE2D_ID_OBJECTINFOPRIMITIVE2D :
+ {
+ mpFound = dynamic_cast< const primitive2d::ObjectInfoPrimitive2D* >(&rCandidate);
+ break;
+ }
+ default :
+ {
+ // we look for an encapsulated primitive, so do not decompose primitives
+ // based on GroupPrimitive2D, just visit their children. It may be that more
+ // group-like primitives need to be added here, but all primitives with
+ // grouping functionality should be implemented based on the GroupPrimitive2D
+ // class and have their main content accessible as children
+ const primitive2d::GroupPrimitive2D* pGroupPrimitive2D = dynamic_cast< const primitive2d::GroupPrimitive2D* >(&rCandidate);
+
+ if(pGroupPrimitive2D)
+ {
+ // process group children recursively
+ process(pGroupPrimitive2D->getChildren());
+ }
+ else
+ {
+ // do not process recursively, we *only* want to find existing
+ // ObjectInfoPrimitive2D entries
+ }
+
+ break;
+ }
+ }
+ }
+
+ ObjectInfoPrimitiveExtractor2D::ObjectInfoPrimitiveExtractor2D(const geometry::ViewInformation2D& rViewInformation)
+ : BaseProcessor2D(rViewInformation),
+ mpFound(nullptr)
+ {
+ }
+
+ ObjectInfoPrimitiveExtractor2D::~ObjectInfoPrimitiveExtractor2D()
+ {
+ }
+
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/processor2dtools.cxx b/drawinglayer/source/processor2d/processor2dtools.cxx
new file mode 100644
index 000000000..7bc0f5fa0
--- /dev/null
+++ b/drawinglayer/source/processor2d/processor2dtools.cxx
@@ -0,0 +1,62 @@
+/* -*- 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 <drawinglayer/processor2d/processor2dtools.hxx>
+#include <vcl/gdimtf.hxx>
+#include "vclpixelprocessor2d.hxx"
+#include "vclmetafileprocessor2d.hxx"
+
+
+using namespace com::sun::star;
+
+
+namespace drawinglayer::processor2d
+{
+ std::unique_ptr<BaseProcessor2D> createPixelProcessor2DFromOutputDevice(
+ OutputDevice& rTargetOutDev,
+ const drawinglayer::geometry::ViewInformation2D& rViewInformation2D)
+ {
+ // create Pixel Vcl-Processor
+ return std::make_unique<VclPixelProcessor2D>(rViewInformation2D, rTargetOutDev);
+ }
+
+ std::unique_ptr<BaseProcessor2D> createProcessor2DFromOutputDevice(
+ OutputDevice& rTargetOutDev,
+ const drawinglayer::geometry::ViewInformation2D& rViewInformation2D)
+ {
+ const GDIMetaFile* pMetaFile = rTargetOutDev.GetConnectMetaFile();
+ const bool bOutputToRecordingMetaFile(pMetaFile && pMetaFile->IsRecord() && !pMetaFile->IsPause());
+
+ if(bOutputToRecordingMetaFile)
+ {
+ // create MetaFile Vcl-Processor and process
+ return std::make_unique<VclMetafileProcessor2D>(rViewInformation2D, rTargetOutDev);
+ }
+ else
+ {
+ // create Pixel Vcl-Processor
+ return createPixelProcessor2DFromOutputDevice(
+ rTargetOutDev,
+ rViewInformation2D);
+ }
+ }
+
+} // end of namespace
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/processorfromoutputdevice.cxx b/drawinglayer/source/processor2d/processorfromoutputdevice.cxx
new file mode 100644
index 000000000..c8433753a
--- /dev/null
+++ b/drawinglayer/source/processor2d/processorfromoutputdevice.cxx
@@ -0,0 +1,50 @@
+/* -*- 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 <vcl/outdev.hxx>
+#include <vcl/gdimtf.hxx>
+#include <drawinglayer/processor2d/processorfromoutputdevice.hxx>
+#include "vclmetafileprocessor2d.hxx"
+#include "vclpixelprocessor2d.hxx"
+
+using namespace com::sun::star;
+
+namespace drawinglayer::processor2d
+{
+ std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> createBaseProcessor2DFromOutputDevice(
+ OutputDevice& rTargetOutDev,
+ const drawinglayer::geometry::ViewInformation2D& rViewInformation2D)
+ {
+ const GDIMetaFile* pMetaFile = rTargetOutDev.GetConnectMetaFile();
+ const bool bOutputToRecordingMetaFile(pMetaFile && pMetaFile->IsRecord() && !pMetaFile->IsPause());
+
+ if(bOutputToRecordingMetaFile)
+ {
+ // create MetaFile Vcl-Processor and process
+ return std::make_unique<drawinglayer::processor2d::VclMetafileProcessor2D>(rViewInformation2D, rTargetOutDev);
+ }
+ else
+ {
+ // create Pixel Vcl-Processor
+ return std::make_unique<drawinglayer::processor2d::VclPixelProcessor2D>(rViewInformation2D, rTargetOutDev);
+ }
+ }
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/textaspolygonextractor2d.cxx b/drawinglayer/source/processor2d/textaspolygonextractor2d.cxx
new file mode 100644
index 000000000..fcf15b189
--- /dev/null
+++ b/drawinglayer/source/processor2d/textaspolygonextractor2d.cxx
@@ -0,0 +1,231 @@
+/* -*- 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 <drawinglayer/processor2d/textaspolygonextractor2d.hxx>
+#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
+#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonHairlinePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+
+
+namespace drawinglayer::processor2d
+{
+ void TextAsPolygonExtractor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
+ {
+ switch(rCandidate.getPrimitive2DID())
+ {
+ case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D :
+ {
+ // TextDecoratedPortionPrimitive2D can produce the following primitives
+ // when being decomposed:
+ //
+ // - TextSimplePortionPrimitive2D
+ // - PolygonWavePrimitive2D
+ // - PolygonStrokePrimitive2D
+ // - PolygonStrokePrimitive2D
+ // - PolyPolygonColorPrimitive2D
+ // - PolyPolygonHairlinePrimitive2D
+ // - PolygonHairlinePrimitive2D
+ // - ShadowPrimitive2D
+ // - ModifiedColorPrimitive2D
+ // - TransformPrimitive2D
+ // - TextEffectPrimitive2D
+ // - ModifiedColorPrimitive2D
+ // - TransformPrimitive2D
+ // - GroupPrimitive2D
+
+ // encapsulate with flag and use decomposition
+ mnInText++;
+ process(rCandidate);
+ mnInText--;
+
+ break;
+ }
+ case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D :
+ {
+ // TextSimplePortionPrimitive2D can produce the following primitives
+ // when being decomposed:
+ //
+ // - PolyPolygonColorPrimitive2D
+ // - TextEffectPrimitive2D
+ // - ModifiedColorPrimitive2D
+ // - TransformPrimitive2D
+ // - GroupPrimitive2D
+
+ // encapsulate with flag and use decomposition
+ mnInText++;
+ process(rCandidate);
+ mnInText--;
+
+ break;
+ }
+
+ // as can be seen from the TextSimplePortionPrimitive2D and the
+ // TextDecoratedPortionPrimitive2D, inside of the mnInText marks
+ // the following primitives can occur containing geometry data
+ // from text decomposition:
+ //
+ // - PolyPolygonColorPrimitive2D
+ // - PolygonHairlinePrimitive2D
+ // - PolyPolygonHairlinePrimitive2D (for convenience)
+ //
+ case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D :
+ {
+ if(mnInText)
+ {
+ const primitive2d::PolyPolygonColorPrimitive2D& rPoPoCoCandidate(static_cast< const primitive2d::PolyPolygonColorPrimitive2D& >(rCandidate));
+ basegfx::B2DPolyPolygon aPolyPolygon(rPoPoCoCandidate.getB2DPolyPolygon());
+
+ if(aPolyPolygon.count())
+ {
+ // transform the PolyPolygon
+ aPolyPolygon.transform(getViewInformation2D().getObjectToViewTransformation());
+
+ // get evtl. corrected color
+ const basegfx::BColor aColor(maBColorModifierStack.getModifiedColor(rPoPoCoCandidate.getBColor()));
+
+ // add to result vector
+ maTarget.emplace_back(aPolyPolygon, aColor, true);
+ }
+ }
+
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D :
+ {
+ if(mnInText)
+ {
+ const primitive2d::PolygonHairlinePrimitive2D& rPoHaCandidate(static_cast< const primitive2d::PolygonHairlinePrimitive2D& >(rCandidate));
+ basegfx::B2DPolygon aPolygon(rPoHaCandidate.getB2DPolygon());
+
+ if(aPolygon.count())
+ {
+ // transform the Polygon
+ aPolygon.transform(getViewInformation2D().getObjectToViewTransformation());
+
+ // get evtl. corrected color
+ const basegfx::BColor aColor(maBColorModifierStack.getModifiedColor(rPoHaCandidate.getBColor()));
+
+ // add to result vector
+ maTarget.emplace_back(basegfx::B2DPolyPolygon(aPolygon), aColor, false);
+ }
+ }
+
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYPOLYGONHAIRLINEPRIMITIVE2D :
+ {
+ if(mnInText)
+ {
+ const primitive2d::PolyPolygonHairlinePrimitive2D& rPoPoHaCandidate(static_cast< const primitive2d::PolyPolygonHairlinePrimitive2D& >(rCandidate));
+ basegfx::B2DPolyPolygon aPolyPolygon(rPoPoHaCandidate.getB2DPolyPolygon());
+
+ if(aPolyPolygon.count())
+ {
+ // transform the Polygon
+ aPolyPolygon.transform(getViewInformation2D().getObjectToViewTransformation());
+
+ // get evtl. corrected color
+ const basegfx::BColor aColor(maBColorModifierStack.getModifiedColor(rPoPoHaCandidate.getBColor()));
+
+ // add to result vector
+ maTarget.emplace_back(aPolyPolygon, aColor, false);
+ }
+ }
+
+ break;
+ }
+
+ // usage of color modification stack is needed
+ case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D :
+ {
+ const primitive2d::ModifiedColorPrimitive2D& rModifiedColorCandidate(static_cast< const primitive2d::ModifiedColorPrimitive2D& >(rCandidate));
+
+ if(!rModifiedColorCandidate.getChildren().empty())
+ {
+ maBColorModifierStack.push(rModifiedColorCandidate.getColorModifier());
+ process(rModifiedColorCandidate.getChildren());
+ maBColorModifierStack.pop();
+ }
+
+ break;
+ }
+
+ // usage of transformation stack is needed
+ case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D :
+ {
+ // remember current transformation and ViewInformation
+ const primitive2d::TransformPrimitive2D& rTransformCandidate(static_cast< const primitive2d::TransformPrimitive2D& >(rCandidate));
+ const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
+
+ // create new transformations for CurrentTransformation and for local ViewInformation2D
+ const geometry::ViewInformation2D aViewInformation2D(
+ getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation(),
+ getViewInformation2D().getViewTransformation(),
+ getViewInformation2D().getViewport(),
+ getViewInformation2D().getVisualizedPage(),
+ getViewInformation2D().getViewTime());
+ updateViewInformation(aViewInformation2D);
+
+ // process content
+ process(rTransformCandidate.getChildren());
+
+ // restore transformations
+ updateViewInformation(aLastViewInformation2D);
+
+ break;
+ }
+
+ // ignorable primitives
+ case PRIMITIVE2D_ID_SCENEPRIMITIVE2D :
+ case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D :
+ case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D :
+ case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D :
+ case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D :
+ case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D :
+ case PRIMITIVE2D_ID_MASKPRIMITIVE2D :
+ {
+ break;
+ }
+
+ default :
+ {
+ // process recursively
+ process(rCandidate);
+ break;
+ }
+ }
+ }
+
+ TextAsPolygonExtractor2D::TextAsPolygonExtractor2D(const geometry::ViewInformation2D& rViewInformation)
+ : BaseProcessor2D(rViewInformation),
+ maBColorModifierStack(),
+ mnInText(0)
+ {
+ }
+
+ TextAsPolygonExtractor2D::~TextAsPolygonExtractor2D()
+ {
+ }
+
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx b/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx
new file mode 100644
index 000000000..4a2934402
--- /dev/null
+++ b/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx
@@ -0,0 +1,444 @@
+/* -*- 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 <sal/log.hxx>
+
+#include <algorithm>
+#include <map>
+#include <vector>
+
+#include "vclhelperbufferdevice.hxx"
+#include <basegfx/range/b2drange.hxx>
+#include <vcl/bitmapex.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <tools/stream.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/lazydelete.hxx>
+#include <vcl/dibtools.hxx>
+#include <vcl/skia/SkiaHelper.hxx>
+#include <mutex>
+
+// buffered VDev usage
+
+namespace
+{
+class VDevBuffer : public Timer
+{
+private:
+ struct Entry
+ {
+ VclPtr<VirtualDevice> buf;
+ bool isTransparent = false;
+ Entry(const VclPtr<VirtualDevice>& vdev, bool bTransparent)
+ : buf(vdev)
+ , isTransparent(bTransparent)
+ {
+ }
+ };
+
+ std::mutex m_aMutex;
+
+ // available buffers
+ std::vector<Entry> maFreeBuffers;
+
+ // allocated/used buffers (remembered to allow deleting them in destructor)
+ std::vector<Entry> maUsedBuffers;
+
+ // remember what outputdevice was the template passed to VirtualDevice::Create
+ // so we can test if that OutputDevice was disposed before reusing a
+ // virtualdevice because that isn't safe to do at least for Gtk2
+ std::map<VclPtr<VirtualDevice>, VclPtr<OutputDevice>> maDeviceTemplates;
+
+ static bool isSizeSuitable(const VclPtr<VirtualDevice>& device, const Size& size);
+
+public:
+ VDevBuffer();
+ virtual ~VDevBuffer() override;
+
+ VclPtr<VirtualDevice> alloc(OutputDevice& rOutDev, const Size& rSizePixel, bool bTransparent);
+ void free(VirtualDevice& rDevice);
+
+ // Timer virtuals
+ virtual void Invoke() override;
+};
+
+VDevBuffer::VDevBuffer()
+ : Timer("drawinglayer::VDevBuffer via Invoke()")
+{
+ SetTimeout(10L * 1000L); // ten seconds
+}
+
+VDevBuffer::~VDevBuffer()
+{
+ std::unique_lock aGuard(m_aMutex);
+ Stop();
+
+ while (!maFreeBuffers.empty())
+ {
+ maFreeBuffers.back().buf.disposeAndClear();
+ maFreeBuffers.pop_back();
+ }
+
+ while (!maUsedBuffers.empty())
+ {
+ maUsedBuffers.back().buf.disposeAndClear();
+ maUsedBuffers.pop_back();
+ }
+}
+
+bool VDevBuffer::isSizeSuitable(const VclPtr<VirtualDevice>& device, const Size& rSizePixel)
+{
+ if (device->GetOutputWidthPixel() >= rSizePixel.getWidth()
+ && device->GetOutputHeightPixel() >= rSizePixel.getHeight())
+ {
+ bool requireSmall = false;
+#if defined(UNX)
+ // HACK: See the small size handling in SvpSalVirtualDevice::CreateSurface().
+ // Make sure to not reuse a larger device when a small one should be preferred.
+ if (device->GetRenderBackendName() == "svp")
+ requireSmall = true;
+#endif
+ // The same for Skia, see renderMethodToUseForSize().
+ if (SkiaHelper::isVCLSkiaEnabled())
+ requireSmall = true;
+ if (requireSmall)
+ {
+ if (rSizePixel.getWidth() <= 32 && rSizePixel.getHeight() <= 32
+ && (device->GetOutputWidthPixel() > 32 || device->GetOutputHeightPixel() > 32))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSizePixel,
+ bool bTransparent)
+{
+ std::unique_lock aGuard(m_aMutex);
+ VclPtr<VirtualDevice> pRetval;
+
+ sal_Int32 nBits = rOutDev.GetBitCount();
+
+ bool bOkay(false);
+ if (!maFreeBuffers.empty())
+ {
+ auto aFound(maFreeBuffers.end());
+
+ for (auto a = maFreeBuffers.begin(); a != maFreeBuffers.end(); ++a)
+ {
+ assert(a->buf && "Empty pointer in VDevBuffer (!)");
+
+ if (nBits == a->buf->GetBitCount() && bTransparent == a->isTransparent)
+ {
+ // candidate is valid due to bit depth
+ if (aFound != maFreeBuffers.end())
+ {
+ // already found
+ if (bOkay)
+ {
+ // found is valid
+ const bool bCandidateOkay = isSizeSuitable(a->buf, rSizePixel);
+
+ if (bCandidateOkay)
+ {
+ // found and candidate are valid
+ const sal_uLong aSquare(aFound->buf->GetOutputWidthPixel()
+ * aFound->buf->GetOutputHeightPixel());
+ const sal_uLong aCandidateSquare(a->buf->GetOutputWidthPixel()
+ * a->buf->GetOutputHeightPixel());
+
+ if (aCandidateSquare < aSquare)
+ {
+ // candidate is valid and smaller, use it
+ aFound = a;
+ }
+ }
+ else
+ {
+ // found is valid, candidate is not. Keep found
+ }
+ }
+ else
+ {
+ // found is invalid, use candidate
+ aFound = a;
+ bOkay = isSizeSuitable(aFound->buf, rSizePixel);
+ }
+ }
+ else
+ {
+ // none yet, use candidate
+ aFound = a;
+ bOkay = isSizeSuitable(aFound->buf, rSizePixel);
+ }
+ }
+ }
+
+ if (aFound != maFreeBuffers.end())
+ {
+ pRetval = aFound->buf;
+ maFreeBuffers.erase(aFound);
+ }
+ }
+
+ if (pRetval)
+ {
+ // found a suitable cached virtual device, but the
+ // outputdevice it was based on has been disposed,
+ // drop it and create a new one instead as reusing
+ // such devices is unsafe under at least Gtk2
+ if (maDeviceTemplates[pRetval]->isDisposed())
+ {
+ maDeviceTemplates.erase(pRetval);
+ pRetval.disposeAndClear();
+ }
+ else
+ {
+ if (bOkay)
+ {
+ pRetval->Erase(pRetval->PixelToLogic(
+ tools::Rectangle(0, 0, rSizePixel.getWidth(), rSizePixel.getHeight())));
+ }
+ else
+ {
+ pRetval->SetOutputSizePixel(rSizePixel, true);
+ }
+ }
+ }
+
+ // no success yet, create new buffer
+ if (!pRetval)
+ {
+ pRetval = VclPtr<VirtualDevice>::Create(rOutDev, DeviceFormat::DEFAULT,
+ bTransparent ? DeviceFormat::DEFAULT
+ : DeviceFormat::NONE);
+ maDeviceTemplates[pRetval] = &rOutDev;
+ pRetval->SetOutputSizePixel(rSizePixel, true);
+ }
+ else
+ {
+ // reused, reset some values
+ pRetval->SetMapMode();
+ pRetval->SetRasterOp(RasterOp::OverPaint);
+ }
+
+ // remember allocated buffer
+ maUsedBuffers.emplace_back(pRetval, bTransparent);
+
+ return pRetval;
+}
+
+void VDevBuffer::free(VirtualDevice& rDevice)
+{
+ std::unique_lock aGuard(m_aMutex);
+ const auto aUsedFound
+ = std::find_if(maUsedBuffers.begin(), maUsedBuffers.end(),
+ [&rDevice](const Entry& el) { return el.buf == &rDevice; });
+ SAL_WARN_IF(aUsedFound == maUsedBuffers.end(), "drawinglayer",
+ "OOps, non-registered buffer freed (!)");
+ if (aUsedFound != maUsedBuffers.end())
+ {
+ maFreeBuffers.emplace_back(*aUsedFound);
+ maUsedBuffers.erase(aUsedFound);
+ SAL_WARN_IF(maFreeBuffers.size() > 1000, "drawinglayer",
+ "excessive cached buffers, " << maFreeBuffers.size() << " entries!");
+ }
+ Start();
+}
+
+void VDevBuffer::Invoke()
+{
+ std::unique_lock aGuard(m_aMutex);
+
+ while (!maFreeBuffers.empty())
+ {
+ auto aLastOne = maFreeBuffers.back();
+ maDeviceTemplates.erase(aLastOne.buf);
+ aLastOne.buf.disposeAndClear();
+ maFreeBuffers.pop_back();
+ }
+}
+}
+
+// support for rendering Bitmap and BitmapEx contents
+
+namespace drawinglayer
+{
+// static global VDev buffer for the VclProcessor2D's (VclMetafileProcessor2D and VclPixelProcessor2D)
+VDevBuffer& getVDevBuffer()
+{
+ // secure global instance with Vcl's safe destroyer of external (seen by
+ // library base) stuff, the remembered VDevs need to be deleted before
+ // Vcl's deinit
+ static vcl::DeleteOnDeinit<VDevBuffer> aVDevBuffer{};
+ return *aVDevBuffer.get();
+}
+
+impBufferDevice::impBufferDevice(OutputDevice& rOutDev, const basegfx::B2DRange& rRange, bool bCrop)
+ : mrOutDev(rOutDev)
+ , mpContent(nullptr)
+ , mpAlpha(nullptr)
+{
+ basegfx::B2DRange aRangePixel(rRange);
+ aRangePixel.transform(mrOutDev.GetViewTransformation());
+ maDestPixel = tools::Rectangle(floor(aRangePixel.getMinX()), floor(aRangePixel.getMinY()),
+ ceil(aRangePixel.getMaxX()), ceil(aRangePixel.getMaxY()));
+ if (bCrop)
+ maDestPixel.Intersection({ {}, mrOutDev.GetOutputSizePixel() });
+
+ if (!isVisible())
+ return;
+
+ mpContent = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize(), true);
+
+ // #i93485# assert when copying from window to VDev is used
+ SAL_WARN_IF(
+ mrOutDev.GetOutDevType() == OUTDEV_WINDOW, "drawinglayer",
+ "impBufferDevice render helper: Copying from Window to VDev, this should be avoided (!)");
+
+ MapMode aNewMapMode(mrOutDev.GetMapMode());
+
+ const Point aLogicTopLeft(mrOutDev.PixelToLogic(maDestPixel.TopLeft()));
+ aNewMapMode.SetOrigin(Point(-aLogicTopLeft.X(), -aLogicTopLeft.Y()));
+
+ mpContent->SetMapMode(aNewMapMode);
+
+ // copy AA flag for new target
+ mpContent->SetAntialiasing(mrOutDev.GetAntialiasing());
+
+ // copy RasterOp (e.g. may be RasterOp::Xor on destination)
+ mpContent->SetRasterOp(mrOutDev.GetRasterOp());
+}
+
+impBufferDevice::~impBufferDevice()
+{
+ if (mpContent)
+ {
+ getVDevBuffer().free(*mpContent);
+ }
+
+ if (mpAlpha)
+ {
+ getVDevBuffer().free(*mpAlpha);
+ }
+}
+
+void impBufferDevice::paint(double fTrans)
+{
+ if (!isVisible())
+ return;
+
+ const Point aEmptyPoint;
+ const Size aSizePixel(maDestPixel.GetSize());
+ const bool bWasEnabledDst(mrOutDev.IsMapModeEnabled());
+#ifdef DBG_UTIL
+ static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore
+#endif
+
+ mrOutDev.EnableMapMode(false);
+ mpContent->EnableMapMode(false);
+
+#ifdef DBG_UTIL
+ if (bDoSaveForVisualControl)
+ {
+ SvFileStream aNew(
+#ifdef _WIN32
+ "c:\\content.bmp",
+#else
+ "~/content.bmp",
+#endif
+ StreamMode::WRITE | StreamMode::TRUNC);
+ Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel));
+ WriteDIB(aContent, aNew, false, true);
+ }
+#endif
+
+ // during painting the buffer, disable evtl. set RasterOp (may be RasterOp::Xor)
+ const RasterOp aOrigRasterOp(mrOutDev.GetRasterOp());
+ mrOutDev.SetRasterOp(RasterOp::OverPaint);
+
+ if (mpAlpha)
+ {
+ mpAlpha->EnableMapMode(false);
+ AlphaMask aAlphaMask(mpAlpha->GetBitmap(aEmptyPoint, aSizePixel));
+
+#ifdef DBG_UTIL
+ if (bDoSaveForVisualControl)
+ {
+ SvFileStream aNew(
+#ifdef _WIN32
+ "c:\\transparence.bmp",
+#else
+ "~/transparence.bmp",
+#endif
+ StreamMode::WRITE | StreamMode::TRUNC);
+ WriteDIB(aAlphaMask.GetBitmap(), aNew, false, true);
+ }
+#endif
+
+ BitmapEx aContent(mpContent->GetBitmapEx(aEmptyPoint, aSizePixel));
+ aAlphaMask.BlendWith(aContent.GetAlpha());
+ mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent.GetBitmap(), aAlphaMask));
+ }
+ else if (0.0 != fTrans)
+ {
+ basegfx::B2DHomMatrix trans, scale;
+ trans.translate(maDestPixel.TopLeft().X(), maDestPixel.TopLeft().Y());
+ scale.scale(aSizePixel.Width(), aSizePixel.Height());
+ mrOutDev.DrawTransformedBitmapEx(
+ trans * scale, mpContent->GetBitmapEx(aEmptyPoint, aSizePixel), 1 - fTrans);
+ }
+ else
+ {
+ mrOutDev.DrawOutDev(maDestPixel.TopLeft(), aSizePixel, aEmptyPoint, aSizePixel, *mpContent);
+ }
+
+ mrOutDev.SetRasterOp(aOrigRasterOp);
+ mrOutDev.EnableMapMode(bWasEnabledDst);
+}
+
+VirtualDevice& impBufferDevice::getContent()
+{
+ SAL_WARN_IF(!mpContent, "drawinglayer",
+ "impBufferDevice: No content, check isVisible() before accessing (!)");
+ return *mpContent;
+}
+
+VirtualDevice& impBufferDevice::getTransparence()
+{
+ SAL_WARN_IF(!mpContent, "drawinglayer",
+ "impBufferDevice: No content, check isVisible() before accessing (!)");
+ if (!mpAlpha)
+ {
+ mpAlpha = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize(), false);
+ mpAlpha->SetMapMode(mpContent->GetMapMode());
+
+ // copy AA flag for new target; masking needs to be smooth
+ mpAlpha->SetAntialiasing(mpContent->GetAntialiasing());
+ }
+
+ return *mpAlpha;
+}
+} // end of namespace drawinglayer
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/vclhelperbufferdevice.hxx b/drawinglayer/source/processor2d/vclhelperbufferdevice.hxx
new file mode 100644
index 000000000..99585b05b
--- /dev/null
+++ b/drawinglayer/source/processor2d/vclhelperbufferdevice.hxx
@@ -0,0 +1,51 @@
+/* -*- 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 <vcl/virdev.hxx>
+
+namespace basegfx
+{
+class B2DRange;
+}
+
+// support methods for vcl direct gradient rendering
+
+namespace drawinglayer
+{
+class impBufferDevice
+{
+ OutputDevice& mrOutDev;
+ VclPtr<VirtualDevice> mpContent;
+ VclPtr<VirtualDevice> mpAlpha;
+ tools::Rectangle maDestPixel;
+
+public:
+ impBufferDevice(OutputDevice& rOutDev, const basegfx::B2DRange& rRange, bool bCrop = true);
+ ~impBufferDevice();
+
+ void paint(double fTrans = 0.0);
+ bool isVisible() const { return !maDestPixel.IsEmpty(); }
+ VirtualDevice& getContent();
+ VirtualDevice& getTransparence();
+};
+} // end of namespace drawinglayer
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx
new file mode 100644
index 000000000..673fd093d
--- /dev/null
+++ b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx
@@ -0,0 +1,2571 @@
+/* -*- 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 <cmath>
+#include <memory>
+#include "vclmetafileprocessor2d.hxx"
+#include "vclpixelprocessor2d.hxx"
+#include <rtl/ustring.hxx>
+#include <tools/gen.hxx>
+#include <tools/stream.hxx>
+#include <tools/diagnose_ex.h>
+#include <comphelper/flagguard.hxx>
+#include <comphelper/processfactory.hxx>
+#include <config_global.h>
+#include <basegfx/polygon/b2dpolygonclipper.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dlinegeometry.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/gradient.hxx>
+#include <vcl/graphictools.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/graph.hxx> // for PDFExtOutDevData Graphic support
+#include <toolkit/helper/formpdfexport.hxx> // for PDFExtOutDevData Graphic support
+#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
+#include <drawinglayer/primitive2d/textprimitive2d.hxx>
+#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolygonStrokeArrowPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonGradientPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonHatchPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
+#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
+#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
+#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
+#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
+#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx>
+#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
+#include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx>
+#include <drawinglayer/primitive2d/controlprimitive2d.hxx>
+#include <drawinglayer/primitive2d/graphicprimitive2d.hxx>
+#include <drawinglayer/primitive2d/pagepreviewprimitive2d.hxx>
+#include <drawinglayer/primitive2d/epsprimitive2d.hxx>
+#include <drawinglayer/primitive2d/structuretagprimitive2d.hxx>
+#include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx> // for Title/Description metadata
+
+#include <com/sun/star/awt/XControl.hpp>
+#include <com/sun/star/i18n/BreakIterator.hpp>
+#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
+#include <com/sun/star/i18n/WordType.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+
+using namespace com::sun::star;
+
+// #112245# definition for maximum allowed point count due to Metafile target.
+// To be on the safe side with the old tools polygon, use slightly less than
+// the theoretical maximum (bad experiences with tools polygon)
+
+#define MAX_POLYGON_POINT_COUNT_METAFILE (0x0000fff0)
+
+namespace
+{
+// #112245# helper to split line polygon in half
+void splitLinePolygon(const basegfx::B2DPolygon& rBasePolygon, basegfx::B2DPolygon& o_aLeft,
+ basegfx::B2DPolygon& o_aRight)
+{
+ const sal_uInt32 nCount(rBasePolygon.count());
+
+ if (nCount)
+ {
+ const sal_uInt32 nHalfCount((nCount - 1) >> 1);
+
+ o_aLeft = basegfx::B2DPolygon(rBasePolygon, 0, nHalfCount + 1);
+ o_aLeft.setClosed(false);
+
+ o_aRight = basegfx::B2DPolygon(rBasePolygon, nHalfCount, nCount - nHalfCount);
+ o_aRight.setClosed(false);
+
+ if (rBasePolygon.isClosed())
+ {
+ o_aRight.append(rBasePolygon.getB2DPoint(0));
+
+ if (rBasePolygon.areControlPointsUsed())
+ {
+ o_aRight.setControlPoints(o_aRight.count() - 1, rBasePolygon.getPrevControlPoint(0),
+ rBasePolygon.getNextControlPoint(0));
+ }
+ }
+ }
+ else
+ {
+ o_aLeft.clear();
+ o_aRight.clear();
+ }
+}
+
+// #112245# helper to evtl. split filled polygons to maximum metafile point count
+void fillPolyPolygonNeededToBeSplit(basegfx::B2DPolyPolygon& rPolyPolygon)
+{
+ const sal_uInt32 nPolyCount(rPolyPolygon.count());
+
+ if (!nPolyCount)
+ return;
+
+ basegfx::B2DPolyPolygon aSplitted;
+
+ for (sal_uInt32 a(0); a < nPolyCount; a++)
+ {
+ const basegfx::B2DPolygon& aCandidate(rPolyPolygon.getB2DPolygon(a));
+ const sal_uInt32 nPointCount(aCandidate.count());
+ bool bNeedToSplit(false);
+
+ if (aCandidate.areControlPointsUsed())
+ {
+ // compare with the maximum for bezier curved polygons
+ bNeedToSplit = nPointCount > ((MAX_POLYGON_POINT_COUNT_METAFILE / 3L) - 1);
+ }
+ else
+ {
+ // compare with the maximum for simple point polygons
+ bNeedToSplit = nPointCount > (MAX_POLYGON_POINT_COUNT_METAFILE - 1);
+ }
+
+ if (bNeedToSplit)
+ {
+ // need to split the partial polygon
+ const basegfx::B2DRange aRange(aCandidate.getB2DRange());
+ const basegfx::B2DPoint aCenter(aRange.getCenter());
+
+ if (aRange.getWidth() > aRange.getHeight())
+ {
+ // clip in left and right
+ const basegfx::B2DPolyPolygon aLeft(basegfx::utils::clipPolygonOnParallelAxis(
+ aCandidate, false, true, aCenter.getX(), false));
+ const basegfx::B2DPolyPolygon aRight(basegfx::utils::clipPolygonOnParallelAxis(
+ aCandidate, false, false, aCenter.getX(), false));
+
+ aSplitted.append(aLeft);
+ aSplitted.append(aRight);
+ }
+ else
+ {
+ // clip in top and bottom
+ const basegfx::B2DPolyPolygon aTop(basegfx::utils::clipPolygonOnParallelAxis(
+ aCandidate, true, true, aCenter.getY(), false));
+ const basegfx::B2DPolyPolygon aBottom(basegfx::utils::clipPolygonOnParallelAxis(
+ aCandidate, true, false, aCenter.getY(), false));
+
+ aSplitted.append(aTop);
+ aSplitted.append(aBottom);
+ }
+ }
+ else
+ {
+ aSplitted.append(aCandidate);
+ }
+ }
+
+ if (aSplitted.count() != nPolyCount)
+ {
+ rPolyPolygon = aSplitted;
+ }
+}
+
+/** Filter input polypolygon for effectively empty sub-fills
+
+ Needed to fix fdo#37559
+
+ @param rPoly
+ tools::PolyPolygon to filter
+
+ @return converted tools PolyPolygon, w/o one-point fills
+ */
+tools::PolyPolygon getFillPolyPolygon(const ::basegfx::B2DPolyPolygon& rPoly)
+{
+ // filter input rPoly
+ basegfx::B2DPolyPolygon aPoly;
+ sal_uInt32 nCount(rPoly.count());
+ for (sal_uInt32 i = 0; i < nCount; ++i)
+ {
+ const basegfx::B2DPolygon& aCandidate(rPoly.getB2DPolygon(i));
+ if (!aCandidate.isClosed() || aCandidate.count() > 1)
+ aPoly.append(aCandidate);
+ }
+ return tools::PolyPolygon(aPoly);
+}
+
+} // end of anonymous namespace
+
+namespace drawinglayer::processor2d
+{
+tools::Rectangle
+VclMetafileProcessor2D::impDumpToMetaFile(const primitive2d::Primitive2DContainer& rContent,
+ GDIMetaFile& o_rContentMetafile)
+{
+ // Prepare VDev, MetaFile and connections
+ OutputDevice* pLastOutputDevice = mpOutputDevice;
+ GDIMetaFile* pLastMetafile = mpMetaFile;
+ basegfx::B2DRange aPrimitiveRange(rContent.getB2DRange(getViewInformation2D()));
+
+ // transform primitive range with current transformation (e.g shadow offset)
+ aPrimitiveRange.transform(maCurrentTransformation);
+
+ const tools::Rectangle aPrimitiveRectangle(
+ basegfx::fround(aPrimitiveRange.getMinX()), basegfx::fround(aPrimitiveRange.getMinY()),
+ basegfx::fround(aPrimitiveRange.getMaxX()), basegfx::fround(aPrimitiveRange.getMaxY()));
+ ScopedVclPtrInstance<VirtualDevice> aContentVDev;
+ MapMode aNewMapMode(pLastOutputDevice->GetMapMode());
+
+ mpOutputDevice = aContentVDev.get();
+ mpMetaFile = &o_rContentMetafile;
+ aContentVDev->EnableOutput(false);
+ aContentVDev->SetMapMode(pLastOutputDevice->GetMapMode());
+ o_rContentMetafile.Record(aContentVDev.get());
+ aContentVDev->SetLineColor(pLastOutputDevice->GetLineColor());
+ aContentVDev->SetFillColor(pLastOutputDevice->GetFillColor());
+ aContentVDev->SetFont(pLastOutputDevice->GetFont());
+ aContentVDev->SetDrawMode(pLastOutputDevice->GetDrawMode());
+ aContentVDev->SetSettings(pLastOutputDevice->GetSettings());
+ aContentVDev->SetRefPoint(pLastOutputDevice->GetRefPoint());
+
+ // dump to MetaFile
+ process(rContent);
+
+ // cleanups
+ o_rContentMetafile.Stop();
+ o_rContentMetafile.WindStart();
+ aNewMapMode.SetOrigin(aPrimitiveRectangle.TopLeft());
+ o_rContentMetafile.SetPrefMapMode(aNewMapMode);
+ o_rContentMetafile.SetPrefSize(aPrimitiveRectangle.GetSize());
+ mpOutputDevice = pLastOutputDevice;
+ mpMetaFile = pLastMetafile;
+
+ return aPrimitiveRectangle;
+}
+
+void VclMetafileProcessor2D::impConvertFillGradientAttributeToVCLGradient(
+ Gradient& o_rVCLGradient, const attribute::FillGradientAttribute& rFiGrAtt,
+ bool bIsTransparenceGradient) const
+{
+ if (bIsTransparenceGradient)
+ {
+ // it's about transparence channel intensities (black/white), do not use color modifier
+ o_rVCLGradient.SetStartColor(Color(rFiGrAtt.getStartColor()));
+ o_rVCLGradient.SetEndColor(Color(rFiGrAtt.getEndColor()));
+ }
+ else
+ {
+ // use color modifier to influence start/end color of gradient
+ o_rVCLGradient.SetStartColor(
+ Color(maBColorModifierStack.getModifiedColor(rFiGrAtt.getStartColor())));
+ o_rVCLGradient.SetEndColor(
+ Color(maBColorModifierStack.getModifiedColor(rFiGrAtt.getEndColor())));
+ }
+
+ o_rVCLGradient.SetAngle(
+ Degree10(static_cast<sal_uInt32>(basegfx::rad2deg<10>(rFiGrAtt.getAngle()))));
+ o_rVCLGradient.SetBorder(static_cast<sal_uInt16>(rFiGrAtt.getBorder() * 100.0));
+ o_rVCLGradient.SetOfsX(static_cast<sal_uInt16>(rFiGrAtt.getOffsetX() * 100.0));
+ o_rVCLGradient.SetOfsY(static_cast<sal_uInt16>(rFiGrAtt.getOffsetY() * 100.0));
+ o_rVCLGradient.SetSteps(rFiGrAtt.getSteps());
+
+ // defaults for intensity; those were computed into the start/end colors already
+ o_rVCLGradient.SetStartIntensity(100);
+ o_rVCLGradient.SetEndIntensity(100);
+
+ switch (rFiGrAtt.getStyle())
+ {
+ default: // attribute::GradientStyle::Linear :
+ {
+ o_rVCLGradient.SetStyle(GradientStyle::Linear);
+ break;
+ }
+ case attribute::GradientStyle::Axial:
+ {
+ o_rVCLGradient.SetStyle(GradientStyle::Axial);
+ break;
+ }
+ case attribute::GradientStyle::Radial:
+ {
+ o_rVCLGradient.SetStyle(GradientStyle::Radial);
+ break;
+ }
+ case attribute::GradientStyle::Elliptical:
+ {
+ o_rVCLGradient.SetStyle(GradientStyle::Elliptical);
+ break;
+ }
+ case attribute::GradientStyle::Square:
+ {
+ o_rVCLGradient.SetStyle(GradientStyle::Square);
+ break;
+ }
+ case attribute::GradientStyle::Rect:
+ {
+ o_rVCLGradient.SetStyle(GradientStyle::Rect);
+ break;
+ }
+ }
+}
+
+void VclMetafileProcessor2D::impStartSvtGraphicFill(SvtGraphicFill const* pSvtGraphicFill)
+{
+ if (pSvtGraphicFill && !mnSvtGraphicFillCount)
+ {
+ SvMemoryStream aMemStm;
+
+ WriteSvtGraphicFill(aMemStm, *pSvtGraphicFill);
+ mpMetaFile->AddAction(new MetaCommentAction(
+ "XPATHFILL_SEQ_BEGIN", 0, static_cast<const sal_uInt8*>(aMemStm.GetData()),
+ aMemStm.TellEnd()));
+ mnSvtGraphicFillCount++;
+ }
+}
+
+void VclMetafileProcessor2D::impEndSvtGraphicFill(SvtGraphicFill const* pSvtGraphicFill)
+{
+ if (pSvtGraphicFill && mnSvtGraphicFillCount)
+ {
+ mnSvtGraphicFillCount--;
+ mpMetaFile->AddAction(new MetaCommentAction("XPATHFILL_SEQ_END"));
+ }
+}
+
+double VclMetafileProcessor2D::getTransformedLineWidth(double fWidth) const
+{
+ // #i113922# the LineWidth is duplicated in the MetaPolylineAction,
+ // and also inside the SvtGraphicStroke and needs transforming into
+ // the same space as its coordinates here cf. fdo#61789
+ // This is a partial fix. When an object transformation is used which
+ // e.g. contains a scaleX != scaleY, an unproportional scaling will happen.
+ const basegfx::B2DVector aDiscreteUnit(maCurrentTransformation
+ * basegfx::B2DVector(fWidth, 0.0));
+
+ return aDiscreteUnit.getLength();
+}
+
+std::unique_ptr<SvtGraphicStroke> VclMetafileProcessor2D::impTryToCreateSvtGraphicStroke(
+ const basegfx::B2DPolygon& rB2DPolygon, const basegfx::BColor* pColor,
+ const attribute::LineAttribute* pLineAttribute,
+ const attribute::StrokeAttribute* pStrokeAttribute,
+ const attribute::LineStartEndAttribute* pStart, const attribute::LineStartEndAttribute* pEnd)
+{
+ std::unique_ptr<SvtGraphicStroke> pRetval;
+
+ if (rB2DPolygon.count() && !mnSvtGraphicStrokeCount)
+ {
+ basegfx::B2DPolygon aLocalPolygon(rB2DPolygon);
+ basegfx::BColor aStrokeColor;
+ basegfx::B2DPolyPolygon aStartArrow;
+ basegfx::B2DPolyPolygon aEndArrow;
+
+ if (pColor)
+ {
+ aStrokeColor = *pColor;
+ }
+ else if (pLineAttribute)
+ {
+ aStrokeColor = maBColorModifierStack.getModifiedColor(pLineAttribute->getColor());
+ }
+
+ // It IS needed to record the stroke color at all in the metafile,
+ // SvtGraphicStroke has NO entry for stroke color(!)
+ mpOutputDevice->SetLineColor(Color(aStrokeColor));
+
+ if (!aLocalPolygon.isClosed())
+ {
+ double fPolyLength(0.0);
+ double fStart(0.0);
+ double fEnd(0.0);
+
+ if (pStart && pStart->isActive())
+ {
+ fPolyLength = basegfx::utils::getLength(aLocalPolygon);
+
+ aStartArrow = basegfx::utils::createAreaGeometryForLineStartEnd(
+ aLocalPolygon, pStart->getB2DPolyPolygon(), true, pStart->getWidth(),
+ fPolyLength, pStart->isCentered() ? 0.5 : 0.0, &fStart);
+ }
+
+ if (pEnd && pEnd->isActive())
+ {
+ if (basegfx::fTools::equalZero(fPolyLength))
+ {
+ fPolyLength = basegfx::utils::getLength(aLocalPolygon);
+ }
+
+ aEndArrow = basegfx::utils::createAreaGeometryForLineStartEnd(
+ aLocalPolygon, pEnd->getB2DPolyPolygon(), false, pEnd->getWidth(), fPolyLength,
+ pEnd->isCentered() ? 0.5 : 0.0, &fEnd);
+ }
+
+ if (0.0 != fStart || 0.0 != fEnd)
+ {
+ // build new poly, consume something from old poly
+ aLocalPolygon = basegfx::utils::getSnippetAbsolute(aLocalPolygon, fStart,
+ fPolyLength - fEnd, fPolyLength);
+ }
+ }
+
+ SvtGraphicStroke::JoinType eJoin(SvtGraphicStroke::joinNone);
+ SvtGraphicStroke::CapType eCap(SvtGraphicStroke::capButt);
+ double fLineWidth(0.0);
+ double fMiterLength(0.0);
+ SvtGraphicStroke::DashArray aDashArray;
+
+ if (pLineAttribute)
+ {
+ fLineWidth = fMiterLength = getTransformedLineWidth(pLineAttribute->getWidth());
+
+ // get Join
+ switch (pLineAttribute->getLineJoin())
+ {
+ case basegfx::B2DLineJoin::NONE:
+ {
+ eJoin = SvtGraphicStroke::joinNone;
+ break;
+ }
+ case basegfx::B2DLineJoin::Bevel:
+ {
+ eJoin = SvtGraphicStroke::joinBevel;
+ break;
+ }
+ case basegfx::B2DLineJoin::Miter:
+ {
+ eJoin = SvtGraphicStroke::joinMiter;
+ // ATM 15 degrees is assumed
+ // TODO wait for P1383R0 and C++20's std::numbers::pi
+ fMiterLength /= std::sin(M_PI / 12);
+ break;
+ }
+ case basegfx::B2DLineJoin::Round:
+ {
+ eJoin = SvtGraphicStroke::joinRound;
+ break;
+ }
+ }
+
+ // get stroke
+ switch (pLineAttribute->getLineCap())
+ {
+ default: /* css::drawing::LineCap_BUTT */
+ {
+ eCap = SvtGraphicStroke::capButt;
+ break;
+ }
+ case css::drawing::LineCap_ROUND:
+ {
+ eCap = SvtGraphicStroke::capRound;
+ break;
+ }
+ case css::drawing::LineCap_SQUARE:
+ {
+ eCap = SvtGraphicStroke::capSquare;
+ break;
+ }
+ }
+ }
+
+ if (pStrokeAttribute)
+ {
+ // copy dash array
+ aDashArray = pStrokeAttribute->getDotDashArray();
+ }
+
+ // #i101734# apply current object transformation to created geometry.
+ // This is a partial fix. When an object transformation is used which
+ // e.g. contains a scaleX != scaleY, an unproportional scaling would
+ // have to be applied to the evtl. existing fat line. The current
+ // concept of PDF export and SvtGraphicStroke usage does simply not
+ // allow handling such definitions. The only clean way would be to
+ // add the transformation to SvtGraphicStroke and to handle it there
+ aLocalPolygon.transform(maCurrentTransformation);
+ aStartArrow.transform(maCurrentTransformation);
+ aEndArrow.transform(maCurrentTransformation);
+
+ pRetval.reset(
+ new SvtGraphicStroke(tools::Polygon(aLocalPolygon), tools::PolyPolygon(aStartArrow),
+ tools::PolyPolygon(aEndArrow), mfCurrentUnifiedTransparence,
+ fLineWidth, eCap, eJoin, fMiterLength, std::move(aDashArray)));
+ }
+
+ return pRetval;
+}
+
+void VclMetafileProcessor2D::impStartSvtGraphicStroke(SvtGraphicStroke const* pSvtGraphicStroke)
+{
+ if (pSvtGraphicStroke && !mnSvtGraphicStrokeCount)
+ {
+ SvMemoryStream aMemStm;
+
+ WriteSvtGraphicStroke(aMemStm, *pSvtGraphicStroke);
+ mpMetaFile->AddAction(new MetaCommentAction(
+ "XPATHSTROKE_SEQ_BEGIN", 0, static_cast<const sal_uInt8*>(aMemStm.GetData()),
+ aMemStm.TellEnd()));
+ mnSvtGraphicStrokeCount++;
+ }
+}
+
+void VclMetafileProcessor2D::impEndSvtGraphicStroke(SvtGraphicStroke const* pSvtGraphicStroke)
+{
+ if (pSvtGraphicStroke && mnSvtGraphicStrokeCount)
+ {
+ mnSvtGraphicStrokeCount--;
+ mpMetaFile->AddAction(new MetaCommentAction("XPATHSTROKE_SEQ_END"));
+ }
+}
+
+void VclMetafileProcessor2D::popStructureElement(vcl::PDFWriter::StructElement eElem)
+{
+ if (!maListElements.empty() && maListElements.top() == eElem)
+ {
+ maListElements.pop();
+ mpPDFExtOutDevData->EndStructureElement();
+ }
+}
+
+void VclMetafileProcessor2D::popListItem()
+{
+ popStructureElement(vcl::PDFWriter::LIBody);
+ popStructureElement(vcl::PDFWriter::ListItem);
+}
+
+void VclMetafileProcessor2D::popList()
+{
+ popListItem();
+ popStructureElement(vcl::PDFWriter::List);
+}
+
+// init static break iterator
+uno::Reference<css::i18n::XBreakIterator> VclMetafileProcessor2D::mxBreakIterator;
+
+VclMetafileProcessor2D::VclMetafileProcessor2D(const geometry::ViewInformation2D& rViewInformation,
+ OutputDevice& rOutDev)
+ : VclProcessor2D(rViewInformation, rOutDev)
+ , mpMetaFile(rOutDev.GetConnectMetaFile())
+ , mnSvtGraphicFillCount(0)
+ , mnSvtGraphicStrokeCount(0)
+ , mfCurrentUnifiedTransparence(0.0)
+ , mpPDFExtOutDevData(dynamic_cast<vcl::PDFExtOutDevData*>(rOutDev.GetExtOutDevData()))
+ , mnCurrentOutlineLevel(-1)
+ , mbInListItem(false)
+ , mbBulletPresent(false)
+{
+ OSL_ENSURE(rOutDev.GetConnectMetaFile(),
+ "VclMetafileProcessor2D: Used on OutDev which has no MetaFile Target (!)");
+ // draw to logic coordinates, do not initialize maCurrentTransformation to viewTransformation
+ // but only to ObjectTransformation. Do not change MapMode of destination.
+ maCurrentTransformation = rViewInformation.getObjectTransformation();
+}
+
+VclMetafileProcessor2D::~VclMetafileProcessor2D()
+{
+ // MapMode was not changed, no restore necessary
+}
+
+/***********************************************************************************************
+
+ Support of MetaCommentActions in the VclMetafileProcessor2D
+ Found MetaCommentActions and how they are supported:
+
+ XGRAD_SEQ_BEGIN, XGRAD_SEQ_END:
+
+ Used inside OutputDevice::DrawGradient to mark the start and end of a MetaGradientEx action.
+ It is used in various exporters/importers to have direct access to the gradient before it
+ is rendered by VCL (and thus fragmented to polygon color actions and others). On that base, e.g.
+ the Metafile to SdrObject import creates its gradient objects.
+ Best (and safest) way to support it here is to use PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D,
+ map it back to the corresponding tools tools::PolyPolygon and the Gradient and just call
+ OutputDevice::DrawGradient which creates the necessary compatible actions.
+
+ XPATHFILL_SEQ_BEGIN, XPATHFILL_SEQ_END:
+
+ Two producers, one is vcl/source/gdi/gdimtf.cxx, line 1273. There, it is transformed
+ inside GDIMetaFile::Rotate, nothing to take care of here.
+ The second producer is in graphics/svx/source/svdraw/impgrfll.cxx, line 374. This is used
+ with each incarnation of Imp_GraphicFill when a metafile is recorded, fillstyle is not
+ XFILL_NONE and not completely transparent. It creates a SvtGraphicFill and streams it
+ to the comment action. A closing end token is created in the destructor.
+ Usages of Imp_GraphicFill are in Do_Paint_Object-methods of SdrCircObj, SdrPathObj and
+ SdrRectObj.
+ The token users pick various actions from SvtGraphicFill, so it may need to be added for all kind
+ of filled objects, even simple colored polygons. It is added as extra information; the
+ Metafile actions between the two tokens are interpreted as output generated from those
+ fills. Thus, users have the choice to use the SvtGraphicFill info or the created output
+ actions.
+ Even for XFillTransparenceItem it is used, thus it may need to be supported in
+ UnifiedTransparencePrimitive2D, too, when interpreted as normally filled PolyPolygon.
+ Implemented for:
+ PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D,
+ PRIMITIVE2D_ID_POLYPOLYGONHATCHPRIMITIVE2D,
+ PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D,
+ PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D,
+ and for PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D when detected unified transparence
+
+ XPATHSTROKE_SEQ_BEGIN, XPATHSTROKE_SEQ_END:
+
+ Similar to pathfill, but using SvtGraphicStroke instead. It also has two producers where one
+ is also the GDIMetaFile::Rotate. Another user is MetaCommentAction::Move which modifies the
+ contained path accordingly.
+ The other one is SdrObject::Imp_DrawLineGeometry. It's done when MetaFile is set at OutDev and
+ only when geometry is a single polygon (!). I see no reason for that; in the PS exporter this
+ would hinder to make use of tools::PolyPolygon strokes. I will need to add support at:
+ PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D
+ PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D
+ PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D
+ This can be done hierarchical, too.
+ Okay, base implementation done based on those three primitives.
+
+ FIELD_SEQ_BEGIN, FIELD_SEQ_END
+
+ Used from slideshow for URLs, created from diverse SvxField implementations inside
+ createBeginComment()/createEndComment(). createBeginComment() is used from editeng\impedit3.cxx
+ inside ImpEditEngine::Paint.
+ Created TextHierarchyFieldPrimitive2D and added needed infos there; it is a group primitive and wraps
+ text primitives (but is not limited to that). It contains the field type if special actions for the
+ support of FIELD_SEQ_BEGIN/END are needed; this is the case for Page and URL fields. If more is
+ needed, it may be supported there.
+ FIELD_SEQ_BEGIN;PageField
+ FIELD_SEQ_END
+ Okay, these are now completely supported by TextHierarchyFieldPrimitive2D. URL works, too.
+
+ XTEXT
+
+ XTEXT_EOC(i) end of character
+ XTEXT_EOW(i) end of word
+ XTEXT_EOS(i) end of sentence
+
+ this three are with index and are created with the help of an i18n::XBreakIterator in
+ ImplDrawWithComments. Simplifying, moving out text painting, reworking to create some
+ data structure for holding those TEXT infos.
+ Supported directly by TextSimplePortionPrimitive2D with adding a Locale to the basic text
+ primitive. In the MetaFileRenderer, the creation is now done (see below). This has the advantage
+ that this creations do not need to be done for all paints all the time. This would be
+ expensive since the BreakIterator and it's usage is expensive and for each paint also the
+ whole character stops would need to be created.
+ Created only for TextDecoratedPortionPrimitive2D due to XTEXT_EOL and XTEXT_EOP (see below)
+
+ XTEXT_EOL() end of line
+ XTEXT_EOP() end of paragraph
+
+ First try with boolean marks at TextDecoratedPortionPrimitive2D did not work too well,
+ i decided to solve it with structure. I added the TextHierarchyPrimitives for this,
+ namely:
+ - TextHierarchyLinePrimitive2D: Encapsulates single line
+ - TextHierarchyParagraphPrimitive2D: Encapsulates single paragraph
+ - TextHierarchyBlockPrimitive2D: encapsulates object texts (only one ATM)
+ Those are now supported in hierarchy. This means the MetaFile renderer will support them
+ by using them, recursively using their content and adding MetaFile comments as needed.
+ This also means that when another text layouter will be used it will be necessary to
+ create/support the same HierarchyPrimitives to support users.
+ To transport the information using this hierarchy is best suited to all future needs;
+ the slideshow will be able to profit from it directly when using primitives; all other
+ renderers not interested in the text structure will just ignore the encapsulations.
+
+ XTEXT_PAINTSHAPE_BEGIN, XTEXT_PAINTSHAPE_END
+ Supported now by the TextHierarchyBlockPrimitive2D.
+
+ EPSReplacementGraphic:
+ Only used in goodies\source\filter.vcl\ieps\ieps.cxx and svx\source\xml\xmlgrhlp.cxx to
+ hold the original EPS which was imported in the same MetaFile as first 2 entries. Only
+ used to export the original again (if exists).
+ Not necessary to support with MetaFileRenderer.
+
+ XTEXT_SCROLLRECT, XTEXT_PAINTRECT
+ Currently used to get extra MetaFile infos using GraphicExporter which again uses
+ SdrTextObj::GetTextScrollMetaFileAndRectangle(). ATM works with primitives since
+ the rectangle data is added directly by the GraphicsExporter as comment. Does not need
+ to be adapted at once.
+ When adapting later, the only user - the diashow - should directly use the provided
+ Animation infos in the appropriate primitives (e.g. AnimatedSwitchPrimitive2D)
+
+ PRNSPOOL_TRANSPARENTBITMAP_BEGIN, PRNSPOOL_TRANSPARENTBITMAP_END
+ VCL usage when printing PL -> THB. Okay, THB confirms that it is only used as
+ a fix (hack) while VCL printing. It is needed to not downscale a bitmap which
+ was explicitly created for the printer already again to some default maximum
+ bitmap sizes.
+ Nothing to do here for the primitive renderer.
+
+ Support for vcl::PDFExtOutDevData:
+ PL knows that SJ did that stuff, it's used to hold a pointer to PDFExtOutDevData at
+ the OutDev. When set, some extra data is written there. Trying simple PDF export and
+ watching if I get those infos.
+ Well, a PDF export does not use e.g. ImpEditEngine::Paint since the PdfFilter uses
+ the SdXImpressDocument::render and thus uses the VclMetafileProcessor2D. I will check
+ if I get a PDFExtOutDevData at the target output device.
+ Indeed, I get one. Checking what all may be done when that extra-device-info is there.
+
+ All in all I have to talk to SJ. I will need to emulate some of those actions, but
+ i need to discuss which ones.
+ In the future, all those infos would be taken from the primitive sequence anyways,
+ thus these extensions would potentially be temporary, too.
+ Discussed with SJ, added the necessary support and tested it. Details follow.
+
+ - In ImpEditEngine::Paint, paragraph infos and URL stuff is added.
+ Added in primitive MetaFile renderer.
+ Checking URL: Indeed, current version exports it, but it is missing in primitive
+ CWS version. Adding support.
+ Okay, URLs work. Checked, Done.
+
+ - UnoControlPDFExportContact is only created when PDFExtOutDevData is used at the
+ target and uno control data is created in UnoControlPDFExportContact::do_PaintObject.
+ This may be added in primitive MetaFile renderer.
+ Adding support...
+ OOps, the necessary helper stuff is in svx/source/form/formpdxexport.cxx in namespace
+ svxform. Have to talk to FS if this has to be like that. Especially since
+ vcl::PDFWriter::AnyWidget is filled out, which is already part of vcl.
+ Wrote an eMail to FS, he is on vacation currently. I see no reason why not to move
+ that stuff to somewhere else, maybe tools or svtools ?!? We will see...
+ Moved to toolkit, so I have to link against it. I tried VCL first, but it did
+ not work since VCLUnoHelper::CreateFont is unresolved in VCL (!). Other than the name
+ may imply, it is defined in toolkit (!). Since toolkit is linked against VCL itself,
+ the lowest movement plane is toolkit.
+ Checked form control export, it works well. Done.
+
+ - In goodies, in GraphicObject::Draw, when the used Graphic is linked, infos are
+ generated. I will need to check what happens here with primitives.
+ To support, use of GraphicPrimitive2D (PRIMITIVE2D_ID_GRAPHICPRIMITIVE2D) may be needed.
+ Added support, but feature is broken in main version, so i cannot test at all.
+ Writing a bug to CL (or SJ) and seeing what happens (#i80380#).
+ SJ took a look and we got it working. Tested VCL MetaFile Renderer based export,
+ as intended, the original file is exported. Works, Done.
+
+
+ To be done:
+
+ - Maybe there are more places to take care of for vcl::PDFExtOutDevData!
+
+
+****************************************************************************************************/
+
+void VclMetafileProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
+{
+ switch (rCandidate.getPrimitive2DID())
+ {
+ case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D:
+ {
+ // directdraw of wrong spell primitive
+ // Ignore for VclMetafileProcessor2D, this is for printing and MetaFile recording only
+ break;
+ }
+ case PRIMITIVE2D_ID_GRAPHICPRIMITIVE2D:
+ {
+ processGraphicPrimitive2D(
+ static_cast<const primitive2d::GraphicPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D:
+ {
+ processControlPrimitive2D(
+ static_cast<const primitive2d::ControlPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_TEXTHIERARCHYFIELDPRIMITIVE2D:
+ {
+ processTextHierarchyFieldPrimitive2D(
+ static_cast<const primitive2d::TextHierarchyFieldPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_TEXTHIERARCHYLINEPRIMITIVE2D:
+ {
+ processTextHierarchyLinePrimitive2D(
+ static_cast<const primitive2d::TextHierarchyLinePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_TEXTHIERARCHYBULLETPRIMITIVE2D:
+ {
+ processTextHierarchyBulletPrimitive2D(
+ static_cast<const primitive2d::TextHierarchyBulletPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_TEXTHIERARCHYPARAGRAPHPRIMITIVE2D:
+ {
+ processTextHierarchyParagraphPrimitive2D(
+ static_cast<const primitive2d::TextHierarchyParagraphPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_TEXTHIERARCHYBLOCKPRIMITIVE2D:
+ {
+ processTextHierarchyBlockPrimitive2D(
+ static_cast<const primitive2d::TextHierarchyBlockPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D:
+ case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D:
+ {
+ // for supporting TEXT_ MetaFile actions there is more to do here; get the candidate
+ processTextSimplePortionPrimitive2D(
+ static_cast<const primitive2d::TextSimplePortionPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D:
+ {
+ processPolygonHairlinePrimitive2D(
+ static_cast<const primitive2d::PolygonHairlinePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D:
+ {
+ processPolygonStrokePrimitive2D(
+ static_cast<const primitive2d::PolygonStrokePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D:
+ {
+ processPolygonStrokeArrowPrimitive2D(
+ static_cast<const primitive2d::PolygonStrokeArrowPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D:
+ {
+ // direct draw of transformed BitmapEx primitive; use default processing, but without
+ // former testing if graphic content is inside discrete local viewport; this is not
+ // setup for metafile targets (metafile renderer tries to render in logic coordinates,
+ // the mapping is kept to the OutputDevice for better Metafile recording)
+ RenderBitmapPrimitive2D(static_cast<const primitive2d::BitmapPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D:
+ {
+ processPolyPolygonGraphicPrimitive2D(
+ static_cast<const primitive2d::PolyPolygonGraphicPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYPOLYGONHATCHPRIMITIVE2D:
+ {
+ processPolyPolygonHatchPrimitive2D(
+ static_cast<const primitive2d::PolyPolygonHatchPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D:
+ {
+ processPolyPolygonGradientPrimitive2D(
+ static_cast<const primitive2d::PolyPolygonGradientPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D:
+ {
+ processPolyPolygonColorPrimitive2D(
+ static_cast<const primitive2d::PolyPolygonColorPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_MASKPRIMITIVE2D:
+ {
+ processMaskPrimitive2D(static_cast<const primitive2d::MaskPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D:
+ {
+ // modified color group. Force output to unified color. Use default processing.
+ RenderModifiedColorPrimitive2D(
+ static_cast<const primitive2d::ModifiedColorPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D:
+ {
+ processUnifiedTransparencePrimitive2D(
+ static_cast<const primitive2d::UnifiedTransparencePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D:
+ {
+ processTransparencePrimitive2D(
+ static_cast<const primitive2d::TransparencePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D:
+ {
+ // use default transform group processing
+ RenderTransformPrimitive2D(
+ static_cast<const primitive2d::TransformPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D:
+ {
+ // new XDrawPage for ViewInformation2D
+ RenderPagePreviewPrimitive2D(
+ static_cast<const primitive2d::PagePreviewPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D:
+ {
+ // use default marker array processing
+ RenderMarkerArrayPrimitive2D(
+ static_cast<const primitive2d::MarkerArrayPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D:
+ {
+ // use default point array processing
+ RenderPointArrayPrimitive2D(
+ static_cast<const primitive2d::PointArrayPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_STRUCTURETAGPRIMITIVE2D:
+ {
+ processStructureTagPrimitive2D(
+ static_cast<const primitive2d::StructureTagPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_EPSPRIMITIVE2D:
+ {
+ RenderEpsPrimitive2D(static_cast<const primitive2d::EpsPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_OBJECTINFOPRIMITIVE2D:
+ {
+ processObjectInfoPrimitive2D(
+ static_cast<const primitive2d::ObjectInfoPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D:
+ case PRIMITIVE2D_ID_GLOWPRIMITIVE2D:
+ case PRIMITIVE2D_ID_SOFTEDGEPRIMITIVE2D:
+ {
+ processPrimitive2DOnPixelProcessor(rCandidate);
+ break;
+ }
+ default:
+ {
+ // process recursively
+ process(rCandidate);
+ break;
+ }
+ }
+}
+
+void VclMetafileProcessor2D::processObjectInfoPrimitive2D(
+ primitive2d::ObjectInfoPrimitive2D const& rObjectInfoPrimitive2D)
+{
+ // currently StructureTagPrimitive2D is only used for SdrObjects - have to
+ // avoid adding Alt text if the SdrObject is not actually tagged, as it
+ // would then end up on an unrelated structure element.
+ if (mpCurrentStructureTag && mpCurrentStructureTag->isTaggedSdrObject())
+ {
+ // Create image alternative description from ObjectInfoPrimitive2D info
+ // for PDF export, for the currently active SdrObject's structure element
+ if (mpPDFExtOutDevData->GetIsExportTaggedPDF())
+ {
+ OUString aAlternateDescription;
+
+ if (!rObjectInfoPrimitive2D.getTitle().isEmpty())
+ {
+ aAlternateDescription += rObjectInfoPrimitive2D.getTitle();
+ }
+
+ if (!rObjectInfoPrimitive2D.getDesc().isEmpty())
+ {
+ if (!aAlternateDescription.isEmpty())
+ {
+ aAlternateDescription += " - ";
+ }
+
+ aAlternateDescription += rObjectInfoPrimitive2D.getDesc();
+ }
+
+ // Use SetAlternateText to set it. This will work as long as some
+ // structure is used (see PDFWriterImpl::setAlternateText and
+ // m_nCurrentStructElement - tagged PDF export works with this in
+ // Draw/Impress/Writer, but not in Calc due to too less structure...)
+ //Z maybe add structure to Calc PDF export, may need some BeginGroup/EndGroup stuff ..?
+ if (!aAlternateDescription.isEmpty())
+ {
+ mpPDFExtOutDevData->SetAlternateText(aAlternateDescription);
+ }
+ }
+ }
+
+ // process content
+ process(rObjectInfoPrimitive2D.getChildren());
+}
+
+void VclMetafileProcessor2D::processGraphicPrimitive2D(
+ const primitive2d::GraphicPrimitive2D& rGraphicPrimitive)
+{
+ bool bUsingPDFExtOutDevData(false);
+ basegfx::B2DVector aTranslate, aScale;
+ static bool bSuppressPDFExtOutDevDataSupport(false); // loplugin:constvars:ignore
+
+ if (mpPDFExtOutDevData && !bSuppressPDFExtOutDevDataSupport)
+ {
+ // emulate data handling from UnoControlPDFExportContact, original see
+ // svtools/source/graphic/grfmgr.cxx
+ const Graphic& rGraphic = rGraphicPrimitive.getGraphicObject().GetGraphic();
+
+ if (rGraphic.IsGfxLink())
+ {
+ const GraphicAttr& rAttr = rGraphicPrimitive.getGraphicAttr();
+
+ if (!rAttr.IsSpecialDrawMode() && !rAttr.IsAdjusted())
+ {
+ const basegfx::B2DHomMatrix& rTransform = rGraphicPrimitive.getTransform();
+ double fRotate, fShearX;
+ rTransform.decompose(aScale, aTranslate, fRotate, fShearX);
+
+ if (basegfx::fTools::equalZero(fRotate) && (aScale.getX() > 0.0)
+ && (aScale.getY() > 0.0))
+ {
+ bUsingPDFExtOutDevData = true;
+ mpPDFExtOutDevData->BeginGroup();
+ }
+ }
+ }
+ }
+
+ // process recursively and add MetaFile comment
+ process(rGraphicPrimitive);
+
+ if (!bUsingPDFExtOutDevData)
+ return;
+
+ // emulate data handling from UnoControlPDFExportContact, original see
+ // svtools/source/graphic/grfmgr.cxx
+ const basegfx::B2DRange aCurrentRange(aTranslate.getX(), aTranslate.getY(),
+ aTranslate.getX() + aScale.getX(),
+ aTranslate.getY() + aScale.getY());
+ const tools::Rectangle aCurrentRect(
+ sal_Int32(floor(aCurrentRange.getMinX())), sal_Int32(floor(aCurrentRange.getMinY())),
+ sal_Int32(ceil(aCurrentRange.getMaxX())), sal_Int32(ceil(aCurrentRange.getMaxY())));
+ const GraphicAttr& rAttr = rGraphicPrimitive.getGraphicAttr();
+ // fdo#72530 don't pass empty Rectangle to EndGroup
+ tools::Rectangle aCropRect(aCurrentRect);
+
+ if (rAttr.IsCropped())
+ {
+ // calculate scalings between real image size and logic object size. This
+ // is necessary since the crop values are relative to original bitmap size
+ double fFactorX(1.0);
+ double fFactorY(1.0);
+
+ {
+ const MapMode aMapMode100thmm(MapUnit::Map100thMM);
+ const Size aBitmapSize(OutputDevice::LogicToLogic(
+ rGraphicPrimitive.getGraphicObject().GetPrefSize(),
+ rGraphicPrimitive.getGraphicObject().GetPrefMapMode(), aMapMode100thmm));
+ const double fDivX(aBitmapSize.Width() - rAttr.GetLeftCrop() - rAttr.GetRightCrop());
+ const double fDivY(aBitmapSize.Height() - rAttr.GetTopCrop() - rAttr.GetBottomCrop());
+
+ if (!basegfx::fTools::equalZero(fDivX))
+ {
+ fFactorX = aScale.getX() / fDivX;
+ }
+
+ if (!basegfx::fTools::equalZero(fDivY))
+ {
+ fFactorY = aScale.getY() / fDivY;
+ }
+ }
+
+ // calculate crop range and rect
+ basegfx::B2DRange aCropRange;
+ aCropRange.expand(
+ aCurrentRange.getMinimum()
+ - basegfx::B2DPoint(rAttr.GetLeftCrop() * fFactorX, rAttr.GetTopCrop() * fFactorY));
+ aCropRange.expand(
+ aCurrentRange.getMaximum()
+ + basegfx::B2DPoint(rAttr.GetRightCrop() * fFactorX, rAttr.GetBottomCrop() * fFactorY));
+
+ aCropRect = tools::Rectangle(
+ sal_Int32(floor(aCropRange.getMinX())), sal_Int32(floor(aCropRange.getMinY())),
+ sal_Int32(ceil(aCropRange.getMaxX())), sal_Int32(ceil(aCropRange.getMaxY())));
+ }
+
+ // #i123295# 3rd param is uncropped rect, 4th is cropped. The primitive has the cropped
+ // object transformation, thus aCurrentRect *is* the clip region while aCropRect is the expanded,
+ // uncropped region. Thus, correct order is aCropRect, aCurrentRect
+ mpPDFExtOutDevData->EndGroup(rGraphicPrimitive.getGraphicObject().GetGraphic(),
+ 255 - rAttr.GetAlpha(), aCropRect, aCurrentRect);
+}
+
+void VclMetafileProcessor2D::processControlPrimitive2D(
+ const primitive2d::ControlPrimitive2D& rControlPrimitive)
+{
+ const uno::Reference<awt::XControl>& rXControl(rControlPrimitive.getXControl());
+ bool bIsPrintableControl(false);
+
+ // find out if control is printable
+ if (rXControl.is())
+ {
+ try
+ {
+ uno::Reference<beans::XPropertySet> xModelProperties(rXControl->getModel(),
+ uno::UNO_QUERY);
+ uno::Reference<beans::XPropertySetInfo> xPropertyInfo(
+ xModelProperties.is() ? xModelProperties->getPropertySetInfo()
+ : uno::Reference<beans::XPropertySetInfo>());
+ static const OUStringLiteral sPrintablePropertyName(u"Printable");
+
+ if (xPropertyInfo.is() && xPropertyInfo->hasPropertyByName(sPrintablePropertyName))
+ {
+ OSL_VERIFY(xModelProperties->getPropertyValue(sPrintablePropertyName)
+ >>= bIsPrintableControl);
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("drawinglayer",
+ "VclMetafileProcessor2D: No access to printable flag of Control");
+ }
+ }
+
+ // PDF export and printing only for printable controls
+ if (!bIsPrintableControl)
+ return;
+
+ const bool bPDFExport(mpPDFExtOutDevData && mpPDFExtOutDevData->GetIsExportFormFields());
+ bool bDoProcessRecursively(true);
+
+ if (bPDFExport)
+ {
+ // PDF export. Emulate data handling from UnoControlPDFExportContact
+ // I have now moved describePDFControl to toolkit, thus i can implement the PDF
+ // form control support now as follows
+ std::unique_ptr<vcl::PDFWriter::AnyWidget> pPDFControl(
+ ::toolkitform::describePDFControl(rXControl, *mpPDFExtOutDevData));
+
+ if (pPDFControl)
+ {
+ // still need to fill in the location (is a class Rectangle)
+ const basegfx::B2DRange aRangeLogic(
+ rControlPrimitive.getB2DRange(getViewInformation2D()));
+ const tools::Rectangle aRectLogic(static_cast<sal_Int32>(floor(aRangeLogic.getMinX())),
+ static_cast<sal_Int32>(floor(aRangeLogic.getMinY())),
+ static_cast<sal_Int32>(ceil(aRangeLogic.getMaxX())),
+ static_cast<sal_Int32>(ceil(aRangeLogic.getMaxY())));
+ pPDFControl->Location = aRectLogic;
+
+ Size aFontSize(pPDFControl->TextFont.GetFontSize());
+ aFontSize = OutputDevice::LogicToLogic(aFontSize, MapMode(MapUnit::MapPoint),
+ mpOutputDevice->GetMapMode());
+ pPDFControl->TextFont.SetFontSize(aFontSize);
+
+ mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::Form);
+ mpPDFExtOutDevData->CreateControl(*pPDFControl);
+ mpPDFExtOutDevData->EndStructureElement();
+
+ // no normal paint needed (see original UnoControlPDFExportContact::do_PaintObject);
+ // do not process recursively
+ bDoProcessRecursively = false;
+ }
+ else
+ {
+ // PDF export did not work, try simple output.
+ // Fallback to printer output by not setting bDoProcessRecursively
+ // to false.
+ }
+ }
+
+ // #i93169# used flag the wrong way; true means that nothing was done yet
+ if (bDoProcessRecursively)
+ {
+ // printer output
+ try
+ {
+ // remember old graphics and create new
+ uno::Reference<awt::XView> xControlView(rXControl, uno::UNO_QUERY_THROW);
+ const uno::Reference<awt::XGraphics> xOriginalGraphics(xControlView->getGraphics());
+ const uno::Reference<awt::XGraphics> xNewGraphics(mpOutputDevice->CreateUnoGraphics());
+
+ if (xNewGraphics.is())
+ {
+ // link graphics and view
+ xControlView->setGraphics(xNewGraphics);
+
+ // get position
+ const basegfx::B2DHomMatrix aObjectToDiscrete(
+ getViewInformation2D().getObjectToViewTransformation()
+ * rControlPrimitive.getTransform());
+ const basegfx::B2DPoint aTopLeftDiscrete(aObjectToDiscrete
+ * basegfx::B2DPoint(0.0, 0.0));
+
+ // draw it
+ xControlView->draw(basegfx::fround(aTopLeftDiscrete.getX()),
+ basegfx::fround(aTopLeftDiscrete.getY()));
+ bDoProcessRecursively = false;
+
+ // restore original graphics
+ xControlView->setGraphics(xOriginalGraphics);
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("drawinglayer",
+ "VclMetafileProcessor2D: Printing of Control failed");
+ }
+ }
+
+ // process recursively if not done yet to export as decomposition (bitmap)
+ if (bDoProcessRecursively)
+ {
+ process(rControlPrimitive);
+ }
+}
+
+void VclMetafileProcessor2D::processTextHierarchyFieldPrimitive2D(
+ const primitive2d::TextHierarchyFieldPrimitive2D& rFieldPrimitive)
+{
+ // support for FIELD_SEQ_BEGIN, FIELD_SEQ_END and URL. It wraps text primitives (but is not limited to)
+ // thus do the MetafileAction embedding stuff but just handle recursively.
+ const OString aCommentStringCommon("FIELD_SEQ_BEGIN");
+ OUString aURL;
+
+ switch (rFieldPrimitive.getType())
+ {
+ default: // case drawinglayer::primitive2d::FIELD_TYPE_COMMON :
+ {
+ mpMetaFile->AddAction(new MetaCommentAction(aCommentStringCommon));
+ break;
+ }
+ case drawinglayer::primitive2d::FIELD_TYPE_PAGE:
+ {
+ mpMetaFile->AddAction(new MetaCommentAction("FIELD_SEQ_BEGIN;PageField"));
+ break;
+ }
+ case drawinglayer::primitive2d::FIELD_TYPE_URL:
+ {
+ aURL = rFieldPrimitive.getValue("URL");
+
+ if (!aURL.isEmpty())
+ {
+ mpMetaFile->AddAction(new MetaCommentAction(
+ aCommentStringCommon, 0, reinterpret_cast<const sal_uInt8*>(aURL.getStr()),
+ 2 * aURL.getLength()));
+ }
+
+ break;
+ }
+ }
+
+ // process recursively
+ primitive2d::Primitive2DContainer rContent;
+ rFieldPrimitive.get2DDecomposition(rContent, getViewInformation2D());
+ process(rContent);
+
+ // for the end comment the type is not relevant yet, they are all the same. Just add.
+ mpMetaFile->AddAction(new MetaCommentAction("FIELD_SEQ_END"));
+
+ if (!(mpPDFExtOutDevData
+ && drawinglayer::primitive2d::FIELD_TYPE_URL == rFieldPrimitive.getType()))
+ return;
+
+ // emulate data handling from ImpEditEngine::Paint
+ const basegfx::B2DRange aViewRange(rContent.getB2DRange(getViewInformation2D()));
+ const tools::Rectangle aRectLogic(static_cast<sal_Int32>(floor(aViewRange.getMinX())),
+ static_cast<sal_Int32>(floor(aViewRange.getMinY())),
+ static_cast<sal_Int32>(ceil(aViewRange.getMaxX())),
+ static_cast<sal_Int32>(ceil(aViewRange.getMaxY())));
+ vcl::PDFExtOutDevBookmarkEntry aBookmark;
+ OUString const content(rFieldPrimitive.getValue("Representation"));
+ aBookmark.nLinkId = mpPDFExtOutDevData->CreateLink(aRectLogic, content);
+ aBookmark.aBookmark = aURL;
+ std::vector<vcl::PDFExtOutDevBookmarkEntry>& rBookmarks = mpPDFExtOutDevData->GetBookmarks();
+ rBookmarks.push_back(aBookmark);
+}
+
+void VclMetafileProcessor2D::processTextHierarchyLinePrimitive2D(
+ const primitive2d::TextHierarchyLinePrimitive2D& rLinePrimitive)
+{
+ // process recursively and add MetaFile comment
+ process(rLinePrimitive);
+ mpMetaFile->AddAction(new MetaCommentAction("XTEXT_EOL"));
+}
+
+void VclMetafileProcessor2D::processTextHierarchyBulletPrimitive2D(
+ const primitive2d::TextHierarchyBulletPrimitive2D& rBulletPrimitive)
+{
+ // this is a part of list item, start LILabel ( = bullet)
+ if (mbInListItem)
+ {
+ maListElements.push(vcl::PDFWriter::LILabel);
+ mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::LILabel);
+ }
+
+ // process recursively and add MetaFile comment
+ process(rBulletPrimitive);
+ // in Outliner::PaintBullet(), a MetafileComment for bullets is added, too. The
+ // "XTEXT_EOC" is used, use here, too.
+ mpMetaFile->AddAction(new MetaCommentAction("XTEXT_EOC"));
+
+ if (mbInListItem)
+ {
+ if (maListElements.top() == vcl::PDFWriter::LILabel)
+ {
+ maListElements.pop();
+ mpPDFExtOutDevData->EndStructureElement(); // end LILabel
+ mbBulletPresent = true;
+ }
+ }
+}
+
+void VclMetafileProcessor2D::processTextHierarchyParagraphPrimitive2D(
+ const primitive2d::TextHierarchyParagraphPrimitive2D& rParagraphPrimitive)
+{
+ const OString aCommentString("XTEXT_EOP");
+ static bool bSuppressPDFExtOutDevDataSupport(false); // loplugin:constvars:ignore
+
+ if (nullptr == mpPDFExtOutDevData || bSuppressPDFExtOutDevDataSupport)
+ {
+ // Non-PDF export behaviour (metafile only).
+ // Process recursively and add MetaFile comment.
+ process(rParagraphPrimitive);
+ mpMetaFile->AddAction(new MetaCommentAction(aCommentString));
+ return;
+ }
+
+ if (!mpPDFExtOutDevData->GetIsExportTaggedPDF())
+ {
+ // No Tagged PDF -> Dump as Paragraph
+ // Emulate data handling from old ImpEditEngine::Paint
+ mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::Paragraph);
+
+ // Process recursively and add MetaFile comment
+ process(rParagraphPrimitive);
+ mpMetaFile->AddAction(new MetaCommentAction(aCommentString));
+
+ // Emulate data handling from ImpEditEngine::Paint
+ mpPDFExtOutDevData->EndStructureElement();
+ return;
+ }
+
+ // Create Tagged PDF -> deeper tagged data using StructureElements.
+ // Use OutlineLevel from ParagraphPrimitive, ensure not below -1 what
+ // means 'not active'
+ const sal_Int16 nNewOutlineLevel(
+ std::max(static_cast<sal_Int16>(-1), rParagraphPrimitive.getOutlineLevel()));
+
+ // Do we have a change in OutlineLevel compared to the current one?
+ if (nNewOutlineLevel != mnCurrentOutlineLevel)
+ {
+ if (nNewOutlineLevel > mnCurrentOutlineLevel)
+ {
+ // increase List level
+ for (sal_Int16 a(mnCurrentOutlineLevel); a != nNewOutlineLevel; ++a)
+ {
+ maListElements.push(vcl::PDFWriter::List);
+ mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::List);
+ }
+ }
+ else // if(nNewOutlineLevel < mnCurrentOutlineLevel)
+ {
+ // close list levels below nNewOutlineLevel completely by removing
+ // list items as well as list tag itself
+ for (sal_Int16 a(nNewOutlineLevel); a < mnCurrentOutlineLevel; ++a)
+ {
+ popList(); // end LBody LI and L
+ }
+
+ // on nNewOutlineLevel close the previous list item (LBody and LI)
+ popListItem();
+ }
+
+ // Remember new current OutlineLevel
+ mnCurrentOutlineLevel = nNewOutlineLevel;
+ }
+ else // the same list level
+ {
+ // close the previous list item (LBody and LI)
+ popListItem();
+ }
+
+ const bool bDumpAsListItem(-1 != mnCurrentOutlineLevel);
+
+ if (bDumpAsListItem)
+ {
+ // Dump as ListItem
+ maListElements.push(vcl::PDFWriter::ListItem);
+ mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::ListItem);
+ mbInListItem = true;
+ }
+ else
+ {
+ // Dump as Paragraph
+ mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::Paragraph);
+ }
+
+ // Process recursively and add MetaFile comment
+ process(rParagraphPrimitive);
+ mpMetaFile->AddAction(new MetaCommentAction(aCommentString));
+
+ if (bDumpAsListItem)
+ mbInListItem = false;
+ else
+ mpPDFExtOutDevData->EndStructureElement(); // end Paragraph
+}
+
+void VclMetafileProcessor2D::processTextHierarchyBlockPrimitive2D(
+ const primitive2d::TextHierarchyBlockPrimitive2D& rBlockPrimitive)
+{
+ // add MetaFile comment, process recursively and add MetaFile comment
+ mpMetaFile->AddAction(new MetaCommentAction("XTEXT_PAINTSHAPE_BEGIN"));
+ process(rBlockPrimitive);
+
+ if (mnCurrentOutlineLevel >= 0)
+ {
+ // end any opened List structure elements (LBody, LI, L)
+ for (sal_Int16 a(0); a <= mnCurrentOutlineLevel; ++a)
+ {
+ popList();
+ }
+ }
+
+ mpMetaFile->AddAction(new MetaCommentAction("XTEXT_PAINTSHAPE_END"));
+}
+
+void VclMetafileProcessor2D::processTextSimplePortionPrimitive2D(
+ const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate)
+{
+ // Adapt evtl. used special DrawMode
+ const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode());
+ adaptTextToFillDrawMode();
+
+ // this is a 2nd portion of list item
+ // bullet has been already processed, start LIBody
+ if (mbInListItem && mbBulletPresent)
+ {
+ maListElements.push(vcl::PDFWriter::LIBody);
+ mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::LIBody);
+ }
+
+ // directdraw of text simple portion; use default processing
+ RenderTextSimpleOrDecoratedPortionPrimitive2D(rTextCandidate);
+
+ if (mbInListItem && mbBulletPresent)
+ mbBulletPresent = false;
+
+ // restore DrawMode
+ mpOutputDevice->SetDrawMode(nOriginalDrawMode);
+
+ // #i101169# if(pTextDecoratedCandidate)
+ {
+ // support for TEXT_ MetaFile actions only for decorated texts
+ if (!mxBreakIterator.is())
+ {
+ uno::Reference<uno::XComponentContext> xContext(
+ ::comphelper::getProcessComponentContext());
+ mxBreakIterator = i18n::BreakIterator::create(xContext);
+ }
+
+ const OUString& rTxt = rTextCandidate.getText();
+ const sal_Int32 nTextLength(rTextCandidate.getTextLength()); // rTxt.getLength());
+
+ if (nTextLength)
+ {
+ const css::lang::Locale& rLocale = rTextCandidate.getLocale();
+ const sal_Int32 nTextPosition(rTextCandidate.getTextPosition());
+
+ sal_Int32 nDone;
+ sal_Int32 nNextCellBreak(mxBreakIterator->nextCharacters(
+ rTxt, nTextPosition, rLocale, css::i18n::CharacterIteratorMode::SKIPCELL, 0,
+ nDone));
+ css::i18n::Boundary nNextWordBoundary(mxBreakIterator->getWordBoundary(
+ rTxt, nTextPosition, rLocale, css::i18n::WordType::ANY_WORD, true));
+ sal_Int32 nNextSentenceBreak(
+ mxBreakIterator->endOfSentence(rTxt, nTextPosition, rLocale));
+ const OString aCommentStringA("XTEXT_EOC");
+ const OString aCommentStringB("XTEXT_EOW");
+ const OString aCommentStringC("XTEXT_EOS");
+
+ for (sal_Int32 i(nTextPosition); i < nTextPosition + nTextLength; i++)
+ {
+ // create the entries for the respective break positions
+ if (i == nNextCellBreak)
+ {
+ mpMetaFile->AddAction(
+ new MetaCommentAction(aCommentStringA, i - nTextPosition));
+ nNextCellBreak = mxBreakIterator->nextCharacters(
+ rTxt, i, rLocale, css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
+ }
+ if (i == nNextWordBoundary.endPos)
+ {
+ mpMetaFile->AddAction(
+ new MetaCommentAction(aCommentStringB, i - nTextPosition));
+ nNextWordBoundary = mxBreakIterator->getWordBoundary(
+ rTxt, i + 1, rLocale, css::i18n::WordType::ANY_WORD, true);
+ }
+ if (i == nNextSentenceBreak)
+ {
+ mpMetaFile->AddAction(
+ new MetaCommentAction(aCommentStringC, i - nTextPosition));
+ nNextSentenceBreak = mxBreakIterator->endOfSentence(rTxt, i + 1, rLocale);
+ }
+ }
+ }
+ }
+}
+
+void VclMetafileProcessor2D::processPolygonHairlinePrimitive2D(
+ const primitive2d::PolygonHairlinePrimitive2D& rHairlinePrimitive)
+{
+ const basegfx::B2DPolygon& rBasePolygon = rHairlinePrimitive.getB2DPolygon();
+
+ if (rBasePolygon.count() > (MAX_POLYGON_POINT_COUNT_METAFILE - 1))
+ {
+ // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points
+ // per polygon. If there are more, split the polygon in half and call recursively
+ basegfx::B2DPolygon aLeft, aRight;
+ splitLinePolygon(rBasePolygon, aLeft, aRight);
+ rtl::Reference<primitive2d::PolygonHairlinePrimitive2D> xPLeft(
+ new primitive2d::PolygonHairlinePrimitive2D(aLeft, rHairlinePrimitive.getBColor()));
+ rtl::Reference<primitive2d::PolygonHairlinePrimitive2D> xPRight(
+ new primitive2d::PolygonHairlinePrimitive2D(aRight, rHairlinePrimitive.getBColor()));
+
+ processBasePrimitive2D(*xPLeft);
+ processBasePrimitive2D(*xPRight);
+ }
+ else
+ {
+ // direct draw of hairline; use default processing
+ // support SvtGraphicStroke MetaCommentAction
+ const basegfx::BColor aLineColor(
+ maBColorModifierStack.getModifiedColor(rHairlinePrimitive.getBColor()));
+ std::unique_ptr<SvtGraphicStroke> pSvtGraphicStroke;
+
+ // #i121267# Not needed, does not give better quality compared with
+ // the MetaActionType::POLYPOLYGON written by RenderPolygonHairlinePrimitive2D
+ // below
+ const bool bSupportSvtGraphicStroke(false);
+
+ if (bSupportSvtGraphicStroke)
+ {
+ pSvtGraphicStroke
+ = impTryToCreateSvtGraphicStroke(rHairlinePrimitive.getB2DPolygon(), &aLineColor,
+ nullptr, nullptr, nullptr, nullptr);
+
+ impStartSvtGraphicStroke(pSvtGraphicStroke.get());
+ }
+
+ RenderPolygonHairlinePrimitive2D(rHairlinePrimitive, false);
+
+ if (bSupportSvtGraphicStroke)
+ {
+ impEndSvtGraphicStroke(pSvtGraphicStroke.get());
+ }
+ }
+}
+
+void VclMetafileProcessor2D::processPolygonStrokePrimitive2D(
+ const primitive2d::PolygonStrokePrimitive2D& rStrokePrimitive)
+{
+ const basegfx::B2DPolygon& rBasePolygon = rStrokePrimitive.getB2DPolygon();
+
+ if (rBasePolygon.count() > (MAX_POLYGON_POINT_COUNT_METAFILE - 1))
+ {
+ // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points
+ // per polygon. If there are more, split the polygon in half and call recursively
+ basegfx::B2DPolygon aLeft, aRight;
+ splitLinePolygon(rBasePolygon, aLeft, aRight);
+ rtl::Reference<primitive2d::PolygonStrokePrimitive2D> xPLeft(
+ new primitive2d::PolygonStrokePrimitive2D(aLeft, rStrokePrimitive.getLineAttribute(),
+ rStrokePrimitive.getStrokeAttribute()));
+ rtl::Reference<primitive2d::PolygonStrokePrimitive2D> xPRight(
+ new primitive2d::PolygonStrokePrimitive2D(aRight, rStrokePrimitive.getLineAttribute(),
+ rStrokePrimitive.getStrokeAttribute()));
+
+ processBasePrimitive2D(*xPLeft);
+ processBasePrimitive2D(*xPRight);
+ }
+ else
+ {
+ mpOutputDevice->Push(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR);
+
+ // support SvtGraphicStroke MetaCommentAction
+ std::unique_ptr<SvtGraphicStroke> pSvtGraphicStroke = impTryToCreateSvtGraphicStroke(
+ rBasePolygon, nullptr, &rStrokePrimitive.getLineAttribute(),
+ &rStrokePrimitive.getStrokeAttribute(), nullptr, nullptr);
+
+ impStartSvtGraphicStroke(pSvtGraphicStroke.get());
+ const attribute::LineAttribute& rLine = rStrokePrimitive.getLineAttribute();
+
+ // create MetaPolyLineActions, but without LineStyle::Dash
+ if (basegfx::fTools::more(rLine.getWidth(), 0.0))
+ {
+ const attribute::StrokeAttribute& rStroke = rStrokePrimitive.getStrokeAttribute();
+
+ const basegfx::BColor aHairlineColor(
+ maBColorModifierStack.getModifiedColor(rLine.getColor()));
+ mpOutputDevice->SetLineColor(Color(aHairlineColor));
+ mpOutputDevice->SetFillColor();
+
+ // use the transformed line width
+ LineInfo aLineInfo(LineStyle::Solid,
+ basegfx::fround(getTransformedLineWidth(rLine.getWidth())));
+ aLineInfo.SetLineJoin(rLine.getLineJoin());
+ aLineInfo.SetLineCap(rLine.getLineCap());
+
+ basegfx::B2DPolyPolygon aHairLinePolyPolygon;
+ if (0.0 == rStroke.getFullDotDashLen())
+ {
+ aHairLinePolyPolygon.append(rBasePolygon);
+ }
+ else
+ {
+ bool done = false;
+ const std::vector<double>& array = rStroke.getDotDashArray();
+ // The dotdash array should generally have the form
+ // (<dashLen> <distance>)+ (<dotLen> <distance>)*
+ // (where (,),+ and * have their regex meaning).
+ // Find out what the lengths and their counts are.
+ if (!array.empty() && array.size() % 2 == 0)
+ {
+ double dashLen = array[0];
+ double distance = array[1];
+ int dashCount = 1;
+ double dotLen = 0;
+ int dotCount = 0;
+ size_t pos = 2;
+ while (pos + 2 <= array.size())
+ {
+ if (array[pos] != dashLen || array[pos + 1] != distance)
+ break;
+ ++dashCount;
+ pos += 2;
+ }
+ if (pos + 2 <= array.size() && array[pos + 1] == distance)
+ {
+ dotLen = array[pos];
+ ++dotCount;
+ pos += 2;
+ while (pos + 2 <= array.size())
+ {
+ if (array[pos] != dotLen || array[pos + 1] != distance)
+ break;
+ ++dotCount;
+ pos += 2;
+ }
+ }
+ if (array.size() == pos)
+ {
+ aHairLinePolyPolygon.append(rBasePolygon);
+ // This will be used by setupStrokeAttributes() in cppcanvas.
+ aLineInfo.SetStyle(LineStyle::Dash);
+ aLineInfo.SetDashCount(dashCount);
+ aLineInfo.SetDashLen(getTransformedLineWidth(dashLen));
+ aLineInfo.SetDistance(getTransformedLineWidth(distance));
+ if (dotCount != 0)
+ {
+ aLineInfo.SetDotCount(dotCount);
+ aLineInfo.SetDotLen(getTransformedLineWidth(dotLen));
+ }
+ done = true;
+ }
+ }
+ if (!done)
+ {
+ // LineInfo can hold only limited info about dashing, apply dashing manually
+ // if LineInfo cannot describe it. That should not happen though.
+ SAL_WARN("drawinglayer", "dotdash array cannot be converted to LineInfo");
+ basegfx::utils::applyLineDashing(rBasePolygon, rStroke.getDotDashArray(),
+ &aHairLinePolyPolygon, nullptr,
+ rStroke.getFullDotDashLen());
+ }
+ }
+ aHairLinePolyPolygon.transform(maCurrentTransformation);
+
+ for (sal_uInt32 a(0); a < aHairLinePolyPolygon.count(); a++)
+ {
+ const basegfx::B2DPolygon& aCandidate(aHairLinePolyPolygon.getB2DPolygon(a));
+
+ if (aCandidate.count() > 1)
+ {
+ const tools::Polygon aToolsPolygon(aCandidate);
+
+ mpMetaFile->AddAction(new MetaPolyLineAction(aToolsPolygon, aLineInfo));
+ }
+ }
+ }
+ else
+ {
+ process(rStrokePrimitive);
+ }
+
+ impEndSvtGraphicStroke(pSvtGraphicStroke.get());
+
+ mpOutputDevice->Pop();
+ }
+}
+
+void VclMetafileProcessor2D::processPolygonStrokeArrowPrimitive2D(
+ const primitive2d::PolygonStrokeArrowPrimitive2D& rStrokeArrowPrimitive)
+{
+ const basegfx::B2DPolygon& rBasePolygon = rStrokeArrowPrimitive.getB2DPolygon();
+
+ if (rBasePolygon.count() > (MAX_POLYGON_POINT_COUNT_METAFILE - 1))
+ {
+ // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points
+ // per polygon. If there are more, split the polygon in half and call recursively
+ basegfx::B2DPolygon aLeft, aRight;
+ splitLinePolygon(rBasePolygon, aLeft, aRight);
+ const attribute::LineStartEndAttribute aEmpty;
+ rtl::Reference<primitive2d::PolygonStrokeArrowPrimitive2D> xPLeft(
+ new primitive2d::PolygonStrokeArrowPrimitive2D(
+ aLeft, rStrokeArrowPrimitive.getLineAttribute(),
+ rStrokeArrowPrimitive.getStrokeAttribute(), rStrokeArrowPrimitive.getStart(),
+ aEmpty));
+ rtl::Reference<primitive2d::PolygonStrokeArrowPrimitive2D> xPRight(
+ new primitive2d::PolygonStrokeArrowPrimitive2D(
+ aRight, rStrokeArrowPrimitive.getLineAttribute(),
+ rStrokeArrowPrimitive.getStrokeAttribute(), aEmpty,
+ rStrokeArrowPrimitive.getEnd()));
+
+ processBasePrimitive2D(*xPLeft);
+ processBasePrimitive2D(*xPRight);
+ }
+ else
+ {
+ // support SvtGraphicStroke MetaCommentAction
+ std::unique_ptr<SvtGraphicStroke> pSvtGraphicStroke = impTryToCreateSvtGraphicStroke(
+ rBasePolygon, nullptr, &rStrokeArrowPrimitive.getLineAttribute(),
+ &rStrokeArrowPrimitive.getStrokeAttribute(), &rStrokeArrowPrimitive.getStart(),
+ &rStrokeArrowPrimitive.getEnd());
+
+ // write LineGeometry start marker
+ impStartSvtGraphicStroke(pSvtGraphicStroke.get());
+
+ // #i116162# When B&W is set as DrawMode, DrawModeFlags::WhiteFill is used
+ // to let all fills be just white; for lines DrawModeFlags::BlackLine is used
+ // so all line geometry is supposed to get black. Since in the in-between
+ // stages of line geometry drawing filled polygons are used (e.g. line
+ // start/ends) it is necessary to change these drawmodes to preserve
+ // that lines shall be black; thus change DrawModeFlags::WhiteFill to
+ // DrawModeFlags::BlackFill during line geometry processing to have line geometry
+ // parts filled black.
+ const DrawModeFlags nOldDrawMode(mpOutputDevice->GetDrawMode());
+ const bool bDrawmodeChange(nOldDrawMode & DrawModeFlags::WhiteFill
+ && mnSvtGraphicStrokeCount);
+
+ if (bDrawmodeChange)
+ {
+ mpOutputDevice->SetDrawMode((nOldDrawMode & ~DrawModeFlags::WhiteFill)
+ | DrawModeFlags::BlackFill);
+ }
+
+ // process sub-line geometry (evtl. filled PolyPolygons)
+ process(rStrokeArrowPrimitive);
+
+ if (bDrawmodeChange)
+ {
+ mpOutputDevice->SetDrawMode(nOldDrawMode);
+ }
+
+ // write LineGeometry end marker
+ impEndSvtGraphicStroke(pSvtGraphicStroke.get());
+ }
+}
+
+void VclMetafileProcessor2D::processPolyPolygonGraphicPrimitive2D(
+ const primitive2d::PolyPolygonGraphicPrimitive2D& rBitmapCandidate)
+{
+ // need to handle PolyPolygonGraphicPrimitive2D here to support XPATHFILL_SEQ_BEGIN/XPATHFILL_SEQ_END
+ basegfx::B2DPolyPolygon aLocalPolyPolygon(rBitmapCandidate.getB2DPolyPolygon());
+
+ if (!rBitmapCandidate.getDefinitionRange().isEmpty()
+ && aLocalPolyPolygon.getB2DRange() != rBitmapCandidate.getDefinitionRange())
+ {
+ // The range which defines the bitmap fill is defined and different from the
+ // range of the defining geometry (e.g. used for FillStyle UseSlideBackground).
+ // This cannot be done calling vcl, thus use decomposition here directly
+ process(rBitmapCandidate);
+ return;
+ }
+
+ fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon);
+
+ std::unique_ptr<SvtGraphicFill> pSvtGraphicFill;
+
+ if (!mnSvtGraphicFillCount && aLocalPolyPolygon.count())
+ {
+ // #121194# Changed implementation and checked usages of convert to metafile,
+ // presentation start (uses SvtGraphicFill) and printing.
+
+ // calculate transformation. Get real object size, all values in FillGraphicAttribute
+ // are relative to the unified object
+ aLocalPolyPolygon.transform(maCurrentTransformation);
+ const basegfx::B2DVector aOutlineSize(aLocalPolyPolygon.getB2DRange().getRange());
+
+ // the scaling needs scale from pixel to logic coordinate system
+ const attribute::FillGraphicAttribute& rFillGraphicAttribute
+ = rBitmapCandidate.getFillGraphic();
+ const Size aBmpSizePixel(rFillGraphicAttribute.getGraphic().GetSizePixel());
+
+ // setup transformation like in impgrfll. Multiply with aOutlineSize
+ // to get from unit coordinates in rFillGraphicAttribute.getGraphicRange()
+ // to object coordinates with object's top left being at (0,0). Divide
+ // by pixel size so that scale from pixel to logic will work in SvtGraphicFill.
+ const basegfx::B2DVector aTransformScale(
+ rFillGraphicAttribute.getGraphicRange().getRange()
+ / basegfx::B2DVector(std::max(1.0, double(aBmpSizePixel.Width())),
+ std::max(1.0, double(aBmpSizePixel.Height())))
+ * aOutlineSize);
+ const basegfx::B2DPoint aTransformPosition(
+ rFillGraphicAttribute.getGraphicRange().getMinimum() * aOutlineSize);
+
+ // setup transformation like in impgrfll
+ SvtGraphicFill::Transform aTransform;
+
+ // scale values are divided by bitmap pixel sizes
+ aTransform.matrix[0] = aTransformScale.getX();
+ aTransform.matrix[4] = aTransformScale.getY();
+
+ // translates are absolute
+ aTransform.matrix[2] = aTransformPosition.getX();
+ aTransform.matrix[5] = aTransformPosition.getY();
+
+ pSvtGraphicFill.reset(new SvtGraphicFill(
+ getFillPolyPolygon(aLocalPolyPolygon), Color(), 0.0, SvtGraphicFill::fillEvenOdd,
+ SvtGraphicFill::fillTexture, aTransform, rFillGraphicAttribute.getTiling(),
+ SvtGraphicFill::hatchSingle, Color(), SvtGraphicFill::GradientType::Linear, Color(),
+ Color(), 0, rFillGraphicAttribute.getGraphic()));
+ }
+
+ // Do use decomposition; encapsulate with SvtGraphicFill
+ impStartSvtGraphicFill(pSvtGraphicFill.get());
+ process(rBitmapCandidate);
+ impEndSvtGraphicFill(pSvtGraphicFill.get());
+}
+
+void VclMetafileProcessor2D::processPolyPolygonHatchPrimitive2D(
+ const primitive2d::PolyPolygonHatchPrimitive2D& rHatchCandidate)
+{
+ // need to handle PolyPolygonHatchPrimitive2D here to support XPATHFILL_SEQ_BEGIN/XPATHFILL_SEQ_END
+ const attribute::FillHatchAttribute& rFillHatchAttribute = rHatchCandidate.getFillHatch();
+ basegfx::B2DPolyPolygon aLocalPolyPolygon(rHatchCandidate.getB2DPolyPolygon());
+
+ if (aLocalPolyPolygon.getB2DRange() != rHatchCandidate.getDefinitionRange())
+ {
+ // the range which defines the hatch is different from the range of the
+ // geometry (used for writer frames). This cannot be done calling vcl, thus use
+ // decomposition here
+ process(rHatchCandidate);
+ return;
+ }
+
+ // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points
+ // per polygon. Split polygon until there are less than that
+ fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon);
+
+ if (rFillHatchAttribute.isFillBackground())
+ {
+ // with fixing #i111954# (see below) the possible background
+ // fill of a hatched object was lost.Generate a background fill
+ // primitive and render it
+ const primitive2d::Primitive2DReference xBackground(
+ new primitive2d::PolyPolygonColorPrimitive2D(aLocalPolyPolygon,
+ rHatchCandidate.getBackgroundColor()));
+
+ process(primitive2d::Primitive2DContainer{ xBackground });
+ }
+
+ std::unique_ptr<SvtGraphicFill> pSvtGraphicFill;
+ aLocalPolyPolygon.transform(maCurrentTransformation);
+
+ if (!mnSvtGraphicFillCount && aLocalPolyPolygon.count())
+ {
+ // re-create a VCL hatch as base data
+ SvtGraphicFill::HatchType eHatch(SvtGraphicFill::hatchSingle);
+
+ switch (rFillHatchAttribute.getStyle())
+ {
+ default: // attribute::HatchStyle::Single :
+ {
+ eHatch = SvtGraphicFill::hatchSingle;
+ break;
+ }
+ case attribute::HatchStyle::Double:
+ {
+ eHatch = SvtGraphicFill::hatchDouble;
+ break;
+ }
+ case attribute::HatchStyle::Triple:
+ {
+ eHatch = SvtGraphicFill::hatchTriple;
+ break;
+ }
+ }
+
+ SvtGraphicFill::Transform aTransform;
+
+ // scale
+ aTransform.matrix[0] *= rFillHatchAttribute.getDistance();
+ aTransform.matrix[4] *= rFillHatchAttribute.getDistance();
+
+ // rotate (was never correct in impgrfll anyways, use correct angle now)
+ aTransform.matrix[0] *= cos(rFillHatchAttribute.getAngle());
+ aTransform.matrix[1] *= -sin(rFillHatchAttribute.getAngle());
+ aTransform.matrix[3] *= sin(rFillHatchAttribute.getAngle());
+ aTransform.matrix[4] *= cos(rFillHatchAttribute.getAngle());
+
+ pSvtGraphicFill.reset(new SvtGraphicFill(
+ getFillPolyPolygon(aLocalPolyPolygon), Color(), 0.0, SvtGraphicFill::fillEvenOdd,
+ SvtGraphicFill::fillHatch, aTransform, false, eHatch,
+ Color(maBColorModifierStack.getModifiedColor(rFillHatchAttribute.getColor())),
+ SvtGraphicFill::GradientType::Linear, Color(), Color(), 0, Graphic()));
+ }
+
+ // Do use decomposition; encapsulate with SvtGraphicFill
+ impStartSvtGraphicFill(pSvtGraphicFill.get());
+
+ // #i111954# do NOT use decomposition, but use direct VCL-command
+ // process(rCandidate.get2DDecomposition(getViewInformation2D()));
+ const tools::PolyPolygon aToolsPolyPolygon(
+ basegfx::utils::adaptiveSubdivideByAngle(aLocalPolyPolygon));
+ const HatchStyle aHatchStyle(
+ attribute::HatchStyle::Single == rFillHatchAttribute.getStyle()
+ ? HatchStyle::Single
+ : attribute::HatchStyle::Double == rFillHatchAttribute.getStyle() ? HatchStyle::Double
+ : HatchStyle::Triple);
+
+ mpOutputDevice->DrawHatch(
+ aToolsPolyPolygon,
+ Hatch(aHatchStyle,
+ Color(maBColorModifierStack.getModifiedColor(rFillHatchAttribute.getColor())),
+ basegfx::fround(rFillHatchAttribute.getDistance()),
+ Degree10(basegfx::fround(basegfx::rad2deg<10>(rFillHatchAttribute.getAngle())))));
+
+ impEndSvtGraphicFill(pSvtGraphicFill.get());
+}
+
+void VclMetafileProcessor2D::processPolyPolygonGradientPrimitive2D(
+ const primitive2d::PolyPolygonGradientPrimitive2D& rGradientCandidate)
+{
+ basegfx::B2DVector aScale, aTranslate;
+ double fRotate, fShearX;
+
+ maCurrentTransformation.decompose(aScale, aTranslate, fRotate, fShearX);
+
+ if (!basegfx::fTools::equalZero(fRotate) || !basegfx::fTools::equalZero(fShearX))
+ {
+ // #i121185# When rotation or shear is used, a VCL Gradient cannot be used directly.
+ // This is because VCL Gradient mechanism does *not* support to rotate the gradient
+ // with objects and this case is not expressible in a Metafile (and cannot be added
+ // since the FileFormats used, e.g. *.wmf, do not support it either).
+ // Such cases happen when a graphic object uses a Metafile as graphic information or
+ // a fill style definition uses a Metafile. In this cases the graphic content is
+ // rotated with the graphic or filled object; this is not supported by the target
+ // format of this conversion renderer - Metafiles.
+ // To solve this, not a Gradient is written, but the decomposition of this object
+ // is written to the Metafile. This is the PolyPolygons building the gradient fill.
+ // These will need more space and time, but the result will be as if the Gradient
+ // was rotated with the object.
+ // This mechanism is used by all exporters still not using Primitives (e.g. Print,
+ // Slideshow, Export rto PDF, export to Picture, ...) but relying on Metafile
+ // transfers. One more reason to *change* these to primitives.
+ // BTW: One more example how useful the principles of primitives are; the decomposition
+ // is by definition a simpler, maybe more expensive representation of the same content.
+ process(rGradientCandidate);
+ return;
+ }
+
+ basegfx::B2DPolyPolygon aLocalPolyPolygon(rGradientCandidate.getB2DPolyPolygon());
+
+ if (aLocalPolyPolygon.getB2DRange() != rGradientCandidate.getDefinitionRange())
+ {
+ // the range which defines the gradient is different from the range of the
+ // geometry (used for writer frames). This cannot be done calling vcl, thus use
+ // decomposition here
+ process(rGradientCandidate);
+ return;
+ }
+
+ // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points
+ // per polygon. Split polygon until there are less than that
+ fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon);
+
+ // for support of MetaCommentActions of the form XGRAD_SEQ_BEGIN, XGRAD_SEQ_END
+ // it is safest to use the VCL OutputDevice::DrawGradient method which creates those.
+ // re-create a VCL-gradient from FillGradientPrimitive2D and the needed tools PolyPolygon
+ Gradient aVCLGradient;
+ impConvertFillGradientAttributeToVCLGradient(aVCLGradient, rGradientCandidate.getFillGradient(),
+ false);
+ aLocalPolyPolygon.transform(maCurrentTransformation);
+
+ // #i82145# ATM VCL printing of gradients using curved shapes does not work,
+ // i submitted the bug with the given ID to THB. When that task is fixed it is
+ // necessary to again remove this subdivision since it decreases possible
+ // printing quality (not even resolution-dependent for now). THB will tell
+ // me when that task is fixed in the master
+ const tools::PolyPolygon aToolsPolyPolygon(
+ getFillPolyPolygon(basegfx::utils::adaptiveSubdivideByAngle(aLocalPolyPolygon)));
+
+ // XPATHFILL_SEQ_BEGIN/XPATHFILL_SEQ_END support
+ std::unique_ptr<SvtGraphicFill> pSvtGraphicFill;
+
+ if (!mnSvtGraphicFillCount && aLocalPolyPolygon.count())
+ {
+ // setup gradient stuff like in impgrfll
+ SvtGraphicFill::GradientType eGrad(SvtGraphicFill::GradientType::Linear);
+
+ switch (aVCLGradient.GetStyle())
+ {
+ default: // GradientStyle::Linear:
+ case GradientStyle::Axial:
+ eGrad = SvtGraphicFill::GradientType::Linear;
+ break;
+ case GradientStyle::Radial:
+ case GradientStyle::Elliptical:
+ eGrad = SvtGraphicFill::GradientType::Radial;
+ break;
+ case GradientStyle::Square:
+ case GradientStyle::Rect:
+ eGrad = SvtGraphicFill::GradientType::Rectangular;
+ break;
+ }
+
+ pSvtGraphicFill.reset(new SvtGraphicFill(
+ aToolsPolyPolygon, Color(), 0.0, SvtGraphicFill::fillEvenOdd,
+ SvtGraphicFill::fillGradient, SvtGraphicFill::Transform(), false,
+ SvtGraphicFill::hatchSingle, Color(), eGrad, aVCLGradient.GetStartColor(),
+ aVCLGradient.GetEndColor(), aVCLGradient.GetSteps(), Graphic()));
+ }
+
+ // call VCL directly; encapsulate with SvtGraphicFill
+ impStartSvtGraphicFill(pSvtGraphicFill.get());
+ mpOutputDevice->DrawGradient(aToolsPolyPolygon, aVCLGradient);
+ impEndSvtGraphicFill(pSvtGraphicFill.get());
+}
+
+void VclMetafileProcessor2D::processPolyPolygonColorPrimitive2D(
+ const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate)
+{
+ mpOutputDevice->Push(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR);
+ basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolygonCandidate.getB2DPolyPolygon());
+
+ // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points
+ // per polygon. Split polygon until there are less than that
+ fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon);
+
+ const basegfx::BColor aPolygonColor(
+ maBColorModifierStack.getModifiedColor(rPolygonCandidate.getBColor()));
+ aLocalPolyPolygon.transform(maCurrentTransformation);
+
+ // set line and fill color
+ mpOutputDevice->SetFillColor(Color(aPolygonColor));
+ mpOutputDevice->SetLineColor();
+
+ mpOutputDevice->DrawPolyPolygon(aLocalPolyPolygon);
+
+ mpOutputDevice->Pop();
+}
+
+void VclMetafileProcessor2D::processMaskPrimitive2D(
+ const primitive2d::MaskPrimitive2D& rMaskCandidate)
+{
+ // mask group. Special handling for MetaFiles.
+ if (rMaskCandidate.getChildren().empty())
+ return;
+
+ basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask());
+
+ if (aMask.count())
+ {
+ // prepare new mask polygon and rescue current one
+ aMask.transform(maCurrentTransformation);
+ const basegfx::B2DPolyPolygon aLastClipPolyPolygon(maClipPolyPolygon);
+
+ if (maClipPolyPolygon.count())
+ {
+ // there is already a clip polygon set; build clipped union of
+ // current mask polygon and new one
+ maClipPolyPolygon = basegfx::utils::clipPolyPolygonOnPolyPolygon(
+ aMask, maClipPolyPolygon,
+ true, // #i106516# we want the inside of aMask, not the outside
+ false);
+ }
+ else
+ {
+ // use mask directly
+ maClipPolyPolygon = aMask;
+ }
+
+ if (maClipPolyPolygon.count())
+ {
+ // set VCL clip region; subdivide before conversion to tools polygon. Subdivision necessary (!)
+ // Removed subdivision and fixed in vcl::Region::ImplPolyPolyRegionToBandRegionFunc() in VCL where
+ // the ClipRegion is built from the Polygon. An AdaptiveSubdivide on the source polygon was missing there
+ mpOutputDevice->Push(vcl::PushFlags::CLIPREGION);
+ mpOutputDevice->SetClipRegion(vcl::Region(maClipPolyPolygon));
+
+ // recursively paint content
+ // #i121267# Only need to process sub-content when clip polygon is *not* empty.
+ // If it is empty, the clip is empty and there can be nothing inside.
+ process(rMaskCandidate.getChildren());
+
+ // restore VCL clip region
+ mpOutputDevice->Pop();
+ }
+
+ // restore to rescued clip polygon
+ maClipPolyPolygon = aLastClipPolyPolygon;
+ }
+ else
+ {
+ // no mask, no clipping. recursively paint content
+ process(rMaskCandidate.getChildren());
+ }
+}
+
+void VclMetafileProcessor2D::processUnifiedTransparencePrimitive2D(
+ const primitive2d::UnifiedTransparencePrimitive2D& rUniTransparenceCandidate)
+{
+ mpOutputDevice->Push(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR);
+ // for metafile: Need to examine what the pure vcl version is doing here actually
+ // - uses DrawTransparent with metafile for content and a gradient
+ // - uses DrawTransparent for single PolyPolygons directly. Can be detected by
+ // checking the content for single PolyPolygonColorPrimitive2D
+ const primitive2d::Primitive2DContainer& rContent = rUniTransparenceCandidate.getChildren();
+
+ if (!rContent.empty())
+ {
+ if (0.0 == rUniTransparenceCandidate.getTransparence())
+ {
+ // not transparent at all, use content
+ process(rUniTransparenceCandidate.getChildren());
+ }
+ else if (rUniTransparenceCandidate.getTransparence() > 0.0
+ && rUniTransparenceCandidate.getTransparence() < 1.0)
+ {
+ // try to identify a single PolyPolygonColorPrimitive2D in the
+ // content part of the transparence primitive
+ const primitive2d::PolyPolygonColorPrimitive2D* pPoPoColor = nullptr;
+ static bool bForceToMetafile(false); // loplugin:constvars:ignore
+
+ if (!bForceToMetafile && 1 == rContent.size())
+ {
+ const primitive2d::Primitive2DReference xReference(rContent[0]);
+ pPoPoColor = dynamic_cast<const primitive2d::PolyPolygonColorPrimitive2D*>(
+ xReference.get());
+ }
+
+ // PolyPolygonGradientPrimitive2D, PolyPolygonHatchPrimitive2D and
+ // PolyPolygonGraphicPrimitive2D are derived from PolyPolygonColorPrimitive2D.
+ // Check also for correct ID to exclude derived implementations
+ if (pPoPoColor
+ && PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D == pPoPoColor->getPrimitive2DID())
+ {
+ // single transparent tools::PolyPolygon identified, use directly
+ const basegfx::BColor aPolygonColor(
+ maBColorModifierStack.getModifiedColor(pPoPoColor->getBColor()));
+ basegfx::B2DPolyPolygon aLocalPolyPolygon(pPoPoColor->getB2DPolyPolygon());
+
+ // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points
+ // per polygon. Split polygon until there are less than that
+ fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon);
+
+ // now transform
+ aLocalPolyPolygon.transform(maCurrentTransformation);
+
+ // set line and fill color
+ const sal_uInt16 nTransPercentVcl(static_cast<sal_uInt16>(
+ basegfx::fround(rUniTransparenceCandidate.getTransparence() * 100.0)));
+ mpOutputDevice->SetFillColor(Color(aPolygonColor));
+ mpOutputDevice->SetLineColor();
+
+ mpOutputDevice->DrawTransparent(tools::PolyPolygon(aLocalPolyPolygon),
+ nTransPercentVcl);
+ }
+ else
+ {
+ // save old mfCurrentUnifiedTransparence and set new one
+ // so that contained SvtGraphicStroke may use the current one
+ const double fLastCurrentUnifiedTransparence(mfCurrentUnifiedTransparence);
+ // #i105377# paint the content metafile opaque as the transparency gets
+ // split of into the gradient below
+ // mfCurrentUnifiedTransparence = rUniTransparenceCandidate.getTransparence();
+ mfCurrentUnifiedTransparence = 0;
+
+ // various content, create content-metafile
+ GDIMetaFile aContentMetafile;
+ const tools::Rectangle aPrimitiveRectangle(
+ impDumpToMetaFile(rContent, aContentMetafile));
+
+ // restore mfCurrentUnifiedTransparence; it may have been used
+ // while processing the sub-content in impDumpToMetaFile
+ mfCurrentUnifiedTransparence = fLastCurrentUnifiedTransparence;
+
+ // create uniform VCL gradient for uniform transparency
+ Gradient aVCLGradient;
+ const sal_uInt8 nTransPercentVcl(static_cast<sal_uInt8>(
+ basegfx::fround(rUniTransparenceCandidate.getTransparence() * 255.0)));
+ const Color aTransColor(nTransPercentVcl, nTransPercentVcl, nTransPercentVcl);
+
+ aVCLGradient.SetStyle(GradientStyle::Linear);
+ aVCLGradient.SetStartColor(aTransColor);
+ aVCLGradient.SetEndColor(aTransColor);
+ aVCLGradient.SetAngle(0_deg10);
+ aVCLGradient.SetBorder(0);
+ aVCLGradient.SetOfsX(0);
+ aVCLGradient.SetOfsY(0);
+ aVCLGradient.SetStartIntensity(100);
+ aVCLGradient.SetEndIntensity(100);
+ aVCLGradient.SetSteps(2);
+
+ // render it to VCL
+ mpOutputDevice->DrawTransparent(aContentMetafile, aPrimitiveRectangle.TopLeft(),
+ aPrimitiveRectangle.GetSize(), aVCLGradient);
+ }
+ }
+ }
+
+ mpOutputDevice->Pop();
+}
+
+void VclMetafileProcessor2D::processTransparencePrimitive2D(
+ const primitive2d::TransparencePrimitive2D& rTransparenceCandidate)
+{
+ // for metafile: Need to examine what the pure vcl version is doing here actually
+ // - uses DrawTransparent with metafile for content and a gradient
+ // i can detect this here with checking the gradient part for a single
+ // FillGradientPrimitive2D and reconstruct the gradient.
+ // If that detection goes wrong, I have to create a transparence-blended bitmap. Eventually
+ // do that in stripes, else RenderTransparencePrimitive2D may just be used
+ const primitive2d::Primitive2DContainer& rContent = rTransparenceCandidate.getChildren();
+ const primitive2d::Primitive2DContainer& rTransparence
+ = rTransparenceCandidate.getTransparence();
+
+ if (rContent.empty() || rTransparence.empty())
+ return;
+
+ // try to identify a single FillGradientPrimitive2D in the
+ // transparence part of the primitive
+ const primitive2d::FillGradientPrimitive2D* pFiGradient = nullptr;
+ static bool bForceToBigTransparentVDev(false); // loplugin:constvars:ignore
+
+ if (!bForceToBigTransparentVDev && 1 == rTransparence.size())
+ {
+ const primitive2d::Primitive2DReference xReference(rTransparence[0]);
+ pFiGradient = dynamic_cast<const primitive2d::FillGradientPrimitive2D*>(xReference.get());
+ }
+
+ // Check also for correct ID to exclude derived implementations
+ if (pFiGradient && PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D == pFiGradient->getPrimitive2DID())
+ {
+ // various content, create content-metafile
+ GDIMetaFile aContentMetafile;
+ const tools::Rectangle aPrimitiveRectangle(impDumpToMetaFile(rContent, aContentMetafile));
+
+ // re-create a VCL-gradient from FillGradientPrimitive2D
+ Gradient aVCLGradient;
+ impConvertFillGradientAttributeToVCLGradient(aVCLGradient, pFiGradient->getFillGradient(),
+ true);
+
+ // render it to VCL
+ mpOutputDevice->DrawTransparent(aContentMetafile, aPrimitiveRectangle.TopLeft(),
+ aPrimitiveRectangle.GetSize(), aVCLGradient);
+ }
+ else
+ {
+ // sub-transparence group. Draw to VDev first.
+ // this may get refined to tiling when resolution is too big here
+
+ // need to avoid switching off MapMode stuff here; maybe need another
+ // tooling class, cannot just do the same as with the pixel renderer.
+ // Need to experiment...
+
+ // Okay, basic implementation finished and tested. The DPI stuff was hard
+ // and not easy to find out that it's needed.
+ // Since this will not yet happen normally (as long as no one constructs
+ // transparence primitives with non-trivial transparence content) i will for now not
+ // refine to tiling here.
+
+ basegfx::B2DRange aViewRange(rContent.getB2DRange(getViewInformation2D()));
+ aViewRange.transform(maCurrentTransformation);
+ const tools::Rectangle aRectLogic(static_cast<sal_Int32>(floor(aViewRange.getMinX())),
+ static_cast<sal_Int32>(floor(aViewRange.getMinY())),
+ static_cast<sal_Int32>(ceil(aViewRange.getMaxX())),
+ static_cast<sal_Int32>(ceil(aViewRange.getMaxY())));
+ const tools::Rectangle aRectPixel(mpOutputDevice->LogicToPixel(aRectLogic));
+ Size aSizePixel(aRectPixel.GetSize());
+ ScopedVclPtrInstance<VirtualDevice> aBufferDevice;
+ const sal_uInt32 nMaxSquarePixels(500000);
+ const sal_uInt32 nViewVisibleArea(aSizePixel.getWidth() * aSizePixel.getHeight());
+ double fReduceFactor(1.0);
+
+ if (nViewVisibleArea > nMaxSquarePixels)
+ {
+ // reduce render size
+ fReduceFactor = sqrt(double(nMaxSquarePixels) / static_cast<double>(nViewVisibleArea));
+ aSizePixel = Size(
+ basegfx::fround(static_cast<double>(aSizePixel.getWidth()) * fReduceFactor),
+ basegfx::fround(static_cast<double>(aSizePixel.getHeight()) * fReduceFactor));
+ }
+
+ if (aBufferDevice->SetOutputSizePixel(aSizePixel))
+ {
+ // create and set MapModes for target devices
+ MapMode aNewMapMode(mpOutputDevice->GetMapMode());
+ aNewMapMode.SetOrigin(Point(-aRectLogic.Left(), -aRectLogic.Top()));
+ aBufferDevice->SetMapMode(aNewMapMode);
+
+ // prepare view transformation for target renderers
+ // ATTENTION! Need to apply another scaling because of the potential DPI differences
+ // between Printer and VDev (mpOutputDevice and aBufferDevice here).
+ // To get the DPI, LogicToPixel from (1,1) from MapUnit::MapInch needs to be used.
+ basegfx::B2DHomMatrix aViewTransform(aBufferDevice->GetViewTransformation());
+ const Size aDPIOld(mpOutputDevice->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch)));
+ const Size aDPINew(aBufferDevice->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch)));
+ const double fDPIXChange(static_cast<double>(aDPIOld.getWidth())
+ / static_cast<double>(aDPINew.getWidth()));
+ const double fDPIYChange(static_cast<double>(aDPIOld.getHeight())
+ / static_cast<double>(aDPINew.getHeight()));
+
+ if (!basegfx::fTools::equal(fDPIXChange, 1.0)
+ || !basegfx::fTools::equal(fDPIYChange, 1.0))
+ {
+ aViewTransform.scale(fDPIXChange, fDPIYChange);
+ }
+
+ // also take scaling from Size reduction into account
+ if (!basegfx::fTools::equal(fReduceFactor, 1.0))
+ {
+ aViewTransform.scale(fReduceFactor, fReduceFactor);
+ }
+
+ // create view information and pixel renderer. Reuse known ViewInformation
+ // except new transformation and range
+ const geometry::ViewInformation2D aViewInfo(
+ getViewInformation2D().getObjectTransformation(), aViewTransform, aViewRange,
+ getViewInformation2D().getVisualizedPage(), getViewInformation2D().getViewTime());
+
+ VclPixelProcessor2D aBufferProcessor(aViewInfo, *aBufferDevice);
+
+ // draw content using pixel renderer
+ const Point aEmptyPoint;
+ aBufferProcessor.process(rContent);
+ const Bitmap aBmContent(aBufferDevice->GetBitmap(aEmptyPoint, aSizePixel));
+
+ // draw transparence using pixel renderer
+ aBufferDevice->Erase();
+ aBufferProcessor.process(rTransparence);
+ const AlphaMask aBmAlpha(aBufferDevice->GetBitmap(aEmptyPoint, aSizePixel));
+
+ // paint
+ mpOutputDevice->DrawBitmapEx(aRectLogic.TopLeft(), aRectLogic.GetSize(),
+ BitmapEx(aBmContent, aBmAlpha));
+ }
+ }
+}
+
+void VclMetafileProcessor2D::processStructureTagPrimitive2D(
+ const primitive2d::StructureTagPrimitive2D& rStructureTagCandidate)
+{
+ ::comphelper::ValueRestorationGuard const g(mpCurrentStructureTag, &rStructureTagCandidate);
+
+ // structured tag primitive
+ const vcl::PDFWriter::StructElement& rTagElement(rStructureTagCandidate.getStructureElement());
+ bool bTagUsed((vcl::PDFWriter::NonStructElement != rTagElement));
+ sal_Int32 nPreviousElement(-1);
+
+ if (!rStructureTagCandidate.isTaggedSdrObject())
+ {
+ bTagUsed = false;
+ }
+
+ if (mpPDFExtOutDevData && bTagUsed)
+ {
+ // foreground object: tag as regular structure element
+ if (!rStructureTagCandidate.isBackground())
+ {
+ if (rStructureTagCandidate.GetAnchorStructureElementId() != -1)
+ {
+ auto const nTemp = mpPDFExtOutDevData->GetCurrentStructureElement();
+ bool const bSuccess = mpPDFExtOutDevData->SetCurrentStructureElement(
+ rStructureTagCandidate.GetAnchorStructureElementId());
+ if (bSuccess)
+ {
+ nPreviousElement = nTemp;
+ }
+ else
+ {
+ SAL_WARN("drawinglayer", "anchor structure element not found?");
+ }
+ }
+ mpPDFExtOutDevData->BeginStructureElement(rTagElement);
+ switch (rTagElement)
+ {
+ case vcl::PDFWriter::H1:
+ case vcl::PDFWriter::H2:
+ case vcl::PDFWriter::H3:
+ case vcl::PDFWriter::H4:
+ case vcl::PDFWriter::H5:
+ case vcl::PDFWriter::H6:
+ case vcl::PDFWriter::Paragraph:
+ case vcl::PDFWriter::Heading:
+ case vcl::PDFWriter::Caption:
+ case vcl::PDFWriter::BlockQuote:
+ case vcl::PDFWriter::Table:
+ case vcl::PDFWriter::TableRow:
+ case vcl::PDFWriter::Formula:
+ case vcl::PDFWriter::Figure:
+ mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Placement,
+ vcl::PDFWriter::Block);
+ break;
+ case vcl::PDFWriter::TableData:
+ case vcl::PDFWriter::TableHeader:
+ mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Placement,
+ vcl::PDFWriter::Inline);
+ break;
+ default:
+ break;
+ }
+ switch (rTagElement)
+ {
+ case vcl::PDFWriter::Table:
+ case vcl::PDFWriter::Formula:
+ case vcl::PDFWriter::Figure:
+ {
+ auto const range(rStructureTagCandidate.getB2DRange(getViewInformation2D()));
+ tools::Rectangle const aLogicRect(
+ basegfx::fround(range.getMinX()), basegfx::fround(range.getMinY()),
+ basegfx::fround(range.getMaxX()), basegfx::fround(range.getMaxY()));
+ mpPDFExtOutDevData->SetStructureBoundingBox(aLogicRect);
+ break;
+ }
+ default:
+ break;
+ }
+ if (rTagElement == vcl::PDFWriter::TableHeader)
+ {
+ mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Scope,
+ vcl::PDFWriter::Column);
+ }
+ }
+ // background object
+ else
+ {
+ // background image: tag as artifact
+ if (rStructureTagCandidate.isImage())
+ mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::NonStructElement);
+ // any other background object: do not tag
+ else
+ assert(false);
+ }
+ }
+
+ // process children normally
+ process(rStructureTagCandidate.getChildren());
+
+ if (mpPDFExtOutDevData && bTagUsed)
+ {
+ // write end tag
+ mpPDFExtOutDevData->EndStructureElement();
+ if (nPreviousElement != -1)
+ {
+#ifndef NDEBUG
+ bool const bSuccess =
+#endif
+ mpPDFExtOutDevData->SetCurrentStructureElement(nPreviousElement);
+ assert(bSuccess);
+ }
+ }
+}
+
+VclPtr<VirtualDevice>
+VclMetafileProcessor2D::CreateBufferDevice(const basegfx::B2DRange& rCandidateRange,
+ geometry::ViewInformation2D& rViewInfo,
+ tools::Rectangle& rRectLogic, Size& rSizePixel) const
+{
+ constexpr double fMaxSquarePixels = 500000;
+ basegfx::B2DRange aViewRange(rCandidateRange);
+ aViewRange.transform(maCurrentTransformation);
+ rRectLogic = tools::Rectangle(static_cast<tools::Long>(std::floor(aViewRange.getMinX())),
+ static_cast<tools::Long>(std::floor(aViewRange.getMinY())),
+ static_cast<tools::Long>(std::ceil(aViewRange.getMaxX())),
+ static_cast<tools::Long>(std::ceil(aViewRange.getMaxY())));
+ const tools::Rectangle aRectPixel(mpOutputDevice->LogicToPixel(rRectLogic));
+ rSizePixel = aRectPixel.GetSize();
+ const double fViewVisibleArea(rSizePixel.getWidth() * rSizePixel.getHeight());
+ double fReduceFactor(1.0);
+
+ if (fViewVisibleArea > fMaxSquarePixels)
+ {
+ // reduce render size
+ fReduceFactor = sqrt(fMaxSquarePixels / fViewVisibleArea);
+ rSizePixel = Size(basegfx::fround(rSizePixel.getWidth() * fReduceFactor),
+ basegfx::fround(rSizePixel.getHeight() * fReduceFactor));
+ }
+
+ VclPtrInstance<VirtualDevice> pBufferDevice(DeviceFormat::DEFAULT, DeviceFormat::DEFAULT);
+ if (pBufferDevice->SetOutputSizePixel(rSizePixel))
+ {
+ // create and set MapModes for target devices
+ MapMode aNewMapMode(mpOutputDevice->GetMapMode());
+ aNewMapMode.SetOrigin(Point(-rRectLogic.Left(), -rRectLogic.Top()));
+ pBufferDevice->SetMapMode(aNewMapMode);
+
+ // prepare view transformation for target renderers
+ // ATTENTION! Need to apply another scaling because of the potential DPI differences
+ // between Printer and VDev (mpOutputDevice and pBufferDevice here).
+ // To get the DPI, LogicToPixel from (1,1) from MapUnit::MapInch needs to be used.
+ basegfx::B2DHomMatrix aViewTransform(pBufferDevice->GetViewTransformation());
+ const Size aDPIOld(mpOutputDevice->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch)));
+ const Size aDPINew(pBufferDevice->LogicToPixel(Size(1, 1), MapMode(MapUnit::MapInch)));
+ const double fDPIXChange(static_cast<double>(aDPIOld.getWidth())
+ / static_cast<double>(aDPINew.getWidth()));
+ const double fDPIYChange(static_cast<double>(aDPIOld.getHeight())
+ / static_cast<double>(aDPINew.getHeight()));
+
+ if (!basegfx::fTools::equal(fDPIXChange, 1.0) || !basegfx::fTools::equal(fDPIYChange, 1.0))
+ {
+ aViewTransform.scale(fDPIXChange, fDPIYChange);
+ }
+
+ // also take scaling from Size reduction into account
+ if (!basegfx::fTools::equal(fReduceFactor, 1.0))
+ {
+ aViewTransform.scale(fReduceFactor, fReduceFactor);
+ }
+
+ // create view information and pixel renderer. Reuse known ViewInformation
+ // except new transformation and range
+ rViewInfo = geometry::ViewInformation2D(
+ getViewInformation2D().getObjectTransformation(), aViewTransform, aViewRange,
+ getViewInformation2D().getVisualizedPage(), getViewInformation2D().getViewTime());
+ }
+ else
+ pBufferDevice.disposeAndClear();
+
+#if HAVE_P1155R3
+ return pBufferDevice;
+#else
+ return std::move(pBufferDevice);
+#endif
+}
+
+void VclMetafileProcessor2D::processPrimitive2DOnPixelProcessor(
+ const primitive2d::BasePrimitive2D& rCandidate)
+{
+ basegfx::B2DRange aViewRange(rCandidate.getB2DRange(getViewInformation2D()));
+ geometry::ViewInformation2D aViewInfo;
+ tools::Rectangle aRectLogic;
+ Size aSizePixel;
+ auto pBufferDevice(CreateBufferDevice(aViewRange, aViewInfo, aRectLogic, aSizePixel));
+ if (pBufferDevice)
+ {
+ VclPixelProcessor2D aBufferProcessor(aViewInfo, *pBufferDevice, maBColorModifierStack);
+
+ // draw content using pixel renderer
+ primitive2d::Primitive2DReference aRef(
+ &const_cast<primitive2d::BasePrimitive2D&>(rCandidate));
+ aBufferProcessor.process({ aRef });
+ const BitmapEx aBmContent(pBufferDevice->GetBitmapEx(Point(), aSizePixel));
+ mpOutputDevice->DrawBitmapEx(aRectLogic.TopLeft(), aRectLogic.GetSize(), aBmContent);
+
+ // aBufferProcessor dtor pops state off pBufferDevice pushed on by its ctor, let
+ // pBufferDevice live past aBufferProcessor scope to avoid warnings
+ }
+ pBufferDevice.disposeAndClear();
+}
+
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx b/drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx
new file mode 100644
index 000000000..6b66a160b
--- /dev/null
+++ b/drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <stack>
+
+#include "vclprocessor2d.hxx"
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <vcl/pdfextoutdevdata.hxx> // vcl::PDFExtOutDevData support
+
+class GDIMetaFile;
+namespace tools
+{
+class Rectangle;
+}
+class Gradient;
+class SvtGraphicFill;
+class SvtGraphicStroke;
+
+namespace drawinglayer::attribute
+{
+class FillGradientAttribute;
+class LineAttribute;
+class StrokeAttribute;
+class LineStartEndAttribute;
+}
+
+namespace drawinglayer::primitive2d
+{
+class GraphicPrimitive2D;
+class ControlPrimitive2D;
+class TextHierarchyFieldPrimitive2D;
+class TextHierarchyLinePrimitive2D;
+class TextHierarchyBulletPrimitive2D;
+class TextHierarchyParagraphPrimitive2D;
+class TextHierarchyBlockPrimitive2D;
+class TextSimplePortionPrimitive2D;
+class PolygonHairlinePrimitive2D;
+class PolygonStrokePrimitive2D;
+class PolygonStrokeArrowPrimitive2D;
+class PolyPolygonGraphicPrimitive2D;
+class PolyPolygonHatchPrimitive2D;
+class PolyPolygonGradientPrimitive2D;
+class PolyPolygonColorPrimitive2D;
+class MaskPrimitive2D;
+class UnifiedTransparencePrimitive2D;
+class TransparencePrimitive2D;
+class ObjectInfoPrimitive2D;
+class StructureTagPrimitive2D;
+}
+
+namespace basegfx
+{
+class BColor;
+}
+
+namespace drawinglayer::processor2d
+{
+/** VclMetafileProcessor2D class
+
+ This processor derived from VclProcessor2D is the base class for rendering
+ all fed primitives to a classical VCL-Metafile, including all over the
+ time grown extra data in comments and PDF exception data creations. Also
+ printing needs some exception stuff.
+
+ All in all it is needed to emulate the old ::paint output from the old
+ Drawinglayer as long as exporters and/or filters still use the Metafile
+ and the extra-data added to it (which can be seen mostly as 'extensions'
+ or simply as 'hacks').
+ */
+class VclMetafileProcessor2D : public VclProcessor2D
+{
+private:
+ tools::Rectangle impDumpToMetaFile(const primitive2d::Primitive2DContainer& rContent,
+ GDIMetaFile& o_rContentMetafile);
+ void
+ impConvertFillGradientAttributeToVCLGradient(Gradient& o_rVCLGradient,
+ const attribute::FillGradientAttribute& rFiGrAtt,
+ bool bIsTransparenceGradient) const;
+ void impStartSvtGraphicFill(SvtGraphicFill const* pSvtGraphicFill);
+ void impEndSvtGraphicFill(SvtGraphicFill const* pSvtGraphicFill);
+ std::unique_ptr<SvtGraphicStroke>
+ impTryToCreateSvtGraphicStroke(const basegfx::B2DPolygon& rB2DPolygon,
+ const basegfx::BColor* pColor,
+ const attribute::LineAttribute* pLineAttribute,
+ const attribute::StrokeAttribute* pStrokeAttribute,
+ const attribute::LineStartEndAttribute* pStart,
+ const attribute::LineStartEndAttribute* pEnd);
+ void impStartSvtGraphicStroke(SvtGraphicStroke const* pSvtGraphicStroke);
+ void impEndSvtGraphicStroke(SvtGraphicStroke const* pSvtGraphicStroke);
+ void popStructureElement(vcl::PDFWriter::StructElement eElem);
+ void popListItem();
+ void popList();
+
+ void processGraphicPrimitive2D(const primitive2d::GraphicPrimitive2D& rGraphicPrimitive);
+ void processControlPrimitive2D(const primitive2d::ControlPrimitive2D& rControlPrimitive);
+ void processTextHierarchyFieldPrimitive2D(
+ const primitive2d::TextHierarchyFieldPrimitive2D& rFieldPrimitive);
+ void processTextHierarchyLinePrimitive2D(
+ const primitive2d::TextHierarchyLinePrimitive2D& rLinePrimitive);
+ void processTextHierarchyBulletPrimitive2D(
+ const primitive2d::TextHierarchyBulletPrimitive2D& rBulletPrimitive);
+ void processTextHierarchyParagraphPrimitive2D(
+ const primitive2d::TextHierarchyParagraphPrimitive2D& rParagraphPrimitive);
+ void processTextHierarchyBlockPrimitive2D(
+ const primitive2d::TextHierarchyBlockPrimitive2D& rBlockPrimitive);
+ void processTextSimplePortionPrimitive2D(
+ const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate);
+ void processPolygonHairlinePrimitive2D(
+ const primitive2d::PolygonHairlinePrimitive2D& rHairlinePrimitive);
+ void
+ processPolygonStrokePrimitive2D(const primitive2d::PolygonStrokePrimitive2D& rStrokePrimitive);
+ void processPolygonStrokeArrowPrimitive2D(
+ const primitive2d::PolygonStrokeArrowPrimitive2D& rStrokeArrowPrimitive);
+ void processPolyPolygonGraphicPrimitive2D(
+ const primitive2d::PolyPolygonGraphicPrimitive2D& rBitmapCandidate);
+ void processPolyPolygonHatchPrimitive2D(
+ const primitive2d::PolyPolygonHatchPrimitive2D& rHatchCandidate);
+ void processPolyPolygonGradientPrimitive2D(
+ const primitive2d::PolyPolygonGradientPrimitive2D& rGradientCandidate);
+ void processPolyPolygonColorPrimitive2D(
+ const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate);
+ void processMaskPrimitive2D(const primitive2d::MaskPrimitive2D& rMaskCandidate);
+ void processUnifiedTransparencePrimitive2D(
+ const primitive2d::UnifiedTransparencePrimitive2D& rUniTransparenceCandidate);
+ void processTransparencePrimitive2D(
+ const primitive2d::TransparencePrimitive2D& rTransparenceCandidate);
+ void
+ processObjectInfoPrimitive2D(const primitive2d::ObjectInfoPrimitive2D& rObjectInfoPrimitive2D);
+ void processStructureTagPrimitive2D(
+ const primitive2d::StructureTagPrimitive2D& rStructureTagCandidate);
+ void processPrimitive2DOnPixelProcessor(const primitive2d::BasePrimitive2D& rCandidate);
+ VclPtr<VirtualDevice> CreateBufferDevice(const basegfx::B2DRange& rCandidateRange,
+ geometry::ViewInformation2D& rViewInfo,
+ tools::Rectangle& rRectLogic, Size& rSizePixel) const;
+
+ /// Convert the fWidth to the same space as its coordinates.
+ double getTransformedLineWidth(double fWidth) const;
+
+ /// the current clipping tools::PolyPolygon from MaskPrimitive2D
+ basegfx::B2DPolyPolygon maClipPolyPolygon;
+
+ /// the target MetaFile
+ GDIMetaFile* mpMetaFile;
+
+ /* do not allow embedding SvtGraphicFills into each other,
+ use a counter to prevent that
+ */
+ sal_uInt32 mnSvtGraphicFillCount;
+
+ /// same for SvtGraphicStroke
+ sal_uInt32 mnSvtGraphicStrokeCount;
+
+ /* hold the last unified transparence value to have it handy
+ on SvtGraphicStroke creation
+ */
+ double mfCurrentUnifiedTransparence;
+
+ /* break iterator support
+ made static so it only needs to be fetched once, even with many single
+ constructed VclMetafileProcessor2D. It's still incarnated on demand,
+ but exists for OOo runtime now by purpose.
+ */
+ static css::uno::Reference<css::i18n::XBreakIterator> mxBreakIterator;
+
+ /* vcl::PDFExtOutDevData support
+ For the first step, some extra actions at vcl::PDFExtOutDevData need to
+ be emulated with the VclMetafileProcessor2D. These are potentially temporarily
+ since PDF export may use PrimitiveSequences one day directly.
+ */
+ vcl::PDFExtOutDevData* mpPDFExtOutDevData;
+
+ // Remember the current OutlineLevel. This is used when tagged PDF export
+ // is used to create/write valid structured list entries using PDF statements
+ // like '/L', '/LI', 'LBody' instead of simple '/P' (Paragraph).
+ // The value -1 means 'no OutlineLevel' and values >= 0 express the level.
+ sal_Int16 mnCurrentOutlineLevel;
+ bool mbInListItem;
+ bool mbBulletPresent;
+
+ std::stack<vcl::PDFWriter::StructElement> maListElements;
+
+ primitive2d::StructureTagPrimitive2D const* mpCurrentStructureTag = nullptr;
+
+protected:
+ /* the local processor for BasePrimitive2D-Implementation based primitives,
+ called from the common process()-implementation
+ */
+ virtual void processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) override;
+
+public:
+ /// constructor/destructor
+ VclMetafileProcessor2D(const geometry::ViewInformation2D& rViewInformation,
+ OutputDevice& rOutDev);
+ virtual ~VclMetafileProcessor2D() override;
+};
+} // end of namespace processor2d::drawinglayer
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
new file mode 100644
index 000000000..9291c1830
--- /dev/null
+++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
@@ -0,0 +1,1351 @@
+/* -*- 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 "vclpixelprocessor2d.hxx"
+#include "vclhelperbufferdevice.hxx"
+#include "helperwrongspellrenderer.hxx"
+#include <comphelper/lok.hxx>
+
+#include <sal/log.hxx>
+#include <vcl/BitmapBasicMorphologyFilter.hxx>
+#include <vcl/BitmapFilterStackBlur.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/hatch.hxx>
+#include <vcl/canvastools.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+
+#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
+#include <drawinglayer/primitive2d/Tools.hxx>
+#include <drawinglayer/primitive2d/textprimitive2d.hxx>
+#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonGradientPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
+#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx>
+#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
+#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
+#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx>
+#include <drawinglayer/primitive2d/glowprimitive2d.hxx>
+#include <drawinglayer/primitive2d/wrongspellprimitive2d.hxx>
+#include <drawinglayer/primitive2d/controlprimitive2d.hxx>
+#include <drawinglayer/primitive2d/borderlineprimitive2d.hxx>
+#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx>
+#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
+#include <drawinglayer/primitive2d/pagepreviewprimitive2d.hxx>
+#include <drawinglayer/primitive2d/backgroundcolorprimitive2d.hxx>
+#include <drawinglayer/primitive2d/svggradientprimitive2d.hxx>
+#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
+#include <drawinglayer/primitive2d/fillhatchprimitive2d.hxx>
+#include <drawinglayer/primitive2d/epsprimitive2d.hxx>
+#include <drawinglayer/primitive2d/softedgeprimitive2d.hxx>
+#include <drawinglayer/primitive2d/shadowprimitive2d.hxx>
+#include <drawinglayer/primitive2d/patternfillprimitive2d.hxx>
+
+#include <com/sun/star/awt/XWindow2.hpp>
+#include <com/sun/star/awt/XControl.hpp>
+
+#include <svtools/optionsdrawinglayer.hxx>
+#include <vcl/gradient.hxx>
+
+using namespace com::sun::star;
+
+namespace drawinglayer::processor2d
+{
+struct VclPixelProcessor2D::Impl
+{
+ AntialiasingFlags m_nOrigAntiAliasing;
+
+ explicit Impl(OutputDevice const& rOutDev)
+ : m_nOrigAntiAliasing(rOutDev.GetAntialiasing())
+ {
+ }
+};
+
+VclPixelProcessor2D::VclPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation,
+ OutputDevice& rOutDev,
+ const basegfx::BColorModifierStack& rInitStack)
+ : VclProcessor2D(rViewInformation, rOutDev, rInitStack)
+ , m_pImpl(new Impl(rOutDev))
+{
+ // prepare maCurrentTransformation matrix with viewTransformation to target directly to pixels
+ maCurrentTransformation = rViewInformation.getObjectToViewTransformation();
+
+ // prepare output directly to pixels
+ mpOutputDevice->Push(vcl::PushFlags::MAPMODE);
+ mpOutputDevice->SetMapMode();
+
+ // react on AntiAliasing settings
+ if (SvtOptionsDrawinglayer::IsAntiAliasing())
+ {
+ mpOutputDevice->SetAntialiasing(m_pImpl->m_nOrigAntiAliasing | AntialiasingFlags::Enable);
+ }
+ else
+ {
+ mpOutputDevice->SetAntialiasing(m_pImpl->m_nOrigAntiAliasing & ~AntialiasingFlags::Enable);
+ }
+}
+
+VclPixelProcessor2D::~VclPixelProcessor2D()
+{
+ // restore MapMode
+ mpOutputDevice->Pop();
+
+ // restore AntiAliasing
+ mpOutputDevice->SetAntialiasing(m_pImpl->m_nOrigAntiAliasing);
+}
+
+void VclPixelProcessor2D::tryDrawPolyPolygonColorPrimitive2DDirect(
+ const drawinglayer::primitive2d::PolyPolygonColorPrimitive2D& rSource, double fTransparency)
+{
+ if (!rSource.getB2DPolyPolygon().count() || fTransparency < 0.0 || fTransparency >= 1.0)
+ {
+ // no geometry, done
+ return;
+ }
+
+ const basegfx::BColor aPolygonColor(
+ maBColorModifierStack.getModifiedColor(rSource.getBColor()));
+
+ mpOutputDevice->SetFillColor(Color(aPolygonColor));
+ mpOutputDevice->SetLineColor();
+ mpOutputDevice->DrawTransparent(maCurrentTransformation, rSource.getB2DPolyPolygon(),
+ fTransparency);
+}
+
+bool VclPixelProcessor2D::tryDrawPolygonHairlinePrimitive2DDirect(
+ const drawinglayer::primitive2d::PolygonHairlinePrimitive2D& rSource, double fTransparency)
+{
+ const basegfx::B2DPolygon& rLocalPolygon(rSource.getB2DPolygon());
+
+ if (!rLocalPolygon.count() || fTransparency < 0.0 || fTransparency >= 1.0)
+ {
+ // no geometry, done
+ return true;
+ }
+
+ const basegfx::BColor aLineColor(maBColorModifierStack.getModifiedColor(rSource.getBColor()));
+
+ mpOutputDevice->SetFillColor();
+ mpOutputDevice->SetLineColor(Color(aLineColor));
+ //aLocalPolygon.transform(maCurrentTransformation);
+
+ // try drawing; if it did not work, use standard fallback
+ return mpOutputDevice->DrawPolyLineDirect(maCurrentTransformation, rLocalPolygon, 0.0,
+ fTransparency);
+}
+
+bool VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect(
+ const drawinglayer::primitive2d::PolygonStrokePrimitive2D& rSource, double fTransparency)
+{
+ const basegfx::B2DPolygon& rLocalPolygon(rSource.getB2DPolygon());
+
+ if (!rLocalPolygon.count() || fTransparency < 0.0 || fTransparency >= 1.0)
+ {
+ // no geometry, done
+ return true;
+ }
+
+ if (basegfx::B2DLineJoin::NONE == rSource.getLineAttribute().getLineJoin()
+ && css::drawing::LineCap_BUTT != rSource.getLineAttribute().getLineCap())
+ {
+ // better use decompose to get that combination done for now, see discussion
+ // at https://bugs.documentfoundation.org/show_bug.cgi?id=130478#c17 and ff
+ return false;
+ }
+
+ // MM01: Radically change here - no dismantle/applyLineDashing,
+ // let that happen low-level at DrawPolyLineDirect implementations
+ // to open up for buffering and evtl. direct draw with sys-dep
+ // graphic systems. Check for stroke is in use
+ const bool bStrokeAttributeNotUsed(rSource.getStrokeAttribute().isDefault()
+ || 0.0 == rSource.getStrokeAttribute().getFullDotDashLen());
+
+ const basegfx::BColor aLineColor(
+ maBColorModifierStack.getModifiedColor(rSource.getLineAttribute().getColor()));
+
+ mpOutputDevice->SetFillColor();
+ mpOutputDevice->SetLineColor(Color(aLineColor));
+
+ // MM01 draw direct, hand over dash data if available
+ return mpOutputDevice->DrawPolyLineDirect(
+ maCurrentTransformation, rLocalPolygon,
+ // tdf#124848 use LineWidth direct, do not try to solve for zero-case (aka hairline)
+ rSource.getLineAttribute().getWidth(), fTransparency,
+ bStrokeAttributeNotUsed ? nullptr : &rSource.getStrokeAttribute().getDotDashArray(),
+ rSource.getLineAttribute().getLineJoin(), rSource.getLineAttribute().getLineCap(),
+ rSource.getLineAttribute().getMiterMinimumAngle());
+}
+
+namespace
+{
+GradientStyle convertGradientStyle(drawinglayer::attribute::GradientStyle eGradientStyle)
+{
+ switch (eGradientStyle)
+ {
+ case drawinglayer::attribute::GradientStyle::Axial:
+ return GradientStyle::Axial;
+ case drawinglayer::attribute::GradientStyle::Radial:
+ return GradientStyle::Radial;
+ case drawinglayer::attribute::GradientStyle::Elliptical:
+ return GradientStyle::Elliptical;
+ case drawinglayer::attribute::GradientStyle::Square:
+ return GradientStyle::Square;
+ case drawinglayer::attribute::GradientStyle::Rect:
+ return GradientStyle::Rect;
+ case drawinglayer::attribute::GradientStyle::Linear:
+ return GradientStyle::Linear;
+ default:
+ assert(false);
+ return GradientStyle::Linear;
+ }
+}
+
+} // end anonymous namespace
+
+void VclPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
+{
+ switch (rCandidate.getPrimitive2DID())
+ {
+ case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D:
+ {
+ processWrongSpellPrimitive2D(
+ static_cast<const primitive2d::WrongSpellPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D:
+ {
+ processTextSimplePortionPrimitive2D(
+ static_cast<const primitive2d::TextSimplePortionPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D:
+ {
+ processTextDecoratedPortionPrimitive2D(
+ static_cast<const primitive2d::TextSimplePortionPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D:
+ {
+ processPolygonHairlinePrimitive2D(
+ static_cast<const primitive2d::PolygonHairlinePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D:
+ {
+ // direct draw of transformed BitmapEx primitive
+ processBitmapPrimitive2D(
+ static_cast<const primitive2d::BitmapPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D:
+ {
+ // direct draw of fillBitmapPrimitive
+ RenderFillGraphicPrimitive2D(
+ static_cast<const primitive2d::FillGraphicPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D:
+ {
+ processPolyPolygonGradientPrimitive2D(
+ static_cast<const primitive2d::PolyPolygonGradientPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D:
+ {
+ // direct draw of bitmap
+ RenderPolyPolygonGraphicPrimitive2D(
+ static_cast<const primitive2d::PolyPolygonGraphicPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D:
+ {
+ processPolyPolygonColorPrimitive2D(
+ static_cast<const primitive2d::PolyPolygonColorPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D:
+ {
+ processMetaFilePrimitive2D(rCandidate);
+ break;
+ }
+ case PRIMITIVE2D_ID_MASKPRIMITIVE2D:
+ {
+ // mask group.
+ RenderMaskPrimitive2DPixel(
+ static_cast<const primitive2d::MaskPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D:
+ {
+ // modified color group. Force output to unified color.
+ RenderModifiedColorPrimitive2D(
+ static_cast<const primitive2d::ModifiedColorPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D:
+ {
+ processUnifiedTransparencePrimitive2D(
+ static_cast<const primitive2d::UnifiedTransparencePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D:
+ {
+ // sub-transparence group. Draw to VDev first.
+ RenderTransparencePrimitive2D(
+ static_cast<const primitive2d::TransparencePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D:
+ {
+ // transform group.
+ RenderTransformPrimitive2D(
+ static_cast<const primitive2d::TransformPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D:
+ {
+ // new XDrawPage for ViewInformation2D
+ RenderPagePreviewPrimitive2D(
+ static_cast<const primitive2d::PagePreviewPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D:
+ {
+ // marker array
+ RenderMarkerArrayPrimitive2D(
+ static_cast<const primitive2d::MarkerArrayPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D:
+ {
+ // point array
+ RenderPointArrayPrimitive2D(
+ static_cast<const primitive2d::PointArrayPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D:
+ {
+ processControlPrimitive2D(
+ static_cast<const primitive2d::ControlPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D:
+ {
+ processPolygonStrokePrimitive2D(
+ static_cast<const primitive2d::PolygonStrokePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_FILLHATCHPRIMITIVE2D:
+ {
+ processFillHatchPrimitive2D(
+ static_cast<const primitive2d::FillHatchPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_BACKGROUNDCOLORPRIMITIVE2D:
+ {
+ processBackgroundColorPrimitive2D(
+ static_cast<const primitive2d::BackgroundColorPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_TEXTHIERARCHYEDITPRIMITIVE2D:
+ {
+ // #i97628#
+ // This primitive means that the content is derived from an active text edit,
+ // not from model data itself. Some renderers need to suppress this content, e.g.
+ // the pixel renderer used for displaying the edit view (like this one). It's
+ // not to be suppressed by the MetaFile renderers, so that the edited text is
+ // part of the MetaFile, e.g. needed for presentation previews.
+ // Action: Ignore here, do nothing.
+ break;
+ }
+ case PRIMITIVE2D_ID_INVERTPRIMITIVE2D:
+ {
+ processInvertPrimitive2D(rCandidate);
+ break;
+ }
+ case PRIMITIVE2D_ID_EPSPRIMITIVE2D:
+ {
+ RenderEpsPrimitive2D(static_cast<const primitive2d::EpsPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_SVGLINEARATOMPRIMITIVE2D:
+ {
+ RenderSvgLinearAtomPrimitive2D(
+ static_cast<const primitive2d::SvgLinearAtomPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_SVGRADIALATOMPRIMITIVE2D:
+ {
+ RenderSvgRadialAtomPrimitive2D(
+ static_cast<const primitive2d::SvgRadialAtomPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_BORDERLINEPRIMITIVE2D:
+ {
+ processBorderLinePrimitive2D(
+ static_cast<const drawinglayer::primitive2d::BorderLinePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_GLOWPRIMITIVE2D:
+ {
+ processGlowPrimitive2D(
+ static_cast<const drawinglayer::primitive2d::GlowPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_SOFTEDGEPRIMITIVE2D:
+ {
+ processSoftEdgePrimitive2D(
+ static_cast<const drawinglayer::primitive2d::SoftEdgePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D:
+ {
+ processShadowPrimitive2D(
+ static_cast<const drawinglayer::primitive2d::ShadowPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D:
+ {
+ processFillGradientPrimitive2D(
+ static_cast<const drawinglayer::primitive2d::FillGradientPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_PATTERNFILLPRIMITIVE2D:
+ {
+ processPatternFillPrimitive2D(
+ static_cast<const drawinglayer::primitive2d::PatternFillPrimitive2D&>(rCandidate));
+ break;
+ }
+ default:
+ {
+ SAL_INFO("drawinglayer", "default case for " << drawinglayer::primitive2d::idToString(
+ rCandidate.getPrimitive2DID()));
+ // process recursively
+ process(rCandidate);
+ break;
+ }
+ }
+}
+
+void VclPixelProcessor2D::processWrongSpellPrimitive2D(
+ const primitive2d::WrongSpellPrimitive2D& rWrongSpellPrimitive)
+{
+ if (!renderWrongSpellPrimitive2D(rWrongSpellPrimitive, *mpOutputDevice, maCurrentTransformation,
+ maBColorModifierStack))
+ {
+ // fallback to decomposition (MetaFile)
+ process(rWrongSpellPrimitive);
+ }
+}
+
+void VclPixelProcessor2D::processTextSimplePortionPrimitive2D(
+ const primitive2d::TextSimplePortionPrimitive2D& rCandidate)
+{
+ // Adapt evtl. used special DrawMode
+ const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode());
+ adaptTextToFillDrawMode();
+
+ if (SvtOptionsDrawinglayer::IsRenderSimpleTextDirect())
+ {
+ RenderTextSimpleOrDecoratedPortionPrimitive2D(rCandidate);
+ }
+ else
+ {
+ process(rCandidate);
+ }
+
+ // restore DrawMode
+ mpOutputDevice->SetDrawMode(nOriginalDrawMode);
+}
+
+void VclPixelProcessor2D::processTextDecoratedPortionPrimitive2D(
+ const primitive2d::TextSimplePortionPrimitive2D& rCandidate)
+{
+ // Adapt evtl. used special DrawMode
+ const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode());
+ adaptTextToFillDrawMode();
+
+ if (SvtOptionsDrawinglayer::IsRenderDecoratedTextDirect())
+ {
+ RenderTextSimpleOrDecoratedPortionPrimitive2D(rCandidate);
+ }
+ else
+ {
+ process(rCandidate);
+ }
+
+ // restore DrawMode
+ mpOutputDevice->SetDrawMode(nOriginalDrawMode);
+}
+
+void VclPixelProcessor2D::processPolygonHairlinePrimitive2D(
+ const primitive2d::PolygonHairlinePrimitive2D& rPolygonHairlinePrimitive2D)
+{
+ if (tryDrawPolygonHairlinePrimitive2DDirect(rPolygonHairlinePrimitive2D, 0.0))
+ {
+ return;
+ }
+
+ // direct draw of hairline
+ RenderPolygonHairlinePrimitive2D(rPolygonHairlinePrimitive2D, true);
+}
+
+void VclPixelProcessor2D::processBitmapPrimitive2D(
+ const primitive2d::BitmapPrimitive2D& rBitmapCandidate)
+{
+ // check if graphic content is inside discrete local ViewPort
+ const basegfx::B2DRange& rDiscreteViewPort(getViewInformation2D().getDiscreteViewport());
+ const basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation
+ * rBitmapCandidate.getTransform());
+
+ if (!rDiscreteViewPort.isEmpty())
+ {
+ basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0);
+
+ aUnitRange.transform(aLocalTransform);
+
+ if (!aUnitRange.overlaps(rDiscreteViewPort))
+ {
+ // content is outside discrete local ViewPort
+ return;
+ }
+ }
+
+ RenderBitmapPrimitive2D(rBitmapCandidate);
+}
+
+void VclPixelProcessor2D::processPolyPolygonGradientPrimitive2D(
+ const primitive2d::PolyPolygonGradientPrimitive2D& rPolygonCandidate)
+{
+ // direct draw of gradient
+ const attribute::FillGradientAttribute& rGradient(rPolygonCandidate.getFillGradient());
+ basegfx::BColor aStartColor(maBColorModifierStack.getModifiedColor(rGradient.getStartColor()));
+ basegfx::BColor aEndColor(maBColorModifierStack.getModifiedColor(rGradient.getEndColor()));
+ basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolygonCandidate.getB2DPolyPolygon());
+
+ if (!aLocalPolyPolygon.count())
+ return;
+
+ if (aStartColor == aEndColor)
+ {
+ // no gradient at all, draw as polygon in AA and non-AA case
+ aLocalPolyPolygon.transform(maCurrentTransformation);
+ mpOutputDevice->SetLineColor();
+ mpOutputDevice->SetFillColor(Color(aStartColor));
+ mpOutputDevice->DrawPolyPolygon(aLocalPolyPolygon);
+ }
+ else
+ {
+ // use the primitive decomposition of the metafile
+ process(rPolygonCandidate);
+ }
+}
+
+void VclPixelProcessor2D::processPolyPolygonColorPrimitive2D(
+ const primitive2d::PolyPolygonColorPrimitive2D& rPolyPolygonColorPrimitive2D)
+{
+ // try to use directly
+ basegfx::B2DPolyPolygon aLocalPolyPolygon;
+
+ tryDrawPolyPolygonColorPrimitive2DDirect(rPolyPolygonColorPrimitive2D, 0.0);
+ // okay, done. In this case no gaps should have to be repaired, too
+
+ // when AA is on and this filled polygons are the result of stroked line geometry,
+ // draw the geometry once extra as lines to avoid AA 'gaps' between partial polygons
+ // Caution: This is needed in both cases (!)
+ if (!(mnPolygonStrokePrimitive2D && SvtOptionsDrawinglayer::IsAntiAliasing()
+ && (mpOutputDevice->GetAntialiasing() & AntialiasingFlags::Enable)))
+ return;
+
+ const basegfx::BColor aPolygonColor(
+ maBColorModifierStack.getModifiedColor(rPolyPolygonColorPrimitive2D.getBColor()));
+ sal_uInt32 nCount(aLocalPolyPolygon.count());
+
+ if (!nCount)
+ {
+ aLocalPolyPolygon = rPolyPolygonColorPrimitive2D.getB2DPolyPolygon();
+ aLocalPolyPolygon.transform(maCurrentTransformation);
+ nCount = aLocalPolyPolygon.count();
+ }
+
+ mpOutputDevice->SetFillColor();
+ mpOutputDevice->SetLineColor(Color(aPolygonColor));
+
+ for (sal_uInt32 a(0); a < nCount; a++)
+ {
+ mpOutputDevice->DrawPolyLine(aLocalPolyPolygon.getB2DPolygon(a), 0.0);
+ }
+}
+
+void VclPixelProcessor2D::processUnifiedTransparencePrimitive2D(
+ const primitive2d::UnifiedTransparencePrimitive2D& rUniTransparenceCandidate)
+{
+ // Detect if a single PolyPolygonColorPrimitive2D is contained; in that case,
+ // use the faster OutputDevice::DrawTransparent method
+ const primitive2d::Primitive2DContainer& rContent = rUniTransparenceCandidate.getChildren();
+
+ if (rContent.empty())
+ return;
+
+ if (0.0 == rUniTransparenceCandidate.getTransparence())
+ {
+ // not transparent at all, use content
+ process(rUniTransparenceCandidate.getChildren());
+ }
+ else if (rUniTransparenceCandidate.getTransparence() > 0.0
+ && rUniTransparenceCandidate.getTransparence() < 1.0)
+ {
+ bool bDrawTransparentUsed(false);
+
+ if (1 == rContent.size())
+ {
+ const primitive2d::BasePrimitive2D* pBasePrimitive = rContent[0].get();
+
+ switch (pBasePrimitive->getPrimitive2DID())
+ {
+ case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D:
+ {
+ // single transparent tools::PolyPolygon identified, use directly
+ const primitive2d::PolyPolygonColorPrimitive2D* pPoPoColor
+ = static_cast<const primitive2d::PolyPolygonColorPrimitive2D*>(
+ pBasePrimitive);
+ SAL_WARN_IF(!pPoPoColor, "drawinglayer",
+ "OOps, PrimitiveID and PrimitiveType do not match (!)");
+ bDrawTransparentUsed = true;
+ tryDrawPolyPolygonColorPrimitive2DDirect(
+ *pPoPoColor, rUniTransparenceCandidate.getTransparence());
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D:
+ {
+ // single transparent PolygonHairlinePrimitive2D identified, use directly
+ const primitive2d::PolygonHairlinePrimitive2D* pPoHair
+ = static_cast<const primitive2d::PolygonHairlinePrimitive2D*>(
+ pBasePrimitive);
+ SAL_WARN_IF(!pPoHair, "drawinglayer",
+ "OOps, PrimitiveID and PrimitiveType do not match (!)");
+
+ // do no tallow by default - problem is that self-overlapping parts of this geometry will
+ // not be in an all-same transparency but will already alpha-cover themselves with blending.
+ // This is not what the UnifiedTransparencePrimitive2D defines: It requires all its
+ // content to be uniformly transparent.
+ // For hairline the effect is pretty minimal, but still not correct.
+ bDrawTransparentUsed = false;
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D:
+ {
+ // single transparent PolygonStrokePrimitive2D identified, use directly
+ const primitive2d::PolygonStrokePrimitive2D* pPoStroke
+ = static_cast<const primitive2d::PolygonStrokePrimitive2D*>(pBasePrimitive);
+ SAL_WARN_IF(!pPoStroke, "drawinglayer",
+ "OOps, PrimitiveID and PrimitiveType do not match (!)");
+
+ // do no tallow by default - problem is that self-overlapping parts of this geometry will
+ // not be in an all-same transparency but will already alpha-cover themselves with blending.
+ // This is not what the UnifiedTransparencePrimitive2D defines: It requires all its
+ // content to be uniformly transparent.
+ // To check, activate and draw a wide transparent self-crossing line/curve
+ bDrawTransparentUsed = false;
+ break;
+ }
+ default:
+ SAL_INFO("drawinglayer",
+ "default case for " << drawinglayer::primitive2d::idToString(
+ rUniTransparenceCandidate.getPrimitive2DID()));
+ break;
+ }
+ }
+
+ if (!bDrawTransparentUsed)
+ {
+ // unified sub-transparence. Draw to VDev first.
+ RenderUnifiedTransparencePrimitive2D(rUniTransparenceCandidate);
+ }
+ }
+}
+
+void VclPixelProcessor2D::processControlPrimitive2D(
+ const primitive2d::ControlPrimitive2D& rControlPrimitive)
+{
+ // control primitive
+ const uno::Reference<awt::XControl>& rXControl(rControlPrimitive.getXControl());
+
+ try
+ {
+ // remember old graphics and create new
+ uno::Reference<awt::XView> xControlView(rXControl, uno::UNO_QUERY_THROW);
+ const uno::Reference<awt::XGraphics> xOriginalGraphics(xControlView->getGraphics());
+ const uno::Reference<awt::XGraphics> xNewGraphics(mpOutputDevice->CreateUnoGraphics());
+
+ if (xNewGraphics.is())
+ {
+ // find out if the control is already visualized as a VCL-ChildWindow. If yes,
+ // it does not need to be painted at all.
+ uno::Reference<awt::XWindow2> xControlWindow(rXControl, uno::UNO_QUERY_THROW);
+ bool bControlIsVisibleAsChildWindow(rXControl->getPeer().is()
+ && xControlWindow->isVisible());
+
+ // tdf#131281 The FormControls are not painted when using the Tiled Rendering for a simple
+ // reason: when e.g. bControlIsVisibleAsChildWindow is true. This is the case because the
+ // office is in non-layout mode (default for controls at startup). For the common office
+ // this means that there exists a real VCL-System-Window for the control, so it is *not*
+ // painted here due to being exactly obscured by that real Window (and creates danger of
+ // flickering, too).
+ // Tiled Rendering clients usually do *not* have real VCL-Windows for the controls, but
+ // exactly that would be needed on each client displaying the tiles (what would be hard
+ // to do but also would have advantages - the clients would have real controls in the
+ // shape of their target system which could be interacted with...). It is also what the
+ // office does.
+ // For now, fallback to just render these controls when Tiled Rendering is active to just
+ // have them displayed on all clients.
+ if (bControlIsVisibleAsChildWindow && comphelper::LibreOfficeKit::isActive())
+ {
+ // Do force paint when we are in Tiled Renderer and FormControl is 'visible'
+ bControlIsVisibleAsChildWindow = false;
+ }
+
+ if (!bControlIsVisibleAsChildWindow)
+ {
+ // Needs to be drawn. Link new graphics and view
+ xControlView->setGraphics(xNewGraphics);
+
+ // get position
+ const basegfx::B2DHomMatrix aObjectToPixel(maCurrentTransformation
+ * rControlPrimitive.getTransform());
+ const basegfx::B2DPoint aTopLeftPixel(aObjectToPixel * basegfx::B2DPoint(0.0, 0.0));
+
+ // Do not forget to use the evtl. offsetted origin of the target device,
+ // e.g. when used with mask/transparence buffer device
+ const Point aOrigin(mpOutputDevice->GetMapMode().GetOrigin());
+ xControlView->draw(aOrigin.X() + basegfx::fround(aTopLeftPixel.getX()),
+ aOrigin.Y() + basegfx::fround(aTopLeftPixel.getY()));
+
+ // restore original graphics
+ xControlView->setGraphics(xOriginalGraphics);
+ }
+ }
+ }
+ catch (const uno::Exception&)
+ {
+ // #i116763# removing since there is a good alternative when the xControlView
+ // is not found and it is allowed to happen
+ // DBG_UNHANDLED_EXCEPTION();
+
+ // process recursively and use the decomposition as Bitmap
+ process(rControlPrimitive);
+ }
+}
+
+void VclPixelProcessor2D::processPolygonStrokePrimitive2D(
+ const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokePrimitive2D)
+{
+ // try to use directly
+ if (tryDrawPolygonStrokePrimitive2DDirect(rPolygonStrokePrimitive2D, 0.0))
+ {
+ return;
+ }
+
+ // the stroke primitive may be decomposed to filled polygons. To keep
+ // evtl. set DrawModes aka DrawModeFlags::BlackLine, DrawModeFlags::GrayLine,
+ // DrawModeFlags::GhostedLine, DrawModeFlags::WhiteLine or DrawModeFlags::SettingsLine
+ // working, these need to be copied to the corresponding fill modes
+ const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode());
+ adaptLineToFillDrawMode();
+
+ // polygon stroke primitive
+
+ // Lines with 1 and 2 pixel width without AA need special treatment since their visualization
+ // as filled polygons is geometrically correct but looks wrong since polygon filling avoids
+ // the right and bottom pixels. The used method evaluates that and takes the correct action,
+ // including calling recursively with decomposition if line is wide enough
+ RenderPolygonStrokePrimitive2D(rPolygonStrokePrimitive2D);
+
+ // restore DrawMode
+ mpOutputDevice->SetDrawMode(nOriginalDrawMode);
+}
+
+void VclPixelProcessor2D::processFillHatchPrimitive2D(
+ const primitive2d::FillHatchPrimitive2D& rFillHatchPrimitive)
+{
+ if (SvtOptionsDrawinglayer::IsAntiAliasing())
+ {
+ // if AA is used (or ignore smoothing is on), there is no need to smooth
+ // hatch painting, use decomposition
+ process(rFillHatchPrimitive);
+ }
+ else
+ {
+ // without AA, use VCL to draw the hatch. It snaps hatch distances to the next pixel
+ // and forces hatch distance to be >= 3 pixels to make the hatch display look smoother.
+ // This is wrong in principle, but looks nicer. This could also be done here directly
+ // without VCL usage if needed
+ const attribute::FillHatchAttribute& rFillHatchAttributes
+ = rFillHatchPrimitive.getFillHatch();
+
+ // create hatch polygon in range size and discrete coordinates
+ basegfx::B2DRange aHatchRange(rFillHatchPrimitive.getOutputRange());
+ aHatchRange.transform(maCurrentTransformation);
+ const basegfx::B2DPolygon aHatchPolygon(basegfx::utils::createPolygonFromRect(aHatchRange));
+
+ if (rFillHatchAttributes.isFillBackground())
+ {
+ // #i111846# background fill is active; draw fill polygon
+ const basegfx::BColor aPolygonColor(
+ maBColorModifierStack.getModifiedColor(rFillHatchPrimitive.getBColor()));
+
+ mpOutputDevice->SetFillColor(Color(aPolygonColor));
+ mpOutputDevice->SetLineColor();
+ mpOutputDevice->DrawPolygon(aHatchPolygon);
+ }
+
+ // set hatch line color
+ const basegfx::BColor aHatchColor(
+ maBColorModifierStack.getModifiedColor(rFillHatchPrimitive.getBColor()));
+ mpOutputDevice->SetFillColor();
+ mpOutputDevice->SetLineColor(Color(aHatchColor));
+
+ // get hatch style
+ HatchStyle eHatchStyle(HatchStyle::Single);
+
+ switch (rFillHatchAttributes.getStyle())
+ {
+ default: // HatchStyle::Single
+ {
+ break;
+ }
+ case attribute::HatchStyle::Double:
+ {
+ eHatchStyle = HatchStyle::Double;
+ break;
+ }
+ case attribute::HatchStyle::Triple:
+ {
+ eHatchStyle = HatchStyle::Triple;
+ break;
+ }
+ }
+
+ // create hatch
+ const basegfx::B2DVector aDiscreteDistance(
+ maCurrentTransformation * basegfx::B2DVector(rFillHatchAttributes.getDistance(), 0.0));
+ const sal_uInt32 nDistance(basegfx::fround(aDiscreteDistance.getLength()));
+ const sal_uInt32 nAngle10(
+ basegfx::rad2deg<10>(basegfx::fround(rFillHatchAttributes.getAngle())));
+ ::Hatch aVCLHatch(eHatchStyle, Color(rFillHatchAttributes.getColor()), nDistance,
+ Degree10(nAngle10));
+
+ // draw hatch using VCL
+ mpOutputDevice->DrawHatch(::tools::PolyPolygon(::tools::Polygon(aHatchPolygon)), aVCLHatch);
+ }
+}
+
+void VclPixelProcessor2D::processBackgroundColorPrimitive2D(
+ const primitive2d::BackgroundColorPrimitive2D& rPrimitive)
+{
+ // #i98404# Handle directly, especially when AA is active
+ const AntialiasingFlags nOriginalAA(mpOutputDevice->GetAntialiasing());
+
+ // switch AA off in all cases
+ mpOutputDevice->SetAntialiasing(mpOutputDevice->GetAntialiasing() & ~AntialiasingFlags::Enable);
+
+ // create color for fill
+ const basegfx::BColor aPolygonColor(
+ maBColorModifierStack.getModifiedColor(rPrimitive.getBColor()));
+ Color aFillColor(aPolygonColor);
+ aFillColor.SetAlpha(255 - sal_uInt8((rPrimitive.getTransparency() * 255.0) + 0.5));
+ mpOutputDevice->SetFillColor(aFillColor);
+ mpOutputDevice->SetLineColor();
+
+ // create rectangle for fill
+ const basegfx::B2DRange& aViewport(getViewInformation2D().getDiscreteViewport());
+ const ::tools::Rectangle aRectangle(static_cast<sal_Int32>(floor(aViewport.getMinX())),
+ static_cast<sal_Int32>(floor(aViewport.getMinY())),
+ static_cast<sal_Int32>(ceil(aViewport.getMaxX())),
+ static_cast<sal_Int32>(ceil(aViewport.getMaxY())));
+ mpOutputDevice->DrawRect(aRectangle);
+
+ // restore AA setting
+ mpOutputDevice->SetAntialiasing(nOriginalAA);
+}
+
+void VclPixelProcessor2D::processBorderLinePrimitive2D(
+ const drawinglayer::primitive2d::BorderLinePrimitive2D& rBorder)
+{
+ // Process recursively, but switch off AntiAliasing for
+ // horizontal/vertical lines (*not* diagonal lines).
+ // Checked using AntialiasingFlags::PixelSnapHairline instead,
+ // but with AntiAliasing on the display really is too 'ghosty' when
+ // using fine stroking. Correct, but 'ghosty'.
+
+ // It has shown that there are quite some problems here:
+ // - vcl OutDev renderer methods still use fallbacks to paint
+ // multiple single lines between discrete sizes of < 3.5 what
+ // looks bad and does not match
+ // - mix of filled Polygons and Lines is bad when AA switched off
+ // - Alignment of AA with non-AA may be bad in diverse different
+ // renderers
+ //
+ // Due to these reasons I change the strategy: Always draw AAed, but
+ // allow fallback to test/check and if needed. The normal case
+ // where BorderLines will be system-dependently snapped to have at
+ // least a single discrete width per partial line (there may be up to
+ // three) works well nowadays due to most renderers moving the AA stuff
+ // by 0.5 pixels (discrete units) to match well with the non-AAed parts.
+ //
+ // Env-Switch for steering this, default is off.
+ // Enable by setting at all (and to something)
+ static const char* pSwitchOffAntiAliasingForHorVerBorderlines(
+ getenv("SAL_SWITCH_OFF_ANTIALIASING_FOR_HOR_VER_BORTDERLINES"));
+ static bool bSwitchOffAntiAliasingForHorVerBorderlines(
+ nullptr != pSwitchOffAntiAliasingForHorVerBorderlines);
+
+ if (bSwitchOffAntiAliasingForHorVerBorderlines
+ && rBorder.isHorizontalOrVertical(getViewInformation2D()))
+ {
+ AntialiasingFlags nAntiAliasing = mpOutputDevice->GetAntialiasing();
+ mpOutputDevice->SetAntialiasing(nAntiAliasing & ~AntialiasingFlags::Enable);
+ process(rBorder);
+ mpOutputDevice->SetAntialiasing(nAntiAliasing);
+ }
+ else
+ {
+ process(rBorder);
+ }
+}
+
+void VclPixelProcessor2D::processInvertPrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
+{
+ // invert primitive (currently only used for HighContrast fallback for selection in SW and SC).
+ // (Not true, also used at least for the drawing of dragged column and row boundaries in SC.)
+ // Set OutDev to XOR and switch AA off (XOR does not work with AA)
+ mpOutputDevice->Push();
+ mpOutputDevice->SetRasterOp(RasterOp::Xor);
+ const AntialiasingFlags nAntiAliasing(mpOutputDevice->GetAntialiasing());
+ mpOutputDevice->SetAntialiasing(nAntiAliasing & ~AntialiasingFlags::Enable);
+
+ // process content recursively
+ process(rCandidate);
+
+ // restore OutDev
+ mpOutputDevice->Pop();
+ mpOutputDevice->SetAntialiasing(nAntiAliasing);
+}
+
+void VclPixelProcessor2D::processMetaFilePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
+{
+ // #i98289#
+ const bool bForceLineSnap(SvtOptionsDrawinglayer::IsAntiAliasing()
+ && SvtOptionsDrawinglayer::IsSnapHorVerLinesToDiscrete());
+ const AntialiasingFlags nOldAntiAliase(mpOutputDevice->GetAntialiasing());
+
+ if (bForceLineSnap)
+ {
+ mpOutputDevice->SetAntialiasing(nOldAntiAliase | AntialiasingFlags::PixelSnapHairline);
+ }
+
+ process(rCandidate);
+
+ if (bForceLineSnap)
+ {
+ mpOutputDevice->SetAntialiasing(nOldAntiAliase);
+ }
+}
+
+namespace
+{
+/* Returns 8-bit alpha mask created from passed mask.
+
+ Negative fErodeDilateRadius values mean erode, positive - dilate.
+ nTransparency defines minimal transparency level.
+*/
+AlphaMask ProcessAndBlurAlphaMask(const Bitmap& rMask, double fErodeDilateRadius,
+ double fBlurRadius, sal_uInt8 nTransparency,
+ bool bConvertTo1Bit = true)
+{
+ // Only completely white pixels on the initial mask must be considered for transparency. Any
+ // other color must be treated as black. This creates 1-bit B&W bitmap.
+ BitmapEx mask(bConvertTo1Bit ? rMask.CreateMask(COL_WHITE) : rMask);
+
+ // Scaling down increases performance without noticeable quality loss. Additionally,
+ // current blur implementation can only handle blur radius between 2 and 254.
+ Size aSize = mask.GetSizePixel();
+ double fScale = 1.0;
+ while (fBlurRadius > 254 || aSize.Height() > 1000 || aSize.Width() > 1000)
+ {
+ fScale /= 2;
+ fBlurRadius /= 2;
+ fErodeDilateRadius /= 2;
+ aSize.setHeight(aSize.Height() / 2);
+ aSize.setWidth(aSize.Width() / 2);
+ }
+
+ // BmpScaleFlag::Fast is important for following color replacement
+ mask.Scale(fScale, fScale, BmpScaleFlag::Fast);
+
+ if (fErodeDilateRadius > 0)
+ BitmapFilter::Filter(mask, BitmapDilateFilter(fErodeDilateRadius));
+ else if (fErodeDilateRadius < 0)
+ BitmapFilter::Filter(mask, BitmapErodeFilter(-fErodeDilateRadius, 0xFF));
+
+ if (nTransparency)
+ {
+ const Color aTransparency(nTransparency, nTransparency, nTransparency);
+ mask.Replace(COL_BLACK, aTransparency);
+ }
+
+ // We need 8-bit grey mask for blurring
+ mask.Convert(BmpConversion::N8BitGreys);
+
+ // calculate blurry effect
+ BitmapFilter::Filter(mask, BitmapFilterStackBlur(fBlurRadius));
+
+ mask.Scale(rMask.GetSizePixel());
+
+ return AlphaMask(mask.GetBitmap());
+}
+
+drawinglayer::geometry::ViewInformation2D
+expandRange(const drawinglayer::geometry::ViewInformation2D& rViewInfo, double nAmount)
+{
+ basegfx::B2DRange viewport(rViewInfo.getViewport());
+ viewport.grow(nAmount);
+ return { rViewInfo.getObjectTransformation(),
+ rViewInfo.getViewTransformation(),
+ viewport,
+ rViewInfo.getVisualizedPage(),
+ rViewInfo.getViewTime(),
+ rViewInfo.getReducedDisplayQuality() };
+}
+}
+
+void VclPixelProcessor2D::processGlowPrimitive2D(const primitive2d::GlowPrimitive2D& rCandidate)
+{
+ const double nGlowRadius(rCandidate.getGlowRadius());
+ // Avoid wrong effect on the cut-off side; so expand by radius
+ const auto aExpandedViewInfo(expandRange(getViewInformation2D(), nGlowRadius));
+ basegfx::B2DRange aRange(rCandidate.getB2DRange(aExpandedViewInfo));
+ aRange.transform(maCurrentTransformation);
+ basegfx::B2DVector aGlowRadiusVector(nGlowRadius, 0);
+ // Calculate the pixel size of glow radius in current transformation
+ aGlowRadiusVector *= maCurrentTransformation;
+ // Glow radius is the size of the halo from each side of the object. The halo is the
+ // border of glow color that fades from glow transparency level to fully transparent
+ // When blurring a sharp boundary (our case), it gets 50% of original intensity, and
+ // fades to both sides by the blur radius; thus blur radius is half of glow radius.
+ const double fBlurRadius = aGlowRadiusVector.getLength() / 2;
+ // Consider glow transparency (initial transparency near the object edge)
+ const sal_uInt8 nAlpha = rCandidate.getGlowColor().GetAlpha();
+
+ impBufferDevice aBufferDevice(*mpOutputDevice, aRange, false);
+ if (aBufferDevice.isVisible())
+ {
+ // remember last OutDev and set to content
+ OutputDevice* pLastOutputDevice = mpOutputDevice;
+ mpOutputDevice = &aBufferDevice.getContent();
+ // We don't need antialiased mask here, which would only make effect thicker
+ const auto aPrevAA = mpOutputDevice->GetAntialiasing();
+ mpOutputDevice->SetAntialiasing(AntialiasingFlags::NONE);
+ process(rCandidate);
+
+ // Limit the bitmap size to the visible area.
+ basegfx::B2DRange bitmapRange(aRange);
+ if (!aExpandedViewInfo.getDiscreteViewport().isEmpty())
+ bitmapRange.intersect(aExpandedViewInfo.getDiscreteViewport());
+ if (!bitmapRange.isEmpty())
+ {
+ const tools::Rectangle aRect(
+ static_cast<tools::Long>(std::floor(bitmapRange.getMinX())),
+ static_cast<tools::Long>(std::floor(bitmapRange.getMinY())),
+ static_cast<tools::Long>(std::ceil(bitmapRange.getMaxX())),
+ static_cast<tools::Long>(std::ceil(bitmapRange.getMaxY())));
+ BitmapEx bmpEx = mpOutputDevice->GetBitmapEx(aRect.TopLeft(), aRect.GetSize());
+ mpOutputDevice->SetAntialiasing(aPrevAA);
+
+ AlphaMask mask
+ = ProcessAndBlurAlphaMask(bmpEx.GetAlpha(), fBlurRadius, fBlurRadius, 255 - nAlpha);
+
+ // The end result is the bitmap filled with glow color and blurred 8-bit alpha mask
+ const basegfx::BColor aGlowColor(
+ maBColorModifierStack.getModifiedColor(rCandidate.getGlowColor().getBColor()));
+ Bitmap bmp = bmpEx.GetBitmap();
+ bmp.Erase(Color(aGlowColor));
+ BitmapEx result(bmp, mask);
+
+ // back to old OutDev
+ mpOutputDevice = pLastOutputDevice;
+ mpOutputDevice->DrawBitmapEx(aRect.TopLeft(), result);
+ }
+ else
+ {
+ mpOutputDevice = pLastOutputDevice;
+ }
+ }
+ else
+ SAL_WARN("drawinglayer", "Temporary buffered virtual device is not visible");
+}
+
+void VclPixelProcessor2D::processSoftEdgePrimitive2D(
+ const primitive2d::SoftEdgePrimitive2D& rCandidate)
+{
+ const double nRadius(rCandidate.getRadius());
+ // Avoid wrong effect on the cut-off side; so expand by diameter
+ const auto aExpandedViewInfo(expandRange(getViewInformation2D(), nRadius * 2));
+
+ basegfx::B2DRange aRange(rCandidate.getB2DRange(aExpandedViewInfo));
+ aRange.transform(maCurrentTransformation);
+ basegfx::B2DVector aRadiusVector(nRadius, 0);
+ // Calculate the pixel size of soft edge radius in current transformation
+ aRadiusVector *= maCurrentTransformation;
+ // Blur radius is equal to soft edge radius
+ const double fBlurRadius = aRadiusVector.getLength();
+
+ impBufferDevice aBufferDevice(*mpOutputDevice, aRange, false);
+ if (aBufferDevice.isVisible())
+ {
+ // remember last OutDev and set to content
+ OutputDevice* pLastOutputDevice = mpOutputDevice;
+ mpOutputDevice = &aBufferDevice.getContent();
+ // Since the effect converts all children to bitmap, we can't disable antialiasing here,
+ // because it would result in poor quality in areas not affected by the effect
+ process(rCandidate);
+
+ // Limit the bitmap size to the visible area.
+ basegfx::B2DRange bitmapRange(aRange);
+ if (!aExpandedViewInfo.getDiscreteViewport().isEmpty())
+ bitmapRange.intersect(aExpandedViewInfo.getDiscreteViewport());
+ if (!bitmapRange.isEmpty())
+ {
+ const tools::Rectangle aRect(
+ static_cast<tools::Long>(std::floor(bitmapRange.getMinX())),
+ static_cast<tools::Long>(std::floor(bitmapRange.getMinY())),
+ static_cast<tools::Long>(std::ceil(bitmapRange.getMaxX())),
+ static_cast<tools::Long>(std::ceil(bitmapRange.getMaxY())));
+ BitmapEx bitmap = mpOutputDevice->GetBitmapEx(aRect.TopLeft(), aRect.GetSize());
+
+ AlphaMask aMask = bitmap.GetAlpha();
+ AlphaMask blurMask = ProcessAndBlurAlphaMask(aMask, -fBlurRadius, fBlurRadius, 0);
+
+ aMask.BlendWith(blurMask);
+
+ // The end result is the original bitmap with blurred 8-bit alpha mask
+ BitmapEx result(bitmap.GetBitmap(), aMask);
+
+ // back to old OutDev
+ mpOutputDevice = pLastOutputDevice;
+ mpOutputDevice->DrawBitmapEx(aRect.TopLeft(), result);
+ }
+ else
+ {
+ mpOutputDevice = pLastOutputDevice;
+ }
+ }
+ else
+ SAL_WARN("drawinglayer", "Temporary buffered virtual device is not visible");
+}
+
+void VclPixelProcessor2D::processShadowPrimitive2D(const primitive2d::ShadowPrimitive2D& rCandidate)
+{
+ if (rCandidate.getShadowBlur() == 0)
+ {
+ process(rCandidate);
+ return;
+ }
+
+ basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D()));
+ aRange.transform(maCurrentTransformation);
+ basegfx::B2DVector aBlurRadiusVector(rCandidate.getShadowBlur(), 0);
+ aBlurRadiusVector *= maCurrentTransformation;
+ const double fBlurRadius = aBlurRadiusVector.getLength();
+
+ impBufferDevice aBufferDevice(*mpOutputDevice, aRange);
+ if (aBufferDevice.isVisible() && !aRange.isEmpty())
+ {
+ OutputDevice* pLastOutputDevice = mpOutputDevice;
+ mpOutputDevice = &aBufferDevice.getContent();
+
+ process(rCandidate);
+
+ const tools::Rectangle aRect(static_cast<tools::Long>(std::floor(aRange.getMinX())),
+ static_cast<tools::Long>(std::floor(aRange.getMinY())),
+ static_cast<tools::Long>(std::ceil(aRange.getMaxX())),
+ static_cast<tools::Long>(std::ceil(aRange.getMaxY())));
+
+ BitmapEx bitmapEx = mpOutputDevice->GetBitmapEx(aRect.TopLeft(), aRect.GetSize());
+
+ AlphaMask mask = ProcessAndBlurAlphaMask(bitmapEx.GetAlpha(), 0, fBlurRadius, 0, false);
+
+ const basegfx::BColor aShadowColor(
+ maBColorModifierStack.getModifiedColor(rCandidate.getShadowColor()));
+
+ Bitmap bitmap = bitmapEx.GetBitmap();
+ bitmap.Erase(Color(aShadowColor));
+ BitmapEx result(bitmap, mask);
+
+ mpOutputDevice = pLastOutputDevice;
+ mpOutputDevice->DrawBitmapEx(aRect.TopLeft(), result);
+ }
+ else
+ SAL_WARN("drawinglayer", "Temporary buffered virtual device is not visible");
+}
+
+void VclPixelProcessor2D::processFillGradientPrimitive2D(
+ const primitive2d::FillGradientPrimitive2D& rPrimitive)
+{
+ const attribute::FillGradientAttribute& rFillGradient = rPrimitive.getFillGradient();
+
+ // VCL should be able to handle all styles, but for tdf#133477 the VCL result
+ // is different from processing the gradient manually by drawinglayer
+ // (and the Writer unittest for it fails). Keep using the drawinglayer code
+ // until somebody founds out what's wrong and fixes it.
+ if (rFillGradient.getStyle() != drawinglayer::attribute::GradientStyle::Linear
+ && rFillGradient.getStyle() != drawinglayer::attribute::GradientStyle::Axial
+ && rFillGradient.getStyle() != drawinglayer::attribute::GradientStyle::Radial)
+ {
+ process(rPrimitive);
+ return;
+ }
+
+ // tdf#149754 VCL gradient draw is not capable to handle all primitive gradient definitions,
+ // what should be clear due to being developed to ectend/replace them in
+ // capabilities & precision.
+ // It is e.g. not capable to correctly paint if the OutputRange is not completely
+ // inside the DefinitionRange, thus forcing to paint gradent parts *outside* the
+ // DefinitionRange.
+ // This happens for Writer with Frames anchored in Frames (and was broken due to
+ // falling back to VCL Gradient paint here), and for the new SlideBackgroundFill
+ // Fill mode for objects using it that reach outside the page (which is the
+ // DefinitionRange in that case).
+ // I see no real reason to fallback here to OutputDevice::DrawGradient and VCL
+ // gradient paint at all (system-dependent renderers wouldn't in the future), but
+ // will for convenience only add that needed additional correcting case
+ if (!rPrimitive.getDefinitionRange().isInside(rPrimitive.getOutputRange()))
+ {
+ process(rPrimitive);
+ return;
+ }
+
+ GradientStyle eGradientStyle = convertGradientStyle(rFillGradient.getStyle());
+
+ Gradient aGradient(eGradientStyle, Color(rFillGradient.getStartColor()),
+ Color(rFillGradient.getEndColor()));
+
+ aGradient.SetAngle(Degree10(static_cast<int>(basegfx::rad2deg<10>(rFillGradient.getAngle()))));
+ aGradient.SetBorder(rFillGradient.getBorder() * 100);
+ aGradient.SetOfsX(rFillGradient.getOffsetX() * 100.0);
+ aGradient.SetOfsY(rFillGradient.getOffsetY() * 100.0);
+ aGradient.SetSteps(rFillGradient.getSteps());
+
+ basegfx::B2DRange aOutputRange(rPrimitive.getOutputRange());
+ aOutputRange.transform(maCurrentTransformation);
+ basegfx::B2DRange aFullRange(rPrimitive.getDefinitionRange());
+ aFullRange.transform(maCurrentTransformation);
+
+ const tools::Rectangle aOutputRectangle(
+ std::floor(aOutputRange.getMinX()), std::floor(aOutputRange.getMinY()),
+ std::ceil(aOutputRange.getMaxX()), std::ceil(aOutputRange.getMaxY()));
+ const tools::Rectangle aFullRectangle(
+ std::floor(aFullRange.getMinX()), std::floor(aFullRange.getMinY()),
+ std::ceil(aFullRange.getMaxX()), std::ceil(aFullRange.getMaxY()));
+
+ mpOutputDevice->Push(vcl::PushFlags::CLIPREGION);
+ mpOutputDevice->IntersectClipRegion(aOutputRectangle);
+ mpOutputDevice->DrawGradient(aFullRectangle, aGradient);
+ mpOutputDevice->Pop();
+}
+
+void VclPixelProcessor2D::processPatternFillPrimitive2D(
+ const primitive2d::PatternFillPrimitive2D& rPrimitive)
+{
+ const basegfx::B2DRange& rReferenceRange = rPrimitive.getReferenceRange();
+ if (rReferenceRange.isEmpty() || rReferenceRange.getWidth() <= 0.0
+ || rReferenceRange.getHeight() <= 0.0)
+ return;
+
+ basegfx::B2DPolyPolygon aMask = rPrimitive.getMask();
+ aMask.transform(maCurrentTransformation);
+ const basegfx::B2DRange aMaskRange(aMask.getB2DRange());
+
+ if (aMaskRange.isEmpty() || aMaskRange.getWidth() <= 0.0 || aMaskRange.getHeight() <= 0.0)
+ return;
+
+ sal_uInt32 nTileWidth, nTileHeight;
+ rPrimitive.getTileSize(nTileWidth, nTileHeight, getViewInformation2D());
+ if (nTileWidth == 0 || nTileHeight == 0)
+ return;
+ BitmapEx aTileImage = rPrimitive.createTileImage(nTileWidth, nTileHeight);
+ tools::Rectangle aMaskRect = vcl::unotools::rectangleFromB2DRectangle(aMaskRange);
+
+ // Unless smooth edges are needed, simply use clipping.
+ if (basegfx::utils::isRectangle(aMask) || !SvtOptionsDrawinglayer::IsAntiAliasing())
+ {
+ mpOutputDevice->Push(vcl::PushFlags::CLIPREGION);
+ mpOutputDevice->IntersectClipRegion(vcl::Region(aMask));
+ Wallpaper aWallpaper(aTileImage);
+ aWallpaper.SetColor(COL_TRANSPARENT);
+ mpOutputDevice->DrawWallpaper(aMaskRect, aWallpaper);
+ mpOutputDevice->Pop();
+ return;
+ }
+
+ impBufferDevice aBufferDevice(*mpOutputDevice, aMaskRange);
+
+ if (!aBufferDevice.isVisible())
+ return;
+
+ // remember last OutDev and set to content
+ OutputDevice* pLastOutputDevice = mpOutputDevice;
+ mpOutputDevice = &aBufferDevice.getContent();
+
+ // if the tile is a single pixel big, just flood fill with that pixel color
+ if (nTileWidth == 1 && nTileHeight == 1)
+ {
+ Color col = aTileImage.GetPixelColor(0, 0);
+ mpOutputDevice->SetLineColor(col);
+ mpOutputDevice->SetFillColor(col);
+ mpOutputDevice->DrawRect(aMaskRect);
+ }
+ else
+ {
+ Wallpaper aWallpaper(aTileImage);
+ aWallpaper.SetColor(COL_TRANSPARENT);
+ mpOutputDevice->DrawWallpaper(aMaskRect, aWallpaper);
+ }
+
+ // back to old OutDev
+ mpOutputDevice = pLastOutputDevice;
+
+ // draw mask
+ VirtualDevice& rMask = aBufferDevice.getTransparence();
+ rMask.SetLineColor();
+ rMask.SetFillColor(COL_BLACK);
+ rMask.DrawPolyPolygon(aMask);
+
+ // dump buffer to outdev
+ aBufferDevice.paint();
+}
+
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx b/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx
new file mode 100644
index 000000000..eaf212c8e
--- /dev/null
+++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx
@@ -0,0 +1,116 @@
+/* -*- 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 "vclprocessor2d.hxx"
+#include <vcl/outdev.hxx>
+
+#include <memory>
+
+namespace drawinglayer::primitive2d
+{
+class PolyPolygonColorPrimitive2D;
+class PolygonHairlinePrimitive2D;
+class PolygonStrokePrimitive2D;
+class WrongSpellPrimitive2D;
+class TextSimplePortionPrimitive;
+class BitmapPrimitive2D;
+class PolyPolygonGradientPrimitive2D;
+class UnifiedTransparencePrimitive2D;
+class ControlPrimitive2D;
+class PolygonStrokePrimitive2D;
+class FillHatchPrimitive2D;
+class BackgroundColorPrimitive2D;
+class BorderLinePrimitive2D;
+class GlowPrimitive2D;
+class ShadowPrimitive2D;
+class SoftEdgePrimitive2D;
+class FillGradientPrimitive2D;
+class PatternFillPrimitive2D;
+}
+
+namespace drawinglayer::processor2d
+{
+/** VclPixelProcessor2D class
+
+ This processor derived from VclProcessor2D is the base class for rendering
+ all fed primitives to a VCL Window. It is the currently used renderer
+ for all VCL editing output from the DrawingLayer.
+ */
+class VclPixelProcessor2D final : public VclProcessor2D
+{
+ struct Impl;
+ std::unique_ptr<Impl> m_pImpl;
+
+ /* the local processor for BasePrimitive2D-Implementation based primitives,
+ called from the common process()-implementation
+ */
+ virtual void processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) override;
+
+ // some helpers to try direct paints (shortcuts)
+ void tryDrawPolyPolygonColorPrimitive2DDirect(
+ const primitive2d::PolyPolygonColorPrimitive2D& rSource, double fTransparency);
+ bool
+ tryDrawPolygonHairlinePrimitive2DDirect(const primitive2d::PolygonHairlinePrimitive2D& rSource,
+ double fTransparency);
+ bool tryDrawPolygonStrokePrimitive2DDirect(const primitive2d::PolygonStrokePrimitive2D& rSource,
+ double fTransparency);
+
+ void
+ processWrongSpellPrimitive2D(const primitive2d::WrongSpellPrimitive2D& rWrongSpellPrimitive);
+ void processTextSimplePortionPrimitive2D(
+ const primitive2d::TextSimplePortionPrimitive2D& rCandidate);
+ void processTextDecoratedPortionPrimitive2D(
+ const primitive2d::TextSimplePortionPrimitive2D& rCandidate);
+ void processPolygonHairlinePrimitive2D(
+ const primitive2d::PolygonHairlinePrimitive2D& rPolygonHairlinePrimitive2D);
+ void processBitmapPrimitive2D(const primitive2d::BitmapPrimitive2D& rBitmapCandidate);
+ void processPolyPolygonGradientPrimitive2D(
+ const primitive2d::PolyPolygonGradientPrimitive2D& rPolygonCandidate);
+ void processPolyPolygonColorPrimitive2D(
+ const primitive2d::PolyPolygonColorPrimitive2D& rPolyPolygonColorPrimitive2D);
+ void processUnifiedTransparencePrimitive2D(
+ const primitive2d::UnifiedTransparencePrimitive2D& rUniTransparenceCandidate);
+ void processControlPrimitive2D(const primitive2d::ControlPrimitive2D& rControlPrimitive);
+ void processPolygonStrokePrimitive2D(
+ const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokePrimitive2D);
+ void processFillHatchPrimitive2D(const primitive2d::FillHatchPrimitive2D& rFillHatchPrimitive);
+ void
+ processBackgroundColorPrimitive2D(const primitive2d::BackgroundColorPrimitive2D& rPrimitive);
+ void
+ processBorderLinePrimitive2D(const drawinglayer::primitive2d::BorderLinePrimitive2D& rBorder);
+ void processInvertPrimitive2D(const primitive2d::BasePrimitive2D& rCandidate);
+ void processMetaFilePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate);
+ void processGlowPrimitive2D(const primitive2d::GlowPrimitive2D& rCandidate);
+ void processSoftEdgePrimitive2D(const primitive2d::SoftEdgePrimitive2D& rCandidate);
+ void processShadowPrimitive2D(const primitive2d::ShadowPrimitive2D& rCandidate);
+ void processFillGradientPrimitive2D(const primitive2d::FillGradientPrimitive2D& rPrimitive);
+ void processPatternFillPrimitive2D(const primitive2d::PatternFillPrimitive2D& rPrimitive);
+
+public:
+ /// constructor/destructor
+ VclPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation, OutputDevice& rOutDev,
+ const basegfx::BColorModifierStack& rInitStack
+ = basegfx::BColorModifierStack());
+ virtual ~VclPixelProcessor2D() override;
+};
+} // end of namespace drawinglayer::processor2d
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/vclprocessor2d.cxx b/drawinglayer/source/processor2d/vclprocessor2d.cxx
new file mode 100644
index 000000000..b77c1d9c0
--- /dev/null
+++ b/drawinglayer/source/processor2d/vclprocessor2d.cxx
@@ -0,0 +1,1480 @@
+/* -*- 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 "vclprocessor2d.hxx"
+
+#include "getdigitlanguage.hxx"
+#include "vclhelperbufferdevice.hxx"
+#include <cmath>
+#include <comphelper/string.hxx>
+#include <svtools/optionsdrawinglayer.hxx>
+#include <tools/debug.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/outdev.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b2dpolygonclipper.hxx>
+#include <basegfx/color/bcolor.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
+#include <drawinglayer/primitive2d/textprimitive2d.hxx>
+#include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
+#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
+#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx>
+#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
+#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
+#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
+#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx>
+#include <drawinglayer/primitive2d/pagepreviewprimitive2d.hxx>
+#include <drawinglayer/primitive2d/textenumsprimitive2d.hxx>
+#include <drawinglayer/primitive2d/svggradientprimitive2d.hxx>
+// for support of Title/Description in all apps when embedding pictures
+#include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx>
+// control support
+#include <drawinglayer/primitive2d/textlayoutdevice.hxx>
+
+#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
+#include <drawinglayer/primitive2d/epsprimitive2d.hxx>
+
+using namespace com::sun::star;
+
+namespace
+{
+sal_uInt32 calculateStepsForSvgGradient(const basegfx::BColor& rColorA,
+ const basegfx::BColor& rColorB, double fDelta,
+ double fDiscreteUnit)
+{
+ // use color distance, assume to do every color step
+ sal_uInt32 nSteps(basegfx::fround(rColorA.getDistance(rColorB) * 255.0));
+
+ if (nSteps)
+ {
+ // calc discrete length to change color each discrete unit (pixel)
+ const sal_uInt32 nDistSteps(basegfx::fround(fDelta / fDiscreteUnit));
+
+ nSteps = std::min(nSteps, nDistSteps);
+ }
+
+ // reduce quality to 3 discrete units or every 3rd color step for rendering
+ nSteps /= 2;
+
+ // roughly cut when too big or too small (not full quality, reduce complexity)
+ nSteps = std::min(nSteps, sal_uInt32(255));
+ nSteps = std::max(nSteps, sal_uInt32(1));
+
+ return nSteps;
+}
+}
+
+namespace drawinglayer::processor2d
+{
+// rendering support
+
+// directdraw of text simple portion or decorated portion primitive. When decorated, all the extra
+// information is translated to VCL parameters and set at the font.
+// Acceptance is restricted to no shearing and positive scaling in X and Y (no font mirroring
+// for VCL)
+void VclProcessor2D::RenderTextSimpleOrDecoratedPortionPrimitive2D(
+ const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate)
+{
+ // decompose matrix to have position and size of text
+ basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation
+ * rTextCandidate.getTextTransform());
+ basegfx::B2DVector aFontScaling, aTranslate;
+ double fRotate, fShearX;
+ aLocalTransform.decompose(aFontScaling, aTranslate, fRotate, fShearX);
+ bool bPrimitiveAccepted(false);
+
+ // tdf#95581: Assume tiny shears are rounding artefacts or whatever and can be ignored,
+ // especially if the effect is less than a pixel.
+ if (std::abs(aFontScaling.getY() * fShearX) < 1)
+ {
+ if (basegfx::fTools::less(aFontScaling.getX(), 0.0)
+ && basegfx::fTools::less(aFontScaling.getY(), 0.0))
+ {
+ // handle special case: If scale is negative in (x,y) (3rd quadrant), it can
+ // be expressed as rotation by PI. Use this since the Font rendering will not
+ // apply the negative scales in any form
+ aFontScaling = basegfx::absolute(aFontScaling);
+ fRotate += M_PI;
+ }
+
+ if (basegfx::fTools::more(aFontScaling.getX(), 0.0)
+ && basegfx::fTools::more(aFontScaling.getY(), 0.0))
+ {
+ // Get the VCL font (use FontHeight as FontWidth)
+ vcl::Font aFont(primitive2d::getVclFontFromFontAttribute(
+ rTextCandidate.getFontAttribute(), aFontScaling.getX(), aFontScaling.getY(),
+ fRotate, rTextCandidate.getLocale()));
+
+ // Don't draw fonts without height
+ if (aFont.GetFontHeight() <= 0)
+ return;
+
+ // set FillColor Attribute
+ const Color aFillColor(rTextCandidate.getTextFillColor());
+ if (aFillColor != COL_TRANSPARENT)
+ {
+ aFont.SetFillColor(aFillColor);
+ aFont.SetTransparent(false);
+ }
+
+ // handle additional font attributes
+ const primitive2d::TextDecoratedPortionPrimitive2D* pTCPP = nullptr;
+ if (rTextCandidate.getPrimitive2DID() == PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D)
+ pTCPP = static_cast<const primitive2d::TextDecoratedPortionPrimitive2D*>(
+ &rTextCandidate);
+
+ if (pTCPP != nullptr)
+ {
+ // set the color of text decorations
+ const basegfx::BColor aTextlineColor
+ = maBColorModifierStack.getModifiedColor(pTCPP->getTextlineColor());
+ mpOutputDevice->SetTextLineColor(Color(aTextlineColor));
+
+ // set Overline attribute
+ const FontLineStyle eFontOverline(
+ primitive2d::mapTextLineToFontLineStyle(pTCPP->getFontOverline()));
+ if (eFontOverline != LINESTYLE_NONE)
+ {
+ aFont.SetOverline(eFontOverline);
+ const basegfx::BColor aOverlineColor
+ = maBColorModifierStack.getModifiedColor(pTCPP->getOverlineColor());
+ mpOutputDevice->SetOverlineColor(Color(aOverlineColor));
+ if (pTCPP->getWordLineMode())
+ aFont.SetWordLineMode(true);
+ }
+
+ // set Underline attribute
+ const FontLineStyle eFontLineStyle(
+ primitive2d::mapTextLineToFontLineStyle(pTCPP->getFontUnderline()));
+ if (eFontLineStyle != LINESTYLE_NONE)
+ {
+ aFont.SetUnderline(eFontLineStyle);
+ if (pTCPP->getWordLineMode())
+ aFont.SetWordLineMode(true);
+ }
+
+ // set Strikeout attribute
+ const FontStrikeout eFontStrikeout(
+ primitive2d::mapTextStrikeoutToFontStrikeout(pTCPP->getTextStrikeout()));
+
+ if (eFontStrikeout != STRIKEOUT_NONE)
+ aFont.SetStrikeout(eFontStrikeout);
+
+ // set EmphasisMark attribute
+ FontEmphasisMark eFontEmphasisMark = FontEmphasisMark::NONE;
+ switch (pTCPP->getTextEmphasisMark())
+ {
+ default:
+ SAL_WARN("drawinglayer",
+ "Unknown EmphasisMark style " << pTCPP->getTextEmphasisMark());
+ [[fallthrough]];
+ case primitive2d::TEXT_FONT_EMPHASIS_MARK_NONE:
+ eFontEmphasisMark = FontEmphasisMark::NONE;
+ break;
+ case primitive2d::TEXT_FONT_EMPHASIS_MARK_DOT:
+ eFontEmphasisMark = FontEmphasisMark::Dot;
+ break;
+ case primitive2d::TEXT_FONT_EMPHASIS_MARK_CIRCLE:
+ eFontEmphasisMark = FontEmphasisMark::Circle;
+ break;
+ case primitive2d::TEXT_FONT_EMPHASIS_MARK_DISC:
+ eFontEmphasisMark = FontEmphasisMark::Disc;
+ break;
+ case primitive2d::TEXT_FONT_EMPHASIS_MARK_ACCENT:
+ eFontEmphasisMark = FontEmphasisMark::Accent;
+ break;
+ }
+
+ if (eFontEmphasisMark != FontEmphasisMark::NONE)
+ {
+ DBG_ASSERT((pTCPP->getEmphasisMarkAbove() != pTCPP->getEmphasisMarkBelow()),
+ "DrawingLayer: Bad EmphasisMark position!");
+ if (pTCPP->getEmphasisMarkAbove())
+ eFontEmphasisMark |= FontEmphasisMark::PosAbove;
+ else
+ eFontEmphasisMark |= FontEmphasisMark::PosBelow;
+ aFont.SetEmphasisMark(eFontEmphasisMark);
+ }
+
+ // set Relief attribute
+ FontRelief eFontRelief = FontRelief::NONE;
+ switch (pTCPP->getTextRelief())
+ {
+ default:
+ SAL_WARN("drawinglayer", "Unknown Relief style " << pTCPP->getTextRelief());
+ [[fallthrough]];
+ case primitive2d::TEXT_RELIEF_NONE:
+ eFontRelief = FontRelief::NONE;
+ break;
+ case primitive2d::TEXT_RELIEF_EMBOSSED:
+ eFontRelief = FontRelief::Embossed;
+ break;
+ case primitive2d::TEXT_RELIEF_ENGRAVED:
+ eFontRelief = FontRelief::Engraved;
+ break;
+ }
+
+ if (eFontRelief != FontRelief::NONE)
+ aFont.SetRelief(eFontRelief);
+
+ // set Shadow attribute
+ if (pTCPP->getShadow())
+ aFont.SetShadow(true);
+ }
+
+ // create transformed integer DXArray in view coordinate system
+ std::vector<sal_Int32> aTransformedDXArray;
+
+ if (!rTextCandidate.getDXArray().empty())
+ {
+ aTransformedDXArray.reserve(rTextCandidate.getDXArray().size());
+ const basegfx::B2DVector aPixelVector(maCurrentTransformation
+ * basegfx::B2DVector(1.0, 0.0));
+ const double fPixelVectorFactor(aPixelVector.getLength());
+
+ for (auto const& elem : rTextCandidate.getDXArray())
+ {
+ aTransformedDXArray.push_back(basegfx::fround(elem * fPixelVectorFactor));
+ }
+ }
+
+ // set parameters and paint text snippet
+ const basegfx::BColor aRGBFontColor(
+ maBColorModifierStack.getModifiedColor(rTextCandidate.getFontColor()));
+ const basegfx::B2DPoint aPoint(aLocalTransform * basegfx::B2DPoint(0.0, 0.0));
+ const Point aStartPoint(basegfx::fround(aPoint.getX()), basegfx::fround(aPoint.getY()));
+ const vcl::text::ComplexTextLayoutFlags nOldLayoutMode(mpOutputDevice->GetLayoutMode());
+
+ if (rTextCandidate.getFontAttribute().getRTL())
+ {
+ vcl::text::ComplexTextLayoutFlags nRTLLayoutMode(
+ nOldLayoutMode & ~vcl::text::ComplexTextLayoutFlags::BiDiStrong);
+ nRTLLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl
+ | vcl::text::ComplexTextLayoutFlags::TextOriginLeft;
+ mpOutputDevice->SetLayoutMode(nRTLLayoutMode);
+ }
+
+ mpOutputDevice->SetFont(aFont);
+ mpOutputDevice->SetTextColor(Color(aRGBFontColor));
+
+ OUString aText(rTextCandidate.getText());
+ sal_Int32 nPos = rTextCandidate.getTextPosition();
+ sal_Int32 nLen = rTextCandidate.getTextLength();
+
+ if (rTextCandidate.isFilled())
+ {
+ basegfx::B2DVector aOldFontScaling, aOldTranslate;
+ double fOldRotate, fOldShearX;
+ rTextCandidate.getTextTransform().decompose(aOldFontScaling, aOldTranslate,
+ fOldRotate, fOldShearX);
+
+ tools::Long nWidthToFill = static_cast<tools::Long>(
+ rTextCandidate.getWidthToFill() * aFontScaling.getX() / aOldFontScaling.getX());
+
+ tools::Long nWidth = mpOutputDevice->GetTextArray(rTextCandidate.getText(),
+ &aTransformedDXArray, 0, 1);
+ sal_Int32 nChars = 2;
+ if (nWidth)
+ nChars = nWidthToFill / nWidth;
+
+ OUStringBuffer aFilled(nChars);
+ comphelper::string::padToLength(aFilled, nChars, aText[0]);
+ aText = aFilled.makeStringAndClear();
+ nPos = 0;
+ nLen = nChars;
+
+ if (!aTransformedDXArray.empty())
+ {
+ sal_Int32 nDX = aTransformedDXArray[0];
+ aTransformedDXArray.resize(nLen);
+ for (sal_Int32 i = 1; i < nLen; ++i)
+ aTransformedDXArray[i] = aTransformedDXArray[i - 1] + nDX;
+ }
+ }
+
+ if (!aTransformedDXArray.empty())
+ {
+ mpOutputDevice->DrawTextArray(aStartPoint, aText, aTransformedDXArray, nPos, nLen);
+ }
+ else
+ {
+ mpOutputDevice->DrawText(aStartPoint, aText, nPos, nLen);
+ }
+
+ if (rTextCandidate.getFontAttribute().getRTL())
+ {
+ mpOutputDevice->SetLayoutMode(nOldLayoutMode);
+ }
+
+ bPrimitiveAccepted = true;
+ }
+ }
+
+ if (!bPrimitiveAccepted)
+ {
+ // let break down
+ process(rTextCandidate);
+ }
+}
+
+// direct draw of hairline
+void VclProcessor2D::RenderPolygonHairlinePrimitive2D(
+ const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate, bool bPixelBased)
+{
+ const basegfx::BColor aHairlineColor(
+ maBColorModifierStack.getModifiedColor(rPolygonCandidate.getBColor()));
+ mpOutputDevice->SetLineColor(Color(aHairlineColor));
+ mpOutputDevice->SetFillColor();
+
+ basegfx::B2DPolygon aLocalPolygon(rPolygonCandidate.getB2DPolygon());
+ aLocalPolygon.transform(maCurrentTransformation);
+
+ if (bPixelBased && SvtOptionsDrawinglayer::IsAntiAliasing()
+ && SvtOptionsDrawinglayer::IsSnapHorVerLinesToDiscrete())
+ {
+ // #i98289#
+ // when a Hairline is painted and AntiAliasing is on the option SnapHorVerLinesToDiscrete
+ // allows to suppress AntiAliasing for pure horizontal or vertical lines. This is done since
+ // not-AntiAliased such lines look more pleasing to the eye (e.g. 2D chart content). This
+ // NEEDS to be done in discrete coordinates, so only useful for pixel based rendering.
+ aLocalPolygon = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aLocalPolygon);
+ }
+
+ mpOutputDevice->DrawPolyLine(aLocalPolygon, 0.0);
+}
+
+// direct draw of transformed BitmapEx primitive
+void VclProcessor2D::RenderBitmapPrimitive2D(const primitive2d::BitmapPrimitive2D& rBitmapCandidate)
+{
+ BitmapEx aBitmapEx(VCLUnoHelper::GetBitmap(rBitmapCandidate.getXBitmap()));
+ const basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation
+ * rBitmapCandidate.getTransform());
+
+ if (maBColorModifierStack.count())
+ {
+ aBitmapEx = aBitmapEx.ModifyBitmapEx(maBColorModifierStack);
+
+ if (aBitmapEx.IsEmpty())
+ {
+ // color gets completely replaced, get it
+ const basegfx::BColor aModifiedColor(
+ maBColorModifierStack.getModifiedColor(basegfx::BColor()));
+ basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon());
+ aPolygon.transform(aLocalTransform);
+
+ mpOutputDevice->SetFillColor(Color(aModifiedColor));
+ mpOutputDevice->SetLineColor();
+ mpOutputDevice->DrawPolygon(aPolygon);
+
+ return;
+ }
+ }
+
+ // #122923# do no longer add Alpha channel here; the right place to do this is when really
+ // the own transformer is used (see OutputDevice::DrawTransformedBitmapEx).
+
+ // draw using OutputDevice'sDrawTransformedBitmapEx
+ mpOutputDevice->DrawTransformedBitmapEx(aLocalTransform, aBitmapEx);
+}
+
+void VclProcessor2D::RenderFillGraphicPrimitive2D(
+ const primitive2d::FillGraphicPrimitive2D& rFillBitmapCandidate)
+{
+ const attribute::FillGraphicAttribute& rFillGraphicAttribute(
+ rFillBitmapCandidate.getFillGraphic());
+ bool bPrimitiveAccepted(false);
+
+ // #121194# when tiling is used and content is bitmap-based, do direct tiling in the
+ // renderer on pixel base to ensure tight fitting. Do not do this when
+ // the fill is rotated or sheared.
+ if (rFillGraphicAttribute.getTiling())
+ {
+ // content is bitmap(ex)
+ //
+ // for Vector Graphic Data (SVG, EMF+) support, force decomposition when present. This will lead to use
+ // the primitive representation of the vector data directly.
+ //
+ // when graphic is animated, force decomposition to use the correct graphic, else
+ // fill style will not be animated
+ if (GraphicType::Bitmap == rFillGraphicAttribute.getGraphic().GetType()
+ && !rFillGraphicAttribute.getGraphic().getVectorGraphicData()
+ && !rFillGraphicAttribute.getGraphic().IsAnimated())
+ {
+ // decompose matrix to check for shear, rotate and mirroring
+ basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation
+ * rFillBitmapCandidate.getTransformation());
+ basegfx::B2DVector aScale, aTranslate;
+ double fRotate, fShearX;
+ aLocalTransform.decompose(aScale, aTranslate, fRotate, fShearX);
+
+ // when nopt rotated/sheared
+ if (basegfx::fTools::equalZero(fRotate) && basegfx::fTools::equalZero(fShearX))
+ {
+ // no shear or rotate, draw direct in pixel coordinates
+ bPrimitiveAccepted = true;
+
+ // transform object range to device coordinates (pixels). Use
+ // the device transformation for better accuracy
+ basegfx::B2DRange aObjectRange(aTranslate, aTranslate + aScale);
+ aObjectRange.transform(mpOutputDevice->GetViewTransformation());
+
+ // extract discrete size of object
+ const sal_Int32 nOWidth(basegfx::fround(aObjectRange.getWidth()));
+ const sal_Int32 nOHeight(basegfx::fround(aObjectRange.getHeight()));
+
+ // only do something when object has a size in discrete units
+ if (nOWidth > 0 && nOHeight > 0)
+ {
+ // transform graphic range to device coordinates (pixels). Use
+ // the device transformation for better accuracy
+ basegfx::B2DRange aGraphicRange(rFillGraphicAttribute.getGraphicRange());
+ aGraphicRange.transform(mpOutputDevice->GetViewTransformation()
+ * aLocalTransform);
+
+ // extract discrete size of graphic
+ // caution: when getting to zero, nothing would be painted; thus, do not allow this
+ const sal_Int32 nBWidth(
+ std::max(sal_Int32(1), basegfx::fround(aGraphicRange.getWidth())));
+ const sal_Int32 nBHeight(
+ std::max(sal_Int32(1), basegfx::fround(aGraphicRange.getHeight())));
+
+ // only do something when bitmap fill has a size in discrete units
+ if (nBWidth > 0 && nBHeight > 0)
+ {
+ // nBWidth, nBHeight is the pixel size of the needed bitmap. To not need to scale it
+ // in vcl many times, create a size-optimized version
+ const Size aNeededBitmapSizePixel(nBWidth, nBHeight);
+ BitmapEx aBitmapEx(rFillGraphicAttribute.getGraphic().GetBitmapEx());
+ const bool bPreScaled(nBWidth * nBHeight < (250 * 250));
+
+ // ... but only up to a maximum size, else it gets too expensive
+ if (bPreScaled)
+ {
+ // if color depth is below 24bit, expand before scaling for better quality.
+ // This is even needed for low colors, else the scale will produce
+ // a bitmap in gray or Black/White (!)
+ if (isPalettePixelFormat(aBitmapEx.getPixelFormat()))
+ {
+ aBitmapEx.Convert(BmpConversion::N24Bit);
+ }
+
+ aBitmapEx.Scale(aNeededBitmapSizePixel, BmpScaleFlag::Interpolate);
+ }
+
+ bool bPainted(false);
+
+ if (maBColorModifierStack.count())
+ {
+ // when color modifier, apply to bitmap
+ aBitmapEx = aBitmapEx.ModifyBitmapEx(maBColorModifierStack);
+
+ // impModifyBitmapEx uses empty bitmap as sign to return that
+ // the content will be completely replaced to mono color, use shortcut
+ if (aBitmapEx.IsEmpty())
+ {
+ // color gets completely replaced, get it
+ const basegfx::BColor aModifiedColor(
+ maBColorModifierStack.getModifiedColor(basegfx::BColor()));
+ basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon());
+ aPolygon.transform(aLocalTransform);
+
+ mpOutputDevice->SetFillColor(Color(aModifiedColor));
+ mpOutputDevice->SetLineColor();
+ mpOutputDevice->DrawPolygon(aPolygon);
+
+ bPainted = true;
+ }
+ }
+
+ if (!bPainted)
+ {
+ sal_Int32 nBLeft(basegfx::fround(aGraphicRange.getMinX()));
+ sal_Int32 nBTop(basegfx::fround(aGraphicRange.getMinY()));
+ const sal_Int32 nOLeft(basegfx::fround(aObjectRange.getMinX()));
+ const sal_Int32 nOTop(basegfx::fround(aObjectRange.getMinY()));
+ sal_Int32 nPosX(0);
+ sal_Int32 nPosY(0);
+
+ if (nBLeft > nOLeft)
+ {
+ const sal_Int32 nDiff((nBLeft / nBWidth) + 1);
+
+ nPosX -= nDiff;
+ nBLeft -= nDiff * nBWidth;
+ }
+
+ if (nBLeft + nBWidth <= nOLeft)
+ {
+ const sal_Int32 nDiff(-nBLeft / nBWidth);
+
+ nPosX += nDiff;
+ nBLeft += nDiff * nBWidth;
+ }
+
+ if (nBTop > nOTop)
+ {
+ const sal_Int32 nDiff((nBTop / nBHeight) + 1);
+
+ nPosY -= nDiff;
+ nBTop -= nDiff * nBHeight;
+ }
+
+ if (nBTop + nBHeight <= nOTop)
+ {
+ const sal_Int32 nDiff(-nBTop / nBHeight);
+
+ nPosY += nDiff;
+ nBTop += nDiff * nBHeight;
+ }
+
+ // prepare OutDev
+ const Point aEmptyPoint(0, 0);
+ const ::tools::Rectangle aVisiblePixel(
+ aEmptyPoint, mpOutputDevice->GetOutputSizePixel());
+ const bool bWasEnabled(mpOutputDevice->IsMapModeEnabled());
+ mpOutputDevice->EnableMapMode(false);
+
+ // check if offset is used
+ const sal_Int32 nOffsetX(
+ basegfx::fround(rFillGraphicAttribute.getOffsetX() * nBWidth));
+
+ if (nOffsetX)
+ {
+ // offset in X, so iterate over Y first and draw lines
+ for (sal_Int32 nYPos(nBTop); nYPos < nOTop + nOHeight;
+ nYPos += nBHeight, nPosY++)
+ {
+ for (sal_Int32 nXPos((nPosY % 2) ? nBLeft - nBWidth + nOffsetX
+ : nBLeft);
+ nXPos < nOLeft + nOWidth; nXPos += nBWidth)
+ {
+ const ::tools::Rectangle aOutRectPixel(
+ Point(nXPos, nYPos), aNeededBitmapSizePixel);
+
+ if (aOutRectPixel.Overlaps(aVisiblePixel))
+ {
+ if (bPreScaled)
+ {
+ mpOutputDevice->DrawBitmapEx(
+ aOutRectPixel.TopLeft(), aBitmapEx);
+ }
+ else
+ {
+ mpOutputDevice->DrawBitmapEx(
+ aOutRectPixel.TopLeft(), aNeededBitmapSizePixel,
+ aBitmapEx);
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ // check if offset is used
+ const sal_Int32 nOffsetY(
+ basegfx::fround(rFillGraphicAttribute.getOffsetY() * nBHeight));
+
+ // possible offset in Y, so iterate over X first and draw columns
+ for (sal_Int32 nXPos(nBLeft); nXPos < nOLeft + nOWidth;
+ nXPos += nBWidth, nPosX++)
+ {
+ for (sal_Int32 nYPos((nPosX % 2) ? nBTop - nBHeight + nOffsetY
+ : nBTop);
+ nYPos < nOTop + nOHeight; nYPos += nBHeight)
+ {
+ const ::tools::Rectangle aOutRectPixel(
+ Point(nXPos, nYPos), aNeededBitmapSizePixel);
+
+ if (aOutRectPixel.Overlaps(aVisiblePixel))
+ {
+ if (bPreScaled)
+ {
+ mpOutputDevice->DrawBitmapEx(
+ aOutRectPixel.TopLeft(), aBitmapEx);
+ }
+ else
+ {
+ mpOutputDevice->DrawBitmapEx(
+ aOutRectPixel.TopLeft(), aNeededBitmapSizePixel,
+ aBitmapEx);
+ }
+ }
+ }
+ }
+ }
+
+ // restore OutDev
+ mpOutputDevice->EnableMapMode(bWasEnabled);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (!bPrimitiveAccepted)
+ {
+ // do not accept, use decomposition
+ process(rFillBitmapCandidate);
+ }
+}
+
+// direct draw of Graphic
+void VclProcessor2D::RenderPolyPolygonGraphicPrimitive2D(
+ const primitive2d::PolyPolygonGraphicPrimitive2D& rPolygonCandidate)
+{
+ bool bDone(false);
+ const basegfx::B2DPolyPolygon& rPolyPolygon = rPolygonCandidate.getB2DPolyPolygon();
+
+ // #121194# Todo: check if this works
+ if (!rPolyPolygon.count())
+ {
+ // empty polyPolygon, done
+ bDone = true;
+ }
+ else
+ {
+ const attribute::FillGraphicAttribute& rFillGraphicAttribute
+ = rPolygonCandidate.getFillGraphic();
+
+ // try to catch cases where the graphic will be color-modified to a single
+ // color (e.g. shadow)
+ switch (rFillGraphicAttribute.getGraphic().GetType())
+ {
+ case GraphicType::GdiMetafile:
+ {
+ // metafiles are potentially transparent, cannot optimize, not done
+ break;
+ }
+ case GraphicType::Bitmap:
+ {
+ if (!rFillGraphicAttribute.getGraphic().IsTransparent()
+ && !rFillGraphicAttribute.getGraphic().IsAlpha())
+ {
+ // bitmap is not transparent and has no alpha
+ const sal_uInt32 nBColorModifierStackCount(maBColorModifierStack.count());
+
+ if (nBColorModifierStackCount)
+ {
+ const basegfx::BColorModifierSharedPtr& rTopmostModifier
+ = maBColorModifierStack.getBColorModifier(nBColorModifierStackCount
+ - 1);
+ const basegfx::BColorModifier_replace* pReplacer
+ = dynamic_cast<const basegfx::BColorModifier_replace*>(
+ rTopmostModifier.get());
+
+ if (pReplacer)
+ {
+ // the bitmap fill is in unified color, so we can replace it with
+ // a single polygon fill. The form of the fill depends on tiling
+ if (rFillGraphicAttribute.getTiling())
+ {
+ // with tiling, fill the whole tools::PolyPolygon with the modifier color
+ basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolyPolygon);
+
+ aLocalPolyPolygon.transform(maCurrentTransformation);
+ mpOutputDevice->SetLineColor();
+ mpOutputDevice->SetFillColor(Color(pReplacer->getBColor()));
+ mpOutputDevice->DrawPolyPolygon(aLocalPolyPolygon);
+ }
+ else
+ {
+ // without tiling, only the area common to the bitmap tile and the
+ // tools::PolyPolygon is filled. Create the bitmap tile area in object
+ // coordinates. For this, the object transformation needs to be created
+ // from the already scaled PolyPolygon. The tile area in object
+ // coordinates will always be non-rotated, so it's not necessary to
+ // work with a polygon here
+ basegfx::B2DRange aTileRange(
+ rFillGraphicAttribute.getGraphicRange());
+ const basegfx::B2DRange aPolyPolygonRange(
+ rPolyPolygon.getB2DRange());
+ const basegfx::B2DHomMatrix aNewObjectTransform(
+ basegfx::utils::createScaleTranslateB2DHomMatrix(
+ aPolyPolygonRange.getRange(),
+ aPolyPolygonRange.getMinimum()));
+
+ aTileRange.transform(aNewObjectTransform);
+
+ // now clip the object polyPolygon against the tile range
+ // to get the common area
+ basegfx::B2DPolyPolygon aTarget
+ = basegfx::utils::clipPolyPolygonOnRange(
+ rPolyPolygon, aTileRange, true, false);
+
+ if (aTarget.count())
+ {
+ aTarget.transform(maCurrentTransformation);
+ mpOutputDevice->SetLineColor();
+ mpOutputDevice->SetFillColor(Color(pReplacer->getBColor()));
+ mpOutputDevice->DrawPolyPolygon(aTarget);
+ }
+ }
+
+ // simplified output executed, we are done
+ bDone = true;
+ }
+ }
+ }
+ break;
+ }
+ default: //GraphicType::NONE, GraphicType::Default
+ {
+ // empty graphic, we are done
+ bDone = true;
+ break;
+ }
+ }
+ }
+
+ if (!bDone)
+ {
+ // use default decomposition
+ process(rPolygonCandidate);
+ }
+}
+
+// mask group
+void VclProcessor2D::RenderMaskPrimitive2DPixel(const primitive2d::MaskPrimitive2D& rMaskCandidate)
+{
+ if (rMaskCandidate.getChildren().empty())
+ return;
+
+ basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask());
+
+ if (!aMask.count())
+ return;
+
+ aMask.transform(maCurrentTransformation);
+
+ // Unless smooth edges are needed, simply use clipping.
+ if (basegfx::utils::isRectangle(aMask) || !SvtOptionsDrawinglayer::IsAntiAliasing())
+ {
+ mpOutputDevice->Push(vcl::PushFlags::CLIPREGION);
+ mpOutputDevice->IntersectClipRegion(vcl::Region(aMask));
+ process(rMaskCandidate.getChildren());
+ mpOutputDevice->Pop();
+ return;
+ }
+
+ const basegfx::B2DRange aRange(basegfx::utils::getRange(aMask));
+ impBufferDevice aBufferDevice(*mpOutputDevice, aRange);
+
+ if (!aBufferDevice.isVisible())
+ return;
+
+ // remember last OutDev and set to content
+ OutputDevice* pLastOutputDevice = mpOutputDevice;
+ mpOutputDevice = &aBufferDevice.getContent();
+
+ // paint to it
+ process(rMaskCandidate.getChildren());
+
+ // back to old OutDev
+ mpOutputDevice = pLastOutputDevice;
+
+ // draw mask
+ VirtualDevice& rMask = aBufferDevice.getTransparence();
+ rMask.SetLineColor();
+ rMask.SetFillColor(COL_BLACK);
+ rMask.DrawPolyPolygon(aMask);
+
+ // dump buffer to outdev
+ aBufferDevice.paint();
+}
+
+// modified color group. Force output to unified color.
+void VclProcessor2D::RenderModifiedColorPrimitive2D(
+ const primitive2d::ModifiedColorPrimitive2D& rModifiedCandidate)
+{
+ if (!rModifiedCandidate.getChildren().empty())
+ {
+ maBColorModifierStack.push(rModifiedCandidate.getColorModifier());
+ process(rModifiedCandidate.getChildren());
+ maBColorModifierStack.pop();
+ }
+}
+
+// unified sub-transparence. Draw to VDev first.
+void VclProcessor2D::RenderUnifiedTransparencePrimitive2D(
+ const primitive2d::UnifiedTransparencePrimitive2D& rTransCandidate)
+{
+ if (rTransCandidate.getChildren().empty())
+ return;
+
+ if (0.0 == rTransCandidate.getTransparence())
+ {
+ // no transparence used, so just use the content
+ process(rTransCandidate.getChildren());
+ }
+ else if (rTransCandidate.getTransparence() > 0.0 && rTransCandidate.getTransparence() < 1.0)
+ {
+ // transparence is in visible range
+ basegfx::B2DRange aRange(rTransCandidate.getChildren().getB2DRange(getViewInformation2D()));
+ aRange.transform(maCurrentTransformation);
+ impBufferDevice aBufferDevice(*mpOutputDevice, aRange);
+
+ if (aBufferDevice.isVisible())
+ {
+ // remember last OutDev and set to content
+ OutputDevice* pLastOutputDevice = mpOutputDevice;
+ mpOutputDevice = &aBufferDevice.getContent();
+
+ // paint content to it
+ process(rTransCandidate.getChildren());
+
+ // back to old OutDev
+ mpOutputDevice = pLastOutputDevice;
+
+ // dump buffer to outdev using given transparence
+ aBufferDevice.paint(rTransCandidate.getTransparence());
+ }
+ }
+}
+
+// sub-transparence group. Draw to VDev first.
+void VclProcessor2D::RenderTransparencePrimitive2D(
+ const primitive2d::TransparencePrimitive2D& rTransCandidate)
+{
+ if (rTransCandidate.getChildren().empty())
+ return;
+
+ basegfx::B2DRange aRange(rTransCandidate.getChildren().getB2DRange(getViewInformation2D()));
+ aRange.transform(maCurrentTransformation);
+ impBufferDevice aBufferDevice(*mpOutputDevice, aRange);
+
+ if (!aBufferDevice.isVisible())
+ return;
+
+ // remember last OutDev and set to content
+ OutputDevice* pLastOutputDevice = mpOutputDevice;
+ mpOutputDevice = &aBufferDevice.getContent();
+
+ // paint content to it
+ process(rTransCandidate.getChildren());
+
+ // set to mask
+ mpOutputDevice = &aBufferDevice.getTransparence();
+
+ // when painting transparence masks, reset the color stack
+ basegfx::BColorModifierStack aLastBColorModifierStack(maBColorModifierStack);
+ maBColorModifierStack = basegfx::BColorModifierStack();
+
+ // paint mask to it (always with transparence intensities, evtl. with AA)
+ process(rTransCandidate.getTransparence());
+
+ // back to old color stack
+ maBColorModifierStack = aLastBColorModifierStack;
+
+ // back to old OutDev
+ mpOutputDevice = pLastOutputDevice;
+
+ // dump buffer to outdev
+ aBufferDevice.paint();
+}
+
+// transform group.
+void VclProcessor2D::RenderTransformPrimitive2D(
+ const primitive2d::TransformPrimitive2D& rTransformCandidate)
+{
+ // remember current transformation and ViewInformation
+ const basegfx::B2DHomMatrix aLastCurrentTransformation(maCurrentTransformation);
+ const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
+
+ // create new transformations for CurrentTransformation
+ // and for local ViewInformation2D
+ maCurrentTransformation = maCurrentTransformation * rTransformCandidate.getTransformation();
+ const geometry::ViewInformation2D aViewInformation2D(
+ getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation(),
+ getViewInformation2D().getViewTransformation(), getViewInformation2D().getViewport(),
+ getViewInformation2D().getVisualizedPage(), getViewInformation2D().getViewTime());
+ updateViewInformation(aViewInformation2D);
+
+ // process content
+ process(rTransformCandidate.getChildren());
+
+ // restore transformations
+ maCurrentTransformation = aLastCurrentTransformation;
+ updateViewInformation(aLastViewInformation2D);
+}
+
+// new XDrawPage for ViewInformation2D
+void VclProcessor2D::RenderPagePreviewPrimitive2D(
+ const primitive2d::PagePreviewPrimitive2D& rPagePreviewCandidate)
+{
+ // remember current transformation and ViewInformation
+ const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
+
+ // create new local ViewInformation2D
+ const geometry::ViewInformation2D aViewInformation2D(
+ getViewInformation2D().getObjectTransformation(),
+ getViewInformation2D().getViewTransformation(), getViewInformation2D().getViewport(),
+ rPagePreviewCandidate.getXDrawPage(), getViewInformation2D().getViewTime());
+ updateViewInformation(aViewInformation2D);
+
+ // process decomposed content
+ process(rPagePreviewCandidate);
+
+ // restore transformations
+ updateViewInformation(aLastViewInformation2D);
+}
+
+// marker
+void VclProcessor2D::RenderMarkerArrayPrimitive2D(
+ const primitive2d::MarkerArrayPrimitive2D& rMarkArrayCandidate)
+{
+ // get data
+ const std::vector<basegfx::B2DPoint>& rPositions = rMarkArrayCandidate.getPositions();
+ const sal_uInt32 nCount(rPositions.size());
+
+ if (!nCount || rMarkArrayCandidate.getMarker().IsEmpty())
+ return;
+
+ // get pixel size
+ const BitmapEx& rMarker(rMarkArrayCandidate.getMarker());
+ const Size aBitmapSize(rMarker.GetSizePixel());
+
+ if (!(aBitmapSize.Width() && aBitmapSize.Height()))
+ return;
+
+ // get discrete half size
+ const basegfx::B2DVector aDiscreteHalfSize((aBitmapSize.getWidth() - 1.0) * 0.5,
+ (aBitmapSize.getHeight() - 1.0) * 0.5);
+ const bool bWasEnabled(mpOutputDevice->IsMapModeEnabled());
+
+ // do not forget evtl. moved origin in target device MapMode when
+ // switching it off; it would be missing and lead to wrong positions.
+ // All his could be done using logic sizes and coordinates, too, but
+ // we want a 1:1 bitmap rendering here, so it's more safe and faster
+ // to work with switching off MapMode usage completely.
+ const Point aOrigin(mpOutputDevice->GetMapMode().GetOrigin());
+
+ mpOutputDevice->EnableMapMode(false);
+
+ for (auto const& pos : rPositions)
+ {
+ const basegfx::B2DPoint aDiscreteTopLeft((maCurrentTransformation * pos)
+ - aDiscreteHalfSize);
+ const Point aDiscretePoint(basegfx::fround(aDiscreteTopLeft.getX()),
+ basegfx::fround(aDiscreteTopLeft.getY()));
+
+ mpOutputDevice->DrawBitmapEx(aDiscretePoint + aOrigin, rMarker);
+ }
+
+ mpOutputDevice->EnableMapMode(bWasEnabled);
+}
+
+// point
+void VclProcessor2D::RenderPointArrayPrimitive2D(
+ const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate)
+{
+ const std::vector<basegfx::B2DPoint>& rPositions = rPointArrayCandidate.getPositions();
+ const basegfx::BColor aRGBColor(
+ maBColorModifierStack.getModifiedColor(rPointArrayCandidate.getRGBColor()));
+ const Color aVCLColor(aRGBColor);
+
+ for (auto const& pos : rPositions)
+ {
+ const basegfx::B2DPoint aViewPosition(maCurrentTransformation * pos);
+ const Point aPos(basegfx::fround(aViewPosition.getX()),
+ basegfx::fround(aViewPosition.getY()));
+
+ mpOutputDevice->DrawPixel(aPos, aVCLColor);
+ }
+}
+
+void VclProcessor2D::RenderPolygonStrokePrimitive2D(
+ const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokeCandidate)
+{
+ // #i101491# method restructured to clearly use the DrawPolyLine
+ // calls starting from a defined line width
+ const attribute::LineAttribute& rLineAttribute = rPolygonStrokeCandidate.getLineAttribute();
+ const double fLineWidth(rLineAttribute.getWidth());
+ bool bDone(false);
+
+ if (basegfx::fTools::more(fLineWidth, 0.0))
+ {
+ const basegfx::B2DVector aDiscreteUnit(maCurrentTransformation
+ * basegfx::B2DVector(fLineWidth, 0.0));
+ const double fDiscreteLineWidth(aDiscreteUnit.getLength());
+ const attribute::StrokeAttribute& rStrokeAttribute
+ = rPolygonStrokeCandidate.getStrokeAttribute();
+ const basegfx::BColor aHairlineColor(
+ maBColorModifierStack.getModifiedColor(rLineAttribute.getColor()));
+ basegfx::B2DPolyPolygon aHairlinePolyPolygon;
+
+ mpOutputDevice->SetLineColor(Color(aHairlineColor));
+ mpOutputDevice->SetFillColor();
+
+ if (0.0 == rStrokeAttribute.getFullDotDashLen())
+ {
+ // no line dashing, just copy
+ aHairlinePolyPolygon.append(rPolygonStrokeCandidate.getB2DPolygon());
+ }
+ else
+ {
+ // else apply LineStyle
+ basegfx::utils::applyLineDashing(
+ rPolygonStrokeCandidate.getB2DPolygon(), rStrokeAttribute.getDotDashArray(),
+ &aHairlinePolyPolygon, nullptr, rStrokeAttribute.getFullDotDashLen());
+ }
+
+ const sal_uInt32 nCount(aHairlinePolyPolygon.count());
+
+ if (nCount)
+ {
+ const bool bAntiAliased(SvtOptionsDrawinglayer::IsAntiAliasing());
+ aHairlinePolyPolygon.transform(maCurrentTransformation);
+
+ if (bAntiAliased)
+ {
+ if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 1.0))
+ {
+ // line in range ]0.0 .. 1.0[
+ // paint as simple hairline
+ for (sal_uInt32 a(0); a < nCount; a++)
+ {
+ mpOutputDevice->DrawPolyLine(aHairlinePolyPolygon.getB2DPolygon(a), 0.0);
+ }
+
+ bDone = true;
+ }
+ else if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 2.0))
+ {
+ // line in range [1.0 .. 2.0[
+ // paint as 2x2 with dynamic line distance
+ basegfx::B2DHomMatrix aMat;
+ const double fDistance(fDiscreteLineWidth - 1.0);
+ const double fHalfDistance(fDistance * 0.5);
+
+ for (sal_uInt32 a(0); a < nCount; a++)
+ {
+ basegfx::B2DPolygon aCandidate(aHairlinePolyPolygon.getB2DPolygon(a));
+
+ aMat.set(0, 2, -fHalfDistance);
+ aMat.set(1, 2, -fHalfDistance);
+ aCandidate.transform(aMat);
+ mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
+
+ aMat.set(0, 2, fDistance);
+ aMat.set(1, 2, 0.0);
+ aCandidate.transform(aMat);
+ mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
+
+ aMat.set(0, 2, 0.0);
+ aMat.set(1, 2, fDistance);
+ aCandidate.transform(aMat);
+ mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
+
+ aMat.set(0, 2, -fDistance);
+ aMat.set(1, 2, 0.0);
+ aCandidate.transform(aMat);
+ mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
+ }
+
+ bDone = true;
+ }
+ else if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 3.0))
+ {
+ // line in range [2.0 .. 3.0]
+ // paint as cross in a 3x3 with dynamic line distance
+ basegfx::B2DHomMatrix aMat;
+ const double fDistance((fDiscreteLineWidth - 1.0) * 0.5);
+
+ for (sal_uInt32 a(0); a < nCount; a++)
+ {
+ basegfx::B2DPolygon aCandidate(aHairlinePolyPolygon.getB2DPolygon(a));
+
+ mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
+
+ aMat.set(0, 2, -fDistance);
+ aMat.set(1, 2, 0.0);
+ aCandidate.transform(aMat);
+ mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
+
+ aMat.set(0, 2, fDistance);
+ aMat.set(1, 2, -fDistance);
+ aCandidate.transform(aMat);
+ mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
+
+ aMat.set(0, 2, fDistance);
+ aMat.set(1, 2, fDistance);
+ aCandidate.transform(aMat);
+ mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
+
+ aMat.set(0, 2, -fDistance);
+ aMat.set(1, 2, fDistance);
+ aCandidate.transform(aMat);
+ mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
+ }
+
+ bDone = true;
+ }
+ else
+ {
+ // #i101491# line width above 3.0
+ }
+ }
+ else
+ {
+ if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 1.5))
+ {
+ // line width below 1.5, draw the basic hairline polygon
+ for (sal_uInt32 a(0); a < nCount; a++)
+ {
+ mpOutputDevice->DrawPolyLine(aHairlinePolyPolygon.getB2DPolygon(a), 0.0);
+ }
+
+ bDone = true;
+ }
+ else if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 2.5))
+ {
+ // line width is in range ]1.5 .. 2.5], use four hairlines
+ // drawn in a square
+ for (sal_uInt32 a(0); a < nCount; a++)
+ {
+ basegfx::B2DPolygon aCandidate(aHairlinePolyPolygon.getB2DPolygon(a));
+ basegfx::B2DHomMatrix aMat;
+
+ mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
+
+ aMat.set(0, 2, 1.0);
+ aMat.set(1, 2, 0.0);
+ aCandidate.transform(aMat);
+
+ mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
+
+ aMat.set(0, 2, 0.0);
+ aMat.set(1, 2, 1.0);
+ aCandidate.transform(aMat);
+
+ mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
+
+ aMat.set(0, 2, -1.0);
+ aMat.set(1, 2, 0.0);
+ aCandidate.transform(aMat);
+
+ mpOutputDevice->DrawPolyLine(aCandidate, 0.0);
+ }
+
+ bDone = true;
+ }
+ else
+ {
+ // #i101491# line width is above 2.5
+ }
+ }
+
+ if (!bDone && rPolygonStrokeCandidate.getB2DPolygon().count() > 1000)
+ {
+ // #i101491# If the polygon complexity uses more than a given amount, do
+ // use OutputDevice::DrawPolyLine directly; this will avoid buffering all
+ // decompositions in primitives (memory) and fallback to old line painting
+ // for very complex polygons, too
+ for (sal_uInt32 a(0); a < nCount; a++)
+ {
+ mpOutputDevice->DrawPolyLine(aHairlinePolyPolygon.getB2DPolygon(a),
+ fDiscreteLineWidth, rLineAttribute.getLineJoin(),
+ rLineAttribute.getLineCap(),
+ rLineAttribute.getMiterMinimumAngle());
+ }
+
+ bDone = true;
+ }
+ }
+ }
+
+ if (!bDone)
+ {
+ // remember that we enter a PolygonStrokePrimitive2D decomposition,
+ // used for AA thick line drawing
+ mnPolygonStrokePrimitive2D++;
+
+ // line width is big enough for standard filled polygon visualisation or zero
+ process(rPolygonStrokeCandidate);
+
+ // leave PolygonStrokePrimitive2D
+ mnPolygonStrokePrimitive2D--;
+ }
+}
+
+void VclProcessor2D::RenderEpsPrimitive2D(const primitive2d::EpsPrimitive2D& rEpsPrimitive2D)
+{
+ // The new decomposition of Metafiles made it necessary to add an Eps
+ // primitive to handle embedded Eps data. On some devices, this can be
+ // painted directly (mac, printer).
+ // To be able to handle the replacement correctly, i need to handle it myself
+ // since DrawEPS will not be able e.g. to rotate the replacement. To be able
+ // to do that, i added a boolean return to OutputDevice::DrawEPS(..)
+ // to know when EPS was handled directly already.
+ basegfx::B2DRange aRange(0.0, 0.0, 1.0, 1.0);
+ aRange.transform(maCurrentTransformation * rEpsPrimitive2D.getEpsTransform());
+
+ if (aRange.isEmpty())
+ return;
+
+ const ::tools::Rectangle aRectangle(static_cast<sal_Int32>(floor(aRange.getMinX())),
+ static_cast<sal_Int32>(floor(aRange.getMinY())),
+ static_cast<sal_Int32>(ceil(aRange.getMaxX())),
+ static_cast<sal_Int32>(ceil(aRange.getMaxY())));
+
+ if (aRectangle.IsEmpty())
+ return;
+
+ bool bWillReallyRender = mpOutputDevice->IsDeviceOutputNecessary();
+ // try to paint EPS directly without fallback visualisation
+ const bool bEPSPaintedDirectly
+ = bWillReallyRender
+ && mpOutputDevice->DrawEPS(aRectangle.TopLeft(), aRectangle.GetSize(),
+ rEpsPrimitive2D.getGfxLink());
+
+ if (!bEPSPaintedDirectly)
+ {
+ // use the decomposition which will correctly handle the
+ // fallback visualisation using full transformation (e.g. rotation)
+ process(rEpsPrimitive2D);
+ }
+}
+
+void VclProcessor2D::RenderSvgLinearAtomPrimitive2D(
+ const primitive2d::SvgLinearAtomPrimitive2D& rCandidate)
+{
+ const double fDelta(rCandidate.getOffsetB() - rCandidate.getOffsetA());
+
+ if (!basegfx::fTools::more(fDelta, 0.0))
+ return;
+
+ const basegfx::BColor aColorA(maBColorModifierStack.getModifiedColor(rCandidate.getColorA()));
+ const basegfx::BColor aColorB(maBColorModifierStack.getModifiedColor(rCandidate.getColorB()));
+
+ // calculate discrete unit in WorldCoordinates; use diagonal (1.0, 1.0) and divide by sqrt(2)
+ const basegfx::B2DVector aDiscreteVector(
+ getViewInformation2D().getInverseObjectToViewTransformation()
+ * basegfx::B2DVector(1.0, 1.0));
+ const double fDiscreteUnit(aDiscreteVector.getLength() * (1.0 / M_SQRT2));
+
+ // use color distance and discrete lengths to calculate step count
+ const sal_uInt32 nSteps(calculateStepsForSvgGradient(aColorA, aColorB, fDelta, fDiscreteUnit));
+
+ // switch off line painting
+ mpOutputDevice->SetLineColor();
+
+ // prepare polygon in needed width at start position (with discrete overlap)
+ const basegfx::B2DPolygon aPolygon(basegfx::utils::createPolygonFromRect(
+ basegfx::B2DRange(rCandidate.getOffsetA() - fDiscreteUnit, 0.0,
+ rCandidate.getOffsetA() + (fDelta / nSteps) + fDiscreteUnit, 1.0)));
+
+ // prepare loop ([0.0 .. 1.0[)
+ double fUnitScale(0.0);
+ const double fUnitStep(1.0 / nSteps);
+
+ // loop and paint
+ for (sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep)
+ {
+ basegfx::B2DPolygon aNew(aPolygon);
+
+ aNew.transform(maCurrentTransformation
+ * basegfx::utils::createTranslateB2DHomMatrix(fDelta * fUnitScale, 0.0));
+ mpOutputDevice->SetFillColor(Color(basegfx::interpolate(aColorA, aColorB, fUnitScale)));
+ mpOutputDevice->DrawPolyPolygon(basegfx::B2DPolyPolygon(aNew));
+ }
+}
+
+void VclProcessor2D::RenderSvgRadialAtomPrimitive2D(
+ const primitive2d::SvgRadialAtomPrimitive2D& rCandidate)
+{
+ const double fDeltaScale(rCandidate.getScaleB() - rCandidate.getScaleA());
+
+ if (!basegfx::fTools::more(fDeltaScale, 0.0))
+ return;
+
+ const basegfx::BColor aColorA(maBColorModifierStack.getModifiedColor(rCandidate.getColorA()));
+ const basegfx::BColor aColorB(maBColorModifierStack.getModifiedColor(rCandidate.getColorB()));
+
+ // calculate discrete unit in WorldCoordinates; use diagonal (1.0, 1.0) and divide by sqrt(2)
+ const basegfx::B2DVector aDiscreteVector(
+ getViewInformation2D().getInverseObjectToViewTransformation()
+ * basegfx::B2DVector(1.0, 1.0));
+ const double fDiscreteUnit(aDiscreteVector.getLength() * (1.0 / M_SQRT2));
+
+ // use color distance and discrete lengths to calculate step count
+ const sal_uInt32 nSteps(
+ calculateStepsForSvgGradient(aColorA, aColorB, fDeltaScale, fDiscreteUnit));
+
+ // switch off line painting
+ mpOutputDevice->SetLineColor();
+
+ // prepare loop ([0.0 .. 1.0[, full polygons, no polypolygons with holes)
+ double fUnitScale(0.0);
+ const double fUnitStep(1.0 / nSteps);
+
+ for (sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep)
+ {
+ basegfx::B2DHomMatrix aTransform;
+ const double fEndScale(rCandidate.getScaleB() - (fDeltaScale * fUnitScale));
+
+ if (rCandidate.isTranslateSet())
+ {
+ const basegfx::B2DVector aTranslate(basegfx::interpolate(
+ rCandidate.getTranslateB(), rCandidate.getTranslateA(), fUnitScale));
+
+ aTransform = basegfx::utils::createScaleTranslateB2DHomMatrix(
+ fEndScale, fEndScale, aTranslate.getX(), aTranslate.getY());
+ }
+ else
+ {
+ aTransform = basegfx::utils::createScaleB2DHomMatrix(fEndScale, fEndScale);
+ }
+
+ basegfx::B2DPolygon aNew(basegfx::utils::createPolygonFromUnitCircle());
+
+ aNew.transform(maCurrentTransformation * aTransform);
+ mpOutputDevice->SetFillColor(Color(basegfx::interpolate(aColorB, aColorA, fUnitScale)));
+ mpOutputDevice->DrawPolyPolygon(basegfx::B2DPolyPolygon(aNew));
+ }
+}
+
+void VclProcessor2D::adaptLineToFillDrawMode() const
+{
+ const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode());
+
+ if (!(nOriginalDrawMode
+ & (DrawModeFlags::BlackLine | DrawModeFlags::GrayLine | DrawModeFlags::WhiteLine
+ | DrawModeFlags::SettingsLine)))
+ return;
+
+ DrawModeFlags nAdaptedDrawMode(nOriginalDrawMode);
+
+ if (nOriginalDrawMode & DrawModeFlags::BlackLine)
+ {
+ nAdaptedDrawMode |= DrawModeFlags::BlackFill;
+ }
+ else
+ {
+ nAdaptedDrawMode &= ~DrawModeFlags::BlackFill;
+ }
+
+ if (nOriginalDrawMode & DrawModeFlags::GrayLine)
+ {
+ nAdaptedDrawMode |= DrawModeFlags::GrayFill;
+ }
+ else
+ {
+ nAdaptedDrawMode &= ~DrawModeFlags::GrayFill;
+ }
+
+ if (nOriginalDrawMode & DrawModeFlags::WhiteLine)
+ {
+ nAdaptedDrawMode |= DrawModeFlags::WhiteFill;
+ }
+ else
+ {
+ nAdaptedDrawMode &= ~DrawModeFlags::WhiteFill;
+ }
+
+ if (nOriginalDrawMode & DrawModeFlags::SettingsLine)
+ {
+ nAdaptedDrawMode |= DrawModeFlags::SettingsFill;
+ }
+ else
+ {
+ nAdaptedDrawMode &= ~DrawModeFlags::SettingsFill;
+ }
+
+ mpOutputDevice->SetDrawMode(nAdaptedDrawMode);
+}
+
+void VclProcessor2D::adaptTextToFillDrawMode() const
+{
+ const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode());
+ if (!(nOriginalDrawMode
+ & (DrawModeFlags::BlackText | DrawModeFlags::GrayText | DrawModeFlags::WhiteText
+ | DrawModeFlags::SettingsText)))
+ return;
+
+ DrawModeFlags nAdaptedDrawMode(nOriginalDrawMode);
+
+ if (nOriginalDrawMode & DrawModeFlags::BlackText)
+ {
+ nAdaptedDrawMode |= DrawModeFlags::BlackFill;
+ }
+ else
+ {
+ nAdaptedDrawMode &= ~DrawModeFlags::BlackFill;
+ }
+
+ if (nOriginalDrawMode & DrawModeFlags::GrayText)
+ {
+ nAdaptedDrawMode |= DrawModeFlags::GrayFill;
+ }
+ else
+ {
+ nAdaptedDrawMode &= ~DrawModeFlags::GrayFill;
+ }
+
+ if (nOriginalDrawMode & DrawModeFlags::WhiteText)
+ {
+ nAdaptedDrawMode |= DrawModeFlags::WhiteFill;
+ }
+ else
+ {
+ nAdaptedDrawMode &= ~DrawModeFlags::WhiteFill;
+ }
+
+ if (nOriginalDrawMode & DrawModeFlags::SettingsText)
+ {
+ nAdaptedDrawMode |= DrawModeFlags::SettingsFill;
+ }
+ else
+ {
+ nAdaptedDrawMode &= ~DrawModeFlags::SettingsFill;
+ }
+
+ mpOutputDevice->SetDrawMode(nAdaptedDrawMode);
+}
+
+// process support
+
+VclProcessor2D::VclProcessor2D(const geometry::ViewInformation2D& rViewInformation,
+ OutputDevice& rOutDev,
+ const basegfx::BColorModifierStack& rInitStack)
+ : BaseProcessor2D(rViewInformation)
+ , mpOutputDevice(&rOutDev)
+ , maBColorModifierStack(rInitStack)
+ , mnPolygonStrokePrimitive2D(0)
+{
+ // set digit language, derived from SvtCTLOptions to have the correct
+ // number display for arabic/hindi numerals
+ rOutDev.SetDigitLanguage(drawinglayer::detail::getDigitLanguage());
+}
+
+VclProcessor2D::~VclProcessor2D() {}
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/vclprocessor2d.hxx b/drawinglayer/source/processor2d/vclprocessor2d.hxx
new file mode 100644
index 000000000..1506f481e
--- /dev/null
+++ b/drawinglayer/source/processor2d/vclprocessor2d.hxx
@@ -0,0 +1,119 @@
+/* -*- 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 <drawinglayer/processor2d/baseprocessor2d.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/color/bcolormodifier.hxx>
+#include <vcl/vclptr.hxx>
+
+class OutputDevice;
+
+namespace drawinglayer::primitive2d
+{
+class TextSimplePortionPrimitive2D;
+class PolygonHairlinePrimitive2D;
+class BitmapPrimitive2D;
+class FillGraphicPrimitive2D;
+class PolyPolygonGradientPrimitive2D;
+class PolyPolygonGraphicPrimitive2D;
+class MetafilePrimitive2D;
+class MaskPrimitive2D;
+class UnifiedTransparencePrimitive2D;
+class TransparencePrimitive2D;
+class TransformPrimitive2D;
+class MarkerArrayPrimitive2D;
+class PointArrayPrimitive2D;
+class ModifiedColorPrimitive2D;
+class PolygonStrokePrimitive2D;
+class ControlPrimitive2D;
+class PagePreviewPrimitive2D;
+class EpsPrimitive2D;
+class SvgLinearAtomPrimitive2D;
+class SvgRadialAtomPrimitive2D;
+}
+
+namespace drawinglayer::processor2d
+{
+/** VclProcessor2D class
+
+ This processor is the base class for VCL-Based processors. It has no
+ processBasePrimitive2D implementation and thus is not usable directly.
+ */
+class VclProcessor2D : public BaseProcessor2D
+{
+protected:
+ // the destination OutDev
+ VclPtr<OutputDevice> mpOutputDevice;
+
+ // the modifiedColorPrimitive stack
+ basegfx::BColorModifierStack maBColorModifierStack;
+
+ // the current transformation. Since VCL pixel renderer transforms to pixels
+ // and VCL MetaFile renderer to World (logic) coordinates, the local
+ // ViewInformation2D cannot directly be used, but needs to be kept up to date
+ basegfx::B2DHomMatrix maCurrentTransformation;
+
+ // stack value (increment and decrement) to count how deep we are in
+ // PolygonStrokePrimitive2D's decompositions (normally only one)
+ sal_uInt32 mnPolygonStrokePrimitive2D;
+
+ // common VCL rendering support
+ void RenderTextSimpleOrDecoratedPortionPrimitive2D(
+ const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate);
+ void RenderPolygonHairlinePrimitive2D(
+ const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate, bool bPixelBased);
+ void RenderBitmapPrimitive2D(const primitive2d::BitmapPrimitive2D& rBitmapCandidate);
+ void
+ RenderFillGraphicPrimitive2D(const primitive2d::FillGraphicPrimitive2D& rFillBitmapCandidate);
+ void RenderPolyPolygonGraphicPrimitive2D(
+ const primitive2d::PolyPolygonGraphicPrimitive2D& rPolygonCandidate);
+ void RenderMaskPrimitive2DPixel(const primitive2d::MaskPrimitive2D& rMaskCandidate);
+ void
+ RenderModifiedColorPrimitive2D(const primitive2d::ModifiedColorPrimitive2D& rModifiedCandidate);
+ void RenderUnifiedTransparencePrimitive2D(
+ const primitive2d::UnifiedTransparencePrimitive2D& rTransCandidate);
+ void RenderTransparencePrimitive2D(const primitive2d::TransparencePrimitive2D& rTransCandidate);
+ void RenderTransformPrimitive2D(const primitive2d::TransformPrimitive2D& rTransformCandidate);
+ void
+ RenderPagePreviewPrimitive2D(const primitive2d::PagePreviewPrimitive2D& rPagePreviewCandidate);
+ void
+ RenderMarkerArrayPrimitive2D(const primitive2d::MarkerArrayPrimitive2D& rMarkerArrayCandidate);
+ void
+ RenderPointArrayPrimitive2D(const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate);
+ void RenderPolygonStrokePrimitive2D(
+ const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokeCandidate);
+ void RenderEpsPrimitive2D(const primitive2d::EpsPrimitive2D& rEpsPrimitive2D);
+ void RenderSvgLinearAtomPrimitive2D(const primitive2d::SvgLinearAtomPrimitive2D& rCandidate);
+ void RenderSvgRadialAtomPrimitive2D(const primitive2d::SvgRadialAtomPrimitive2D& rCandidate);
+
+ // DrawMode adaptation support
+ void adaptLineToFillDrawMode() const;
+ void adaptTextToFillDrawMode() const;
+
+public:
+ // constructor/destructor
+ VclProcessor2D(const geometry::ViewInformation2D& rViewInformation, OutputDevice& rOutDev,
+ const basegfx::BColorModifierStack& rInitStack = basegfx::BColorModifierStack());
+ virtual ~VclProcessor2D() override;
+};
+} // end of namespace drawinglayer::processor2d
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */