[Libreoffice-commits] core.git: Branch 'feature/drawinglayercore2' - 2 commits - vcl/headless vcl/inc vcl/Library_vcl.mk vcl/unx

Tomaž Vajngerl (via logerrit) logerrit at kemper.freedesktop.org
Mon Aug 30 23:28:31 UTC 2021


Rebased ref, commits from common ancestor:
commit a8d96eda8a2e44c8bbdb55db7f1618ba9831a9f1
Author:     Tomaž Vajngerl <tomaz.vajngerl at collabora.co.uk>
AuthorDate: Thu Aug 26 12:04:18 2021 +0900
Commit:     Tomaž Vajngerl <tomaz.vajngerl at collabora.co.uk>
CommitDate: Tue Aug 31 08:27:34 2021 +0900

    X11SalGraphics using SvpGraphicsBackend
    
    Change-Id: I1370cf5235752adb8082ac8c664e0bf3dccd41b3

diff --git a/vcl/inc/unx/salgdi.h b/vcl/inc/unx/salgdi.h
index 41e0f57598c8..a273a53caa55 100644
--- a/vcl/inc/unx/salgdi.h
+++ b/vcl/inc/unx/salgdi.h
@@ -311,6 +311,7 @@ private:
 
     bool                            bWindow_ : 1;       // is Window
     bool                            bVirDev_ : 1;       // is VirDev
+    bool m_bCairoBackend : 1;
     bool                            m_bSkia  : 1;
 
 private:
diff --git a/vcl/unx/generic/gdi/salgdi.cxx b/vcl/unx/generic/gdi/salgdi.cxx
index bffe0d7734c4..abf3e55ae45c 100644
--- a/vcl/unx/generic/gdi/salgdi.cxx
+++ b/vcl/unx/generic/gdi/salgdi.cxx
@@ -75,6 +75,7 @@ X11SalGraphics::X11SalGraphics():
     hBrush_(None),
     bWindow_(false),
     bVirDev_(false),
+    m_bCairoBackend(false),
     m_bSkia(SkiaHelper::isVCLSkiaEnabled())
 {
 #if HAVE_FEATURE_SKIA
@@ -85,6 +86,10 @@ X11SalGraphics::X11SalGraphics():
     }
     else
 #endif
+    if (m_bCairoBackend)
+    {
+    }
+    else
     {
         mxTextRenderImpl.reset(new X11CairoTextRender(*this));
         mxImpl.reset(new X11SalGraphicsImpl(*this));
diff --git a/vcl/unx/generic/window/salframe.cxx b/vcl/unx/generic/window/salframe.cxx
index a47ed1c645fa..626dc66f85e8 100644
--- a/vcl/unx/generic/window/salframe.cxx
+++ b/vcl/unx/generic/window/salframe.cxx
@@ -282,76 +282,11 @@ static void CreateNetWmAppIcon( sal_uInt16 nIcon, NetWmIconData& netwm_icon )
     netwm_icon.resize( pos );
 }
 
-static bool lcl_SelectAppIconPixmap( SalDisplay const *pDisplay, SalX11Screen nXScreen,
-                                         sal_uInt16 nIcon, sal_uInt16 iconSize,
-                                         Pixmap& icon_pixmap, Pixmap& icon_mask, NetWmIconData& netwm_icon)
+static bool lcl_SelectAppIconPixmap( SalDisplay const * /*pDisplay*/, SalX11Screen /*nXScreen*/,
+                                         sal_uInt16 /*nIcon*/, sal_uInt16 /*iconSize*/,
+                                         Pixmap& /*icon_pixmap*/, Pixmap& /*icon_mask*/, NetWmIconData& /*netwm_icon*/)
 {
-    CreateNetWmAppIcon( nIcon, netwm_icon );
-
-    OUString sIcon;
-
-    if( iconSize >= 48 )
-        sIcon = SV_ICON_SIZE48[nIcon];
-    else if( iconSize >= 32 )
-        sIcon = SV_ICON_SIZE32[nIcon];
-    else if( iconSize >= 16 )
-         sIcon = SV_ICON_SIZE16[nIcon];
-    else
-        return false;
-
-    BitmapEx aIcon = vcl::bitmap::loadFromName(sIcon, ImageLoadFlags::IgnoreScalingFactor);
-
-    if( aIcon.IsEmpty() )
-        return false;
-
-    X11SalBitmap *pBitmap = dynamic_cast < X11SalBitmap * >
-        (aIcon.ImplGetBitmapSalBitmap().get());
-    if (!pBitmap) // FIXME: opengl , TODO SKIA
-        return false;
-
-    icon_pixmap = XCreatePixmap( pDisplay->GetDisplay(),
-                                 pDisplay->GetRootWindow( nXScreen ),
-                                 iconSize, iconSize,
-                                 DefaultDepth( pDisplay->GetDisplay(),
-                                               nXScreen.getXScreen() )
-                                 );
-
-    SalTwoRect aRect(0, 0, iconSize, iconSize, 0, 0, iconSize, iconSize);
-
-    pBitmap->ImplDraw( icon_pixmap,
-                       nXScreen,
-                       DefaultDepth( pDisplay->GetDisplay(),
-                                     nXScreen.getXScreen() ),
-                       aRect,
-                       DefaultGC( pDisplay->GetDisplay(),
-                                  nXScreen.getXScreen() ) );
-
-    icon_mask = None;
-
-    if( aIcon.IsAlpha() )
-    {
-        icon_mask = XCreatePixmap( pDisplay->GetDisplay(),
-                                   pDisplay->GetRootWindow( pDisplay->GetDefaultXScreen() ),
-                                   iconSize, iconSize, 1);
-
-        XGCValues aValues;
-        aValues.foreground = 0xffffffff;
-        aValues.background = 0;
-        aValues.function = GXcopy;
-        GC aMonoGC = XCreateGC( pDisplay->GetDisplay(), icon_mask,
-            GCFunction|GCForeground|GCBackground, &aValues );
-
-        Bitmap aMask = aIcon.GetAlpha();
-        aMask.Invert();
-
-        X11SalBitmap *pMask = static_cast < X11SalBitmap * >
-            (aMask.ImplGetSalBitmap().get());
-
-        pMask->ImplDraw(icon_mask, nXScreen, 1, aRect, aMonoGC);
-        XFreeGC( pDisplay->GetDisplay(), aMonoGC );
-    }
-
-    return true;
+    return false;
 }
 
 void X11SalFrame::Init( SalFrameStyleFlags nSalFrameStyle, SalX11Screen nXScreen, SystemParentData const * pParentData, bool bUseGeometry )
commit 9d4fdb9ae98a608aae1076c90264d83807ad655d
Author:     Tomaž Vajngerl <tomaz.vajngerl at collabora.co.uk>
AuthorDate: Thu Aug 26 11:51:34 2021 +0900
Commit:     Tomaž Vajngerl <tomaz.vajngerl at collabora.co.uk>
CommitDate: Tue Aug 31 08:27:26 2021 +0900

    move cairo drawing functions into SvpGraphicsBackend
    
    SvpGraphicsBackend is derived from SalGraphicsImpl.
    
    Change-Id: Ic3abc6df3b489457cda4b942738edc20cfa8e171

diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk
index eb0af8aa8cc4..b2effea26d76 100644
--- a/vcl/Library_vcl.mk
+++ b/vcl/Library_vcl.mk
@@ -518,6 +518,8 @@ vcl_headless_code= \
     $(if $(filter-out iOS,$(OS)), \
         vcl/headless/svpbmp \
         vcl/headless/svpgdi \
+        vcl/headless/SvpGraphicsBackend \
+        vcl/headless/CairoCommon \
         $(if $(ENABLE_HEADLESS),vcl/headless/svpdata) \
         vcl/headless/CustomWidgetDraw \
     ) \
diff --git a/vcl/headless/CairoCommon.cxx b/vcl/headless/CairoCommon.cxx
new file mode 100644
index 000000000000..8707b6a98462
--- /dev/null
+++ b/vcl/headless/CairoCommon.cxx
@@ -0,0 +1,959 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this file
+ *   except in compliance with the License. You may obtain a copy of
+ *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <headless/CairoCommon.hxx>
+#include <dlfcn.h>
+#include <vcl/BitmapTools.hxx>
+#include <basegfx/utils/canvastools.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/utils/systemdependentdata.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <svdata.hxx>
+
+void dl_cairo_surface_set_device_scale(cairo_surface_t* surface, double x_scale, double y_scale)
+{
+#ifdef ANDROID
+    cairo_surface_set_device_scale(surface, x_scale, y_scale);
+#else
+    static auto func = reinterpret_cast<void (*)(cairo_surface_t*, double, double)>(
+        dlsym(nullptr, "cairo_surface_set_device_scale"));
+    if (func)
+        func(surface, x_scale, y_scale);
+#endif
+}
+
+void dl_cairo_surface_get_device_scale(cairo_surface_t* surface, double* x_scale, double* y_scale)
+{
+#ifdef ANDROID
+    cairo_surface_get_device_scale(surface, x_scale, y_scale);
+#else
+    static auto func = reinterpret_cast<void (*)(cairo_surface_t*, double*, double*)>(
+        dlsym(nullptr, "cairo_surface_get_device_scale"));
+    if (func)
+        func(surface, x_scale, y_scale);
+    else
+    {
+        if (x_scale)
+            *x_scale = 1.0;
+        if (y_scale)
+            *y_scale = 1.0;
+    }
+#endif
+}
+
+basegfx::B2DRange getFillDamage(cairo_t* cr)
+{
+    double x1, y1, x2, y2;
+
+    // this is faster than cairo_fill_extents, at the cost of some overdraw
+    cairo_path_extents(cr, &x1, &y1, &x2, &y2);
+
+    // support B2DRange::isEmpty()
+    if (0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2)
+    {
+        return basegfx::B2DRange(x1, y1, x2, y2);
+    }
+
+    return basegfx::B2DRange();
+}
+
+basegfx::B2DRange getClipBox(cairo_t* cr)
+{
+    double x1, y1, x2, y2;
+
+    cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
+
+    // support B2DRange::isEmpty()
+    if (0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2)
+    {
+        return basegfx::B2DRange(x1, y1, x2, y2);
+    }
+
+    return basegfx::B2DRange();
+}
+
+basegfx::B2DRange getClippedFillDamage(cairo_t* cr)
+{
+    basegfx::B2DRange aDamageRect(getFillDamage(cr));
+    aDamageRect.intersect(getClipBox(cr));
+    return aDamageRect;
+}
+
+basegfx::B2DRange getClippedStrokeDamage(cairo_t* cr)
+{
+    basegfx::B2DRange aDamageRect(getStrokeDamage(cr));
+    aDamageRect.intersect(getClipBox(cr));
+    return aDamageRect;
+}
+
+basegfx::B2DRange getStrokeDamage(cairo_t* cr)
+{
+    double x1, y1, x2, y2;
+
+    // less accurate, but much faster
+    cairo_path_extents(cr, &x1, &y1, &x2, &y2);
+
+    // support B2DRange::isEmpty()
+    if (0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2)
+    {
+        return basegfx::B2DRange(x1, y1, x2, y2);
+    }
+
+    return basegfx::B2DRange();
+}
+
+// Remove bClosePath: Checked that the already used mechanism for Win using
+// Gdiplus already relies on rPolygon.isClosed(), so should be safe to replace
+// this.
+// For PixelSnap we need the ObjectToDevice transformation here now. This is a
+// special case relative to the also executed LineDraw-Offset of (0.5, 0.5) in
+// DeviceCoordinates: The LineDraw-Offset is applied *after* the snap, so we
+// need the ObjectToDevice transformation *without* that offset here to do the
+// same. The LineDraw-Offset will be applied by the callers using a linear
+// transformation for Cairo now
+// For support of PixelSnapHairline we also need the ObjectToDevice transformation
+// and a method (same as in gdiimpl.cxx for Win and Gdiplus). This is needed e.g.
+// for Chart-content visualization. CAUTION: It's not the same as PixelSnap (!)
+// tdf#129845 add reply value to allow counting a point/byte/size measurement to
+// be included
+size_t AddPolygonToPath(cairo_t* cr, const basegfx::B2DPolygon& rPolygon,
+                        const basegfx::B2DHomMatrix& rObjectToDevice, bool bPixelSnap,
+                        bool bPixelSnapHairline)
+{
+    // short circuit if there is nothing to do
+    const sal_uInt32 nPointCount(rPolygon.count());
+    size_t nSizeMeasure(0);
+
+    if (0 == nPointCount)
+    {
+        return nSizeMeasure;
+    }
+
+    const bool bHasCurves(rPolygon.areControlPointsUsed());
+    const bool bClosePath(rPolygon.isClosed());
+    const bool bObjectToDeviceUsed(!rObjectToDevice.isIdentity());
+    basegfx::B2DHomMatrix aObjectToDeviceInv;
+    basegfx::B2DPoint aLast;
+
+    for (sal_uInt32 nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++)
+    {
+        int nClosedIdx = nPointIdx;
+        if (nPointIdx >= nPointCount)
+        {
+            // prepare to close last curve segment if needed
+            if (bClosePath && (nPointIdx == nPointCount))
+            {
+                nClosedIdx = 0;
+            }
+            else
+            {
+                break;
+            }
+        }
+
+        basegfx::B2DPoint aPoint(rPolygon.getB2DPoint(nClosedIdx));
+
+        if (bPixelSnap)
+        {
+            // snap device coordinates to full pixels
+            if (bObjectToDeviceUsed)
+            {
+                // go to DeviceCoordinates
+                aPoint *= rObjectToDevice;
+            }
+
+            // snap by rounding
+            aPoint.setX(basegfx::fround(aPoint.getX()));
+            aPoint.setY(basegfx::fround(aPoint.getY()));
+
+            if (bObjectToDeviceUsed)
+            {
+                if (aObjectToDeviceInv.isIdentity())
+                {
+                    aObjectToDeviceInv = rObjectToDevice;
+                    aObjectToDeviceInv.invert();
+                }
+
+                // go back to ObjectCoordinates
+                aPoint *= aObjectToDeviceInv;
+            }
+        }
+
+        if (bPixelSnapHairline)
+        {
+            // snap horizontal and vertical lines (mainly used in Chart for
+            // 'nicer' AAing)
+            aPoint = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nClosedIdx);
+        }
+
+        if (!nPointIdx)
+        {
+            // first point => just move there
+            cairo_move_to(cr, aPoint.getX(), aPoint.getY());
+            aLast = aPoint;
+            continue;
+        }
+
+        bool bPendingCurve(false);
+
+        if (bHasCurves)
+        {
+            bPendingCurve = rPolygon.isNextControlPointUsed(nPrevIdx);
+            bPendingCurve |= rPolygon.isPrevControlPointUsed(nClosedIdx);
+        }
+
+        if (!bPendingCurve) // line segment
+        {
+            cairo_line_to(cr, aPoint.getX(), aPoint.getY());
+            nSizeMeasure++;
+        }
+        else // cubic bezier segment
+        {
+            basegfx::B2DPoint aCP1 = rPolygon.getNextControlPoint(nPrevIdx);
+            basegfx::B2DPoint aCP2 = rPolygon.getPrevControlPoint(nClosedIdx);
+
+            // tdf#99165 if the control points are 'empty', create the mathematical
+            // correct replacement ones to avoid problems with the graphical sub-system
+            // tdf#101026 The 1st attempt to create a mathematically correct replacement control
+            // vector was wrong. Best alternative is one as close as possible which means short.
+            if (aCP1.equal(aLast))
+            {
+                aCP1 = aLast + ((aCP2 - aLast) * 0.0005);
+            }
+
+            if (aCP2.equal(aPoint))
+            {
+                aCP2 = aPoint + ((aCP1 - aPoint) * 0.0005);
+            }
+
+            cairo_curve_to(cr, aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(), aPoint.getX(),
+                           aPoint.getY());
+            // take some bigger measure for curve segments - too expensive to subdivide
+            // here and that precision not needed, but four (2 points, 2 control-points)
+            // would be a too low weight
+            nSizeMeasure += 10;
+        }
+
+        aLast = aPoint;
+    }
+
+    if (bClosePath)
+    {
+        cairo_close_path(cr);
+    }
+
+    return nSizeMeasure;
+}
+
+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);
+}
+
+namespace
+{
+class SystemDependentData_CairoPath : public basegfx::SystemDependentData
+{
+private:
+    // the path data itself
+    cairo_path_t* mpCairoPath;
+
+    // all other values the path data  is based on and
+    // need to be compared with to check for data validity
+    bool mbNoJoin;
+    bool mbAntiAlias;
+    std::vector<double> maStroke;
+
+public:
+    SystemDependentData_CairoPath(basegfx::SystemDependentDataManager& rSystemDependentDataManager,
+                                  size_t nSizeMeasure, cairo_t* cr, bool bNoJoin, bool bAntiAlias,
+                                  const std::vector<double>* pStroke); // MM01
+    virtual ~SystemDependentData_CairoPath() override;
+
+    // read access
+    cairo_path_t* getCairoPath() { return mpCairoPath; }
+    bool getNoJoin() const { return mbNoJoin; }
+    bool getAntiAlias() const { return mbAntiAlias; }
+    const std::vector<double>& getStroke() const { return maStroke; }
+
+    virtual sal_Int64 estimateUsageInBytes() const override;
+};
+
+SystemDependentData_CairoPath::SystemDependentData_CairoPath(
+    basegfx::SystemDependentDataManager& rSystemDependentDataManager, size_t nSizeMeasure,
+    cairo_t* cr, bool bNoJoin, bool bAntiAlias, const std::vector<double>* pStroke)
+    : basegfx::SystemDependentData(rSystemDependentDataManager)
+    , mpCairoPath(nullptr)
+    , mbNoJoin(bNoJoin)
+    , mbAntiAlias(bAntiAlias)
+    , maStroke()
+{
+    // tdf#129845 only create a copy of the path when nSizeMeasure is
+    // bigger than some decent threshold
+    if (nSizeMeasure > 50)
+    {
+        mpCairoPath = cairo_copy_path(cr);
+
+        if (nullptr != pStroke)
+        {
+            maStroke = *pStroke;
+        }
+    }
+}
+
+SystemDependentData_CairoPath::~SystemDependentData_CairoPath()
+{
+    if (nullptr != mpCairoPath)
+    {
+        cairo_path_destroy(mpCairoPath);
+        mpCairoPath = nullptr;
+    }
+}
+
+sal_Int64 SystemDependentData_CairoPath::estimateUsageInBytes() const
+{
+    // tdf#129845 by using the default return value of zero when no path
+    // was created, SystemDependentData::calculateCombinedHoldCyclesInSeconds
+    // will do the right thing and not buffer this entry at all
+    sal_Int64 nRetval(0);
+
+    if (nullptr != mpCairoPath)
+    {
+        // per node
+        // - num_data incarnations of
+        // - sizeof(cairo_path_data_t) which is a union of defines and point data
+        //   thus may 2 x sizeof(double)
+        nRetval = mpCairoPath->num_data * sizeof(cairo_path_data_t);
+    }
+
+    return nRetval;
+}
+
+} // end anonymous namespace
+
+bool CairoCommon::drawPolyLine(cairo_t* cr, basegfx::B2DRange* pExtents, const Color& rLineColor,
+                               bool bAntiAlias, const basegfx::B2DHomMatrix& rObjectToDevice,
+                               const basegfx::B2DPolygon& rPolyLine, double fTransparency,
+                               double fLineWidth,
+                               const std::vector<double>* pStroke, // MM01
+                               basegfx::B2DLineJoin eLineJoin, css::drawing::LineCap eLineCap,
+                               double fMiterMinimumAngle, bool bPixelSnapHairline)
+{
+    // short circuit if there is nothing to do
+    if (0 == rPolyLine.count() || fTransparency < 0.0 || fTransparency >= 1.0)
+    {
+        return true;
+    }
+
+    // need to check/handle LineWidth when ObjectToDevice transformation is used
+    const bool bObjectToDeviceIsIdentity(rObjectToDevice.isIdentity());
+
+    // tdf#124848 calculate-back logical LineWidth for a hairline
+    // since this implementation hands over the transformation to
+    // the graphic sub-system
+    if (fLineWidth == 0)
+    {
+        fLineWidth = 1.0;
+
+        if (!bObjectToDeviceIsIdentity)
+        {
+            basegfx::B2DHomMatrix aObjectToDeviceInv(rObjectToDevice);
+            aObjectToDeviceInv.invert();
+            fLineWidth = (aObjectToDeviceInv * basegfx::B2DVector(fLineWidth, 0)).getLength();
+        }
+    }
+
+    // PixelOffset used: Need to reflect in linear transformation
+    cairo_matrix_t aMatrix;
+    basegfx::B2DHomMatrix aDamageMatrix(basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5));
+
+    if (bObjectToDeviceIsIdentity)
+    {
+        // Set PixelOffset as requested
+        cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
+    }
+    else
+    {
+        // Prepare ObjectToDevice transformation. Take PixelOffset for Lines into
+        // account: Multiply from left to act in DeviceCoordinates
+        aDamageMatrix = aDamageMatrix * rObjectToDevice;
+        cairo_matrix_init(&aMatrix, aDamageMatrix.get(0, 0), aDamageMatrix.get(1, 0),
+                          aDamageMatrix.get(0, 1), aDamageMatrix.get(1, 1), aDamageMatrix.get(0, 2),
+                          aDamageMatrix.get(1, 2));
+    }
+
+    // set linear transformation
+    cairo_set_matrix(cr, &aMatrix);
+
+    // setup line attributes
+    cairo_line_join_t eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
+    switch (eLineJoin)
+    {
+        case basegfx::B2DLineJoin::Bevel:
+            eCairoLineJoin = CAIRO_LINE_JOIN_BEVEL;
+            break;
+        case basegfx::B2DLineJoin::Round:
+            eCairoLineJoin = CAIRO_LINE_JOIN_ROUND;
+            break;
+        case basegfx::B2DLineJoin::NONE:
+        case basegfx::B2DLineJoin::Miter:
+            eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
+            break;
+    }
+
+    // convert miter minimum angle to miter limit
+    double fMiterLimit = 1.0 / sin(fMiterMinimumAngle / 2.0);
+
+    // setup cap attribute
+    cairo_line_cap_t eCairoLineCap(CAIRO_LINE_CAP_BUTT);
+
+    switch (eLineCap)
+    {
+        default: // css::drawing::LineCap_BUTT:
+        {
+            eCairoLineCap = CAIRO_LINE_CAP_BUTT;
+            break;
+        }
+        case css::drawing::LineCap_ROUND:
+        {
+            eCairoLineCap = CAIRO_LINE_CAP_ROUND;
+            break;
+        }
+        case css::drawing::LineCap_SQUARE:
+        {
+            eCairoLineCap = CAIRO_LINE_CAP_SQUARE;
+            break;
+        }
+    }
+
+    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, fLineWidth);
+    cairo_set_miter_limit(cr, fMiterLimit);
+
+    // try to access buffered data
+    std::shared_ptr<SystemDependentData_CairoPath> pSystemDependentData_CairoPath(
+        rPolyLine.getSystemDependentData<SystemDependentData_CairoPath>());
+
+    // MM01 need to do line dashing as fallback stuff here now
+    const double fDotDashLength(
+        nullptr != pStroke ? std::accumulate(pStroke->begin(), pStroke->end(), 0.0) : 0.0);
+    const bool bStrokeUsed(0.0 != fDotDashLength);
+    assert(!bStrokeUsed || (bStrokeUsed && pStroke));
+
+    // MM01 decide if to stroke directly
+    static bool bDoDirectCairoStroke(true);
+
+    // MM01 activate to stroke directly
+    if (bDoDirectCairoStroke && bStrokeUsed)
+    {
+        cairo_set_dash(cr, pStroke->data(), pStroke->size(), 0.0);
+    }
+
+    if (!bDoDirectCairoStroke && pSystemDependentData_CairoPath)
+    {
+        // MM01 - check on stroke change. Used against not used, or if both used,
+        // equal or different?
+        const bool bStrokeWasUsed(!pSystemDependentData_CairoPath->getStroke().empty());
+
+        if (bStrokeWasUsed != bStrokeUsed
+            || (bStrokeUsed && *pStroke != pSystemDependentData_CairoPath->getStroke()))
+        {
+            // data invalid, forget
+            pSystemDependentData_CairoPath.reset();
+        }
+    }
+
+    // check for basegfx::B2DLineJoin::NONE to react accordingly
+    const bool bNoJoin(
+        (basegfx::B2DLineJoin::NONE == eLineJoin && basegfx::fTools::more(fLineWidth, 0.0)));
+
+    if (pSystemDependentData_CairoPath)
+    {
+        // check data validity
+        if (nullptr == pSystemDependentData_CairoPath->getCairoPath()
+            || pSystemDependentData_CairoPath->getNoJoin() != bNoJoin
+            || pSystemDependentData_CairoPath->getAntiAlias() != bAntiAlias
+            || bPixelSnapHairline /*tdf#124700*/)
+        {
+            // data invalid, forget
+            pSystemDependentData_CairoPath.reset();
+        }
+    }
+
+    if (pSystemDependentData_CairoPath)
+    {
+        // re-use data
+        cairo_append_path(cr, pSystemDependentData_CairoPath->getCairoPath());
+    }
+    else
+    {
+        // create data
+        size_t nSizeMeasure(0);
+
+        // MM01 need to do line dashing as fallback stuff here now
+        basegfx::B2DPolyPolygon aPolyPolygonLine;
+
+        if (!bDoDirectCairoStroke && bStrokeUsed)
+        {
+            // apply LineStyle
+            basegfx::utils::applyLineDashing(rPolyLine, // source
+                                             *pStroke, // pattern
+                                             &aPolyPolygonLine, // target for lines
+                                             nullptr, // target for gaps
+                                             fDotDashLength); // full length if available
+        }
+        else
+        {
+            // no line dashing or direct stroke, just copy
+            aPolyPolygonLine.append(rPolyLine);
+        }
+
+        // MM01 checked/verified for Cairo
+        for (sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++)
+        {
+            const basegfx::B2DPolygon aPolyLine(aPolyPolygonLine.getB2DPolygon(a));
+
+            if (!bNoJoin)
+            {
+                // PixelOffset now reflected in linear transformation used
+                nSizeMeasure
+                    += AddPolygonToPath(cr, aPolyLine,
+                                        rObjectToDevice, // ObjectToDevice *without* LineDraw-Offset
+                                        !bAntiAlias, bPixelSnapHairline);
+            }
+            else
+            {
+                const sal_uInt32 nPointCount(aPolyLine.count());
+                const sal_uInt32 nEdgeCount(aPolyLine.isClosed() ? nPointCount : nPointCount - 1);
+                basegfx::B2DPolygon aEdge;
+
+                aEdge.append(aPolyLine.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, aPolyLine.getB2DPoint(nNextIndex));
+                    aEdge.setNextControlPoint(0, aPolyLine.getNextControlPoint(i));
+                    aEdge.setPrevControlPoint(1, aPolyLine.getPrevControlPoint(nNextIndex));
+
+                    // PixelOffset now reflected in linear transformation used
+                    nSizeMeasure += AddPolygonToPath(
+                        cr, aEdge,
+                        rObjectToDevice, // ObjectToDevice *without* LineDraw-Offset
+                        !bAntiAlias, bPixelSnapHairline);
+
+                    // prepare next step
+                    aEdge.setB2DPoint(0, aEdge.getB2DPoint(1));
+                }
+            }
+        }
+
+        // copy and add to buffering mechanism
+        if (!bPixelSnapHairline /*tdf#124700*/)
+        {
+            pSystemDependentData_CairoPath
+                = rPolyLine.addOrReplaceSystemDependentData<SystemDependentData_CairoPath>(
+                    ImplGetSystemDependentDataManager(), nSizeMeasure, cr, bNoJoin, bAntiAlias,
+                    pStroke);
+        }
+    }
+
+    // extract extents
+    if (pExtents)
+    {
+        *pExtents = getClippedStrokeDamage(cr);
+        // transform also extents (ranges) of damage so they can be correctly redrawn
+        pExtents->transform(aDamageMatrix);
+    }
+
+    // draw and consume
+    cairo_stroke(cr);
+
+    return true;
+}
+
+void add_polygon_path(cairo_t* cr, const basegfx::B2DPolyPolygon& rPolyPolygon,
+                      const basegfx::B2DHomMatrix& rObjectToDevice, bool bPixelSnap)
+{
+    // try to access buffered data
+    std::shared_ptr<SystemDependentData_CairoPath> pSystemDependentData_CairoPath(
+        rPolyPolygon.getSystemDependentData<SystemDependentData_CairoPath>());
+
+    if (pSystemDependentData_CairoPath)
+    {
+        // re-use data
+        cairo_append_path(cr, pSystemDependentData_CairoPath->getCairoPath());
+    }
+    else
+    {
+        // create data
+        size_t nSizeMeasure(0);
+
+        for (const auto& rPoly : rPolyPolygon)
+        {
+            // PixelOffset used: Was dependent of 'm_aCairoCommon.m_aLineColor != SALCOLOR_NONE'
+            // Adapt setupPolyPolygon-users to set a linear transformation to achieve PixelOffset
+            nSizeMeasure += AddPolygonToPath(cr, rPoly, rObjectToDevice, bPixelSnap, false);
+        }
+
+        // copy and add to buffering mechanism
+        // for decisions how/what to buffer, see Note in WinSalGraphicsImpl::drawPolyPolygon
+        pSystemDependentData_CairoPath
+            = rPolyPolygon.addOrReplaceSystemDependentData<SystemDependentData_CairoPath>(
+                ImplGetSystemDependentDataManager(), nSizeMeasure, cr, false, false, nullptr);
+    }
+}
+
+cairo_t* CairoCommon::getCairoContext(bool bXorModeAllowed, bool bAntiAlias) const
+{
+    cairo_t* cr;
+    if (m_ePaintMode == PaintMode::Xor && bXorModeAllowed)
+        cr = createTmpCompatibleCairoContext();
+    else
+        cr = cairo_create(m_pSurface);
+    cairo_set_line_width(cr, 1);
+    cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
+    cairo_set_antialias(cr, bAntiAlias ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE);
+    cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
+
+    // ensure no linear transformation and no PathInfo in local cairo_path_t
+    cairo_identity_matrix(cr);
+    cairo_new_path(cr);
+
+    return cr;
+}
+
+cairo_t* CairoCommon::createTmpCompatibleCairoContext() const
+{
+#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0)
+    cairo_surface_t* target = cairo_surface_create_similar_image(
+        m_pSurface,
+#else
+    cairo_surface_t* target = cairo_image_surface_create(
+#endif
+        CAIRO_FORMAT_ARGB32, m_aFrameSize.getX() * m_fScale, m_aFrameSize.getY() * m_fScale);
+
+    dl_cairo_surface_set_device_scale(target, m_fScale, m_fScale);
+
+    return cairo_create(target);
+}
+
+void CairoCommon::applyColor(cairo_t* cr, Color aColor, double fTransparency)
+{
+    if (cairo_surface_get_content(m_pSurface) == CAIRO_CONTENT_COLOR_ALPHA)
+    {
+        cairo_set_source_rgba(cr, aColor.GetRed() / 255.0, aColor.GetGreen() / 255.0,
+                              aColor.GetBlue() / 255.0, 1.0 - fTransparency);
+    }
+    else
+    {
+        double fSet = aColor == COL_BLACK ? 1.0 : 0.0;
+        cairo_set_source_rgba(cr, 1, 1, 1, fSet);
+        cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
+    }
+}
+
+void CairoCommon::releaseCairoContext(cairo_t* cr, bool bXorModeAllowed,
+                                      const basegfx::B2DRange& rExtents) const
+{
+    const bool bXoring = (m_ePaintMode == PaintMode::Xor && bXorModeAllowed);
+
+    if (rExtents.isEmpty())
+    {
+        //nothing changed, return early
+        if (bXoring)
+        {
+            cairo_surface_t* surface = cairo_get_target(cr);
+            cairo_surface_destroy(surface);
+        }
+        cairo_destroy(cr);
+        return;
+    }
+
+    basegfx::B2IRange aIntExtents(basegfx::unotools::b2ISurroundingRangeFromB2DRange(rExtents));
+    sal_Int32 nExtentsLeft(aIntExtents.getMinX()), nExtentsTop(aIntExtents.getMinY());
+    sal_Int32 nExtentsRight(aIntExtents.getMaxX()), nExtentsBottom(aIntExtents.getMaxY());
+    sal_Int32 nWidth = m_aFrameSize.getX();
+    sal_Int32 nHeight = m_aFrameSize.getY();
+    nExtentsLeft = std::max<sal_Int32>(nExtentsLeft, 0);
+    nExtentsTop = std::max<sal_Int32>(nExtentsTop, 0);
+    nExtentsRight = std::min<sal_Int32>(nExtentsRight, nWidth);
+    nExtentsBottom = std::min<sal_Int32>(nExtentsBottom, nHeight);
+
+    cairo_surface_t* surface = cairo_get_target(cr);
+    cairo_surface_flush(surface);
+
+    //For the most part we avoid the use of XOR these days, but there
+    //are some edge cases where legacy stuff still supports it, so
+    //emulate it (slowly) here.
+    if (bXoring)
+    {
+        cairo_surface_t* target_surface = m_pSurface;
+        if (cairo_surface_get_type(target_surface) != CAIRO_SURFACE_TYPE_IMAGE)
+        {
+            //in the unlikely case we can't use m_pSurface directly, copy contents
+            //to another temp image surface
+            cairo_t* copycr = createTmpCompatibleCairoContext();
+            cairo_rectangle(copycr, nExtentsLeft, nExtentsTop, nExtentsRight - nExtentsLeft,
+                            nExtentsBottom - nExtentsTop);
+            cairo_set_source_surface(copycr, m_pSurface, 0, 0);
+            cairo_paint(copycr);
+            target_surface = cairo_get_target(copycr);
+            cairo_destroy(copycr);
+        }
+
+        cairo_surface_flush(target_surface);
+        unsigned char* target_surface_data = cairo_image_surface_get_data(target_surface);
+        unsigned char* xor_surface_data = cairo_image_surface_get_data(surface);
+
+        cairo_format_t nFormat = cairo_image_surface_get_format(target_surface);
+        assert(nFormat == CAIRO_FORMAT_ARGB32
+               && "need to implement CAIRO_FORMAT_A1 after all here");
+        sal_Int32 nStride = cairo_format_stride_for_width(nFormat, nWidth * m_fScale);
+        sal_Int32 nUnscaledExtentsLeft = nExtentsLeft * m_fScale;
+        sal_Int32 nUnscaledExtentsRight = nExtentsRight * m_fScale;
+        sal_Int32 nUnscaledExtentsTop = nExtentsTop * m_fScale;
+        sal_Int32 nUnscaledExtentsBottom = nExtentsBottom * m_fScale;
+
+        // not sure why this happens
+        int target_surface_width = cairo_image_surface_get_width(target_surface);
+        if (nUnscaledExtentsLeft > target_surface_width)
+            nUnscaledExtentsLeft = target_surface_width;
+        if (nUnscaledExtentsRight > target_surface_width)
+            nUnscaledExtentsRight = target_surface_width;
+        int target_surface_height = cairo_image_surface_get_height(target_surface);
+        if (nUnscaledExtentsTop > target_surface_height)
+            nUnscaledExtentsTop = target_surface_height;
+        if (nUnscaledExtentsBottom > target_surface_height)
+            nUnscaledExtentsBottom = target_surface_height;
+
+        vcl::bitmap::lookup_table const& unpremultiply_table
+            = vcl::bitmap::get_unpremultiply_table();
+        vcl::bitmap::lookup_table const& premultiply_table = vcl::bitmap::get_premultiply_table();
+        for (sal_Int32 y = nUnscaledExtentsTop; y < nUnscaledExtentsBottom; ++y)
+        {
+            unsigned char* true_row = target_surface_data + (nStride * y);
+            unsigned char* xor_row = xor_surface_data + (nStride * y);
+            unsigned char* true_data = true_row + (nUnscaledExtentsLeft * 4);
+            unsigned char* xor_data = xor_row + (nUnscaledExtentsLeft * 4);
+            for (sal_Int32 x = nUnscaledExtentsLeft; x < nUnscaledExtentsRight; ++x)
+            {
+                sal_uInt8 a = true_data[SVP_CAIRO_ALPHA];
+                sal_uInt8 xor_a = xor_data[SVP_CAIRO_ALPHA];
+                sal_uInt8 b = unpremultiply_table[a][true_data[SVP_CAIRO_BLUE]]
+                              ^ unpremultiply_table[xor_a][xor_data[SVP_CAIRO_BLUE]];
+                sal_uInt8 g = unpremultiply_table[a][true_data[SVP_CAIRO_GREEN]]
+                              ^ unpremultiply_table[xor_a][xor_data[SVP_CAIRO_GREEN]];
+                sal_uInt8 r = unpremultiply_table[a][true_data[SVP_CAIRO_RED]]
+                              ^ unpremultiply_table[xor_a][xor_data[SVP_CAIRO_RED]];
+                true_data[SVP_CAIRO_BLUE] = premultiply_table[a][b];
+                true_data[SVP_CAIRO_GREEN] = premultiply_table[a][g];
+                true_data[SVP_CAIRO_RED] = premultiply_table[a][r];
+                true_data += 4;
+                xor_data += 4;
+            }
+        }
+        cairo_surface_mark_dirty(target_surface);
+
+        if (target_surface != getSurface())
+        {
+            cairo_t* copycr = cairo_create(m_pSurface);
+            //unlikely case we couldn't use m_pSurface directly,
+            //copy contents back from image surface
+            cairo_rectangle(copycr, nExtentsLeft, nExtentsTop, nExtentsRight - nExtentsLeft,
+                            nExtentsBottom - nExtentsTop);
+            cairo_set_source_surface(copycr, target_surface, 0, 0);
+            cairo_paint(copycr);
+            cairo_destroy(copycr);
+            cairo_surface_destroy(target_surface);
+        }
+
+        cairo_surface_destroy(surface);
+    }
+
+    cairo_destroy(cr); // unref
+
+    DamageHandler* pDamage
+        = static_cast<DamageHandler*>(cairo_surface_get_user_data(m_pSurface, getDamageKey()));
+
+    if (pDamage)
+    {
+        pDamage->damaged(pDamage->handle, nExtentsLeft, nExtentsTop, nExtentsRight - nExtentsLeft,
+                         nExtentsBottom - nExtentsTop);
+    }
+}
+
+void CairoCommon::clipRegion(cairo_t* cr, const vcl::Region& rClipRegion)
+{
+    RectangleVector aRectangles;
+    if (!rClipRegion.IsEmpty())
+    {
+        rClipRegion.GetRegionRectangles(aRectangles);
+    }
+    if (!aRectangles.empty())
+    {
+        for (auto const& rectangle : aRectangles)
+        {
+            cairo_rectangle(cr, rectangle.Left(), rectangle.Top(), rectangle.GetWidth(),
+                            rectangle.GetHeight());
+        }
+        cairo_clip(cr);
+    }
+}
+
+void CairoCommon::clipRegion(cairo_t* cr) { CairoCommon::clipRegion(cr, m_aClipRegion); }
+
+cairo_user_data_key_t* CairoCommon::getDamageKey()
+{
+    static cairo_user_data_key_t aDamageKey;
+    return &aDamageKey;
+}
+
+static basegfx::B2DRange renderWithOperator(cairo_t* cr, const SalTwoRect& rTR,
+                                            cairo_surface_t* source,
+                                            cairo_operator_t eOperator = CAIRO_OPERATOR_SOURCE)
+{
+    cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight);
+
+    basegfx::B2DRange extents = getClippedFillDamage(cr);
+
+    cairo_clip(cr);
+
+    cairo_translate(cr, rTR.mnDestX, rTR.mnDestY);
+    double fXScale = 1.0f;
+    double fYScale = 1.0f;
+    if (rTR.mnSrcWidth != 0 && rTR.mnSrcHeight != 0)
+    {
+        fXScale = static_cast<double>(rTR.mnDestWidth) / rTR.mnSrcWidth;
+        fYScale = static_cast<double>(rTR.mnDestHeight) / rTR.mnSrcHeight;
+        cairo_scale(cr, fXScale, fYScale);
+    }
+
+    cairo_save(cr);
+    cairo_set_source_surface(cr, source, -rTR.mnSrcX, -rTR.mnSrcY);
+    if ((fXScale != 1.0 && rTR.mnSrcWidth == 1) || (fYScale != 1.0 && rTR.mnSrcHeight == 1))
+    {
+        cairo_pattern_t* sourcepattern = cairo_get_source(cr);
+        cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_REPEAT);
+        cairo_pattern_set_filter(sourcepattern, CAIRO_FILTER_NEAREST);
+    }
+    cairo_set_operator(cr, eOperator);
+    cairo_paint(cr);
+    cairo_restore(cr);
+
+    return extents;
+}
+
+basegfx::B2DRange CairoCommon::renderSource(cairo_t* cr, const SalTwoRect& rTR,
+                                            cairo_surface_t* source)
+{
+    return renderWithOperator(cr, rTR, source, CAIRO_OPERATOR_SOURCE);
+}
+
+void CairoCommon::copyWithOperator(const SalTwoRect& rTR, cairo_surface_t* source,
+                                   cairo_operator_t eOp, bool bAntiAlias)
+{
+    cairo_t* cr = getCairoContext(false, bAntiAlias);
+    clipRegion(cr);
+
+    basegfx::B2DRange extents = renderWithOperator(cr, rTR, source, eOp);
+
+    releaseCairoContext(cr, false, extents);
+}
+
+void CairoCommon::copySource(const SalTwoRect& rTR, cairo_surface_t* source, bool bAntiAlias)
+{
+    copyWithOperator(rTR, source, CAIRO_OPERATOR_SOURCE, bAntiAlias);
+}
+
+void CairoCommon::copyBitsCairo(const SalTwoRect& rTR, cairo_surface_t* pSourceSurface,
+                                bool bAntiAlias)
+{
+    SalTwoRect aTR(rTR);
+
+    cairo_surface_t* pCopy = nullptr;
+
+    if (pSourceSurface == getSurface())
+    {
+        //self copy is a problem, so dup source in that case
+        pCopy
+            = cairo_surface_create_similar(pSourceSurface, cairo_surface_get_content(getSurface()),
+                                           aTR.mnSrcWidth * m_fScale, aTR.mnSrcHeight * m_fScale);
+        dl_cairo_surface_set_device_scale(pCopy, m_fScale, m_fScale);
+        cairo_t* cr = cairo_create(pCopy);
+        cairo_set_source_surface(cr, pSourceSurface, -aTR.mnSrcX, -aTR.mnSrcY);
+        cairo_rectangle(cr, 0, 0, aTR.mnSrcWidth, aTR.mnSrcHeight);
+        cairo_fill(cr);
+        cairo_destroy(cr);
+
+        pSourceSurface = pCopy;
+
+        aTR.mnSrcX = 0;
+        aTR.mnSrcY = 0;
+    }
+
+    copySource(aTR, pSourceSurface, bAntiAlias);
+
+    if (pCopy)
+        cairo_surface_destroy(pCopy);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/headless/SvpGraphicsBackend.cxx b/vcl/headless/SvpGraphicsBackend.cxx
new file mode 100644
index 000000000000..6908a0af587d
--- /dev/null
+++ b/vcl/headless/SvpGraphicsBackend.cxx
@@ -0,0 +1,483 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this file
+ *   except in compliance with the License. You may obtain a copy of
+ *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <headless/SvpGraphicsBackend.hxx>
+
+#include <sal/log.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+
+SvpGraphicsBackend::SvpGraphicsBackend(CairoCommon& rCairoCommon)
+    : m_rCairoCommon(rCairoCommon)
+{
+}
+
+void SvpGraphicsBackend::Init() {}
+
+void SvpGraphicsBackend::freeResources() {}
+
+bool SvpGraphicsBackend::setClipRegion(const vcl::Region& i_rClip)
+{
+    m_rCairoCommon.m_aClipRegion = i_rClip;
+    return true;
+}
+
+void SvpGraphicsBackend::ResetClipRegion() { m_rCairoCommon.m_aClipRegion.SetNull(); }
+
+sal_uInt16 SvpGraphicsBackend::GetBitCount() const
+{
+    if (cairo_surface_get_content(m_rCairoCommon.getSurface()) != CAIRO_CONTENT_COLOR_ALPHA)
+        return 1;
+    return 32;
+}
+
+tools::Long SvpGraphicsBackend::GetGraphicsWidth() const
+{
+    return m_rCairoCommon.getSurface() ? m_rCairoCommon.m_aFrameSize.getX() : 0;
+}
+
+void SvpGraphicsBackend::SetLineColor() { m_rCairoCommon.m_aLineColor = SALCOLOR_NONE; }
+
+void SvpGraphicsBackend::SetLineColor(Color nColor) { m_rCairoCommon.m_aLineColor = nColor; }
+
+void SvpGraphicsBackend::SetFillColor() { m_rCairoCommon.m_aFillColor = SALCOLOR_NONE; }
+
+void SvpGraphicsBackend::SetFillColor(Color nColor) { m_rCairoCommon.m_aFillColor = nColor; }
+
+void SvpGraphicsBackend::SetXORMode(bool bSet, bool /*bInvertOnly*/)
+{
+    m_rCairoCommon.m_ePaintMode = bSet ? PaintMode::Xor : PaintMode::Over;
+}
+
+void SvpGraphicsBackend::SetROPLineColor(SalROPColor nROPColor)
+{
+    switch (nROPColor)
+    {
+        case SalROPColor::N0:
+            m_rCairoCommon.m_aLineColor = Color(0, 0, 0);
+            break;
+        case SalROPColor::N1:
+            m_rCairoCommon.m_aLineColor = Color(0xff, 0xff, 0xff);
+            break;
+        case SalROPColor::Invert:
+            m_rCairoCommon.m_aLineColor = Color(0xff, 0xff, 0xff);
+            break;
+    }
+}
+
+void SvpGraphicsBackend::SetROPFillColor(SalROPColor nROPColor)
+{
+    switch (nROPColor)
+    {
+        case SalROPColor::N0:
+            m_rCairoCommon.m_aFillColor = Color(0, 0, 0);
+            break;
+        case SalROPColor::N1:
+            m_rCairoCommon.m_aFillColor = Color(0xff, 0xff, 0xff);
+            break;
+        case SalROPColor::Invert:
+            m_rCairoCommon.m_aFillColor = Color(0xff, 0xff, 0xff);
+            break;
+    }
+}
+
+void SvpGraphicsBackend::drawPixel(tools::Long nX, tools::Long nY)
+{
+    if (m_rCairoCommon.m_aLineColor != SALCOLOR_NONE)
+    {
+        drawPixel(nX, nY, m_rCairoCommon.m_aLineColor);
+    }
+}
+
+void SvpGraphicsBackend::drawPixel(tools::Long nX, tools::Long nY, Color aColor)
+{
+    cairo_t* cr = m_rCairoCommon.getCairoContext(true, getAntiAlias());
+    m_rCairoCommon.clipRegion(cr);
+
+    cairo_rectangle(cr, nX, nY, 1, 1);
+    m_rCairoCommon.applyColor(cr, aColor, 0.0);
+    cairo_fill(cr);
+
+    basegfx::B2DRange extents = getClippedFillDamage(cr);
+    m_rCairoCommon.releaseCairoContext(cr, true, extents);
+}
+
+void SvpGraphicsBackend::drawLine(tools::Long nX1, tools::Long nY1, tools::Long nX2,
+                                  tools::Long nY2)
+{
+    basegfx::B2DPolygon aPoly;
+
+    // PixelOffset used: To not mix with possible PixelSnap, cannot do
+    // directly on coordinates as tried before - despite being already 'snapped'
+    // due to being integer. If it would be directly added here, it would be
+    // 'snapped' again when !getAntiAlias(), losing the (0.5, 0.5) offset
+    aPoly.append(basegfx::B2DPoint(nX1, nY1));
+    aPoly.append(basegfx::B2DPoint(nX2, nY2));
+
+    cairo_t* cr = m_rCairoCommon.getCairoContext(false, getAntiAlias());
+    m_rCairoCommon.clipRegion(cr);
+
+    // PixelOffset used: Set PixelOffset as linear transformation
+    cairo_matrix_t aMatrix;
+    cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
+    cairo_set_matrix(cr, &aMatrix);
+
+    AddPolygonToPath(cr, aPoly, basegfx::B2DHomMatrix(), !getAntiAlias(), false);
+
+    m_rCairoCommon.applyColor(cr, m_rCairoCommon.m_aLineColor);
+
+    basegfx::B2DRange extents = getClippedStrokeDamage(cr);
+    extents.transform(basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5));
+
+    cairo_stroke(cr);
+
+    m_rCairoCommon.releaseCairoContext(cr, false, extents);
+}
+
+void SvpGraphicsBackend::drawRect(tools::Long nX, tools::Long nY, tools::Long nWidth,
+                                  tools::Long nHeight)
+{
+    // because of the -1 hack we have to do fill and draw separately
+    Color aOrigFillColor = m_rCairoCommon.m_aFillColor;
+    Color aOrigLineColor = m_rCairoCommon.m_aLineColor;
+    m_rCairoCommon.m_aFillColor = SALCOLOR_NONE;
+    m_rCairoCommon.m_aLineColor = SALCOLOR_NONE;
+
+    if (aOrigFillColor != SALCOLOR_NONE)
+    {
+        basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(
+            basegfx::B2DRectangle(nX, nY, nX + nWidth, nY + nHeight));
+        m_rCairoCommon.m_aFillColor = aOrigFillColor;
+
+        drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aRect), 0.0);
+
+        m_rCairoCommon.m_aFillColor = SALCOLOR_NONE;
+    }
+
+    if (aOrigLineColor != SALCOLOR_NONE)
+    {
+        // need same -1 hack as X11SalGraphicsImpl::drawRect
+        basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(
+            basegfx::B2DRectangle(nX, nY, nX + nWidth - 1, nY + nHeight - 1));
+        m_rCairoCommon.m_aLineColor = aOrigLineColor;
+
+        drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aRect), 0.0);
+
+        m_rCairoCommon.m_aLineColor = SALCOLOR_NONE;
+    }
+
+    m_rCairoCommon.m_aFillColor = aOrigFillColor;
+    m_rCairoCommon.m_aLineColor = aOrigLineColor;
+}
+
+void SvpGraphicsBackend::drawPolyLine(sal_uInt32 nPoints, const Point* pPtAry)
+{
+    basegfx::B2DPolygon aPoly;
+    aPoly.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
+    for (sal_uInt32 i = 1; i < nPoints; ++i)
+        aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
+    aPoly.setClosed(false);
+
+    drawPolyLine(basegfx::B2DHomMatrix(), aPoly, 0.0, 1.0,
+                 nullptr, // MM01
+                 basegfx::B2DLineJoin::Miter, css::drawing::LineCap_BUTT,
+                 basegfx::deg2rad(15.0) /*default*/, false);
+}
+
+void SvpGraphicsBackend::drawPolygon(sal_uInt32 nPoints, const Point* pPtAry)
+{
+    basegfx::B2DPolygon aPoly;
+    aPoly.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
+    for (sal_uInt32 i = 1; i < nPoints; ++i)
+        aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
+
+    drawPolyPolygon(basegfx::B2DHomMatrix(), basegfx::B2DPolyPolygon(aPoly), 0.0);
+}
+
+void SvpGraphicsBackend::drawPolyPolygon(sal_uInt32 nPoly, const sal_uInt32* pPointCounts,
+                                         const Point** pPtAry)
+{
+    basegfx::B2DPolyPolygon aPolyPoly;
+    for (sal_uInt32 nPolygon = 0; nPolygon < nPoly; ++nPolygon)
+    {
+        sal_uInt32 nPoints = pPointCounts[nPolygon];
+        if (nPoints)
+        {
+            const Point* pPoints = pPtAry[nPolygon];
+            basegfx::B2DPolygon aPoly;
+            aPoly.append(basegfx::B2DPoint(pPoints->getX(), pPoints->getY()), nPoints);
+            for (sal_uInt32 i = 1; i < nPoints; ++i)
+                aPoly.setB2DPoint(i, basegfx::B2DPoint(pPoints[i].getX(), pPoints[i].getY()));
+
+            aPolyPoly.append(aPoly);
+        }
+    }
+
+    drawPolyPolygon(basegfx::B2DHomMatrix(), aPolyPoly, 0.0);
+}
+
+bool SvpGraphicsBackend::drawPolyPolygon(const basegfx::B2DHomMatrix& rObjectToDevice,
+                                         const basegfx::B2DPolyPolygon& rPolyPolygon,
+                                         double fTransparency)
+{
+    const bool bHasFill(m_rCairoCommon.m_aFillColor != SALCOLOR_NONE);
+    const bool bHasLine(m_rCairoCommon.m_aLineColor != SALCOLOR_NONE);
+
+    if (0 == rPolyPolygon.count() || !(bHasFill || bHasLine) || fTransparency < 0.0
+        || fTransparency >= 1.0)
+    {
+        return true;
+    }
+
+    cairo_t* cr = m_rCairoCommon.getCairoContext(true, getAntiAlias());
+    m_rCairoCommon.clipRegion(cr);
+
+    // Set full (Object-to-Device) transformation - if used
+    if (!rObjectToDevice.isIdentity())
+    {
+        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);
+    }
+
+    // To make releaseCairoContext work, use empty extents
+    basegfx::B2DRange extents;
+
+    if (bHasFill)
+    {
+        add_polygon_path(cr, rPolyPolygon, rObjectToDevice, !getAntiAlias());
+
+        m_rCairoCommon.applyColor(cr, m_rCairoCommon.m_aFillColor, fTransparency);
+        // Get FillDamage (will be extended for LineDamage below)
+        extents = getClippedFillDamage(cr);
+
+        cairo_fill(cr);
+    }
+
+    if (bHasLine)
+    {
+        // PixelOffset used: Set PixelOffset as linear transformation
+        cairo_matrix_t aMatrix;
+        cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
+        cairo_set_matrix(cr, &aMatrix);
+
+        add_polygon_path(cr, rPolyPolygon, rObjectToDevice, !getAntiAlias());
+
+        m_rCairoCommon.applyColor(cr, m_rCairoCommon.m_aLineColor, fTransparency);
+
+        // expand with possible StrokeDamage
+        basegfx::B2DRange stroke_extents = getClippedStrokeDamage(cr);
+        stroke_extents.transform(basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5));
+        extents.expand(stroke_extents);
+
+        cairo_stroke(cr);
+    }
+
+    // if transformation has been applied, transform also extents (ranges)
+    // of damage so they can be correctly redrawn
+    extents.transform(rObjectToDevice);
+    m_rCairoCommon.releaseCairoContext(cr, true, extents);
+
+    return true;
+}
+
+bool SvpGraphicsBackend::drawPolyLine(const basegfx::B2DHomMatrix& rObjectToDevice,
+                                      const basegfx::B2DPolygon& rPolyLine, double fTransparency,
+                                      double fLineWidth,
+                                      const std::vector<double>* pStroke, // MM01
+                                      basegfx::B2DLineJoin eLineJoin,
+                                      css::drawing::LineCap eLineCap, double fMiterMinimumAngle,
+                                      bool bPixelSnapHairline)
+{
+    // short circuit if there is nothing to do
+    if (0 == rPolyLine.count() || fTransparency < 0.0 || fTransparency >= 1.0)
+    {
+        return true;
+    }
+
+    // Wrap call to static version 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 = m_rCairoCommon.getCairoContext(false, getAntiAlias());
+    basegfx::B2DRange aExtents;
+    m_rCairoCommon.clipRegion(cr);
+
+    bool bRetval
+        = CairoCommon::drawPolyLine(cr, &aExtents, m_rCairoCommon.m_aLineColor, getAntiAlias(),
+                                    rObjectToDevice, rPolyLine, fTransparency, fLineWidth,
+                                    pStroke, // MM01
+                                    eLineJoin, eLineCap, fMiterMinimumAngle, bPixelSnapHairline);
+
+    m_rCairoCommon.releaseCairoContext(cr, false, aExtents);
+
+    return bRetval;
+}
+
+bool SvpGraphicsBackend::drawPolyLineBezier(sal_uInt32, const Point*, const PolyFlags*)
+{
+    SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolyLineBezier case");
+    return false;
+}
+
+bool SvpGraphicsBackend::drawPolygonBezier(sal_uInt32, const Point*, const PolyFlags*)
+{
+    SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolygonBezier case");
+    return false;
+}
+
+bool SvpGraphicsBackend::drawPolyPolygonBezier(sal_uInt32, const sal_uInt32*, const Point* const*,
+                                               const PolyFlags* const*)
+{
+    SAL_INFO("vcl.gdi", "unsupported SvpSalGraphics::drawPolyPolygonBezier case");
+    return false;
+}
+
+void SvpGraphicsBackend::copyArea(tools::Long nDestX, tools::Long nDestY, tools::Long nSrcX,
+                                  tools::Long nSrcY, tools::Long nSrcWidth, tools::Long nSrcHeight,
+                                  bool /*bWindowInvalidate*/)
+{
+    SalTwoRect aTR(nSrcX, nSrcY, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight);
+
+    cairo_surface_t* source = m_rCairoCommon.getSurface();
+    m_rCairoCommon.copyBitsCairo(aTR, source, getAntiAlias());
+}
+
+void SvpGraphicsBackend::copyBits(const SalTwoRect& rTR, SalGraphics* pSrcGraphics)
+{
+    cairo_surface_t* source = nullptr;
+
+    if (pSrcGraphics)
+    {
+        SvpGraphicsBackend* pSrc = static_cast<SvpGraphicsBackend*>(pSrcGraphics->GetImpl());
+        source = pSrc->m_rCairoCommon.getSurface();
+    }
+    else
+    {
+        source = m_rCairoCommon.getSurface();
+    }
+
+    m_rCairoCommon.copyBitsCairo(rTR, source, getAntiAlias());
+}
+
+void SvpGraphicsBackend::drawBitmap(const SalTwoRect& /*rPosAry*/, const SalBitmap& /*rSalBitmap*/)
+{
+}
+
+void SvpGraphicsBackend::drawBitmap(const SalTwoRect& /*rPosAry*/, const SalBitmap& /*rSalBitmap*/,
+                                    const SalBitmap& /*rMaskBitmap*/)
+{
+}
+
+void SvpGraphicsBackend::drawMask(const SalTwoRect& /*rPosAry*/, const SalBitmap& /*rSalBitmap*/,
+                                  Color /*nMaskColor*/)
+{
+}
+
+std::shared_ptr<SalBitmap> SvpGraphicsBackend::getBitmap(tools::Long /*nX*/, tools::Long /*nY*/,
+                                                         tools::Long /*nWidth*/,
+                                                         tools::Long /*nHeight*/)
+{
+    return std::shared_ptr<SalBitmap>();
+}
+
+Color SvpGraphicsBackend::getPixel(tools::Long /*nX*/, tools::Long /*nY*/) { return Color(); }
+
+void SvpGraphicsBackend::invert(tools::Long /*nX*/, tools::Long /*nY*/, tools::Long /*nWidth*/,
+                                tools::Long /*nHeight*/, SalInvert /*nFlags*/)
+{
+}
+
+void SvpGraphicsBackend::invert(sal_uInt32 /*nPoints*/, const Point* /*pPtAry*/,
+                                SalInvert /*nFlags*/)
+{
+}
+
+bool SvpGraphicsBackend::drawEPS(tools::Long /*nX*/, tools::Long /*nY*/, tools::Long /*nWidth*/,
+                                 tools::Long /*nHeight*/, void* /*pPtr*/, sal_uInt32 /*nSize*/)
+{
+    return false;
+}
+
+bool SvpGraphicsBackend::blendBitmap(const SalTwoRect& /*rPosAry*/, const SalBitmap& /*rBitmap*/)
+{
+    return false;
+}
+
+bool SvpGraphicsBackend::blendAlphaBitmap(const SalTwoRect& /*rPosAry*/,
+                                          const SalBitmap& /*rSrcBitmap*/,
+                                          const SalBitmap& /*rMaskBitmap*/,
+                                          const SalBitmap& /*rAlphaBitmap*/)
+{
+    return false;
+}
+
+bool SvpGraphicsBackend::drawAlphaBitmap(const SalTwoRect&, const SalBitmap& /*rSourceBitmap*/,
+                                         const SalBitmap& /*rAlphaBitmap*/)
+{
+    return false;
+}
+
+bool SvpGraphicsBackend::drawTransformedBitmap(const basegfx::B2DPoint& /*rNull*/,
+                                               const basegfx::B2DPoint& /*rX*/,
+                                               const basegfx::B2DPoint& /*rY*/,
+                                               const SalBitmap& /*rSourceBitmap*/,
+                                               const SalBitmap* /*pAlphaBitmap*/, double /*fAlpha*/)
+{
+    return false;
+}
+
+bool SvpGraphicsBackend::hasFastDrawTransformedBitmap() const { return false; }
+
+bool SvpGraphicsBackend::drawAlphaRect(tools::Long /*nX*/, tools::Long /*nY*/,
+                                       tools::Long /*nWidth*/, tools::Long /*nHeight*/,
+                                       sal_uInt8 /*nTransparency*/)
+{
+    return false;
+}
+
+bool SvpGraphicsBackend::drawGradient(const tools::PolyPolygon& /*rPolygon*/,
+                                      const Gradient& /*rGradient*/)
+{
+    return false;
+}
+
+bool SvpGraphicsBackend::implDrawGradient(basegfx::B2DPolyPolygon const& /*rPolyPolygon*/,
+                                          SalGradient const& /*rGradient*/)
+{
+    return false;
+}
+
+bool SvpGraphicsBackend::supportsOperation(OutDevSupportType eType) const
+{
+    switch (eType)
+    {
+        case OutDevSupportType::TransparentRect:
+        case OutDevSupportType::B2DDraw:
+            return true;
+    }
+    return false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/headless/svpgdi.cxx b/vcl/headless/svpgdi.cxx
index 2a5d1ba350ad..58b72c4afa45 100644
--- a/vcl/headless/svpgdi.cxx
+++ b/vcl/headless/svpgdi.cxx
@@ -32,25 +32,24 @@
 #include <sal/log.hxx>
 #include <tools/helpers.hxx>
 #include <o3tl/safeint.hxx>
-#include <vcl/BitmapTools.hxx>
 #include <vcl/sysdata.hxx>
 #include <vcl/gradient.hxx>
 #include <config_cairo_canvas.h>
 #include <basegfx/numeric/ftools.hxx>
 #include <basegfx/range/b2drange.hxx>
-#include <basegfx/range/b2ibox.hxx>
 #include <basegfx/range/b2irange.hxx>
+#include <basegfx/range/b2ibox.hxx>
 #include <basegfx/polygon/b2dpolypolygon.hxx>
 #include <basegfx/polygon/b2dpolypolygontools.hxx>
 #include <basegfx/polygon/b2dpolygon.hxx>
 #include <basegfx/polygon/b2dpolygontools.hxx>
 #include <basegfx/matrix/b2dhommatrix.hxx>
 #include <basegfx/utils/canvastools.hxx>
-#include <basegfx/utils/systemdependentdata.hxx>
 #include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <vcl/BitmapTools.hxx>
 #include <comphelper/lok.hxx>
 #include <unx/gendata.hxx>
-#include <dlfcn.h>
+
 
 #if ENABLE_CAIRO_CANVAS
 #   if defined CAIRO_VERSION && CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 10, 0)
@@ -58,79 +57,6 @@
 #   endif
 #endif
 
-namespace
-{
-    basegfx::B2DRange getClipBox(cairo_t* cr)
-    {
-        double x1, y1, x2, y2;
-
-        cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
-
-        // support B2DRange::isEmpty()
-        if(0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2)
-        {
-            return basegfx::B2DRange(x1, y1, x2, y2);
-        }
-
-        return basegfx::B2DRange();
-    }
-
-    basegfx::B2DRange getFillDamage(cairo_t* cr)
-    {
-        double x1, y1, x2, y2;
-
-        // this is faster than cairo_fill_extents, at the cost of some overdraw
-        cairo_path_extents(cr, &x1, &y1, &x2, &y2);
-
-        // support B2DRange::isEmpty()
-        if(0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2)
-        {
-            return basegfx::B2DRange(x1, y1, x2, y2);
-        }
-
-        return basegfx::B2DRange();
-    }
-
-    basegfx::B2DRange getClippedFillDamage(cairo_t* cr)
-    {
-        basegfx::B2DRange aDamageRect(getFillDamage(cr));
-        aDamageRect.intersect(getClipBox(cr));
-        return aDamageRect;
-    }
-
-    basegfx::B2DRange getStrokeDamage(cairo_t* cr)
-    {
-        double x1, y1, x2, y2;
-
-        // less accurate, but much faster
-        cairo_path_extents(cr, &x1, &y1, &x2, &y2);
-
-        // support B2DRange::isEmpty()
-        if(0.0 != x1 || 0.0 != y1 || 0.0 != x2 || 0.0 != y2)
-        {
-            return basegfx::B2DRange(x1, y1, x2, y2);
-        }
-
-        return basegfx::B2DRange();
-    }
-
-    basegfx::B2DRange getClippedStrokeDamage(cairo_t* cr)
-    {
-        basegfx::B2DRange aDamageRect(getStrokeDamage(cr));
-        aDamageRect.intersect(getClipBox(cr));
-        return aDamageRect;
-    }
-}
-
-bool SvpSalGraphics::blendBitmap( const SalTwoRect&, const SalBitmap& /*rBitmap*/ )
-{
-    return false;
-}
-
-bool SvpSalGraphics::blendAlphaBitmap( const SalTwoRect&, const SalBitmap&, const SalBitmap&, const SalBitmap& )
-{
-    return false;
-}
 
 namespace
 {
@@ -706,8 +632,8 @@ bool SvpSalGraphics::drawAlphaBitmap( const SalTwoRect& rTR, const SalBitmap& rS
         return false;
     }
 
-    cairo_t* cr = getCairoContext(false);
-    clipRegion(cr);
+    cairo_t* cr = m_aCairoCommon.getCairoContext(false, getAntiAlias());
+    m_aCairoCommon.clipRegion(cr);
 
     cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight);
 
@@ -803,8 +729,8 @@ bool SvpSalGraphics::drawTransformedBitmap(
     }
 
     const Size aSize = rSourceBitmap.GetSize();
-    cairo_t* cr = getCairoContext(false);
-    clipRegion(cr);
+    cairo_t* cr = m_aCairoCommon.getCairoContext(false, getAntiAlias());
+    m_aCairoCommon.clipRegion(cr);
 
     // setup the image transformation
     // using the rNull,rX,rY points as destinations for the (0,0),(0,Width),(Height,0) source points
@@ -833,45 +759,18 @@ bool SvpSalGraphics::drawTransformedBitmap(
     return true;
 }
 
-bool SvpSalGraphics::hasFastDrawTransformedBitmap() const
-{
-    return false;
-}
-
-void SvpSalGraphics::clipRegion(cairo_t* cr, const vcl::Region& rClipRegion)
-{
-    RectangleVector aRectangles;
-    if (!rClipRegion.IsEmpty())
-    {
-        rClipRegion.GetRegionRectangles(aRectangles);
-    }
-    if (!aRectangles.empty())
-    {
-        for (auto const& rectangle : aRectangles)
-        {
-            cairo_rectangle(cr, rectangle.Left(), rectangle.Top(), rectangle.GetWidth(), rectangle.GetHeight());
-        }
-        cairo_clip(cr);
-    }
-}
-
-void SvpSalGraphics::clipRegion(cairo_t* cr)
-{
-    SvpSalGraphics::clipRegion(cr, m_aClipRegion);
-}
-
 bool SvpSalGraphics::drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt8 nTransparency)
 {
-    const bool bHasFill(m_aFillColor != SALCOLOR_NONE);
-    const bool bHasLine(m_aLineColor != SALCOLOR_NONE);
+    const bool bHasFill(m_aCairoCommon.m_aFillColor != SALCOLOR_NONE);
+    const bool bHasLine(m_aCairoCommon.m_aLineColor != SALCOLOR_NONE);
 
     if(!(bHasFill || bHasLine))
     {
         return true;
     }
 
-    cairo_t* cr = getCairoContext(false);
-    clipRegion(cr);
+    cairo_t* cr = m_aCairoCommon.getCairoContext(false, getAntiAlias());
+    m_aCairoCommon.clipRegion(cr);
 
     const double fTransparency = nTransparency * (1.0/100);
 
@@ -882,7 +781,7 @@ bool SvpSalGraphics::drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long n
     {
         cairo_rectangle(cr, nX, nY, nWidth, nHeight);
 
-        applyColor(cr, m_aFillColor, fTransparency);
+        m_aCairoCommon.applyColor(cr, m_aCairoCommon.m_aFillColor, fTransparency);
 
         // set FillDamage
         extents = getClippedFillDamage(cr);
@@ -900,7 +799,7 @@ bool SvpSalGraphics::drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long n
 
         cairo_rectangle(cr, nX, nY, nWidth, nHeight);
 
-        applyColor(cr, m_aLineColor, fTransparency);
+        m_aCairoCommon.applyColor(cr, m_aCairoCommon.m_aLineColor, fTransparency);
 
         // expand with possible StrokeDamage
         basegfx::B2DRange stroke_extents = getClippedStrokeDamage(cr);
@@ -916,12 +815,8 @@ bool SvpSalGraphics::drawAlphaRect(tools::Long nX, tools::Long nY, tools::Long n
 }
 
 SvpSalGraphics::SvpSalGraphics()
-    : m_pSurface(nullptr)
-    , m_fScale(1.0)
-    , m_aLineColor(Color(0x00, 0x00, 0x00))
-    , m_aFillColor(Color(0xFF, 0xFF, 0XFF))
-    , m_ePaintMode(PaintMode::Over)
-    , m_aTextRenderImpl(*this)
+    : m_aTextRenderImpl(*this)
+    , m_pBackend(new SvpGraphicsBackend(m_aCairoCommon))
 {
     bool bLOKActive = comphelper::LibreOfficeKit::isActive();
     if (!initWidgetDrawBackends(bLOKActive))
@@ -938,9 +833,9 @@ SvpSalGraphics::~SvpSalGraphics()
 
 void SvpSalGraphics::setSurface(cairo_surface_t* pSurface, const basegfx::B2IVector& rSize)
 {
-    m_pSurface = pSurface;
-    m_aFrameSize = rSize;
-    dl_cairo_surface_get_device_scale(pSurface, &m_fScale, nullptr);
+    m_aCairoCommon.m_pSurface = pSurface;
+    m_aCairoCommon.m_aFrameSize = rSize;
+    dl_cairo_surface_get_device_scale(pSurface, &m_aCairoCommon.m_fScale, nullptr);
     ResetClipRegion();
 }
 
@@ -949,1282 +844,191 @@ void SvpSalGraphics::GetResolution( sal_Int32& rDPIX, sal_Int32& rDPIY )
     rDPIX = rDPIY = 96;
 }
 
-sal_uInt16 SvpSalGraphics::GetBitCount() const
-{
-    if (cairo_surface_get_content(m_pSurface) != CAIRO_CONTENT_COLOR_ALPHA)
-        return 1;
-    return 32;
-}
-
-tools::Long SvpSalGraphics::GetGraphicsWidth() const
-{
-    return m_pSurface ? m_aFrameSize.getX() : 0;
-}
-
-void SvpSalGraphics::ResetClipRegion()
-{
-    m_aClipRegion.SetNull();
-}
-
-bool SvpSalGraphics::setClipRegion( const vcl::Region& i_rClip )
+bool SvpSalGraphics::drawGradient(const tools::PolyPolygon& rPolyPolygon, const Gradient& rGradient)
 {
-    m_aClipRegion = i_rClip;
-    return true;
-}
+    if (rGradient.GetStyle() != GradientStyle::Linear
+        && rGradient.GetStyle() != GradientStyle::Radial)
+        return false; // unsupported
+    if (rGradient.GetSteps() != 0)
+        return false; // We can't tell cairo how many colors to use in the gradient.
 
-void SvpSalGraphics::SetLineColor()
-{
-    m_aLineColor = SALCOLOR_NONE;
-}
+    cairo_t* cr = m_aCairoCommon.getCairoContext(true, getAntiAlias());
+    m_aCairoCommon.clipRegion(cr);
 
-void SvpSalGraphics::SetLineColor( Color nColor )
-{
-    m_aLineColor = nColor;
-}
+    tools::Rectangle aInputRect(rPolyPolygon.GetBoundRect());
+    if( rPolyPolygon.IsRect())
+    {
+        // Rect->Polygon conversion loses the right and bottom edge, fix that.
+        aInputRect.AdjustRight( 1 );
+        aInputRect.AdjustBottom( 1 );
+        basegfx::B2DHomMatrix rObjectToDevice;
+        AddPolygonToPath(cr, tools::Polygon(aInputRect).getB2DPolygon(), rObjectToDevice, !getAntiAlias(), false);
+    }
+    else
+    {
+        basegfx::B2DPolyPolygon aB2DPolyPolygon(rPolyPolygon.getB2DPolyPolygon());
+        for (auto const & rPolygon : std::as_const(aB2DPolyPolygon))
+        {
+            basegfx::B2DHomMatrix rObjectToDevice;
+            AddPolygonToPath(cr, rPolygon, rObjectToDevice, !getAntiAlias(), false);
+        }
+    }
 
-void SvpSalGraphics::SetFillColor()
-{
-    m_aFillColor = SALCOLOR_NONE;
-}
+    Gradient aGradient(rGradient);
 
-void SvpSalGraphics::SetFillColor( Color nColor )
-{
-    m_aFillColor = nColor;
-}
+    tools::Rectangle aBoundRect;
+    Point aCenter;
 
-void SvpSalGraphics::SetXORMode(bool bSet, bool )
-{
-    m_ePaintMode = bSet ? PaintMode::Xor : PaintMode::Over;
-}
+    aGradient.SetAngle(aGradient.GetAngle() + 2700_deg10);
+    aGradient.GetBoundRect(aInputRect, aBoundRect, aCenter);
+    Color aStartColor = aGradient.GetStartColor();
+    Color aEndColor = aGradient.GetEndColor();
 
-void SvpSalGraphics::SetROPLineColor( SalROPColor nROPColor )
-{
-    switch( nROPColor )
+    cairo_pattern_t* pattern;
+    if (rGradient.GetStyle() == GradientStyle::Linear)
     {
-        case SalROPColor::N0:
-            m_aLineColor = Color(0, 0, 0);
-            break;
-        case SalROPColor::N1:
-            m_aLineColor = Color(0xff, 0xff, 0xff);
-            break;
-        case SalROPColor::Invert:
-            m_aLineColor = Color(0xff, 0xff, 0xff);
-            break;
+        tools::Polygon aPoly(aBoundRect);
+        aPoly.Rotate(aCenter, aGradient.GetAngle() % 3600_deg10);
+        pattern = cairo_pattern_create_linear(aPoly[0].X(), aPoly[0].Y(), aPoly[1].X(), aPoly[1].Y());
     }
-}
-
-void SvpSalGraphics::SetROPFillColor( SalROPColor nROPColor )
-{
-    switch( nROPColor )
+    else
     {
-        case SalROPColor::N0:
-            m_aFillColor = Color(0, 0, 0);
-            break;
-        case SalROPColor::N1:
-            m_aFillColor = Color(0xff, 0xff, 0xff);
-            break;
-        case SalROPColor::Invert:
-            m_aFillColor = Color(0xff, 0xff, 0xff);
-            break;
+        double radius = std::max(aBoundRect.GetWidth() / 2.0, aBoundRect.GetHeight() / 2.0);
+        // Move the center a bit to the top-left (the default VCL algorithm is a bit off-center that way,
+        // cairo is the opposite way).
+        pattern = cairo_pattern_create_radial(aCenter.X() - 0.5, aCenter.Y() - 0.5, 0,
+            aCenter.X() - 0.5, aCenter.Y() - 0.5, radius);
+        std::swap( aStartColor, aEndColor );
     }
-}
 
-void SvpSalGraphics::drawPixel( tools::Long nX, tools::Long nY )
-{
-    if (m_aLineColor != SALCOLOR_NONE)
-    {
-        drawPixel(nX, nY, m_aLineColor);
-    }
-}
+    cairo_pattern_add_color_stop_rgba(pattern, aGradient.GetBorder() / 100.0,
+        aStartColor.GetRed() * aGradient.GetStartIntensity()   / 25500.0,
+        aStartColor.GetGreen() * aGradient.GetStartIntensity() / 25500.0,
+        aStartColor.GetBlue() * aGradient.GetStartIntensity()  / 25500.0,
+        1.0);
 
-void SvpSalGraphics::drawPixel( tools::Long nX, tools::Long nY, Color aColor )
-{
-    cairo_t* cr = getCairoContext(true);
-    clipRegion(cr);
+    cairo_pattern_add_color_stop_rgba(pattern, 1.0,
+        aEndColor.GetRed() * aGradient.GetEndIntensity()   / 25500.0,
+        aEndColor.GetGreen() * aGradient.GetEndIntensity() / 25500.0,
+        aEndColor.GetBlue() * aGradient.GetEndIntensity()  / 25500.0,
+        1.0);
 
-    cairo_rectangle(cr, nX, nY, 1, 1);
-    applyColor(cr, aColor, 0.0);
-    cairo_fill(cr);
+    cairo_set_source(cr, pattern);
+    cairo_pattern_destroy(pattern);
 
     basegfx::B2DRange extents = getClippedFillDamage(cr);
+    cairo_fill_preserve(cr);
+
     releaseCairoContext(cr, true, extents);
+
+    return true;
 }
 
-void SvpSalGraphics::drawRect( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+bool SvpSalGraphics::implDrawGradient(basegfx::B2DPolyPolygon const & rPolyPolygon, SalGradient const & rGradient)
 {
-    // because of the -1 hack we have to do fill and draw separately
-    Color aOrigFillColor = m_aFillColor;
-    Color aOrigLineColor = m_aLineColor;
-    m_aFillColor = SALCOLOR_NONE;
-    m_aLineColor = SALCOLOR_NONE;
+    cairo_t* cr = m_aCairoCommon.getCairoContext(true, getAntiAlias());
+    m_aCairoCommon.clipRegion(cr);
 
-    if (aOrigFillColor != SALCOLOR_NONE)
-    {
-        basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(basegfx::B2DRectangle(nX, nY, nX+nWidth, nY+nHeight));
-        m_aFillColor = aOrigFillColor;
+    basegfx::B2DHomMatrix rObjectToDevice;
 
-        drawPolyPolygon(
-            basegfx::B2DHomMatrix(),
-            basegfx::B2DPolyPolygon(aRect),
-            0.0);
+    for (auto const & rPolygon : rPolyPolygon)
+        AddPolygonToPath(cr, rPolygon, rObjectToDevice, !getAntiAlias(), false);
 
-        m_aFillColor = SALCOLOR_NONE;
-    }
+    cairo_pattern_t* pattern = cairo_pattern_create_linear(rGradient.maPoint1.getX(), rGradient.maPoint1.getY(), rGradient.maPoint2.getX(), rGradient.maPoint2.getY());
 
-    if (aOrigLineColor != SALCOLOR_NONE)
+    for (SalGradientStop const & rStop : rGradient.maStops)
     {
-        // need same -1 hack as X11SalGraphicsImpl::drawRect
-        basegfx::B2DPolygon aRect = basegfx::utils::createPolygonFromRect(basegfx::B2DRectangle( nX, nY, nX+nWidth-1, nY+nHeight-1));
-        m_aLineColor = aOrigLineColor;
-
-        drawPolyPolygon(
-            basegfx::B2DHomMatrix(),
-            basegfx::B2DPolyPolygon(aRect),
-            0.0);
+        double r = rStop.maColor.GetRed() / 255.0;
+        double g = rStop.maColor.GetGreen() / 255.0;
+        double b = rStop.maColor.GetBlue() / 255.0;
+        double a = rStop.maColor.GetAlpha() / 255.0;
+        double offset = rStop.mfOffset;
 
-        m_aLineColor = SALCOLOR_NONE;
+        cairo_pattern_add_color_stop_rgba(pattern, offset, r, g, b, a);
     }
+    cairo_set_source(cr, pattern);
+    cairo_pattern_destroy(pattern);
 
-    m_aFillColor = aOrigFillColor;
-    m_aLineColor = aOrigLineColor;
-}
+    basegfx::B2DRange extents = getClippedFillDamage(cr);
+    cairo_fill_preserve(cr);
 
-void SvpSalGraphics::drawPolyLine(sal_uInt32 nPoints, const Point* pPtAry)
-{
-    basegfx::B2DPolygon aPoly;
-    aPoly.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
-    for (sal_uInt32 i = 1; i < nPoints; ++i)
-        aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
-    aPoly.setClosed(false);
+    releaseCairoContext(cr, true, extents);
 
-    drawPolyLine(
-        basegfx::B2DHomMatrix(),
-        aPoly,
-        0.0,
-        1.0,
-        nullptr, // MM01
-        basegfx::B2DLineJoin::Miter,
-        css::drawing::LineCap_BUTT,
-        basegfx::deg2rad(15.0) /*default*/,
-        false);
+    return true;
 }
 
-void SvpSalGraphics::drawPolygon(sal_uInt32 nPoints, const Point* pPtAry)
+void SvpSalGraphics::drawBitmap(const SalTwoRect& rTR, const SalBitmap& rSourceBitmap)
 {
-    basegfx::B2DPolygon aPoly;
-    aPoly.append(basegfx::B2DPoint(pPtAry->getX(), pPtAry->getY()), nPoints);
-    for (sal_uInt32 i = 1; i < nPoints; ++i)
-        aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].getX(), pPtAry[i].getY()));
-
-    drawPolyPolygon(
-        basegfx::B2DHomMatrix(),
-        basegfx::B2DPolyPolygon(aPoly),
-        0.0);
-}
+    // MM02 try to access buffered BitmapHelper
+    std::shared_ptr<BitmapHelper> aSurface;
+    tryToUseSourceBuffer(rSourceBitmap, aSurface);
+    cairo_surface_t* source = aSurface->getSurface(
+        rTR.mnDestWidth,
+        rTR.mnDestHeight);
 
-void SvpSalGraphics::drawPolyPolygon(sal_uInt32 nPoly,
-                                     const sal_uInt32* pPointCounts,
-                                     const Point**   pPtAry)
-{
-    basegfx::B2DPolyPolygon aPolyPoly;
-    for(sal_uInt32 nPolygon = 0; nPolygon < nPoly; ++nPolygon)
+    if (!source)
     {
-        sal_uInt32 nPoints = pPointCounts[nPolygon];
-        if (nPoints)
-        {
-            const Point* pPoints = pPtAry[nPolygon];
-            basegfx::B2DPolygon aPoly;
-            aPoly.append( basegfx::B2DPoint(pPoints->getX(), pPoints->getY()), nPoints);
-            for (sal_uInt32 i = 1; i < nPoints; ++i)
-                aPoly.setB2DPoint(i, basegfx::B2DPoint( pPoints[i].getX(), pPoints[i].getY()));
-
-            aPolyPoly.append(aPoly);
-        }
+        SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case");
+        return;
     }
 
-    drawPolyPolygon(
-        basegfx::B2DHomMatrix(),
-        aPolyPoly,
-        0.0);
+#if 0 // LO code is not yet bitmap32-ready.
+      // if m_bSupportsBitmap32 becomes trye for Svp revisit this
+    m_aCairoCommon.copyWithOperator(rTR, source, CAIRO_OPERATOR_OVER, getAntiAlias());
+#else
+    m_aCairoCommon.copyWithOperator(rTR, source, CAIRO_OPERATOR_SOURCE, getAntiAlias());
+#endif
 }
 
-static basegfx::B2DPoint impPixelSnap(
-    const basegfx::B2DPolygon& rPolygon,
-    const basegfx::B2DHomMatrix& rObjectToDevice,
-    basegfx::B2DHomMatrix& rObjectToDeviceInv,
-    sal_uInt32 nIndex)
+void SvpSalGraphics::drawBitmap(const SalTwoRect& rTR, const BitmapBuffer* pBuffer, cairo_operator_t eOp)
 {
-    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);
+    cairo_surface_t* source = createCairoSurface( pBuffer );
+    m_aCairoCommon.copyWithOperator(rTR, source, eOp, getAntiAlias());
+    cairo_surface_destroy(source);
 }
 
-// Remove bClosePath: Checked that the already used mechanism for Win using
-// Gdiplus already relies on rPolygon.isClosed(), so should be safe to replace
-// this.
-// For PixelSnap we need the ObjectToDevice transformation here now. This is a
-// special case relative to the also executed LineDraw-Offset of (0.5, 0.5) in
-// DeviceCoordinates: The LineDraw-Offset is applied *after* the snap, so we
-// need the ObjectToDevice transformation *without* that offset here to do the
-// same. The LineDraw-Offset will be applied by the callers using a linear
-// transformation for Cairo now
-// For support of PixelSnapHairline we also need the ObjectToDevice transformation
-// and a method (same as in gdiimpl.cxx for Win and Gdiplus). This is needed e.g.
-// for Chart-content visualization. CAUTION: It's not the same as PixelSnap (!)
-// tdf#129845 add reply value to allow counting a point/byte/size measurement to
-// be included
-static size_t AddPolygonToPath(
-    cairo_t* cr,
-    const basegfx::B2DPolygon& rPolygon,
-    const basegfx::B2DHomMatrix& rObjectToDevice,
-    bool bPixelSnap,
-    bool bPixelSnapHairline)
+void SvpSalGraphics::drawBitmap( const SalTwoRect& rTR,
+                                 const SalBitmap& rSourceBitmap,
+                                 const SalBitmap& rTransparentBitmap )
 {
-    // short circuit if there is nothing to do
-    const sal_uInt32 nPointCount(rPolygon.count());
-    size_t nSizeMeasure(0);
+    drawAlphaBitmap(rTR, rSourceBitmap, rTransparentBitmap);
+}
 
-    if(0 == nPointCount)
+void SvpSalGraphics::drawMask( const SalTwoRect& rTR,
+                               const SalBitmap& rSalBitmap,
+                               Color nMaskColor )
+{
+    /** creates an image from the given rectangle, replacing all black pixels
+     *  with nMaskColor and make all other full transparent */
+    // MM02 here decided *against* using buffered BitmapHelper
+    // because the data gets somehow 'unmuliplied'. This may also be
+    // done just once, but I am not sure if this is safe to do.
+    // So for now dispense re-using data here.
+    BitmapHelper aSurface(rSalBitmap, true); // The mask is argb32
+    if (!aSurface.getSurface())
     {
-        return nSizeMeasure;
+        SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawMask case");
+        return;
     }
-
-    const bool bHasCurves(rPolygon.areControlPointsUsed());
-    const bool bClosePath(rPolygon.isClosed());
-    const bool bObjectToDeviceUsed(!rObjectToDevice.isIdentity());
-    basegfx::B2DHomMatrix aObjectToDeviceInv;
-    basegfx::B2DPoint aLast;
-
-    for( sal_uInt32 nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++ )
+    sal_Int32 nStride;
+    unsigned char *mask_data = aSurface.getBits(nStride);
+    vcl::bitmap::lookup_table const & unpremultiply_table = vcl::bitmap::get_unpremultiply_table();
+    for (tools::Long y = rTR.mnSrcY ; y < rTR.mnSrcY + rTR.mnSrcHeight; ++y)
     {
-        int nClosedIdx = nPointIdx;
-        if( nPointIdx >= nPointCount )
+        unsigned char *row = mask_data + (nStride*y);
+        unsigned char *data = row + (rTR.mnSrcX * 4);
+        for (tools::Long x = rTR.mnSrcX; x < rTR.mnSrcX + rTR.mnSrcWidth; ++x)
         {
-            // prepare to close last curve segment if needed
-            if( bClosePath && (nPointIdx == nPointCount) )
+            sal_uInt8 a = data[SVP_CAIRO_ALPHA];
+            sal_uInt8 b = unpremultiply_table[a][data[SVP_CAIRO_BLUE]];
+            sal_uInt8 g = unpremultiply_table[a][data[SVP_CAIRO_GREEN]];
+            sal_uInt8 r = unpremultiply_table[a][data[SVP_CAIRO_RED]];
+            if (r == 0 && g == 0 && b == 0)
             {
-                nClosedIdx = 0;
-            }
-            else
-            {
-                break;
-            }
-        }
-
-        basegfx::B2DPoint aPoint(rPolygon.getB2DPoint(nClosedIdx));
-
-        if(bPixelSnap)
-        {
-            // snap device coordinates to full pixels
-            if(bObjectToDeviceUsed)
-            {
-                // go to DeviceCoordinates
-                aPoint *= rObjectToDevice;
-            }
-
-            // snap by rounding
-            aPoint.setX( basegfx::fround( aPoint.getX() ) );
-            aPoint.setY( basegfx::fround( aPoint.getY() ) );
-
-            if(bObjectToDeviceUsed)
-            {
-                if(aObjectToDeviceInv.isIdentity())
-                {
-                    aObjectToDeviceInv = rObjectToDevice;
-                    aObjectToDeviceInv.invert();
-                }
-
-                // go back to ObjectCoordinates
-                aPoint *= aObjectToDeviceInv;
-            }
-        }
-
-        if(bPixelSnapHairline)
-        {
-            // snap horizontal and vertical lines (mainly used in Chart for
-            // 'nicer' AAing)
-            aPoint = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nClosedIdx);
-        }
-
-        if( !nPointIdx )
-        {
-            // first point => just move there
-            cairo_move_to(cr, aPoint.getX(), aPoint.getY());
-            aLast = aPoint;
-            continue;
-        }
-
-        bool bPendingCurve(false);
-
-        if( bHasCurves )
-        {
-            bPendingCurve = rPolygon.isNextControlPointUsed( nPrevIdx );
-            bPendingCurve |= rPolygon.isPrevControlPointUsed( nClosedIdx );
-        }
-
-        if( !bPendingCurve )    // line segment
-        {
-            cairo_line_to(cr, aPoint.getX(), aPoint.getY());
-            nSizeMeasure++;
-        }
-        else                        // cubic bezier segment
-        {
-            basegfx::B2DPoint aCP1 = rPolygon.getNextControlPoint( nPrevIdx );
-            basegfx::B2DPoint aCP2 = rPolygon.getPrevControlPoint( nClosedIdx );
-
-            // tdf#99165 if the control points are 'empty', create the mathematical
-            // correct replacement ones to avoid problems with the graphical sub-system
-            // tdf#101026 The 1st attempt to create a mathematically correct replacement control
-            // vector was wrong. Best alternative is one as close as possible which means short.
-            if (aCP1.equal(aLast))
-            {
-                aCP1 = aLast + ((aCP2 - aLast) * 0.0005);
-            }
-
-            if(aCP2.equal(aPoint))
-            {
-                aCP2 = aPoint + ((aCP1 - aPoint) * 0.0005);
-            }
-
-            cairo_curve_to(cr, aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(),
-                               aPoint.getX(), aPoint.getY());
-            // take some bigger measure for curve segments - too expensive to subdivide
-            // here and that precision not needed, but four (2 points, 2 control-points)
-            // would be a too low weight
-            nSizeMeasure += 10;
-        }
-
-        aLast = aPoint;
-    }
-
-    if( bClosePath )
-    {
-        cairo_close_path(cr);
-    }
-
-    return nSizeMeasure;
-}
-
-void SvpSalGraphics::drawLine( tools::Long nX1, tools::Long nY1, tools::Long nX2, tools::Long nY2 )
-{
-    basegfx::B2DPolygon aPoly;
-
-    // PixelOffset used: To not mix with possible PixelSnap, cannot do
-    // directly on coordinates as tried before - despite being already 'snapped'
-    // due to being integer. If it would be directly added here, it would be
-    // 'snapped' again when !getAntiAlias(), losing the (0.5, 0.5) offset
-    aPoly.append(basegfx::B2DPoint(nX1, nY1));
-    aPoly.append(basegfx::B2DPoint(nX2, nY2));
-
-    cairo_t* cr = getCairoContext(false);
-    clipRegion(cr);
-
-    // PixelOffset used: Set PixelOffset as linear transformation
-    cairo_matrix_t aMatrix;
-    cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
-    cairo_set_matrix(cr, &aMatrix);
-
-    AddPolygonToPath(
-        cr,
-        aPoly,
-        basegfx::B2DHomMatrix(),
-        !getAntiAlias(),
-        false);
-
-    applyColor(cr, m_aLineColor);
-
-    basegfx::B2DRange extents = getClippedStrokeDamage(cr);
-    extents.transform(basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5));
-
-    cairo_stroke(cr);
-
-    releaseCairoContext(cr, false, extents);
-}
-
-namespace {
-
-class SystemDependentData_CairoPath : public basegfx::SystemDependentData
-{
-private:
-    // the path data itself
-    cairo_path_t*       mpCairoPath;
-
-    // all other values the path data  is based on and
-    // need to be compared with to check for data validity
-    bool                mbNoJoin;
-    bool                mbAntiAlias;
-    std::vector< double >                       maStroke;
-
-public:
-    SystemDependentData_CairoPath(
-        basegfx::SystemDependentDataManager& rSystemDependentDataManager,
-        size_t nSizeMeasure,
-        cairo_t* cr,
-        bool bNoJoin,
-        bool bAntiAlias,
-        const std::vector< double >* pStroke); // MM01
-    virtual ~SystemDependentData_CairoPath() override;
-
-    // read access
-    cairo_path_t* getCairoPath() { return mpCairoPath; }
-    bool getNoJoin() const { return mbNoJoin; }
-    bool getAntiAlias() const { return mbAntiAlias; }
-    const std::vector< double >& getStroke() const { return maStroke; }
-
-    virtual sal_Int64 estimateUsageInBytes() const override;
-};
-
-}
-
-SystemDependentData_CairoPath::SystemDependentData_CairoPath(
-    basegfx::SystemDependentDataManager& rSystemDependentDataManager,
-    size_t nSizeMeasure,
-    cairo_t* cr,
-    bool bNoJoin,
-    bool bAntiAlias,
-    const std::vector< double >* pStroke)
-:   basegfx::SystemDependentData(rSystemDependentDataManager),
-    mpCairoPath(nullptr),
-    mbNoJoin(bNoJoin),
-    mbAntiAlias(bAntiAlias),
-    maStroke()
-{
-    // tdf#129845 only create a copy of the path when nSizeMeasure is
-    // bigger than some decent threshold
-    if(nSizeMeasure > 50)
-    {
-        mpCairoPath = cairo_copy_path(cr);
-
-        if(nullptr != pStroke)
-        {
-            maStroke = *pStroke;
-        }
-    }
-}
-
-SystemDependentData_CairoPath::~SystemDependentData_CairoPath()
-{
-    if(nullptr != mpCairoPath)
-    {
-        cairo_path_destroy(mpCairoPath);
-        mpCairoPath = nullptr;
-    }
-}
-
-sal_Int64 SystemDependentData_CairoPath::estimateUsageInBytes() const
-{
-    // tdf#129845 by using the default return value of zero when no path
-    // was created, SystemDependentData::calculateCombinedHoldCyclesInSeconds
-    // will do the right thing and not buffer this entry at all
-    sal_Int64 nRetval(0);
-
-    if(nullptr != mpCairoPath)
-    {
-        // per node
-        // - num_data incarnations of
-        // - sizeof(cairo_path_data_t) which is a union of defines and point data
-        //   thus may 2 x sizeof(double)
-        nRetval = mpCairoPath->num_data * sizeof(cairo_path_data_t);
-    }
-
-    return nRetval;
-}
-
-bool SvpSalGraphics::drawPolyLine(
-    const basegfx::B2DHomMatrix& rObjectToDevice,
-    const basegfx::B2DPolygon& rPolyLine,
-    double fTransparency,
-    double fLineWidth,
-    const std::vector< double >* pStroke, // MM01
-    basegfx::B2DLineJoin eLineJoin,
-    css::drawing::LineCap eLineCap,
-    double fMiterMinimumAngle,
-    bool bPixelSnapHairline)
-{
-    // short circuit if there is nothing to do
-    if(0 == rPolyLine.count() || fTransparency < 0.0 || fTransparency >= 1.0)
-    {
-        return true;
-    }
-
-    // Wrap call to static version 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,
-            getAntiAlias(),
-            rObjectToDevice,
-            rPolyLine,
-            fTransparency,
-            fLineWidth,
-            pStroke, // MM01
-            eLineJoin,
-            eLineCap,
-            fMiterMinimumAngle,
-            bPixelSnapHairline));
-
-    releaseCairoContext(cr, false, aExtents);
-
-    return bRetval;
-}
-
-bool SvpSalGraphics::drawPolyLine(
-    cairo_t* cr,
-    basegfx::B2DRange* pExtents,
-    const Color& rLineColor,
-    bool bAntiAlias,
-    const basegfx::B2DHomMatrix& rObjectToDevice,
-    const basegfx::B2DPolygon& rPolyLine,
-    double fTransparency,
-    double fLineWidth,
-    const std::vector< double >* pStroke, // MM01
-    basegfx::B2DLineJoin eLineJoin,
-    css::drawing::LineCap eLineCap,
-    double fMiterMinimumAngle,
-    bool bPixelSnapHairline)
-{
-    // short circuit if there is nothing to do
-    if(0 == rPolyLine.count() || fTransparency < 0.0 || fTransparency >= 1.0)
-    {
-        return true;
-    }
-
-    // need to check/handle LineWidth when ObjectToDevice transformation is used
-    const bool bObjectToDeviceIsIdentity(rObjectToDevice.isIdentity());
-
-    // tdf#124848 calculate-back logical LineWidth for a hairline
-    // since this implementation hands over the transformation to
-    // the graphic sub-system
-    if(fLineWidth == 0)
-    {
-        fLineWidth = 1.0;
-
-        if(!bObjectToDeviceIsIdentity)
-        {
-            basegfx::B2DHomMatrix aObjectToDeviceInv(rObjectToDevice);
-            aObjectToDeviceInv.invert();
-            fLineWidth = (aObjectToDeviceInv * basegfx::B2DVector(fLineWidth, 0)).getLength();
-        }
-    }
-
-    // PixelOffset used: Need to reflect in linear transformation
-    cairo_matrix_t aMatrix;
-    basegfx::B2DHomMatrix aDamageMatrix(basegfx::utils::createTranslateB2DHomMatrix(0.5, 0.5));
-
-    if (bObjectToDeviceIsIdentity)
-    {
-        // Set PixelOffset as requested
-        cairo_matrix_init_translate(&aMatrix, 0.5, 0.5);
-    }
-    else
-    {
-        // Prepare ObjectToDevice transformation. Take PixelOffset for Lines into
-        // account: Multiply from left to act in DeviceCoordinates
-        aDamageMatrix = aDamageMatrix * rObjectToDevice;
-        cairo_matrix_init(
-            &aMatrix,
-            aDamageMatrix.get( 0, 0 ),
-            aDamageMatrix.get( 1, 0 ),
-            aDamageMatrix.get( 0, 1 ),
-            aDamageMatrix.get( 1, 1 ),
-            aDamageMatrix.get( 0, 2 ),
-            aDamageMatrix.get( 1, 2 ));
-    }
-
-    // set linear transformation
-    cairo_set_matrix(cr, &aMatrix);
-
-    // setup line attributes
-    cairo_line_join_t eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
-    switch (eLineJoin)
-    {
-        case basegfx::B2DLineJoin::Bevel:
-            eCairoLineJoin = CAIRO_LINE_JOIN_BEVEL;
-            break;
-        case basegfx::B2DLineJoin::Round:
-            eCairoLineJoin = CAIRO_LINE_JOIN_ROUND;
-            break;
-        case basegfx::B2DLineJoin::NONE:
-        case basegfx::B2DLineJoin::Miter:
-            eCairoLineJoin = CAIRO_LINE_JOIN_MITER;
-            break;
-    }
-
-    // convert miter minimum angle to miter limit
-    double fMiterLimit = 1.0 / sin( fMiterMinimumAngle / 2.0);
-
-    // setup cap attribute
-    cairo_line_cap_t eCairoLineCap(CAIRO_LINE_CAP_BUTT);
-
-    switch (eLineCap)
-    {
-        default: // css::drawing::LineCap_BUTT:
-        {
-            eCairoLineCap = CAIRO_LINE_CAP_BUTT;
-            break;
-        }
-        case css::drawing::LineCap_ROUND:
-        {
-            eCairoLineCap = CAIRO_LINE_CAP_ROUND;
-            break;
-        }
-        case css::drawing::LineCap_SQUARE:
-        {
-            eCairoLineCap = CAIRO_LINE_CAP_SQUARE;
-            break;
-        }
-    }
-
-    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, fLineWidth);
-    cairo_set_miter_limit(cr, fMiterLimit);
-
-    // try to access buffered data
-    std::shared_ptr<SystemDependentData_CairoPath> pSystemDependentData_CairoPath(
-        rPolyLine.getSystemDependentData<SystemDependentData_CairoPath>());
-
-    // MM01 need to do line dashing as fallback stuff here now
-    const double fDotDashLength(nullptr != pStroke ? std::accumulate(pStroke->begin(), pStroke->end(), 0.0) : 0.0);
-    const bool bStrokeUsed(0.0 != fDotDashLength);
-    assert(!bStrokeUsed || (bStrokeUsed && pStroke));
-
-    // MM01 decide if to stroke directly
-    static bool bDoDirectCairoStroke(true);
-
-    // MM01 activate to stroke directly
-    if(bDoDirectCairoStroke && bStrokeUsed)
-    {
-        cairo_set_dash(cr, pStroke->data(), pStroke->size(), 0.0);
-    }
-
-    if(!bDoDirectCairoStroke && pSystemDependentData_CairoPath)
-    {
-        // MM01 - check on stroke change. Used against not used, or if both used,
-        // equal or different?
-        const bool bStrokeWasUsed(!pSystemDependentData_CairoPath->getStroke().empty());
-
-        if(bStrokeWasUsed != bStrokeUsed
-        || (bStrokeUsed && *pStroke != pSystemDependentData_CairoPath->getStroke()))
-        {
-            // data invalid, forget
-            pSystemDependentData_CairoPath.reset();
-        }
-    }
-
-    // check for basegfx::B2DLineJoin::NONE to react accordingly
-    const bool bNoJoin((basegfx::B2DLineJoin::NONE == eLineJoin
-        && basegfx::fTools::more(fLineWidth, 0.0)));
-
-    if(pSystemDependentData_CairoPath)
-    {
-        // check data validity
-        if(nullptr == pSystemDependentData_CairoPath->getCairoPath()
-            || pSystemDependentData_CairoPath->getNoJoin() != bNoJoin
-            || pSystemDependentData_CairoPath->getAntiAlias() != bAntiAlias
-            || bPixelSnapHairline /*tdf#124700*/ )
-        {
-            // data invalid, forget
-            pSystemDependentData_CairoPath.reset();
-        }
-    }
-
-    if(pSystemDependentData_CairoPath)
-    {
-        // re-use data
-        cairo_append_path(cr, pSystemDependentData_CairoPath->getCairoPath());
-    }
-    else
-    {
-        // create data
-        size_t nSizeMeasure(0);
-
-        // MM01 need to do line dashing as fallback stuff here now
-        basegfx::B2DPolyPolygon aPolyPolygonLine;
-
-        if(!bDoDirectCairoStroke && bStrokeUsed)
-        {
-            // apply LineStyle
-            basegfx::utils::applyLineDashing(
-                rPolyLine, // source
-                *pStroke, // pattern
-                &aPolyPolygonLine, // target for lines
-                nullptr, // target for gaps
-                fDotDashLength); // full length if available
-        }
-        else
-        {
-            // no line dashing or direct stroke, just copy
-            aPolyPolygonLine.append(rPolyLine);
-        }
-
-        // MM01 checked/verified for Cairo
-        for(sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++)
-        {
-            const basegfx::B2DPolygon aPolyLine(aPolyPolygonLine.getB2DPolygon(a));
-
-            if (!bNoJoin)
-            {
-                // PixelOffset now reflected in linear transformation used
-                nSizeMeasure += AddPolygonToPath(
-                    cr,
-                    aPolyLine,
-                    rObjectToDevice, // ObjectToDevice *without* LineDraw-Offset
-                    !bAntiAlias,
-                    bPixelSnapHairline);
-            }
-            else
-            {
-                const sal_uInt32 nPointCount(aPolyLine.count());
-                const sal_uInt32 nEdgeCount(aPolyLine.isClosed() ? nPointCount : nPointCount - 1);
-                basegfx::B2DPolygon aEdge;
-
-                aEdge.append(aPolyLine.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, aPolyLine.getB2DPoint(nNextIndex));
-                    aEdge.setNextControlPoint(0, aPolyLine.getNextControlPoint(i));
-                    aEdge.setPrevControlPoint(1, aPolyLine.getPrevControlPoint(nNextIndex));
-
-                    // PixelOffset now reflected in linear transformation used
-                    nSizeMeasure += AddPolygonToPath(
-                        cr,
-                        aEdge,
-                        rObjectToDevice, // ObjectToDevice *without* LineDraw-Offset
-                        !bAntiAlias,

... etc. - the rest is truncated


More information about the Libreoffice-commits mailing list