summaryrefslogtreecommitdiffstats
path: root/drawinglayer/source/processor2d
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /drawinglayer/source/processor2d
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drawinglayer/source/processor2d')
-rw-r--r--drawinglayer/source/processor2d/SDPRProcessor2dTools.cxx300
-rw-r--r--drawinglayer/source/processor2d/baseprocessor2d.cxx74
-rw-r--r--drawinglayer/source/processor2d/cairopixelprocessor2d.cxx975
-rw-r--r--drawinglayer/source/processor2d/contourextractor2d.cxx189
-rw-r--r--drawinglayer/source/processor2d/d2dpixelprocessor2d.cxx2191
-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.cxx548
-rw-r--r--drawinglayer/source/processor2d/linegeometryextractor2d.cxx121
-rw-r--r--drawinglayer/source/processor2d/objectinfoextractor2d.cxx77
-rw-r--r--drawinglayer/source/processor2d/processor2dtools.cxx92
-rw-r--r--drawinglayer/source/processor2d/textaspolygonextractor2d.cxx227
-rw-r--r--drawinglayer/source/processor2d/vclhelperbufferdevice.cxx593
-rw-r--r--drawinglayer/source/processor2d/vclhelperbufferdevice.hxx112
-rw-r--r--drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx2655
-rw-r--r--drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx214
-rw-r--r--drawinglayer/source/processor2d/vclpixelprocessor2d.cxx1131
-rw-r--r--drawinglayer/source/processor2d/vclpixelprocessor2d.hxx109
-rw-r--r--drawinglayer/source/processor2d/vclprocessor2d.cxx1560
-rw-r--r--drawinglayer/source/processor2d/vclprocessor2d.hxx123
22 files changed, 11467 insertions, 0 deletions
diff --git a/drawinglayer/source/processor2d/SDPRProcessor2dTools.cxx b/drawinglayer/source/processor2d/SDPRProcessor2dTools.cxx
new file mode 100644
index 0000000000..3d738cd09c
--- /dev/null
+++ b/drawinglayer/source/processor2d/SDPRProcessor2dTools.cxx
@@ -0,0 +1,300 @@
+/* -*- 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/SDPRProcessor2dTools.hxx>
+#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx>
+#include <drawinglayer/geometry/viewinformation2d.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/graph.hxx>
+#include <basegfx/range/b2drange.hxx>
+
+#ifdef DBG_UTIL
+#include <tools/stream.hxx>
+#include <vcl/filter/PngImageWriter.hxx>
+#endif
+
+namespace drawinglayer::processor2d
+{
+void setOffsetXYCreatedBitmap(
+ drawinglayer::primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D,
+ const BitmapEx& rBitmap)
+{
+ rFillGraphicPrimitive2D.impSetOffsetXYCreatedBitmap(rBitmap);
+}
+
+void takeCareOfOffsetXY(
+ const drawinglayer::primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D,
+ BitmapEx& rTarget, basegfx::B2DRange& rFillUnitRange)
+{
+ const attribute::FillGraphicAttribute& rFillGraphicAttribute(
+ rFillGraphicPrimitive2D.getFillGraphic());
+ const bool bOffsetXIsUsed(rFillGraphicAttribute.getOffsetX() > 0.0
+ && rFillGraphicAttribute.getOffsetX() < 1.0);
+ const bool bOffsetYIsUsed(rFillGraphicAttribute.getOffsetY() > 0.0
+ && rFillGraphicAttribute.getOffsetY() < 1.0);
+
+ if (bOffsetXIsUsed)
+ {
+ if (rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty())
+ {
+ const Size& rSize(rTarget.GetSizePixel());
+ const tools::Long w(rSize.Width());
+ const tools::Long a(basegfx::fround(w * (1.0 - rFillGraphicAttribute.getOffsetX())));
+
+ if (0 != a && w != a)
+ {
+ const tools::Long h(rSize.Height());
+ const tools::Long b(w - a);
+ BitmapEx aTarget(Size(w, h * 2), rTarget.getPixelFormat());
+
+ aTarget.SetPrefSize(
+ Size(rTarget.GetPrefSize().Width(), rTarget.GetPrefSize().Height() * 2));
+ const tools::Rectangle aSrcDst(Point(), rSize);
+ aTarget.CopyPixel(aSrcDst, // Dst
+ aSrcDst, // Src
+ rTarget);
+ const Size aSizeA(b, h);
+ aTarget.CopyPixel(tools::Rectangle(Point(0, h), aSizeA), // Dst
+ tools::Rectangle(Point(a, 0), aSizeA), // Src
+ rTarget);
+ const Size aSizeB(a, h);
+ aTarget.CopyPixel(tools::Rectangle(Point(b, h), aSizeB), // Dst
+ tools::Rectangle(Point(), aSizeB), // Src
+ rTarget);
+
+ setOffsetXYCreatedBitmap(
+ const_cast<drawinglayer::primitive2d::FillGraphicPrimitive2D&>(
+ rFillGraphicPrimitive2D),
+ aTarget);
+ }
+ }
+
+ if (!rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty())
+ {
+ rTarget = rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap();
+ rFillUnitRange.expand(basegfx::B2DPoint(
+ rFillUnitRange.getMinX(), rFillUnitRange.getMaxY() + rFillUnitRange.getHeight()));
+ }
+ }
+ else if (bOffsetYIsUsed)
+ {
+ if (rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty())
+ {
+ const Size& rSize(rTarget.GetSizePixel());
+ const tools::Long h(rSize.Height());
+ const tools::Long a(basegfx::fround(h * (1.0 - rFillGraphicAttribute.getOffsetY())));
+
+ if (0 != a && h != a)
+ {
+ const tools::Long w(rSize.Width());
+ const tools::Long b(h - a);
+ BitmapEx aTarget(Size(w * 2, h), rTarget.getPixelFormat());
+
+ aTarget.SetPrefSize(
+ Size(rTarget.GetPrefSize().Width() * 2, rTarget.GetPrefSize().Height()));
+ const tools::Rectangle aSrcDst(Point(), rSize);
+ aTarget.CopyPixel(aSrcDst, // Dst
+ aSrcDst, // Src
+ rTarget);
+ const Size aSizeA(w, b);
+ aTarget.CopyPixel(tools::Rectangle(Point(w, 0), aSizeA), // Dst
+ tools::Rectangle(Point(0, a), aSizeA), // Src
+ rTarget);
+ const Size aSizeB(w, a);
+ aTarget.CopyPixel(tools::Rectangle(Point(w, b), aSizeB), // Dst
+ tools::Rectangle(Point(), aSizeB), // Src
+ rTarget);
+
+ setOffsetXYCreatedBitmap(
+ const_cast<drawinglayer::primitive2d::FillGraphicPrimitive2D&>(
+ rFillGraphicPrimitive2D),
+ aTarget);
+ }
+ }
+
+ if (!rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty())
+ {
+ rTarget = rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap();
+ rFillUnitRange.expand(basegfx::B2DPoint(
+ rFillUnitRange.getMaxX() + rFillUnitRange.getWidth(), rFillUnitRange.getMinY()));
+ }
+ }
+}
+
+bool prepareBitmapForDirectRender(
+ const drawinglayer::primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D,
+ const drawinglayer::geometry::ViewInformation2D& rViewInformation2D, BitmapEx& rTarget,
+ basegfx::B2DRange& rFillUnitRange, double fBigDiscreteArea)
+{
+ const attribute::FillGraphicAttribute& rFillGraphicAttribute(
+ rFillGraphicPrimitive2D.getFillGraphic());
+ const Graphic& rGraphic(rFillGraphicAttribute.getGraphic());
+
+ if (rFillGraphicAttribute.isDefault() || rGraphic.IsNone())
+ {
+ // default attributes or GraphicType::NONE, so no fill .-> done
+ return false;
+ }
+
+ if (!rFillGraphicAttribute.getTiling())
+ {
+ // If no tiling used, the Graphic will need to be painted just once. This
+ // is perfectly done using the decomposition, so use it.
+ // What we want to do here is to optimize tiled paint, for two reasons:
+ // (a) speed: draw one tile, repeat -> obvious
+ // (b) correctness: not so obvious, but since in AAed paint the same edge
+ // of touching polygons both AAed do *not* sum up, but get blended by
+ // multiplication (0.5 * 0.5 -> 0.25) the connection will stay visible,
+ // not only with filled polygons, but also with bitmaps
+ // Signal that paint is needed
+ return true;
+ }
+
+ if (rFillUnitRange.isEmpty())
+ {
+ // no fill range definition, no fill, done
+ return false;
+ }
+
+ const basegfx::B2DHomMatrix aLocalTransform(rViewInformation2D.getObjectToViewTransformation()
+ * rFillGraphicPrimitive2D.getTransformation());
+ const basegfx::B2DRange& rDiscreteViewPort(rViewInformation2D.getDiscreteViewport());
+
+ if (!rDiscreteViewPort.isEmpty())
+ {
+ // calculate discrete covered pixel area
+ basegfx::B2DRange aDiscreteRange(basegfx::B2DRange::getUnitB2DRange());
+ aDiscreteRange.transform(aLocalTransform);
+
+ if (!rDiscreteViewPort.overlaps(rDiscreteViewPort))
+ {
+ // we have a Viewport and visible range of geometry is outside -> not visible, done
+ return false;
+ }
+ }
+
+ if (GraphicType::Bitmap == rGraphic.GetType() && rGraphic.IsAnimated())
+ {
+ // Need to prepare specialized AnimatedGraphicPrimitive2D,
+ // cannot handle here. Signal that paint is needed
+ return true;
+ }
+
+ if (GraphicType::Bitmap == rGraphic.GetType() && !rGraphic.getVectorGraphicData())
+ {
+ // bitmap graphic, always handle locally, so get bitmap data independent
+ // if it'sie or it's discrete display size
+ rTarget = rGraphic.GetBitmapEx();
+ }
+ else
+ {
+ // Vector Graphic Data fill, including metafile:
+ // We can know about discrete pixel size here, calculate and use it.
+ // To do so, using Vectors is sufficient to get the lengths. It is
+ // not necessary to transform the whole target coordinate system.
+ const basegfx::B2DVector aDiscreteXAxis(
+ aLocalTransform
+ * basegfx::B2DVector(rFillUnitRange.getMaxX() - rFillUnitRange.getMinX(), 0.0));
+ const basegfx::B2DVector aDiscreteYAxis(
+ aLocalTransform
+ * basegfx::B2DVector(0.0, rFillUnitRange.getMaxY() - rFillUnitRange.getMinY()));
+
+ // get and ensure minimal size
+ const double fDiscreteWidth(std::max(1.0, aDiscreteXAxis.getLength()));
+ const double fDiscreteHeight(std::max(1.0, aDiscreteYAxis.getLength()));
+
+ // compare with a big visualization size in discrete pixels
+ const double fTargetDiscreteArea(fDiscreteWidth * fDiscreteHeight);
+
+ if (fTargetDiscreteArea > fBigDiscreteArea)
+ {
+ // When the vector data is visualized big it is better to not handle here
+ // but use decomposition fallback which then will visualize the vector data
+ // directly -> better quality, acceptable number of tile repeat(s)
+ // signal that paint is needed
+ return true;
+ }
+ else
+ {
+ // If visualized small, the amount of repeated fills gets expensive, so
+ // in that case use a Bitmap and the Brush technique below.
+ // The Bitmap may be created here exactly for the needed target size
+ // (using local D2DBitmapPixelProcessor2D and the vector data),
+ // but since we have a HW renderer and re-use of system-dependent data
+ // at BitmapEx is possible, just get the default fallback Bitmap from the
+ // vector data to continue. Trust the existing converters for now to
+ // do something with good quality.
+ rTarget = rGraphic.GetBitmapEx();
+ }
+ }
+
+ if (rTarget.IsEmpty() || rTarget.GetSizePixel().IsEmpty())
+ {
+ // no pixel data, done
+ return false;
+ }
+
+ // react if OffsetX/OffsetY of the FillGraphicAttribute is used
+ takeCareOfOffsetXY(rFillGraphicPrimitive2D, rTarget, rFillUnitRange);
+
+#ifdef DBG_UTIL
+ // allow to check bitmap data, e.g. control OffsetX/OffsetY stuff
+ static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore
+ if (bDoSaveForVisualControl)
+ {
+ static const OUString sDumpPath(
+ OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH")));
+ if (!sDumpPath.isEmpty())
+ {
+ SvFileStream aNew(sDumpPath + "test_getreplacement.png",
+ StreamMode::WRITE | StreamMode::TRUNC);
+ vcl::PngImageWriter aPNGWriter(aNew);
+ aPNGWriter.write(rTarget);
+ }
+ }
+#endif
+
+ // signal to render it
+ return true;
+}
+
+void calculateDiscreteVisibleRange(
+ basegfx::B2DRange& rDiscreteVisibleRange, const basegfx::B2DRange& rContentRange,
+ const drawinglayer::geometry::ViewInformation2D& rViewInformation2D)
+{
+ if (rContentRange.isEmpty())
+ {
+ // no content, done
+ rDiscreteVisibleRange.reset();
+ return;
+ }
+
+ basegfx::B2DRange aDiscreteRange(rContentRange);
+ aDiscreteRange.transform(rViewInformation2D.getObjectToViewTransformation());
+ const basegfx::B2DRange& rDiscreteViewPort(rViewInformation2D.getDiscreteViewport());
+ rDiscreteVisibleRange = aDiscreteRange;
+
+ if (!rDiscreteViewPort.isEmpty())
+ {
+ rDiscreteVisibleRange.intersect(rDiscreteViewPort);
+ }
+}
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/processor2d/baseprocessor2d.cxx b/drawinglayer/source/processor2d/baseprocessor2d.cxx
new file mode 100644
index 0000000000..f4aec90383
--- /dev/null
+++ b/drawinglayer/source/processor2d/baseprocessor2d.cxx
@@ -0,0 +1,74 @@
+/* -*- 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>
+#include <utility>
+
+
+using namespace com::sun::star;
+
+
+namespace drawinglayer::processor2d
+{
+ void BaseProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& /*rCandidate*/)
+ {
+ }
+
+ BaseProcessor2D::BaseProcessor2D(geometry::ViewInformation2D aViewInformation)
+ : maViewInformation2D(std::move(aViewInformation))
+ {
+ }
+
+ 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/cairopixelprocessor2d.cxx b/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx
new file mode 100644
index 0000000000..89cb21ddcb
--- /dev/null
+++ b/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx
@@ -0,0 +1,975 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sal/config.h>
+
+#include <drawinglayer/processor2d/cairopixelprocessor2d.hxx>
+#include <sal/log.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <vcl/cairo.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/svapp.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
+#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
+#include <drawinglayer/primitive2d/backgroundcolorprimitive2d.hxx>
+#include <drawinglayer/primitive2d/baseprimitive2d.hxx>
+#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx>
+#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
+#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
+#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
+#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/Tools.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
+#include <drawinglayer/converters.hxx>
+#include <basegfx/curve/b2dcubicbezier.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/utils/systemdependentdata.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+
+using namespace com::sun::star;
+
+namespace
+{
+basegfx::B2DPoint impPixelSnap(const basegfx::B2DPolygon& rPolygon,
+ const drawinglayer::geometry::ViewInformation2D& rViewInformation,
+ sal_uInt32 nIndex)
+{
+ const sal_uInt32 nCount(rPolygon.count());
+
+ // get the data
+ const basegfx::B2ITuple aPrevTuple(
+ basegfx::fround(rViewInformation.getObjectToViewTransformation()
+ * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount)));
+ const basegfx::B2DPoint aCurrPoint(rViewInformation.getObjectToViewTransformation()
+ * rPolygon.getB2DPoint(nIndex));
+ const basegfx::B2ITuple aCurrTuple(basegfx::fround(aCurrPoint));
+ const basegfx::B2ITuple aNextTuple(
+ basegfx::fround(rViewInformation.getObjectToViewTransformation()
+ * rPolygon.getB2DPoint((nIndex + 1) % nCount)));
+
+ // get the states
+ const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX());
+ const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX());
+ const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY());
+ const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY());
+ const bool bSnapX(bPrevVertical || bNextVertical);
+ const bool bSnapY(bPrevHorizontal || bNextHorizontal);
+
+ if (bSnapX || bSnapY)
+ {
+ basegfx::B2DPoint aSnappedPoint(bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(),
+ bSnapY ? aCurrTuple.getY() : aCurrPoint.getY());
+
+ aSnappedPoint *= rViewInformation.getInverseObjectToViewTransformation();
+
+ return aSnappedPoint;
+ }
+
+ return rPolygon.getB2DPoint(nIndex);
+}
+
+void addB2DPolygonToPathGeometry(cairo_t* cr, const basegfx::B2DPolygon& rPolygon,
+ const drawinglayer::geometry::ViewInformation2D* pViewInformation)
+{
+ // short circuit if there is nothing to do
+ const sal_uInt32 nPointCount(rPolygon.count());
+
+ const bool bHasCurves(rPolygon.areControlPointsUsed());
+ const bool bClosePath(rPolygon.isClosed());
+ basegfx::B2DPoint aLast;
+
+ for (sal_uInt32 nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++)
+ {
+ int nClosedIdx = nPointIdx;
+ if (nPointIdx >= nPointCount)
+ {
+ // prepare to close last curve segment if needed
+ if (bClosePath && (nPointIdx == nPointCount))
+ {
+ nClosedIdx = 0;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ const basegfx::B2DPoint aPoint(nullptr == pViewInformation
+ ? rPolygon.getB2DPoint(nClosedIdx)
+ : impPixelSnap(rPolygon, *pViewInformation, nClosedIdx));
+
+ if (!nPointIdx)
+ {
+ // first point => just move there
+ cairo_move_to(cr, aPoint.getX(), aPoint.getY());
+ aLast = aPoint;
+ continue;
+ }
+
+ bool bPendingCurve(false);
+
+ if (bHasCurves)
+ {
+ bPendingCurve = rPolygon.isNextControlPointUsed(nPrevIdx);
+ bPendingCurve |= rPolygon.isPrevControlPointUsed(nClosedIdx);
+ }
+
+ if (!bPendingCurve) // line segment
+ {
+ cairo_line_to(cr, aPoint.getX(), aPoint.getY());
+ }
+ else // cubic bezier segment
+ {
+ basegfx::B2DPoint aCP1 = rPolygon.getNextControlPoint(nPrevIdx);
+ basegfx::B2DPoint aCP2 = rPolygon.getPrevControlPoint(nClosedIdx);
+
+ // tdf#99165 if the control points are 'empty', create the mathematical
+ // correct replacement ones to avoid problems with the graphical sub-system
+ // tdf#101026 The 1st attempt to create a mathematically correct replacement control
+ // vector was wrong. Best alternative is one as close as possible which means short.
+ if (aCP1.equal(aLast))
+ {
+ aCP1 = aLast + ((aCP2 - aLast) * 0.0005);
+ }
+
+ if (aCP2.equal(aPoint))
+ {
+ aCP2 = aPoint + ((aCP1 - aPoint) * 0.0005);
+ }
+
+ cairo_curve_to(cr, aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(), aPoint.getX(),
+ aPoint.getY());
+ }
+
+ aLast = aPoint;
+ }
+
+ if (bClosePath)
+ {
+ cairo_close_path(cr);
+ }
+}
+
+// split alpha remains as a constant irritant
+std::vector<sal_uInt8> createBitmapData(const BitmapEx& rBitmapEx)
+{
+ const Size& rSizePixel(rBitmapEx.GetSizePixel());
+ const bool bAlpha(rBitmapEx.IsAlpha());
+ const sal_uInt32 nStride
+ = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, rSizePixel.Width());
+ std::vector<sal_uInt8> aData(nStride * rSizePixel.Height());
+
+ if (bAlpha)
+ {
+ Bitmap aSrcAlpha(rBitmapEx.GetAlphaMask().GetBitmap());
+ BitmapScopedReadAccess pReadAccess(rBitmapEx.GetBitmap());
+ BitmapScopedReadAccess pAlphaReadAccess(aSrcAlpha);
+ const tools::Long nHeight(pReadAccess->Height());
+ const tools::Long nWidth(pReadAccess->Width());
+
+ for (tools::Long y = 0; y < nHeight; ++y)
+ {
+ unsigned char* pPixelData = aData.data() + (nStride * y);
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ const BitmapColor aColor(pReadAccess->GetColor(y, x));
+ const BitmapColor aAlpha(pAlphaReadAccess->GetColor(y, x));
+ const sal_uInt16 nAlpha(255 - aAlpha.GetRed());
+
+ pPixelData[SVP_CAIRO_RED] = vcl::bitmap::premultiply(nAlpha, aColor.GetRed());
+ pPixelData[SVP_CAIRO_GREEN] = vcl::bitmap::premultiply(nAlpha, aColor.GetGreen());
+ pPixelData[SVP_CAIRO_BLUE] = vcl::bitmap::premultiply(nAlpha, aColor.GetBlue());
+ pPixelData[SVP_CAIRO_ALPHA] = nAlpha;
+ pPixelData += 4;
+ }
+ }
+ }
+ else
+ {
+ BitmapScopedReadAccess pReadAccess(rBitmapEx.GetBitmap());
+ const tools::Long nHeight(pReadAccess->Height());
+ const tools::Long nWidth(pReadAccess->Width());
+
+ for (tools::Long y = 0; y < nHeight; ++y)
+ {
+ unsigned char* pPixelData = aData.data() + (nStride * y);
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ const BitmapColor aColor(pReadAccess->GetColor(y, x));
+ pPixelData[SVP_CAIRO_RED] = aColor.GetRed();
+ pPixelData[SVP_CAIRO_GREEN] = aColor.GetGreen();
+ pPixelData[SVP_CAIRO_BLUE] = aColor.GetBlue();
+ pPixelData[SVP_CAIRO_ALPHA] = 255;
+ pPixelData += 4;
+ }
+ }
+ }
+
+ return aData;
+}
+}
+
+namespace drawinglayer::processor2d
+{
+CairoPixelProcessor2D::CairoPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation)
+ : BaseProcessor2D(rViewInformation)
+ , maBColorModifierStack()
+ , mpRT(nullptr)
+{
+}
+
+CairoPixelProcessor2D::CairoPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation,
+ cairo_surface_t* pTarget)
+ : BaseProcessor2D(rViewInformation)
+ , maBColorModifierStack()
+ , mpRT(nullptr)
+{
+ if (pTarget)
+ {
+ cairo_t* pRT = cairo_create(pTarget);
+ cairo_set_antialias(pRT, rViewInformation.getUseAntiAliasing() ? CAIRO_ANTIALIAS_DEFAULT
+ : CAIRO_ANTIALIAS_NONE);
+ setRenderTarget(pRT);
+ }
+}
+
+CairoPixelProcessor2D::~CairoPixelProcessor2D()
+{
+ if (mpRT)
+ cairo_destroy(mpRT);
+}
+
+void CairoPixelProcessor2D::processPolygonHairlinePrimitive2D(
+ const primitive2d::PolygonHairlinePrimitive2D& rPolygonHairlinePrimitive2D)
+{
+ const basegfx::B2DPolygon& rPolygon(rPolygonHairlinePrimitive2D.getB2DPolygon());
+
+ if (!rPolygon.count())
+ return;
+
+ cairo_save(mpRT);
+
+ cairo_matrix_t aMatrix;
+ const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
+ const basegfx::B2DHomMatrix& rObjectToView(
+ getViewInformation2D().getObjectToViewTransformation());
+ cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), rObjectToView.c(),
+ rObjectToView.d(), rObjectToView.e() + fAAOffset,
+ rObjectToView.f() + fAAOffset);
+
+ // set linear transformation
+ cairo_set_matrix(mpRT, &aMatrix);
+
+ const basegfx::BColor aHairlineColor(
+ maBColorModifierStack.getModifiedColor(rPolygonHairlinePrimitive2D.getBColor()));
+ cairo_set_source_rgb(mpRT, aHairlineColor.getRed(), aHairlineColor.getGreen(),
+ aHairlineColor.getBlue());
+
+ // TODO: Unfortunately Direct2D paint of one pixel wide lines does not
+ // correctly and completely blend 100% over the background. Experimenting
+ // shows that a value around/slightly below 2.0 is needed which hints that
+ // alpha blending the half-shifted lines (see fAAOffset above) is involved.
+ // To get correct blending I try to use just wider hairlines for now. This
+ // may need to be improved - or balanced (trying sqrt(2) now...)
+ cairo_set_line_width(mpRT, 1.44f);
+
+ addB2DPolygonToPathGeometry(mpRT, rPolygon, &getViewInformation2D());
+
+ cairo_stroke(mpRT);
+
+ cairo_restore(mpRT);
+}
+
+void CairoPixelProcessor2D::processPolyPolygonColorPrimitive2D(
+ const primitive2d::PolyPolygonColorPrimitive2D& rPolyPolygonColorPrimitive2D)
+{
+ const basegfx::B2DPolyPolygon& rPolyPolygon(rPolyPolygonColorPrimitive2D.getB2DPolyPolygon());
+ const sal_uInt32 nCount(rPolyPolygon.count());
+
+ if (!nCount)
+ return;
+
+ cairo_save(mpRT);
+
+ cairo_matrix_t aMatrix;
+ const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
+ const basegfx::B2DHomMatrix& rObjectToView(
+ getViewInformation2D().getObjectToViewTransformation());
+ cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), rObjectToView.c(),
+ rObjectToView.d(), rObjectToView.e() + fAAOffset,
+ rObjectToView.f() + fAAOffset);
+
+ // set linear transformation
+ cairo_set_matrix(mpRT, &aMatrix);
+
+ const basegfx::BColor aFillColor(
+ maBColorModifierStack.getModifiedColor(rPolyPolygonColorPrimitive2D.getBColor()));
+ cairo_set_source_rgb(mpRT, aFillColor.getRed(), aFillColor.getGreen(), aFillColor.getBlue());
+
+ for (const auto& rPolygon : rPolyPolygon)
+ addB2DPolygonToPathGeometry(mpRT, rPolygon, &getViewInformation2D());
+
+ cairo_fill(mpRT);
+
+ cairo_restore(mpRT);
+}
+
+void CairoPixelProcessor2D::processBitmapPrimitive2D(
+ const primitive2d::BitmapPrimitive2D& rBitmapCandidate)
+{
+ // check if graphic content is inside discrete local ViewPort
+ const basegfx::B2DRange& rDiscreteViewPort(getViewInformation2D().getDiscreteViewport());
+ const basegfx::B2DHomMatrix aLocalTransform(
+ getViewInformation2D().getObjectToViewTransformation() * 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;
+ }
+ }
+
+ BitmapEx aBitmapEx(rBitmapCandidate.getBitmap());
+
+ if (aBitmapEx.IsEmpty() || aBitmapEx.GetSizePixel().IsEmpty())
+ {
+ return;
+ }
+
+ if (maBColorModifierStack.count())
+ {
+ aBitmapEx = aBitmapEx.ModifyBitmapEx(maBColorModifierStack);
+
+ if (aBitmapEx.IsEmpty())
+ {
+ // color gets completely replaced, get it
+ const basegfx::BColor aModifiedColor(
+ maBColorModifierStack.getModifiedColor(basegfx::BColor()));
+
+ // use unit geometry as fallback object geometry. Do *not*
+ // transform, the below used method will use the already
+ // correctly initialized local ViewInformation
+ basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon());
+
+ rtl::Reference<primitive2d::PolyPolygonColorPrimitive2D> xTemp(
+ new primitive2d::PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aPolygon),
+ aModifiedColor));
+ processPolyPolygonColorPrimitive2D(*xTemp);
+ return;
+ }
+ }
+
+ // nasty copy of bitmap data
+ std::vector<sal_uInt8> aPixelData(createBitmapData(aBitmapEx));
+ const Size& rSizePixel(aBitmapEx.GetSizePixel());
+ cairo_surface_t* pBitmapSurface = cairo_image_surface_create_for_data(
+ aPixelData.data(), CAIRO_FORMAT_ARGB32, rSizePixel.Width(), rSizePixel.Height(),
+ cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, rSizePixel.Width()));
+
+ cairo_save(mpRT);
+
+ cairo_matrix_t aMatrix;
+ const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
+ cairo_matrix_init(&aMatrix, aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(),
+ aLocalTransform.d(), aLocalTransform.e() + fAAOffset,
+ aLocalTransform.f() + fAAOffset);
+
+ // set linear transformation
+ cairo_set_matrix(mpRT, &aMatrix);
+
+ // destinationRectangle is part of transformation above, so use UnitRange
+ cairo_rectangle(mpRT, 0, 0, 1, 1);
+ cairo_clip(mpRT);
+
+ cairo_set_source_surface(mpRT, pBitmapSurface, 0, 0);
+ // get the pattern created by cairo_set_source_surface
+ cairo_pattern_t* sourcepattern = cairo_get_source(mpRT);
+ cairo_pattern_get_matrix(sourcepattern, &aMatrix);
+ // scale to match the current transformation
+ cairo_matrix_scale(&aMatrix, rSizePixel.Width(), rSizePixel.Height());
+ cairo_pattern_set_matrix(sourcepattern, &aMatrix);
+
+ cairo_paint(mpRT);
+
+ cairo_surface_destroy(pBitmapSurface);
+
+ cairo_restore(mpRT);
+}
+
+namespace
+{
+// This bit-tweaking looping is unpleasant and unfortunate
+void LuminanceToAlpha(cairo_surface_t* pMask)
+{
+ cairo_surface_flush(pMask);
+
+ int nWidth = cairo_image_surface_get_width(pMask);
+ int nHeight = cairo_image_surface_get_height(pMask);
+ int nStride = cairo_image_surface_get_stride(pMask);
+ unsigned char* mask_surface_data = cairo_image_surface_get_data(pMask);
+
+ // include/basegfx/color/bcolormodifier.hxx
+ const double nRedMul = 0.2125 / 255.0;
+ const double nGreenMul = 0.7154 / 255.0;
+ const double nBlueMul = 0.0721 / 255.0;
+ for (int y = 0; y < nHeight; ++y)
+ {
+ unsigned char* pMaskPixelData = mask_surface_data + (nStride * y);
+ for (int x = 0; x < nWidth; ++x)
+ {
+ double fLuminance = pMaskPixelData[SVP_CAIRO_RED] * nRedMul
+ + pMaskPixelData[SVP_CAIRO_GREEN] * nGreenMul
+ + pMaskPixelData[SVP_CAIRO_BLUE] * nBlueMul;
+ // Only this alpha channel is taken into account by cairo_mask_surface
+ // so reuse this surface for the alpha result
+ pMaskPixelData[SVP_CAIRO_ALPHA] = 255.0 * fLuminance;
+ pMaskPixelData += 4;
+ }
+ }
+
+ cairo_surface_mark_dirty(pMask);
+}
+}
+
+void CairoPixelProcessor2D::processTransparencePrimitive2D(
+ const primitive2d::TransparencePrimitive2D& rTransCandidate)
+{
+ if (rTransCandidate.getChildren().empty())
+ return;
+
+ if (rTransCandidate.getTransparence().empty())
+ return;
+
+ cairo_surface_t* pTarget = cairo_get_target(mpRT);
+
+ double clip_x1, clip_x2, clip_y1, clip_y2;
+ cairo_clip_extents(mpRT, &clip_x1, &clip_y1, &clip_x2, &clip_y2);
+
+ // calculate visible range, create only for that range
+ basegfx::B2DRange aDiscreteRange(
+ rTransCandidate.getChildren().getB2DRange(getViewInformation2D()));
+ aDiscreteRange.transform(getViewInformation2D().getObjectToViewTransformation());
+ const basegfx::B2DRange aViewRange(basegfx::B2DPoint(clip_x1, clip_y1),
+ basegfx::B2DPoint(clip_x2, clip_y2));
+ basegfx::B2DRange aVisibleRange(aDiscreteRange);
+ aVisibleRange.intersect(aViewRange);
+
+ if (aVisibleRange.isEmpty())
+ {
+ // not visible, done
+ return;
+ }
+
+ const basegfx::B2DHomMatrix aEmbedTransform(basegfx::utils::createTranslateB2DHomMatrix(
+ -aVisibleRange.getMinX(), -aVisibleRange.getMinY()));
+ geometry::ViewInformation2D aViewInformation2D(getViewInformation2D());
+ aViewInformation2D.setViewTransformation(aEmbedTransform
+ * getViewInformation2D().getViewTransformation());
+ // draw mask to temporary surface
+ cairo_surface_t* pMask = cairo_surface_create_similar_image(pTarget, CAIRO_FORMAT_ARGB32,
+ ceil(aVisibleRange.getWidth()),
+ ceil(aVisibleRange.getHeight()));
+ CairoPixelProcessor2D aMaskRenderer(aViewInformation2D, pMask);
+ aMaskRenderer.process(rTransCandidate.getTransparence());
+
+ // convert mask to something cairo can use
+ LuminanceToAlpha(pMask);
+
+ // draw content to temporary surface
+ cairo_surface_t* pContent = cairo_surface_create_similar(
+ pTarget, cairo_surface_get_content(pTarget), ceil(aVisibleRange.getWidth()),
+ ceil(aVisibleRange.getHeight()));
+ CairoPixelProcessor2D aContent(aViewInformation2D, pContent);
+ aContent.process(rTransCandidate.getChildren());
+
+ // munge the temporary surfaces to our target surface
+ cairo_set_source_surface(mpRT, pContent, aVisibleRange.getMinX(), aVisibleRange.getMinY());
+ cairo_mask_surface(mpRT, pMask, aVisibleRange.getMinX(), aVisibleRange.getMinY());
+
+ cairo_surface_destroy(pContent);
+ cairo_surface_destroy(pMask);
+}
+
+void CairoPixelProcessor2D::processMaskPrimitive2DPixel(
+ const primitive2d::MaskPrimitive2D& rMaskCandidate)
+{
+ if (rMaskCandidate.getChildren().empty())
+ return;
+
+ basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask());
+
+ if (!aMask.count())
+ return;
+
+ double clip_x1, clip_x2, clip_y1, clip_y2;
+ cairo_clip_extents(mpRT, &clip_x1, &clip_y1, &clip_x2, &clip_y2);
+
+ basegfx::B2DRange aMaskRange(aMask.getB2DRange());
+ aMaskRange.transform(getViewInformation2D().getObjectToViewTransformation());
+ const basegfx::B2DRange aViewRange(basegfx::B2DPoint(clip_x1, clip_y1),
+ basegfx::B2DPoint(clip_x2, clip_y2));
+
+ if (!aViewRange.overlaps(aMaskRange))
+ return;
+
+ cairo_save(mpRT);
+
+ cairo_matrix_t aMatrix;
+ const basegfx::B2DHomMatrix& rObjectToView(
+ getViewInformation2D().getObjectToViewTransformation());
+ cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), rObjectToView.c(),
+ rObjectToView.d(), rObjectToView.e(), rObjectToView.f());
+
+ // set linear transformation
+ cairo_set_matrix(mpRT, &aMatrix);
+
+ // put mask as path
+ for (const auto& rPolygon : aMask)
+ addB2DPolygonToPathGeometry(mpRT, rPolygon, &getViewInformation2D());
+
+ // clip to this mask
+ cairo_clip(mpRT);
+
+ process(rMaskCandidate.getChildren());
+
+ cairo_restore(mpRT);
+}
+
+void CairoPixelProcessor2D::processPointArrayPrimitive2D(
+ const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate)
+{
+ const std::vector<basegfx::B2DPoint>& rPositions(rPointArrayCandidate.getPositions());
+ if (rPositions.empty())
+ return;
+
+ const basegfx::BColor aPointColor(
+ maBColorModifierStack.getModifiedColor(rPointArrayCandidate.getRGBColor()));
+ cairo_set_source_rgb(mpRT, aPointColor.getRed(), aPointColor.getGreen(), aPointColor.getBlue());
+
+ // To really paint a single pixel I found nothing better than
+ // switch off AA and draw a pixel-aligned rectangle
+ const cairo_antialias_t eOldAAMode(cairo_get_antialias(mpRT));
+ cairo_set_antialias(mpRT, CAIRO_ANTIALIAS_NONE);
+
+ for (auto const& pos : rPositions)
+ {
+ const basegfx::B2DPoint aDiscretePos(getViewInformation2D().getObjectToViewTransformation()
+ * pos);
+ const double fX(ceil(aDiscretePos.getX()));
+ const double fY(ceil(aDiscretePos.getY()));
+
+ cairo_rectangle(mpRT, fX, fY, 1, 1);
+ cairo_fill(mpRT);
+ }
+
+ cairo_set_antialias(mpRT, eOldAAMode);
+}
+
+void CairoPixelProcessor2D::processModifiedColorPrimitive2D(
+ const primitive2d::ModifiedColorPrimitive2D& rModifiedCandidate)
+{
+ if (!rModifiedCandidate.getChildren().empty())
+ {
+ maBColorModifierStack.push(rModifiedCandidate.getColorModifier());
+ process(rModifiedCandidate.getChildren());
+ maBColorModifierStack.pop();
+ }
+}
+
+void CairoPixelProcessor2D::processTransformPrimitive2D(
+ const primitive2d::TransformPrimitive2D& rTransformCandidate)
+{
+ // remember current transformation and ViewInformation
+ const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
+
+ // create new transformations for local ViewInformation2D
+ geometry::ViewInformation2D aViewInformation2D(getViewInformation2D());
+ aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation()
+ * rTransformCandidate.getTransformation());
+ updateViewInformation(aViewInformation2D);
+
+ // process content
+ process(rTransformCandidate.getChildren());
+
+ // restore transformations
+ updateViewInformation(aLastViewInformation2D);
+}
+
+void CairoPixelProcessor2D::processPolygonStrokePrimitive2D(
+ const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokeCandidate)
+{
+ const basegfx::B2DPolygon& rPolygon(rPolygonStrokeCandidate.getB2DPolygon());
+ const attribute::LineAttribute& rLineAttribute(rPolygonStrokeCandidate.getLineAttribute());
+
+ if (!rPolygon.count() || rLineAttribute.getWidth() < 0.0)
+ {
+ // no geometry, done
+ return;
+ }
+
+ // get some values early that might be used for decisions
+ const bool bHairline(0.0 == rLineAttribute.getWidth());
+ const basegfx::B2DHomMatrix& rObjectToView(
+ getViewInformation2D().getObjectToViewTransformation());
+ const double fDiscreteLineWidth(
+ bHairline
+ ? 1.0
+ : (rObjectToView * basegfx::B2DVector(rLineAttribute.getWidth(), 0.0)).getLength());
+
+ // Here for every combination which the system-specific implementation is not
+ // capable of visualizing, use the (for decomposable Primitives always possible)
+ // fallback to the decomposition.
+ if (basegfx::B2DLineJoin::NONE == rLineAttribute.getLineJoin() && fDiscreteLineWidth > 1.5)
+ {
+ // basegfx::B2DLineJoin::NONE is special for our office, no other GraphicSystem
+ // knows that (so far), so fallback to decomposition. This is only needed if
+ // LineJoin will be used, so also check for discrete LineWidth before falling back
+ process(rPolygonStrokeCandidate);
+ return;
+ }
+
+ // This is a method every system-specific implementation of a decomposable Primitive
+ // can use to allow simple optical control of paint implementation:
+ // Create a copy, e.g. change color to 'red' as here and paint before the system
+ // paints it using the decomposition. That way you can - if active - directly
+ // optically compare if the system-specific solution is geometrically identical to
+ // the decomposition (which defines our interpretation that we need to visualize).
+ // Look below in the impl for bRenderDecomposeForCompareInRed to see that in that case
+ // we create a half-transparent paint to better support visual control
+ static bool bRenderDecomposeForCompareInRed(false);
+
+ if (bRenderDecomposeForCompareInRed)
+ {
+ const attribute::LineAttribute aRed(
+ basegfx::BColor(1.0, 0.0, 0.0), rLineAttribute.getWidth(), rLineAttribute.getLineJoin(),
+ rLineAttribute.getLineCap(), rLineAttribute.getMiterMinimumAngle());
+ rtl::Reference<primitive2d::PolygonStrokePrimitive2D> xCopy(
+ new primitive2d::PolygonStrokePrimitive2D(
+ rPolygonStrokeCandidate.getB2DPolygon(), aRed,
+ rPolygonStrokeCandidate.getStrokeAttribute()));
+ process(*xCopy);
+ }
+
+ cairo_save(mpRT);
+
+ cairo_matrix_t aMatrix;
+ const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
+ cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), rObjectToView.c(),
+ rObjectToView.d(), rObjectToView.e() + fAAOffset,
+ rObjectToView.f() + fAAOffset);
+
+ // set linear transformation
+ cairo_set_matrix(mpRT, &aMatrix);
+
+ // setup line attributes
+ cairo_line_join_t eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
+ switch (rLineAttribute.getLineJoin())
+ {
+ case basegfx::B2DLineJoin::Bevel:
+ eCairoLineJoin = CAIRO_LINE_JOIN_BEVEL;
+ break;
+ case basegfx::B2DLineJoin::Round:
+ eCairoLineJoin = CAIRO_LINE_JOIN_ROUND;
+ break;
+ case basegfx::B2DLineJoin::NONE:
+ case basegfx::B2DLineJoin::Miter:
+ eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
+ break;
+ }
+
+ // convert miter minimum angle to miter limit
+ double fMiterLimit
+ = 1.0 / sin(std::max(rLineAttribute.getMiterMinimumAngle(), 0.01 * M_PI) / 2.0);
+
+ // setup cap attribute
+ cairo_line_cap_t eCairoLineCap(CAIRO_LINE_CAP_BUTT);
+
+ switch (rLineAttribute.getLineCap())
+ {
+ default: // css::drawing::LineCap_BUTT:
+ {
+ eCairoLineCap = CAIRO_LINE_CAP_BUTT;
+ break;
+ }
+ case css::drawing::LineCap_ROUND:
+ {
+ eCairoLineCap = CAIRO_LINE_CAP_ROUND;
+ break;
+ }
+ case css::drawing::LineCap_SQUARE:
+ {
+ eCairoLineCap = CAIRO_LINE_CAP_SQUARE;
+ break;
+ }
+ }
+
+ basegfx::BColor aLineColor(maBColorModifierStack.getModifiedColor(rLineAttribute.getColor()));
+ if (bRenderDecomposeForCompareInRed)
+ aLineColor.setRed(0.5);
+
+ cairo_set_source_rgb(mpRT, aLineColor.getRed(), aLineColor.getGreen(), aLineColor.getBlue());
+
+ cairo_set_line_join(mpRT, eCairoLineJoin);
+ cairo_set_line_cap(mpRT, eCairoLineCap);
+
+ // TODO: Hairline LineWidth, see comment at processPolygonHairlinePrimitive2D
+ cairo_set_line_width(mpRT, bHairline ? 1.44 : fDiscreteLineWidth);
+ cairo_set_miter_limit(mpRT, fMiterLimit);
+
+ const attribute::StrokeAttribute& rStrokeAttribute(
+ rPolygonStrokeCandidate.getStrokeAttribute());
+ const bool bDashUsed(!rStrokeAttribute.isDefault()
+ && !rStrokeAttribute.getDotDashArray().empty()
+ && 0.0 < rStrokeAttribute.getFullDotDashLen());
+ if (bDashUsed)
+ {
+ const std::vector<double>& rStroke = rStrokeAttribute.getDotDashArray();
+ cairo_set_dash(mpRT, rStroke.data(), rStroke.size(), 0.0);
+ }
+
+ addB2DPolygonToPathGeometry(mpRT, rPolygon, &getViewInformation2D());
+
+ cairo_stroke(mpRT);
+
+ cairo_restore(mpRT);
+}
+
+void CairoPixelProcessor2D::processLineRectanglePrimitive2D(
+ const primitive2d::LineRectanglePrimitive2D& rLineRectanglePrimitive2D)
+{
+ if (rLineRectanglePrimitive2D.getB2DRange().isEmpty())
+ {
+ // no geometry, done
+ return;
+ }
+
+ cairo_save(mpRT);
+
+ cairo_matrix_t aMatrix;
+ const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
+ const basegfx::B2DHomMatrix& rObjectToView(
+ getViewInformation2D().getObjectToViewTransformation());
+ cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), rObjectToView.c(),
+ rObjectToView.d(), rObjectToView.e() + fAAOffset,
+ rObjectToView.f() + fAAOffset);
+
+ // set linear transformation
+ cairo_set_matrix(mpRT, &aMatrix);
+
+ const basegfx::BColor aHairlineColor(
+ maBColorModifierStack.getModifiedColor(rLineRectanglePrimitive2D.getBColor()));
+ cairo_set_source_rgb(mpRT, aHairlineColor.getRed(), aHairlineColor.getGreen(),
+ aHairlineColor.getBlue());
+
+ const double fDiscreteLineWidth((getViewInformation2D().getInverseObjectToViewTransformation()
+ * basegfx::B2DVector(1.44, 0.0))
+ .getLength());
+ cairo_set_line_width(mpRT, fDiscreteLineWidth);
+
+ const basegfx::B2DRange& rRange(rLineRectanglePrimitive2D.getB2DRange());
+ cairo_rectangle(mpRT, rRange.getMinX(), rRange.getMinY(), rRange.getWidth(),
+ rRange.getHeight());
+ cairo_stroke(mpRT);
+
+ cairo_restore(mpRT);
+}
+
+void CairoPixelProcessor2D::processFilledRectanglePrimitive2D(
+ const primitive2d::FilledRectanglePrimitive2D& rFilledRectanglePrimitive2D)
+{
+ if (rFilledRectanglePrimitive2D.getB2DRange().isEmpty())
+ {
+ // no geometry, done
+ return;
+ }
+
+ cairo_save(mpRT);
+
+ cairo_matrix_t aMatrix;
+ const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
+ const basegfx::B2DHomMatrix& rObjectToView(
+ getViewInformation2D().getObjectToViewTransformation());
+ cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), rObjectToView.c(),
+ rObjectToView.d(), rObjectToView.e() + fAAOffset,
+ rObjectToView.f() + fAAOffset);
+
+ // set linear transformation
+ cairo_set_matrix(mpRT, &aMatrix);
+
+ const basegfx::BColor aFillColor(
+ maBColorModifierStack.getModifiedColor(rFilledRectanglePrimitive2D.getBColor()));
+ cairo_set_source_rgb(mpRT, aFillColor.getRed(), aFillColor.getGreen(), aFillColor.getBlue());
+
+ const basegfx::B2DRange& rRange(rFilledRectanglePrimitive2D.getB2DRange());
+ cairo_rectangle(mpRT, rRange.getMinX(), rRange.getMinY(), rRange.getWidth(),
+ rRange.getHeight());
+ cairo_fill(mpRT);
+
+ cairo_restore(mpRT);
+}
+
+void CairoPixelProcessor2D::processSingleLinePrimitive2D(
+ const primitive2d::SingleLinePrimitive2D& rSingleLinePrimitive2D)
+{
+ cairo_save(mpRT);
+
+ const basegfx::BColor aLineColor(
+ maBColorModifierStack.getModifiedColor(rSingleLinePrimitive2D.getBColor()));
+ cairo_set_source_rgb(mpRT, aLineColor.getRed(), aLineColor.getGreen(), aLineColor.getBlue());
+
+ const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
+ const basegfx::B2DHomMatrix& rObjectToView(
+ getViewInformation2D().getObjectToViewTransformation());
+ const basegfx::B2DPoint aStart(rObjectToView * rSingleLinePrimitive2D.getStart());
+ const basegfx::B2DPoint aEnd(rObjectToView * rSingleLinePrimitive2D.getEnd());
+
+ cairo_set_line_width(mpRT, 1.44f);
+
+ cairo_move_to(mpRT, aStart.getX() + fAAOffset, aStart.getY() + fAAOffset);
+ cairo_line_to(mpRT, aEnd.getX() + fAAOffset, aEnd.getY() + fAAOffset);
+ cairo_stroke(mpRT);
+
+ cairo_restore(mpRT);
+}
+
+void CairoPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
+{
+ switch (rCandidate.getPrimitive2DID())
+ {
+ // geometry that *has* to be processed
+ case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D:
+ {
+ processBitmapPrimitive2D(
+ static_cast<const primitive2d::BitmapPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D:
+ {
+ processPointArrayPrimitive2D(
+ static_cast<const primitive2d::PointArrayPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D:
+ {
+ processPolygonHairlinePrimitive2D(
+ static_cast<const primitive2d::PolygonHairlinePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D:
+ {
+ processPolyPolygonColorPrimitive2D(
+ static_cast<const primitive2d::PolyPolygonColorPrimitive2D&>(rCandidate));
+ break;
+ }
+ // embedding/groups that *have* to be processed
+ case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D:
+ {
+ processTransparencePrimitive2D(
+ static_cast<const primitive2d::TransparencePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_INVERTPRIMITIVE2D:
+ {
+ // TODO: fallback is at VclPixelProcessor2D::processInvertPrimitive2D, so
+ // not in reach. Ignore for now.
+ // processInvertPrimitive2D(rCandidate);
+ break;
+ }
+ case PRIMITIVE2D_ID_MASKPRIMITIVE2D:
+ {
+ processMaskPrimitive2DPixel(
+ static_cast<const primitive2d::MaskPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D:
+ {
+ processModifiedColorPrimitive2D(
+ static_cast<const primitive2d::ModifiedColorPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D:
+ {
+ processTransformPrimitive2D(
+ static_cast<const primitive2d::TransformPrimitive2D&>(rCandidate));
+ break;
+ }
+#if 0
+ // geometry that *may* be processed due to being able to do it better
+ // then using the decomposition
+ case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D:
+ {
+ processUnifiedTransparencePrimitive2D(
+ static_cast<const primitive2d::UnifiedTransparencePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D:
+ {
+ processMarkerArrayPrimitive2D(
+ static_cast<const primitive2d::MarkerArrayPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_BACKGROUNDCOLORPRIMITIVE2D:
+ {
+ processBackgroundColorPrimitive2D(
+ static_cast<const primitive2d::BackgroundColorPrimitive2D&>(rCandidate));
+ break;
+ }
+#endif
+ case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D:
+ {
+ processPolygonStrokePrimitive2D(
+ static_cast<const primitive2d::PolygonStrokePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_LINERECTANGLEPRIMITIVE2D:
+ {
+ processLineRectanglePrimitive2D(
+ static_cast<const primitive2d::LineRectanglePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_FILLEDRECTANGLEPRIMITIVE2D:
+ {
+ processFilledRectanglePrimitive2D(
+ static_cast<const primitive2d::FilledRectanglePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_SINGLELINEPRIMITIVE2D:
+ {
+ processSingleLinePrimitive2D(
+ static_cast<const primitive2d::SingleLinePrimitive2D&>(rCandidate));
+ break;
+ }
+
+ // continue with decompose
+ default:
+ {
+ SAL_INFO("drawinglayer", "default case for " << drawinglayer::primitive2d::idToString(
+ rCandidate.getPrimitive2DID()));
+ // process recursively
+ process(rCandidate);
+ break;
+ }
+ }
+}
+
+} // end of namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/drawinglayer/source/processor2d/contourextractor2d.cxx b/drawinglayer/source/processor2d/contourextractor2d.cxx
new file mode 100644
index 0000000000..65e8ef86a2
--- /dev/null
+++ b/drawinglayer/source/processor2d/contourextractor2d.cxx
@@ -0,0 +1,189 @@
+/* -*- 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
+ geometry::ViewInformation2D aViewInformation2D(getViewInformation2D());
+ aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation());
+ 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/d2dpixelprocessor2d.cxx b/drawinglayer/source/processor2d/d2dpixelprocessor2d.cxx
new file mode 100644
index 0000000000..6bfc958783
--- /dev/null
+++ b/drawinglayer/source/processor2d/d2dpixelprocessor2d.cxx
@@ -0,0 +1,2191 @@
+/* -*- 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>
+
+// win-specific
+#include <prewin.h>
+#include <d2d1.h>
+#include <d2d1_1.h>
+#include <postwin.h>
+
+#include <drawinglayer/processor2d/d2dpixelprocessor2d.hxx>
+#include <drawinglayer/processor2d/SDPRProcessor2dTools.hxx>
+#include <sal/log.hxx>
+#include <vcl/outdev.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
+#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
+#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
+#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx>
+#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
+#include <drawinglayer/primitive2d/backgroundcolorprimitive2d.hxx>
+#include <drawinglayer/primitive2d/baseprimitive2d.hxx>
+#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx>
+#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
+#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
+#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx>
+#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx>
+#include <drawinglayer/primitive2d/Tools.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
+#include <drawinglayer/primitive2d/invertprimitive2d.hxx>
+#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx>
+#include <drawinglayer/converters.hxx>
+#include <basegfx/curve/b2dcubicbezier.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/utils/systemdependentdata.hxx>
+#include <vcl/BitmapReadAccess.hxx>
+#include <vcl/svapp.hxx>
+
+using namespace com::sun::star;
+
+namespace
+{
+class ID2D1GlobalFactoryProvider
+{
+ sal::systools::COMReference<ID2D1Factory> mpD2DFactory;
+
+public:
+ ID2D1GlobalFactoryProvider()
+ : mpD2DFactory(nullptr)
+ {
+ const HRESULT hr(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
+ __uuidof(ID2D1Factory), nullptr,
+ reinterpret_cast<void**>(&mpD2DFactory)));
+
+ if (!SUCCEEDED(hr))
+ mpD2DFactory.clear();
+ }
+
+ sal::systools::COMReference<ID2D1Factory>& getID2D1Factory() { return mpD2DFactory; }
+};
+
+ID2D1GlobalFactoryProvider aID2D1GlobalFactoryProvider;
+
+class ID2D1GlobalRenderTargetProvider
+{
+ sal::systools::COMReference<ID2D1DCRenderTarget> mpID2D1DCRenderTarget;
+
+public:
+ ID2D1GlobalRenderTargetProvider()
+ : mpID2D1DCRenderTarget()
+ {
+ }
+
+ sal::systools::COMReference<ID2D1DCRenderTarget>& getID2D1DCRenderTarget()
+ {
+ if (!mpID2D1DCRenderTarget && aID2D1GlobalFactoryProvider.getID2D1Factory())
+ {
+ const D2D1_RENDER_TARGET_PROPERTIES aRTProps(D2D1::RenderTargetProperties(
+ D2D1_RENDER_TARGET_TYPE_DEFAULT,
+ D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM,
+ D2D1_ALPHA_MODE_IGNORE), //D2D1_ALPHA_MODE_PREMULTIPLIED),
+ 0, 0, D2D1_RENDER_TARGET_USAGE_NONE, D2D1_FEATURE_LEVEL_DEFAULT));
+
+ const HRESULT hr(aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateDCRenderTarget(
+ &aRTProps, &mpID2D1DCRenderTarget));
+
+ // interestingly this ID2D1DCRenderTarget already works and can hold
+ // created ID2D1Bitmap(s) in RenderTarget-specific form, *without*
+ // any call to "BindDC", thus *without* the need of a real HDC - nice :-)
+ // When that would be needed, Application::GetDefaultDevice() would need
+ // to have a HDC that is valid during LO's lifetime.
+
+ if (!SUCCEEDED(hr))
+ mpID2D1DCRenderTarget.clear();
+ }
+
+ return mpID2D1DCRenderTarget;
+ }
+};
+
+ID2D1GlobalRenderTargetProvider aID2D1GlobalRenderTargetProvider;
+
+class SystemDependentData_ID2D1PathGeometry : public basegfx::SystemDependentData
+{
+private:
+ sal::systools::COMReference<ID2D1PathGeometry> mpID2D1PathGeometry;
+
+public:
+ SystemDependentData_ID2D1PathGeometry(
+ sal::systools::COMReference<ID2D1PathGeometry>& rID2D1PathGeometry)
+ : basegfx::SystemDependentData(Application::GetSystemDependentDataManager())
+ , mpID2D1PathGeometry(rID2D1PathGeometry)
+ {
+ }
+
+ const sal::systools::COMReference<ID2D1PathGeometry>& getID2D1PathGeometry() const
+ {
+ return mpID2D1PathGeometry;
+ }
+ virtual sal_Int64 estimateUsageInBytes() const override;
+};
+
+sal_Int64 SystemDependentData_ID2D1PathGeometry::estimateUsageInBytes() const
+{
+ sal_Int64 aRetval(0);
+
+ if (getID2D1PathGeometry())
+ {
+ UINT32 nCount(0);
+ const HRESULT hr(getID2D1PathGeometry()->GetSegmentCount(&nCount));
+
+ if (SUCCEEDED(hr))
+ {
+ // without completely receiving and tracing the GeometrySink
+ // do a rough estimation - each segment is 2D, so has two doubles.
+ // Some are beziers, so add some guessed buffer for two additional
+ // control points
+ aRetval = static_cast<sal_Int64>(nCount) * (6 * sizeof(double));
+ }
+ }
+
+ return aRetval;
+}
+
+basegfx::B2DPoint impPixelSnap(const basegfx::B2DPolygon& rPolygon,
+ const drawinglayer::geometry::ViewInformation2D& rViewInformation,
+ sal_uInt32 nIndex)
+{
+ const sal_uInt32 nCount(rPolygon.count());
+
+ // get the data
+ const basegfx::B2ITuple aPrevTuple(
+ basegfx::fround(rViewInformation.getObjectToViewTransformation()
+ * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount)));
+ const basegfx::B2DPoint aCurrPoint(rViewInformation.getObjectToViewTransformation()
+ * rPolygon.getB2DPoint(nIndex));
+ const basegfx::B2ITuple aCurrTuple(basegfx::fround(aCurrPoint));
+ const basegfx::B2ITuple aNextTuple(
+ basegfx::fround(rViewInformation.getObjectToViewTransformation()
+ * rPolygon.getB2DPoint((nIndex + 1) % nCount)));
+
+ // get the states
+ const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX());
+ const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX());
+ const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY());
+ const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY());
+ const bool bSnapX(bPrevVertical || bNextVertical);
+ const bool bSnapY(bPrevHorizontal || bNextHorizontal);
+
+ if (bSnapX || bSnapY)
+ {
+ basegfx::B2DPoint aSnappedPoint(bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(),
+ bSnapY ? aCurrTuple.getY() : aCurrPoint.getY());
+
+ aSnappedPoint *= rViewInformation.getInverseObjectToViewTransformation();
+
+ return aSnappedPoint;
+ }
+
+ return rPolygon.getB2DPoint(nIndex);
+}
+
+void addB2DPolygonToPathGeometry(sal::systools::COMReference<ID2D1GeometrySink>& rSink,
+ const basegfx::B2DPolygon& rPolygon,
+ const drawinglayer::geometry::ViewInformation2D* pViewInformation)
+{
+ const sal_uInt32 nPointCount(rPolygon.count());
+ const sal_uInt32 nEdgeCount(rPolygon.isClosed() ? nPointCount : nPointCount - 1);
+ basegfx::B2DCubicBezier aEdge;
+
+ for (sal_uInt32 a(0); a < nEdgeCount; a++)
+ {
+ rPolygon.getBezierSegment(a, aEdge);
+
+ const basegfx::B2DPoint aEndPoint(
+ nullptr == pViewInformation
+ ? aEdge.getEndPoint()
+ : impPixelSnap(rPolygon, *pViewInformation, (a + 1) % nPointCount));
+
+ if (aEdge.isBezier())
+ {
+ rSink->AddBezier(
+ D2D1::BezierSegment(D2D1::Point2F(aEdge.getControlPointA().getX(),
+ aEdge.getControlPointA().getY()), //C1
+ D2D1::Point2F(aEdge.getControlPointB().getX(),
+ aEdge.getControlPointB().getY()), //c2
+ D2D1::Point2F(aEndPoint.getX(), aEndPoint.getY()))); //end
+ }
+ else
+ {
+ rSink->AddLine(D2D1::Point2F(aEndPoint.getX(), aEndPoint.getY()));
+ }
+ }
+}
+
+std::shared_ptr<SystemDependentData_ID2D1PathGeometry>
+getOrCreatePathGeometry(const basegfx::B2DPolygon& rPolygon,
+ const drawinglayer::geometry::ViewInformation2D& rViewInformation)
+{
+ // try to access buffered data
+ std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1PathGeometry(
+ rPolygon.getSystemDependentData<SystemDependentData_ID2D1PathGeometry>());
+
+ if (pSystemDependentData_ID2D1PathGeometry)
+ {
+ if (rViewInformation.getPixelSnapHairline())
+ {
+ // do not buffer when PixelSnap is active
+ pSystemDependentData_ID2D1PathGeometry.reset();
+ }
+ else
+ {
+ // use and return buffered data
+ return pSystemDependentData_ID2D1PathGeometry;
+ }
+ }
+
+ sal::systools::COMReference<ID2D1PathGeometry> pID2D1PathGeometry;
+ HRESULT hr(
+ aID2D1GlobalFactoryProvider.getID2D1Factory()->CreatePathGeometry(&pID2D1PathGeometry));
+ const sal_uInt32 nPointCount(rPolygon.count());
+
+ if (SUCCEEDED(hr) && nPointCount)
+ {
+ sal::systools::COMReference<ID2D1GeometrySink> pSink;
+ hr = pID2D1PathGeometry->Open(&pSink);
+
+ if (SUCCEEDED(hr) && pSink)
+ {
+ const basegfx::B2DPoint aStart(rViewInformation.getPixelSnapHairline()
+ ? rPolygon.getB2DPoint(0)
+ : impPixelSnap(rPolygon, rViewInformation, 0));
+
+ pSink->BeginFigure(D2D1::Point2F(aStart.getX(), aStart.getY()),
+ D2D1_FIGURE_BEGIN_HOLLOW);
+ addB2DPolygonToPathGeometry(pSink, rPolygon, &rViewInformation);
+ pSink->EndFigure(rPolygon.isClosed() ? D2D1_FIGURE_END_CLOSED : D2D1_FIGURE_END_OPEN);
+ pSink->Close();
+ }
+ }
+
+ // add to buffering mechanism
+ if (pID2D1PathGeometry)
+ {
+ if (rViewInformation.getPixelSnapHairline() || nPointCount <= 4)
+ {
+ // do not buffer when PixelSnap is active or small polygon
+ return std::make_shared<SystemDependentData_ID2D1PathGeometry>(pID2D1PathGeometry);
+ }
+ else
+ {
+ return rPolygon.addOrReplaceSystemDependentData<SystemDependentData_ID2D1PathGeometry>(
+ pID2D1PathGeometry);
+ }
+ }
+
+ return std::shared_ptr<SystemDependentData_ID2D1PathGeometry>();
+}
+
+std::shared_ptr<SystemDependentData_ID2D1PathGeometry>
+getOrCreateFillGeometry(const basegfx::B2DPolyPolygon& rPolyPolygon)
+{
+ // try to access buffered data
+ std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1PathGeometry(
+ rPolyPolygon.getSystemDependentData<SystemDependentData_ID2D1PathGeometry>());
+
+ if (pSystemDependentData_ID2D1PathGeometry)
+ {
+ // use and return buffered data
+ return pSystemDependentData_ID2D1PathGeometry;
+ }
+
+ sal::systools::COMReference<ID2D1PathGeometry> pID2D1PathGeometry;
+ HRESULT hr(
+ aID2D1GlobalFactoryProvider.getID2D1Factory()->CreatePathGeometry(&pID2D1PathGeometry));
+ const sal_uInt32 nCount(rPolyPolygon.count());
+
+ if (SUCCEEDED(hr) && nCount)
+ {
+ sal::systools::COMReference<ID2D1GeometrySink> pSink;
+ hr = pID2D1PathGeometry->Open(&pSink);
+
+ if (SUCCEEDED(hr) && pSink)
+ {
+ for (sal_uInt32 a(0); a < nCount; a++)
+ {
+ const basegfx::B2DPolygon& rPolygon(rPolyPolygon.getB2DPolygon(a));
+ const sal_uInt32 nPointCount(rPolygon.count());
+
+ if (nPointCount)
+ {
+ const basegfx::B2DPoint aStart(rPolygon.getB2DPoint(0));
+
+ pSink->BeginFigure(D2D1::Point2F(aStart.getX(), aStart.getY()),
+ D2D1_FIGURE_BEGIN_FILLED);
+ addB2DPolygonToPathGeometry(pSink, rPolygon, nullptr);
+ pSink->EndFigure(D2D1_FIGURE_END_CLOSED);
+ }
+ }
+
+ pSink->Close();
+ }
+ }
+
+ // add to buffering mechanism
+ if (pID2D1PathGeometry)
+ {
+ return rPolyPolygon.addOrReplaceSystemDependentData<SystemDependentData_ID2D1PathGeometry>(
+ pID2D1PathGeometry);
+ }
+
+ return std::shared_ptr<SystemDependentData_ID2D1PathGeometry>();
+}
+
+class SystemDependentData_ID2D1Bitmap : public basegfx::SystemDependentData
+{
+private:
+ sal::systools::COMReference<ID2D1Bitmap> mpD2DBitmap;
+ const std::shared_ptr<SalBitmap> maAssociatedAlpha;
+
+public:
+ SystemDependentData_ID2D1Bitmap(sal::systools::COMReference<ID2D1Bitmap>& rD2DBitmap,
+ const std::shared_ptr<SalBitmap>& rAssociatedAlpha)
+ : basegfx::SystemDependentData(Application::GetSystemDependentDataManager())
+ , mpD2DBitmap(rD2DBitmap)
+ , maAssociatedAlpha(rAssociatedAlpha)
+ {
+ }
+
+ const sal::systools::COMReference<ID2D1Bitmap>& getID2D1Bitmap() const { return mpD2DBitmap; }
+ const std::shared_ptr<SalBitmap>& getAssociatedAlpha() const { return maAssociatedAlpha; }
+
+ virtual sal_Int64 estimateUsageInBytes() const override;
+};
+
+sal_Int64 SystemDependentData_ID2D1Bitmap::estimateUsageInBytes() const
+{
+ sal_Int64 aRetval(0);
+
+ if (getID2D1Bitmap())
+ {
+ // use factor 4 for RGBA_8 as estimation
+ const D2D1_SIZE_U aSizePixel(getID2D1Bitmap()->GetPixelSize());
+ aRetval = static_cast<sal_Int64>(aSizePixel.width)
+ * static_cast<sal_Int64>(aSizePixel.height) * 4;
+ }
+
+ return aRetval;
+}
+
+sal::systools::COMReference<ID2D1Bitmap> createB2DBitmap(const BitmapEx& rBitmapEx)
+{
+ const Size& rSizePixel(rBitmapEx.GetSizePixel());
+ const bool bAlpha(rBitmapEx.IsAlpha());
+ const sal_uInt32 nPixelCount(rSizePixel.Width() * rSizePixel.Height());
+ std::unique_ptr<sal_uInt32[]> aData(new sal_uInt32[nPixelCount]);
+ sal_uInt32* pTarget = aData.get();
+
+ if (bAlpha)
+ {
+ Bitmap aSrcAlpha(rBitmapEx.GetAlphaMask().GetBitmap());
+ BitmapScopedReadAccess pReadAccess(rBitmapEx.GetBitmap());
+ BitmapScopedReadAccess pAlphaReadAccess(aSrcAlpha);
+ const tools::Long nHeight(pReadAccess->Height());
+ const tools::Long nWidth(pReadAccess->Width());
+
+ for (tools::Long y = 0; y < nHeight; ++y)
+ {
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ const BitmapColor aColor(pReadAccess->GetColor(y, x));
+ const BitmapColor aAlpha(pAlphaReadAccess->GetColor(y, x));
+ const sal_uInt16 nAlpha(aAlpha.GetRed());
+
+ *pTarget++ = sal_uInt32(BitmapColor(
+ ColorAlpha, sal_uInt8((sal_uInt16(aColor.GetRed()) * nAlpha) >> 8),
+ sal_uInt8((sal_uInt16(aColor.GetGreen()) * nAlpha) >> 8),
+ sal_uInt8((sal_uInt16(aColor.GetBlue()) * nAlpha) >> 8), aAlpha.GetRed()));
+ }
+ }
+ }
+ else
+ {
+ BitmapScopedReadAccess pReadAccess(const_cast<Bitmap&>(rBitmapEx.GetBitmap()));
+ const tools::Long nHeight(pReadAccess->Height());
+ const tools::Long nWidth(pReadAccess->Width());
+
+ for (tools::Long y = 0; y < nHeight; ++y)
+ {
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ const BitmapColor aColor(pReadAccess->GetColor(y, x));
+ *pTarget++ = sal_uInt32(aColor);
+ }
+ }
+ }
+
+ // use GlobalRenderTarget to allow usage combined with
+ // the Direct2D CreateSharedBitmap-mechanism. This is needed
+ // since ID2D1Bitmap is a ID2D1RenderTarget-dependent resource
+ // and thus - in principle - would have to be re-created for
+ // *each* new ID2D1RenderTarget, that means for *each* new
+ // target HDC, resp. OutputDevice
+ sal::systools::COMReference<ID2D1Bitmap> pID2D1Bitmap;
+
+ if (aID2D1GlobalRenderTargetProvider.getID2D1DCRenderTarget())
+ {
+ const HRESULT hr(aID2D1GlobalRenderTargetProvider.getID2D1DCRenderTarget()->CreateBitmap(
+ D2D1::SizeU(rSizePixel.Width(), rSizePixel.Height()), &aData[0],
+ rSizePixel.Width() * sizeof(sal_uInt32),
+ D2D1::BitmapProperties(
+ D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, // DXGI_FORMAT
+ bAlpha ? D2D1_ALPHA_MODE_PREMULTIPLIED
+ : D2D1_ALPHA_MODE_IGNORE)), // D2D1_ALPHA_MODE
+ &pID2D1Bitmap));
+
+ if (!SUCCEEDED(hr))
+ pID2D1Bitmap.clear();
+ }
+
+ return pID2D1Bitmap;
+}
+
+sal::systools::COMReference<ID2D1Bitmap>
+getOrCreateB2DBitmap(sal::systools::COMReference<ID2D1RenderTarget>& rRT, const BitmapEx& rBitmapEx)
+{
+ const basegfx::SystemDependentDataHolder* pHolder(
+ rBitmapEx.GetBitmap().accessSystemDependentDataHolder());
+ std::shared_ptr<SystemDependentData_ID2D1Bitmap> pSystemDependentData_ID2D1Bitmap;
+
+ if (nullptr != pHolder)
+ {
+ // try to access SystemDependentDataHolder and buffered data
+ pSystemDependentData_ID2D1Bitmap
+ = std::static_pointer_cast<SystemDependentData_ID2D1Bitmap>(
+ pHolder->getSystemDependentData(
+ typeid(SystemDependentData_ID2D1Bitmap).hash_code()));
+
+ // check data validity for associated Alpha
+ if (pSystemDependentData_ID2D1Bitmap
+ && pSystemDependentData_ID2D1Bitmap->getAssociatedAlpha()
+ != rBitmapEx.GetAlphaMask().GetBitmap().ImplGetSalBitmap())
+ {
+ // AssociatedAlpha did change, data invalid
+ pSystemDependentData_ID2D1Bitmap.reset();
+ }
+ }
+
+ if (!pSystemDependentData_ID2D1Bitmap)
+ {
+ // have to create newly
+ sal::systools::COMReference<ID2D1Bitmap> pID2D1Bitmap(createB2DBitmap(rBitmapEx));
+
+ if (pID2D1Bitmap)
+ {
+ // creation worked, create SystemDependentData_ID2D1Bitmap
+ pSystemDependentData_ID2D1Bitmap = std::make_shared<SystemDependentData_ID2D1Bitmap>(
+ pID2D1Bitmap, rBitmapEx.GetAlphaMask().GetBitmap().ImplGetSalBitmap());
+
+ // only add if feasible
+ if (nullptr != pHolder
+ && pSystemDependentData_ID2D1Bitmap->calculateCombinedHoldCyclesInSeconds() > 0)
+ {
+ basegfx::SystemDependentData_SharedPtr r2(pSystemDependentData_ID2D1Bitmap);
+ const_cast<basegfx::SystemDependentDataHolder*>(pHolder)
+ ->addOrReplaceSystemDependentData(r2);
+ }
+ }
+ }
+
+ sal::systools::COMReference<ID2D1Bitmap> pWrappedD2DBitmap;
+
+ if (pSystemDependentData_ID2D1Bitmap)
+ {
+ // embed to CreateSharedBitmap, that makes it usable on
+ // the specified RenderTarget
+ const HRESULT hr(rRT->CreateSharedBitmap(
+ __uuidof(ID2D1Bitmap),
+ static_cast<void*>(pSystemDependentData_ID2D1Bitmap->getID2D1Bitmap()), nullptr,
+ &pWrappedD2DBitmap));
+
+ if (!SUCCEEDED(hr))
+ pWrappedD2DBitmap.clear();
+ }
+
+ return pWrappedD2DBitmap;
+}
+
+// This is a simple local derivation of D2DPixelProcessor2D to be used
+// when sub-content needs to be rendered to pixels. Hand over the adapted
+// ViewInformation2D, a pixel size and the parent RenderTarget. It will
+// locally create and use a ID2D1BitmapRenderTarget to render the stuff
+// (you need to call process() with the primitives to be painted of
+// course). Then use the local helper getID2D1Bitmap() to access the
+// ID2D1Bitmap which was the target of that operation.
+class D2DBitmapPixelProcessor2D final : public drawinglayer::processor2d::D2DPixelProcessor2D
+{
+ // the local ID2D1BitmapRenderTarget
+ sal::systools::COMReference<ID2D1BitmapRenderTarget> mpBitmapRenderTarget;
+
+public:
+ // helper class to create another instance of D2DPixelProcessor2D for
+ // creating helper-ID2D1Bitmap's for a given ID2D1RenderTarget
+ D2DBitmapPixelProcessor2D(const drawinglayer::geometry::ViewInformation2D& rViewInformation,
+ sal_uInt32 nWidth, sal_uInt32 nHeight,
+ const sal::systools::COMReference<ID2D1RenderTarget>& rParent)
+ : drawinglayer::processor2d::D2DPixelProcessor2D(rViewInformation)
+ , mpBitmapRenderTarget()
+ {
+ if (0 == nWidth || 0 == nHeight)
+ {
+ // no width/height, done
+ increaseError();
+ }
+
+ if (!hasError())
+ {
+ // Allocate compatible RGBA render target
+ const D2D1_SIZE_U aRenderTargetSizePixel(D2D1::SizeU(nWidth, nHeight));
+ const HRESULT hr(rParent->CreateCompatibleRenderTarget(
+ nullptr, &aRenderTargetSizePixel, nullptr,
+ D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, &mpBitmapRenderTarget));
+
+ if (!SUCCEEDED(hr) || nullptr == mpBitmapRenderTarget)
+ {
+ // did not work, done
+ increaseError();
+ }
+ else
+ {
+ sal::systools::COMReference<ID2D1RenderTarget> pRT;
+ mpBitmapRenderTarget->QueryInterface(__uuidof(ID2D1RenderTarget),
+ reinterpret_cast<void**>(&pRT));
+ setRenderTarget(pRT);
+ }
+ }
+
+ if (hasRenderTarget())
+ {
+ // set Viewort if none was given. We have a fixed pixel target, s we know the
+ // exact Viewport to work on
+ if (getViewInformation2D().getViewport().isEmpty())
+ {
+ drawinglayer::geometry::ViewInformation2D aViewInformation(getViewInformation2D());
+ basegfx::B2DRange aViewport(0.0, 0.0, nWidth, nHeight);
+ basegfx::B2DHomMatrix aInvViewTransform(aViewInformation.getViewTransformation());
+
+ aInvViewTransform.invert();
+ aViewport.transform(aInvViewTransform);
+ aViewInformation.setViewport(aViewport);
+ updateViewInformation(aViewInformation);
+ }
+
+ // clear as render preparation
+ getRenderTarget()->BeginDraw();
+ getRenderTarget()->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f));
+ getRenderTarget()->EndDraw();
+ }
+ }
+
+ sal::systools::COMReference<ID2D1Bitmap> getID2D1Bitmap() const
+ {
+ sal::systools::COMReference<ID2D1Bitmap> pResult;
+
+ // access the resulting bitmap if exists
+ if (mpBitmapRenderTarget)
+ {
+ mpBitmapRenderTarget->GetBitmap(&pResult);
+ }
+
+ return pResult;
+ }
+};
+
+bool createBitmapSubContent(sal::systools::COMReference<ID2D1Bitmap>& rResult,
+ basegfx::B2DRange& rDiscreteVisibleRange,
+ const drawinglayer::primitive2d::Primitive2DContainer& rContent,
+ const drawinglayer::geometry::ViewInformation2D& rViewInformation2D,
+ const sal::systools::COMReference<ID2D1RenderTarget>& rRenderTarget)
+{
+ if (rContent.empty() || !rRenderTarget)
+ {
+ // no content or no render target, done
+ return false;
+ }
+
+ drawinglayer::processor2d::calculateDiscreteVisibleRange(
+ rDiscreteVisibleRange, rContent.getB2DRange(rViewInformation2D), rViewInformation2D);
+
+ if (rDiscreteVisibleRange.isEmpty())
+ {
+ // not visible, done
+ return false;
+ }
+
+ // Use a temporary second instance of a D2DBitmapPixelProcessor2D with adapted
+ // ViewInformation2D, it will create the needed ID2D1BitmapRenderTarget
+ // locally and Clear() it.
+ drawinglayer::geometry::ViewInformation2D aAdaptedViewInformation2D(rViewInformation2D);
+ const double fTargetWidth(ceil(rDiscreteVisibleRange.getWidth()));
+ const double fTargetHeight(ceil(rDiscreteVisibleRange.getHeight()));
+
+ {
+ // create adapted ViewTransform, needs to be offset in discrete coordinates,
+ // so multiply from left
+ basegfx::B2DHomMatrix aAdapted(
+ basegfx::utils::createTranslateB2DHomMatrix(-rDiscreteVisibleRange.getMinX(),
+ -rDiscreteVisibleRange.getMinY())
+ * rViewInformation2D.getViewTransformation());
+ aAdaptedViewInformation2D.setViewTransformation(aAdapted);
+
+ // reset Viewport (world coordinates), so the helper renderer will create it's
+ // own based on it's given internal discrete size
+ aAdaptedViewInformation2D.setViewport(basegfx::B2DRange());
+ }
+
+ D2DBitmapPixelProcessor2D aSubContentRenderer(aAdaptedViewInformation2D, fTargetWidth,
+ fTargetHeight, rRenderTarget);
+
+ if (!aSubContentRenderer.valid())
+ {
+ // did not work, done
+ return false;
+ }
+
+ // render sub-content recursively
+ aSubContentRenderer.process(rContent);
+
+ // grab Bitmap & prepare results from RGBA content rendering
+ rResult = aSubContentRenderer.getID2D1Bitmap();
+ return true;
+}
+}
+
+namespace drawinglayer::processor2d
+{
+D2DPixelProcessor2D::D2DPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation)
+ : BaseProcessor2D(rViewInformation)
+ , maBColorModifierStack()
+ , mpRT()
+ , mnRecursionCounter(0)
+ , mnErrorCounter(0)
+{
+}
+
+D2DPixelProcessor2D::D2DPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation,
+ HDC aHdc)
+ : BaseProcessor2D(rViewInformation)
+ , maBColorModifierStack()
+ , mpRT()
+ , mnRecursionCounter(0)
+ , mnErrorCounter(0)
+{
+ sal::systools::COMReference<ID2D1DCRenderTarget> pDCRT;
+ tools::Long aOutWidth(0), aOutHeight(0);
+
+ if (aHdc)
+ {
+ aOutWidth = GetDeviceCaps(aHdc, HORZRES);
+ aOutHeight = GetDeviceCaps(aHdc, VERTRES);
+ }
+
+ if (aOutWidth > 0 && aOutHeight > 0 && aID2D1GlobalFactoryProvider.getID2D1Factory())
+ {
+ const D2D1_RENDER_TARGET_PROPERTIES aRTProps(D2D1::RenderTargetProperties(
+ D2D1_RENDER_TARGET_TYPE_DEFAULT,
+ D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM,
+ D2D1_ALPHA_MODE_IGNORE), //D2D1_ALPHA_MODE_PREMULTIPLIED),
+ 0, 0, D2D1_RENDER_TARGET_USAGE_NONE, D2D1_FEATURE_LEVEL_DEFAULT));
+
+ const HRESULT hr(
+ aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateDCRenderTarget(&aRTProps, &pDCRT));
+
+ if (!SUCCEEDED(hr))
+ pDCRT.clear();
+ }
+
+ if (pDCRT)
+ {
+ const RECT rc(
+ { 0, 0, o3tl::narrowing<LONG>(aOutWidth), o3tl::narrowing<LONG>(aOutHeight) });
+ const HRESULT hr(pDCRT->BindDC(aHdc, &rc));
+
+ if (!SUCCEEDED(hr))
+ pDCRT.clear();
+ }
+
+ if (pDCRT)
+ {
+ if (rViewInformation.getUseAntiAliasing())
+ {
+ D2D1_ANTIALIAS_MODE eAAMode = D2D1_ANTIALIAS_MODE_PER_PRIMITIVE;
+ pDCRT->SetAntialiasMode(eAAMode);
+ }
+ else
+ {
+ D2D1_ANTIALIAS_MODE eAAMode = D2D1_ANTIALIAS_MODE_ALIASED;
+ pDCRT->SetAntialiasMode(eAAMode);
+ }
+
+ // since ID2D1DCRenderTarget depends on the transformation
+ // set at hdc, be careful and reset it to identity
+ XFORM aXForm;
+ aXForm.eM11 = 1.0;
+ aXForm.eM12 = 0.0;
+ aXForm.eM21 = 0.0;
+ aXForm.eM22 = 1.0;
+ aXForm.eDx = 0.0;
+ aXForm.eDy = 0.0;
+ SetWorldTransform(aHdc, &aXForm);
+ }
+
+ if (pDCRT)
+ {
+ sal::systools::COMReference<ID2D1RenderTarget> pRT;
+ pDCRT->QueryInterface(__uuidof(ID2D1RenderTarget), reinterpret_cast<void**>(&pRT));
+ setRenderTarget(pRT);
+ }
+ else
+ {
+ increaseError();
+ }
+}
+
+void D2DPixelProcessor2D::processPolygonHairlinePrimitive2D(
+ const primitive2d::PolygonHairlinePrimitive2D& rPolygonHairlinePrimitive2D)
+{
+ const basegfx::B2DPolygon& rPolygon(rPolygonHairlinePrimitive2D.getB2DPolygon());
+
+ if (!rPolygon.count())
+ {
+ // no geometry, done
+ return;
+ }
+
+ bool bDone(false);
+ std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1PathGeometry(
+ getOrCreatePathGeometry(rPolygon, getViewInformation2D()));
+
+ if (pSystemDependentData_ID2D1PathGeometry)
+ {
+ sal::systools::COMReference<ID2D1TransformedGeometry> pTransformedGeometry;
+ const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
+ const basegfx::B2DHomMatrix& rObjectToView(
+ getViewInformation2D().getObjectToViewTransformation());
+ HRESULT hr(aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateTransformedGeometry(
+ pSystemDependentData_ID2D1PathGeometry->getID2D1PathGeometry(),
+ D2D1::Matrix3x2F(rObjectToView.a(), rObjectToView.b(), rObjectToView.c(),
+ rObjectToView.d(), rObjectToView.e() + fAAOffset,
+ rObjectToView.f() + fAAOffset),
+ &pTransformedGeometry));
+
+ if (SUCCEEDED(hr) && pTransformedGeometry)
+ {
+ const basegfx::BColor aHairlineColor(
+ maBColorModifierStack.getModifiedColor(rPolygonHairlinePrimitive2D.getBColor()));
+ const D2D1::ColorF aD2DColor(aHairlineColor.getRed(), aHairlineColor.getGreen(),
+ aHairlineColor.getBlue());
+ sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush;
+ hr = getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush);
+
+ if (SUCCEEDED(hr) && pColorBrush)
+ {
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
+ // TODO: Unfortunately Direct2D paint of one pixel wide lines does not
+ // correctly and completely blend 100% over the background. Experimenting
+ // shows that a value around/slightly below 2.0 is needed which hints that
+ // alpha blending the half-shifted lines (see fAAOffset above) is involved.
+ // To get correct blending I try to use just wider hairlines for now. This
+ // may need to be improved - or balanced (trying sqrt(2) now...)
+ getRenderTarget()->DrawGeometry(pTransformedGeometry, pColorBrush, 1.44f);
+ bDone = true;
+ }
+ }
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+bool D2DPixelProcessor2D::drawPolyPolygonColorTransformed(
+ const basegfx::B2DHomMatrix& rTansformation, const basegfx::B2DPolyPolygon& rPolyPolygon,
+ const basegfx::BColor& rColor)
+{
+ std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1PathGeometry(
+ getOrCreateFillGeometry(rPolyPolygon));
+
+ if (pSystemDependentData_ID2D1PathGeometry)
+ {
+ sal::systools::COMReference<ID2D1TransformedGeometry> pTransformedGeometry;
+ const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
+ basegfx::B2DHomMatrix aTansformation(getViewInformation2D().getObjectToViewTransformation()
+ * rTansformation);
+ HRESULT hr(aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateTransformedGeometry(
+ pSystemDependentData_ID2D1PathGeometry->getID2D1PathGeometry(),
+ D2D1::Matrix3x2F(aTansformation.a(), aTansformation.b(), aTansformation.c(),
+ aTansformation.d(), aTansformation.e() + fAAOffset,
+ aTansformation.f() + fAAOffset),
+ &pTransformedGeometry));
+
+ if (SUCCEEDED(hr) && pTransformedGeometry)
+ {
+ const basegfx::BColor aFillColor(maBColorModifierStack.getModifiedColor(rColor));
+ const D2D1::ColorF aD2DColor(aFillColor.getRed(), aFillColor.getGreen(),
+ aFillColor.getBlue());
+
+ sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush;
+ hr = getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush);
+
+ if (SUCCEEDED(hr) && pColorBrush)
+ {
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
+ getRenderTarget()->FillGeometry(pTransformedGeometry, pColorBrush);
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+void D2DPixelProcessor2D::processPolyPolygonColorPrimitive2D(
+ const primitive2d::PolyPolygonColorPrimitive2D& rPolyPolygonColorPrimitive2D)
+{
+ const basegfx::B2DPolyPolygon& rPolyPolygon(rPolyPolygonColorPrimitive2D.getB2DPolyPolygon());
+ const sal_uInt32 nCount(rPolyPolygon.count());
+
+ if (!nCount)
+ {
+ // no geometry, done
+ return;
+ }
+
+ const bool bDone(drawPolyPolygonColorTransformed(basegfx::B2DHomMatrix(), rPolyPolygon,
+ rPolyPolygonColorPrimitive2D.getBColor()));
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processBitmapPrimitive2D(
+ const primitive2d::BitmapPrimitive2D& rBitmapCandidate)
+{
+ // check if graphic content is inside discrete local ViewPort
+ if (!getViewInformation2D().getDiscreteViewport().isEmpty())
+ {
+ // calculate logic object range, remember: the helper below will
+ // transform using getObjectToViewTransformation, so the bitmap-local
+ // transform would be missing
+ basegfx::B2DRange aDiscreteVisibleRange(basegfx::B2DRange::getUnitB2DRange());
+ aDiscreteVisibleRange.transform(rBitmapCandidate.getTransform());
+
+ // calculate visible range
+ calculateDiscreteVisibleRange(aDiscreteVisibleRange, aDiscreteVisibleRange,
+ getViewInformation2D());
+
+ if (aDiscreteVisibleRange.isEmpty())
+ {
+ // not visible, done
+ return;
+ }
+ }
+
+ BitmapEx aBitmapEx(rBitmapCandidate.getBitmap());
+
+ if (aBitmapEx.IsEmpty() || aBitmapEx.GetSizePixel().IsEmpty())
+ {
+ // no pixel data, done
+ return;
+ }
+
+ if (maBColorModifierStack.count())
+ {
+ // need to apply ColorModifier to Bitmap data
+ aBitmapEx = aBitmapEx.ModifyBitmapEx(maBColorModifierStack);
+
+ if (aBitmapEx.IsEmpty())
+ {
+ // color gets completely replaced, get it (any input works)
+ const basegfx::BColor aModifiedColor(
+ maBColorModifierStack.getModifiedColor(basegfx::BColor()));
+
+ // use unit geometry as fallback object geometry. Do *not*
+ // transform, the below used method will use the already
+ // correctly initialized local ViewInformation
+ basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon());
+
+ rtl::Reference<primitive2d::PolyPolygonColorPrimitive2D> aTemp(
+ new primitive2d::PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aPolygon),
+ aModifiedColor));
+
+ // draw as Polygon, done
+ processPolyPolygonColorPrimitive2D(*aTemp);
+ return;
+ }
+ }
+
+ bool bDone(false);
+ sal::systools::COMReference<ID2D1Bitmap> pD2DBitmap(
+ getOrCreateB2DBitmap(getRenderTarget(), aBitmapEx));
+
+ if (pD2DBitmap)
+ {
+ const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
+ const basegfx::B2DHomMatrix aLocalTransform(
+ getViewInformation2D().getObjectToViewTransformation()
+ * rBitmapCandidate.getTransform());
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F(
+ aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(), aLocalTransform.d(),
+ aLocalTransform.e() + fAAOffset, aLocalTransform.f() + fAAOffset));
+
+ // destinationRectangle is part of transformation above, so use UnitRange
+ getRenderTarget()->DrawBitmap(pD2DBitmap, D2D1::RectF(0.0, 0.0, 1.0, 1.0));
+ bDone = true;
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+sal::systools::COMReference<ID2D1Bitmap> D2DPixelProcessor2D::implCreateAlpha_Direct(
+ const primitive2d::TransparencePrimitive2D& rTransCandidate)
+{
+ // Try if we can use ID2D1DeviceContext/d2d1_1 by querying for interface.
+ // Only then can we use ID2D1Effect/CLSID_D2D1LuminanceToAlpha and it makes
+ // sense to try to do it this way in this implementation
+ sal::systools::COMReference<ID2D1DeviceContext> pID2D1DeviceContext;
+ getRenderTarget()->QueryInterface(__uuidof(ID2D1DeviceContext),
+ reinterpret_cast<void**>(&pID2D1DeviceContext));
+ sal::systools::COMReference<ID2D1Bitmap> pRetval;
+
+ if (!pID2D1DeviceContext)
+ {
+ // no, done - tell caller to use fallback by returning empty - we have
+ // not the preconditions for this
+ return pRetval;
+ }
+
+ // Release early
+ pID2D1DeviceContext.clear();
+ basegfx::B2DRange aDiscreteVisibleRange;
+
+ if (!createBitmapSubContent(pRetval, aDiscreteVisibleRange, rTransCandidate.getTransparence(),
+ getViewInformation2D(), getRenderTarget())
+ || !pRetval)
+ {
+ // return of false means no display needed, return
+ return pRetval;
+ }
+
+ // Now we need a target to render this to, using the ID2D1Effect tooling.
+ // We can directly apply the effect to an alpha-only 8bit target here,
+ // so create one (no RGBA needed for this).
+ // We need another render target: I tried to render pInBetweenResult
+ // to pContent again, but that does not work due to the bitmap
+ // fetched being probably only an internal reference to the
+ // ID2D1BitmapRenderTarget, thus it would draw onto itself -> chaos
+ sal::systools::COMReference<ID2D1BitmapRenderTarget> pContent;
+ const D2D1_PIXEL_FORMAT aAlphaFormat(
+ D2D1::PixelFormat(DXGI_FORMAT_A8_UNORM, D2D1_ALPHA_MODE_STRAIGHT));
+ const D2D1_SIZE_U aRenderTargetSizePixel(D2D1::SizeU(ceil(aDiscreteVisibleRange.getWidth()),
+ ceil(aDiscreteVisibleRange.getHeight())));
+ const HRESULT hr(getRenderTarget()->CreateCompatibleRenderTarget(
+ nullptr, &aRenderTargetSizePixel, &aAlphaFormat, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE,
+ &pContent));
+
+ if (SUCCEEDED(hr) && pContent)
+ {
+ // try to access ID2D1DeviceContext of that target, we need that *now*
+ pContent->QueryInterface(__uuidof(ID2D1DeviceContext),
+ reinterpret_cast<void**>(&pID2D1DeviceContext));
+
+ if (pID2D1DeviceContext)
+ {
+ // create the effect
+ sal::systools::COMReference<ID2D1Effect> pLuminanceToAlpha;
+ pID2D1DeviceContext->CreateEffect(CLSID_D2D1LuminanceToAlpha, &pLuminanceToAlpha);
+
+ if (pLuminanceToAlpha)
+ {
+ // chain effect stuff together & paint it
+ pLuminanceToAlpha->SetInput(0, pRetval);
+
+ pID2D1DeviceContext->BeginDraw();
+ pID2D1DeviceContext->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f));
+ pID2D1DeviceContext->DrawImage(pLuminanceToAlpha);
+ pID2D1DeviceContext->EndDraw();
+
+ // grab result
+ pContent->GetBitmap(&pRetval);
+ }
+ }
+ }
+
+ return pRetval;
+}
+
+sal::systools::COMReference<ID2D1Bitmap> D2DPixelProcessor2D::implCreateAlpha_B2DBitmap(
+ const primitive2d::TransparencePrimitive2D& rTransCandidate,
+ const basegfx::B2DRange& rVisibleRange, D2D1_MATRIX_3X2_F& rMaskScale)
+{
+ // Use this fallback that will also use a pixel processor indirectly,
+ // but allows to get the AlphaMask as vcl Bitmap using existing tooling
+ const sal_uInt32 nDiscreteClippedWidth(ceil(rVisibleRange.getWidth()));
+ const sal_uInt32 nDiscreteClippedHeight(ceil(rVisibleRange.getHeight()));
+ const sal_uInt32 nMaximumQuadraticPixels(250000);
+
+ // Embed content graphics to TransformPrimitive2D
+ const basegfx::B2DHomMatrix aAlphaEmbedding(
+ basegfx::utils::createTranslateB2DHomMatrix(-rVisibleRange.getMinX(),
+ -rVisibleRange.getMinY())
+ * getViewInformation2D().getObjectToViewTransformation());
+ const primitive2d::Primitive2DReference xAlphaEmbedRef(new primitive2d::TransformPrimitive2D(
+ aAlphaEmbedding,
+ drawinglayer::primitive2d::Primitive2DContainer(rTransCandidate.getTransparence())));
+ drawinglayer::primitive2d::Primitive2DContainer xEmbedSeq{ xAlphaEmbedRef };
+
+ // use empty ViewInformation to have neutral transformation
+ const geometry::ViewInformation2D aEmptyViewInformation2D;
+
+ // use new mode to create AlphaChannel (not just AlphaMask) for transparency channel
+ const AlphaMask aAlpha(::drawinglayer::createAlphaMask(
+ std::move(xEmbedSeq), aEmptyViewInformation2D, nDiscreteClippedWidth,
+ nDiscreteClippedHeight, nMaximumQuadraticPixels, true));
+ sal::systools::COMReference<ID2D1Bitmap> pRetval;
+
+ if (aAlpha.IsEmpty())
+ {
+ // if we have no content we are done
+ return pRetval;
+ }
+
+ // use alpha data to create the ID2D1Bitmap
+ const Size& rSizePixel(aAlpha.GetSizePixel());
+ const sal_uInt32 nPixelCount(rSizePixel.Width() * rSizePixel.Height());
+ std::unique_ptr<sal_uInt8[]> aData(new sal_uInt8[nPixelCount]);
+ sal_uInt8* pTarget = aData.get();
+ Bitmap aSrcAlpha(aAlpha.GetBitmap());
+ BitmapScopedReadAccess pReadAccess(aSrcAlpha);
+ const tools::Long nHeight(pReadAccess->Height());
+ const tools::Long nWidth(pReadAccess->Width());
+
+ for (tools::Long y = 0; y < nHeight; ++y)
+ {
+ for (tools::Long x = 0; x < nWidth; ++x)
+ {
+ const BitmapColor aColor(pReadAccess->GetColor(y, x));
+ *pTarget++ = aColor.GetLuminance();
+ }
+ }
+
+ const D2D1_BITMAP_PROPERTIES aBmProps(D2D1::BitmapProperties(
+ D2D1::PixelFormat(DXGI_FORMAT_A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)));
+ const HRESULT hr(getRenderTarget()->CreateBitmap(
+ D2D1::SizeU(rSizePixel.Width(), rSizePixel.Height()), &aData[0],
+ rSizePixel.Width() * sizeof(sal_uInt8), &aBmProps, &pRetval));
+
+ if (!SUCCEEDED(hr) || !pRetval)
+ {
+ // did not work, done
+ return pRetval;
+ }
+
+ // create needed adapted transformation for alpha brush.
+ // We may have to take a corrective scaling into account when the
+ // MaximumQuadraticPixel limit was used/triggered
+ const Size& rBitmapExSizePixel(aAlpha.GetSizePixel());
+
+ if (static_cast<sal_uInt32>(rBitmapExSizePixel.Width()) != nDiscreteClippedWidth
+ || static_cast<sal_uInt32>(rBitmapExSizePixel.Height()) != nDiscreteClippedHeight)
+ {
+ // scale in X and Y should be the same (see fReduceFactor in createAlphaMask),
+ // so adapt numerically to a single scale value, they are integer rounded values
+ const double fScaleX(static_cast<double>(rBitmapExSizePixel.Width())
+ / static_cast<double>(nDiscreteClippedWidth));
+ const double fScaleY(static_cast<double>(rBitmapExSizePixel.Height())
+ / static_cast<double>(nDiscreteClippedHeight));
+
+ const double fScale(1.0 / ((fScaleX + fScaleY) * 0.5));
+ rMaskScale = D2D1::Matrix3x2F::Scale(fScale, fScale);
+ }
+
+ return pRetval;
+}
+
+void D2DPixelProcessor2D::processTransparencePrimitive2D(
+ const primitive2d::TransparencePrimitive2D& rTransCandidate)
+{
+ if (rTransCandidate.getChildren().empty())
+ {
+ // no content, done
+ return;
+ }
+
+ if (rTransCandidate.getTransparence().empty())
+ {
+ // no mask (so nothing visible), done
+ return;
+ }
+
+ // calculate visible range, create only for that range
+ basegfx::B2DRange aDiscreteVisibleRange;
+ calculateDiscreteVisibleRange(aDiscreteVisibleRange,
+ rTransCandidate.getChildren().getB2DRange(getViewInformation2D()),
+ getViewInformation2D());
+
+ if (aDiscreteVisibleRange.isEmpty())
+ {
+ // not visible, done
+ return;
+ }
+
+ // try to create directly, this needs the current mpRT to be a ID2D1DeviceContext/d2d1_1
+ // what is not guaranteed but usually works for more modern windows (after 7)
+ sal::systools::COMReference<ID2D1Bitmap> pAlphaBitmap(implCreateAlpha_Direct(rTransCandidate));
+ D2D1_MATRIX_3X2_F aMaskScale(D2D1::Matrix3x2F::Identity());
+
+ if (!pAlphaBitmap)
+ {
+ // did not work, use more expensive fallback to existing tooling
+ pAlphaBitmap
+ = implCreateAlpha_B2DBitmap(rTransCandidate, aDiscreteVisibleRange, aMaskScale);
+ }
+
+ if (!pAlphaBitmap)
+ {
+ // could not create alpha channel, error
+ increaseError();
+ return;
+ }
+
+ sal::systools::COMReference<ID2D1Layer> pLayer;
+ HRESULT hr(getRenderTarget()->CreateLayer(nullptr, &pLayer));
+ bool bDone(false);
+
+ if (SUCCEEDED(hr) && pLayer)
+ {
+ sal::systools::COMReference<ID2D1BitmapBrush> pBitmapBrush;
+ hr = getRenderTarget()->CreateBitmapBrush(pAlphaBitmap, &pBitmapBrush);
+
+ if (SUCCEEDED(hr) && pBitmapBrush)
+ {
+ // apply MaskScale to Brush, maybe used if implCreateAlpha_B2DBitmap was needed
+ pBitmapBrush->SetTransform(aMaskScale);
+
+ // need to set transform offset for Layer initialization, we work
+ // in discrete device coordinates
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Translation(
+ floor(aDiscreteVisibleRange.getMinX()), floor(aDiscreteVisibleRange.getMinY())));
+
+ getRenderTarget()->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), nullptr,
+ D2D1_ANTIALIAS_MODE_PER_PRIMITIVE,
+ D2D1::Matrix3x2F::Identity(), 1.0,
+ pBitmapBrush),
+ pLayer);
+
+ // ... but need to reset to paint content unchanged
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
+
+ // draw content recursively
+ process(rTransCandidate.getChildren());
+
+ getRenderTarget()->PopLayer();
+ bDone = true;
+ }
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processUnifiedTransparencePrimitive2D(
+ const primitive2d::UnifiedTransparencePrimitive2D& rTransCandidate)
+{
+ if (rTransCandidate.getChildren().empty())
+ {
+ // no content, done
+ return;
+ }
+
+ if (0.0 == rTransCandidate.getTransparence())
+ {
+ // not transparent at all, use content
+ process(rTransCandidate.getChildren());
+ return;
+ }
+
+ if (rTransCandidate.getTransparence() < 0.0 || rTransCandidate.getTransparence() > 1.0)
+ {
+ // invalid transparence, done
+ return;
+ }
+
+ // calculate visible range
+ basegfx::B2DRange aDiscreteVisibleRange;
+ calculateDiscreteVisibleRange(aDiscreteVisibleRange,
+ rTransCandidate.getChildren().getB2DRange(getViewInformation2D()),
+ getViewInformation2D());
+
+ if (aDiscreteVisibleRange.isEmpty())
+ {
+ // not visible, done
+ return;
+ }
+
+ bool bDone(false);
+ sal::systools::COMReference<ID2D1Layer> pLayer;
+ const HRESULT hr(getRenderTarget()->CreateLayer(nullptr, &pLayer));
+
+ if (SUCCEEDED(hr) && pLayer)
+ {
+ // need to set correct transform for Layer initialization, we work
+ // in discrete device coordinates
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
+ getRenderTarget()->PushLayer(
+ D2D1::LayerParameters(D2D1::InfiniteRect(), nullptr, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE,
+ D2D1::IdentityMatrix(),
+ 1.0 - rTransCandidate.getTransparence()), // opacity
+ pLayer);
+ process(rTransCandidate.getChildren());
+ getRenderTarget()->PopLayer();
+ bDone = true;
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processMaskPrimitive2DPixel(
+ const primitive2d::MaskPrimitive2D& rMaskCandidate)
+{
+ if (rMaskCandidate.getChildren().empty())
+ {
+ // no content, done
+ return;
+ }
+
+ basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask());
+
+ if (!aMask.count())
+ {
+ // no mask (so nothing inside), done
+ return;
+ }
+
+ // calculate visible range
+ basegfx::B2DRange aDiscreteVisibleRange;
+ calculateDiscreteVisibleRange(aDiscreteVisibleRange, aMask.getB2DRange(),
+ getViewInformation2D());
+
+ if (aDiscreteVisibleRange.isEmpty())
+ {
+ // not visible, done
+ return;
+ }
+
+ bool bDone(false);
+ std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1MaskGeometry(
+ getOrCreateFillGeometry(rMaskCandidate.getMask()));
+
+ if (pSystemDependentData_ID2D1MaskGeometry)
+ {
+ sal::systools::COMReference<ID2D1TransformedGeometry> pTransformedMaskGeometry;
+ const basegfx::B2DHomMatrix& rObjectToView(
+ getViewInformation2D().getObjectToViewTransformation());
+ HRESULT hr(aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateTransformedGeometry(
+ pSystemDependentData_ID2D1MaskGeometry->getID2D1PathGeometry(),
+ D2D1::Matrix3x2F(rObjectToView.a(), rObjectToView.b(), rObjectToView.c(),
+ rObjectToView.d(), rObjectToView.e(), rObjectToView.f()),
+ &pTransformedMaskGeometry));
+
+ if (SUCCEEDED(hr) && pTransformedMaskGeometry)
+ {
+ sal::systools::COMReference<ID2D1Layer> pLayer;
+ hr = getRenderTarget()->CreateLayer(nullptr, &pLayer);
+
+ if (SUCCEEDED(hr) && pLayer)
+ {
+ // need to set correct transform for Layer initialization, we work
+ // in discrete device coordinates
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
+ getRenderTarget()->PushLayer(
+ D2D1::LayerParameters(D2D1::InfiniteRect(), pTransformedMaskGeometry), pLayer);
+ process(rMaskCandidate.getChildren());
+ getRenderTarget()->PopLayer();
+ bDone = true;
+ }
+ }
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processPointArrayPrimitive2D(
+ const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate)
+{
+ const std::vector<basegfx::B2DPoint>& rPositions(rPointArrayCandidate.getPositions());
+
+ if (rPositions.empty())
+ {
+ // no geometry, done
+ return;
+ }
+
+ const basegfx::BColor aPointColor(
+ maBColorModifierStack.getModifiedColor(rPointArrayCandidate.getRGBColor()));
+ sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush;
+ D2D1::ColorF aD2DColor(aPointColor.getRed(), aPointColor.getGreen(), aPointColor.getBlue());
+ const HRESULT hr(getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush));
+ bool bDone(false);
+
+ if (SUCCEEDED(hr) && pColorBrush)
+ {
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
+
+ // To really paint a single pixel I found nothing better than
+ // switch off AA and draw a pixel-aligned rectangle
+ const D2D1_ANTIALIAS_MODE aOldAAMode(getRenderTarget()->GetAntialiasMode());
+ getRenderTarget()->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
+
+ for (auto const& pos : rPositions)
+ {
+ const basegfx::B2DPoint aDiscretePos(
+ getViewInformation2D().getObjectToViewTransformation() * pos);
+ const double fX(ceil(aDiscretePos.getX()));
+ const double fY(ceil(aDiscretePos.getY()));
+ const D2D1_RECT_F rect = { FLOAT(fX), FLOAT(fY), FLOAT(fX), FLOAT(fY) };
+
+ getRenderTarget()->DrawRectangle(&rect, pColorBrush);
+ }
+
+ getRenderTarget()->SetAntialiasMode(aOldAAMode);
+ bDone = true;
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processMarkerArrayPrimitive2D(
+ const primitive2d::MarkerArrayPrimitive2D& rMarkerArrayCandidate)
+{
+ const std::vector<basegfx::B2DPoint>& rPositions(rMarkerArrayCandidate.getPositions());
+
+ if (rPositions.empty())
+ {
+ // no geometry, done
+ return;
+ }
+
+ const BitmapEx& rMarker(rMarkerArrayCandidate.getMarker());
+
+ if (rMarker.IsEmpty())
+ {
+ // no marker defined, done
+ return;
+ }
+
+ sal::systools::COMReference<ID2D1Bitmap> pD2DBitmap(
+ getOrCreateB2DBitmap(getRenderTarget(), rMarker));
+ bool bDone(false);
+
+ if (pD2DBitmap)
+ {
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
+ const Size& rSizePixel(rMarker.GetSizePixel());
+ const tools::Long nMiX((rSizePixel.Width() / 2) + 1);
+ const tools::Long nMiY((rSizePixel.Height() / 2) + 1);
+ const tools::Long nPlX(rSizePixel.Width() - nMiX);
+ const tools::Long nPlY(rSizePixel.Height() - nMiY);
+
+ // draw with non-AA to show unhampered, clear, non-scaled marker
+ const D2D1_ANTIALIAS_MODE aOldAAMode(getRenderTarget()->GetAntialiasMode());
+ getRenderTarget()->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
+
+ for (auto const& pos : rPositions)
+ {
+ const basegfx::B2DPoint aDiscretePos(
+ getViewInformation2D().getObjectToViewTransformation() * pos);
+ const double fX(ceil(aDiscretePos.getX()));
+ const double fY(ceil(aDiscretePos.getY()));
+ const D2D1_RECT_F rect
+ = { FLOAT(fX - nMiX), FLOAT(fY - nMiY), FLOAT(fX + nPlX), FLOAT(fY + nPlY) };
+
+ getRenderTarget()->DrawBitmap(pD2DBitmap, &rect);
+ }
+
+ getRenderTarget()->SetAntialiasMode(aOldAAMode);
+ bDone = true;
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processBackgroundColorPrimitive2D(
+ const primitive2d::BackgroundColorPrimitive2D& rBackgroundColorCandidate)
+{
+ // check for allowed range [0.0 .. 1.0[
+ if (rBackgroundColorCandidate.getTransparency() < 0.0
+ || rBackgroundColorCandidate.getTransparency() >= 1.0)
+ return;
+
+ const D2D1::ColorF aD2DColor(rBackgroundColorCandidate.getBColor().getRed(),
+ rBackgroundColorCandidate.getBColor().getGreen(),
+ rBackgroundColorCandidate.getBColor().getBlue(),
+ 1.0 - rBackgroundColorCandidate.getTransparency());
+
+ getRenderTarget()->Clear(aD2DColor);
+}
+
+void D2DPixelProcessor2D::processModifiedColorPrimitive2D(
+ const primitive2d::ModifiedColorPrimitive2D& rModifiedCandidate)
+{
+ if (!rModifiedCandidate.getChildren().empty())
+ {
+ maBColorModifierStack.push(rModifiedCandidate.getColorModifier());
+ process(rModifiedCandidate.getChildren());
+ maBColorModifierStack.pop();
+ }
+}
+
+void D2DPixelProcessor2D::processTransformPrimitive2D(
+ const primitive2d::TransformPrimitive2D& rTransformCandidate)
+{
+ // remember current transformation and ViewInformation
+ const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D());
+
+ // create new transformations for local ViewInformation2D
+ geometry::ViewInformation2D aViewInformation2D(getViewInformation2D());
+ aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation()
+ * rTransformCandidate.getTransformation());
+ updateViewInformation(aViewInformation2D);
+
+ // process content
+ process(rTransformCandidate.getChildren());
+
+ // restore transformations
+ updateViewInformation(aLastViewInformation2D);
+}
+
+void D2DPixelProcessor2D::processPolygonStrokePrimitive2D(
+ const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokeCandidate)
+{
+ const basegfx::B2DPolygon& rPolygon(rPolygonStrokeCandidate.getB2DPolygon());
+ const attribute::LineAttribute& rLineAttribute(rPolygonStrokeCandidate.getLineAttribute());
+
+ if (!rPolygon.count() || rLineAttribute.getWidth() < 0.0)
+ {
+ // no geometry, done
+ return;
+ }
+
+ // get some values early that might be used for decisions
+ const bool bHairline(0.0 == rLineAttribute.getWidth());
+ const basegfx::B2DHomMatrix& rObjectToView(
+ getViewInformation2D().getObjectToViewTransformation());
+ const double fDiscreteLineWidth(
+ bHairline
+ ? 1.0
+ : (rObjectToView * basegfx::B2DVector(rLineAttribute.getWidth(), 0.0)).getLength());
+
+ // Here for every combination which the system-specific implementation is not
+ // capable of visualizing, use the (for decomposable Primitives always possible)
+ // fallback to the decomposition.
+ if (basegfx::B2DLineJoin::NONE == rLineAttribute.getLineJoin() && fDiscreteLineWidth > 1.5)
+ {
+ // basegfx::B2DLineJoin::NONE is special for our office, no other GraphicSystem
+ // knows that (so far), so fallback to decomposition. This is only needed if
+ // LineJoin will be used, so also check for discrete LineWidth before falling back
+ process(rPolygonStrokeCandidate);
+ return;
+ }
+
+ // This is a method every system-specific implementation of a decomposable Primitive
+ // can use to allow simple optical control of paint implementation:
+ // Create a copy, e.g. change color to 'red' as here and paint before the system
+ // paints it using the decomposition. That way you can - if active - directly
+ // optically compare if the system-specific solution is geometrically identical to
+ // the decomposition (which defines our interpretation that we need to visualize).
+ // Look below in the impl for bRenderDecomposeForCompareInRed to see that in that case
+ // we create a half-transparent paint to better support visual control
+ static bool bRenderDecomposeForCompareInRed(false);
+
+ if (bRenderDecomposeForCompareInRed)
+ {
+ const attribute::LineAttribute aRed(
+ basegfx::BColor(1.0, 0.0, 0.0), rLineAttribute.getWidth(), rLineAttribute.getLineJoin(),
+ rLineAttribute.getLineCap(), rLineAttribute.getMiterMinimumAngle());
+ rtl::Reference<primitive2d::PolygonStrokePrimitive2D> aCopy(
+ new primitive2d::PolygonStrokePrimitive2D(
+ rPolygonStrokeCandidate.getB2DPolygon(), aRed,
+ rPolygonStrokeCandidate.getStrokeAttribute()));
+ process(*aCopy);
+ }
+
+ bool bDone(false);
+ std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1PathGeometry(
+ getOrCreatePathGeometry(rPolygon, getViewInformation2D()));
+
+ if (pSystemDependentData_ID2D1PathGeometry)
+ {
+ sal::systools::COMReference<ID2D1TransformedGeometry> pTransformedGeometry;
+ const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
+ HRESULT hr(aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateTransformedGeometry(
+ pSystemDependentData_ID2D1PathGeometry->getID2D1PathGeometry(),
+ D2D1::Matrix3x2F(rObjectToView.a(), rObjectToView.b(), rObjectToView.c(),
+ rObjectToView.d(), rObjectToView.e() + fAAOffset,
+ rObjectToView.f() + fAAOffset),
+ &pTransformedGeometry));
+
+ if (SUCCEEDED(hr) && pTransformedGeometry)
+ {
+ const basegfx::BColor aLineColor(
+ maBColorModifierStack.getModifiedColor(rLineAttribute.getColor()));
+ D2D1::ColorF aD2DColor(aLineColor.getRed(), aLineColor.getGreen(),
+ aLineColor.getBlue());
+
+ if (bRenderDecomposeForCompareInRed)
+ {
+ aD2DColor.a = 0.5;
+ }
+
+ sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush;
+ hr = getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush);
+
+ if (SUCCEEDED(hr) && pColorBrush)
+ {
+ sal::systools::COMReference<ID2D1StrokeStyle> pStrokeStyle;
+ D2D1_CAP_STYLE aCapStyle(D2D1_CAP_STYLE_FLAT);
+ D2D1_LINE_JOIN aLineJoin(D2D1_LINE_JOIN_MITER);
+ const attribute::StrokeAttribute& rStrokeAttribute(
+ rPolygonStrokeCandidate.getStrokeAttribute());
+ const bool bDashUsed(!rStrokeAttribute.isDefault()
+ && !rStrokeAttribute.getDotDashArray().empty()
+ && 0.0 < rStrokeAttribute.getFullDotDashLen());
+ D2D1_DASH_STYLE aDashStyle(bDashUsed ? D2D1_DASH_STYLE_CUSTOM
+ : D2D1_DASH_STYLE_SOLID);
+ std::vector<float> dashes;
+ float miterLimit(1.0);
+
+ switch (rLineAttribute.getLineCap())
+ {
+ case css::drawing::LineCap_ROUND:
+ aCapStyle = D2D1_CAP_STYLE_ROUND;
+ break;
+ case css::drawing::LineCap_SQUARE:
+ aCapStyle = D2D1_CAP_STYLE_SQUARE;
+ break;
+ default:
+ break;
+ }
+
+ switch (rLineAttribute.getLineJoin())
+ {
+ case basegfx::B2DLineJoin::NONE:
+ break;
+ case basegfx::B2DLineJoin::Bevel:
+ aLineJoin = D2D1_LINE_JOIN_BEVEL;
+ break;
+ case basegfx::B2DLineJoin::Miter:
+ {
+ // for basegfx::B2DLineJoin::Miter there are two problems:
+ // (1) MS uses D2D1_LINE_JOIN_MITER which handles the cut-off when MiterLimit is hit not by
+ // fallback to Bevel, but by cutting miter geometry at the defined distance. That is
+ // nice, but not what we need or is the standard for other graphic systems. Luckily there
+ // is also D2D1_LINE_JOIN_MITER_OR_BEVEL and (after some search) the page
+ // https://learn.microsoft.com/en-us/windows/win32/api/d2d1/ne-d2d1-d2d1_line_join
+ // which gives some explanation, so that is what we need to use here.
+ // (2) Instead of using an angle in radians (15 deg default) MS uses
+ // "miterLimit is relative to 1/2 LineWidth", so a length. After some experimenting
+ // it shows that the (better understandable) angle has to be converted to the length
+ // that a miter prolongation would have at that angle, so use some trigonometry.
+ // Unfortunately there is also some'precision' problem (probably), so I had to
+ // experimentally come to a correction value around 0.9925. Since that seems to
+ // be no obvious numerical value involved somehow (and as long as I find no other
+ // explanation) I will have to use that.
+ // NOTE: To find that correction value I usd that handy bRenderDecomposeForCompareInRed
+ // and changes in debugger - as work tipp
+ // With both done I can use Direct2D for Miter completely - what is good for speed.
+ aLineJoin = D2D1_LINE_JOIN_MITER_OR_BEVEL;
+
+ // snap absolute value of angle in radians to [0.0 .. PI]
+ double fVal(::basegfx::snapToZeroRange(
+ fabs(rLineAttribute.getMiterMinimumAngle()), M_PI));
+
+ // cut at 0.0 and PI since sin would be zero ('endless' miter)
+ const double fSmallValue(M_PI * 0.0000001);
+ fVal = std::max(fSmallValue, fVal);
+ fVal = std::min(M_PI - fSmallValue, fVal);
+
+ // get relative length
+ fVal = 1.0 / sin(fVal);
+
+ // use for miterLimit, we need factor 2.0 (relative to double LineWidth)
+ // and the correction mentioned in (2) above
+ const double fCorrector(2.0 * 0.9925);
+
+ miterLimit = fVal * fCorrector;
+ break;
+ }
+ case basegfx::B2DLineJoin::Round:
+ aLineJoin = D2D1_LINE_JOIN_ROUND;
+ break;
+ default:
+ break;
+ }
+
+ if (bDashUsed)
+ {
+ // dashes need to be discrete and relative to LineWidth
+ for (auto& value : rStrokeAttribute.getDotDashArray())
+ {
+ dashes.push_back(
+ (rObjectToView * basegfx::B2DVector(value, 0.0)).getLength()
+ / fDiscreteLineWidth);
+ }
+ }
+
+ hr = aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateStrokeStyle(
+ D2D1::StrokeStyleProperties(aCapStyle, // startCap
+ aCapStyle, // endCap
+ aCapStyle, // dashCap
+ aLineJoin, // lineJoin
+ miterLimit, // miterLimit
+ aDashStyle, // dashStyle
+ 0.0f), // dashOffset
+ bDashUsed ? dashes.data() : nullptr, bDashUsed ? dashes.size() : 0,
+ &pStrokeStyle);
+
+ if (SUCCEEDED(hr) && pStrokeStyle)
+ {
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
+ getRenderTarget()->DrawGeometry(
+ pTransformedGeometry, pColorBrush,
+ // TODO: Hairline LineWidth, see comment at processPolygonHairlinePrimitive2D
+ bHairline ? 1.44 : fDiscreteLineWidth, pStrokeStyle);
+ bDone = true;
+ }
+ }
+ }
+ }
+
+ if (!bDone)
+ {
+ // fallback to decomposition
+ process(rPolygonStrokeCandidate);
+ }
+}
+
+void D2DPixelProcessor2D::processLineRectanglePrimitive2D(
+ const primitive2d::LineRectanglePrimitive2D& rLineRectanglePrimitive2D)
+{
+ if (rLineRectanglePrimitive2D.getB2DRange().isEmpty())
+ {
+ // no geometry, done
+ return;
+ }
+
+ const basegfx::BColor aHairlineColor(
+ maBColorModifierStack.getModifiedColor(rLineRectanglePrimitive2D.getBColor()));
+ const D2D1::ColorF aD2DColor(aHairlineColor.getRed(), aHairlineColor.getGreen(),
+ aHairlineColor.getBlue());
+ sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush;
+ const HRESULT hr(getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush));
+ bool bDone(false);
+
+ if (SUCCEEDED(hr) && pColorBrush)
+ {
+ const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
+ const basegfx::B2DHomMatrix aLocalTransform(
+ getViewInformation2D().getObjectToViewTransformation());
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F(
+ aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(), aLocalTransform.d(),
+ aLocalTransform.e() + fAAOffset, aLocalTransform.f() + fAAOffset));
+ const basegfx::B2DRange& rRange(rLineRectanglePrimitive2D.getB2DRange());
+ const D2D1_RECT_F rect = { FLOAT(rRange.getMinX()), FLOAT(rRange.getMinY()),
+ FLOAT(rRange.getMaxX()), FLOAT(rRange.getMaxY()) };
+ const double fDiscreteLineWidth(
+ (getViewInformation2D().getInverseObjectToViewTransformation()
+ * basegfx::B2DVector(1.44, 0.0))
+ .getLength());
+
+ getRenderTarget()->DrawRectangle(&rect, pColorBrush, fDiscreteLineWidth);
+ bDone = true;
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processFilledRectanglePrimitive2D(
+ const primitive2d::FilledRectanglePrimitive2D& rFilledRectanglePrimitive2D)
+{
+ if (rFilledRectanglePrimitive2D.getB2DRange().isEmpty())
+ {
+ // no geometry, done
+ return;
+ }
+
+ const basegfx::BColor aFillColor(
+ maBColorModifierStack.getModifiedColor(rFilledRectanglePrimitive2D.getBColor()));
+ const D2D1::ColorF aD2DColor(aFillColor.getRed(), aFillColor.getGreen(), aFillColor.getBlue());
+ sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush;
+ const HRESULT hr(getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush));
+ bool bDone(false);
+
+ if (SUCCEEDED(hr) && pColorBrush)
+ {
+ const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
+ const basegfx::B2DHomMatrix aLocalTransform(
+ getViewInformation2D().getObjectToViewTransformation());
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F(
+ aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(), aLocalTransform.d(),
+ aLocalTransform.e() + fAAOffset, aLocalTransform.f() + fAAOffset));
+ const basegfx::B2DRange& rRange(rFilledRectanglePrimitive2D.getB2DRange());
+ const D2D1_RECT_F rect = { FLOAT(rRange.getMinX()), FLOAT(rRange.getMinY()),
+ FLOAT(rRange.getMaxX()), FLOAT(rRange.getMaxY()) };
+
+ getRenderTarget()->FillRectangle(&rect, pColorBrush);
+ bDone = true;
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processSingleLinePrimitive2D(
+ const primitive2d::SingleLinePrimitive2D& rSingleLinePrimitive2D)
+{
+ const basegfx::BColor aLineColor(
+ maBColorModifierStack.getModifiedColor(rSingleLinePrimitive2D.getBColor()));
+ const D2D1::ColorF aD2DColor(aLineColor.getRed(), aLineColor.getGreen(), aLineColor.getBlue());
+ sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush;
+ const HRESULT hr(getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush));
+ bool bDone(false);
+
+ if (SUCCEEDED(hr) && pColorBrush)
+ {
+ const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
+ basegfx::B2DHomMatrix aLocalTransform(
+ getViewInformation2D().getObjectToViewTransformation());
+ const basegfx::B2DPoint aStart(aLocalTransform * rSingleLinePrimitive2D.getStart());
+ const basegfx::B2DPoint aEnd(aLocalTransform * rSingleLinePrimitive2D.getEnd());
+
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
+ const D2D1_POINT_2F aD2D1Start
+ = { FLOAT(aStart.getX() + fAAOffset), FLOAT(aStart.getY() + fAAOffset) };
+ const D2D1_POINT_2F aD2D1End
+ = { FLOAT(aEnd.getX() + fAAOffset), FLOAT(aEnd.getY() + fAAOffset) };
+
+ getRenderTarget()->DrawLine(aD2D1Start, aD2D1End, pColorBrush, 1.44f);
+ bDone = true;
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processFillGraphicPrimitive2D(
+ const primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D)
+{
+ BitmapEx aPreparedBitmap;
+ basegfx::B2DRange aFillUnitRange(rFillGraphicPrimitive2D.getFillGraphic().getGraphicRange());
+ static double fBigDiscreteArea(300.0 * 300.0);
+
+ // use tooling to do various checks and prepare tiled rendering, see
+ // description of method, parameters and return value there
+ if (!prepareBitmapForDirectRender(rFillGraphicPrimitive2D, getViewInformation2D(),
+ aPreparedBitmap, aFillUnitRange, fBigDiscreteArea))
+ {
+ // no output needed, done
+ return;
+ }
+
+ if (aPreparedBitmap.IsEmpty())
+ {
+ // output needed and Bitmap data empty, so no bitmap data based
+ // tiled rendering is suggested. Use fallback for paint (decomposition)
+ process(rFillGraphicPrimitive2D);
+ return;
+ }
+
+ // render tiled using the prepared Bitmap data
+ if (maBColorModifierStack.count())
+ {
+ // need to apply ColorModifier to Bitmap data
+ aPreparedBitmap = aPreparedBitmap.ModifyBitmapEx(maBColorModifierStack);
+
+ if (aPreparedBitmap.IsEmpty())
+ {
+ // color gets completely replaced, get it (any input works)
+ const basegfx::BColor aModifiedColor(
+ maBColorModifierStack.getModifiedColor(basegfx::BColor()));
+
+ // use unit geometry as fallback object geometry. Do *not*
+ // transform, the below used method will use the already
+ // correctly initialized local ViewInformation
+ basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon());
+
+ // what we still need to apply is the object transform from the
+ // local primitive, that is not part of DisplayInfo yet
+ aPolygon.transform(rFillGraphicPrimitive2D.getTransformation());
+
+ rtl::Reference<primitive2d::PolyPolygonColorPrimitive2D> aTemp(
+ new primitive2d::PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aPolygon),
+ aModifiedColor));
+
+ // draw as colored Polygon, done
+ processPolyPolygonColorPrimitive2D(*aTemp);
+ return;
+ }
+ }
+
+ bool bDone(false);
+ sal::systools::COMReference<ID2D1Bitmap> pD2DBitmap(
+ getOrCreateB2DBitmap(getRenderTarget(), aPreparedBitmap));
+
+ if (pD2DBitmap)
+ {
+ sal::systools::COMReference<ID2D1BitmapBrush> pBitmapBrush;
+ const HRESULT hr(getRenderTarget()->CreateBitmapBrush(pD2DBitmap, &pBitmapBrush));
+
+ if (SUCCEEDED(hr) && pBitmapBrush)
+ {
+ // set extended to repeat/wrap AKA tiling
+ pBitmapBrush->SetExtendModeX(D2D1_EXTEND_MODE_WRAP);
+ pBitmapBrush->SetExtendModeY(D2D1_EXTEND_MODE_WRAP);
+
+ // set interpolation mode
+ // NOTE: This uses D2D1_BITMAP_INTERPOLATION_MODE, but there seem to be
+ // advanced modes when using D2D1_INTERPOLATION_MODE, but that needs
+ // D2D1_BITMAP_BRUSH_PROPERTIES1 and ID2D1BitmapBrush1
+ sal::systools::COMReference<ID2D1BitmapBrush1> pBrush1;
+ pBitmapBrush->QueryInterface(__uuidof(ID2D1BitmapBrush1),
+ reinterpret_cast<void**>(&pBrush1));
+
+ if (pBrush1)
+ {
+ pBrush1->SetInterpolationMode1(D2D1_INTERPOLATION_MODE_MULTI_SAMPLE_LINEAR);
+ }
+ else
+ {
+ pBitmapBrush->SetInterpolationMode(D2D1_BITMAP_INTERPOLATION_MODE_LINEAR);
+ }
+
+ // set BitmapBrush transformation relative to it's PixelSize and
+ // the used FillUnitRange. Since we use unit coordinates here this
+ // is pretty simple
+ const D2D1_SIZE_U aBMSPixel(pD2DBitmap->GetPixelSize());
+ const double fScaleX((aFillUnitRange.getMaxX() - aFillUnitRange.getMinX())
+ / aBMSPixel.width);
+ const double fScaleY((aFillUnitRange.getMaxY() - aFillUnitRange.getMinY())
+ / aBMSPixel.height);
+ const D2D1_MATRIX_3X2_F aBTrans(D2D1::Matrix3x2F(
+ fScaleX, 0.0, 0.0, fScaleY, aFillUnitRange.getMinX(), aFillUnitRange.getMinY()));
+ pBitmapBrush->SetTransform(&aBTrans);
+
+ // set transform to ObjectToWorld to be able to paint in unit coordinates, so
+ // evtl. shear/rotate in that transform is used and does not influence the
+ // orthogonal and unit-oriented brush handling
+ const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0);
+ const basegfx::B2DHomMatrix aLocalTransform(
+ getViewInformation2D().getObjectToViewTransformation()
+ * rFillGraphicPrimitive2D.getTransformation());
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F(
+ aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(), aLocalTransform.d(),
+ aLocalTransform.e() + fAAOffset, aLocalTransform.f() + fAAOffset));
+
+ // use unit rectangle, transformation is already set to include ObjectToWorld
+ const D2D1_RECT_F rect = { FLOAT(0.0), FLOAT(0.0), FLOAT(1.0), FLOAT(1.0) };
+
+ // draw as unit rectangle as brush filled rectangle
+ getRenderTarget()->FillRectangle(&rect, pBitmapBrush);
+ bDone = true;
+ }
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processFillGradientPrimitive2D(
+ const primitive2d::FillGradientPrimitive2D& rFillGradientPrimitive2D)
+{
+ // draw all-covering initial BG polygon 1st
+ bool bDone(drawPolyPolygonColorTransformed(
+ basegfx::B2DHomMatrix(),
+ basegfx::B2DPolyPolygon(
+ basegfx::utils::createPolygonFromRect(rFillGradientPrimitive2D.getOutputRange())),
+ rFillGradientPrimitive2D.getOuterColor()));
+
+ if (bDone)
+ {
+ const basegfx::B2DPolyPolygon aForm(rFillGradientPrimitive2D.getUnitPolygon());
+
+ // paint solid fill steps by providing callback as lambda
+ auto aCallback([&aForm, &bDone, this](const basegfx::B2DHomMatrix& rMatrix,
+ const basegfx::BColor& rColor) {
+ if (bDone)
+ {
+ bDone = drawPolyPolygonColorTransformed(rMatrix, aForm, rColor);
+ }
+ });
+
+ // call value generator to trigger callbacks
+ rFillGradientPrimitive2D.generateMatricesAndColors(aCallback);
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processInvertPrimitive2D(
+ const primitive2d::InvertPrimitive2D& rInvertPrimitive2D)
+{
+ if (rInvertPrimitive2D.getChildren().empty())
+ {
+ // no content, done
+ return;
+ }
+
+ // Try if we can use ID2D1DeviceContext/d2d1_1 by querying for interface.
+ // Only with ID2D1DeviceContext we can use ::DrawImage which supports
+ // D2D1_COMPOSITE_MODE_XOR
+ sal::systools::COMReference<ID2D1DeviceContext> pID2D1DeviceContext;
+ getRenderTarget()->QueryInterface(__uuidof(ID2D1DeviceContext),
+ reinterpret_cast<void**>(&pID2D1DeviceContext));
+
+ if (!pID2D1DeviceContext)
+ {
+ // TODO: We have *no* ID2D1DeviceContext and cannot use D2D1_COMPOSITE_MODE_XOR,
+ // so there is currently no (simple?) way to solve this, there is no 'Invert' method.
+ // It may be possible to convert to a WICBitmap (gets read access) and do the invert
+ // there, but that needs experimenting and is probably not performant - but doable.
+ increaseError();
+ return;
+ }
+
+ sal::systools::COMReference<ID2D1Bitmap> pInBetweenResult;
+ basegfx::B2DRange aDiscreteVisibleRange;
+
+ // create in-between result in discrete coordinates, clipped against visible
+ // part of ViewInformation (if available)
+ if (!createBitmapSubContent(pInBetweenResult, aDiscreteVisibleRange,
+ rInvertPrimitive2D.getChildren(), getViewInformation2D(),
+ getRenderTarget()))
+ {
+ // return of false means no display needed, return
+ return;
+ }
+
+ bool bDone(false);
+
+ if (pInBetweenResult)
+ {
+ getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity());
+ const D2D1_POINT_2F aTopLeft = { FLOAT(floor(aDiscreteVisibleRange.getMinX())),
+ FLOAT(floor(aDiscreteVisibleRange.getMinY())) };
+
+ pID2D1DeviceContext->DrawImage(pInBetweenResult, aTopLeft, D2D1_INTERPOLATION_MODE_LINEAR,
+ D2D1_COMPOSITE_MODE_XOR);
+ bDone = true;
+ }
+
+ if (!bDone)
+ increaseError();
+}
+
+void D2DPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate)
+{
+ if (0 == mnRecursionCounter)
+ getRenderTarget()->BeginDraw();
+ mnRecursionCounter++;
+
+ switch (rCandidate.getPrimitive2DID())
+ {
+ // Geometry that *has* to be processed
+ //
+ // These Primitives have *no* decompose implementation, so these are the basic ones
+ // Just four to go to make a processor work completely (but not optimized)
+ // NOTE: This *could* theoretically be reduced to one and all could implement
+ // a decompose to pixel data, but that seemed not to make sense to me when
+ // I designed this. Thus these four are the lowest-level best representation
+ // from my POV
+ case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D:
+ {
+ processBitmapPrimitive2D(
+ static_cast<const primitive2d::BitmapPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D:
+ {
+ processPointArrayPrimitive2D(
+ static_cast<const primitive2d::PointArrayPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D:
+ {
+ processPolygonHairlinePrimitive2D(
+ static_cast<const primitive2d::PolygonHairlinePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D:
+ {
+ processPolyPolygonColorPrimitive2D(
+ static_cast<const primitive2d::PolyPolygonColorPrimitive2D&>(rCandidate));
+ break;
+ }
+
+ // Embedding/groups that *have* to be processed
+ //
+ // These represent qualifiers for freely defined content, e.g. making
+ // any combination of primitives freely transformed or transparent
+ // NOTE: PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D and
+ // PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D are pretty much default-
+ // implementations that can and are re-used in all processors.
+ // So - with these and PRIMITIVE2D_ID_INVERTPRIMITIVE2D marked to
+ // be removed in the future - just three really to be implemented
+ // locally specifically
+ case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D:
+ {
+ processTransparencePrimitive2D(
+ static_cast<const primitive2d::TransparencePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_INVERTPRIMITIVE2D:
+ {
+ // We urgently should get rid of XOR paint, modern graphic systems
+ // allow no read access to the pixel targets, but that's naturally
+ // a precondition for XOR. While we can do that for the office's
+ // visualization, we can in principle *not* fully avoid getting
+ // stuff that needs/defines XOR paint, e.g. EMF/WMF imports, so
+ // we *have* to support it (for now - sigh)...
+ processInvertPrimitive2D(
+ static_cast<const primitive2d::InvertPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_MASKPRIMITIVE2D:
+ {
+ processMaskPrimitive2DPixel(
+ static_cast<const primitive2d::MaskPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D:
+ {
+ processModifiedColorPrimitive2D(
+ static_cast<const primitive2d::ModifiedColorPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D:
+ {
+ processTransformPrimitive2D(
+ static_cast<const primitive2d::TransformPrimitive2D&>(rCandidate));
+ break;
+ }
+
+ // Geometry that *may* be processed due to being able to do it better
+ // then using the decomposition.
+ // NOTE: In these implementations you could always call what the default
+ // case below does - call process(rCandidate) to use the decomposition.
+ // So these impls should only do something if they can do it better/
+ // faster that the decomposition. So some of them check if they could
+ // - and if not - use exactly that.
+ case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D:
+ {
+ // transparence with a fixed alpha for all content, can be done
+ // significally faster
+ processUnifiedTransparencePrimitive2D(
+ static_cast<const primitive2d::UnifiedTransparencePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D:
+ {
+ // can be done simpler and without AA better locally
+ processMarkerArrayPrimitive2D(
+ static_cast<const primitive2d::MarkerArrayPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_BACKGROUNDCOLORPRIMITIVE2D:
+ {
+ // reset to a color, can be done more effectively locally, would
+ // else decompose to a polygon fill
+ processBackgroundColorPrimitive2D(
+ static_cast<const primitive2d::BackgroundColorPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D:
+ {
+ // fat and stroked lines - much better doable locally, would decompose
+ // to the full line geometry creation (tessellation)
+ processPolygonStrokePrimitive2D(
+ static_cast<const primitive2d::PolygonStrokePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_LINERECTANGLEPRIMITIVE2D:
+ {
+ // simple primitive to support future fast callbacks from OutputDevice
+ // (see 'Example POC' in Gerrit), decomposes to polygon primitive
+ processLineRectanglePrimitive2D(
+ static_cast<const primitive2d::LineRectanglePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_FILLEDRECTANGLEPRIMITIVE2D:
+ {
+ // simple primitive to support future fast callbacks from OutputDevice
+ // (see 'Example POC' in Gerrit), decomposes to filled polygon primitive
+ processFilledRectanglePrimitive2D(
+ static_cast<const primitive2d::FilledRectanglePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_SINGLELINEPRIMITIVE2D:
+ {
+ // simple primitive to support future fast callbacks from OutputDevice
+ // (see 'Example POC' in Gerrit), decomposes to polygon primitive
+ processSingleLinePrimitive2D(
+ static_cast<const primitive2d::SingleLinePrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D:
+ {
+ processFillGraphicPrimitive2D(
+ static_cast<const primitive2d::FillGraphicPrimitive2D&>(rCandidate));
+ break;
+ }
+ case PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D:
+ {
+ processFillGradientPrimitive2D(
+ static_cast<const primitive2d::FillGradientPrimitive2D&>(rCandidate));
+ break;
+ }
+
+ // continue with decompose as fallback
+ default:
+ {
+ SAL_INFO("drawinglayer", "default case for " << drawinglayer::primitive2d::idToString(
+ rCandidate.getPrimitive2DID()));
+ // process recursively
+ process(rCandidate);
+ break;
+ }
+ }
+
+ mnRecursionCounter--;
+ if (0 == mnRecursionCounter)
+ getRenderTarget()->EndDraw();
+}
+} // 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 0000000000..464fbf642a
--- /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 0000000000..c22076fa7c
--- /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 0000000000..9f838a7e1b
--- /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 0000000000..886ce99bb7
--- /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 0000000000..b760b24f84
--- /dev/null
+++ b/drawinglayer/source/processor2d/hittestprocessor2d.cxx
@@ -0,0 +1,548 @@
+/* -*- 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,
+ const basegfx::B2DVector& rLogicHitTolerancePerAxis,
+ bool bHitTextOnly)
+ : BaseProcessor2D(rViewInformation),
+ maDiscreteHitTolerancePerAxis(rLogicHitTolerancePerAxis),
+ mbCollectHitStack(false),
+ mbHit(false),
+ mbHitTextOnly(bHitTextOnly)
+ {
+ // ensure input parameters for hit tolerance is >= 0.0
+ if (maDiscreteHitTolerancePerAxis.getX() < 0.0)
+ maDiscreteHitTolerancePerAxis.setX(0.0);
+ if (maDiscreteHitTolerancePerAxis.getY() < 0.0)
+ maDiscreteHitTolerancePerAxis.setY(0.0);
+
+ if (!maDiscreteHitTolerancePerAxis.equalZero())
+ {
+ // generate discrete hit tolerance
+ maDiscreteHitTolerancePerAxis
+ = getViewInformation2D().getObjectToViewTransformation() * rLogicHitTolerancePerAxis;
+ }
+
+ // generate discrete hit position
+ maDiscreteHitPosition = getViewInformation2D().getObjectToViewTransformation() * rLogicHitPosition;
+ }
+
+ HitTestProcessor2D::~HitTestProcessor2D()
+ {
+ }
+
+ bool HitTestProcessor2D::checkHairlineHitWithTolerance(
+ const basegfx::B2DPolygon& rPolygon,
+ const basegfx::B2DVector& rDiscreteHitTolerancePerAxis) const
+ {
+ basegfx::B2DPolygon aLocalPolygon(rPolygon);
+ aLocalPolygon.transform(getViewInformation2D().getObjectToViewTransformation());
+
+ // get discrete range
+ basegfx::B2DRange aPolygonRange(aLocalPolygon.getB2DRange());
+
+ if(rDiscreteHitTolerancePerAxis.getX() > 0 || rDiscreteHitTolerancePerAxis.getY() > 0)
+ {
+ aPolygonRange.grow(rDiscreteHitTolerancePerAxis);
+ }
+
+ // do rough range test first
+ if(aPolygonRange.isInside(getDiscreteHitPosition()))
+ {
+ // check if a polygon edge is hit
+ return basegfx::utils::isInEpsilonRange(
+ aLocalPolygon,
+ getDiscreteHitPosition(),
+ std::max(rDiscreteHitTolerancePerAxis.getX(), rDiscreteHitTolerancePerAxis.getY()));
+ }
+
+ return false;
+ }
+
+ bool HitTestProcessor2D::checkFillHitWithTolerance(
+ const basegfx::B2DPolyPolygon& rPolyPolygon,
+ const basegfx::B2DVector& rDiscreteHitTolerancePerAxis) const
+ {
+ bool bRetval(false);
+ basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolyPolygon);
+ aLocalPolyPolygon.transform(getViewInformation2D().getObjectToViewTransformation());
+
+ // get discrete range
+ basegfx::B2DRange aPolygonRange(aLocalPolyPolygon.getB2DRange());
+
+ const bool bDiscreteHitToleranceUsed(rDiscreteHitTolerancePerAxis.getX() > 0
+ || rDiscreteHitTolerancePerAxis.getY() > 0);
+
+ if (bDiscreteHitToleranceUsed)
+ {
+ aPolygonRange.grow(rDiscreteHitTolerancePerAxis);
+ }
+
+ // 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(),
+ std::max(rDiscreteHitTolerancePerAxis.getX(), rDiscreteHitTolerancePerAxis.getY())))
+ {
+ 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
+ geometry::ViewInformation2D aViewInformation2D(getViewInformation2D());
+ aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation());
+ 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, rLineAttribute.getWidth() * 0.5));
+ mbHit = checkHairlineHitWithTolerance(
+ rPolygonCandidate.getB2DPolygon(),
+ getDiscreteHitTolerance() + aDiscreteHalfLineVector);
+ }
+ }
+ 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, fLogicHitTolerance));
+
+ mbHit = checkHairlineHitWithTolerance(
+ rPolygonCandidate.getB2DPolygon(),
+ getDiscreteHitTolerance() + aDiscreteHalfLineVector);
+ }
+
+ 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(rBitmapCandidate.getBitmap());
+ 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() <= std::max(getDiscreteHitTolerance().getX(),
+ getDiscreteHitTolerance().getY()))
+ {
+ 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 0000000000..11af79725b
--- /dev/null
+++ b/drawinglayer/source/processor2d/linegeometryextractor2d.cxx
@@ -0,0 +1,121 @@
+/* -*- 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
+ geometry::ViewInformation2D aViewInformation2D(getViewInformation2D());
+ aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation());
+ 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 0000000000..552406d53f
--- /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 0000000000..cf823b005e
--- /dev/null
+++ b/drawinglayer/source/processor2d/processor2dtools.cxx
@@ -0,0 +1,92 @@
+/* -*- 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 <vcl/sysdata.hxx>
+#include "vclpixelprocessor2d.hxx"
+#include "vclmetafileprocessor2d.hxx"
+#include <config_vclplug.h>
+
+#if defined(_WIN32)
+#include <drawinglayer/processor2d/d2dpixelprocessor2d.hxx>
+#elif USE_HEADLESS_CODE
+#include <drawinglayer/processor2d/cairopixelprocessor2d.hxx>
+#endif
+
+using namespace com::sun::star;
+
+namespace drawinglayer::processor2d
+{
+std::unique_ptr<BaseProcessor2D> createPixelProcessor2DFromOutputDevice(
+ OutputDevice& rTargetOutDev,
+ const drawinglayer::geometry::ViewInformation2D& rViewInformation2D)
+{
+ static const bool bTestSystemPrimitiveRenderer(nullptr != std::getenv("TEST_SYSTEM_PRIMITIVE_RENDERER"));
+ if(bTestSystemPrimitiveRenderer)
+ {
+ drawinglayer::geometry::ViewInformation2D aViewInformation2D(rViewInformation2D);
+ // if mnOutOffX/mnOutOffY is set (a 'hack' to get a cheap additional offset), apply it additionally
+ if(0 != rTargetOutDev.GetOutOffXPixel() || 0 != rTargetOutDev.GetOutOffYPixel())
+ {
+ basegfx::B2DHomMatrix aTransform(aViewInformation2D.getViewTransformation());
+ aTransform.translate(rTargetOutDev.GetOutOffXPixel(), rTargetOutDev.GetOutOffYPixel());
+ aViewInformation2D.setViewTransformation(aTransform);
+ }
+#if defined(_WIN32)
+ SystemGraphicsData aData(rTargetOutDev.GetSystemGfxData());
+ std::unique_ptr<D2DPixelProcessor2D> aRetval(
+ std::make_unique<D2DPixelProcessor2D>(aViewInformation2D, aData.hDC));
+ if (aRetval->valid())
+ return aRetval;
+#elif USE_HEADLESS_CODE
+ SystemGraphicsData aData(rTargetOutDev.GetSystemGfxData());
+ std::unique_ptr<CairoPixelProcessor2D> aRetval(
+ std::make_unique<CairoPixelProcessor2D>(aViewInformation2D, static_cast<cairo_surface_t*>(aData.pSurface)));
+ if (aRetval->valid())
+ return aRetval;
+#endif
+ }
+
+ // 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/textaspolygonextractor2d.cxx b/drawinglayer/source/processor2d/textaspolygonextractor2d.cxx
new file mode 100644
index 0000000000..c98fae69d3
--- /dev/null
+++ b/drawinglayer/source/processor2d/textaspolygonextractor2d.cxx
@@ -0,0 +1,227 @@
+/* -*- 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
+ geometry::ViewInformation2D aViewInformation2D(getViewInformation2D());
+ aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation());
+ 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 0000000000..28d383230e
--- /dev/null
+++ b/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx
@@ -0,0 +1,593 @@
+/* -*- 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 <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/lazydelete.hxx>
+#include <vcl/dibtools.hxx>
+#include <vcl/skia/SkiaHelper.hxx>
+#include <mutex>
+
+#ifdef DBG_UTIL
+#include <tools/stream.hxx>
+#endif
+
+// #define SPEED_COMPARE
+#ifdef SPEED_COMPARE
+#include <tools/time.hxx>
+#endif
+
+// buffered VDev usage
+namespace
+{
+class VDevBuffer : public Timer
+{
+private:
+ struct Entry
+ {
+ VclPtr<VirtualDevice> buf;
+ Entry(const VclPtr<VirtualDevice>& vdev)
+ : buf(vdev)
+ {
+ }
+ };
+
+ 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);
+ 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)
+{
+ 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())
+ {
+ // 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::WITHOUT_ALPHA);
+ 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);
+
+ 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();
+ }
+}
+
+#ifdef SPEED_COMPARE
+void doSpeedCompare(double fTrans, const Bitmap& rContent, const tools::Rectangle& rDestPixel,
+ OutputDevice& rOutDev)
+{
+ const int nAvInd(500);
+ static double fFactors[nAvInd];
+ static int nIndex(nAvInd + 1);
+ static int nRepeat(5);
+ static int nWorseTotal(0);
+ static int nBetterTotal(0);
+ int a(0);
+
+ const Size aSizePixel(rDestPixel.GetSize());
+
+ // init statics
+ if (nIndex > nAvInd)
+ {
+ for (a = 0; a < nAvInd; a++)
+ fFactors[a] = 1.0;
+ nIndex = 0;
+ }
+
+ // get start time
+ const sal_uInt64 nTimeA(tools::Time::GetSystemTicks());
+
+ // loop nRepeat times to get somewhat better timings, else
+ // numbers are pretty small
+ for (a = 0; a < nRepeat; a++)
+ {
+ // "Former" method using a temporary AlphaMask & DrawBitmapEx
+ sal_uInt8 nMaskValue(static_cast<sal_uInt8>(basegfx::fround(fTrans * 255.0)));
+ const AlphaMask aAlphaMask(aSizePixel, &nMaskValue);
+ rOutDev.DrawBitmapEx(rDestPixel.TopLeft(), BitmapEx(rContent, aAlphaMask));
+ }
+
+ // get intermediate time
+ const sal_uInt64 nTimeB(tools::Time::GetSystemTicks());
+
+ // loop nRepeat times
+ for (a = 0; a < nRepeat; a++)
+ {
+ // New method using DrawTransformedBitmapEx & fTrans directly
+ rOutDev.DrawTransformedBitmapEx(basegfx::utils::createScaleTranslateB2DHomMatrix(
+ aSizePixel.Width(), aSizePixel.Height(),
+ rDestPixel.TopLeft().X(), rDestPixel.TopLeft().Y()),
+ BitmapEx(rContent), 1 - fTrans);
+ }
+
+ // get end time
+ const sal_uInt64 nTimeC(tools::Time::GetSystemTicks());
+
+ // calculate deltas
+ const sal_uInt64 nTimeFormer(nTimeB - nTimeA);
+ const sal_uInt64 nTimeNew(nTimeC - nTimeB);
+
+ // compare & note down
+ if (nTimeFormer != nTimeNew && 0 != nTimeFormer && 0 != nTimeNew)
+ {
+ if ((nTimeFormer < 10 || nTimeNew < 10) && nRepeat < 500)
+ {
+ nRepeat += 1;
+ SAL_INFO("drawinglayer.processor2d", "Increment nRepeat to " << nRepeat);
+ return;
+ }
+
+ const double fNewFactor((double)nTimeFormer / nTimeNew);
+ fFactors[nIndex % nAvInd] = fNewFactor;
+ nIndex++;
+ double fAverage(0.0);
+ {
+ for (a = 0; a < nAvInd; a++)
+ fAverage += fFactors[a];
+ fAverage /= nAvInd;
+ }
+ if (fNewFactor < 1.0)
+ nWorseTotal++;
+ else
+ nBetterTotal++;
+
+ char buf[300];
+ sprintf(buf,
+ "Former: %ld New: %ld It got %s (factor %f) (av. last %d Former/New is %f, "
+ "WorseTotal: %d, BetterTotal: %d)",
+ nTimeFormer, nTimeNew, fNewFactor < 1.0 ? "WORSE" : "BETTER",
+ fNewFactor < 1.0 ? 1.0 / fNewFactor : fNewFactor, nAvInd, fAverage, nWorseTotal,
+ nBetterTotal);
+ SAL_INFO("drawinglayer.processor2d", buf);
+ }
+}
+#endif
+}
+
+// support for rendering Bitmap and BitmapEx contents
+namespace drawinglayer
+{
+// static global VDev buffer for VclProcessor2D/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)
+ : 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()));
+ maDestPixel.Intersection(tools::Rectangle{ Point{}, mrOutDev.GetOutputSizePixel() });
+
+ if (!isVisible())
+ return;
+
+ mpContent = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize());
+
+ // #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 (!)");
+
+ // initialize buffer by blitting content of source to prepare for
+ // transparence/ copying back
+ const bool bWasEnabledSrc(mrOutDev.IsMapModeEnabled());
+ mrOutDev.EnableMapMode(false);
+ mpContent->DrawOutDev(Point(), maDestPixel.GetSize(), maDestPixel.TopLeft(),
+ maDestPixel.GetSize(), mrOutDev);
+ mrOutDev.EnableMapMode(bWasEnabledSrc);
+
+ 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());
+
+ mrOutDev.EnableMapMode(false);
+ mpContent->EnableMapMode(false);
+
+#ifdef DBG_UTIL
+ // VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/
+ static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore
+ static const OUString sDumpPath(OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH")));
+
+ if (!sDumpPath.isEmpty() && bDoSaveForVisualControl)
+ {
+ SvFileStream aNew(sDumpPath + "content.bmp", 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));
+ aAlphaMask.Invert(); // convert transparency to alpha
+
+#ifdef DBG_UTIL
+ if (!sDumpPath.isEmpty() && bDoSaveForVisualControl)
+ {
+ SvFileStream aNew(sDumpPath + "transparence.bmp",
+ StreamMode::WRITE | StreamMode::TRUNC);
+ WriteDIB(aAlphaMask.GetBitmap(), aNew, false, true);
+ }
+#endif
+
+ Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel));
+ mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent, aAlphaMask));
+ }
+ else if (0.0 != fTrans)
+ {
+ const Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel));
+
+#ifdef SPEED_COMPARE
+ static bool bCompareFormerAndNewTimings(true);
+
+ if (bCompareFormerAndNewTimings)
+ {
+ doSpeedCompare(fTrans, aContent, maDestPixel, mrOutDev);
+ }
+ else
+#endif
+ // Note: this extra scope is needed due to 'clang plugin indentation'. It complains
+ // that lines 494 and (now) 539 are 'statement mis-aligned compared to neighbours'.
+ // That is true if SPEED_COMPARE is not defined. Not nice, but have to fix this.
+ {
+ // For the case we have a unified transparency value there is a former
+ // and new method to paint that which can be used. To decide on measurements,
+ // I added 'doSpeedCompare' above which can be activated by defining
+ // SPEED_COMPARE at the top of this file.
+ // I added the used Testdoc: blurplay3.odg as
+ // https://bugs.documentfoundation.org/attachment.cgi?id=182463
+ // I did measure on
+ //
+ // Linux Dbg:
+ // Former: 21 New: 32 It got WORSE (factor 1.523810) (av. last 500 Former/New is 0.968533, WorseTotal: 515, BetterTotal: 934)
+ //
+ // Linux Pro:
+ // Former: 27 New: 44 It got WORSE (factor 1.629630) (av. last 500 Former/New is 0.923256, WorseTotal: 433, BetterTotal: 337)
+ //
+ // Win Dbg:
+ // Former: 21 New: 78 It got WORSE (factor 3.714286) (av. last 500 Former/New is 1.007176, WorseTotal: 85, BetterTotal: 1428)
+ //
+ // Win Pro:
+ // Former: 3 New: 4 It got WORSE (factor 1.333333) (av. last 500 Former/New is 1.054167, WorseTotal: 143, BetterTotal: 3909)
+ //
+ // Note: I am aware that the Dbg are of limited usefulness, but include them here
+ // for reference.
+ //
+ // The important part is "av. last 500 Former/New is %ld" which describes the averaged factor from Former/New
+ // over the last 500 measurements. When < 1.0 Former is better (Linux), > 1.0 (Win) New is better. Since the
+ // factor on Win is still close to 1.0 what means we lose nearly nothing and Linux Former is better, I will
+ // use Former for now.
+ //
+ // To easily allow to change this (maybe system-dependent) I add a static switch here,
+ // also for eventually experimenting (hint: can be changed in the debugger).
+ static bool bUseNew(false);
+
+ if (bUseNew)
+ {
+ // New method using DrawTransformedBitmapEx & fTrans directly
+ mrOutDev.DrawTransformedBitmapEx(basegfx::utils::createScaleTranslateB2DHomMatrix(
+ aSizePixel.Width(), aSizePixel.Height(),
+ maDestPixel.TopLeft().X(),
+ maDestPixel.TopLeft().Y()),
+ BitmapEx(aContent), 1 - fTrans);
+ }
+ else
+ {
+ // "Former" method using a temporary AlphaMask & DrawBitmapEx
+ sal_uInt8 nMaskValue(static_cast<sal_uInt8>(basegfx::fround(fTrans * 255.0)));
+ const AlphaMask aAlphaMask(aSizePixel, &nMaskValue);
+ mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent, aAlphaMask));
+ }
+ }
+ }
+ 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());
+ 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 0000000000..618fb38209
--- /dev/null
+++ b/drawinglayer/source/processor2d/vclhelperbufferdevice.hxx
@@ -0,0 +1,112 @@
+/* -*- 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>
+
+// Helper class *exclusively* for VclProcessor2D. It should only
+// be used internally, see current four usages. It is used to
+// render something with mask or transparence (see MaskPrimitive2D,
+// UnifiedTransparencePrimitive2D and TransparencePrimitive2D) or
+// as tooling for preparing pixelized output in the renderer
+// (see PatternFillPrimitive2D) if that is faster.
+//
+// To do so, initializing this instance takes over a lot of work
+// from you:
+// - It initializes a 'Content' VDev which is buffered (since it had
+// shown that re-allocating all the time is slower). It checks
+// visibility and all usages initializing this should check for
+// isVisible() after construction.
+// - It pre-initializes the 'Content' VDev with blitting the content
+// of the target VDev.
+// - It offers to get a 'Transparence' VDev (also from the buffer) if
+// needed.
+//
+// If 'Transparence' is/was used, it combines as needed to paint
+// all buffered stuff to target VDev when calling paint().
+// Caution: It is designed to use *either* a fixed transparence in
+// the paint()-call *or* a fill TransparenceChannel using a
+// 'Transparence' VDev. It is *not* designed to use/combine
+// both - it's simply not needed for it's intended purpose/usage.
+//
+// Painting transparent works based on a simple principle: It first
+// blits the original content of the target VDev. Then the content
+// is painted on top of that, plus a Transparence/Mask prepared.
+// The combination always works since unchanged pixels will not
+// change, independent of the transparence value [0..255] it gets
+// mixed with. Or the other way around: Only pixels changed on the
+// Content *can* be changed by transparence values.
+//
+// This is 2.5 times faster than first painting to a
+// 'combined' VDev that supports transparency, as is used by the
+// presentation engine.
+// For the mentioned factor refer to:
+// Patch to demonstrate former and now repaint differences
+// https://gerrit.libreoffice.org/c/core/+/129301
+// git fetch https://git.libreoffice.org/core refs/changes/01/129301/3 && git cherry-pick FETCH_HEAD
+//
+// Note: This principle only works when the target is RGB, so
+// useful for EditViews like for PrimitiveRenderers where this is
+// the case. For usage with GBA targets the starting conditions
+// would have to be modified to not blend against the initial color
+// of 'Content' (usually COL_WHITE).
+//
+// After having finished the rework of ShadowPrimitive2D,
+// SoftEdgePrimitive2D and GlowPrimitive2D (see commits:)
+// e735ad1c57cddaf17d6ffc0cf15b5e14fa63c4ad
+// 707b0c328a282d993fa33b618083d20b6c521de6
+// c2d1458723c66c2fd717a112f89f773226adc841
+// which used the impBufferDevice in such a mode combined with
+// mentioned 'combined' transparence VDev it is now possible
+// to return to this former, much faster method.
+//
+// Please do *not* hack/use this helper class, better create
+// a new one fitting your/the intended purpose. I do not want
+// to see losing performance by this getting modified again.
+//
+// Note: Using that 'combined' transparence VDev is not really
+// recommended, it may vanish anytime. That it works with
+// PrimitiveRenderers *at all* is neither designed nor tested
+// or recommended - it's pure coincidence.
+//
+// I hope that for the future all this will vanish by getting to
+// fully RGBA-capable devices - what is planned and makes sense.
+
+namespace drawinglayer
+{
+class impBufferDevice
+{
+ OutputDevice& mrOutDev;
+ VclPtr<VirtualDevice> mpContent;
+ VclPtr<VirtualDevice> mpAlpha;
+ tools::Rectangle maDestPixel;
+
+public:
+ impBufferDevice(OutputDevice& rOutDev, const basegfx::B2DRange& rRange);
+ ~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 0000000000..e2bf833a4e
--- /dev/null
+++ b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx
@@ -0,0 +1,2655 @@
+/* -*- 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 <comphelper/diagnose_ex.hxx>
+#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 <basegfx/utils/gradienttools.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 <vcl/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 <drawinglayer/converters.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <tools/vcompat.hxx>
+
+#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
+{
+ const basegfx::BColor aStartColor(rFiGrAtt.getColorStops().front().getStopColor());
+ const basegfx::BColor aEndColor(rFiGrAtt.getColorStops().back().getStopColor());
+
+ if (bIsTransparenceGradient)
+ {
+ // it's about transparence channel intensities (black/white), do not use color modifier
+ o_rVCLGradient.SetStartColor(Color(aStartColor));
+ o_rVCLGradient.SetEndColor(Color(aEndColor));
+ }
+ else
+ {
+ // use color modifier to influence start/end color of gradient
+ o_rVCLGradient.SetStartColor(Color(maBColorModifierStack.getModifiedColor(aStartColor)));
+ o_rVCLGradient.SetEndColor(Color(maBColorModifierStack.getModifiedColor(aEndColor)));
+ }
+
+ 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);
+ o_rVCLGradient.SetStyle(rFiGrAtt.getStyle());
+}
+
+void VclMetafileProcessor2D::impStartSvtGraphicFill(SvtGraphicFill const* pSvtGraphicFill)
+{
+ if (pSvtGraphicFill && !mnSvtGraphicFillCount)
+ {
+ SvMemoryStream aMemStm;
+
+ WriteSvtGraphicFill(aMemStm, *pSvtGraphicFill);
+ mpMetaFile->AddAction(new MetaCommentAction(
+ "XPATHFILL_SEQ_BEGIN"_ostr, 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"_ostr));
+ }
+}
+
+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"_ostr, 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"_ostr));
+ }
+}
+
+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
+vcl::DeleteOnDeinit<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 was added in primitive MetaFile renderer.
+ 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:
+ {
+ if (maBColorModifierStack.count())
+ {
+ // tdf#151104 unfortunately processPolyPolygonGraphicPrimitive2D below
+ // does not support an active BColorModifierStack, so use the default
+ process(rCandidate);
+ }
+ else
+ {
+ 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_TEXTHIERARCHYEDITPRIMITIVE2D:
+ {
+ // This primitive is created if a text edit is active and contains it's
+ // current content, not from model data itself.
+ // Pixel renderers need to suppress that content, it gets displayed by the active
+ // TextEdit in the EditView. Suppression is done by decomposing to nothing.
+ // MetaFile renderers have to show it, so that the edited text is part of the
+ // MetaFile, e.g. needed for presentation previews and exports.
+ // So take action here and process it's content:
+ // Note: Former error was #i97628#
+ process(static_cast<const primitive2d::TextHierarchyEditPrimitive2D&>(rCandidate)
+ .getContent());
+ 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;
+ }
+ default:
+ {
+ // process recursively
+ process(rCandidate);
+ break;
+ }
+ }
+}
+
+void VclMetafileProcessor2D::processObjectInfoPrimitive2D(
+ primitive2d::ObjectInfoPrimitive2D const& rObjectInfoPrimitive2D)
+{
+ // tdf#154982 process content first, so this object overrides any nested one
+ process(rObjectInfoPrimitive2D.getChildren());
+
+ // 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);
+ }
+ }
+ }
+}
+
+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 constexpr OUString sPrintablePropertyName(u"Printable"_ustr);
+
+ 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;
+
+ ::std::optional<sal_Int32> oAnchorParent;
+ if (mpPDFExtOutDevData)
+ {
+ if (rControlPrimitive.GetAnchorStructureElementKey())
+ {
+ sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement(
+ rControlPrimitive.GetAnchorStructureElementKey());
+ oAnchorParent.emplace(mpPDFExtOutDevData->GetCurrentStructureElement());
+ mpPDFExtOutDevData->SetCurrentStructureElement(id);
+ }
+ }
+
+ const bool bPDFExport(mpPDFExtOutDevData && mpPDFExtOutDevData->GetIsExportFormFields());
+ bool bDoProcessRecursively(true);
+
+ if (bPDFExport)
+ {
+ // PDF export. Emulate data handling from UnoControlPDFExportContact
+ 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->WrapBeginStructureElement(vcl::PDFWriter::Form);
+ vcl::PDFWriter::StructAttributeValue role;
+ switch (pPDFControl->Type)
+ {
+ case vcl::PDFWriter::PushButton:
+ role = vcl::PDFWriter::Pb;
+ break;
+ case vcl::PDFWriter::RadioButton:
+ role = vcl::PDFWriter::Rb;
+ break;
+ case vcl::PDFWriter::CheckBox:
+ role = vcl::PDFWriter::Cb;
+ break;
+ default: // there is a paucity of roles, tv is the catch-all one
+ role = vcl::PDFWriter::Tv;
+ break;
+ }
+ // ISO 14289-1:2014, Clause: 7.18.4
+ mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Role, role);
+ // ISO 14289-1:2014, Clause: 7.18.1
+ OUString const& rAltText(rControlPrimitive.GetAltText());
+ if (!rAltText.isEmpty())
+ {
+ mpPDFExtOutDevData->SetAlternateText(rAltText);
+ }
+ mpPDFExtOutDevData->CreateControl(*pPDFControl);
+ mpPDFExtOutDevData->EndStructureElement();
+ if (oAnchorParent)
+ {
+ mpPDFExtOutDevData->SetCurrentStructureElement(*oAnchorParent);
+ }
+
+ // 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.
+ }
+ }
+
+ if (!bDoProcessRecursively)
+ {
+ return;
+ }
+
+ if (mpPDFExtOutDevData)
+ { // no corresponding PDF Form, use Figure instead
+ mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Figure);
+ mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Placement, vcl::PDFWriter::Block);
+ auto const range(rControlPrimitive.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);
+ OUString const& rAltText(rControlPrimitive.GetAltText());
+ if (!rAltText.isEmpty())
+ {
+ mpPDFExtOutDevData->SetAlternateText(rAltText);
+ }
+ }
+
+ // #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);
+ }
+
+ if (mpPDFExtOutDevData)
+ {
+ mpPDFExtOutDevData->EndStructureElement();
+ if (oAnchorParent)
+ {
+ mpPDFExtOutDevData->SetCurrentStructureElement(*oAnchorParent);
+ }
+ }
+}
+
+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.
+ static constexpr OString aCommentStringCommon("FIELD_SEQ_BEGIN"_ostr);
+ 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"_ostr));
+ 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"_ostr));
+
+ 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"_ostr));
+}
+
+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->WrapBeginStructureElement(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"_ostr));
+
+ if (mbInListItem)
+ {
+ if (maListElements.top() == vcl::PDFWriter::LILabel)
+ {
+ maListElements.pop();
+ mpPDFExtOutDevData->EndStructureElement(); // end LILabel
+ mbBulletPresent = true;
+ }
+ }
+}
+
+void VclMetafileProcessor2D::processTextHierarchyParagraphPrimitive2D(
+ const primitive2d::TextHierarchyParagraphPrimitive2D& rParagraphPrimitive)
+{
+ static constexpr OString aCommentString("XTEXT_EOP"_ostr);
+ 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->WrapBeginStructureElement(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->WrapBeginStructureElement(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->WrapBeginStructureElement(vcl::PDFWriter::ListItem);
+ mbInListItem = true;
+ }
+ else
+ {
+ // Dump as Paragraph
+ mpPDFExtOutDevData->WrapBeginStructureElement(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"_ostr));
+ 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"_ostr));
+}
+
+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->WrapBeginStructureElement(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.get() || !mxBreakIterator.get()->get())
+ {
+ uno::Reference<uno::XComponentContext> xContext(
+ ::comphelper::getProcessComponentContext());
+ mxBreakIterator.set(i18n::BreakIterator::create(xContext));
+ }
+ auto& rBreakIterator = *mxBreakIterator.get()->get();
+
+ 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(rBreakIterator.nextCharacters(
+ rTxt, nTextPosition, rLocale, css::i18n::CharacterIteratorMode::SKIPCELL, 0,
+ nDone));
+ css::i18n::Boundary nNextWordBoundary(rBreakIterator.getWordBoundary(
+ rTxt, nTextPosition, rLocale, css::i18n::WordType::ANY_WORD, true));
+ sal_Int32 nNextSentenceBreak(
+ rBreakIterator.endOfSentence(rTxt, nTextPosition, rLocale));
+ static constexpr OStringLiteral aCommentStringA("XTEXT_EOC");
+ static constexpr OStringLiteral aCommentStringB("XTEXT_EOW");
+ static constexpr OStringLiteral 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 = rBreakIterator.nextCharacters(
+ rTxt, i, rLocale, css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
+ }
+ if (i == nNextWordBoundary.endPos)
+ {
+ mpMetaFile->AddAction(
+ new MetaCommentAction(aCommentStringB, i - nTextPosition));
+ nNextWordBoundary = rBreakIterator.getWordBoundary(
+ rTxt, i + 1, rLocale, css::i18n::WordType::ANY_WORD, true);
+ }
+ if (i == nNextSentenceBreak)
+ {
+ mpMetaFile->AddAction(
+ new MetaCommentAction(aCommentStringC, i - nTextPosition));
+ nNextSentenceBreak = rBreakIterator.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(std::move(aLeft),
+ rHairlinePrimitive.getBColor()));
+ rtl::Reference<primitive2d::PolygonHairlinePrimitive2D> xPRight(
+ new primitive2d::PolygonHairlinePrimitive2D(std::move(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(std::move(aLeft),
+ rStrokePrimitive.getLineAttribute(),
+ rStrokePrimitive.getStrokeAttribute()));
+ rtl::Reference<primitive2d::PolygonStrokePrimitive2D> xPRight(
+ new primitive2d::PolygonStrokePrimitive2D(std::move(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)
+{
+ bool useDecompose(false);
+
+ if (!useDecompose)
+ {
+ basegfx::B2DVector aScale, aTranslate;
+ double fRotate, fShearX;
+
+ maCurrentTransformation.decompose(aScale, aTranslate, fRotate, fShearX);
+
+ // detect if transformation is rotated, sheared or mirrored in X and/or Y
+ if (!basegfx::fTools::equalZero(fRotate) || !basegfx::fTools::equalZero(fShearX)
+ || aScale.getX() < 0.0 || aScale.getY() < 0.0)
+ {
+ // #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.
+ useDecompose = true;
+ }
+ }
+
+ // tdf#150551 for PDF export, use the decomposition for better gradient visualization
+ if (!useDecompose && nullptr != mpPDFExtOutDevData)
+ {
+ useDecompose = true;
+ }
+
+ basegfx::B2DPolyPolygon aLocalPolyPolygon(rGradientCandidate.getB2DPolyPolygon());
+
+ if (!useDecompose && 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
+ useDecompose = true;
+ }
+
+ const attribute::FillGradientAttribute& rFillGradient(rGradientCandidate.getFillGradient());
+
+ if (!useDecompose && rFillGradient.cannotBeHandledByVCL())
+ {
+ // MCGR: if we have ColorStops, do not try to fallback to old VCL-Gradient,
+ // that will *not* be capable of representing this properly. Use the
+ // correct decomposition instead
+ useDecompose = true;
+ }
+
+ if (useDecompose)
+ {
+ GDIMetaFile* pMetaFile(mpOutputDevice->GetConnectMetaFile());
+
+ // tdf#155479 only add 'BGRAD_SEQ_BEGIN' if SVG export
+ if (nullptr != pMetaFile && pMetaFile->getSVG())
+ {
+ // write the color stops to a memory stream
+ SvMemoryStream aMemStm;
+ VersionCompatWrite aCompat(aMemStm, 1);
+
+ const basegfx::BColorStops& rColorStops(rFillGradient.getColorStops());
+ sal_uInt16 nTmp(sal::static_int_cast<sal_uInt16>(rColorStops.size()));
+ aMemStm.WriteUInt16(nTmp);
+
+ for (auto const& rCand : rColorStops)
+ {
+ aMemStm.WriteDouble(rCand.getStopOffset());
+ const basegfx::BColor& rColor(rCand.getStopColor());
+ aMemStm.WriteDouble(rColor.getRed());
+ aMemStm.WriteDouble(rColor.getGreen());
+ aMemStm.WriteDouble(rColor.getBlue());
+ }
+
+ // Add a new MetaCommentAction section of type 'BGRAD_SEQ_BEGIN/BGRAD_SEQ_END'
+ // that is capable of holding the new color step information, plus the
+ // already used MetaActionType::GRADIENTEX.
+ // With that combination only places that know about that new BGRAD_SEQ_* will
+ // use it while all others will work on the created decomposition of the
+ // gradient for compatibility - which are single-color filled polygons
+ pMetaFile->AddAction(new MetaCommentAction(
+ "BGRAD_SEQ_BEGIN"_ostr, 0, static_cast<const sal_uInt8*>(aMemStm.GetData()),
+ aMemStm.TellEnd()));
+
+ // create MetaActionType::GRADIENTEX
+ // NOTE: with the new BGRAD_SEQ_* we could use basegfx::B2DPolygon and
+ // basegfx::BGradient here directly, but may have to add streaming OPs
+ // for these, so for now just go with what we use all the time. The real
+ // work for improvement should not go to this 'compromize' but to a real
+ // re-work of the SVG export (or/and others) to no longer work on metafiles
+ // but on UNO API or primitives (whatever fits best to the specific export)
+ fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon);
+ Gradient aVCLGradient;
+ impConvertFillGradientAttributeToVCLGradient(aVCLGradient, rFillGradient, false);
+ aLocalPolyPolygon.transform(maCurrentTransformation);
+ const tools::PolyPolygon aToolsPolyPolygon(
+ getFillPolyPolygon(basegfx::utils::adaptiveSubdivideByAngle(aLocalPolyPolygon)));
+ mpOutputDevice->DrawGradient(aToolsPolyPolygon, aVCLGradient);
+ }
+
+ // use decompose to draw, will create PolyPolygon ColorFill actions
+ process(rGradientCandidate);
+
+ // tdf#155479 only add 'BGRAD_SEQ_END' if SVG export
+ if (nullptr != pMetaFile && pMetaFile->getSVG())
+ {
+ // close the BGRAD_SEQ_* actions range
+ pMetaFile->AddAction(new MetaCommentAction("BGRAD_SEQ_END"_ostr));
+ }
+
+ 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, rFillGradient, 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: // css::awt::GradientStyle_LINEAR:
+ case css::awt::GradientStyle_AXIAL:
+ eGrad = SvtGraphicFill::GradientType::Linear;
+ break;
+ case css::awt::GradientStyle_RADIAL:
+ case css::awt::GradientStyle_ELLIPTICAL:
+ eGrad = SvtGraphicFill::GradientType::Radial;
+ break;
+ case css::awt::GradientStyle_SQUARE:
+ case css::awt::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;
+
+ // tdf#155479 always forward propagate SVG flag for sub-content,
+ // it may contain cannotBeHandledByVCL gradients or transparencyGradients
+ aContentMetafile.setSVG(mpOutputDevice->GetConnectMetaFile()->getSVG());
+
+ 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(css::awt::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. The hope is to handle
+ // the more specific case in a better way than the general
+ // TransparencePrimitive2D which has strongly separated
+ // definitions for transparency and content, both completely
+ // free definable by primitives
+ const primitive2d::FillGradientPrimitive2D* pFiGradient(nullptr);
+ static bool bForceToBigTransparentVDev(false); // loplugin:constvars:ignore
+
+ // check for single FillGradientPrimitive2D
+ if (!bForceToBigTransparentVDev && 1 == rTransparence.size())
+ {
+ pFiGradient
+ = dynamic_cast<const primitive2d::FillGradientPrimitive2D*>(rTransparence[0].get());
+
+ // check also for correct ID to exclude derived implementations
+ if (pFiGradient
+ && PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D != pFiGradient->getPrimitive2DID())
+ pFiGradient = nullptr;
+ }
+
+ // tdf#155479 preps for holding extra-MCGR infos
+ bool bSVGTransparencyColorStops(false);
+ basegfx::BColorStops aSVGTransparencyColorStops;
+
+ // MCGR: tdf#155437 If we have identified a transparency gradient,
+ // check if VCL is able to handle it at all
+ if (nullptr != pFiGradient && pFiGradient->getFillGradient().cannotBeHandledByVCL())
+ {
+ // If not, reset the pointer and do not make use of this special case.
+ // Adding a gradient in incomplete state that can not be handled by vcl
+ // makes no sense and will knowingly lead to errors, especially with
+ // MCGR extended possibilities. I checked what happens with the
+ // MetaFloatTransparentAction added by OutputDevice::DrawTransparent, but
+ // in most cases it gets converted to bitmap or even ignored, see e.g.
+ // - vcl/source/gdi/pdfwriter_impl2.cxx for PDF export
+ // - vcl/source/filter/wmf/wmfwr.cxx -> does ignore TransparenceGradient completely
+ // - vcl/source/filter/wmf/emfwr.cxx -> same
+ // - vcl/source/filter/eps/eps.cxx -> same
+ // NOTE: Theoretically it would be possible to make the new extended Gradient data
+ // available in metafiles, with the known limitations (not backward comp, all
+ // places using it would need adaption, ...), but combined with knowing that nearly
+ // all usages ignore or render it locally anyways makes that a non-option.
+
+ // tdf#155479 Yepp, as already mentioned above we need to add
+ // some MCGR infos in case of SVG export, prepare that here
+ if (mpOutputDevice->GetConnectMetaFile()->getSVG())
+ {
+ // for SVG, do not use decompose & prep extra data
+ bSVGTransparencyColorStops = true;
+ aSVGTransparencyColorStops = pFiGradient->getFillGradient().getColorStops();
+ }
+ else
+ {
+ // use decomposition
+ pFiGradient = nullptr;
+ }
+ }
+
+ if (nullptr != pFiGradient)
+ {
+ // this combination of Gradient can be expressed/handled by
+ // vcl/metafile, so add it directly. various content, create content-metafile
+ GDIMetaFile aContentMetafile;
+
+ // tdf#155479 always forward propagate SVG flag for sub-content,
+ // it may contain cannotBeHandledByVCL gradients or transparencyGradients
+ aContentMetafile.setSVG(mpOutputDevice->GetConnectMetaFile()->getSVG());
+
+ const tools::Rectangle aPrimitiveRectangle(impDumpToMetaFile(rContent, aContentMetafile));
+
+ // re-create a VCL-gradient from FillGradientPrimitive2D
+ Gradient aVCLGradient;
+ impConvertFillGradientAttributeToVCLGradient(aVCLGradient, pFiGradient->getFillGradient(),
+ true);
+
+ if (bSVGTransparencyColorStops)
+ {
+ // tdf#155479 create action directly & add extra
+ // MCGR infos to the metafile, do that by adding - ONLY in
+ // case of SVG export - to the MetaFileAction. For that
+ // reason, do what OutputDevice::DrawTransparent will do,
+ // but locally.
+ // NOTE: That would be good for this whole
+ // VclMetafileProcessor2D anyways to allow to get it
+ // completely independent from OutputDevice in the long run
+ GDIMetaFile* pMetaFile(mpOutputDevice->GetConnectMetaFile());
+ rtl::Reference<::MetaFloatTransparentAction> pAction(
+ new MetaFloatTransparentAction(aContentMetafile, aPrimitiveRectangle.TopLeft(),
+ aPrimitiveRectangle.GetSize(), aVCLGradient));
+
+ pAction->addSVGTransparencyColorStops(aSVGTransparencyColorStops);
+ pMetaFile->AddAction(pAction);
+ }
+ else
+ {
+ // render it to VCL (creates MetaFloatTransparentAction)
+ mpOutputDevice->DrawTransparent(aContentMetafile, aPrimitiveRectangle.TopLeft(),
+ aPrimitiveRectangle.GetSize(), aVCLGradient);
+ }
+ return;
+ }
+
+ // Here we need to create a correct replacement visualization for the
+ // TransparencePrimitive2D for the target metafile.
+ // I replaced the n'th iteration to convert-to-bitmap which was
+ // used here by using the existing tooling. The orig here was also producing
+ // transparency errors with test-file from tdf#155437 on the right part of the
+ // image.
+ // Just rely on existing tooling doing the right thing in one place, so also
+ // corrections/optimizations can be in one single place
+
+ // Start by getting logic range of content, transform object-to-world, then world-to-view
+ // to get to discrete values ('pixels'). Matrix multiplication is right-to-left (and not
+ // commutative)
+ basegfx::B2DRange aLogicRange(rTransparenceCandidate.getB2DRange(getViewInformation2D()));
+ aLogicRange.transform(mpOutputDevice->GetViewTransformation() * maCurrentTransformation);
+
+ // expand in discrete coordinates to next-bigger 'pixel' boundaries and remember
+ // created discrete range
+ aLogicRange.expand(
+ basegfx::B2DPoint(floor(aLogicRange.getMinX()), floor(aLogicRange.getMinY())));
+ aLogicRange.expand(basegfx::B2DPoint(ceil(aLogicRange.getMaxX()), ceil(aLogicRange.getMaxY())));
+ const basegfx::B2DRange aDiscreteRange(aLogicRange);
+
+ // transform back from discrete to world coordinates: this creates the
+ // pixel-boundaries extended logic range we need to cover all content
+ // reliably
+ aLogicRange.transform(mpOutputDevice->GetInverseViewTransformation());
+
+ // create transform embedding for renderer. Goal is to translate what we
+ // want to paint to top/left 0/0 and the calculated discrete size
+ basegfx::B2DHomMatrix aEmbedding(basegfx::utils::createTranslateB2DHomMatrix(
+ -aLogicRange.getMinX(), -aLogicRange.getMinY()));
+ const double fLogicWidth(
+ basegfx::fTools::equalZero(aLogicRange.getWidth()) ? 1.0 : aLogicRange.getWidth());
+ const double fLogicHeight(
+ basegfx::fTools::equalZero(aLogicRange.getHeight()) ? 1.0 : aLogicRange.getHeight());
+ aEmbedding.scale(aDiscreteRange.getWidth() / fLogicWidth,
+ aDiscreteRange.getHeight() / fLogicHeight);
+
+ // use the whole TransparencePrimitive2D as input (no need to create a new
+ // one with the sub-contents, these are ref-counted) and add to embedding
+ // primitive2d::TransparencePrimitive2D& rTrCand();
+ primitive2d::Primitive2DContainer xEmbedSeq{ &const_cast<primitive2d::TransparencePrimitive2D&>(
+ rTransparenceCandidate) };
+
+ // tdf#158743 when embedding, do not forget to 1st apply the evtl. used
+ // CurrentTransformation (right-to-left, apply that 1st)
+ xEmbedSeq = primitive2d::Primitive2DContainer{ new primitive2d::TransformPrimitive2D(
+ aEmbedding * maCurrentTransformation, std::move(xEmbedSeq)) };
+
+ // use empty ViewInformation & a useful MaximumQuadraticPixels
+ // limitation to paint the content
+ const auto aViewInformation2D(geometry::createViewInformation2D({}));
+ const sal_uInt32 nMaximumQuadraticPixels(500000);
+ const BitmapEx aBitmapEx(convertToBitmapEx(
+ std::move(xEmbedSeq), aViewInformation2D, basegfx::fround(aDiscreteRange.getWidth()),
+ basegfx::fround(aDiscreteRange.getHeight()), nMaximumQuadraticPixels));
+
+ // add to target metafile (will create MetaFloatTransparentAction)
+ mpOutputDevice->DrawBitmapEx(
+ Point(basegfx::fround(aLogicRange.getMinX()), basegfx::fround(aLogicRange.getMinY())),
+ Size(basegfx::fround(aLogicRange.getWidth()), basegfx::fround(aLogicRange.getHeight())),
+ aBitmapEx);
+}
+
+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));
+ ::std::optional<sal_Int32> oAnchorParent;
+
+ if (!rStructureTagCandidate.isTaggedSdrObject())
+ {
+ bTagUsed = false;
+ }
+
+ if (mpPDFExtOutDevData && bTagUsed)
+ {
+ // foreground object: tag as regular structure element
+ if (!rStructureTagCandidate.isBackground())
+ {
+ if (rStructureTagCandidate.GetAnchorStructureElementKey() != nullptr)
+ {
+ sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement(
+ rStructureTagCandidate.GetAnchorStructureElementKey());
+ oAnchorParent.emplace(mpPDFExtOutDevData->GetCurrentStructureElement());
+ mpPDFExtOutDevData->SetCurrentStructureElement(id);
+ }
+ mpPDFExtOutDevData->WrapBeginStructureElement(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:
+ case vcl::PDFWriter::Annot:
+ {
+ 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::Annot)
+ {
+ mpPDFExtOutDevData->SetStructureAnnotIds(rStructureTagCandidate.GetAnnotIds());
+ }
+ 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->WrapBeginStructureElement(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 (oAnchorParent)
+ {
+ mpPDFExtOutDevData->SetCurrentStructureElement(*oAnchorParent);
+ }
+ }
+}
+
+} // 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 0000000000..c315281ebf
--- /dev/null
+++ b/drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx
@@ -0,0 +1,214 @@
+/* -*- 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
+#include <vcl/lazydelete.hxx>
+
+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);
+
+ /// 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 vcl::DeleteOnDeinit<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 0000000000..e71cda4a0b
--- /dev/null
+++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
@@ -0,0 +1,1131 @@
+/* -*- 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/outdev.hxx>
+#include <vcl/hatch.hxx>
+#include <vcl/canvastools.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/utils/gradienttools.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/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
+{
+VclPixelProcessor2D::VclPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation,
+ OutputDevice& rOutDev,
+ const basegfx::BColorModifierStack& rInitStack)
+ : VclProcessor2D(rViewInformation, rOutDev, rInitStack)
+ , m_nOrigAntiAliasing(rOutDev.GetAntialiasing())
+{
+ // 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 (rViewInformation.getUseAntiAliasing())
+ {
+ mpOutputDevice->SetAntialiasing(m_nOrigAntiAliasing | AntialiasingFlags::Enable);
+ }
+ else
+ {
+ mpOutputDevice->SetAntialiasing(m_nOrigAntiAliasing & ~AntialiasingFlags::Enable);
+ }
+}
+
+VclPixelProcessor2D::~VclPixelProcessor2D()
+{
+ // restore MapMode
+ mpOutputDevice->Pop();
+
+ // restore AntiAliasing
+ mpOutputDevice->SetAntialiasing(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());
+}
+
+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_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_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)
+{
+ basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolygonCandidate.getB2DPolyPolygon());
+
+ // no geometry, no need to render, done
+ if (!aLocalPolyPolygon.count())
+ return;
+
+ // *try* direct draw (AKA using old VCL stuff) to render gradient
+ const attribute::FillGradientAttribute& rGradient(rPolygonCandidate.getFillGradient());
+
+ // MCGR: *many* - and not only GradientStops - cases cannot be handled by VCL
+ // so use decomposition
+ // NOTE: There may be even more reasons to detect, e.g. a ViewTransformation
+ // that uses shear/rotate/mirror (what VCL cannot handle at all), see
+ // other checks already in processFillGradientPrimitive2D
+ if (rGradient.cannotBeHandledByVCL())
+ {
+ process(rPolygonCandidate);
+ return;
+ }
+
+ basegfx::BColor aStartColor(
+ maBColorModifierStack.getModifiedColor(rGradient.getColorStops().front().getStopColor()));
+ basegfx::BColor aEndColor(
+ maBColorModifierStack.getModifiedColor(rGradient.getColorStops().back().getStopColor()));
+
+ 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);
+ return;
+ }
+
+ // use the primitive decomposition
+ 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 && getViewInformation2D().getUseAntiAliasing()
+ && (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 (getViewInformation2D().getUseAntiAliasing())
+ {
+ // 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(getViewInformation2D().getPixelSnapHairline());
+ const AntialiasingFlags nOldAntiAliase(mpOutputDevice->GetAntialiasing());
+
+ if (bForceLineSnap)
+ {
+ mpOutputDevice->SetAntialiasing(nOldAntiAliase | AntialiasingFlags::PixelSnapHairline);
+ }
+
+ process(rCandidate);
+
+ if (bForceLineSnap)
+ {
+ mpOutputDevice->SetAntialiasing(nOldAntiAliase);
+ }
+}
+
+void VclPixelProcessor2D::processFillGradientPrimitive2D(
+ const primitive2d::FillGradientPrimitive2D& rPrimitive)
+{
+ const attribute::FillGradientAttribute& rFillGradient = rPrimitive.getFillGradient();
+ bool useDecompose(false);
+
+ // MCGR: *many* - and not only GradientStops - cases cannot be handled by VCL
+ // so use decomposition
+ if (rFillGradient.cannotBeHandledByVCL())
+ {
+ useDecompose = true;
+ }
+
+ // tdf#149754 VCL gradient draw is not capable to handle all primitive gradient definitions,
+ // what should be clear due to being developed to extend/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 gradient 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 (!useDecompose && !rPrimitive.getDefinitionRange().isInside(rPrimitive.getOutputRange()))
+ {
+ useDecompose = true;
+ }
+
+ // tdf#151081 need to use regular primitive decomposition when the gradient
+ // is transformed in any other way then just translate & scale
+ if (!useDecompose)
+ {
+ basegfx::B2DVector aScale, aTranslate;
+ double fRotate, fShearX;
+
+ maCurrentTransformation.decompose(aScale, aTranslate, fRotate, fShearX);
+
+ // detect if transformation is rotated, sheared or mirrored in X and/or Y
+ if (!basegfx::fTools::equalZero(fRotate) || !basegfx::fTools::equalZero(fShearX)
+ || aScale.getX() < 0.0 || aScale.getY() < 0.0)
+ {
+ useDecompose = true;
+ }
+ }
+
+ if (useDecompose)
+ {
+ // default is to use the direct render below. For security,
+ // keep the (simple) fallback to decompose in place here
+ static bool bTryDirectRender(true);
+
+ if (bTryDirectRender)
+ {
+ // MCGR: Avoid one level of primitive creation, use FillGradientPrimitive2D
+ // tooling to directly create needed geometry & color for getting better
+ // performance (partially compensate for potentially more expensive multi
+ // color gradients).
+ // To handle a primitive that needs paint, either use decompose, or - when you
+ // do not want that for any reason, e.g. extra primitives created - implement
+ // a direct handling in your primitive renderer. This is always possible
+ // since primitives by definition are self-contained what means they have all
+ // needed data locally available to do so.
+ // The question is the complexity to invest - the implemented decompose
+ // is always a good hint of what is needed to do this. In this case I decided
+ // to add some tooling methods to the primitive itself to support this. These
+ // are used in decompose and can be used - as here now - for direct handling,
+ // too. This is always a possibility in primitive handling - you can, but do not
+ // have to.
+ mpOutputDevice->SetFillColor(
+ Color(maBColorModifierStack.getModifiedColor(rPrimitive.getOuterColor())));
+ mpOutputDevice->SetLineColor();
+ mpOutputDevice->DrawTransparent(
+ maCurrentTransformation,
+ basegfx::B2DPolyPolygon(
+ basegfx::utils::createPolygonFromRect(rPrimitive.getOutputRange())),
+ 0.0);
+
+ // paint solid fill steps by providing callback as lambda
+ auto aCallback([&rPrimitive, this](const basegfx::B2DHomMatrix& rMatrix,
+ const basegfx::BColor& rColor) {
+ // create part polygon
+ basegfx::B2DPolygon aNewPoly(rPrimitive.getUnitPolygon());
+ aNewPoly.transform(rMatrix);
+
+ // create solid fill
+ mpOutputDevice->SetFillColor(Color(maBColorModifierStack.getModifiedColor(rColor)));
+ mpOutputDevice->DrawTransparent(maCurrentTransformation,
+ basegfx::B2DPolyPolygon(aNewPoly), 0.0);
+ });
+
+ // call value generator to trigger callbacks
+ rPrimitive.generateMatricesAndColors(aCallback);
+ }
+ else
+ {
+ // use the decompose
+ process(rPrimitive);
+ }
+
+ return;
+ }
+
+ // try to use vcl - since vcl uses the old gradient paint mechanisms this may
+ // create wrong geometries. If so, add another case above for useDecompose
+ Gradient aGradient(rFillGradient.getStyle(),
+ Color(rFillGradient.getColorStops().front().getStopColor()),
+ Color(rFillGradient.getColorStops().back().getStopColor()));
+
+ 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) || !getViewInformation2D().getUseAntiAliasing())
+ {
+ 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 0000000000..c144ba9647
--- /dev/null
+++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx
@@ -0,0 +1,109 @@
+/* -*- 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 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
+{
+ AntialiasingFlags m_nOrigAntiAliasing;
+
+ /* 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 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 0000000000..2c3521ace0
--- /dev/null
+++ b/drawinglayer/source/processor2d/vclprocessor2d.cxx
@@ -0,0 +1,1560 @@
+/* -*- 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 <comphelper/lok.hxx>
+#include <svtools/optionsdrawinglayer.hxx>
+#include <tools/debug.hxx>
+#include <tools/fract.hxx>
+#include <utility>
+#include <vcl/glyphitemcache.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/kernarray.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
+{
+/** helper to convert a MapMode to a transformation */
+basegfx::B2DHomMatrix getTransformFromMapMode(const MapMode& rMapMode)
+{
+ basegfx::B2DHomMatrix aMapping;
+ const Fraction aNoScale(1, 1);
+ const Point& rOrigin(rMapMode.GetOrigin());
+
+ if (0 != rOrigin.X() || 0 != rOrigin.Y())
+ {
+ aMapping.translate(rOrigin.X(), rOrigin.Y());
+ }
+
+ if (rMapMode.GetScaleX() != aNoScale || rMapMode.GetScaleY() != aNoScale)
+ {
+ aMapping.scale(double(rMapMode.GetScaleX()), double(rMapMode.GetScaleY()));
+ }
+
+ return aMapping;
+}
+}
+
+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))
+ {
+ double fIgnoreRotate, fIgnoreShearX;
+
+ basegfx::B2DVector aFontSize, aTextTranslate;
+ rTextCandidate.getTextTransform().decompose(aFontSize, aTextTranslate, fIgnoreRotate,
+ fIgnoreShearX);
+
+ // tdf#153092 Ideally we don't have to scale the font and dxarray, but we might have
+ // to nevertheless if dealing with non integer sizes
+ const bool bScaleFont(aFontSize.getY() != std::round(aFontSize.getY())
+ || comphelper::LibreOfficeKit::isActive());
+ vcl::Font aFont;
+
+ // Get the VCL font
+ if (!bScaleFont)
+ {
+ aFont = primitive2d::getVclFontFromFontAttribute(
+ rTextCandidate.getFontAttribute(), aFontSize.getX(), aFontSize.getY(), fRotate,
+ rTextCandidate.getLocale());
+ }
+ else
+ {
+ 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());
+ aFont.SetTransparent(aFillColor.IsTransparent());
+ aFont.SetFillColor(aFillColor);
+
+ // 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 integer DXArray
+ KernArray aDXArray;
+
+ if (!rTextCandidate.getDXArray().empty())
+ {
+ double fPixelVectorFactor(1.0);
+ if (bScaleFont)
+ {
+ const basegfx::B2DVector aPixelVector(maCurrentTransformation
+ * basegfx::B2DVector(1.0, 0.0));
+ fPixelVectorFactor = aPixelVector.getLength();
+ }
+
+ aDXArray.reserve(rTextCandidate.getDXArray().size());
+ for (auto const& elem : rTextCandidate.getDXArray())
+ aDXArray.push_back(basegfx::fround(elem * fPixelVectorFactor));
+ }
+
+ // set parameters and paint text snippet
+ const basegfx::BColor aRGBFontColor(
+ maBColorModifierStack.getModifiedColor(rTextCandidate.getFontColor()));
+ 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->SetTextColor(Color(aRGBFontColor));
+
+ OUString aText(rTextCandidate.getText());
+ sal_Int32 nPos = rTextCandidate.getTextPosition();
+ sal_Int32 nLen = rTextCandidate.getTextLength();
+
+ // this contraption is used in editeng, with format paragraph used to
+ // set a tab with a tab-fill character
+ if (rTextCandidate.isFilled())
+ {
+ tools::Long nWidthToFill = rTextCandidate.getWidthToFill();
+
+ tools::Long nWidth
+ = mpOutputDevice->GetTextArray(rTextCandidate.getText(), &aDXArray, 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 (!aDXArray.empty())
+ {
+ sal_Int32 nDX = aDXArray[0];
+ aDXArray.resize(nLen);
+ for (sal_Int32 i = 1; i < nLen; ++i)
+ aDXArray.set(i, aDXArray[i - 1] + nDX);
+ }
+ }
+
+ Point aStartPoint;
+ bool bChangeMapMode(false);
+ if (!bScaleFont)
+ {
+ basegfx::B2DHomMatrix aCombinedTransform(
+ getTransformFromMapMode(mpOutputDevice->GetMapMode())
+ * maCurrentTransformation);
+
+ basegfx::B2DVector aCurrentScaling, aCurrentTranslate;
+ double fCurrentRotate;
+ aCombinedTransform.decompose(aCurrentScaling, aCurrentTranslate, fCurrentRotate,
+ fIgnoreShearX);
+
+ const Point aOrigin(
+ basegfx::fround(aCurrentTranslate.getX() / aCurrentScaling.getX()),
+ basegfx::fround(aCurrentTranslate.getY() / aCurrentScaling.getY()));
+
+ Fraction aScaleX(aCurrentScaling.getX());
+ if (!aScaleX.IsValid())
+ {
+ SAL_WARN("drawinglayer", "invalid X Scale");
+ return;
+ }
+
+ Fraction aScaleY(aCurrentScaling.getY());
+ if (!aScaleY.IsValid())
+ {
+ SAL_WARN("drawinglayer", "invalid Y Scale");
+ return;
+ }
+
+ MapMode aMapMode(mpOutputDevice->GetMapMode().GetMapUnit(), aOrigin, aScaleX,
+ aScaleY);
+
+ if (fCurrentRotate)
+ aTextTranslate *= basegfx::utils::createRotateB2DHomMatrix(fCurrentRotate);
+ aStartPoint = Point(aTextTranslate.getX(), aTextTranslate.getY());
+
+ bChangeMapMode = aMapMode != mpOutputDevice->GetMapMode();
+ if (bChangeMapMode)
+ {
+ mpOutputDevice->Push(vcl::PushFlags::MAPMODE);
+ mpOutputDevice->SetRelativeMapMode(aMapMode);
+ }
+ }
+ else
+ {
+ const basegfx::B2DPoint aPoint(aLocalTransform * basegfx::B2DPoint(0.0, 0.0));
+ aStartPoint = Point(basegfx::fround(aPoint.getX()), basegfx::fround(aPoint.getY()));
+ }
+
+ // tdf#152990 set the font after the MapMode is (potentially) set so canvas uses the desired
+ // font size
+ mpOutputDevice->SetFont(aFont);
+
+ if (!aDXArray.empty())
+ {
+ const SalLayoutGlyphs* pGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs(
+ mpOutputDevice, aText, nPos, nLen);
+ mpOutputDevice->DrawTextArray(aStartPoint, aText, aDXArray,
+ rTextCandidate.getKashidaArray(), nPos, nLen,
+ SalLayoutFlags::NONE, pGlyphs);
+ }
+ else
+ {
+ mpOutputDevice->DrawText(aStartPoint, aText, nPos, nLen);
+ }
+
+ if (rTextCandidate.getFontAttribute().getRTL())
+ {
+ mpOutputDevice->SetLayoutMode(nOldLayoutMode);
+ }
+
+ if (bChangeMapMode)
+ mpOutputDevice->Pop();
+
+ 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 && getViewInformation2D().getPixelSnapHairline())
+ {
+ // #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(rBitmapCandidate.getBitmap());
+ 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)
+{
+ bool bPrimitiveAccepted = RenderFillGraphicPrimitive2DImpl(rFillBitmapCandidate);
+
+ if (!bPrimitiveAccepted)
+ {
+ // do not accept, use decomposition
+ process(rFillBitmapCandidate);
+ }
+}
+
+bool VclProcessor2D::RenderFillGraphicPrimitive2DImpl(
+ const primitive2d::FillGraphicPrimitive2D& rFillBitmapCandidate)
+{
+ const attribute::FillGraphicAttribute& rFillGraphicAttribute(
+ rFillBitmapCandidate.getFillGraphic());
+
+ // #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())
+ return false;
+
+ // 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())
+ return false;
+
+ // 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))
+ return false;
+
+ // no shear or rotate, draw direct in pixel coordinates
+
+ // 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)
+ return true;
+
+ // 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())));
+
+ // 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);
+ }
+
+ if (maBColorModifierStack.count())
+ {
+ // when color modifier, apply to bitmap
+ aBitmapEx = aBitmapEx.ModifyBitmapEx(maBColorModifierStack);
+
+ // ModifyBitmapEx 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);
+
+ return true;
+ }
+ }
+
+ 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);
+ // the visible rect, in pixels
+ 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);
+ return true;
+}
+
+// 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) || !getViewInformation2D().getUseAntiAliasing())
+ {
+ 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();
+ geometry::ViewInformation2D aViewInformation2D(getViewInformation2D());
+ aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation()
+ * rTransformCandidate.getTransformation());
+ 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
+ geometry::ViewInformation2D aViewInformation2D(getViewInformation2D());
+ aViewInformation2D.setVisualizedPage(rPagePreviewCandidate.getXDrawPage());
+ 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(getViewInformation2D().getUseAntiAliasing());
+ 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, basegfx::BColorModifierStack aInitStack)
+ : BaseProcessor2D(rViewInformation)
+ , mpOutputDevice(&rOutDev)
+ , maBColorModifierStack(std::move(aInitStack))
+ , 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 0000000000..d2f0a69157
--- /dev/null
+++ b/drawinglayer/source/processor2d/vclprocessor2d.hxx
@@ -0,0 +1,123 @@
+/* -*- 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,
+ basegfx::BColorModifierStack aInitStack = basegfx::BColorModifierStack());
+ virtual ~VclProcessor2D() override;
+
+private:
+ bool RenderFillGraphicPrimitive2DImpl(
+ const primitive2d::FillGraphicPrimitive2D& rFillBitmapCandidate);
+};
+} // end of namespace drawinglayer::processor2d
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */