[Libreoffice-commits] core.git: Branch 'distro/lhm/libreoffice-5-2+backports' - basegfx/Library_basegfx.mk basegfx/source drawinglayer/source include/basegfx include/vcl vcl/headless vcl/inc vcl/opengl vcl/quartz vcl/source vcl/unx vcl/win

Libreoffice Gerrit user logerrit at kemper.freedesktop.org
Sat Sep 8 21:51:39 UTC 2018


 basegfx/Library_basegfx.mk                              |    1 
 basegfx/source/polygon/b2dpolygon.cxx                   |   47 +-
 basegfx/source/tools/systemdependentdata.cxx            |  141 ++++++
 drawinglayer/source/primitive2d/polygonprimitive2d.cxx  |    6 
 drawinglayer/source/processor2d/vclpixelprocessor2d.cxx |   62 +-
 include/basegfx/polygon/b2dpolygon.hxx                  |   26 +
 include/basegfx/tools/systemdependentdata.hxx           |  139 ++++++
 include/vcl/outdev.hxx                                  |    1 
 vcl/headless/svpgdi.cxx                                 |  270 ++++++++++-
 vcl/inc/headless/svpgdi.hxx                             |   34 +
 vcl/inc/openglgdiimpl.hxx                               |    4 
 vcl/inc/quartz/salgdi.h                                 |    4 
 vcl/inc/salgdi.hxx                                      |   11 
 vcl/inc/salgdiimpl.hxx                                  |    4 
 vcl/inc/unx/genpspgraphics.h                            |   15 
 vcl/inc/unx/salgdi.h                                    |    4 
 vcl/inc/win/salbmp.h                                    |   32 -
 vcl/inc/win/salgdi.h                                    |    4 
 vcl/opengl/gdiimpl.cxx                                  |   43 +
 vcl/quartz/salgdicommon.cxx                             |   39 +
 vcl/source/app/svmain.cxx                               |    6 
 vcl/source/gdi/salgdilayout.cxx                         |  183 +++++++-
 vcl/source/outdev/line.cxx                              |   25 -
 vcl/source/outdev/polygon.cxx                           |   82 ++-
 vcl/source/outdev/polyline.cxx                          |  150 ++++--
 vcl/source/outdev/transparent.cxx                       |   28 -
 vcl/unx/generic/gdi/gdiimpl.cxx                         |   41 +
 vcl/unx/generic/gdi/gdiimpl.hxx                         |    4 
 vcl/unx/generic/gdi/salgdi.cxx                          |   25 -
 vcl/unx/generic/print/genpspgraphics.cxx                |    4 
 vcl/win/gdi/gdiimpl.cxx                                 |  362 +++++++++++-----
 vcl/win/gdi/gdiimpl.hxx                                 |    4 
 vcl/win/gdi/salbmp.cxx                                  |  233 +++-------
 vcl/win/gdi/salgdi_gdiplus.cxx                          |   15 
 34 files changed, 1505 insertions(+), 544 deletions(-)

New commits:
commit 7dc383a8fb83c9a361bb057b97226c1d84a7d16a
Author:     Armin Le Grand <Armin.Le.Grand at cib.de>
AuthorDate: Fri Aug 24 13:01:08 2018 +0200
Commit:     Thorsten Behrens <Thorsten.Behrens at CIB.de>
CommitDate: Sat Sep 8 23:51:17 2018 +0200

    Support buffering SystemDependent GraphicData
    
    Note: This is the backport to LO52bp version
    
    This is a first step to allow buffering of system
    dependent data, especially (but not only) for the
    system-dependent implementations of graphic output.
    For example, for B2DPolygon and Win output, it allows
    buffering the Gdiplus::GraphicsPath instead of re-
    creating it all the time.
    To support that, the change includes forwarding the
    current transformation to the renderers in SalGraphics.
    The current state in VCL is to transform all and
    everything to device coordinates at every single
    paint.
    I have currently started to do this for ::drawPolyLine
    implementations. The fallbacks for all systems will
    at the start of that method just transform the data
    to device coordinates, so all works as before.
    This may also be done for FilledPolygon paint in a later
    step, but most urgent is FatLine painting.
    An arrangement of shared_ptr/weak_ptr is used so that
    either the instance buffering (in the example B2DPolygon)
    or the instance managing it can delete it. The instance
    managing it currently uses a 1s Timer and a cycle-lifetime
    management, but that can be extended in the future
    to e.g. include size hints, too.
    The mechanism it designed to support multiple Data per
    buffering element, e.g. for B2DPolygon at the same time
    system-dependent instances of Gdiplus and Cairo can be
    buffered, but also PDF-data.
    This is achieved semi-automatic by using
    typeid(class).hash_code() as key for organization.
    The mechanism will be used for now at B2DPolygon, but
    is not limited to. There is already a similar but less
    general buffer (see GdiPlusBuffer) that can and will
    be converted to use this new mechanism.
    
    Added vcl/headless Cairo renderer to support given
    ObjectToDevice transformation (not to transform given
    B2DPolygon)
    Added support for CairoPath buffered at B2DPolygon,
    seems to work well. Need to do more tests
    
    Moved usage to templates suggested by Noel Grandin
    (Noel Grandin <noelgrandin at gmail.com>), thanks for
    these suggestions. Adapted Win usage to that, too.
    
    Converted Win-specific GdiPlus BitmapBuffer to new
    mechanism, works well. Checked, the manager holds
    now a mix of bitmap and path data under Win
    
    Added a cleanup mechanism to flush all buffered data
    at DeInitVCL() using flushAll() at
    SystemDependentDataBuffer
    
    Adapted Linux-versions of ::drawPolyLine to support
    PixelSnapHairline, for now in a simplified version
    that still allows buffering. This will also be used
    (and use buffering) for the Cairo-fallback in
    X11SalGraphics
    
    Change-Id: I88d7e438a20b96ddab7707050893bdd590c098c7
    Reviewed-on: https://gerrit.libreoffice.org/59555
    Tested-by: Armin Le Grand <Armin.Le.Grand at cib.de>
    Reviewed-by: Armin Le Grand <Armin.Le.Grand at cib.de>
    Reviewed-on: https://gerrit.libreoffice.org/60036
    Reviewed-by: Thorsten Behrens <Thorsten.Behrens at CIB.de>
    Tested-by: Thorsten Behrens <Thorsten.Behrens at CIB.de>

diff --git a/basegfx/Library_basegfx.mk b/basegfx/Library_basegfx.mk
index 68cfe1cfae05..d2d28107d989 100644
--- a/basegfx/Library_basegfx.mk
+++ b/basegfx/Library_basegfx.mk
@@ -72,6 +72,7 @@ $(eval $(call gb_Library_add_exception_objects,basegfx,\
     basegfx/source/tools/keystoplerp \
     basegfx/source/tools/numbertools \
     basegfx/source/tools/stringconversiontools \
+    basegfx/source/tools/systemdependentdata \
     basegfx/source/tools/tools \
     basegfx/source/tools/unopolypolygon \
     basegfx/source/tools/unotools \
diff --git a/basegfx/source/polygon/b2dpolygon.cxx b/basegfx/source/polygon/b2dpolygon.cxx
index 5ad06eaedb57..d07354952358 100644
--- a/basegfx/source/polygon/b2dpolygon.cxx
+++ b/basegfx/source/polygon/b2dpolygon.cxx
@@ -25,6 +25,7 @@
 #include <basegfx/curve/b2dcubicbezier.hxx>
 #include <rtl/instance.hxx>
 #include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/tools/systemdependentdata.hxx>
 #include <algorithm>
 #include <memory>
 #include <vector>
@@ -464,20 +465,21 @@ public:
     }
 };
 
-class ImplBufferedData
+class ImplBufferedData : public basegfx::SystemDependentDataHolder
 {
 private:
     // Possibility to hold the last subdivision
-    std::unique_ptr< basegfx::B2DPolygon >        mpDefaultSubdivision;
+    std::unique_ptr< basegfx::B2DPolygon >  mpDefaultSubdivision;
 
     // Possibility to hold the last B2DRange calculation
-    std::unique_ptr< basegfx::B2DRange >          mpB2DRange;
+    std::unique_ptr< basegfx::B2DRange >    mpB2DRange;
 
 public:
     ImplBufferedData()
     :   mpDefaultSubdivision(),
         mpB2DRange()
-    {}
+    {
+    }
 
     const basegfx::B2DPolygon& getDefaultAdaptiveSubdivision(const basegfx::B2DPolygon& rSource) const
     {
@@ -1092,6 +1094,26 @@ public:
             maPoints.transform(rMatrix);
         }
     }
+
+    void addOrReplaceSystemDependentData(basegfx::SystemDependentData_SharedPtr& rData)
+    {
+        if(!mpBufferedData)
+        {
+            mpBufferedData.reset(new ImplBufferedData);
+        }
+
+        mpBufferedData->addOrReplaceSystemDependentData(rData);
+    }
+
+    basegfx::SystemDependentData_SharedPtr getSystemDependentData(size_t hash_code) const
+    {
+        if(mpBufferedData)
+        {
+            return mpBufferedData->getSystemDependentData(hash_code);
+        }
+
+        return basegfx::SystemDependentData_SharedPtr();
+    }
 };
 
 namespace basegfx
@@ -1464,6 +1486,23 @@ namespace basegfx
         }
     }
 
+    void B2DPolygon::addOrReplaceSystemDependentDataInternal(SystemDependentData_SharedPtr& rData) const
+    {
+        // Need to get ImplB2DPolygon* from cow_wrapper *without*
+        // calling make_unique() here - we do not want to
+        // 'modify' the ImplB2DPolygon, but add buffered data that
+        // is valid for all referencing instances
+        const B2DPolygon* pMe(this);
+        const ImplB2DPolygon* pMyImpl(pMe->mpPolygon.get());
+
+        const_cast<ImplB2DPolygon*>(pMyImpl)->addOrReplaceSystemDependentData(rData);
+    }
+
+    SystemDependentData_SharedPtr B2DPolygon::getSystemDependantDataInternal(size_t hash_code) const
+    {
+        return mpPolygon->getSystemDependentData(hash_code);
+    }
+
 } // end of namespace basegfx
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/basegfx/source/tools/systemdependentdata.cxx b/basegfx/source/tools/systemdependentdata.cxx
new file mode 100755
index 000000000000..ddb99b06e912
--- /dev/null
+++ b/basegfx/source/tools/systemdependentdata.cxx
@@ -0,0 +1,141 @@
+/* -*- 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 <basegfx/tools/systemdependentdata.hxx>
+
+namespace basegfx
+{
+    SystemDependentDataManager::SystemDependentDataManager()
+    {
+    }
+
+    SystemDependentDataManager::~SystemDependentDataManager()
+    {
+    }
+} // namespace basegfx
+
+namespace basegfx
+{
+    MinimalSystemDependentDataManager::MinimalSystemDependentDataManager()
+    :   SystemDependentDataManager(),
+        maSystemDependentDataReferences()
+    {
+    }
+
+    MinimalSystemDependentDataManager::~MinimalSystemDependentDataManager()
+    {
+    }
+
+    void MinimalSystemDependentDataManager::startUsage(basegfx::SystemDependentData_SharedPtr& rData)
+    {
+        if(rData)
+        {
+            maSystemDependentDataReferences.insert(rData);
+        }
+    }
+
+    void MinimalSystemDependentDataManager::endUsage(basegfx::SystemDependentData_SharedPtr& rData)
+    {
+        if(rData)
+        {
+            maSystemDependentDataReferences.erase(rData);
+        }
+    }
+
+    void MinimalSystemDependentDataManager::touchUsage(basegfx::SystemDependentData_SharedPtr& /* rData */)
+    {
+    }
+
+    void MinimalSystemDependentDataManager::flushAll()
+    {
+        maSystemDependentDataReferences.clear();
+    }
+} // namespace basegfx
+
+namespace basegfx
+{
+    SystemDependentData::SystemDependentData(
+        SystemDependentDataManager& rSystemDependentDataManager,
+        sal_uInt32 nHoldCycles)
+    :   mrSystemDependentDataManager(rSystemDependentDataManager),
+        mnHoldCycles(nHoldCycles)
+    {
+    }
+
+    SystemDependentData::~SystemDependentData()
+    {
+    }
+} // namespace basegfx
+
+namespace basegfx
+{
+    SystemDependentDataHolder::SystemDependentDataHolder()
+    :   maSystemDependentReferences()
+    {
+    }
+
+    SystemDependentDataHolder::~SystemDependentDataHolder()
+    {
+        for(auto& candidate : maSystemDependentReferences)
+        {
+            basegfx::SystemDependentData_SharedPtr aData(candidate.second.lock());
+
+            if(aData)
+            {
+                aData->getSystemDependentDataManager().endUsage(aData);
+            }
+        }
+    }
+
+    void SystemDependentDataHolder::addOrReplaceSystemDependentData(basegfx::SystemDependentData_SharedPtr& rData)
+    {
+        const size_t hash_code(typeid(*rData.get()).hash_code());
+        auto result(maSystemDependentReferences.find(hash_code));
+
+        if(result != maSystemDependentReferences.end())
+        {
+            basegfx::SystemDependentData_SharedPtr aData(result->second.lock());
+
+            if(aData)
+            {
+                aData->getSystemDependentDataManager().endUsage(aData);
+            }
+
+            maSystemDependentReferences.erase(result);
+            result = maSystemDependentReferences.end();
+        }
+
+        maSystemDependentReferences[hash_code] = rData;
+        rData->getSystemDependentDataManager().startUsage(rData);
+    }
+
+    SystemDependentData_SharedPtr SystemDependentDataHolder::getSystemDependentData(size_t hash_code) const
+    {
+        basegfx::SystemDependentData_SharedPtr aRetval;
+        auto result(maSystemDependentReferences.find(hash_code));
+
+        if(result != maSystemDependentReferences.end())
+        {
+            aRetval = result->second.lock();
+
+            if(aRetval)
+            {
+                aRetval->getSystemDependentDataManager().touchUsage(aRetval);
+            }
+            else
+            {
+                const_cast< SystemDependentDataHolder* >(this)->maSystemDependentReferences.erase(result);
+            }
+        }
+
+        return aRetval;
+    }
+} // namespace basegfx
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/drawinglayer/source/primitive2d/polygonprimitive2d.cxx b/drawinglayer/source/primitive2d/polygonprimitive2d.cxx
index 76fc498d34d5..3ef630cc7b5c 100644
--- a/drawinglayer/source/primitive2d/polygonprimitive2d.cxx
+++ b/drawinglayer/source/primitive2d/polygonprimitive2d.cxx
@@ -313,6 +313,9 @@ namespace drawinglayer
             maLineAttribute(rLineAttribute),
             maStrokeAttribute(rStrokeAttribute)
         {
+            // simplify curve segments: moved here to not need to use it
+            // at VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect
+            maPolygon = basegfx::tools::simplifyCurveSegments(maPolygon);
         }
 
         PolygonStrokePrimitive2D::PolygonStrokePrimitive2D(
@@ -323,6 +326,9 @@ namespace drawinglayer
             maLineAttribute(rLineAttribute),
             maStrokeAttribute()
         {
+            // simplify curve segments: moved here to not need to use it
+            // at VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect
+            maPolygon = basegfx::tools::simplifyCurveSegments(maPolygon);
         }
 
         bool PolygonStrokePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
index 80f4ab6e0618..91214fbd030c 100644
--- a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
+++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx
@@ -129,9 +129,9 @@ namespace drawinglayer
 
         bool VclPixelProcessor2D::tryDrawPolygonHairlinePrimitive2DDirect(const drawinglayer::primitive2d::PolygonHairlinePrimitive2D& rSource, double fTransparency)
         {
-            basegfx::B2DPolygon aLocalPolygon(rSource.getB2DPolygon());
+            const basegfx::B2DPolygon& rLocalPolygon(rSource.getB2DPolygon());
 
-            if(!aLocalPolygon.count())
+            if(!rLocalPolygon.count())
             {
                 // no geometry, done
                 return true;
@@ -141,15 +141,14 @@ namespace drawinglayer
 
             mpOutputDevice->SetFillColor();
             mpOutputDevice->SetLineColor(Color(aLineColor));
-            aLocalPolygon.transform(maCurrentTransformation);
+            //aLocalPolygon.transform(maCurrentTransformation);
 
             // try drawing; if it did not work, use standard fallback
-            if(mpOutputDevice->DrawPolyLineDirect( aLocalPolygon, 0.0, fTransparency))
-            {
-                return true;
-            }
-
-            return false;
+            return mpOutputDevice->DrawPolyLineDirect(
+                maCurrentTransformation,
+                rLocalPolygon,
+                0.0,
+                fTransparency);
         }
 
         bool VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect(const drawinglayer::primitive2d::PolygonStrokePrimitive2D& rSource, double fTransparency)
@@ -165,6 +164,10 @@ namespace drawinglayer
             aLocalPolygon = basegfx::tools::simplifyCurveSegments(aLocalPolygon);
             basegfx::B2DPolyPolygon aHairLinePolyPolygon;
 
+            // simplify curve segments
+            // moved to PolygonStrokePrimitive2D::PolygonStrokePrimitive2D
+            // aLocalPolygon = basegfx::tools::simplifyCurveSegments(aLocalPolygon);
+
             if(rSource.getStrokeAttribute().isDefault() || 0.0 == rSource.getStrokeAttribute().getFullDotDashLen())
             {
                 // no line dashing, just copy
@@ -187,34 +190,36 @@ namespace drawinglayer
                 return true;
             }
 
-            const basegfx::BColor aLineColor(
-                maBColorModifierStack.getModifiedColor(
-                    rSource.getLineAttribute().getColor()));
-
-            mpOutputDevice->SetFillColor();
-            mpOutputDevice->SetLineColor(Color(aLineColor));
-            aHairLinePolyPolygon.transform(maCurrentTransformation);
-
+            // check if LineWidth can be simplified in world coordinates
             double fLineWidth(rSource.getLineAttribute().getWidth());
 
             if(basegfx::fTools::more(fLineWidth, 0.0))
             {
                 basegfx::B2DVector aLineWidth(fLineWidth, 0.0);
-
                 aLineWidth = maCurrentTransformation * aLineWidth;
-                fLineWidth = aLineWidth.getLength();
-            }
+                const double fWorldLineWidth(aLineWidth.getLength());
 
-            // draw simple hairline for small line widths
-            // see also RenderPolygonStrokePrimitive2D which is used if this try fails
-            bool bIsAntiAliasing = getOptionsDrawinglayer().IsAntiAliasing();
-            if (   (basegfx::fTools::lessOrEqual(fLineWidth, 1.0) && bIsAntiAliasing)
-                || (basegfx::fTools::lessOrEqual(fLineWidth, 1.5) && !bIsAntiAliasing))
-            {
-                // draw simple hairline
-                fLineWidth = 0.0;
+                // draw simple hairline for small line widths
+                // see also RenderPolygonStrokePrimitive2D which is used if this try fails
+                bool bIsAntiAliasing = getOptionsDrawinglayer().IsAntiAliasing();
+                if (   (basegfx::fTools::lessOrEqual(fWorldLineWidth, 1.0) && bIsAntiAliasing)
+                    || (basegfx::fTools::lessOrEqual(fWorldLineWidth, 1.5) && !bIsAntiAliasing))
+                {
+                    // draw simple hairline
+                    fLineWidth = 0.0;
+                }
             }
 
+            const basegfx::BColor aLineColor(
+                maBColorModifierStack.getModifiedColor(
+                    rSource.getLineAttribute().getColor()));
+
+            mpOutputDevice->SetFillColor();
+            mpOutputDevice->SetLineColor(Color(aLineColor));
+
+            // do not transform self
+            // aHairLinePolyPolygon.transform(maCurrentTransformation);
+
             bool bHasPoints(false);
             bool bTryWorked(false);
 
@@ -227,6 +232,7 @@ namespace drawinglayer
                     bHasPoints = true;
 
                     if(mpOutputDevice->DrawPolyLineDirect(
+                        maCurrentTransformation,
                         aSingle,
                         fLineWidth,
                         fTransparency,
diff --git a/include/basegfx/polygon/b2dpolygon.hxx b/include/basegfx/polygon/b2dpolygon.hxx
index 6ef76933eebe..a1766bd09ffb 100644
--- a/include/basegfx/polygon/b2dpolygon.hxx
+++ b/include/basegfx/polygon/b2dpolygon.hxx
@@ -21,7 +21,7 @@
 #define INCLUDED_BASEGFX_POLYGON_B2DPOLYGON_HXX
 
 #include <ostream>
-
+#include <memory>
 #include <sal/types.h>
 #include <o3tl/cow_wrapper.hxx>
 #include <basegfx/vector/b2enums.hxx>
@@ -29,6 +29,7 @@
 #include <basegfx/basegfxdllapi.h>
 
 class ImplB2DPolygon;
+class SalGraphicsImpl;
 
 namespace basegfx
 {
@@ -37,6 +38,9 @@ namespace basegfx
     class B2DVector;
     class B2DHomMatrix;
     class B2DCubicBezier;
+    class SystemDependentData;
+    class SystemDependentDataManager;
+    typedef std::shared_ptr<SystemDependentData> SystemDependentData_SharedPtr;
 }
 
 namespace basegfx
@@ -214,6 +218,26 @@ namespace basegfx
 
         /// apply transformation given in matrix form
         void transform(const basegfx::B2DHomMatrix& rMatrix);
+
+        // exclusive management op's for SystemDependentData at B2DPolygon
+        template<class T>
+        std::shared_ptr<T> getSystemDependentData() const
+        {
+            return std::static_pointer_cast<T>(getSystemDependantDataInternal(typeid(T).hash_code()));
+        }
+
+        template<class T, class... Args>
+        std::shared_ptr<T> addOrReplaceSystemDependentData(SystemDependentDataManager& manager, Args&&... args) const
+        {
+            std::shared_ptr<T> r = std::make_shared<T>(manager, std::forward<Args>(args)...);
+            basegfx::SystemDependentData_SharedPtr r2(r);
+            addOrReplaceSystemDependentDataInternal(r2);
+            return r;
+        }
+
+    private:
+        void addOrReplaceSystemDependentDataInternal(SystemDependentData_SharedPtr& rData) const;
+        SystemDependentData_SharedPtr getSystemDependantDataInternal(size_t hash_code) const;
     };
 
     // typedef for a vector of B2DPolygons
diff --git a/include/basegfx/tools/systemdependentdata.hxx b/include/basegfx/tools/systemdependentdata.hxx
new file mode 100755
index 000000000000..17a0ce44f815
--- /dev/null
+++ b/include/basegfx/tools/systemdependentdata.hxx
@@ -0,0 +1,139 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_BASEGFX_SYSTEMDEPENDENTDATA_HXX
+#define INCLUDED_BASEGFX_SYSTEMDEPENDENTDATA_HXX
+
+#include <sal/types.h>
+#include <basegfx/basegfxdllapi.h>
+#include <memory>
+#include <map>
+#include <set>
+
+namespace basegfx
+{
+    class SystemDependentData;
+    typedef std::shared_ptr<SystemDependentData> SystemDependentData_SharedPtr;
+    typedef std::weak_ptr<SystemDependentData> SystemDependentData_WeakPtr;
+} // end of namespace basegfx
+
+namespace basegfx
+{
+    class BASEGFX_DLLPUBLIC SystemDependentDataManager
+    {
+    private:
+        // noncopyable
+        SystemDependentDataManager(const SystemDependentDataManager&) = delete;
+        SystemDependentDataManager& operator=(const SystemDependentDataManager&) = delete;
+
+    public:
+        SystemDependentDataManager();
+        virtual ~SystemDependentDataManager();
+
+        // call from (and with) SystemDependentData objects when start/end/touch
+        // usage is needed
+        virtual void startUsage(basegfx::SystemDependentData_SharedPtr& rData) = 0;
+        virtual void endUsage(basegfx::SystemDependentData_SharedPtr& rData) = 0;
+        virtual void touchUsage(basegfx::SystemDependentData_SharedPtr& rData) = 0;
+
+        // flush all buffred data (e.g. cleanup/shutdown)
+        virtual void flushAll() = 0;
+    };
+} // end of namespace basegfx
+
+namespace basegfx
+{
+    class BASEGFX_DLLPUBLIC MinimalSystemDependentDataManager : public SystemDependentDataManager
+    {
+    private:
+        // example of a minimal SystemDependentDataManager. It *needs to hold*
+        // a SystemDependentData_SharedPtr while SystemDependentDataHolder's will
+        // use a SystemDependentData_WeakPtr. When the held SystemDependentData_SharedPtr
+        // is deleted, the corresponding SystemDependentData_WeakPtr will get void.
+        // To make this work, a minimal SystemDependentDataManager *has* to hold at
+        // least that one SystemDependentData_SharedPtr.
+        // That SystemDependentData_SharedPtr may be (e.g. Timer-based or ressource-based)
+        // be freed then. This minimal implementation does never free it, so all stay valid.
+        // The instances may still be removed by endUsage calls, but there is no
+        // caching/buffering mechanism involved here at all. It's an example, but
+        // not used - better use an advanced derivation of SystemDependentDataManager
+        std::set< SystemDependentData_SharedPtr >   maSystemDependentDataReferences;
+
+    public:
+        MinimalSystemDependentDataManager();
+        virtual ~MinimalSystemDependentDataManager() override;
+
+        virtual void startUsage(basegfx::SystemDependentData_SharedPtr& rData) override;
+        virtual void endUsage(basegfx::SystemDependentData_SharedPtr& rData) override;
+        virtual void touchUsage(basegfx::SystemDependentData_SharedPtr& rData) override;
+        virtual void flushAll() override;
+    };
+} // end of namespace basegfx
+
+namespace basegfx
+{
+    class BASEGFX_DLLPUBLIC SystemDependentData
+    {
+    private:
+        // noncopyable
+        SystemDependentData(const SystemDependentData&) = delete;
+        SystemDependentData& operator=(const SystemDependentData&) = delete;
+
+        // reference to a SystemDependentDataManager, probably
+        // a single, globally used one, but not necessarily
+        SystemDependentDataManager&     mrSystemDependentDataManager;
+
+        // number of cycles a SystemDependentDataManager should/might
+        // hold this instance - does not have to be used, but should be
+        sal_uInt32                      mnHoldCycles;
+
+    public:
+        SystemDependentData(
+            SystemDependentDataManager& rSystemDependentDataManager,
+            sal_uInt32 nHoldCycles = 60);
+
+        // CAUTION! It is VERY important to keep this base class
+        // virtual, else typeid(class).hash_code() from derived classes
+        // will NOT work what is ESSENTIAL for the SystemDependentData
+        // mechanism to work properly. So DO NOT REMOVE virtual here, please.
+        virtual ~SystemDependentData();
+
+        // allow access to call startUsage/endUsage/touchUsage
+        // using getSystemDependentDataManager()
+        SystemDependentDataManager& getSystemDependentDataManager() { return mrSystemDependentDataManager; }
+
+        // number of cycles to hold data
+        sal_uInt32 getHoldCycles() const { return mnHoldCycles; }
+    };
+} // end of namespace basegfx
+
+namespace basegfx
+{
+    class BASEGFX_DLLPUBLIC SystemDependentDataHolder
+    {
+    private:
+        // Possibility to hold System-Dependent B2DPolygon-Representations
+        std::map< size_t, SystemDependentData_WeakPtr > maSystemDependentReferences;
+
+        // noncopyable
+        SystemDependentDataHolder(const SystemDependentDataHolder&) = delete;
+        SystemDependentDataHolder& operator=(const SystemDependentDataHolder&) = delete;
+
+    public:
+        SystemDependentDataHolder();
+        virtual ~SystemDependentDataHolder();
+
+        void addOrReplaceSystemDependentData(SystemDependentData_SharedPtr& rData);
+        SystemDependentData_SharedPtr getSystemDependentData(size_t hash_code) const;
+    };
+} // end of namespace basegfx
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/include/vcl/outdev.hxx b/include/vcl/outdev.hxx
index f6f53548bae4..fd5e5565d7b8 100644
--- a/include/vcl/outdev.hxx
+++ b/include/vcl/outdev.hxx
@@ -816,6 +816,7 @@ public:
     // #i101491#
     // Helper who tries to use SalGDI's DrawPolyLine direct and returns it's bool.
     bool                        DrawPolyLineDirect(
+                                    const basegfx::B2DHomMatrix& rObjectTransform,
                                     const basegfx::B2DPolygon& rB2DPolygon,
                                     double fLineWidth = 0.0,
                                     double fTransparency = 0.0,
diff --git a/vcl/headless/svpgdi.cxx b/vcl/headless/svpgdi.cxx
index 11f408aaf773..825d5b1ebf21 100644
--- a/vcl/headless/svpgdi.cxx
+++ b/vcl/headless/svpgdi.cxx
@@ -34,6 +34,8 @@
 #include <basegfx/polygon/b2dpolypolygontools.hxx>
 #include <basegfx/polygon/b2dpolygon.hxx>
 #include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/tools/systemdependentdata.hxx>
 
 #include <cairo.h>
 
@@ -542,8 +544,15 @@ void SvpSalGraphics::drawPolyLine(sal_uInt32 nPoints, const SalPoint* pPtAry)
         aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY));
     aPoly.setClosed(false);
 
-    drawPolyLine(aPoly, 0.0, basegfx::B2DVector(1.0, 1.0), basegfx::B2DLineJoin::Miter,
-                 css::drawing::LineCap_BUTT, 15.0 * F_PI180 /*default*/);
+    drawPolyLine(
+        basegfx::B2DHomMatrix(),
+        aPoly,
+        0.0,
+        basegfx::B2DVector(1.0, 1.0),
+        basegfx::B2DLineJoin::Miter,
+        css::drawing::LineCap_BUTT,
+        basegfx::deg2rad(15.0) /*default*/,
+        false);
 }
 
 void SvpSalGraphics::drawPolygon(sal_uInt32 nPoints, const SalPoint* pPtAry)
@@ -699,26 +708,142 @@ void SvpSalGraphics::drawLine( long nX1, long nY1, long nX2, long nY2 )
     releaseCairoContext(cr, false, extents);
 }
 
+class SystemDependentData_CairoPath : public basegfx::SystemDependentData
+{
+private:
+    cairo_path_t*       mpCairoPath;
+
+public:
+    SystemDependentData_CairoPath(
+        basegfx::SystemDependentDataManager& rSystemDependentDataManager,
+        cairo_path_t* pCairoPath);
+    virtual ~SystemDependentData_CairoPath() override;
+
+    cairo_path_t* getCairoPath() { return mpCairoPath; }
+};
+
+SystemDependentData_CairoPath::SystemDependentData_CairoPath(
+    basegfx::SystemDependentDataManager& rSystemDependentDataManager,
+    cairo_path_t* pCairoPath)
+:   basegfx::SystemDependentData(rSystemDependentDataManager),
+    mpCairoPath(pCairoPath)
+{
+}
+
+SystemDependentData_CairoPath::~SystemDependentData_CairoPath()
+{
+    if(nullptr != mpCairoPath)
+    {
+        cairo_path_destroy(mpCairoPath);
+        mpCairoPath = nullptr;
+    }
+}
+
 bool SvpSalGraphics::drawPolyLine(
+    const basegfx::B2DHomMatrix& rObjectToDevice,
     const basegfx::B2DPolygon& rPolyLine,
     double fTransparency,
     const basegfx::B2DVector& rLineWidths,
     basegfx::B2DLineJoin eLineJoin,
     css::drawing::LineCap eLineCap,
-    double fMiterMinimumAngle)
+    double fMiterMinimumAngle,
+    bool bPixelSnapHairline)
 {
     // short circuit if there is nothing to do
-    const int nPointCount = rPolyLine.count();
-    if (nPointCount <= 0)
+    if(0 == rPolyLine.count())
     {
         return true;
     }
 
-    const bool bNoJoin = (basegfx::B2DLineJoin::NONE == eLineJoin && basegfx::fTools::more(rLineWidths.getX(), 0.0));
-
+    // Wrap call to static verion of ::drawPolyLine by
+    // preparing/getting some local data and parameters
+    // due to usage in vcl/unx/generic/gdi/salgdi.cxx.
+    // This is mainly about extended handling of extents
+    // and the way destruction of CairoContext is handled
+    // due to current XOR stuff
     cairo_t* cr = getCairoContext(false);
+    basegfx::B2DRange aExtents;
     clipRegion(cr);
 
+    bool bRetval(
+        drawPolyLine(
+            cr,
+            &aExtents,
+            m_aLineColor,
+            getAntiAliasB2DDraw(),
+            rObjectToDevice,
+            rPolyLine,
+            fTransparency,
+            rLineWidths,
+            eLineJoin,
+            eLineCap,
+            fMiterMinimumAngle,
+            bPixelSnapHairline));
+
+    releaseCairoContext(cr, false, aExtents);
+
+    return bRetval;
+}
+
+bool SvpSalGraphics::drawPolyLine(
+    cairo_t* cr,
+    basegfx::B2DRange* pExtents,
+    const Color& rLineColor,
+    bool bAntiAliasB2DDraw,
+    const basegfx::B2DHomMatrix& rObjectToDevice,
+    const basegfx::B2DPolygon& rPolyLine,
+    double fTransparency,
+    const basegfx::B2DVector& rLineWidths,
+    basegfx::B2DLineJoin eLineJoin,
+    css::drawing::LineCap eLineCap,
+    double fMiterMinimumAngle,
+    bool bPixelSnapHairline)
+{
+    // short circuit if there is nothing to do
+    if(0 == rPolyLine.count())
+    {
+        return true;
+    }
+
+    // need to check/handle LineWidth when ObjectToDevice transformation is used
+    basegfx::B2DVector aLineWidths(rLineWidths);
+    const bool bObjectToDeviceIsIdentity(rObjectToDevice.isIdentity());
+    const basegfx::B2DVector aDeviceLineWidths(bObjectToDeviceIsIdentity ? rLineWidths : rObjectToDevice * rLineWidths);
+    const bool bCorrectLineWidth(!bObjectToDeviceIsIdentity && aDeviceLineWidths.getX() < 1.0 && aLineWidths.getX() >= 1.0);
+
+    // on-demand inverse of ObjectToDevice transformation
+    basegfx::B2DHomMatrix aObjectToDeviceInv;
+
+    if(bCorrectLineWidth)
+    {
+        if(aObjectToDeviceInv.isIdentity())
+        {
+            aObjectToDeviceInv = rObjectToDevice;
+            aObjectToDeviceInv.invert();
+        }
+
+        // calculate-back logical LineWidth for a hairline
+        aLineWidths = aObjectToDeviceInv * basegfx::B2DVector(1.0, 1.0);
+    }
+
+    if(!bObjectToDeviceIsIdentity)
+    {
+        // set ObjectToDevice transformation
+        cairo_matrix_t aMatrix;
+
+        cairo_matrix_init(
+            &aMatrix,
+            rObjectToDevice.get( 0, 0 ),
+            rObjectToDevice.get( 1, 0 ),
+            rObjectToDevice.get( 0, 1 ),
+            rObjectToDevice.get( 1, 1 ),
+            rObjectToDevice.get( 0, 2 ),
+            rObjectToDevice.get( 1, 2 ));
+        cairo_set_matrix(cr, &aMatrix);
+    }
+
+    const bool bNoJoin((basegfx::B2DLineJoin::NONE == eLineJoin && basegfx::fTools::more(aLineWidths.getX(), 0.0)));
+
     // setup line attributes
     cairo_line_join_t eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
     switch (eLineJoin)
@@ -760,54 +885,131 @@ bool SvpSalGraphics::drawPolyLine(
         }
     }
 
-    cairo_set_source_rgba(cr, SALCOLOR_RED(m_aLineColor)/255.0,
-                              SALCOLOR_GREEN(m_aLineColor)/255.0,
-                              SALCOLOR_BLUE(m_aLineColor)/255.0,
-                              1.0-fTransparency);
+    cairo_set_source_rgba(
+        cr,
+        rLineColor.GetRed()/255.0,
+        rLineColor.GetGreen()/255.0,
+        rLineColor.GetBlue()/255.0,
+        1.0-fTransparency);
 
     cairo_set_line_join(cr, eCairoLineJoin);
     cairo_set_line_cap(cr, eCairoLineCap);
-    cairo_set_line_width(cr, rLineWidths.getX());
+    cairo_set_line_width(cr, aLineWidths.getX());
     cairo_set_miter_limit(cr, fMiterLimit);
 
+    // try to access buffered data
+    std::shared_ptr<SystemDependentData_CairoPath> pSystemDependentData_CairoPath(
+        rPolyLine.getSystemDependentData<SystemDependentData_CairoPath>());
 
-    basegfx::B2DRange extents(0, 0, 0, 0);
+    if(pSystemDependentData_CairoPath)
+    {
+        // check data validity
+        if(nullptr == pSystemDependentData_CairoPath->getCairoPath())
+        {
+            // data invalid, forget
+            pSystemDependentData_CairoPath.reset();
+        }
+    }
 
-    if (!bNoJoin)
+    if(pSystemDependentData_CairoPath)
     {
-        AddPolygonToPath(cr, rPolyLine, rPolyLine.isClosed(), !getAntiAliasB2DDraw(), true);
-        extents = getClippedStrokeDamage(cr);
-        cairo_stroke(cr);
+        // re-use data
+        cairo_append_path(cr, pSystemDependentData_CairoPath->getCairoPath());
     }
     else
     {
-        // emulate rendering::PathJoinType::NONE by painting single edges
-        const sal_uInt32 nEdgeCount(rPolyLine.isClosed() ? nPointCount : nPointCount - 1);
-        basegfx::B2DPolygon aEdge;
-        aEdge.append(rPolyLine.getB2DPoint(0));
-        aEdge.append(basegfx::B2DPoint(0.0, 0.0));
+        // create data
+        basegfx::B2DPolygon aPolyLine(rPolyLine);
 
-        for (sal_uInt32 i = 0; i < nEdgeCount; ++i)
+        if(bPixelSnapHairline)
         {
-            const sal_uInt32 nNextIndex((i + 1) % nPointCount);
-            aEdge.setB2DPoint(1, rPolyLine.getB2DPoint(nNextIndex));
-            aEdge.setNextControlPoint(0, rPolyLine.getNextControlPoint(i % nPointCount));
-            aEdge.setPrevControlPoint(1, rPolyLine.getPrevControlPoint(nNextIndex));
+            // Need to take care of PixelSnapHairline now. The 'short' version
+            // will manipulate the Polygon by using the known tooling at
+            // basegfx. To do this correct, this needs to be done in device
+            // coordinates, so when the transformation is used, transform
+            // to device first, execute, transform back using the inverse.
+            // The important part for buffering the result and not need to
+            // do this at each repaint (for now) is to change a copy of the
+            // Polygon to create the CairoData, but to buffer it at the original
+            // unmodified Polygon.
+            // The 'long' version would be to add this to AddPolygonToPath
+            // equal as done in Win version (see impPixelSnap), should be done
+            // later
+            if(!bObjectToDeviceIsIdentity)
+            {
+                aPolyLine.transform(rObjectToDevice);
+            }
 
-            AddPolygonToPath(cr, aEdge, false, !getAntiAliasB2DDraw(), true);
+            aPolyLine = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aPolyLine);
 
-            extents.expand(getStrokeDamage(cr));
+            if(!bObjectToDeviceIsIdentity)
+            {
+                if(aObjectToDeviceInv.isIdentity())
+                {
+                    aObjectToDeviceInv = rObjectToDevice;
+                    aObjectToDeviceInv.invert();
+                }
 
-            cairo_stroke(cr);
+                aPolyLine.transform(aObjectToDeviceInv);
+            }
+        }
 
-            // prepare next step
-            aEdge.setB2DPoint(0, aEdge.getB2DPoint(1));
+        if (!bNoJoin)
+        {
+            AddPolygonToPath(cr, rPolyLine, rPolyLine.isClosed(), !bAntiAliasB2DDraw, true);
         }
+        else
+        {
+            const sal_uInt32 nPointCount(rPolyLine.count());
+            const sal_uInt32 nEdgeCount(rPolyLine.isClosed() ? nPointCount : nPointCount - 1);
+            basegfx::B2DPolygon aEdge;
 
-        extents.intersect(getClipBox(cr));
+            aEdge.append(rPolyLine.getB2DPoint(0));
+            aEdge.append(basegfx::B2DPoint(0.0, 0.0));
+
+            for (sal_uInt32 i(0); i < nEdgeCount; i++)
+            {
+                const sal_uInt32 nNextIndex((i + 1) % nPointCount);
+                aEdge.setB2DPoint(1, rPolyLine.getB2DPoint(nNextIndex));
+                aEdge.setNextControlPoint(0, rPolyLine.getNextControlPoint(i));
+                aEdge.setPrevControlPoint(1, rPolyLine.getPrevControlPoint(nNextIndex));
+
+                AddPolygonToPath(cr, aEdge, false, !bAntiAliasB2DDraw, true);
+
+                // prepare next step
+                aEdge.setB2DPoint(0, aEdge.getB2DPoint(1));
+            }
+        }
+
+        // copy and add to buffering mechanism
+        rPolyLine.addOrReplaceSystemDependentData<SystemDependentData_CairoPath>(
+            SalGraphics::getSystemDependentDataManager(),
+            cairo_copy_path(cr));
     }
 
-    releaseCairoContext(cr, false, extents);
+    // extract extents
+    if(nullptr != pExtents)
+    {
+        *pExtents = getClippedStrokeDamage(cr);
+    }
+
+    // draw and consume
+    cairo_stroke(cr);
+
+    if(!bObjectToDeviceIsIdentity)
+    {
+        // reset ObjectToDevice transformation if was set (safe, may
+        // be better suited at ::getCairoContext)
+        cairo_identity_matrix(cr);
+    }
+
+    if(nullptr != pExtents && !pExtents->isEmpty() && !bObjectToDeviceIsIdentity)
+    {
+        // transform extents to DeviceCoordiinates if used. These
+        // were calculated with ObjectToDevice transformation actively set,
+        // but use DeviceCoordinates locally
+        pExtents->transform(rObjectToDevice);
+    }
 
     return true;
 }
diff --git a/vcl/inc/headless/svpgdi.hxx b/vcl/inc/headless/svpgdi.hxx
index 060c9f967312..dcf022bcd528 100644
--- a/vcl/inc/headless/svpgdi.hxx
+++ b/vcl/inc/headless/svpgdi.hxx
@@ -90,6 +90,25 @@ public:
     void setSurface(cairo_surface_t* pSurface);
     static cairo_user_data_key_t* getDamageKey();
 
+    static void clipRegion(cairo_t* cr, const vcl::Region& rClipRegion);
+
+    // need this static version of ::drawPolyLine for usage from
+    // vcl/unx/generic/gdi/salgdi.cxx. It gets wrapped by
+    // ::drawPolyLine with some added parameters (see there)
+    static bool drawPolyLine(
+        cairo_t* cr,
+        basegfx::B2DRange* pExtents,
+        const Color& rLineColor,
+        bool bAntiAliasB2DDraw,
+        const basegfx::B2DHomMatrix& rObjectToDevice,
+        const basegfx::B2DPolygon& rPolyLine,
+        double fTransparency,
+        const basegfx::B2DVector& rLineWidths,
+        basegfx::B2DLineJoin eLineJoin,
+        css::drawing::LineCap eLineCap,
+        double fMiterMinimumAngle,
+        bool bPixelSnapHairline);
+
 private:
     void invert(const basegfx::B2DPolygon &rPoly, SalInvert nFlags);
     void copySource(const SalTwoRect& rTR, cairo_surface_t* source);
@@ -175,12 +194,15 @@ public:
     virtual void            drawLine( long nX1, long nY1, long nX2, long nY2 ) override;
     virtual void            drawRect( long nX, long nY, long nWidth, long nHeight ) override;
     virtual bool            drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) override;
-    virtual bool            drawPolyLine( const basegfx::B2DPolygon&,
-                                          double fTransparency,
-                                          const basegfx::B2DVector& rLineWidths,
-                                          basegfx::B2DLineJoin,
-                                          css::drawing::LineCap,
-                                          double fMiterMinimumAngle) override;
+    virtual bool            drawPolyLine(
+                                const basegfx::B2DHomMatrix& rObjectToDevice,
+                                const basegfx::B2DPolygon&,
+                                double fTransparency,
+                                const basegfx::B2DVector& rLineWidths,
+                                basegfx::B2DLineJoin,
+                                css::drawing::LineCap,
+                                double fMiterMinimumAngle,
+                                bool bPixelSnapHairline) override;
     virtual void            drawPolyLine( sal_uInt32 nPoints, const SalPoint* pPtAry ) override;
     virtual void            drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry ) override;
     virtual void            drawPolyPolygon( sal_uInt32 nPoly,
diff --git a/vcl/inc/openglgdiimpl.hxx b/vcl/inc/openglgdiimpl.hxx
index 265d21603dd5..3fdc86740478 100644
--- a/vcl/inc/openglgdiimpl.hxx
+++ b/vcl/inc/openglgdiimpl.hxx
@@ -250,12 +250,14 @@ public:
     virtual bool drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) override;
 
     virtual bool drawPolyLine(
+                const basegfx::B2DHomMatrix& rObjectToDevice,
                 const basegfx::B2DPolygon&,
                 double fTransparency,
                 const basegfx::B2DVector& rLineWidths,
                 basegfx::B2DLineJoin,
                 css::drawing::LineCap,
-                double fMiterMinimumAngle) override;
+                double fMiterMinimumAngle,
+                bool bPixelSnapHairline) override;
 
     virtual bool drawPolyLineBezier(
                 sal_uInt32 nPoints,
diff --git a/vcl/inc/quartz/salgdi.h b/vcl/inc/quartz/salgdi.h
index 09dc6193148b..997115c193c2 100644
--- a/vcl/inc/quartz/salgdi.h
+++ b/vcl/inc/quartz/salgdi.h
@@ -242,12 +242,14 @@ public:
     virtual bool            drawPolygonBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const sal_uInt8* pFlgAry ) override;
     virtual bool            drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt32* pPoints, const SalPoint* const* pPtAry, const sal_uInt8* const* pFlgAry ) override;
     virtual bool            drawPolyLine(
+                                const basegfx::B2DHomMatrix& rObjectToDevice,
                                 const basegfx::B2DPolygon&,
                                 double fTransparency,
                                 const basegfx::B2DVector& rLineWidths,
                                 basegfx::B2DLineJoin,
                                 css::drawing::LineCap eLineCap,
-                                double fMiterMinimumAngle) override;
+                                double fMiterMinimumAngle,
+                                bool bPixelSnapHairline) override;
     virtual bool            drawGradient( const tools::PolyPolygon&, const Gradient& ) override { return false; };
 
     // CopyArea --> No RasterOp, but ClipRegion
diff --git a/vcl/inc/salgdi.hxx b/vcl/inc/salgdi.hxx
index 56fe42dbfe38..c421b0b86b27 100644
--- a/vcl/inc/salgdi.hxx
+++ b/vcl/inc/salgdi.hxx
@@ -54,6 +54,7 @@ namespace basegfx {
     class B2DVector;
     class B2DPolygon;
     class B2DPolyPolygon;
+    class SystemDependentDataManager;
 }
 
 typedef sal_Unicode sal_Ucs; // TODO: use sal_UCS4 instead of sal_Unicode
@@ -79,6 +80,10 @@ public:
 
     virtual SalGraphicsImpl*    GetImpl() const = 0;
 
+    // access to single global managing instance of a basegfx::SystemDependentDataManager,
+    // used to handle graphic data in system-dependent form
+    static basegfx::SystemDependentDataManager& getSystemDependentDataManager();
+
     /// Check that our mpImpl is OpenGL and return the context, otherwise NULL.
     rtl::Reference<OpenGLContext> GetOpenGLContext() const;
 
@@ -263,12 +268,14 @@ public:
                                     const OutputDevice *i_pOutDev);
 
     bool                        DrawPolyLine(
+                                    const basegfx::B2DHomMatrix& rObjectToDevice,
                                     const basegfx::B2DPolygon& i_rPolygon,
                                     double i_fTransparency,
                                     const basegfx::B2DVector& i_rLineWidth,
                                     basegfx::B2DLineJoin i_eLineJoin,
                                     css::drawing::LineCap i_eLineCap,
                                     double i_fMiterMinimumAngle,
+                                    bool bPixelSnapHairline,
                                     const OutputDevice* i_pOutDev);
 
     bool                        DrawPolyLineBezier(
@@ -462,12 +469,14 @@ protected:
     virtual bool                drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) = 0;
 
     virtual bool                drawPolyLine(
+                                    const basegfx::B2DHomMatrix& rObjectToDevice,
                                     const basegfx::B2DPolygon&,
                                     double fTransparency,
                                     const basegfx::B2DVector& rLineWidths,
                                     basegfx::B2DLineJoin,
                                     css::drawing::LineCap,
-                                    double fMiterMinimumAngle) = 0;
+                                    double fMiterMinimumAngle,
+                                    bool bPixelSnapHairline) = 0;
 
     virtual bool                drawPolyLineBezier(
                                     sal_uInt32 nPoints,
diff --git a/vcl/inc/salgdiimpl.hxx b/vcl/inc/salgdiimpl.hxx
index c2c008e68462..1bdc245b8799 100644
--- a/vcl/inc/salgdiimpl.hxx
+++ b/vcl/inc/salgdiimpl.hxx
@@ -101,12 +101,14 @@ public:
     virtual bool drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) = 0;
 
     virtual bool drawPolyLine(
+                const basegfx::B2DHomMatrix& rObjectToDevice,
                 const basegfx::B2DPolygon&,
                 double fTransparency,
                 const basegfx::B2DVector& rLineWidths,
                 basegfx::B2DLineJoin,
                 css::drawing::LineCap,
-                double fMiterMinimumAngle) = 0;
+                double fMiterMinimumAngle,
+                bool bPixelSnapHairline) = 0;
 
     virtual bool drawPolyLineBezier(
                 sal_uInt32 nPoints,
diff --git a/vcl/inc/unx/genpspgraphics.h b/vcl/inc/unx/genpspgraphics.h
index b7658b93f3dd..94a99bbf9386 100644
--- a/vcl/inc/unx/genpspgraphics.h
+++ b/vcl/inc/unx/genpspgraphics.h
@@ -144,12 +144,15 @@ public:
                                              PCONSTSALPOINT* pPtAry ) override;
     virtual bool            drawPolyPolygon( const basegfx::B2DPolyPolygon&,
                                              double fTransparency ) override;
-    virtual bool            drawPolyLine( const basegfx::B2DPolygon&,
-                                          double fTransparency,
-                                          const basegfx::B2DVector& rLineWidths,
-                                          basegfx::B2DLineJoin,
-                                          css::drawing::LineCap,
-                                          double fMiterMinimumAngle) override;
+    virtual bool            drawPolyLine(
+                                const basegfx::B2DHomMatrix& rObjectToDevice,
+                                const basegfx::B2DPolygon&,
+                                double fTransparency,
+                                const basegfx::B2DVector& rLineWidths,
+                                basegfx::B2DLineJoin,
+                                css::drawing::LineCap,
+                                double fMiterMinimumAngle,
+                                bool bPixelSnapHairline) override;
     virtual bool            drawPolyLineBezier( sal_uInt32 nPoints,
                                                 const SalPoint* pPtAry,
                                                 const sal_uInt8* pFlgAry ) override;
diff --git a/vcl/inc/unx/salgdi.h b/vcl/inc/unx/salgdi.h
index 40aa91b6ac70..120071ca5e3b 100644
--- a/vcl/inc/unx/salgdi.h
+++ b/vcl/inc/unx/salgdi.h
@@ -172,12 +172,14 @@ public:
     virtual bool                    drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) override;
 
     virtual bool                    drawPolyLine(
+                                        const basegfx::B2DHomMatrix& rObjectToDevice,
                                         const basegfx::B2DPolygon&,
                                         double fTransparency,
                                         const basegfx::B2DVector& rLineWidth,
                                         basegfx::B2DLineJoin,
                                         css::drawing::LineCap,
-                                        double fMiterMinimumAngle) override;
+                                        double fMiterMinimumAngle,
+                                        bool bPixelSnapHairline) override;
 
     virtual bool                    drawGradient( const tools::PolyPolygon&, const Gradient& ) override;
 
diff --git a/vcl/inc/win/salbmp.h b/vcl/inc/win/salbmp.h
index 0a698e5eae46..55108f2ef679 100644
--- a/vcl/inc/win/salbmp.h
+++ b/vcl/inc/win/salbmp.h
@@ -23,6 +23,7 @@
 #include <tools/gen.hxx>
 #include <win/wincomp.hxx>
 #include <salbmp.hxx>
+#include <basegfx/tools/systemdependentdata.hxx>
 #include <memory>
 
 
@@ -33,26 +34,13 @@ class   SalGraphics;
 namespace Gdiplus { class Bitmap; }
 typedef std::shared_ptr< Gdiplus::Bitmap > GdiPlusBmpPtr;
 
-class WinSalBitmap : public SalBitmap
+class WinSalBitmap : public SalBitmap, public basegfx::SystemDependentDataHolder
 {
 private:
-    friend class GdiPlusBuffer; // allow buffer to remove maGdiPlusBitmap and mpAssociatedAlpha eventually
-
     Size                maSize;
     HGLOBAL             mhDIB;
     HBITMAP             mhDDB;
 
-    // the buffered evtl. used Gdiplus::Bitmap instance. It is managed by
-    // GdiPlusBuffer. To make this safe, it is only handed out as shared
-    // pointer; the GdiPlusBuffer may delete the local instance.
-
-    // mpAssociatedAlpha holds the last WinSalBitmap used to construct an
-    // evtl. buffered GdiPlusBmp. This is needed since the GdiPlusBmp is a single
-    // instance and remembered only on the content-WinSalBitmap, not on the
-    // alpha-WinSalBitmap.
-    GdiPlusBmpPtr       maGdiPlusBitmap;
-    const WinSalBitmap* mpAssociatedAlpha;
-
     sal_uInt16          mnBitCount;
 
     Gdiplus::Bitmap*    ImplCreateGdiPlusBitmap(const WinSalBitmap& rAlphaSource);
@@ -98,6 +86,22 @@ public:
 
     virtual bool                Scale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag ) override;
     virtual bool                Replace( const Color& rSearchColor, const Color& rReplaceColor, sal_uLong nTol ) override;
+
+    // exclusive management op's for SystemDependentData at WinSalBitmap
+    template<class T>
+    std::shared_ptr<T> getSystemDependentData() const
+    {
+        return std::static_pointer_cast<T>(basegfx::SystemDependentDataHolder::getSystemDependentData(typeid(T).hash_code()));
+    }
+
+    template<class T, class... Args>
+    std::shared_ptr<T> addOrReplaceSystemDependentData(basegfx::SystemDependentDataManager& manager, Args&&... args) const
+    {
+        std::shared_ptr<T> r = std::make_shared<T>(manager, std::forward<Args>(args)...);
+        basegfx::SystemDependentData_SharedPtr r2(r);
+        const_cast< WinSalBitmap* >(this)->basegfx::SystemDependentDataHolder::addOrReplaceSystemDependentData(r2);
+        return r;
+    }
 };
 
 #endif // INCLUDED_VCL_INC_WIN_SALBMP_H
diff --git a/vcl/inc/win/salgdi.h b/vcl/inc/win/salgdi.h
index d2a81624cb4a..9091f0700817 100644
--- a/vcl/inc/win/salgdi.h
+++ b/vcl/inc/win/salgdi.h
@@ -282,12 +282,14 @@ protected:
     virtual void        drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPoints, PCONSTSALPOINT* pPtAry ) override;
     virtual bool        drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) override;
     virtual bool        drawPolyLine(
+        const basegfx::B2DHomMatrix& rObjectToDevice,
         const basegfx::B2DPolygon&,
         double fTransparency,
         const basegfx::B2DVector& rLineWidth,
         basegfx::B2DLineJoin,
         css::drawing::LineCap,
-        double fMiterMinimumAngle) override;
+        double fMiterMinimumAngle,
+        bool bPixelSnapHairline) override;
     virtual bool        drawPolyLineBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const sal_uInt8* pFlgAry ) override;
     virtual bool        drawPolygonBezier( sal_uInt32 nPoints, const SalPoint* pPtAry, const sal_uInt8* pFlgAry ) override;
     virtual bool        drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt32* pPoints, const SalPoint* const* pPtAry, const BYTE* const* pFlgAry ) override;
diff --git a/vcl/opengl/gdiimpl.cxx b/vcl/opengl/gdiimpl.cxx
index ab719f3c2bd5..e4dbac03dfb4 100644
--- a/vcl/opengl/gdiimpl.cxx
+++ b/vcl/opengl/gdiimpl.cxx
@@ -1842,8 +1842,15 @@ void OpenGLSalGraphicsImpl::drawPolyLine( sal_uInt32 nPoints, const SalPoint* pP
         aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY));
     aPoly.setClosed(false);
 
-    drawPolyLine(aPoly, 0.0, basegfx::B2DVector(1.0, 1.0), basegfx::B2DLineJoin::Miter,
-                 css::drawing::LineCap_BUTT, 15.0 * F_PI180 /*default*/);
+    drawPolyLine(
+        basegfx::B2DHomMatrix(),
+        aPoly,
+        0.0,
+        basegfx::B2DVector(1.0, 1.0),
+        basegfx::B2DLineJoin::Miter,
+        css::drawing::LineCap_BUTT,
+        basegfx::deg2rad(15.0) /*default*/,
+        false);
 }
 
 void OpenGLSalGraphicsImpl::drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry )
@@ -1916,12 +1923,14 @@ bool OpenGLSalGraphicsImpl::drawPolyPolygon( const basegfx::B2DPolyPolygon& rPol
 }
 
 bool OpenGLSalGraphicsImpl::drawPolyLine(
-            const basegfx::B2DPolygon& rPolygon,
-            double fTransparency,
-            const basegfx::B2DVector& rLineWidth,
-            basegfx::B2DLineJoin eLineJoin,
-            css::drawing::LineCap eLineCap,
-            double fMiterMinimumAngle)
+    const basegfx::B2DHomMatrix& rObjectToDevice,
+    const basegfx::B2DPolygon& rPolygon,
+    double fTransparency,
+    const basegfx::B2DVector& rLineWidth,
+    basegfx::B2DLineJoin eLineJoin,
+    css::drawing::LineCap eLineCap,
+    double fMiterMinimumAngle,
+    bool bPixelSnapHairline)
 {
     VCL_GL_INFO( "::drawPolyLine trans " << fTransparency );
     if( mnLineColor == SALCOLOR_NONE )
@@ -1929,19 +1938,23 @@ bool OpenGLSalGraphicsImpl::drawPolyLine(
     if (rPolygon.count() <= 1)
         return true;
 
-    const bool bIsHairline = (rLineWidth.getX() == rLineWidth.getY()) && (rLineWidth.getX() <= 1.2);
-    const float fLineWidth = bIsHairline ? 1.0f : rLineWidth.getX();
+    // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
+    const basegfx::B2DVector aLineWidths(rObjectToDevice * rLineWidth);
+    basegfx::B2DPolygon aPolyLine(rPolygon);
+    aPolyLine.transform(rObjectToDevice);
+    if(bPixelSnapHairline) { aPolyLine = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aPolyLine); }
+
+    const bool bIsHairline = (aLineWidths.getX() == aLineWidths.getY()) && (aLineWidths.getX() <= 1.2);
+    const float fLineWidth = bIsHairline ? 1.0f : aLineWidths.getX();
 
     PreDraw(XOROption::IMPLEMENT_XOR);
 
     if (UseLine(mnLineColor, 0.0f, fLineWidth, mrParent.getAntiAliasB2DDraw()))
     {
-        basegfx::B2DPolygon aPolygon(rPolygon);
-
-        if (aPolygon.areControlPointsUsed())
-            aPolygon = aPolygon.getDefaultAdaptiveSubdivision();
+        if (aPolyLine.areControlPointsUsed())
+            aPolyLine = aPolyLine.getDefaultAdaptiveSubdivision();
 
-        DrawPolyLine(aPolygon, fLineWidth, eLineJoin, eLineCap, fMiterMinimumAngle);
+        DrawPolyLine(aPolyLine, fLineWidth, eLineJoin, eLineCap, fMiterMinimumAngle);
     }
     PostDraw();
 
diff --git a/vcl/quartz/salgdicommon.cxx b/vcl/quartz/salgdicommon.cxx
index 93c22b3c98c8..8b9d2752bf2d 100644
--- a/vcl/quartz/salgdicommon.cxx
+++ b/vcl/quartz/salgdicommon.cxx
@@ -23,6 +23,7 @@
 #include <cstring>
 
 #include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
 #include <osl/endian.h>
 #include <osl/file.hxx>
 #include <sal/types.h>
@@ -957,18 +958,20 @@ void AquaSalGraphics::drawPixel( long nX, long nY, SalColor nSalColor )
     ImplDrawPixel( nX, nY, aPixelColor );
 }
 
-bool AquaSalGraphics::drawPolyLine( const basegfx::B2DPolygon& rPolyLine,
-                                    double fTransparency,
-                                    const basegfx::B2DVector& rLineWidths,
-                                    basegfx::B2DLineJoin eLineJoin,
-                                    css::drawing::LineCap eLineCap,
-                                    double fMiterMinimumAngle)
+bool AquaSalGraphics::drawPolyLine(
+    const basegfx::B2DHomMatrix& rObjectToDevice,
+    const basegfx::B2DPolygon& rPolyLine,
+    double fTransparency,
+    const basegfx::B2DVector& rLineWidths,
+    basegfx::B2DLineJoin eLineJoin,
+    css::drawing::LineCap eLineCap,
+    double fMiterMinimumAngle,
+    bool bPixelSnapHairline)
 {
     DBG_DRAW_OPERATION("drawPolyLine", true);
 
     // short circuit if there is nothing to do
-    const int nPointCount = rPolyLine.count();
-    if( nPointCount <= 0 )
+    if(0 == rPolyLine.count())
     {
         DBG_DRAW_OPERATION_EXIT_EARLY("drawPolyLine");
         return true;
@@ -982,16 +985,23 @@ bool AquaSalGraphics::drawPolyLine( const basegfx::B2DPolygon& rPolyLine,
     }
 #endif
 
+    // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
+    const basegfx::B2DVector aLineWidths(rObjectToDevice * rLineWidths);
+
     // #i101491# Aqua does not support B2DLineJoin::NONE; return false to use
     // the fallback (own geometry preparation)
     // #i104886# linejoin-mode and thus the above only applies to "fat" lines
-    if( (basegfx::B2DLineJoin::NONE == eLineJoin) &&
-        (rLineWidths.getX() > 1.3) )
+    if( (basegfx::B2DLineJoin::NONE == eLineJoin) && (aLineWidths.getX() > 1.3) )
     {
         DBG_DRAW_OPERATION_EXIT_EARLY("drawPolyLine");
         return false;
     }
 
+    // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
+    basegfx::B2DPolygon aPolyLine(rPolyLine);
+    aPolyLine.transform(rObjectToDevice);
+    if(bPixelSnapHairline) { aPolyLine = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aPolyLine); }
+
     // setup line attributes
     CGLineJoin aCGLineJoin = kCGLineJoinMiter;
     switch( eLineJoin )
@@ -1028,7 +1038,12 @@ bool AquaSalGraphics::drawPolyLine( const basegfx::B2DPolygon& rPolyLine,
     // setup poly-polygon path
     CGMutablePathRef xPath = CGPathCreateMutable();
     SAL_INFO( "vcl.cg", "CGPathCreateMutable() = " << xPath );
-    AddPolygonToPath( xPath, rPolyLine, rPolyLine.isClosed(), !getAntiAliasB2DDraw(), true );
+    AddPolygonToPath(
+        xPath,
+        aPolyLine,
+        aPolyLine.isClosed(),
+        !getAntiAliasB2DDraw(),
+        true);
 
     const CGRect aRefreshRect = CGPathGetBoundingBox( xPath );
     SAL_INFO( "vcl.cg", "CGPathGetBoundingBox(" << xPath << ") = " << aRefreshRect );
@@ -1048,7 +1063,7 @@ bool AquaSalGraphics::drawPolyLine( const basegfx::B2DPolygon& rPolyLine,
         CGContextSetAlpha( mrContext, 1.0 - fTransparency );
         CGContextSetLineJoin( mrContext, aCGLineJoin );
         CGContextSetLineCap( mrContext, aCGLineCap );
-        CGContextSetLineWidth( mrContext, rLineWidths.getX() );
+        CGContextSetLineWidth( mrContext, aLineWidths.getX() );
         CGContextSetMiterLimit(mrContext, fCGMiterLimit);
         SAL_INFO( "vcl.cg", "CGContextDrawPath(" << mrContext << ",kCGPathStroke)" );
         CGContextDrawPath( mrContext, kCGPathStroke );
diff --git a/vcl/source/app/svmain.cxx b/vcl/source/app/svmain.cxx
index 042ec278075a..b05b7e324394 100644
--- a/vcl/source/app/svmain.cxx
+++ b/vcl/source/app/svmain.cxx
@@ -93,6 +93,8 @@
 #include <opengl/zone.hxx>
 #include <opengl/watchdog.hxx>
 
+#include <basegfx/tools/systemdependentdata.hxx>
+
 #if OSL_DEBUG_LEVEL > 0
 #include <typeinfo>
 #include <rtl/strbuf.hxx>
@@ -370,6 +372,10 @@ VCLUnoWrapperDeleter::disposing(lang::EventObject const& /* rSource */)
 void DeInitVCL()
 {
     ImplSVData* pSVData = ImplGetSVData();
+
+    // cleanup SystemDependentData
+    SalGraphics::getSystemDependentDataManager().flushAll();
+
     // lp#1560328: clear cache before disposing rest of VCL
     if(pSVData->mpBlendFrameCache)
         pSVData->mpBlendFrameCache->m_aLastResult.Clear();
diff --git a/vcl/source/gdi/salgdilayout.cxx b/vcl/source/gdi/salgdilayout.cxx
index 7c13637fcbe3..495918360d9f 100644
--- a/vcl/source/gdi/salgdilayout.cxx
+++ b/vcl/source/gdi/salgdilayout.cxx
@@ -24,6 +24,10 @@
 #include "salgdi.hxx"
 #include "salframe.hxx"
 #include <basegfx/numeric/ftools.hxx> //for F_PI180
+#include <basegfx/tools/systemdependentdata.hxx>
+#include <cppuhelper/basemutex.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <o3tl/make_unique.hxx>
 
 // The only common SalFrame method
 
@@ -65,6 +69,131 @@ rtl::Reference<OpenGLContext> SalGraphics::GetOpenGLContext() const
 }
 #endif
 
+basegfx::SystemDependentDataManager& SalGraphics::getSystemDependentDataManager()
+{
+    typedef ::std::map< basegfx::SystemDependentData_SharedPtr, sal_uInt32 > EntryMap;
+
+    class SystemDependentDataBuffer : public basegfx::SystemDependentDataManager, protected cppu::BaseMutex, public Timer
+    {
+    private:
+        EntryMap        maEntries;
+
+    public:
+        SystemDependentDataBuffer( const sal_Char *pDebugName )
+        :   basegfx::SystemDependentDataManager(),
+            Timer( pDebugName ),
+            maEntries()
+        {
+            SetTimeout(1000);
+            Stop();
+        }
+
+        virtual ~SystemDependentDataBuffer() override
+        {
+            Stop();
+        }
+
+        void startUsage(basegfx::SystemDependentData_SharedPtr& rData) override
+        {
+            ::osl::MutexGuard aGuard(m_aMutex);
+            EntryMap::iterator aFound(maEntries.find(rData));
+
+            if(aFound == maEntries.end())
+            {
+                if(maEntries.empty())
+                {
+                    Start();
+                }
+
+                maEntries[rData] = rData->getHoldCycles();
+            }
+        }
+
+        void endUsage(basegfx::SystemDependentData_SharedPtr& rData) override
+        {
+            ::osl::MutexGuard aGuard(m_aMutex);
+            EntryMap::iterator aFound(maEntries.find(rData));
+
+            if(aFound != maEntries.end())
+            {
+                maEntries.erase(aFound);
+
+                if(maEntries.empty())
+                {
+                    Stop();
+                }
+            }
+        }
+
+        void touchUsage(basegfx::SystemDependentData_SharedPtr& rData) override
+        {
+            ::osl::MutexGuard aGuard(m_aMutex);
+            EntryMap::iterator aFound(maEntries.find(rData));
+
+            if(aFound != maEntries.end())
+            {
+                aFound->second = rData->getHoldCycles();
+            }
+        }
+
+        void flushAll() override
+        {
+            ::osl::MutexGuard aGuard(m_aMutex);
+            EntryMap::iterator aIter(maEntries.begin());
+
+            Stop();
+
+            while(aIter != maEntries.end())
+            {
+                EntryMap::iterator aDelete(aIter);
+                ++aIter;
+                maEntries.erase(aDelete);
+            }
+        }
+
+        // from parent Timer
+        virtual void Invoke() override
+        {
+            ::osl::MutexGuard aGuard(m_aMutex);
+            EntryMap::iterator aIter(maEntries.begin());
+
+            while(aIter != maEntries.end())
+            {
+                if(aIter->second)
+                {
+                    aIter->second--;
+                    ++aIter;
+                }
+                else
+                {
+                    EntryMap::iterator aDelete(aIter);
+                    ++aIter;
+                    maEntries.erase(aDelete);
+
+                    if(maEntries.empty())
+                    {
+                        Stop();
+                    }
+                }
+            }
+
+            if(!maEntries.empty())
+            {
+                Start();
+            }
+        }
+    };
+
+    static std::unique_ptr<SystemDependentDataBuffer> aSystemDependentDataBuffer;
+
+    if(!aSystemDependentDataBuffer)
+    {
+        aSystemDependentDataBuffer = o3tl::make_unique<SystemDependentDataBuffer>(nullptr);
+    }
+
+    return *aSystemDependentDataBuffer.get();
+}
+
 bool SalGraphics::drawTransformedBitmap(
     const basegfx::B2DPoint& /* rNull */,
     const basegfx::B2DPoint& /* rX */,
@@ -477,22 +606,56 @@ bool SalGraphics::DrawPolyPolygonBezier( sal_uInt32 i_nPoly, const sal_uInt32* i
     return bRet;
 }
 
-bool SalGraphics::DrawPolyLine( const basegfx::B2DPolygon& i_rPolygon,
-                                double i_fTransparency,
-                                const basegfx::B2DVector& i_rLineWidth,
-                                basegfx::B2DLineJoin i_eLineJoin,
-                                css::drawing::LineCap i_eLineCap,
-                                double i_fMiterMinimumAngle,
-                                const OutputDevice* i_pOutDev )
+bool SalGraphics::DrawPolyLine(
+    const basegfx::B2DHomMatrix& rObjectToDevice,
+    const basegfx::B2DPolygon& i_rPolygon,
+    double i_fTransparency,
+    const basegfx::B2DVector& i_rLineWidth,
+    basegfx::B2DLineJoin i_eLineJoin,
+    css::drawing::LineCap i_eLineCap,
+    double i_fMiterMinimumAngle,
+    bool bPixelSnapHairline,
+    const OutputDevice* i_pOutDev)
 {
     bool bRet = false;
+
     if( (m_nLayout & SalLayoutFlags::BiDiRtl) || (i_pOutDev && i_pOutDev->IsRTLEnabled()) )
     {
-        basegfx::B2DPolygon aMirror( mirror( i_rPolygon, i_pOutDev ) );
-        bRet = drawPolyLine( aMirror, i_fTransparency, i_rLineWidth, i_eLineJoin, i_eLineCap, i_fMiterMinimumAngle );
+        // if mirrored, we need to apply transformation since it is
+        // not clear what 'mirror' does - might be changed when this
+        // happens often
+        basegfx::B2DPolygon aMirror(i_rPolygon);
+
+        aMirror.transform(rObjectToDevice);
+        aMirror = mirror(aMirror, i_pOutDev);
+        // basegfx::B2DPolygon aMirror( mirror( i_rPolygon, i_pOutDev ) );
+
+        // also need to transform LineWidth
+        const basegfx::B2DVector aLineWidth(rObjectToDevice * i_rLineWidth);
+
+        bRet = drawPolyLine(
+            basegfx::B2DHomMatrix(), // now empty transformation, already used
+            aMirror,
+            i_fTransparency,
+            aLineWidth,
+            i_eLineJoin,
+            i_eLineCap,
+            i_fMiterMinimumAngle,
+            bPixelSnapHairline);
     }
     else
-        bRet = drawPolyLine( i_rPolygon, i_fTransparency, i_rLineWidth, i_eLineJoin, i_eLineCap, i_fMiterMinimumAngle );
+    {
+        bRet = drawPolyLine(
+            rObjectToDevice,
+            i_rPolygon,
+            i_fTransparency,
+            i_rLineWidth,
+            i_eLineJoin,
+            i_eLineCap,
+            i_fMiterMinimumAngle,
+            bPixelSnapHairline);
+    }
+
     return bRet;
 }
 
diff --git a/vcl/source/outdev/line.cxx b/vcl/source/outdev/line.cxx
index a04dd802ee96..49ba56f28e61 100644
--- a/vcl/source/outdev/line.cxx
+++ b/vcl/source/outdev/line.cxx
@@ -129,18 +129,21 @@ void OutputDevice::DrawLine( const Point& rStartPt, const Point& rEndPt )
         aB2DPolyLine.append(basegfx::B2DPoint(rEndPt.X(), rEndPt.Y()));
         aB2DPolyLine.transform( aTransform );
 
-        if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline)
-        {
-            aB2DPolyLine = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyLine);
-        }
+        const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+        // if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline)
+        // {
+        //     aB2DPolyLine = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyLine);
+        // }
 
         if( mpGraphics->DrawPolyLine(
+            basegfx::B2DHomMatrix(),
             aB2DPolyLine,
             0.0,
             aB2DLineWidth,
             basegfx::B2DLineJoin::NONE,
             css::drawing::LineCap_BUTT,
-            15.0 * F_PI180, // not used with B2DLineJoin::NONE, but the correct default
+            basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
+            bPixelSnapHairline,
             this))
         {
             return;
@@ -239,24 +242,30 @@ void OutputDevice::drawLine( basegfx::B2DPolyPolygon aLinePolyPolygon, const Lin
         for(sal_uInt32 a(0); a < aLinePolyPolygon.count(); a++)
         {
             const basegfx::B2DPolygon aCandidate(aLinePolyPolygon.getB2DPolygon(a));
+            const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
             bool bDone(false);
 
             if(bTryAA)
             {
                 bDone = mpGraphics->DrawPolyLine(
+                    basegfx::B2DHomMatrix(),
                     aCandidate,
                     0.0,
                     basegfx::B2DVector(1.0,1.0),
                     basegfx::B2DLineJoin::NONE,
                     css::drawing::LineCap_BUTT,
-                    15.0 * F_PI180, // not used with B2DLineJoin::NONE, but the correct default
+                    basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
+                    bPixelSnapHairline,
                     this);
             }
 
             if(!bDone)
             {
-                const tools::Polygon aPolygon(aCandidate);
-                mpGraphics->DrawPolyLine(aPolygon.GetSize(), reinterpret_cast<const SalPoint*>(aPolygon.GetConstPointAry()), this);
+                tools::Polygon aPolygon(aCandidate);
+                mpGraphics->DrawPolyLine(
+                    aPolygon.GetSize(),
+                    reinterpret_cast<const SalPoint*>(aPolygon.GetConstPointAry()),
+                    this);
             }
         }
     }
diff --git a/vcl/source/outdev/polygon.cxx b/vcl/source/outdev/polygon.cxx
index 612e93716bb1..3b9333131133 100644
--- a/vcl/source/outdev/polygon.cxx
+++ b/vcl/source/outdev/polygon.cxx
@@ -84,21 +84,25 @@ void OutputDevice::DrawPolyPolygon( const tools::PolyPolygon& rPolyPoly )
         if(bSuccess && IsLineColor())
         {
             const basegfx::B2DVector aB2DLineWidth( 1.0, 1.0 );
+            const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
 
-            if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline)
-            {
-                aB2DPolyPolygon = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyPolygon);
-            }
+            // if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline)
+            // {
+            //     aB2DPolyPolygon = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyPolygon);
+            // }
 
             for(sal_uInt32 a(0); bSuccess && a < aB2DPolyPolygon.count(); a++)
             {
-                bSuccess = mpGraphics->DrawPolyLine( aB2DPolyPolygon.getB2DPolygon(a),
-                                                     0.0,
-                                                     aB2DLineWidth,
-                                                     basegfx::B2DLineJoin::NONE,
-                                                     css::drawing::LineCap_BUTT,
-                                                     15.0 * F_PI180, // not used with B2DLineJoin::NONE, but the correct default
-                                                     this);
+                bSuccess = mpGraphics->DrawPolyLine(
+                    basegfx::B2DHomMatrix(),
+                    aB2DPolyPolygon.getB2DPolygon(a),
+                    0.0,
+                    aB2DLineWidth,
+                    basegfx::B2DLineJoin::NONE,
+                    css::drawing::LineCap_BUTT,
+                    basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
+                    bPixelSnapHairline,
+                    this);
             }
         }
 
@@ -197,19 +201,23 @@ void OutputDevice::DrawPolygon( const tools::Polygon& rPoly )
         if(bSuccess && IsLineColor())
         {
             const basegfx::B2DVector aB2DLineWidth( 1.0, 1.0 );
-
-            if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline)
-            {
-                aB2DPolygon = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolygon);
-            }
-
-            bSuccess = mpGraphics->DrawPolyLine( aB2DPolygon,
-                                                 0.0,
-                                                 aB2DLineWidth,
-                                                 basegfx::B2DLineJoin::NONE,
-                                                 css::drawing::LineCap_BUTT,
-                                                 15.0 * F_PI180, // not used with B2DLineJoin::NONE, but the correct default
-                                                 this);
+            const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+            // if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline)
+            // {
+            //     aB2DPolygon = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolygon);
+            // }
+
+            bSuccess = mpGraphics->DrawPolyLine(
+                basegfx::B2DHomMatrix(),
+                aB2DPolygon,
+                0.0,
+                aB2DLineWidth,
+                basegfx::B2DLineJoin::NONE,
+                css::drawing::LineCap_BUTT,
+                basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
+                bPixelSnapHairline,
+                this);
         }
 
         if(bSuccess)
@@ -300,21 +308,25 @@ void OutputDevice::ImplDrawPolyPolygonWithB2DPolyPolygon(const basegfx::B2DPolyP
         if(bSuccess && IsLineColor())
         {
             const basegfx::B2DVector aB2DLineWidth( 1.0, 1.0 );
+            const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
 
-            if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline)
-            {
-                aB2DPolyPolygon = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyPolygon);
-            }
+            // if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline)
+            // {
+            //     aB2DPolyPolygon = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyPolygon);
+            // }
 
             for(sal_uInt32 a(0);bSuccess && a < aB2DPolyPolygon.count(); a++)
             {
-                bSuccess = mpGraphics->DrawPolyLine( aB2DPolyPolygon.getB2DPolygon(a),
-                                                     0.0,
-                                                     aB2DLineWidth,
-                                                     basegfx::B2DLineJoin::NONE,
-                                                     css::drawing::LineCap_BUTT,
-                                                     15.0 * F_PI180, // not used with B2DLineJoin::NONE, but the correct default
-                                                     this);
+                bSuccess = mpGraphics->DrawPolyLine(
+                    basegfx::B2DHomMatrix(),
+                    aB2DPolyPolygon.getB2DPolygon(a),
+                    0.0,
+                    aB2DLineWidth,
+                    basegfx::B2DLineJoin::NONE,
+                    css::drawing::LineCap_BUTT,
+                    basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
+                    bPixelSnapHairline,
+                    this);
             }
         }
 
diff --git a/vcl/source/outdev/polyline.cxx b/vcl/source/outdev/polyline.cxx
index ff89d7b8d3ec..d4131c343b9a 100644
--- a/vcl/source/outdev/polyline.cxx
+++ b/vcl/source/outdev/polyline.cxx
@@ -58,31 +58,38 @@ void OutputDevice::DrawPolyLine( const tools::Polygon& rPoly )
         InitLineColor();
 
     // use b2dpolygon drawing if possible
-    if ( DrawPolyLineDirect( rPoly.getB2DPolygon() ) )
+    if(DrawPolyLineDirect(
+        basegfx::B2DHomMatrix(),
+        rPoly.getB2DPolygon()))
     {
-        basegfx::B2DPolygon aB2DPolyLine(rPoly.getB2DPolygon());
-        const basegfx::B2DHomMatrix aTransform = ImplGetDeviceTransformation();
-        const basegfx::B2DVector aB2DLineWidth( 1.0, 1.0 );
-
-        // transform the polygon
-        aB2DPolyLine.transform( aTransform );
-
-        if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline)
-        {
-            aB2DPolyLine = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyLine);
-        }
+        return;
+    }
 
-        if(mpGraphics->DrawPolyLine(
-            aB2DPolyLine,
-            0.0,
-            aB2DLineWidth,
-            basegfx::B2DLineJoin::NONE,
-            css::drawing::LineCap_BUTT,
-            15.0 * F_PI180 /*default fMiterMinimumAngle, not used*/,
-            this))
-        {
-            return;
-        }
+    const basegfx::B2DPolygon aB2DPolyLine(rPoly.getB2DPolygon());
+    const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
+    const basegfx::B2DVector aB2DLineWidth( 1.0, 1.0 );
+    const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+    // transform the polygon - do not (!)
+    // aB2DPolyLine.transform( aTransform );
+
+    // if(mnAntialiasing & AntialiasingFlags::PixelSnapHairline)
+    // {
+    //     aB2DPolyLine = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolyLine);
+    // }
+
+    if(mpGraphics->DrawPolyLine(
+        aTransform,
+        aB2DPolyLine,
+        0.0,
+        aB2DLineWidth,
+        basegfx::B2DLineJoin::NONE,
+        css::drawing::LineCap_BUTT,
+        basegfx::deg2rad(15.0) /*default fMiterMinimumAngle, not used*/,
+        bPixelSnapHairline,
+        this))
+    {
+        return;
     }
 
     tools::Polygon aPoly = ImplLogicToDevicePixel( rPoly );
@@ -174,8 +181,17 @@ void OutputDevice::DrawPolyLine( const basegfx::B2DPolygon& rB2DPolygon,
         InitLineColor();
 
     // use b2dpolygon drawing if possible
-    if ( DrawPolyLineDirect(rB2DPolygon, fLineWidth, 0.0, eLineJoin, eLineCap, fMiterMinimumAngle) )
+    if(DrawPolyLineDirect(
+        basegfx::B2DHomMatrix(),
+        rB2DPolygon,
+        fLineWidth,
+        0.0,
+        eLineJoin,
+        eLineCap,
+        fMiterMinimumAngle))
+    {
         return;
+    }
 
     // #i101491#
     // no output yet; fallback to geometry decomposition and use filled polygon paint
@@ -221,7 +237,15 @@ void OutputDevice::DrawPolyLine( const basegfx::B2DPolygon& rB2DPolygon,
         // to avoid optical gaps
         for(sal_uInt32 a(0); a < aAreaPolyPolygon.count(); a++)
         {
-            DrawPolyLineDirect( aAreaPolyPolygon.getB2DPolygon(a), 0.0, 0.0, basegfx::B2DLineJoin::NONE, css::drawing::LineCap_BUTT, 15.0 * F_PI180 /*default, not used*/, bTryAA);
+            (void)DrawPolyLineDirect(
+                basegfx::B2DHomMatrix(),
+                aAreaPolyPolygon.getB2DPolygon(a),
+                0.0,
+                0.0,
+                basegfx::B2DLineJoin::NONE,
+                css::drawing::LineCap_BUTT,
+                basegfx::deg2rad(15.0) /*default, not used*/,
+                bTryAA);
         }
     }
     else
@@ -284,13 +308,15 @@ void OutputDevice::drawPolyLine(const tools::Polygon& rPoly, const LineInfo& rLi
         mpAlphaVDev->DrawPolyLine( rPoly, rLineInfo );
 }
 
-bool OutputDevice::DrawPolyLineDirect( const basegfx::B2DPolygon& rB2DPolygon,
-                                       double fLineWidth,
-                                       double fTransparency,
-                                       basegfx::B2DLineJoin eLineJoin,
-                                       css::drawing::LineCap eLineCap,
-                                       double fMiterMinimumAngle,
-                                       bool bBypassAACheck)
+bool OutputDevice::DrawPolyLineDirect(
+    const basegfx::B2DHomMatrix& rObjectTransform,
+    const basegfx::B2DPolygon& rB2DPolygon,
+    double fLineWidth,
+    double fTransparency,
+    basegfx::B2DLineJoin eLineJoin,
+    css::drawing::LineCap eLineCap,
+    double fMiterMinimumAngle,
+    bool bBypassAACheck)
 {
     assert(!is_double_buffered_window());
 
@@ -319,37 +345,43 @@ bool OutputDevice::DrawPolyLineDirect( const basegfx::B2DPolygon& rB2DPolygon,
 
     if(bTryAA)
     {
-        const basegfx::B2DHomMatrix aTransform = ImplGetDeviceTransformation();
-        basegfx::B2DVector aB2DLineWidth(1.0, 1.0);
+        // combine rObjectTransform with WorldToDevice
+        const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation() * rObjectTransform);
+        const bool bLineWidthZero(basegfx::fTools::equalZero(fLineWidth));
+        const basegfx::B2DVector aB2DLineWidth(bLineWidthZero ? 1.0 : fLineWidth, bLineWidthZero ? 1.0 : fLineWidth);
 
         // transform the line width if used
-        if( fLineWidth != 0.0 )
-        {
-            aB2DLineWidth = aTransform * basegfx::B2DVector( fLineWidth, fLineWidth );
-        }
-
-        // transform the polygon
-        basegfx::B2DPolygon aB2DPolygon(rB2DPolygon);
-        aB2DPolygon.transform(aTransform);
-
-        if((mnAntialiasing & AntialiasingFlags::PixelSnapHairline) &&
-           aB2DPolygon.count() < 1000)
-        {
-            // #i98289#, #i101491#
-            // better to remove doubles on device coordinates. Also assume from a given amount
-            // of points that the single edges are not long enough to smooth
-            aB2DPolygon.removeDoublePoints();
-            aB2DPolygon = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolygon);
-        }
+        // if( fLineWidth != 0.0 )
+        // {
+        //     aB2DLineWidth = aTransform * basegfx::B2DVector( fLineWidth, fLineWidth );
+        // }
+
+        // transform the polygon - no!
+        // basegfx::B2DPolygon aB2DPolygon(rB2DPolygon);
+        // aB2DPolygon.transform(aTransform);
+
+        const bool bPixelSnapHairline((mnAntialiasing & AntialiasingFlags::PixelSnapHairline) && rB2DPolygon.count() < 1000);
+        // if((mnAntialiasing & AntialiasingFlags::PixelSnapHairline) &&
+        //    aB2DPolygon.count() < 1000)
+        // {
+        //     // #i98289#, #i101491#
+        //     // better to remove doubles on device coordinates. Also assume from a given amount
+        //     // of points that the single edges are not long enough to smooth
+        //     aB2DPolygon.removeDoublePoints();
+        //     aB2DPolygon = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aB2DPolygon);
+        // }
 
         // draw the polyline
-        bool bDrawSuccess = mpGraphics->DrawPolyLine( aB2DPolygon,
-                                                      fTransparency,
-                                                      aB2DLineWidth,
-                                                      eLineJoin,
-                                                      eLineCap,
-                                                      fMiterMinimumAngle,
-                                                      this );
+        bool bDrawSuccess = mpGraphics->DrawPolyLine(
+            aTransform,
+            rB2DPolygon,
+            fTransparency,
+            aB2DLineWidth,
+            eLineJoin,
+            eLineCap,
+            fMiterMinimumAngle,
+            bPixelSnapHairline,
+            this);
 
         if( bDrawSuccess )
         {
diff --git a/vcl/source/outdev/transparent.cxx b/vcl/source/outdev/transparent.cxx
index 0f06d065e33c..5070821f1e58 100644
--- a/vcl/source/outdev/transparent.cxx
+++ b/vcl/source/outdev/transparent.cxx
@@ -252,17 +252,22 @@ void OutputDevice::DrawTransparent( const basegfx::B2DPolyPolygon& rB2DPolyPoly,
         if( bDrawnOk && IsLineColor() )
         {
             const basegfx::B2DVector aHairlineWidth(1,1);
-            const int nPolyCount = aB2DPolyPolygon.count();
-            for( int nPolyIdx = 0; nPolyIdx < nPolyCount; ++nPolyIdx )
+            const sal_uInt32 nPolyCount(aB2DPolyPolygon.count());
+            const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+            for( sal_uInt32 nPolyIdx = 0; nPolyIdx < nPolyCount; ++nPolyIdx )
             {
-                const basegfx::B2DPolygon aOnePoly = aB2DPolyPolygon.getB2DPolygon( nPolyIdx );
+                const basegfx::B2DPolygon aOnePoly(aB2DPolyPolygon.getB2DPolygon(nPolyIdx));
+
                 mpGraphics->DrawPolyLine(
+                    basegfx::B2DHomMatrix(),
                     aOnePoly,
                     fTransparency,
                     aHairlineWidth,
                     basegfx::B2DLineJoin::NONE,
                     css::drawing::LineCap_BUTT,
-                    15.0 * F_PI180, // not used with B2DLineJoin::NONE, but the correct default
+                    basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
+                    bPixelSnapHairline,
                     this );
             }
         }
@@ -355,17 +360,22 @@ bool OutputDevice::DrawTransparentNatively ( const tools::PolyPolygon& rPolyPoly
             mpGraphics->SetFillColor();
             // draw the border line
             const basegfx::B2DVector aLineWidths( 1, 1 );
-            const int nPolyCount = aB2DPolyPolygon.count();
-            for( int nPolyIdx = 0; nPolyIdx < nPolyCount; ++nPolyIdx )
+            const sal_uInt32 nPolyCount(aB2DPolyPolygon.count());
+            const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
+
+            for( sal_uInt32 nPolyIdx = 0; nPolyIdx < nPolyCount; ++nPolyIdx )
             {
-                const basegfx::B2DPolygon& rPolygon = aB2DPolyPolygon.getB2DPolygon( nPolyIdx );
+                const basegfx::B2DPolygon aPolygon(aB2DPolyPolygon.getB2DPolygon(nPolyIdx));
+
                 bDrawn = mpGraphics->DrawPolyLine(
-                    rPolygon,
+                    basegfx::B2DHomMatrix(),
+                    aPolygon,
                     fTransparency,
                     aLineWidths,
                     basegfx::B2DLineJoin::NONE,
                     css::drawing::LineCap_BUTT,
-                    15.0 * F_PI180, // not used with B2DLineJoin::NONE, but the correct default
+                    basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
+                    bPixelSnapHairline,
                     this );
             }
             // prepare to restore the fill color
diff --git a/vcl/unx/generic/gdi/gdiimpl.cxx b/vcl/unx/generic/gdi/gdiimpl.cxx
index 7845d49f9df3..0d12d15f3a8e 100644
--- a/vcl/unx/generic/gdi/gdiimpl.cxx
+++ b/vcl/unx/generic/gdi/gdiimpl.cxx
@@ -1593,14 +1593,18 @@ bool X11SalGraphicsImpl::drawFilledTrapezoids( const basegfx::B2DTrapezoid* pB2D
 }
 
 bool X11SalGraphicsImpl::drawPolyLine(
+    const basegfx::B2DHomMatrix& rObjectToDevice,
     const basegfx::B2DPolygon& rPolygon,
     double fTransparency,
     const basegfx::B2DVector& rLineWidth,
     basegfx::B2DLineJoin eLineJoin,
     css::drawing::LineCap eLineCap,
-    double fMiterMinimumAngle)
+    double fMiterMinimumAngle,
+    bool bPixelSnapHairline)
 {
-    const bool bIsHairline = (rLineWidth.getX() == rLineWidth.getY()) && (rLineWidth.getX() <= 1.2);
+    // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
+    const basegfx::B2DVector aLineWidth(rObjectToDevice * rLineWidth);
+    const bool bIsHairline((aLineWidth.getX() == aLineWidth.getY()) && (aLineWidth.getX() <= 1.2));
 
     // #i101491#
     if( !bIsHairline && (rPolygon.count() > 1000) )
@@ -1614,18 +1618,23 @@ bool X11SalGraphicsImpl::drawPolyLine(
         return false;
     }
 
+    // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline
+    basegfx::B2DPolygon aPolyLine(rPolygon);
+    aPolyLine.transform(rObjectToDevice);
+    if(bPixelSnapHairline) { aPolyLine = basegfx::tools::snapPointsOfHorizontalOrVerticalEdges(aPolyLine); }
+
     // temporarily adjust brush color to pen color
     // since the line is drawn as an area-polygon
     const SalColor aKeepBrushColor = mnBrushColor;
     mnBrushColor = mnPenColor;
 
-    // #i11575#desc5#b adjust B2D tesselation result to raster positions
-    basegfx::B2DPolygon aPolygon = rPolygon;
-    const double fHalfWidth = 0.5 * rLineWidth.getX();
+    // #i11575#desc5#b adjust B2D tessellation result to raster positions
+    // basegfx::B2DPolygon aPolygon = rPolygon;
+    const double fHalfWidth = 0.5 * aLineWidth.getX();
 
     // #i122456# This is probably thought to happen to align hairlines to pixel positions, so
     // it should be a 0.5 translation, not more. It will definitely go wrong with fat lines
-    aPolygon.transform( basegfx::tools::createTranslateB2DHomMatrix(0.5, 0.5) );
+    aPolyLine.transform( basegfx::tools::createTranslateB2DHomMatrix(0.5, 0.5) );
 
     // shortcut for hairline drawing to improve performance
     bool bDrawnOk = true;
@@ -1634,7 +1643,7 @@ bool X11SalGraphicsImpl::drawPolyLine(
         // hairlines can benefit from a simplified tesselation
         // e.g. for hairlines the linejoin style can be ignored
         basegfx::B2DTrapezoidVector aB2DTrapVector;
-        basegfx::tools::createLineTrapezoidFromB2DPolygon( aB2DTrapVector, aPolygon, rLineWidth.getX() );
+        basegfx::tools::createLineTrapezoidFromB2DPolygon( aB2DTrapVector, aPolyLine, aLineWidth.getX() );
 
         // draw tesselation result
         const int nTrapCount = aB2DTrapVector.size();
@@ -1647,21 +1656,25 @@ bool X11SalGraphicsImpl::drawPolyLine(
     }
 
     // get the area polygon for the line polygon
-    if( (rLineWidth.getX() != rLineWidth.getY())
-    && !basegfx::fTools::equalZero( rLineWidth.getY() ) )
+    if( (aLineWidth.getX() != aLineWidth.getY()) && !basegfx::fTools::equalZero( aLineWidth.getY() ) )
     {
         // prepare for createAreaGeometry() with anisotropic linewidth
-        aPolygon.transform( basegfx::tools::createScaleB2DHomMatrix(1.0, rLineWidth.getX() / rLineWidth.getY()));
+        aPolyLine.transform( basegfx::tools::createScaleB2DHomMatrix(1.0, aLineWidth.getX() / aLineWidth.getY()));
     }
 
     // create the area-polygon for the line
-    const basegfx::B2DPolyPolygon aAreaPolyPoly( basegfx::tools::createAreaGeometry(aPolygon, fHalfWidth, eLineJoin, eLineCap, fMiterMinimumAngle) );
+    const basegfx::B2DPolyPolygon aAreaPolyPoly(
+        basegfx::tools::createAreaGeometry(
+            aPolyLine,
+            fHalfWidth,
+            eLineJoin,
+            eLineCap,
+            fMiterMinimumAngle));
 
-    if( (rLineWidth.getX() != rLineWidth.getY())
-    && !basegfx::fTools::equalZero( rLineWidth.getX() ) )
+    if( (aLineWidth.getX() != aLineWidth.getY()) && !basegfx::fTools::equalZero( aLineWidth.getX() ) )
     {
         // postprocess createAreaGeometry() for anisotropic linewidth
-        aPolygon.transform(basegfx::tools::createScaleB2DHomMatrix(1.0, rLineWidth.getY() / rLineWidth.getX()));
+        aPolyLine.transform(basegfx::tools::createScaleB2DHomMatrix(1.0, aLineWidth.getY() / aLineWidth.getX()));
     }
 
     // draw each area polypolygon component individually
diff --git a/vcl/unx/generic/gdi/gdiimpl.hxx b/vcl/unx/generic/gdi/gdiimpl.hxx
index 5b04ba67be0c..c8d0d79596fe 100644
--- a/vcl/unx/generic/gdi/gdiimpl.hxx
+++ b/vcl/unx/generic/gdi/gdiimpl.hxx
@@ -160,12 +160,14 @@ public:
     virtual bool drawPolyPolygon( const basegfx::B2DPolyPolygon&, double fTransparency ) override;
 
     virtual bool drawPolyLine(
+                const basegfx::B2DHomMatrix& rObjectToDevice,
                 const basegfx::B2DPolygon&,
                 double fTransparency,
                 const basegfx::B2DVector& rLineWidths,
                 basegfx::B2DLineJoin,
                 css::drawing::LineCap,
-                double fMiterMinimumAngle) override;
+                double fMiterMinimumAngle,
+                bool bPixelSnapHairline) override;
 
     virtual bool drawPolyLineBezier(
                 sal_uInt32 nPoints,
diff --git a/vcl/unx/generic/gdi/salgdi.cxx b/vcl/unx/generic/gdi/salgdi.cxx
index 814173fbcbe7..f837c19e8ffc 100644
--- a/vcl/unx/generic/gdi/salgdi.cxx
+++ b/vcl/unx/generic/gdi/salgdi.cxx
@@ -559,15 +559,34 @@ bool X11SalGraphics::drawPolyPolygon( const basegfx::B2DPolyPolygon& rOrigPolyPo
 }
 
 bool X11SalGraphics::drawPolyLine(
+    const basegfx::B2DHomMatrix& rObjectToDevice,
     const basegfx::B2DPolygon& rPolygon,
     double fTransparency,
     const basegfx::B2DVector& rLineWidth,
     basegfx::B2DLineJoin eLineJoin,
     css::drawing::LineCap eLineCap,
-    double fMiterMinimumAngle)
+    double fMiterMinimumAngle,
+    bool bPixelSnapHairline)
 {
-    return mxImpl->drawPolyLine( rPolygon, fTransparency, rLineWidth,
-            eLineJoin, eLineCap, fMiterMinimumAngle );
+    if(0 == rPolygon.count())
+    {
+        return true;
+    }
+
+    if(fTransparency >= 1.0)
+    {
+        return true;
+    }
+
+    return mxImpl->drawPolyLine(
+        rObjectToDevice,
+        rPolygon,
+        fTransparency,
+        rLineWidth,
+        eLineJoin,
+        eLineCap,
+        fMiterMinimumAngle,
+        bPixelSnapHairline);
 }
 
 bool X11SalGraphics::drawGradient(const tools::PolyPolygon& rPoly, const Gradient& rGradient)
diff --git a/vcl/unx/generic/print/genpspgraphics.cxx b/vcl/unx/generic/print/genpspgraphics.cxx
index 78eee8212a43..d67a73534e27 100644
--- a/vcl/unx/generic/print/genpspgraphics.cxx
+++ b/vcl/unx/generic/print/genpspgraphics.cxx
@@ -441,12 +441,14 @@ bool GenPspGraphics::drawPolyPolygon( const basegfx::B2DPolyPolygon&, double /*f
 }
 
 bool GenPspGraphics::drawPolyLine(
+    const basegfx::B2DHomMatrix& /* rObjectToDevice */,
     const basegfx::B2DPolygon&,
     double /*fTranspareny*/,
     const basegfx::B2DVector& /*rLineWidths*/,
     basegfx::B2DLineJoin /*eJoin*/,
     css::drawing::LineCap /*eLineCap*/,
-    double /*fMiterMinimumAngle*/)
+    double /*fMiterMinimumAngle*/,
+    bool /* bPixelSnapHairline */)
 {
     // TODO: a PS printer can draw B2DPolyLines almost directly
     return false;
diff --git a/vcl/win/gdi/gdiimpl.cxx b/vcl/win/gdi/gdiimpl.cxx
index 915cd9351521..16fd8128a8e8 100644
--- a/vcl/win/gdi/gdiimpl.cxx
+++ b/vcl/win/gdi/gdiimpl.cxx
@@ -35,6 +35,7 @@
 #include <vcl/salbtype.hxx>
 #include <win/salframe.h>
 #include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/tools/systemdependentdata.hxx>
 
 #include "outdata.hxx"
 #include "win/salids.hrc"
@@ -1895,69 +1896,127 @@ bool WinSalGraphicsImpl::drawPolyPolygonBezier( sal_uInt32 nPoly, const sal_uInt
     return bRet;
 }
 
+basegfx::B2DPoint impPixelSnap(
+    const basegfx::B2DPolygon& rPolygon,
+    const basegfx::B2DHomMatrix& rObjectToDevice,
+    basegfx::B2DHomMatrix& rObjectToDeviceInv,
+    sal_uInt32 nIndex)
+{
+    const sal_uInt32 nCount(rPolygon.count());
+
+    // get the data
+    const basegfx::B2ITuple aPrevTuple(basegfx::fround(rObjectToDevice * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount)));
+    const basegfx::B2DPoint aCurrPoint(rObjectToDevice * rPolygon.getB2DPoint(nIndex));
+    const basegfx::B2ITuple aCurrTuple(basegfx::fround(aCurrPoint));
+    const basegfx::B2ITuple aNextTuple(basegfx::fround(rObjectToDevice * 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());
+
+        if(rObjectToDeviceInv.isIdentity())
+        {
+            rObjectToDeviceInv = rObjectToDevice;
+            rObjectToDeviceInv.invert();
+        }
+
+        aSnappedPoint *= rObjectToDeviceInv;
+
+        return aSnappedPoint;
+    }
+
+    return rPolygon.getB2DPoint(nIndex);
+}
+
 void impAddB2DPolygonToGDIPlusGraphicsPathReal(
     Gdiplus::GraphicsPath& rGraphicsPath,
     const basegfx::B2DPolygon& rPolygon,
-    bool bNoLineJoin)
+    const basegfx::B2DHomMatrix& rObjectToDevice,
+    bool bNoLineJoin,
+    bool bPixelSnapHairline)
 {
     sal_uInt32 nCount(rPolygon.count());
 
     if(nCount)
     {
         const sal_uInt32 nEdgeCount(rPolygon.isClosed() ? nCount : nCount - 1);
-        const bool bControls(rPolygon.areControlPointsUsed());
-        basegfx::B2DPoint aCurr(rPolygon.getB2DPoint(0));
 
-        for(sal_uInt32 a(0); a < nEdgeCount; a++)
+        if(nEdgeCount)
         {
-            const sal_uInt32 nNextIndex((a + 1) % nCount);
-            const basegfx::B2DPoint aNext(rPolygon.getB2DPoint(nNextIndex));
-            const bool b1stControlPointUsed(bControls && rPolygon.isNextControlPointUsed(a));
-            const bool b2ndControlPointUsed(bControls && rPolygon.isPrevControlPointUsed(nNextIndex));
+            const bool bControls(rPolygon.areControlPointsUsed());
+            basegfx::B2DPoint aCurr(rPolygon.getB2DPoint(0));
+            basegfx::B2DHomMatrix aObjectToDeviceInv;
 
-            if(b1stControlPointUsed || b2ndControlPointUsed)
+            if(bPixelSnapHairline)
             {
-                basegfx::B2DPoint aCa(rPolygon.getNextControlPoint(a));
-                basegfx::B2DPoint aCb(rPolygon.getPrevControlPoint(nNextIndex));
-
-                // tdf#99165 MS Gdiplus cannot handle creating correct extra geometry for fat lines
-                // with LineCap or LineJoin when a bezier segment starts or ends trivial, e.g. has
-                // no 1st or 2nd control point, despite that these are mathematicaly correct definitions
-                // (basegfx can handle that). To solve, create replacement vectors to thre resp. next
-                // control point with 1/3rd of length (the default control vector for these cases).
-                // Only one of this can happen here, else the is(Next|Prev)ControlPointUsed wopuld have
-                // both been false.
-                // Caution: This error (and it's correction) might be necessary for other graphical
-                // sub-systems in a similar way
-                if(!b1stControlPointUsed)
-                {
-                    aCa = aCurr + ((aCb - aCurr) * 0.3);
-                }
-                else if(!b2ndControlPointUsed)
+                aCurr = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, 0);
+            }
+
+            for(sal_uInt32 a(0); a < nEdgeCount; a++)
+            {
+                const sal_uInt32 nNextIndex((a + 1) % nCount);
+                basegfx::B2DPoint aNext(rPolygon.getB2DPoint(nNextIndex));
+                const bool b1stControlPointUsed(bControls && rPolygon.isNextControlPointUsed(a));
+                const bool b2ndControlPointUsed(bControls && rPolygon.isPrevControlPointUsed(nNextIndex));
+
+                if(bPixelSnapHairline)
                 {
-                    aCb = aNext + ((aCa - aNext) * 0.3);

... etc. - the rest is truncated


More information about the Libreoffice-commits mailing list