/* -*- 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 <numeric>
#include <algorithm>
#include <stack>
 
#include <basegfx/numeric/ftools.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <osl/diagnose.h>
#include <rtl/math.hxx>
#include <sal/log.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/range/b2drange.hxx>
#include <basegfx/curve/b2dcubicbezier.hxx>
#include <basegfx/point/b3dpoint.hxx>
#include <basegfx/matrix/b3dhommatrix.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/curve/b2dbeziertools.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
 
// #i37443#
#define ANGLE_BOUND_START_VALUE     (2.25)
#define ANGLE_BOUND_MINIMUM_VALUE   (0.1)
#define STEPSPERQUARTER     (3)
 
namespace basegfx::utils
{
        void openWithGeometryChange(B2DPolygon& rCandidate)
        {
            if(!rCandidate.isClosed())
                return;
 
            if(rCandidate.count())
            {
                rCandidate.append(rCandidate.getB2DPoint(0));
 
                if(rCandidate.areControlPointsUsed() && rCandidate.isPrevControlPointUsed(0))
                {
                    rCandidate.setPrevControlPoint(rCandidate.count() - 1, rCandidate.getPrevControlPoint(0));
                    rCandidate.resetPrevControlPoint(0);
                }
            }
 
            rCandidate.setClosed(false);
        }
 
        void closeWithGeometryChange(B2DPolygon& rCandidate)
        {
            if(rCandidate.isClosed())
                return;
 
            while(rCandidate.count() > 1 && rCandidate.getB2DPoint(0) == rCandidate.getB2DPoint(rCandidate.count() - 1))
            {
                if(rCandidate.areControlPointsUsed() && rCandidate.isPrevControlPointUsed(rCandidate.count() - 1))
                {
                    rCandidate.setPrevControlPoint(0, rCandidate.getPrevControlPoint(rCandidate.count() - 1));
                }
 
                rCandidate.remove(rCandidate.count() - 1);
            }
 
            rCandidate.setClosed(true);
        }
 
        void checkClosed(B2DPolygon& rCandidate)
        {
            // #i80172# Removed unnecessary assertion
            // OSL_ENSURE(!rCandidate.isClosed(), "checkClosed: already closed (!)");
 
            if(rCandidate.count() > 1 && rCandidate.getB2DPoint(0) == rCandidate.getB2DPoint(rCandidate.count() - 1))
            {
                closeWithGeometryChange(rCandidate);
            }
        }
 
        // Get successor and predecessor indices. Returning the same index means there
        // is none. Same for successor.
        sal_uInt32 getIndexOfPredecessor(sal_uInt32 nIndex, const B2DPolygon& rCandidate)
        {
            OSL_ENSURE(nIndex < rCandidate.count(), "getIndexOfPredecessor: Access to polygon out of range (!)");
 
            if(nIndex)
            {
                return nIndex - 1;
            }
            else if(rCandidate.count())
            {
                return rCandidate.count() - 1;
            }
            else
            {
                return nIndex;
            }
        }
 
        sal_uInt32 getIndexOfSuccessor(sal_uInt32 nIndex, const B2DPolygon& rCandidate)
        {
            OSL_ENSURE(nIndex < rCandidate.count(), "getIndexOfPredecessor: Access to polygon out of range (!)");
 
            if(nIndex + 1 < rCandidate.count())
            {
                return nIndex + 1;
            }
            else if(nIndex + 1 == rCandidate.count())
            {
                return 0;
            }
            else
            {
                return nIndex;
            }
        }
 
        B2VectorOrientation getOrientation(const B2DPolygon& rCandidate)
        {
            B2VectorOrientation eRetval(B2VectorOrientation::Neutral);
 
            if(rCandidate.count() > 2 || rCandidate.areControlPointsUsed())
            {
                const double fSignedArea(getSignedArea(rCandidate));
 
                if(fTools::equalZero(fSignedArea))
                {
                    // B2VectorOrientation::Neutral, already set
                }
                if(fSignedArea > 0.0)
                {
                    eRetval = B2VectorOrientation::Positive;
                }
                else if(fSignedArea < 0.0)
                {
                    eRetval = B2VectorOrientation::Negative;
                }
            }
 
            return eRetval;
        }
 
        B2VectorContinuity getContinuityInPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
        {
            return rCandidate.getContinuityInPoint(nIndex);
        }
 
        B2DPolygon adaptiveSubdivideByDistance(const B2DPolygon& rCandidate, double fDistanceBound, int nRecurseLimit)
        {
            if(rCandidate.areControlPointsUsed())
            {
                const sal_uInt32 nPointCount(rCandidate.count());
                B2DPolygon aRetval;
 
                if(nPointCount)
                {
                    // prepare edge-oriented loop
                    const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
                    B2DCubicBezier aBezier;
                    aBezier.setStartPoint(rCandidate.getB2DPoint(0));
 
                    // perf: try to avoid too many reallocations by guessing the result's pointcount
                    aRetval.reserve(nPointCount*4);
 
                    // add start point (always)
                    aRetval.append(aBezier.getStartPoint());
 
                    for(sal_uInt32 a(0); a < nEdgeCount; a++)
                    {
                        // get next and control points
                        const sal_uInt32 nNextIndex((a + 1) % nPointCount);
                        aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
                        aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
                        aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
                        aBezier.testAndSolveTrivialBezier();
 
                        if(aBezier.isBezier())
                        {
                            // add curved edge and generate DistanceBound
                            double fBound(0.0);
 
                            if(fDistanceBound == 0.0)
                            {
                                // If not set, use B2DCubicBezier functionality to guess a rough value
                                const double fRoughLength((aBezier.getEdgeLength() + aBezier.getControlPolygonLength()) / 2.0);
 
                                // take 1/100th of the rough curve length
                                fBound = fRoughLength * 0.01;
                            }
                            else
                            {
                                // use given bound value
                                fBound = fDistanceBound;
                            }
 
                            // make sure bound value is not too small. The base units are 1/100th mm, thus
                            // just make sure it's not smaller then 1/100th of that
                            if(fBound < 0.01)
                            {
                                fBound = 0.01;
                            }
 
                            // call adaptive subdivide which adds edges to aRetval accordingly
                            aBezier.adaptiveSubdivideByDistance(aRetval, fBound, nRecurseLimit);
                        }
                        else
                        {
                            // add non-curved edge
                            aRetval.append(aBezier.getEndPoint());
                        }
 
                        // prepare next step
                        aBezier.setStartPoint(aBezier.getEndPoint());
                    }
 
                    if(rCandidate.isClosed())
                    {
                        // set closed flag and correct last point (which is added double now).
                        closeWithGeometryChange(aRetval);
                    }
                }
 
                return aRetval;
            }
            else
            {
                return rCandidate;
            }
        }
 
        B2DPolygon adaptiveSubdivideByAngle(const B2DPolygon& rCandidate, double fAngleBound)
        {
            if(rCandidate.areControlPointsUsed())
            {
                const sal_uInt32 nPointCount(rCandidate.count());
                B2DPolygon aRetval;
 
                if(nPointCount)
                {
                    // prepare edge-oriented loop
                    const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
                    B2DCubicBezier aBezier;
                    aBezier.setStartPoint(rCandidate.getB2DPoint(0));
 
                    // perf: try to avoid too many reallocations by guessing the result's pointcount
                    aRetval.reserve(nPointCount*4);
 
                    // add start point (always)
                    aRetval.append(aBezier.getStartPoint());
 
                    // #i37443# prepare convenient AngleBound if none was given
                    if(fAngleBound == 0.0)
                    {
                        fAngleBound = ANGLE_BOUND_START_VALUE;
                    }
                    else if(fTools::less(fAngleBound, ANGLE_BOUND_MINIMUM_VALUE))
                    {
                        fAngleBound = 0.1;
                    }
 
                    for(sal_uInt32 a(0); a < nEdgeCount; a++)
                    {
                        // get next and control points
                        const sal_uInt32 nNextIndex((a + 1) % nPointCount);
                        aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
                        aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
                        aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
                        aBezier.testAndSolveTrivialBezier();
 
                        if(aBezier.isBezier())
                        {
                            // call adaptive subdivide
                            aBezier.adaptiveSubdivideByAngle(aRetval, fAngleBound);
                        }
                        else
                        {
                            // add non-curved edge
                            aRetval.append(aBezier.getEndPoint());
                        }
 
                        // prepare next step
                        aBezier.setStartPoint(aBezier.getEndPoint());
                    }
 
                    if(rCandidate.isClosed())
                    {
                        // set closed flag and correct last point (which is added double now).
                        closeWithGeometryChange(aRetval);
                    }
                }
 
                return aRetval;
            }
            else
            {
                return rCandidate;
            }
        }
 
        bool isInside(const B2DPolygon& rCandidate, const B2DPoint& rPoint, bool bWithBorder)
        {
            const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
 
            if(bWithBorder && isPointOnPolygon(aCandidate, rPoint))
            {
                return true;
            }
            else
            {
                bool bRetval(false);
                const sal_uInt32 nPointCount(aCandidate.count());
 
                if(nPointCount)
                {
                    B2DPoint aCurrentPoint(aCandidate.getB2DPoint(nPointCount - 1));
 
                    for(sal_uInt32 a(0); a < nPointCount; a++)
                    {
                        const B2DPoint aPreviousPoint(aCurrentPoint);
                        aCurrentPoint = aCandidate.getB2DPoint(a);
 
                        // cross-over in Y? tdf#130150 use full precision, no need for epsilon
                        const bool bCompYA(aPreviousPoint.getY() > rPoint.getY());
                        const bool bCompYB(aCurrentPoint.getY() > rPoint.getY());
 
                        if(bCompYA != bCompYB)
                        {
                            // cross-over in X? tdf#130150 use full precision, no need for epsilon
                            const bool bCompXA(aPreviousPoint.getX() > rPoint.getX());
                            const bool bCompXB(aCurrentPoint.getX() > rPoint.getX());
 
                            if(bCompXA == bCompXB)
                            {
                                if(bCompXA)
                                {
                                    bRetval = !bRetval;
                                }
                            }
                            else
                            {
                                const double fCompare(
                                    aCurrentPoint.getX() - (aCurrentPoint.getY() - rPoint.getY()) *
                                    (aPreviousPoint.getX() - aCurrentPoint.getX()) /
                                    (aPreviousPoint.getY() - aCurrentPoint.getY()));
 
                                // tdf#130150 use full precision, no need for epsilon
                                if(fCompare > rPoint.getX())
                                {
                                    bRetval = !bRetval;
                                }
                            }
                        }
                    }
                }
 
                return bRetval;
            }
        }
 
        bool isInside(const B2DPolygon& rCandidate, const B2DPolygon& rPolygon, bool bWithBorder)
        {
            const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
            const B2DPolygon aPolygon(rPolygon.areControlPointsUsed() ? rPolygon.getDefaultAdaptiveSubdivision() : rPolygon);
            const sal_uInt32 nPointCount(aPolygon.count());
 
            for(sal_uInt32 a(0); a < nPointCount; a++)
            {
                const B2DPoint aTestPoint(aPolygon.getB2DPoint(a));
 
                if(!isInside(aCandidate, aTestPoint, bWithBorder))
                {
                    return false;
                }
            }
 
            return true;
        }
 
        B2DRange getRange(const B2DPolygon& rCandidate)
        {
            // changed to use internally buffered version at B2DPolygon
            return rCandidate.getB2DRange();
        }
 
        double getSignedArea(const B2DPolygon& rCandidate)
        {
            const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
            double fRetval(0.0);
            const sal_uInt32 nPointCount(aCandidate.count());
 
            if(nPointCount > 2)
            {
                for(sal_uInt32 a(0); a < nPointCount; a++)
                {
                    const B2DPoint aPreviousPoint(aCandidate.getB2DPoint((!a) ? nPointCount - 1 : a - 1));
                    const B2DPoint aCurrentPoint(aCandidate.getB2DPoint(a));
 
                    fRetval += aPreviousPoint.getX() * aCurrentPoint.getY();
                    fRetval -= aPreviousPoint.getY() * aCurrentPoint.getX();
                }
 
                // correct to zero if small enough. Also test the quadratic
                // of the result since the precision is near quadratic due to
                // the algorithm
                if(fTools::equalZero(fRetval) || fTools::equalZero(fRetval * fRetval))
                {
                    fRetval = 0.0;
                }
            }
 
            return fRetval;
        }
 
        double getArea(const B2DPolygon& rCandidate)
        {
            double fRetval(0.0);
 
            if(rCandidate.count() > 2 || rCandidate.areControlPointsUsed())
            {
                fRetval = getSignedArea(rCandidate);
                const double fZero(0.0);
 
                if(fTools::less(fRetval, fZero))
                {
                    fRetval = -fRetval;
                }
            }
 
            return fRetval;
        }
 
        double getEdgeLength(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
        {
            const sal_uInt32 nPointCount(rCandidate.count());
            OSL_ENSURE(nIndex < nPointCount, "getEdgeLength: Access to polygon out of range (!)");
            double fRetval(0.0);
 
            if(nPointCount)
            {
                const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
 
                if(rCandidate.areControlPointsUsed())
                {
                    B2DCubicBezier aEdge;
 
                    aEdge.setStartPoint(rCandidate.getB2DPoint(nIndex));
                    aEdge.setControlPointA(rCandidate.getNextControlPoint(nIndex));
                    aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
                    aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
 
                    fRetval = aEdge.getLength();
                }
                else
                {
                    const B2DPoint aCurrent(rCandidate.getB2DPoint(nIndex));
                    const B2DPoint aNext(rCandidate.getB2DPoint(nNextIndex));
 
                    fRetval = B2DVector(aNext - aCurrent).getLength();
                }
            }
 
            return fRetval;
        }
 
        double getLength(const B2DPolygon& rCandidate)
        {
            double fRetval(0.0);
            const sal_uInt32 nPointCount(rCandidate.count());
 
            if(nPointCount)
            {
                const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
 
                if(rCandidate.areControlPointsUsed())
                {
                    B2DCubicBezier aEdge;
                    aEdge.setStartPoint(rCandidate.getB2DPoint(0));
 
                    for(sal_uInt32 a(0); a < nEdgeCount; a++)
                    {
                        const sal_uInt32 nNextIndex((a + 1) % nPointCount);
                        aEdge.setControlPointA(rCandidate.getNextControlPoint(a));
                        aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
                        aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
 
                        fRetval += aEdge.getLength();
                        aEdge.setStartPoint(aEdge.getEndPoint());
                    }
                }
                else
                {
                    B2DPoint aCurrent(rCandidate.getB2DPoint(0));
 
                    for(sal_uInt32 a(0); a < nEdgeCount; a++)
                    {
                        const sal_uInt32 nNextIndex((a + 1) % nPointCount);
                        const B2DPoint aNext(rCandidate.getB2DPoint(nNextIndex));
 
                        fRetval += B2DVector(aNext - aCurrent).getLength();
                        aCurrent = aNext;
                    }
                }
            }
 
            return fRetval;
        }
 
        B2DPoint getPositionAbsolute(const B2DPolygon& rCandidate, double fDistance, double fLength)
        {
            B2DPoint aRetval;
            const sal_uInt32 nPointCount(rCandidate.count());
 
            if( nPointCount == 1 )
            {
                // only one point (i.e. no edge) - simply take that point
                aRetval = rCandidate.getB2DPoint(0);
            }
            else if(nPointCount > 1)
            {
                const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
                sal_uInt32 nIndex(0);
                bool bIndexDone(false);
 
                // get length if not given
                if(fTools::equalZero(fLength))
                {
                    fLength = getLength(rCandidate);
                }
 
                if (fDistance < 0.0)
                {
                    // handle fDistance < 0.0
                    if(rCandidate.isClosed())
                    {
                        // if fDistance < 0.0 increment with multiple of fLength
                        sal_uInt32 nCount(sal_uInt32(-fDistance / fLength));
                        fDistance += double(nCount + 1) * fLength;
                    }
                    else
                    {
                        // crop to polygon start
                        fDistance = 0.0;
                        bIndexDone = true;
                    }
                }
                else if(fTools::moreOrEqual(fDistance, fLength))
                {
                    // handle fDistance >= fLength
                    if(rCandidate.isClosed())
                    {
                        // if fDistance >= fLength decrement with multiple of fLength
                        sal_uInt32 nCount(sal_uInt32(fDistance / fLength));
                        fDistance -= static_cast<double>(nCount) * fLength;
                    }
                    else
                    {
                        // crop to polygon end
                        fDistance = 0.0;
                        nIndex = nEdgeCount;
                        bIndexDone = true;
                    }
                }
 
                // look for correct index. fDistance is now [0.0 .. fLength[
                double fEdgeLength(getEdgeLength(rCandidate, nIndex));
 
                while(!bIndexDone)
                {
                    // edge found must be on the half-open range
                    // [0,fEdgeLength).
                    // Note that in theory, we cannot move beyond
                    // the last polygon point, since fDistance>=fLength
                    // is checked above. Unfortunately, with floating-
                    // point calculations, this case might happen.
                    // Handled by nIndex check below
                    if (nIndex+1 < nEdgeCount && fTools::moreOrEqual(fDistance, fEdgeLength))
                    {
                        // go to next edge
                        fDistance -= fEdgeLength;
                        fEdgeLength = getEdgeLength(rCandidate, ++nIndex);
                    }
                    else
                    {
                        // it's on this edge, stop
                        bIndexDone = true;
                    }
                }
 
                // get the point using nIndex
                aRetval = rCandidate.getB2DPoint(nIndex);
 
                // if fDistance != 0.0, move that length on the edge. The edge
                // length is in fEdgeLength.
                if(!fTools::equalZero(fDistance))
                {
                    if(fTools::moreOrEqual(fDistance, fEdgeLength))
                    {
                        // end point of chosen edge
                        const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
                        aRetval = rCandidate.getB2DPoint(nNextIndex);
                    }
                    else if(fTools::equalZero(fDistance))
                    {
                        // start point of chosen edge
                    }
                    else
                    {
                        // inside edge
                        const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
                        const B2DPoint aNextPoint(rCandidate.getB2DPoint(nNextIndex));
                        bool bDone(false);
 
                        // add calculated average value to the return value
                        if(rCandidate.areControlPointsUsed())
                        {
                            // get as bezier segment
                            const B2DCubicBezier aBezierSegment(
                                aRetval, rCandidate.getNextControlPoint(nIndex),
                                rCandidate.getPrevControlPoint(nNextIndex), aNextPoint);
 
                            if(aBezierSegment.isBezier())
                            {
                                // use B2DCubicBezierHelper to bridge the non-linear gap between
                                // length and bezier distances
                                const B2DCubicBezierHelper aBezierSegmentHelper(aBezierSegment);
                                const double fBezierDistance(aBezierSegmentHelper.distanceToRelative(fDistance));
 
                                aRetval = aBezierSegment.interpolatePoint(fBezierDistance);
                                bDone = true;
                            }
                        }
 
                        if(!bDone)
                        {
                            const double fRelativeInEdge(fDistance / fEdgeLength);
                            aRetval = interpolate(aRetval, aNextPoint, fRelativeInEdge);
                        }
                    }
                }
            }
 
            return aRetval;
        }
 
        B2DPoint getPositionRelative(const B2DPolygon& rCandidate, double fDistance, double fLength)
        {
            // get length if not given
            if(fTools::equalZero(fLength))
            {
                fLength = getLength(rCandidate);
            }
 
            // multiply fDistance with real length to get absolute position and
            // use getPositionAbsolute
            return getPositionAbsolute(rCandidate, fDistance * fLength, fLength);
        }
 
        B2DPolygon getSnippetAbsolute(const B2DPolygon& rCandidate, double fFrom, double fTo, double fLength)
        {
            const sal_uInt32 nPointCount(rCandidate.count());
 
            if(nPointCount)
            {
                // get length if not given
                if(fTools::equalZero(fLength))
                {
                    fLength = getLength(rCandidate);
                }
 
                // test and correct fFrom
                if (fFrom < 0.0)
                {
                    fFrom = 0.0;
                }
 
                // test and correct fTo
                if(fTools::more(fTo, fLength))
                {
                    fTo = fLength;
                }
 
                // test and correct relationship of fFrom, fTo
                if(fTools::more(fFrom, fTo))
                {
                    fFrom = fTo = (fFrom + fTo) / 2.0;
                }
 
                if(fTools::equalZero(fFrom) && fTools::equal(fTo, fLength))
                {
                    // no change, result is the whole polygon
                    return rCandidate;
                }
                else
                {
                    B2DPolygon aRetval;
                    const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
                    double fPositionOfStart(0.0);
                    bool bStartDone(false);
                    bool bEndDone(false);
 
                    for(sal_uInt32 a(0); !(bStartDone && bEndDone) && a < nEdgeCount; a++)
                    {
                        const double fEdgeLength(getEdgeLength(rCandidate, a));
 
                        if(!bStartDone)
                        {
                            if(fTools::equalZero(fFrom))
                            {
                                aRetval.append(rCandidate.getB2DPoint(a));
 
                                if(rCandidate.areControlPointsUsed())
                                {
                                    aRetval.setNextControlPoint(aRetval.count() - 1, rCandidate.getNextControlPoint(a));
                                }
 
                                bStartDone = true;
                            }
                            else if(fTools::moreOrEqual(fFrom, fPositionOfStart) && fTools::less(fFrom, fPositionOfStart + fEdgeLength))
                            {
                                // calculate and add start point
                                if(fTools::equalZero(fEdgeLength))
                                {
                                    aRetval.append(rCandidate.getB2DPoint(a));
 
                                    if(rCandidate.areControlPointsUsed())
                                    {
                                        aRetval.setNextControlPoint(aRetval.count() - 1, rCandidate.getNextControlPoint(a));
                                    }
                                }
                                else
                                {
                                    const sal_uInt32 nNextIndex((a + 1) % nPointCount);
                                    const B2DPoint aStart(rCandidate.getB2DPoint(a));
                                    const B2DPoint aEnd(rCandidate.getB2DPoint(nNextIndex));
                                    bool bDone(false);
 
                                    if(rCandidate.areControlPointsUsed())
                                    {
                                        const B2DCubicBezier aBezierSegment(
                                            aStart, rCandidate.getNextControlPoint(a),
                                            rCandidate.getPrevControlPoint(nNextIndex), aEnd);
 
                                        if(aBezierSegment.isBezier())
                                        {
                                            // use B2DCubicBezierHelper to bridge the non-linear gap between
                                            // length and bezier distances
                                            const B2DCubicBezierHelper aBezierSegmentHelper(aBezierSegment);
                                            const double fBezierDistance(aBezierSegmentHelper.distanceToRelative(fFrom - fPositionOfStart));
                                            B2DCubicBezier aRight;
 
                                            aBezierSegment.split(fBezierDistance, nullptr, &aRight);
                                            aRetval.append(aRight.getStartPoint());
                                            aRetval.setNextControlPoint(aRetval.count() - 1, aRight.getControlPointA());
                                            bDone = true;
                                        }
                                    }
 
                                    if(!bDone)
                                    {
                                        const double fRelValue((fFrom - fPositionOfStart) / fEdgeLength);
                                        aRetval.append(interpolate(aStart, aEnd, fRelValue));
                                    }
                                }
 
                                bStartDone = true;
 
                                // if same point, end is done, too.
                                if(rtl::math::approxEqual(fFrom, fTo))
                                {
                                    bEndDone = true;
                                }
                            }
                        }
 
                        if(!bEndDone && fTools::moreOrEqual(fTo, fPositionOfStart) && fTools::less(fTo, fPositionOfStart + fEdgeLength))
                        {
                            // calculate and add end point
                            if(fTools::equalZero(fEdgeLength))
                            {
                                const sal_uInt32 nNextIndex((a + 1) % nPointCount);
                                aRetval.append(rCandidate.getB2DPoint(nNextIndex));
 
                                if(rCandidate.areControlPointsUsed())
                                {
                                    aRetval.setPrevControlPoint(aRetval.count() - 1, rCandidate.getPrevControlPoint(nNextIndex));
                                }
                            }
                            else
                            {
                                const sal_uInt32 nNextIndex((a + 1) % nPointCount);
                                const B2DPoint aStart(rCandidate.getB2DPoint(a));
                                const B2DPoint aEnd(rCandidate.getB2DPoint(nNextIndex));
                                bool bDone(false);
 
                                if(rCandidate.areControlPointsUsed())
                                {
                                    const B2DCubicBezier aBezierSegment(
                                        aStart, rCandidate.getNextControlPoint(a),
                                        rCandidate.getPrevControlPoint(nNextIndex), aEnd);
 
                                    if(aBezierSegment.isBezier())
                                    {
                                        // use B2DCubicBezierHelper to bridge the non-linear gap between
                                        // length and bezier distances
                                        const B2DCubicBezierHelper aBezierSegmentHelper(aBezierSegment);
                                        const double fBezierDistance(aBezierSegmentHelper.distanceToRelative(fTo - fPositionOfStart));
                                        B2DCubicBezier aLeft;
 
                                        aBezierSegment.split(fBezierDistance, &aLeft, nullptr);
                                        aRetval.append(aLeft.getEndPoint());
                                        aRetval.setPrevControlPoint(aRetval.count() - 1, aLeft.getControlPointB());
                                        bDone = true;
                                    }
                                }
 
                                if(!bDone)
                                {
                                    const double fRelValue((fTo - fPositionOfStart) / fEdgeLength);
                                    aRetval.append(interpolate(aStart, aEnd, fRelValue));
                                }
                            }
 
                            bEndDone = true;
                        }
 
                        if(!bEndDone)
                        {
                            if(bStartDone)
                            {
                                // add segments end point
                                const sal_uInt32 nNextIndex((a + 1) % nPointCount);
                                aRetval.append(rCandidate.getB2DPoint(nNextIndex));
 
                                if(rCandidate.areControlPointsUsed())
                                {
                                    aRetval.setPrevControlPoint(aRetval.count() - 1, rCandidate.getPrevControlPoint(nNextIndex));
                                    aRetval.setNextControlPoint(aRetval.count() - 1, rCandidate.getNextControlPoint(nNextIndex));
                                }
                            }
 
                            // increment fPositionOfStart
                            fPositionOfStart += fEdgeLength;
                        }
                    }
                    return aRetval;
                }
            }
            else
            {
                return rCandidate;
            }
        }
 
        CutFlagValue findCut(
            const B2DPoint& rEdge1Start, const B2DVector& rEdge1Delta,
            const B2DPoint& rEdge2Start, const B2DVector& rEdge2Delta,
            CutFlagValue aCutFlags,
            double* pCut1, double* pCut2)
        {
            CutFlagValue aRetval(CutFlagValue::NONE);
            double fCut1(0.0);
            double fCut2(0.0);
            bool bFinished(!static_cast<bool>(aCutFlags & CutFlagValue::ALL));
 
            // test for same points?
            if(!bFinished
                && (aCutFlags & (CutFlagValue::START1|CutFlagValue::END1))
                && (aCutFlags & (CutFlagValue::START2|CutFlagValue::END2)))
            {
                // same startpoint?
                if((aCutFlags & (CutFlagValue::START1|CutFlagValue::START2)) == (CutFlagValue::START1|CutFlagValue::START2))
                {
                    if(rEdge1Start.equal(rEdge2Start))
                    {
                        bFinished = true;
                        aRetval = (CutFlagValue::START1|CutFlagValue::START2);
                    }
                }
 
                // same endpoint?
                if(!bFinished && (aCutFlags & (CutFlagValue::END1|CutFlagValue::END2)) == (CutFlagValue::END1|CutFlagValue::END2))
                {
                    const B2DPoint aEnd1(rEdge1Start + rEdge1Delta);
                    const B2DPoint aEnd2(rEdge2Start + rEdge2Delta);
 
                    if(aEnd1.equal(aEnd2))
                    {
                        bFinished = true;
                        aRetval = (CutFlagValue::END1|CutFlagValue::END2);
                        fCut1 = fCut2 = 1.0;
                    }
                }
 
                // startpoint1 == endpoint2?
                if(!bFinished && (aCutFlags & (CutFlagValue::START1|CutFlagValue::END2)) == (CutFlagValue::START1|CutFlagValue::END2))
                {
                    const B2DPoint aEnd2(rEdge2Start + rEdge2Delta);
 
                    if(rEdge1Start.equal(aEnd2))
                    {
                        bFinished = true;
                        aRetval = (CutFlagValue::START1|CutFlagValue::END2);
                        fCut1 = 0.0;
                        fCut2 = 1.0;
                    }
                }
 
                // startpoint2 == endpoint1?
                if(!bFinished&& (aCutFlags & (CutFlagValue::START2|CutFlagValue::END1)) == (CutFlagValue::START2|CutFlagValue::END1))
                {
                    const B2DPoint aEnd1(rEdge1Start + rEdge1Delta);
 
                    if(rEdge2Start.equal(aEnd1))
                    {
                        bFinished = true;
                        aRetval = (CutFlagValue::START2|CutFlagValue::END1);
                        fCut1 = 1.0;
                        fCut2 = 0.0;
                    }
                }
            }
 
            if(!bFinished && (aCutFlags & CutFlagValue::LINE))
            {
                if(aCutFlags & CutFlagValue::START1)
                {
                    // start1 on line 2 ?
                    if(isPointOnEdge(rEdge1Start, rEdge2Start, rEdge2Delta, &fCut2))
                    {
                        bFinished = true;
                        aRetval = (CutFlagValue::LINE|CutFlagValue::START1);
                    }
                }
 
                if(!bFinished && (aCutFlags & CutFlagValue::START2))
                {
                    // start2 on line 1 ?
                    if(isPointOnEdge(rEdge2Start, rEdge1Start, rEdge1Delta, &fCut1))
                    {
                        bFinished = true;
                        aRetval = (CutFlagValue::LINE|CutFlagValue::START2);
                    }
                }
 
                if(!bFinished && (aCutFlags & CutFlagValue::END1))
                {
                    // end1 on line 2 ?
                    const B2DPoint aEnd1(rEdge1Start + rEdge1Delta);
 
                    if(isPointOnEdge(aEnd1, rEdge2Start, rEdge2Delta, &fCut2))
                    {
                        bFinished = true;
                        aRetval = (CutFlagValue::LINE|CutFlagValue::END1);
                    }
                }
 
                if(!bFinished && (aCutFlags & CutFlagValue::END2))
                {
                    // end2 on line 1 ?
                    const B2DPoint aEnd2(rEdge2Start + rEdge2Delta);
 
                    if(isPointOnEdge(aEnd2, rEdge1Start, rEdge1Delta, &fCut1))
                    {
                        bFinished = true;
                        aRetval = (CutFlagValue::LINE|CutFlagValue::END2);
                    }
                }
 
                if(!bFinished)
                {
                    // cut in line1, line2 ?
                    fCut1 = (rEdge1Delta.getX() * rEdge2Delta.getY()) - (rEdge1Delta.getY() * rEdge2Delta.getX());
 
                    if(!fTools::equalZero(fCut1))
                    {
                        fCut1 = (rEdge2Delta.getY() * (rEdge2Start.getX() - rEdge1Start.getX())
                            + rEdge2Delta.getX() * (rEdge1Start.getY() - rEdge2Start.getY())) / fCut1;
 
                        const double fZero(0.0);
                        const double fOne(1.0);
 
                        // inside parameter range edge1 AND fCut2 is calculable
                        if(fTools::more(fCut1, fZero) && fTools::less(fCut1, fOne)
                            && (!fTools::equalZero(rEdge2Delta.getX()) || !fTools::equalZero(rEdge2Delta.getY())))
                        {
                            // take the more precise calculation of the two possible
                            if(fabs(rEdge2Delta.getX()) > fabs(rEdge2Delta.getY()))
                            {
                                fCut2 = (rEdge1Start.getX() + fCut1
                                    * rEdge1Delta.getX() - rEdge2Start.getX()) / rEdge2Delta.getX();
                            }
                            else
                            {
                                fCut2 = (rEdge1Start.getY() + fCut1
                                    * rEdge1Delta.getY() - rEdge2Start.getY()) / rEdge2Delta.getY();
                            }
 
                            // inside parameter range edge2, too
                            if(fTools::more(fCut2, fZero) && fTools::less(fCut2, fOne))
                            {
                                aRetval = CutFlagValue::LINE;
                            }
                        }
                    }
                }
            }
 
            // copy values if wanted
            if(pCut1)
            {
                *pCut1 = fCut1;
            }
 
            if(pCut2)
            {
                *pCut2 = fCut2;
            }
 
            return aRetval;
        }
 
        bool isPointOnEdge(
            const B2DPoint& rPoint,
            const B2DPoint& rEdgeStart,
            const B2DVector& rEdgeDelta,
            double* pCut)
        {
            bool bDeltaXIsZero(fTools::equalZero(rEdgeDelta.getX()));
            bool bDeltaYIsZero(fTools::equalZero(rEdgeDelta.getY()));
            const double fZero(0.0);
            const double fOne(1.0);
 
            if(bDeltaXIsZero && bDeltaYIsZero)
            {
                // no line, just a point
                return false;
            }
            else if(bDeltaXIsZero)
            {
                // vertical line
                if(fTools::equal(rPoint.getX(), rEdgeStart.getX()))
                {
                    double fValue = (rPoint.getY() - rEdgeStart.getY()) / rEdgeDelta.getY();
 
                    if(fTools::more(fValue, fZero) && fTools::less(fValue, fOne))
                    {
                        if(pCut)
                        {
                            *pCut = fValue;
                        }
 
                        return true;
                    }
                }
            }
            else if(bDeltaYIsZero)
            {
                // horizontal line
                if(fTools::equal(rPoint.getY(), rEdgeStart.getY()))
                {
                    double fValue = (rPoint.getX() - rEdgeStart.getX()) / rEdgeDelta.getX();
 
                    if(fTools::more(fValue, fZero) && fTools::less(fValue, fOne))
                    {
                        if(pCut)
                        {
                            *pCut = fValue;
                        }
 
                        return true;
                    }
                }
            }
            else
            {
                // any angle line
                double fTOne = (rPoint.getX() - rEdgeStart.getX()) / rEdgeDelta.getX();
                double fTTwo = (rPoint.getY() - rEdgeStart.getY()) / rEdgeDelta.getY();
 
                if(fTools::equal(fTOne, fTTwo))
                {
                    // same parameter representation, point is on line. Take
                    // middle value for better results
                    double fValue = (fTOne + fTTwo) / 2.0;
 
                    if(fTools::more(fValue, fZero) && fTools::less(fValue, fOne))
                    {
                        // point is inside line bounds, too
                        if(pCut)
                        {
                            *pCut = fValue;
                        }
 
                        return true;
                    }
                }
            }
 
            return false;
        }
 
        void applyLineDashing(
            const B2DPolygon& rCandidate,
            const std::vector<double>& rDotDashArray,
            B2DPolyPolygon* pLineTarget,
            B2DPolyPolygon* pGapTarget,
            double fDotDashLength)
        {
            // clear targets in any case
            if(pLineTarget)
            {
                pLineTarget->clear();
            }
 
            if(pGapTarget)
            {
                pGapTarget->clear();
            }
 
            // call version that uses callbacks
            applyLineDashing(
                rCandidate,
                rDotDashArray,
                // provide callbacks as lambdas
                (!pLineTarget
                    ? std::function<void(const basegfx::B2DPolygon&)>()
                    : [&pLineTarget](const basegfx::B2DPolygon& rSnippet){ pLineTarget->append(rSnippet); }),
                (!pGapTarget
                    ? std::function<void(const basegfx::B2DPolygon&)>()
                    : [&pGapTarget](const basegfx::B2DPolygon& rSnippet){ pGapTarget->append(rSnippet); }),
                fDotDashLength);
        }
 
        static void implHandleSnippet(
            const B2DPolygon& rSnippet,
            const std::function<void(const basegfx::B2DPolygon& rSnippet)>& rTargetCallback,
            B2DPolygon& rFirst,
            B2DPolygon& rLast)
        {
            if(rSnippet.isClosed())
            {
                if(!rFirst.count())
                {
                    rFirst = rSnippet;
                }
                else
                {
                    if(rLast.count())
                    {
                        rTargetCallback(rLast);
                    }
 
                    rLast = rSnippet;
                }
            }
            else
            {
                rTargetCallback(rSnippet);
            }
        }
 
        static void implHandleFirstLast(
            const std::function<void(const basegfx::B2DPolygon& rSnippet)>& rTargetCallback,
            B2DPolygon& rFirst,
            B2DPolygon& rLast)
        {
            if(rFirst.count() && rLast.count()
                && rFirst.getB2DPoint(0).equal(rLast.getB2DPoint(rLast.count() - 1)))
            {
                // start of first and end of last are the same -> merge them
                rLast.append(rFirst);
                rLast.removeDoublePoints();
                rFirst.clear();
            }
 
            if(rLast.count())
            {
                rTargetCallback(rLast);
            }
 
            if(rFirst.count())
            {
                rTargetCallback(rFirst);
            }
        }
 
        void applyLineDashing(
            const B2DPolygon& rCandidate,
            const std::vector<double>& rDotDashArray,
            const std::function<void(const basegfx::B2DPolygon& rSnippet)>& rLineTargetCallback,
            const std::function<void(const basegfx::B2DPolygon& rSnippet)>& rGapTargetCallback,
            double fDotDashLength)
        {
            const sal_uInt32 nPointCount(rCandidate.count());
            const sal_uInt32 nDotDashCount(rDotDashArray.size());
 
            if(fDotDashLength <= 0.0)
            {
                fDotDashLength = std::accumulate(rDotDashArray.begin(), rDotDashArray.end(), 0.0);
            }
 
            if(fDotDashLength <= 0.0 || (!rLineTargetCallback && !rGapTargetCallback) || !nPointCount)
            {
                // parameters make no sense, just add source to targets
                if (rLineTargetCallback)
                    rLineTargetCallback(rCandidate);
 
                if (rGapTargetCallback)
                    rGapTargetCallback(rCandidate);
 
                return;
            }
 
            // precalculate maximal acceptable length of candidate polygon assuming
            // we want to create a maximum of fNumberOfAllowedSnippets. For
            // fNumberOfAllowedSnippets use ca. 65536, double due to line & gap.
            static const double fNumberOfAllowedSnippets(65535.0 * 2.0);
            const double fAllowedLength((fNumberOfAllowedSnippets * fDotDashLength) / double(rDotDashArray.size()));
            const double fCandidateLength(basegfx::utils::getLength(rCandidate));
            std::vector<double> aDotDashArray(rDotDashArray);
 
            if(fCandidateLength > fAllowedLength)
            {
                // we would produce more than fNumberOfAllowedSnippets, so
                // adapt aDotDashArray to exactly produce assumed number. Also
                // assert this to let the caller know about it.
                // If this asserts: Please think about checking your DotDashArray
                // before calling this function or evtl. use the callback version
                // to *not* produce that much of data. Even then, you may still
                // think about producing too much runtime (!)
                assert(true && "applyLineDashing: potentially too expensive to do the requested dismantle - please consider stretched LineDash pattern (!)");
 
                // calculate correcting factor, apply to aDotDashArray and fDotDashLength
                // to enlarge these as needed
                const double fFactor(fCandidateLength / fAllowedLength);
                std::for_each(aDotDashArray.begin(), aDotDashArray.end(), [&fFactor](double &f){ f *= fFactor; });
            }
 
            // prepare current edge's start
            B2DCubicBezier aCurrentEdge;
            const bool bIsClosed(rCandidate.isClosed());
            const sal_uInt32 nEdgeCount(bIsClosed ? nPointCount : nPointCount - 1);
            aCurrentEdge.setStartPoint(rCandidate.getB2DPoint(0));
 
            // prepare DotDashArray iteration and the line/gap switching bool
            sal_uInt32 nDotDashIndex(0);
            bool bIsLine(true);
            double fDotDashMovingLength(aDotDashArray[0]);
            B2DPolygon aSnippet;
 
            // remember 1st and last snippets to try to merge after execution
            // is complete and hand to callback
            B2DPolygon aFirstLine, aLastLine;
            B2DPolygon aFirstGap, aLastGap;
 
            // iterate over all edges
            for(sal_uInt32 a(0); a < nEdgeCount; a++)
            {
                // update current edge (fill in C1, C2 and end point)
                double fLastDotDashMovingLength(0.0);
                const sal_uInt32 nNextIndex((a + 1) % nPointCount);
                aCurrentEdge.setControlPointA(rCandidate.getNextControlPoint(a));
                aCurrentEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
                aCurrentEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
 
                // check if we have a trivial bezier segment -> possible fallback to edge
                aCurrentEdge.testAndSolveTrivialBezier();
 
                if(aCurrentEdge.isBezier())
                {
                    // bezier segment
                    const B2DCubicBezierHelper aCubicBezierHelper(aCurrentEdge);
                    const double fEdgeLength(aCubicBezierHelper.getLength());
 
                    if(!fTools::equalZero(fEdgeLength))
                    {
                        while(fTools::less(fDotDashMovingLength, fEdgeLength))
                        {
                            // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength]
                            const bool bHandleLine(bIsLine && rLineTargetCallback);
                            const bool bHandleGap(!bIsLine && rGapTargetCallback);
 
                            if(bHandleLine || bHandleGap)
                            {
                                const double fBezierSplitStart(aCubicBezierHelper.distanceToRelative(fLastDotDashMovingLength));
                                const double fBezierSplitEnd(aCubicBezierHelper.distanceToRelative(fDotDashMovingLength));
                                B2DCubicBezier aBezierSnippet(aCurrentEdge.snippet(fBezierSplitStart, fBezierSplitEnd));
 
                                if(!aSnippet.count())
                                {
                                    aSnippet.append(aBezierSnippet.getStartPoint());
                                }
 
                                aSnippet.appendBezierSegment(aBezierSnippet.getControlPointA(), aBezierSnippet.getControlPointB(), aBezierSnippet.getEndPoint());
 
                                if(bHandleLine)
                                {
                                    implHandleSnippet(aSnippet, rLineTargetCallback, aFirstLine, aLastLine);
                                }
 
                                if(bHandleGap)
                                {
                                    implHandleSnippet(aSnippet, rGapTargetCallback, aFirstGap, aLastGap);
                                }
 
                                aSnippet.clear();
                            }
 
                            // prepare next DotDashArray step and flip line/gap flag
                            fLastDotDashMovingLength = fDotDashMovingLength;
                            fDotDashMovingLength += aDotDashArray[(++nDotDashIndex) % nDotDashCount];
                            bIsLine = !bIsLine;
                        }
 
                        // append closing snippet [fLastDotDashMovingLength, fEdgeLength]
                        const bool bHandleLine(bIsLine && rLineTargetCallback);
                        const bool bHandleGap(!bIsLine && rGapTargetCallback);
 
                        if(bHandleLine || bHandleGap)
                        {
                            B2DCubicBezier aRight;
                            const double fBezierSplit(aCubicBezierHelper.distanceToRelative(fLastDotDashMovingLength));
 
                            aCurrentEdge.split(fBezierSplit, nullptr, &aRight);
 
                            if(!aSnippet.count())
                            {
                                aSnippet.append(aRight.getStartPoint());
                            }
 
                            aSnippet.appendBezierSegment(aRight.getControlPointA(), aRight.getControlPointB(), aRight.getEndPoint());
                        }
 
                        // prepare move to next edge
                        fDotDashMovingLength -= fEdgeLength;
                    }
                }
                else
                {
                    // simple edge
                    const double fEdgeLength(aCurrentEdge.getEdgeLength());
 
                    if(!fTools::equalZero(fEdgeLength))
                    {
                        while(fTools::less(fDotDashMovingLength, fEdgeLength))
                        {
                            // new split is inside edge, create and append snippet [fLastDotDashMovingLength, fDotDashMovingLength]
                            const bool bHandleLine(bIsLine && rLineTargetCallback);
                            const bool bHandleGap(!bIsLine && rGapTargetCallback);
 
                            if(bHandleLine || bHandleGap)
                            {
                                if(!aSnippet.count())
                                {
                                    aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fLastDotDashMovingLength / fEdgeLength));
                                }
 
                                aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fDotDashMovingLength / fEdgeLength));
 
                                if(bHandleLine)
                                {
                                    implHandleSnippet(aSnippet, rLineTargetCallback, aFirstLine, aLastLine);
                                }
 
                                if(bHandleGap)
                                {
                                    implHandleSnippet(aSnippet, rGapTargetCallback, aFirstGap, aLastGap);
                                }
 
                                aSnippet.clear();
                            }
 
                            // prepare next DotDashArray step and flip line/gap flag
                            fLastDotDashMovingLength = fDotDashMovingLength;
                            fDotDashMovingLength += aDotDashArray[(++nDotDashIndex) % nDotDashCount];
                            bIsLine = !bIsLine;
                        }
 
                        // append snippet [fLastDotDashMovingLength, fEdgeLength]
                        const bool bHandleLine(bIsLine && rLineTargetCallback);
                        const bool bHandleGap(!bIsLine && rGapTargetCallback);
 
                        if(bHandleLine || bHandleGap)
                        {
                            if(!aSnippet.count())
                            {
                                aSnippet.append(interpolate(aCurrentEdge.getStartPoint(), aCurrentEdge.getEndPoint(), fLastDotDashMovingLength / fEdgeLength));
                            }
 
                            aSnippet.append(aCurrentEdge.getEndPoint());
                        }
 
                        // prepare move to next edge
                        fDotDashMovingLength -= fEdgeLength;
                    }
                }
 
                // prepare next edge step (end point gets new start point)
                aCurrentEdge.setStartPoint(aCurrentEdge.getEndPoint());
            }
 
            // append last intermediate results (if exists)
            if(aSnippet.count())
            {
                const bool bHandleLine(bIsLine && rLineTargetCallback);
                const bool bHandleGap(!bIsLine && rGapTargetCallback);
 
                if(bHandleLine)
                {
                    implHandleSnippet(aSnippet, rLineTargetCallback, aFirstLine, aLastLine);
                }
 
                if(bHandleGap)
                {
                    implHandleSnippet(aSnippet, rGapTargetCallback, aFirstGap, aLastGap);
                }
            }
 
            if(bIsClosed && rLineTargetCallback)
            {
                implHandleFirstLast(rLineTargetCallback, aFirstLine, aLastLine);
            }
 
            if(bIsClosed && rGapTargetCallback)
            {
                implHandleFirstLast(rGapTargetCallback, aFirstGap, aLastGap);
            }
        }
 
        // test if point is inside epsilon-range around an edge defined
        // by the two given points. Can be used for HitTesting. The epsilon-range
        // is defined to be the rectangle centered to the given edge, using height
        // 2 x fDistance, and the circle around both points with radius fDistance.
        bool isInEpsilonRange(const B2DPoint& rEdgeStart, const B2DPoint& rEdgeEnd, const B2DPoint& rTestPosition, double fDistance)
        {
            // build edge vector
            const B2DVector aEdge(rEdgeEnd - rEdgeStart);
            bool bDoDistanceTestStart(false);
            bool bDoDistanceTestEnd(false);
 
            if(aEdge.equalZero())
            {
                // no edge, just a point. Do one of the distance tests.
                bDoDistanceTestStart = true;
            }
            else
            {
                // edge has a length. Create perpendicular vector.
                const B2DVector aPerpend(getPerpendicular(aEdge));
                double fCut(
                    (aPerpend.getY() * (rTestPosition.getX() - rEdgeStart.getX())
                    + aPerpend.getX() * (rEdgeStart.getY() - rTestPosition.getY())) /
                    (aEdge.getX() * aEdge.getX() + aEdge.getY() * aEdge.getY()));
                const double fZero(0.0);
                const double fOne(1.0);
 
                if(fTools::less(fCut, fZero))
                {
                    // left of rEdgeStart
                    bDoDistanceTestStart = true;
                }
                else if(fTools::more(fCut, fOne))
                {
                    // right of rEdgeEnd
                    bDoDistanceTestEnd = true;
                }
                else
                {
                    // inside line [0.0 .. 1.0]
                    const B2DPoint aCutPoint(interpolate(rEdgeStart, rEdgeEnd, fCut));
                    const B2DVector aDelta(rTestPosition - aCutPoint);
                    const double fDistanceSquare(aDelta.scalar(aDelta));
 
                    return fDistanceSquare <= fDistance * fDistance;
                }
            }
 
            if(bDoDistanceTestStart)
            {
                const B2DVector aDelta(rTestPosition - rEdgeStart);
                const double fDistanceSquare(aDelta.scalar(aDelta));
 
                if(fDistanceSquare <= fDistance * fDistance)
                {
                    return true;
                }
            }
            else if(bDoDistanceTestEnd)
            {
                const B2DVector aDelta(rTestPosition - rEdgeEnd);
                const double fDistanceSquare(aDelta.scalar(aDelta));
 
                if(fDistanceSquare <= fDistance * fDistance)
                {
                    return true;
                }
            }
 
            return false;
        }
 
        // test if point is inside epsilon-range around the given Polygon. Can be used
        // for HitTesting. The epsilon-range is defined to be the tube around the polygon
        // with distance fDistance and rounded edges (start and end point).
        bool isInEpsilonRange(const B2DPolygon& rCandidate, const B2DPoint& rTestPosition, double fDistance)
        {
            // force to non-bezier polygon
            const B2DPolygon& aCandidate(rCandidate.getDefaultAdaptiveSubdivision());
            const sal_uInt32 nPointCount(aCandidate.count());
 
            if(!nPointCount)
                return false;
 
            const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1);
            B2DPoint aCurrent(aCandidate.getB2DPoint(0));
 
            if(nEdgeCount)
            {
                // edges
                for(sal_uInt32 a(0); a < nEdgeCount; a++)
                {
                    const sal_uInt32 nNextIndex((a + 1) % nPointCount);
                    const B2DPoint aNext(aCandidate.getB2DPoint(nNextIndex));
 
                    if(isInEpsilonRange(aCurrent, aNext, rTestPosition, fDistance))
                    {
                        return true;
                    }
 
                    // prepare next step
                    aCurrent = aNext;
                }
            }
            else
            {
                // no edges, but points -> not closed. Check single point. Just
                // use isInEpsilonRange with twice the same point, it handles those well
                if(isInEpsilonRange(aCurrent, aCurrent, rTestPosition, fDistance))
                {
                    return true;
                }
            }
 
            return false;
        }
 
        // Calculates distance of curve point to its control point for a Bézier curve, that
        // approximates a unit circle arc. fAngle is the center angle of the circle arc. The
        // constrain 0<=fAngle<=pi/2 must not be violated to give a useful accuracy. For details
        // and alternatives read document "ApproxCircleInfo.odt", attachment of bug tdf#121425.
        static double impDistanceBezierPointToControl(double fAngle)
        {
            SAL_WARN_IF(fAngle < 0 || fAngle > M_PI_2,"basegfx","angle not suitable for approximate circle");
            if (0 <= fAngle && fAngle <= M_PI_2)
            {
                return 4.0/3.0 * ( tan(fAngle/4.0));
            }
            else
                return 0;
        }
 
        B2DPolygon createPolygonFromRect( const B2DRectangle& rRect, double fRadiusX, double fRadiusY )
        {
            const double fZero(0.0);
            const double fOne(1.0);
 
            fRadiusX = std::clamp(fRadiusX, 0.0, 1.0);
            fRadiusY = std::clamp(fRadiusY, 0.0, 1.0);
 
            if(rtl::math::approxEqual(fZero, fRadiusX) || rtl::math::approxEqual(fZero, fRadiusY))
            {
                // at least in one direction no radius, use rectangle.
                // Do not use createPolygonFromRect() here since original
                // creator (historical reasons) still creates a start point at the
                // bottom center, so do the same here to get the same line patterns.
                // Due to this the order of points is different, too.
                const B2DPoint aBottomCenter(rRect.getCenter().getX(), rRect.getMaxY());
                B2DPolygon aPolygon {
                    aBottomCenter,
                    { rRect.getMinX(), rRect.getMaxY() },
                    { rRect.getMinX(), rRect.getMinY() },
                    { rRect.getMaxX(), rRect.getMinY() },
                    { rRect.getMaxX(), rRect.getMaxY() }
                };
 
                // close
                aPolygon.setClosed( true );
 
                return aPolygon;
            }
            else if(rtl::math::approxEqual(fOne, fRadiusX) && rtl::math::approxEqual(fOne, fRadiusY))
            {
                // in both directions full radius, use ellipse
                const B2DPoint aCenter(rRect.getCenter());
                const double fRectRadiusX(rRect.getWidth() / 2.0);
                const double fRectRadiusY(rRect.getHeight() / 2.0);
 
                return createPolygonFromEllipse( aCenter, fRectRadiusX, fRectRadiusY );
            }
            else
            {
                B2DPolygon aRetval;
                const double fBowX((rRect.getWidth() / 2.0) * fRadiusX);
                const double fBowY((rRect.getHeight() / 2.0) * fRadiusY);
                const double fKappa(impDistanceBezierPointToControl(M_PI_2));
 
                // create start point at bottom center
                if(!rtl::math::approxEqual(fOne, fRadiusX))
                {
                    const B2DPoint aBottomCenter(rRect.getCenter().getX(), rRect.getMaxY());
                    aRetval.append(aBottomCenter);
                }
 
                // create first bow
                {
                    const B2DPoint aBottomRight(rRect.getMaxX(), rRect.getMaxY());
                    const B2DPoint aStart(aBottomRight + B2DPoint(-fBowX, 0.0));
                    const B2DPoint aStop(aBottomRight + B2DPoint(0.0, -fBowY));
                    aRetval.append(aStart);
                    aRetval.appendBezierSegment(interpolate(aStart, aBottomRight, fKappa), interpolate(aStop, aBottomRight, fKappa), aStop);
                }
 
                // create second bow
                {
                    const B2DPoint aTopRight(rRect.getMaxX(), rRect.getMinY());
                    const B2DPoint aStart(aTopRight + B2DPoint(0.0, fBowY));
                    const B2DPoint aStop(aTopRight + B2DPoint(-fBowX, 0.0));
                    aRetval.append(aStart);
                    aRetval.appendBezierSegment(interpolate(aStart, aTopRight, fKappa), interpolate(aStop, aTopRight, fKappa), aStop);
                }
 
                // create third bow
                {
                    const B2DPoint aTopLeft(rRect.getMinX(), rRect.getMinY());
                    const B2DPoint aStart(aTopLeft + B2DPoint(fBowX, 0.0));
                    const B2DPoint aStop(aTopLeft + B2DPoint(0.0, fBowY));
                    aRetval.append(aStart);
                    aRetval.appendBezierSegment(interpolate(aStart, aTopLeft, fKappa), interpolate(aStop, aTopLeft, fKappa), aStop);
                }
 
                // create forth bow
                {
                    const B2DPoint aBottomLeft(rRect.getMinX(), rRect.getMaxY());
                    const B2DPoint aStart(aBottomLeft + B2DPoint(0.0, -fBowY));
                    const B2DPoint aStop(aBottomLeft + B2DPoint(fBowX, 0.0));
                    aRetval.append(aStart);
                    aRetval.appendBezierSegment(interpolate(aStart, aBottomLeft, fKappa), interpolate(aStop, aBottomLeft, fKappa), aStop);
                }
 
                // close
                aRetval.setClosed( true );
 
                // remove double created points if there are extreme radii involved
                if(rtl::math::approxEqual(fOne, fRadiusX) || rtl::math::approxEqual(fOne, fRadiusY))
                {
                    aRetval.removeDoublePoints();
                }
 
                return aRetval;
            }
        }
 
        B2DPolygon createPolygonFromRect( const B2DRectangle& rRect )
        {
            B2DPolygon aPolygon {
                { rRect.getMinX(), rRect.getMinY() },
                { rRect.getMaxX(), rRect.getMinY() },
                { rRect.getMaxX(), rRect.getMaxY() },
                { rRect.getMinX(), rRect.getMaxY() }
            };
 
            // close
            aPolygon.setClosed( true );
 
            return aPolygon;
        }
 
        B2DPolygon const & createUnitPolygon()
        {
            static auto const singleton = [] {
                    B2DPolygon aPolygon {
                        { 0.0, 0.0 },
                        { 1.0, 0.0 },
                        { 1.0, 1.0 },
                        { 0.0, 1.0 }
                    };
 
                    // close
                    aPolygon.setClosed( true );
 
                    return aPolygon;
            }();
            return singleton;
        }
 
        B2DPolygon createPolygonFromCircle( const B2DPoint& rCenter, double fRadius )
        {
            return createPolygonFromEllipse( rCenter, fRadius, fRadius );
        }
 
        static B2DPolygon impCreateUnitCircle(sal_uInt32 nStartQuadrant)
        {
            B2DPolygon aUnitCircle;
            const double fSegmentKappa = impDistanceBezierPointToControl(M_PI_2 / STEPSPERQUARTER);
            const B2DHomMatrix aRotateMatrix(createRotateB2DHomMatrix(M_PI_2 / STEPSPERQUARTER));
 
            B2DPoint aPoint(1.0, 0.0);
            B2DPoint aForward(1.0, fSegmentKappa);
            B2DPoint aBackward(1.0, -fSegmentKappa);
 
            if(nStartQuadrant != 0)
            {
                const B2DHomMatrix aQuadrantMatrix(createRotateB2DHomMatrix(M_PI_2 * (nStartQuadrant % 4)));
                aPoint *= aQuadrantMatrix;
                aBackward *= aQuadrantMatrix;
                aForward *= aQuadrantMatrix;
            }
 
            aUnitCircle.append(aPoint);
 
            for(sal_uInt32 a(0); a < STEPSPERQUARTER * 4; a++)
            {
                aPoint *= aRotateMatrix;
                aBackward *= aRotateMatrix;
                aUnitCircle.appendBezierSegment(aForward, aBackward, aPoint);
                aForward *= aRotateMatrix;
            }
 
            aUnitCircle.setClosed(true);
            aUnitCircle.removeDoublePoints();
 
            return aUnitCircle;
        }
 
        B2DPolygon const & createHalfUnitCircle()
        {
            static auto const singleton = [] {
                    B2DPolygon aUnitHalfCircle;
                    const double fSegmentKappa(impDistanceBezierPointToControl(M_PI_2 / STEPSPERQUARTER));
                    const B2DHomMatrix aRotateMatrix(createRotateB2DHomMatrix(M_PI_2 / STEPSPERQUARTER));
                    B2DPoint aPoint(1.0, 0.0);
                    B2DPoint aForward(1.0, fSegmentKappa);
                    B2DPoint aBackward(1.0, -fSegmentKappa);
 
                    aUnitHalfCircle.append(aPoint);
 
                    for(sal_uInt32 a(0); a < STEPSPERQUARTER * 2; a++)
                    {
                        aPoint *= aRotateMatrix;
                        aBackward *= aRotateMatrix;
                        aUnitHalfCircle.appendBezierSegment(aForward, aBackward, aPoint);
                        aForward *= aRotateMatrix;
                    }
                    return aUnitHalfCircle;
                }();
            return singleton;
        }
 
        B2DPolygon const & createPolygonFromUnitCircle(sal_uInt32 nStartQuadrant)
        {
            switch(nStartQuadrant % 4)
            {
                case 1 :
                    {
                        static auto const singleton = impCreateUnitCircle(1);
                        return singleton;
                    }
 
                case 2 :
                    {
                        static auto const singleton = impCreateUnitCircle(2);
                        return singleton;
                    }
 
                case 3 :
                    {
                        static auto const singleton = impCreateUnitCircle(3);
                        return singleton;
                    }
 
                default : // case 0 :
                    {
                        static auto const singleton = impCreateUnitCircle(0);
                        return singleton;
                    }
            }
        }
 
        B2DPolygon createPolygonFromEllipse( const B2DPoint& rCenter, double fRadiusX, double fRadiusY, sal_uInt32 nStartQuadrant)
        {
            B2DPolygon aRetval(createPolygonFromUnitCircle(nStartQuadrant));
            const B2DHomMatrix aMatrix(createScaleTranslateB2DHomMatrix(fRadiusX, fRadiusY, rCenter.getX(), rCenter.getY()));
 
            aRetval.transform(aMatrix);
 
            return aRetval;
        }
 
        B2DPolygon createPolygonFromUnitEllipseSegment( double fStart, double fEnd )
        {
            B2DPolygon aRetval;
 
            // truncate fStart, fEnd to a range of [0.0 .. 2PI[ where 2PI
            // falls back to 0.0 to ensure a unique definition
            if(fStart < 0.0)
            {
                fStart = 0.0;
            }
 
            if(fTools::moreOrEqual(fStart, 2 * M_PI))
            {
                fStart = 0.0;
            }
 
            if(fEnd < 0.0)
            {
                fEnd = 0.0;
            }
 
            if(fTools::moreOrEqual(fEnd, 2 * M_PI))
            {
                fEnd = 0.0;
            }
 
            if(fTools::equal(fStart, fEnd))
            {
                // same start and end angle, add single point
                aRetval.append(B2DPoint(cos(fStart), sin(fStart)));
            }
            else
            {
                const sal_uInt32 nSegments(STEPSPERQUARTER * 4);
                const double fAnglePerSegment(M_PI_2 / STEPSPERQUARTER);
                const sal_uInt32 nStartSegment(sal_uInt32(fStart / fAnglePerSegment) % nSegments);
                const sal_uInt32 nEndSegment(sal_uInt32(fEnd / fAnglePerSegment) % nSegments);
                const double fSegmentKappa(impDistanceBezierPointToControl(fAnglePerSegment));
 
                B2DPoint aSegStart(cos(fStart), sin(fStart));
                aRetval.append(aSegStart);
 
                if(nStartSegment == nEndSegment && fTools::more(fEnd, fStart))
                {
                    // start and end in one sector and in the right order, create in one segment
                    const B2DPoint aSegEnd(cos(fEnd), sin(fEnd));
                    const double fFactor(impDistanceBezierPointToControl(fEnd - fStart));
 
                    aRetval.appendBezierSegment(
                        aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor),
                        aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor),
                        aSegEnd);
                }
                else
                {
                    double fSegEndRad((nStartSegment + 1) * fAnglePerSegment);
                    double fFactor(impDistanceBezierPointToControl(fSegEndRad - fStart));
                    B2DPoint aSegEnd(cos(fSegEndRad), sin(fSegEndRad));
 
                    aRetval.appendBezierSegment(
                        aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor),
                        aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor),
                        aSegEnd);
 
                    sal_uInt32 nSegment((nStartSegment + 1) % nSegments);
                    aSegStart = aSegEnd;
 
                    while(nSegment != nEndSegment)
                    {
                        // No end in this sector, add full sector.
                        fSegEndRad = (nSegment + 1) * fAnglePerSegment;
                        aSegEnd = B2DPoint(cos(fSegEndRad), sin(fSegEndRad));
 
                        aRetval.appendBezierSegment(
                            aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fSegmentKappa),
                            aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fSegmentKappa),
                            aSegEnd);
 
                        nSegment = (nSegment + 1) % nSegments;
                        aSegStart = aSegEnd;
                    }
 
                    // End in this sector
                    const double fSegStartRad(nSegment * fAnglePerSegment);
                    fFactor= impDistanceBezierPointToControl(fEnd - fSegStartRad);
                    aSegEnd = B2DPoint(cos(fEnd), sin(fEnd));
 
                    aRetval.appendBezierSegment(
                        aSegStart + (B2DPoint(-aSegStart.getY(), aSegStart.getX()) * fFactor),
                        aSegEnd - (B2DPoint(-aSegEnd.getY(), aSegEnd.getX()) * fFactor),
                        aSegEnd);
                }
            }
 
            // remove double points between segments created by segmented creation
            aRetval.removeDoublePoints();
 
            return aRetval;
        }
 
        B2DPolygon createPolygonFromEllipseSegment( const B2DPoint& rCenter, double fRadiusX, double fRadiusY, double fStart, double fEnd )
        {
            B2DPolygon aRetval(createPolygonFromUnitEllipseSegment(fStart, fEnd));
            const B2DHomMatrix aMatrix(createScaleTranslateB2DHomMatrix(fRadiusX, fRadiusY, rCenter.getX(), rCenter.getY()));
 
            aRetval.transform(aMatrix);
 
            return aRetval;
        }
 
        bool hasNeutralPoints(const B2DPolygon& rCandidate)
        {
            OSL_ENSURE(!rCandidate.areControlPointsUsed(), "hasNeutralPoints: ATM works not for curves (!)");
            const sal_uInt32 nPointCount(rCandidate.count());
 
            if(nPointCount <= 2)
                return false;
 
            B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1));
            B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
 
            for(sal_uInt32 a(0); a < nPointCount; a++)
            {
                const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount));
                const B2DVector aPrevVec(aPrevPoint - aCurrPoint);
                const B2DVector aNextVec(aNextPoint - aCurrPoint);
                const B2VectorOrientation aOrientation(getOrientation(aNextVec, aPrevVec));
 
                if(aOrientation == B2VectorOrientation::Neutral)
                {
                    // current has neutral orientation
                    return true;
                }
                else
                {
                    // prepare next
                    aPrevPoint = aCurrPoint;
                    aCurrPoint = aNextPoint;
                }
            }
 
            return false;
        }
 
        B2DPolygon removeNeutralPoints(const B2DPolygon& rCandidate)
        {
            if(hasNeutralPoints(rCandidate))
            {
                const sal_uInt32 nPointCount(rCandidate.count());
                B2DPolygon aRetval;
                B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1));
                B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
 
                for(sal_uInt32 a(0); a < nPointCount; a++)
                {
                    const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount));
                    const B2DVector aPrevVec(aPrevPoint - aCurrPoint);
                    const B2DVector aNextVec(aNextPoint - aCurrPoint);
                    const B2VectorOrientation aOrientation(getOrientation(aNextVec, aPrevVec));
 
                    if(aOrientation == B2VectorOrientation::Neutral)
                    {
                        // current has neutral orientation, leave it out and prepare next
                        aCurrPoint = aNextPoint;
                    }
                    else
                    {
                        // add current point
                        aRetval.append(aCurrPoint);
 
                        // prepare next
                        aPrevPoint = aCurrPoint;
                        aCurrPoint = aNextPoint;
                    }
                }
 
                while(aRetval.count() && getOrientationForIndex(aRetval, 0) == B2VectorOrientation::Neutral)
                {
                    aRetval.remove(0);
                }
 
                // copy closed state
                aRetval.setClosed(rCandidate.isClosed());
 
                return aRetval;
            }
            else
            {
                return rCandidate;
            }
        }
 
        bool isConvex(const B2DPolygon& rCandidate)
        {
            OSL_ENSURE(!rCandidate.areControlPointsUsed(), "isConvex: ATM works not for curves (!)");
            const sal_uInt32 nPointCount(rCandidate.count());
 
            if(nPointCount <= 2)
                return true;
 
            const B2DPoint aPrevPoint(rCandidate.getB2DPoint(nPointCount - 1));
            B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
            B2DVector aCurrVec(aPrevPoint - aCurrPoint);
            B2VectorOrientation aOrientation(B2VectorOrientation::Neutral);
 
            for(sal_uInt32 a(0); a < nPointCount; a++)
            {
                const B2DPoint aNextPoint(rCandidate.getB2DPoint((a + 1) % nPointCount));
                const B2DVector aNextVec(aNextPoint - aCurrPoint);
                const B2VectorOrientation aCurrentOrientation(getOrientation(aNextVec, aCurrVec));
 
                if(aOrientation == B2VectorOrientation::Neutral)
                {
                    // set start value, maybe neutral again
                    aOrientation = aCurrentOrientation;
                }
                else
                {
                    if(aCurrentOrientation != B2VectorOrientation::Neutral && aCurrentOrientation != aOrientation)
                    {
                        // different orientations found, that's it
                        return false;
                    }
                }
 
                // prepare next
                aCurrPoint = aNextPoint;
                aCurrVec = -aNextVec;
            }
 
            return true;
        }
 
        B2VectorOrientation getOrientationForIndex(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
        {
            OSL_ENSURE(nIndex < rCandidate.count(), "getOrientationForIndex: index out of range (!)");
            const B2DPoint aPrev(rCandidate.getB2DPoint(getIndexOfPredecessor(nIndex, rCandidate)));
            const B2DPoint aCurr(rCandidate.getB2DPoint(nIndex));
            const B2DPoint aNext(rCandidate.getB2DPoint(getIndexOfSuccessor(nIndex, rCandidate)));
            const B2DVector aBack(aPrev - aCurr);
            const B2DVector aForw(aNext - aCurr);
 
            return getOrientation(aForw, aBack);
        }
 
        bool isPointOnLine(const B2DPoint& rStart, const B2DPoint& rEnd, const B2DPoint& rCandidate, bool bWithPoints)
        {
            if(rCandidate.equal(rStart) || rCandidate.equal(rEnd))
            {
                // candidate is in epsilon around start or end -> inside
                return bWithPoints;
            }
            else if(rStart.equal(rEnd))
            {
                // start and end are equal, but candidate is outside their epsilon -> outside
                return false;
            }
            else
            {
                const B2DVector aEdgeVector(rEnd - rStart);
                const B2DVector aTestVector(rCandidate - rStart);
 
                if(areParallel(aEdgeVector, aTestVector))
                {
                    const double fZero(0.0);
                    const double fOne(1.0);
                    const double fParamTestOnCurr(fabs(aEdgeVector.getX()) > fabs(aEdgeVector.getY())
                        ? aTestVector.getX() / aEdgeVector.getX()
                        : aTestVector.getY() / aEdgeVector.getY());
 
                    if(fTools::more(fParamTestOnCurr, fZero) && fTools::less(fParamTestOnCurr, fOne))
                    {
                        return true;
                    }
                }
 
                return false;
            }
        }
 
        bool isPointOnPolygon(const B2DPolygon& rCandidate, const B2DPoint& rPoint, bool bWithPoints)
        {
            const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
            const sal_uInt32 nPointCount(aCandidate.count());
 
            if(nPointCount > 1)
            {
                const sal_uInt32 nLoopCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1);
                B2DPoint aCurrentPoint(aCandidate.getB2DPoint(0));
 
                for(sal_uInt32 a(0); a < nLoopCount; a++)
                {
                    const B2DPoint aNextPoint(aCandidate.getB2DPoint((a + 1) % nPointCount));
 
                    if(isPointOnLine(aCurrentPoint, aNextPoint, rPoint, bWithPoints))
                    {
                        return true;
                    }
 
                    aCurrentPoint = aNextPoint;
                }
            }
            else if(nPointCount && bWithPoints)
            {
                return rPoint.equal(aCandidate.getB2DPoint(0));
            }
 
            return false;
        }
 
        bool isPointInTriangle(const B2DPoint& rA, const B2DPoint& rB, const B2DPoint& rC, const B2DPoint& rCandidate, bool bWithBorder)
        {
            if(arePointsOnSameSideOfLine(rA, rB, rC, rCandidate, bWithBorder))
            {
                if(arePointsOnSameSideOfLine(rB, rC, rA, rCandidate, bWithBorder))
                {
                    if(arePointsOnSameSideOfLine(rC, rA, rB, rCandidate, bWithBorder))
                    {
                        return true;
                    }
                }
            }
 
            return false;
        }
 
        bool arePointsOnSameSideOfLine(const B2DPoint& rStart, const B2DPoint& rEnd, const B2DPoint& rCandidateA, const B2DPoint& rCandidateB, bool bWithLine)
        {
            const B2DVector aLineVector(rEnd - rStart);
            const B2DVector aVectorToA(rEnd - rCandidateA);
            const double fCrossA(aLineVector.cross(aVectorToA));
 
            // tdf#88352 increase numerical correctness and use rtl::math::approxEqual
            // instead of fTools::equalZero which compares with a fixed small value
            if(fCrossA == 0.0)
            {
                // one point on the line
                return bWithLine;
            }
 
            const B2DVector aVectorToB(rEnd - rCandidateB);
            const double fCrossB(aLineVector.cross(aVectorToB));
 
            // increase numerical correctness
            if(fCrossB == 0.0)
            {
                // one point on the line
                return bWithLine;
            }
 
            // return true if they both have the same sign
            return ((fCrossA > 0.0) == (fCrossB > 0.0));
        }
 
        void addTriangleFan(
            const B2DPolygon& rCandidate,
            triangulator::B2DTriangleVector& rTarget)
        {
            const sal_uInt32 nCount(rCandidate.count());
 
            if(nCount <= 2)
                return;
 
            const B2DPoint aStart(rCandidate.getB2DPoint(0));
            B2DPoint aLast(rCandidate.getB2DPoint(1));
 
            for(sal_uInt32 a(2); a < nCount; a++)
            {
                const B2DPoint aCurrent(rCandidate.getB2DPoint(a));
                rTarget.emplace_back(
                    aStart,
                    aLast,
                    aCurrent);
 
                // prepare next
                aLast = aCurrent;
            }
        }
 
        namespace
        {
            /// return 0 for input of 0, -1 for negative and 1 for positive input
            int lcl_sgn( const double n )
            {
                return n == 0.0 ? 0 : 1 - 2*int(std::signbit(n));
            }
        }
 
        bool isRectangle( const B2DPolygon& rPoly )
        {
            // polygon must be closed to resemble a rect, and contain
            // at least four points.
            if( !rPoly.isClosed() ||
                rPoly.count() < 4 ||
                rPoly.areControlPointsUsed() )
            {
                return false;
            }
 
            // number of 90 degree turns the polygon has taken
            int nNumTurns(0);
 
            int  nVerticalEdgeType=0;
            int  nHorizontalEdgeType=0;
            bool bNullVertex(true);
            bool bCWPolygon(false);  // when true, polygon is CW
                                     // oriented, when false, CCW
            bool bOrientationSet(false); // when false, polygon
                                         // orientation has not yet
                                         // been determined.
 
            // scan all _edges_ (which involves coming back to point 0
            // for the last edge - thus the modulo operation below)
            const sal_Int32 nCount( rPoly.count() );
            for( sal_Int32 i=0; i<nCount; ++i )
            {
                const B2DPoint& rPoint0( rPoly.getB2DPoint(i % nCount) );
                const B2DPoint& rPoint1( rPoly.getB2DPoint((i+1) % nCount) );
 
                // is 0 for zero direction vector, 1 for south edge and -1
                // for north edge (standard screen coordinate system)
                int nCurrVerticalEdgeType( lcl_sgn( rPoint1.getY() - rPoint0.getY() ) );
 
                // is 0 for zero direction vector, 1 for east edge and -1
                // for west edge (standard screen coordinate system)
                int nCurrHorizontalEdgeType( lcl_sgn(rPoint1.getX() - rPoint0.getX()) );
 
                if( nCurrVerticalEdgeType && nCurrHorizontalEdgeType )
                    return false; // oblique edge - for sure no rect
 
                const bool bCurrNullVertex( !nCurrVerticalEdgeType && !nCurrHorizontalEdgeType );
 
                // current vertex is equal to previous - just skip,
                // until we have a real edge
                if( bCurrNullVertex )
                    continue;
 
                // if previous edge has two identical points, because
                // no previous edge direction was available, simply
                // take this first non-null edge as the start
                // direction. That's what will happen here, if
                // bNullVertex is false
                if( !bNullVertex )
                {
                    // 2D cross product - is 1 for CW and -1 for CCW turns
                    const int nCrossProduct( nHorizontalEdgeType*nCurrVerticalEdgeType -
                                             nVerticalEdgeType*nCurrHorizontalEdgeType );
 
                    if( !nCrossProduct )
                        continue; // no change in orientation -
                                  // collinear edges - just go on
 
                    // if polygon orientation is not set, we'll
                    // determine it now
                    if( !bOrientationSet )
                    {
                        bCWPolygon = nCrossProduct == 1;
                        bOrientationSet = true;
                    }
                    else
                    {
                        // if current turn orientation is not equal
                        // initial orientation, this is not a
                        // rectangle (as rectangles have consistent
                        // orientation).
                        if( (nCrossProduct == 1) != bCWPolygon )
                            return false;
                    }
 
                    ++nNumTurns;
 
                    // More than four 90 degree turns are an
                    // indication that this must not be a rectangle.
                    if( nNumTurns > 4 )
                        return false;
                }
 
                // store current state for the next turn
                nVerticalEdgeType   = nCurrVerticalEdgeType;
                nHorizontalEdgeType = nCurrHorizontalEdgeType;
                bNullVertex         = false; // won't reach this line,
                                             // if bCurrNullVertex is
                                             // true - see above
            }
 
            return true;
        }
 
        B3DPolygon createB3DPolygonFromB2DPolygon(const B2DPolygon& rCandidate, double fZCoordinate)
        {
            if(rCandidate.areControlPointsUsed())
            {
                // call myself recursively with subdivided input
                const B2DPolygon aCandidate(adaptiveSubdivideByAngle(rCandidate));
                return createB3DPolygonFromB2DPolygon(aCandidate, fZCoordinate);
            }
            else
            {
                B3DPolygon aRetval;
 
                for(sal_uInt32 a(0); a < rCandidate.count(); a++)
                {
                    B2DPoint aPoint(rCandidate.getB2DPoint(a));
                    aRetval.append(B3DPoint(aPoint.getX(), aPoint.getY(), fZCoordinate));
                }
 
                // copy closed state
                aRetval.setClosed(rCandidate.isClosed());
 
                return aRetval;
            }
        }
 
        B2DPolygon createB2DPolygonFromB3DPolygon(const B3DPolygon& rCandidate, const B3DHomMatrix& rMat)
        {
            B2DPolygon aRetval;
            const sal_uInt32 nCount(rCandidate.count());
            const bool bIsIdentity(rMat.isIdentity());
 
            for(sal_uInt32 a(0); a < nCount; a++)
            {
                B3DPoint aCandidate(rCandidate.getB3DPoint(a));
 
                if(!bIsIdentity)
                {
                    aCandidate *= rMat;
                }
 
                aRetval.append(B2DPoint(aCandidate.getX(), aCandidate.getY()));
            }
 
            // copy closed state
            aRetval.setClosed(rCandidate.isClosed());
 
            return aRetval;
        }
 
        double getSmallestDistancePointToEdge(const B2DPoint& rPointA, const B2DPoint& rPointB, const B2DPoint& rTestPoint, double& rCut)
        {
            if(rPointA.equal(rPointB))
            {
                rCut = 0.0;
                const B2DVector aVector(rTestPoint - rPointA);
                return aVector.getLength();
            }
            else
            {
                // get the relative cut value on line vector (Vector1) for cut with perpendicular through TestPoint
                const B2DVector aVector1(rPointB - rPointA);
                const B2DVector aVector2(rTestPoint - rPointA);
                const double fDividend((aVector2.getX() * aVector1.getX()) + (aVector2.getY() * aVector1.getY()));
                const double fDivisor((aVector1.getX() * aVector1.getX()) + (aVector1.getY() * aVector1.getY()));
                const double fCut(fDividend / fDivisor);
 
                if(fCut < 0.0)
                {
                    // not in line range, get distance to PointA
                    rCut = 0.0;
                    return aVector2.getLength();
                }
                else if(fCut > 1.0)
                {
                    // not in line range, get distance to PointB
                    rCut = 1.0;
                    const B2DVector aVector(rTestPoint - rPointB);
                    return aVector.getLength();
                }
                else
                {
                    // in line range
                    const B2DPoint aCutPoint(rPointA + fCut * aVector1);
                    const B2DVector aVector(rTestPoint - aCutPoint);
                    rCut = fCut;
                    return aVector.getLength();
                }
            }
        }
 
        double getSmallestDistancePointToPolygon(const B2DPolygon& rCandidate, const B2DPoint& rTestPoint, sal_uInt32& rEdgeIndex, double& rCut)
        {
            double fRetval(DBL_MAX);
            const sal_uInt32 nPointCount(rCandidate.count());
 
            if(nPointCount > 1)
            {
                const double fZero(0.0);
                const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
                B2DCubicBezier aBezier;
                aBezier.setStartPoint(rCandidate.getB2DPoint(0));
 
                for(sal_uInt32 a(0); a < nEdgeCount; a++)
                {
                    const sal_uInt32 nNextIndex((a + 1) % nPointCount);
                    aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
                    double fEdgeDist;
                    double fNewCut(0.0);
                    bool bEdgeIsCurve(false);
 
                    if(rCandidate.areControlPointsUsed())
                    {
                        aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
                        aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
                        aBezier.testAndSolveTrivialBezier();
                        bEdgeIsCurve = aBezier.isBezier();
                    }
 
                    if(bEdgeIsCurve)
                    {
                        fEdgeDist = aBezier.getSmallestDistancePointToBezierSegment(rTestPoint, fNewCut);
                    }
                    else
                    {
                        fEdgeDist = getSmallestDistancePointToEdge(aBezier.getStartPoint(), aBezier.getEndPoint(), rTestPoint, fNewCut);
                    }
 
                    if(fRetval == DBL_MAX || fEdgeDist < fRetval)
                    {
                        fRetval = fEdgeDist;
                        rEdgeIndex = a;
                        rCut = fNewCut;
 
                        if(fTools::equal(fRetval, fZero))
                        {
                            // already found zero distance, cannot get better. Ensure numerical zero value and end loop.
                            fRetval = 0.0;
                            break;
                        }
                    }
 
                    // prepare next step
                    aBezier.setStartPoint(aBezier.getEndPoint());
                }
 
                if(rtl::math::approxEqual(1.0, rCut))
                {
                    // correct rEdgeIndex when not last point
                    if(rCandidate.isClosed())
                    {
                        rEdgeIndex = getIndexOfSuccessor(rEdgeIndex, rCandidate);
                        rCut = 0.0;
                    }
                    else
                    {
                        if(rEdgeIndex != nEdgeCount - 1)
                        {
                            rEdgeIndex++;
                            rCut = 0.0;
                        }
                    }
                }
            }
 
            return fRetval;
        }
 
        B2DPoint distort(const B2DPoint& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight)
        {
            if(fTools::equalZero(rOriginal.getWidth()) || fTools::equalZero(rOriginal.getHeight()))
            {
                return rCandidate;
            }
            else
            {
                const double fRelativeX((rCandidate.getX() - rOriginal.getMinX()) / rOriginal.getWidth());
                const double fRelativeY((rCandidate.getY() - rOriginal.getMinY()) / rOriginal.getHeight());
                const double fOneMinusRelativeX(1.0 - fRelativeX);
                const double fOneMinusRelativeY(1.0 - fRelativeY);
                const double fNewX(fOneMinusRelativeY * (fOneMinusRelativeX * rTopLeft.getX() + fRelativeX * rTopRight.getX()) +
                    fRelativeY * (fOneMinusRelativeX * rBottomLeft.getX() + fRelativeX * rBottomRight.getX()));
                const double fNewY(fOneMinusRelativeX * (fOneMinusRelativeY * rTopLeft.getY() + fRelativeY * rBottomLeft.getY()) +
                    fRelativeX * (fOneMinusRelativeY * rTopRight.getY() + fRelativeY * rBottomRight.getY()));
 
                return B2DPoint(fNewX, fNewY);
            }
        }
 
        B2DPolygon distort(const B2DPolygon& rCandidate, const B2DRange& rOriginal, const B2DPoint& rTopLeft, const B2DPoint& rTopRight, const B2DPoint& rBottomLeft, const B2DPoint& rBottomRight)
        {
            const sal_uInt32 nPointCount(rCandidate.count());
 
            if(nPointCount && rOriginal.getWidth() != 0.0 && rOriginal.getHeight() != 0.0)
            {
                B2DPolygon aRetval;
 
                for(sal_uInt32 a(0); a < nPointCount; a++)
                {
                    aRetval.append(distort(rCandidate.getB2DPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight));
 
                    if(rCandidate.areControlPointsUsed())
                    {
                        if(!rCandidate.getPrevControlPoint(a).equalZero())
                        {
                            aRetval.setPrevControlPoint(a, distort(rCandidate.getPrevControlPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight));
                        }
 
                        if(!rCandidate.getNextControlPoint(a).equalZero())
                        {
                            aRetval.setNextControlPoint(a, distort(rCandidate.getNextControlPoint(a), rOriginal, rTopLeft, rTopRight, rBottomLeft, rBottomRight));
                        }
                    }
                }
 
                aRetval.setClosed(rCandidate.isClosed());
                return aRetval;
            }
            else
            {
                return rCandidate;
            }
        }
 
        B2DPolygon expandToCurve(const B2DPolygon& rCandidate)
        {
            B2DPolygon aRetval(rCandidate);
 
            for(sal_uInt32 a(0); a < rCandidate.count(); a++)
            {
                expandToCurveInPoint(aRetval, a);
            }
 
            return aRetval;
        }
 
        bool expandToCurveInPoint(B2DPolygon& rCandidate, sal_uInt32 nIndex)
        {
            OSL_ENSURE(nIndex < rCandidate.count(), "expandToCurveInPoint: Access to polygon out of range (!)");
            bool bRetval(false);
            const sal_uInt32 nPointCount(rCandidate.count());
 
            if(nPointCount)
            {
                // predecessor
                if(!rCandidate.isPrevControlPointUsed(nIndex))
                {
                    if(!rCandidate.isClosed() && nIndex == 0)
                    {
                        // do not create previous vector for start point of open polygon
                    }
                    else
                    {
                        const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount);
                        rCandidate.setPrevControlPoint(nIndex, interpolate(rCandidate.getB2DPoint(nIndex), rCandidate.getB2DPoint(nPrevIndex), 1.0 / 3.0));
                        bRetval = true;
                    }
                }
 
                // successor
                if(!rCandidate.isNextControlPointUsed(nIndex))
                {
                    if(!rCandidate.isClosed() && nIndex + 1 == nPointCount)
                    {
                        // do not create next vector for end point of open polygon
                    }
                    else
                    {
                        const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
                        rCandidate.setNextControlPoint(nIndex, interpolate(rCandidate.getB2DPoint(nIndex), rCandidate.getB2DPoint(nNextIndex), 1.0 / 3.0));
                        bRetval = true;
                    }
                }
            }
 
            return bRetval;
        }
 
        bool setContinuityInPoint(B2DPolygon& rCandidate, sal_uInt32 nIndex, B2VectorContinuity eContinuity)
        {
            OSL_ENSURE(nIndex < rCandidate.count(), "setContinuityInPoint: Access to polygon out of range (!)");
            bool bRetval(false);
            const sal_uInt32 nPointCount(rCandidate.count());
 
            if(nPointCount)
            {
                const B2DPoint aCurrentPoint(rCandidate.getB2DPoint(nIndex));
 
                switch(eContinuity)
                {
                    case B2VectorContinuity::NONE :
                    {
                        if(rCandidate.isPrevControlPointUsed(nIndex))
                        {
                            if(!rCandidate.isClosed() && nIndex == 0)
                            {
                                // remove existing previous vector for start point of open polygon
                                rCandidate.resetPrevControlPoint(nIndex);
                            }
                            else
                            {
                                const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount);
                                rCandidate.setPrevControlPoint(nIndex, interpolate(aCurrentPoint, rCandidate.getB2DPoint(nPrevIndex), 1.0 / 3.0));
                            }
 
                            bRetval = true;
                        }
 
                        if(rCandidate.isNextControlPointUsed(nIndex))
                        {
                            if(!rCandidate.isClosed() && nIndex == nPointCount + 1)
                            {
                                // remove next vector for end point of open polygon
                                rCandidate.resetNextControlPoint(nIndex);
                            }
                            else
                            {
                                const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
                                rCandidate.setNextControlPoint(nIndex, interpolate(aCurrentPoint, rCandidate.getB2DPoint(nNextIndex), 1.0 / 3.0));
                            }
 
                            bRetval = true;
                        }
 
                        break;
                    }
                    case B2VectorContinuity::C1 :
                    {
                        if(rCandidate.isPrevControlPointUsed(nIndex) && rCandidate.isNextControlPointUsed(nIndex))
                        {
                            // lengths both exist since both are used
                            B2DVector aVectorPrev(rCandidate.getPrevControlPoint(nIndex) - aCurrentPoint);
                            B2DVector aVectorNext(rCandidate.getNextControlPoint(nIndex) - aCurrentPoint);
                            const double fLenPrev(aVectorPrev.getLength());
                            const double fLenNext(aVectorNext.getLength());
                            aVectorPrev.normalize();
                            aVectorNext.normalize();
                            const B2VectorOrientation aOrientation(getOrientation(aVectorPrev, aVectorNext));
 
                            if(aOrientation == B2VectorOrientation::Neutral && aVectorPrev.scalar(aVectorNext) < 0.0)
                            {
                                // parallel and opposite direction; check length
                                if(fTools::equal(fLenPrev, fLenNext))
                                {
                                    // this would be even C2, but we want C1. Use the lengths of the corresponding edges.
                                    const sal_uInt32 nPrevIndex((nIndex + (nPointCount - 1)) % nPointCount);
                                    const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
                                    const double fLenPrevEdge(B2DVector(rCandidate.getB2DPoint(nPrevIndex) - aCurrentPoint).getLength() * (1.0 / 3.0));
                                    const double fLenNextEdge(B2DVector(rCandidate.getB2DPoint(nNextIndex) - aCurrentPoint).getLength() * (1.0 / 3.0));
 
                                    rCandidate.setControlPoints(nIndex,
                                        aCurrentPoint + (aVectorPrev * fLenPrevEdge),
                                        aCurrentPoint + (aVectorNext * fLenNextEdge));
                                    bRetval = true;
                                }
                            }
                            else
                            {
                                // not parallel or same direction, set vectors and length
                                const B2DVector aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev + aVectorNext));
 
                                if(aOrientation == B2VectorOrientation::Positive)
                                {
                                    rCandidate.setControlPoints(nIndex,
                                        aCurrentPoint - (aNormalizedPerpendicular * fLenPrev),
                                        aCurrentPoint + (aNormalizedPerpendicular * fLenNext));
                                }
                                else
                                {
                                    rCandidate.setControlPoints(nIndex,
                                        aCurrentPoint + (aNormalizedPerpendicular * fLenPrev),
                                        aCurrentPoint - (aNormalizedPerpendicular * fLenNext));
                                }
 
                                bRetval = true;
                            }
                        }
                        break;
                    }
                    case B2VectorContinuity::C2 :
                    {
                        if(rCandidate.isPrevControlPointUsed(nIndex) && rCandidate.isNextControlPointUsed(nIndex))
                        {
                            // lengths both exist since both are used
                            B2DVector aVectorPrev(rCandidate.getPrevControlPoint(nIndex) - aCurrentPoint);
                            B2DVector aVectorNext(rCandidate.getNextControlPoint(nIndex) - aCurrentPoint);
                            const double fCommonLength((aVectorPrev.getLength() + aVectorNext.getLength()) / 2.0);
                            aVectorPrev.normalize();
                            aVectorNext.normalize();
                            const B2VectorOrientation aOrientation(getOrientation(aVectorPrev, aVectorNext));
 
                            if(aOrientation == B2VectorOrientation::Neutral && aVectorPrev.scalar(aVectorNext) < 0.0)
                            {
                                // parallel and opposite direction; set length. Use one direction for better numerical correctness
                                const B2DVector aScaledDirection(aVectorPrev * fCommonLength);
 
                                rCandidate.setControlPoints(nIndex,
                                    aCurrentPoint + aScaledDirection,
                                    aCurrentPoint - aScaledDirection);
                            }
                            else
                            {
                                // not parallel or same direction, set vectors and length
                                const B2DVector aNormalizedPerpendicular(getNormalizedPerpendicular(aVectorPrev + aVectorNext));
                                const B2DVector aPerpendicular(aNormalizedPerpendicular * fCommonLength);
 
                                if(aOrientation == B2VectorOrientation::Positive)
                                {
                                    rCandidate.setControlPoints(nIndex,
                                        aCurrentPoint - aPerpendicular,
                                        aCurrentPoint + aPerpendicular);
                                }
                                else
                                {
                                    rCandidate.setControlPoints(nIndex,
                                        aCurrentPoint + aPerpendicular,
                                        aCurrentPoint - aPerpendicular);
                                }
                            }
 
                            bRetval = true;
                        }
                        break;
                    }
                }
            }
 
            return bRetval;
        }
 
        B2DPolygon growInNormalDirection(const B2DPolygon& rCandidate, double fValue)
        {
            if(fValue != 0.0)
            {
                if(rCandidate.areControlPointsUsed())
                {
                    // call myself recursively with subdivided input
                    const B2DPolygon aCandidate(adaptiveSubdivideByAngle(rCandidate));
                    return growInNormalDirection(aCandidate, fValue);
                }
                else
                {
                    B2DPolygon aRetval;
                    const sal_uInt32 nPointCount(rCandidate.count());
 
                    if(nPointCount)
                    {
                        B2DPoint aPrev(rCandidate.getB2DPoint(nPointCount - 1));
                        B2DPoint aCurrent(rCandidate.getB2DPoint(0));
 
                        for(sal_uInt32 a(0); a < nPointCount; a++)
                        {
                            const B2DPoint aNext(rCandidate.getB2DPoint(a + 1 == nPointCount ? 0 : a + 1));
                            const B2DVector aBack(aPrev - aCurrent);
                            const B2DVector aForw(aNext - aCurrent);
                            const B2DVector aPerpBack(getNormalizedPerpendicular(aBack));
                            const B2DVector aPerpForw(getNormalizedPerpendicular(aForw));
                            B2DVector aDirection(aPerpBack - aPerpForw);
                            aDirection.normalize();
                            aDirection *= fValue;
                            aRetval.append(aCurrent + aDirection);
 
                            // prepare next step
                            aPrev = aCurrent;
                            aCurrent = aNext;
                        }
                    }
 
                    // copy closed state
                    aRetval.setClosed(rCandidate.isClosed());
 
                    return aRetval;
                }
            }
            else
            {
                return rCandidate;
            }
        }
 
        B2DPolygon reSegmentPolygon(const B2DPolygon& rCandidate, sal_uInt32 nSegments)
        {
            B2DPolygon aRetval;
            const sal_uInt32 nPointCount(rCandidate.count());
 
            if(nPointCount && nSegments)
            {
                // get current segment count
                const sal_uInt32 nSegmentCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
 
                if(nSegmentCount == nSegments)
                {
                    aRetval = rCandidate;
                }
                else
                {
                    const double fLength(getLength(rCandidate));
                    const sal_uInt32 nLoopCount(rCandidate.isClosed() ? nSegments : nSegments + 1);
 
                    for(sal_uInt32 a(0); a < nLoopCount; a++)
                    {
                        const double fRelativePos(static_cast<double>(a) / static_cast<double>(nSegments)); // 0.0 .. 1.0
                        const B2DPoint aNewPoint(getPositionRelative(rCandidate, fRelativePos, fLength));
                        aRetval.append(aNewPoint);
                    }
 
                    // copy closed flag
                    aRetval.setClosed(rCandidate.isClosed());
                }
            }
 
            return aRetval;
        }
 
        B2DPolygon interpolate(const B2DPolygon& rOld1, const B2DPolygon& rOld2, double t)
        {
            OSL_ENSURE(rOld1.count() == rOld2.count(), "B2DPolygon interpolate: Different geometry (!)");
 
            if(t <= 0.0 || rOld1 == rOld2)
            {
                return rOld1;
            }
            else if(fTools::moreOrEqual(t, 1.0))
            {
                return rOld2;
            }
            else
            {
                B2DPolygon aRetval;
                const bool bInterpolateVectors(rOld1.areControlPointsUsed() || rOld2.areControlPointsUsed());
                aRetval.setClosed(rOld1.isClosed() && rOld2.isClosed());
 
                for(sal_uInt32 a(0); a < rOld1.count(); a++)
                {
                    aRetval.append(interpolate(rOld1.getB2DPoint(a), rOld2.getB2DPoint(a), t));
 
                    if(bInterpolateVectors)
                    {
                        aRetval.setPrevControlPoint(a, interpolate(rOld1.getPrevControlPoint(a), rOld2.getPrevControlPoint(a), t));
                        aRetval.setNextControlPoint(a, interpolate(rOld1.getNextControlPoint(a), rOld2.getNextControlPoint(a), t));
                    }
                }
 
                return aRetval;
            }
        }
 
        // #i76891#
        B2DPolygon simplifyCurveSegments(const B2DPolygon& rCandidate)
        {
            const sal_uInt32 nPointCount(rCandidate.count());
 
            if(nPointCount && rCandidate.areControlPointsUsed())
            {
                // prepare loop
                const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
                B2DPolygon aRetval;
                B2DCubicBezier aBezier;
                aBezier.setStartPoint(rCandidate.getB2DPoint(0));
 
                // try to avoid costly reallocations
                aRetval.reserve( nEdgeCount+1);
 
                // add start point
                aRetval.append(aBezier.getStartPoint());
 
                for(sal_uInt32 a(0); a < nEdgeCount; a++)
                {
                    // get values for edge
                    const sal_uInt32 nNextIndex((a + 1) % nPointCount);
                    aBezier.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
                    aBezier.setControlPointA(rCandidate.getNextControlPoint(a));
                    aBezier.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
                    aBezier.testAndSolveTrivialBezier();
 
                    // still bezier?
                    if(aBezier.isBezier())
                    {
                        // add edge with control vectors
                        aRetval.appendBezierSegment(aBezier.getControlPointA(), aBezier.getControlPointB(), aBezier.getEndPoint());
                    }
                    else
                    {
                        // add edge
                        aRetval.append(aBezier.getEndPoint());
                    }
 
                    // next point
                    aBezier.setStartPoint(aBezier.getEndPoint());
                }
 
                if(rCandidate.isClosed())
                {
                    // set closed flag, rescue control point and correct last double point
                    closeWithGeometryChange(aRetval);
                }
 
                return aRetval;
            }
            else
            {
                return rCandidate;
            }
        }
 
        // makes the given indexed point the new polygon start point. To do that, the points in the
        // polygon will be rotated. This is only valid for closed polygons, for non-closed ones
        // an assertion will be triggered
        B2DPolygon makeStartPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndexOfNewStatPoint)
        {
            const sal_uInt32 nPointCount(rCandidate.count());
 
            if(nPointCount > 2 && nIndexOfNewStatPoint != 0 && nIndexOfNewStatPoint < nPointCount)
            {
                OSL_ENSURE(rCandidate.isClosed(), "makeStartPoint: only valid for closed polygons (!)");
                B2DPolygon aRetval;
 
                for(sal_uInt32 a(0); a < nPointCount; a++)
                {
                    const sal_uInt32 nSourceIndex((a + nIndexOfNewStatPoint) % nPointCount);
                    aRetval.append(rCandidate.getB2DPoint(nSourceIndex));
 
                    if(rCandidate.areControlPointsUsed())
                    {
                        aRetval.setPrevControlPoint(a, rCandidate.getPrevControlPoint(nSourceIndex));
                        aRetval.setNextControlPoint(a, rCandidate.getNextControlPoint(nSourceIndex));
                    }
                }
 
                return aRetval;
            }
 
            return rCandidate;
        }
 
        B2DPolygon createEdgesOfGivenLength(const B2DPolygon& rCandidate, double fLength, double fStart, double fEnd)
        {
            B2DPolygon aRetval;
 
            if(fLength < 0.0)
            {
                fLength = 0.0;
            }
 
            if(!fTools::equalZero(fLength))
            {
                if(fStart < 0.0)
                {
                    fStart = 0.0;
                }
 
                if(fEnd < 0.0)
                {
                    fEnd = 0.0;
                }
 
                if(fEnd < fStart)
                {
                    fEnd = fStart;
                }
 
                // iterate and consume pieces with fLength. First subdivide to reduce input to line segments
                const B2DPolygon aCandidate(rCandidate.areControlPointsUsed() ? rCandidate.getDefaultAdaptiveSubdivision() : rCandidate);
                const sal_uInt32 nPointCount(aCandidate.count());
 
                if(nPointCount > 1)
                {
                    const bool bEndActive(!fTools::equalZero(fEnd));
                    const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount : nPointCount - 1);
                    B2DPoint aCurrent(aCandidate.getB2DPoint(0));
                    double fPositionInEdge(fStart);
                    double fAbsolutePosition(fStart);
 
                    for(sal_uInt32 a(0); a < nEdgeCount; a++)
                    {
                        const sal_uInt32 nNextIndex((a + 1) % nPointCount);
                        const B2DPoint aNext(aCandidate.getB2DPoint(nNextIndex));
                        const B2DVector aEdge(aNext - aCurrent);
                        double fEdgeLength(aEdge.getLength());
 
                        if(!fTools::equalZero(fEdgeLength))
                        {
                            while(fTools::less(fPositionInEdge, fEdgeLength))
                            {
                                // move position on edge forward as long as on edge
                                const double fScalar(fPositionInEdge / fEdgeLength);
                                aRetval.append(aCurrent + (aEdge * fScalar));
                                fPositionInEdge += fLength;
 
                                if(bEndActive)
                                {
                                    fAbsolutePosition += fLength;
 
                                    if(fTools::more(fAbsolutePosition, fEnd))
                                    {
                                        break;
                                    }
                                }
                            }
 
                            // subtract length of current edge
                            fPositionInEdge -= fEdgeLength;
                        }
 
                        if(bEndActive && fTools::more(fAbsolutePosition, fEnd))
                        {
                            break;
                        }
 
                        // prepare next step
                        aCurrent = aNext;
                    }
 
                    // keep closed state
                    aRetval.setClosed(aCandidate.isClosed());
                }
                else
                {
                    // source polygon has only one point, return unchanged
                    aRetval = aCandidate;
                }
            }
 
            return aRetval;
        }
 
        B2DPolygon createWaveline(const B2DPolygon& rCandidate, double fWaveWidth, double fWaveHeight)
        {
            B2DPolygon aRetval;
 
            if(fWaveWidth < 0.0)
            {
                fWaveWidth = 0.0;
            }
 
            if(fWaveHeight < 0.0)
            {
                fWaveHeight = 0.0;
            }
 
            const bool bHasWidth(!fTools::equalZero(fWaveWidth));
 
            if(bHasWidth)
            {
                const bool bHasHeight(!fTools::equalZero(fWaveHeight));
                if(bHasHeight)
                {
                    // width and height, create waveline. First subdivide to reduce input to line segments
                    // of WaveWidth. Last segment may be missing. If this turns out to be a problem, it
                    // may be added here again using the original last point from rCandidate. It may
                    // also be the case that rCandidate was closed. To simplify things it is handled here
                    // as if it was opened.
                    // Result from createEdgesOfGivenLength contains no curved segments, handle as straight
                    // edges.
                    const B2DPolygon aEqualLenghEdges(createEdgesOfGivenLength(rCandidate, fWaveWidth));
                    const sal_uInt32 nPointCount(aEqualLenghEdges.count());
 
                    if(nPointCount > 1)
                    {
                        // iterate over straight edges, add start point
                        B2DPoint aCurrent(aEqualLenghEdges.getB2DPoint(0));
                        aRetval.append(aCurrent);
 
                        for(sal_uInt32 a(0); a < nPointCount - 1; a++)
                        {
                            const sal_uInt32 nNextIndex((a + 1) % nPointCount);
                            const B2DPoint aNext(aEqualLenghEdges.getB2DPoint(nNextIndex));
                            const B2DVector aEdge(aNext - aCurrent);
                            const B2DVector aPerpendicular(getNormalizedPerpendicular(aEdge));
                            const B2DVector aControlOffset((aEdge * 0.467308) - (aPerpendicular * fWaveHeight));
 
                            // add curve segment
                            aRetval.appendBezierSegment(
                                aCurrent + aControlOffset,
                                aNext - aControlOffset,
                                aNext);
 
                            // prepare next step
                            aCurrent = aNext;
                        }
                    }
                }
                else
                {
                    // width but no height -> return original polygon
                    aRetval = rCandidate;
                }
            }
            else
            {
                // no width -> no waveline, stay empty and return
            }
 
            return aRetval;
        }
 
        // snap points of horizontal or vertical edges to discrete values
        B2DPolygon snapPointsOfHorizontalOrVerticalEdges(const B2DPolygon& rCandidate)
        {
            const sal_uInt32 nPointCount(rCandidate.count());
 
            if(nPointCount > 1)
            {
                // Start by copying the source polygon to get a writeable copy. The closed state is
                // copied by aRetval's initialisation, too, so no need to copy it in this method
                B2DPolygon aRetval(rCandidate);
 
                // prepare geometry data. Get rounded from original
                B2ITuple aPrevTuple(basegfx::fround(rCandidate.getB2DPoint(nPointCount - 1)));
                B2DPoint aCurrPoint(rCandidate.getB2DPoint(0));
                B2ITuple aCurrTuple(basegfx::fround(aCurrPoint));
 
                // loop over all points. This will also snap the implicit closing edge
                // even when not closed, but that's no problem here
                for(sal_uInt32 a(0); a < nPointCount; a++)
                {
                    // get next point. Get rounded from original
                    const bool bLastRun(a + 1 == nPointCount);
                    const sal_uInt32 nNextIndex(bLastRun ? 0 : a + 1);
                    const B2DPoint aNextPoint(rCandidate.getB2DPoint(nNextIndex));
                    const B2ITuple aNextTuple(basegfx::fround(aNextPoint));
 
                    // 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)
                    {
                        const B2DPoint aSnappedPoint(
                            bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(),
                            bSnapY ? aCurrTuple.getY() : aCurrPoint.getY());
 
                        aRetval.setB2DPoint(a, aSnappedPoint);
                    }
 
                    // prepare next point
                    if(!bLastRun)
                    {
                        aPrevTuple = aCurrTuple;
                        aCurrPoint = aNextPoint;
                        aCurrTuple = aNextTuple;
                    }
                }
 
                return aRetval;
            }
            else
            {
                return rCandidate;
            }
        }
 
        B2DVector getTangentEnteringPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
        {
            B2DVector aRetval(0.0, 0.0);
            const sal_uInt32 nCount(rCandidate.count());
 
            if(nIndex >= nCount)
            {
                // out of range
                return aRetval;
            }
 
            // start immediately at prev point compared to nIndex
            const bool bClosed(rCandidate.isClosed());
            sal_uInt32 nPrev(bClosed ? (nIndex + nCount - 1) % nCount : nIndex ? nIndex - 1 : nIndex);
 
            if(nPrev == nIndex)
            {
                // no previous, done
                return aRetval;
            }
 
            B2DCubicBezier aSegment;
 
            // go backward in the polygon; if closed, maximal back to start index (nIndex); if not closed,
            // until zero. Use nIndex as stop criteria
            while(nPrev != nIndex)
            {
                // get BezierSegment and tangent at the *end* of segment
                rCandidate.getBezierSegment(nPrev, aSegment);
                aRetval = aSegment.getTangent(1.0);
 
                if(!aRetval.equalZero())
                {
                    // if we have a tangent, return it
                    return aRetval;
                }
 
                // prepare index before checked one
                nPrev = bClosed ? (nPrev + nCount - 1) % nCount : nPrev ? nPrev - 1 : nIndex;
            }
 
            return aRetval;
        }
 
        B2DVector getTangentLeavingPoint(const B2DPolygon& rCandidate, sal_uInt32 nIndex)
        {
            B2DVector aRetval(0.0, 0.0);
            const sal_uInt32 nCount(rCandidate.count());
 
            if(nIndex >= nCount)
            {
                // out of range
                return aRetval;
            }
 
            // start at nIndex
            const bool bClosed(rCandidate.isClosed());
            sal_uInt32 nCurrent(nIndex);
            B2DCubicBezier aSegment;
 
            // go forward; if closed, do this until once around and back at start index (nIndex); if not
            // closed, until last point (nCount - 1). Use nIndex as stop criteria
            do
            {
                // get BezierSegment and tangent at the *beginning* of segment
                rCandidate.getBezierSegment(nCurrent, aSegment);
                aRetval = aSegment.getTangent(0.0);
 
                if(!aRetval.equalZero())
                {
                    // if we have a tangent, return it
                    return aRetval;
                }
 
                // prepare next index
                nCurrent = bClosed ? (nCurrent + 1) % nCount : nCurrent + 1 < nCount ? nCurrent + 1 : nIndex;
            }
            while(nCurrent != nIndex);
 
            return aRetval;
        }
 
        // converters for css::drawing::PointSequence
 
        B2DPolygon UnoPointSequenceToB2DPolygon(
            const css::drawing::PointSequence& rPointSequenceSource)
        {
            B2DPolygon aRetval;
            const sal_uInt32 nLength(rPointSequenceSource.getLength());
 
            if(nLength)
            {
                aRetval.reserve(nLength);
 
                for(auto& point : rPointSequenceSource)
                {
                    aRetval.append(B2DPoint(point.X, point.Y));
                }
 
                // check for closed state flag
                utils::checkClosed(aRetval);
            }
 
            return aRetval;
        }
 
        void B2DPolygonToUnoPointSequence(
            const B2DPolygon& rPolygon,
            css::drawing::PointSequence& rPointSequenceRetval)
        {
            B2DPolygon aPolygon(rPolygon);
 
            if(aPolygon.areControlPointsUsed())
            {
                OSL_ENSURE(false, "B2DPolygonToUnoPointSequence: Source contains bezier segments, wrong UNO API data type may be used (!)");
                aPolygon = aPolygon.getDefaultAdaptiveSubdivision();
            }
 
            const sal_uInt32 nPointCount(aPolygon.count());
 
            if(nPointCount)
            {
                // Take closed state into account, the API polygon still uses the old closed definition
                // with last/first point are identical (cannot hold information about open polygons with identical
                // first and last point, though)
                const bool bIsClosed(aPolygon.isClosed());
 
                rPointSequenceRetval.realloc(bIsClosed ? nPointCount + 1 : nPointCount);
                css::awt::Point* pSequence = rPointSequenceRetval.getArray();
 
                for(sal_uInt32 b(0); b < nPointCount; b++)
                {
                    const B2DPoint aPoint(aPolygon.getB2DPoint(b));
                    const css::awt::Point aAPIPoint(fround(aPoint.getX()), fround(aPoint.getY()));
 
                    *pSequence = aAPIPoint;
                    pSequence++;
                }
 
                // copy first point if closed
                if(bIsClosed)
                {
                    *pSequence = rPointSequenceRetval[0];
                }
            }
            else
            {
                rPointSequenceRetval.realloc(0);
            }
        }
 
        // converters for css::drawing::PointSequence and
        // css::drawing::FlagSequence to B2DPolygon (curved polygons)
 
        B2DPolygon UnoPolygonBezierCoordsToB2DPolygon(
            const css::drawing::PointSequence& rPointSequenceSource,
            const css::drawing::FlagSequence& rFlagSequenceSource)
        {
            const sal_Int32 nCount(rPointSequenceSource.getLength());
            OSL_ENSURE(nCount == rFlagSequenceSource.getLength(),
                "UnoPolygonBezierCoordsToB2DPolygon: Unequal count of Points and Flags (!)");
 
            // prepare new polygon
            B2DPolygon aRetval;
 
            if(0 != nCount)
            {
                aRetval.reserve(nCount);
 
                // get first point and flag
                B2DPoint aNewCoordinatePair(rPointSequenceSource[0].X, rPointSequenceSource[0].Y);
                B2DPoint aControlA;
                B2DPoint aControlB;
 
                // first point is not allowed to be a control point
                OSL_ENSURE(rFlagSequenceSource[0] != css::drawing::PolygonFlags_CONTROL,
                    "UnoPolygonBezierCoordsToB2DPolygon: Start point is a control point, illegal input polygon (!)");
 
                // add first point as start point
                aRetval.append(aNewCoordinatePair);
 
                for(sal_Int32 b(1); b < nCount;)
                {
                    // prepare loop
                    bool bControlA(false);
                    bool bControlB(false);
 
                    // get next point and flag
                    aNewCoordinatePair = B2DPoint(rPointSequenceSource[b].X, rPointSequenceSource[b].Y);
                    css::drawing::PolygonFlags ePolygonFlag = rFlagSequenceSource[b];
                    b++;
 
                    if(b < nCount && ePolygonFlag == css::drawing::PolygonFlags_CONTROL)
                    {
                        aControlA = aNewCoordinatePair;
                        bControlA = true;
 
                        // get next point and flag
                        aNewCoordinatePair = B2DPoint(rPointSequenceSource[b].X, rPointSequenceSource[b].Y);
                        ePolygonFlag = rFlagSequenceSource[b];
                        b++;
                    }
 
                    if(b < nCount && ePolygonFlag == css::drawing::PolygonFlags_CONTROL)
                    {
                        aControlB = aNewCoordinatePair;
                        bControlB = true;
 
                        // get next point and flag
                        aNewCoordinatePair = B2DPoint(rPointSequenceSource[b].X, rPointSequenceSource[b].Y);
                        ePolygonFlag = rFlagSequenceSource[b];
                        b++;
                    }
 
                    // two or no control points are consumed, another one would be an error.
                    // It's also an error if only one control point was read
                    SAL_WARN_IF(ePolygonFlag == css::drawing::PolygonFlags_CONTROL || bControlA != bControlB,
                        "basegfx", "UnoPolygonBezierCoordsToB2DPolygon: Illegal source polygon (!)");
 
                    // the previous writes used the B2DPolyPolygon -> utils::PolyPolygon converter
                    // which did not create minimal PolyPolygons, but created all control points
                    // as null vectors (identical points). Because of the former P(CA)(CB)-norm of
                    // B2DPolygon and it's unused sign of being the zero-vector and CA and CB being
                    // relative to P, an empty edge was exported as P == CA == CB. Luckily, the new
                    // export format can be read without errors by the old OOo-versions, so we need only
                    // to correct here at read and do not need to export a wrong but compatible version
                    // for the future.
                    if(bControlA
                        && aControlA.equal(aControlB)
                        && aControlA.equal(aRetval.getB2DPoint(aRetval.count() - 1)))
                    {
                        bControlA = false;
                    }
 
                    if(bControlA)
                    {
                        // add bezier edge
                        aRetval.appendBezierSegment(aControlA, aControlB, aNewCoordinatePair);
                    }
                    else
                    {
                        // add edge
                        aRetval.append(aNewCoordinatePair);
                    }
                }
 
                // #i72807# API import uses old line start/end-equal definition for closed,
                // so we need to correct this to closed state here
                checkClosed(aRetval);
            }
 
            return aRetval;
        }
 
        void B2DPolygonToUnoPolygonBezierCoords(
            const B2DPolygon& rPolygon,
            css::drawing::PointSequence& rPointSequenceRetval,
            css::drawing::FlagSequence& rFlagSequenceRetval)
        {
            const sal_uInt32 nPointCount(rPolygon.count());
 
            if(nPointCount)
            {
                const bool bCurve(rPolygon.areControlPointsUsed());
                const bool bClosed(rPolygon.isClosed());
 
                if(bCurve)
                {
                    // calculate target point count
                    const sal_uInt32 nLoopCount(bClosed ? nPointCount : nPointCount - 1);
 
                    if(nLoopCount)
                    {
                        // prepare target data. The real needed number of target points (and flags)
                        // could only be calculated by using two loops, so use dynamic memory
                        std::vector< css::awt::Point > aCollectPoints;
                        std::vector< css::drawing::PolygonFlags > aCollectFlags;
 
                        // reserve maximum creatable points
                        const sal_uInt32 nMaxTargetCount((nLoopCount * 3) + 1);
                        aCollectPoints.reserve(nMaxTargetCount);
                        aCollectFlags.reserve(nMaxTargetCount);
 
                        // prepare current bezier segment by setting start point
                        B2DCubicBezier aBezierSegment;
                        aBezierSegment.setStartPoint(rPolygon.getB2DPoint(0));
 
                        for(sal_uInt32 a(0); a < nLoopCount; a++)
                        {
                            // add current point (always) and remember StartPointIndex for evtl. later corrections
                            const sal_uInt32 nStartPointIndex(aCollectPoints.size());
                            aCollectPoints.emplace_back(
                                    fround(aBezierSegment.getStartPoint().getX()),
                                    fround(aBezierSegment.getStartPoint().getY()));
                            aCollectFlags.push_back(css::drawing::PolygonFlags_NORMAL);
 
                            // prepare next segment
                            const sal_uInt32 nNextIndex((a + 1) % nPointCount);
                            aBezierSegment.setEndPoint(rPolygon.getB2DPoint(nNextIndex));
                            aBezierSegment.setControlPointA(rPolygon.getNextControlPoint(a));
                            aBezierSegment.setControlPointB(rPolygon.getPrevControlPoint(nNextIndex));
 
                            if(aBezierSegment.isBezier())
                            {
                                // if bezier is used, add always two control points due to the old schema
                                aCollectPoints.emplace_back(
                                        fround(aBezierSegment.getControlPointA().getX()),
                                        fround(aBezierSegment.getControlPointA().getY()));
                                aCollectFlags.push_back(css::drawing::PolygonFlags_CONTROL);
 
                                aCollectPoints.emplace_back(
                                        fround(aBezierSegment.getControlPointB().getX()),
                                        fround(aBezierSegment.getControlPointB().getY()));
                                aCollectFlags.push_back(css::drawing::PolygonFlags_CONTROL);
                            }
 
                            // test continuity with previous control point to set flag value
                            if(aBezierSegment.getControlPointA() != aBezierSegment.getStartPoint() && (bClosed || a))
                            {
                                const B2VectorContinuity eCont(rPolygon.getContinuityInPoint(a));
 
                                if(eCont == B2VectorContinuity::C1)
                                {
                                    aCollectFlags[nStartPointIndex] = css::drawing::PolygonFlags_SMOOTH;
                                }
                                else if(eCont == B2VectorContinuity::C2)
                                {
                                    aCollectFlags[nStartPointIndex] = css::drawing::PolygonFlags_SYMMETRIC;
                                }
                            }
 
                            // prepare next loop
                            aBezierSegment.setStartPoint(aBezierSegment.getEndPoint());
                        }
 
                        if(bClosed)
                        {
                            // add first point again as closing point due to old definition
                            aCollectPoints.push_back(aCollectPoints[0]);
                            aCollectFlags.push_back(css::drawing::PolygonFlags_NORMAL);
                        }
                        else
                        {
                            // add last point as closing point
                            const B2DPoint aClosingPoint(rPolygon.getB2DPoint(nPointCount - 1));
                            aCollectPoints.emplace_back(
                                    fround(aClosingPoint.getX()),
                                    fround(aClosingPoint.getY()));
                            aCollectFlags.push_back(css::drawing::PolygonFlags_NORMAL);
                        }
 
                        // copy collected data to target arrays
                        const sal_uInt32 nTargetCount(aCollectPoints.size());
                        OSL_ENSURE(nTargetCount == aCollectFlags.size(), "Unequal Point and Flag count (!)");
 
                        rPointSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
                        rFlagSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
                        css::awt::Point* pPointSequence = rPointSequenceRetval.getArray();
                        css::drawing::PolygonFlags* pFlagSequence = rFlagSequenceRetval.getArray();
 
                        for(sal_uInt32 a(0); a < nTargetCount; a++)
                        {
                            *pPointSequence = aCollectPoints[a];
                            *pFlagSequence = aCollectFlags[a];
                            pPointSequence++;
                            pFlagSequence++;
                        }
                    }
                }
                else
                {
                    // straightforward point list creation
                    const sal_uInt32 nTargetCount(nPointCount + (bClosed ? 1 : 0));
 
                    rPointSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
                    rFlagSequenceRetval.realloc(static_cast<sal_Int32>(nTargetCount));
 
                    css::awt::Point* pPointSequence = rPointSequenceRetval.getArray();
                    css::drawing::PolygonFlags* pFlagSequence = rFlagSequenceRetval.getArray();
 
                    for(sal_uInt32 a(0); a < nPointCount; a++)
                    {
                        const B2DPoint aB2DPoint(rPolygon.getB2DPoint(a));
                        const css::awt::Point aAPIPoint(
                            fround(aB2DPoint.getX()),
                            fround(aB2DPoint.getY()));
 
                        *pPointSequence = aAPIPoint;
                        *pFlagSequence = css::drawing::PolygonFlags_NORMAL;
                        pPointSequence++;
                        pFlagSequence++;
                    }
 
                    if(bClosed)
                    {
                        // add first point as closing point
                        *pPointSequence = rPointSequenceRetval[0];
                        *pFlagSequence = css::drawing::PolygonFlags_NORMAL;
                    }
                }
            }
            else
            {
                rPointSequenceRetval.realloc(0);
                rFlagSequenceRetval.realloc(0);
            }
        }
 
B2DPolygon createSimplifiedPolygon(const B2DPolygon& rCandidate, const double fTolerance)
{
    const sal_uInt32 nPointCount(rCandidate.count());
    if (nPointCount < 3 || rCandidate.areControlPointsUsed())
        return rCandidate;
 
    // The solution does not use recursion directly, since this could lead to a stack overflow if
    // nPointCount is very large. Instead, an own stack is used. This does not contain points, but
    // pairs of low and high index of a range in rCandidate. A parallel boolean vector is used to note
    // whether a point of rCandidate belongs to the output polygon or not.
    std::vector<bool> bIsKeptVec(nPointCount, false);
    bIsKeptVec[0] = true;
    bIsKeptVec[nPointCount - 1] = true;
    sal_uInt32 nKept = 2;
    std::stack<std::pair<sal_uInt32, sal_uInt32>> aUnfinishedRangesStack;
 
    // The RDP algorithm draws a straight line from the first point to the last point of a range.
    // Then, from the inner points of the range, the point that has the largest distance to the line
    // is determined. If the distance is greater than the tolerance, this point is kept and the lower
    // and upper sub-segments of the range are treated in the same way. If the distance is smaller
    // than the tolerance, none of the inner points will be kept.
    sal_uInt32 nLowIndex = 0;
    sal_uInt32 nHighIndex = nPointCount - 1;
    bool bContinue = true;
    do
    {
        bContinue = false;
        if (nHighIndex - nLowIndex < 2) // maximal two points, range is finished.
        {
            // continue with sibling upper range if any
            if (!aUnfinishedRangesStack.empty())
            {
                std::pair<sal_uInt32, sal_uInt32> aIndexPair = aUnfinishedRangesStack.top();
                aUnfinishedRangesStack.pop();
                nLowIndex = aIndexPair.first;
                nHighIndex = aIndexPair.second;
                bContinue = true;
            }
        }
        else // the range needs examine the max distance
        {
            // Get maximal distance of inner points of the range to the straight line from start to
            // end point of the range.
            // For calculation of the distance we use the Hesse normal form of the straight line.
            B2DPoint aLowPoint = rCandidate.getB2DPoint(nLowIndex);
            B2DPoint aHighPoint = rCandidate.getB2DPoint(nHighIndex);
            B2DVector aNormalVec
                = basegfx::getNormalizedPerpendicular(B2DVector(aHighPoint) - B2DVector(aLowPoint));
            double fLineOriginDistance = aNormalVec.scalar(B2DVector(aLowPoint));
            double fMaxDist = 0;
            sal_uInt32 nMaxPointIndex = nLowIndex;
            for (sal_uInt32 i = nLowIndex + 1; i < nHighIndex; i++)
            {
                double fDistance
                    = aNormalVec.scalar(B2DVector(rCandidate.getB2DPoint(i))) - fLineOriginDistance;
                if (std::fabs(fDistance) > fMaxDist)
                {
                    fMaxDist = std::fabs(fDistance);
                    nMaxPointIndex = i;
                }
            }
 
            if (fMaxDist >= fTolerance)
            {
                // We need to divide the current range into two sub ranges.
                bIsKeptVec[nMaxPointIndex] = true;
                nKept++;
                // continue with lower sub range and push upper sub range to stack
                aUnfinishedRangesStack.push(std::make_pair(nMaxPointIndex, nHighIndex));
                nHighIndex = nMaxPointIndex;
                bContinue = true;
            }
            else
            {
                // We do not keep any of the inner points of the current range.
                // continue with sibling upper range if any
                if (!aUnfinishedRangesStack.empty())
                {
                    std::pair<sal_uInt32, sal_uInt32> aIndexPair = aUnfinishedRangesStack.top();
                    aUnfinishedRangesStack.pop();
                    nLowIndex = aIndexPair.first;
                    nHighIndex = aIndexPair.second;
                    bContinue = true;
                }
            }
        }
    } while (bContinue);
 
    // Put all points which are marked as "keep" into the result polygon
    B2DPolygon aResultPolygon;
    aResultPolygon.reserve(nKept);
    for (sal_uInt32 i = 0; i < nPointCount; i++)
    {
        if (bIsKeptVec[i])
            aResultPolygon.append(rCandidate.getB2DPoint(i));
    }
    return aResultPolygon;
}
 
} // end of namespace
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'normalize' is required to be utilized.

V530 The return value of function 'normalize' is required to be utilized.

V530 The return value of function 'normalize' is required to be utilized.

V530 The return value of function 'normalize' is required to be utilized.

V530 The return value of function 'normalize' is required to be utilized.