tdf#63130 reduce duplicated work when pixel snapping

Cache the calculations so we don't repeat work unnecessarily. Shaves 5%
off load time.

Change-Id: Iffbdd08768fea5b25ac83926b812067f52cba3a2
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/151883
Tested-by: Jenkins
Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
diff --git a/vcl/headless/CairoCommon.cxx b/vcl/headless/CairoCommon.cxx
index d9c77af..9c67fb0 100644
--- a/vcl/headless/CairoCommon.cxx
+++ b/vcl/headless/CairoCommon.cxx
@@ -160,6 +160,7 @@ size_t AddPolygonToPath(cairo_t* cr, const basegfx::B2DPolygon& rPolygon,
    const bool bObjectToDeviceUsed(!rObjectToDevice.isIdentity());
    basegfx::B2DHomMatrix aObjectToDeviceInv;
    basegfx::B2DPoint aLast;
    PixelSnapper aSnapper;

    for (sal_uInt32 nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++)
    {
@@ -209,7 +210,7 @@ size_t AddPolygonToPath(cairo_t* cr, const basegfx::B2DPolygon& rPolygon,
        {
            // snap horizontal and vertical lines (mainly used in Chart for
            // 'nicer' AAing)
            aPoint = impPixelSnap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nClosedIdx);
            aPoint = aSnapper.snap(rPolygon, rObjectToDevice, aObjectToDeviceInv, nClosedIdx);
        }

        if (!nPointIdx)
@@ -271,32 +272,44 @@ size_t AddPolygonToPath(cairo_t* cr, const basegfx::B2DPolygon& rPolygon,
    return nSizeMeasure;
}

basegfx::B2DPoint impPixelSnap(const basegfx::B2DPolygon& rPolygon,
                               const basegfx::B2DHomMatrix& rObjectToDevice,
                               basegfx::B2DHomMatrix& rObjectToDeviceInv, sal_uInt32 nIndex)
basegfx::B2DPoint PixelSnapper::snap(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)));
    if (nIndex == 0)
    {
        // if it's the first time, we need to calculate everything
        maPrevPoint = rObjectToDevice * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount);
        maCurrPoint = rObjectToDevice * rPolygon.getB2DPoint(nIndex);
        maPrevTuple = basegfx::fround(maPrevPoint);
        maCurrTuple = basegfx::fround(maCurrPoint);
    }
    else
    {
        // but for all other times, we can re-use the previous iteration computations
        maPrevPoint = maCurrPoint;
        maPrevTuple = maCurrTuple;
        maCurrPoint = maNextPoint;
        maCurrTuple = maNextTuple;
    }
    maNextPoint = rObjectToDevice * rPolygon.getB2DPoint((nIndex + 1) % nCount);
    maNextTuple = basegfx::fround(maNextPoint);

    // 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 bPrevVertical(maPrevTuple.getX() == maCurrTuple.getX());
    const bool bNextVertical(maNextTuple.getX() == maCurrTuple.getX());
    const bool bPrevHorizontal(maPrevTuple.getY() == maCurrTuple.getY());
    const bool bNextHorizontal(maNextTuple.getY() == maCurrTuple.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());
        basegfx::B2DPoint aSnappedPoint(bSnapX ? maCurrTuple.getX() : maCurrPoint.getX(),
                                        bSnapY ? maCurrTuple.getY() : maCurrPoint.getY());

        if (rObjectToDeviceInv.isIdentity())
        {
diff --git a/vcl/inc/headless/CairoCommon.hxx b/vcl/inc/headless/CairoCommon.hxx
index 6d280d3..f7556ed 100644
--- a/vcl/inc/headless/CairoCommon.hxx
+++ b/vcl/inc/headless/CairoCommon.hxx
@@ -91,10 +91,17 @@ VCL_DLLPUBLIC size_t AddPolygonToPath(cairo_t* cr, const basegfx::B2DPolygon& rP
                                      const basegfx::B2DHomMatrix& rObjectToDevice, bool bPixelSnap,
                                      bool bPixelSnapHairline);

VCL_DLLPUBLIC basegfx::B2DPoint impPixelSnap(const basegfx::B2DPolygon& rPolygon,
                                             const basegfx::B2DHomMatrix& rObjectToDevice,
                                             basegfx::B2DHomMatrix& rObjectToDeviceInv,
                                             sal_uInt32 nIndex);
class VCL_DLLPUBLIC PixelSnapper
{
public:
    basegfx::B2DPoint snap(const basegfx::B2DPolygon& rPolygon,
                           const basegfx::B2DHomMatrix& rObjectToDevice,
                           basegfx::B2DHomMatrix& rObjectToDeviceInv, sal_uInt32 nIndex);

private:
    basegfx::B2DPoint maPrevPoint, maCurrPoint, maNextPoint;
    basegfx::B2ITuple maPrevTuple, maCurrTuple, maNextTuple;
};

VCL_DLLPUBLIC void add_polygon_path(cairo_t* cr, const basegfx::B2DPolyPolygon& rPolyPolygon,
                                    const basegfx::B2DHomMatrix& rObjectToDevice, bool bPixelSnap);