/**************************************************************
 * 
 * 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
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_basegfx.hxx"
#include <cstdio>
#include <osl/diagnose.h>
#include <basegfx/polygon/b2dlinegeometry.hxx>
#include <basegfx/point/b2dpoint.hxx>
#include <basegfx/vector/b2dvector.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/range/b2drange.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/curve/b2dcubicbezier.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <com/sun/star/drawing/LineCap.hpp>
#include <basegfx/polygon/b2dpolypolygoncutter.hxx>

//////////////////////////////////////////////////////////////////////////////

namespace basegfx
{
	namespace tools
	{
		B2DPolyPolygon createAreaGeometryForLineStartEnd(
			const B2DPolygon& rCandidate, 
			const B2DPolyPolygon& rArrow, 
			bool bStart,
			double fWidth,
			double fCandidateLength,
			double fDockingPosition, // 0->top, 1->bottom
			double* pConsumedLength)
		{
			B2DPolyPolygon aRetval;
			OSL_ENSURE(rCandidate.count() > 1L, "createAreaGeometryForLineStartEnd: Line polygon has too less points (!)");
			OSL_ENSURE(rArrow.count() > 0L, "createAreaGeometryForLineStartEnd: Empty arrow PolyPolygon (!)");
			OSL_ENSURE(fWidth > 0.0, "createAreaGeometryForLineStartEnd: Width too small (!)");
			OSL_ENSURE(fDockingPosition >= 0.0 && fDockingPosition <= 1.0, 
				"createAreaGeometryForLineStartEnd: fDockingPosition out of range [0.0 .. 1.0] (!)");

            if(fWidth < 0.0)
            {
                fWidth = -fWidth;
            }

            if(rCandidate.count() > 1 && rArrow.count() && !fTools::equalZero(fWidth))
            {
                if(fDockingPosition < 0.0)
                {
                    fDockingPosition = 0.0;
                }
                else if(fDockingPosition > 1.0)
                {
                    fDockingPosition = 1.0;
                }

			    // init return value from arrow
			    aRetval.append(rArrow);

			    // get size of the arrow
			    const B2DRange aArrowSize(getRange(rArrow));

			    // build ArrowTransform; center in X, align with axis in Y
                B2DHomMatrix aArrowTransform(basegfx::tools::createTranslateB2DHomMatrix(
                    -aArrowSize.getCenter().getX(), -aArrowSize.getMinimum().getY()));

			    // scale to target size
			    const double fArrowScale(fWidth / (aArrowSize.getRange().getX()));
			    aArrowTransform.scale(fArrowScale, fArrowScale);

			    // get arrow size in Y
			    B2DPoint aUpperCenter(aArrowSize.getCenter().getX(), aArrowSize.getMaximum().getY());
			    aUpperCenter *= aArrowTransform;
			    const double fArrowYLength(B2DVector(aUpperCenter).getLength());

			    // move arrow to have docking position centered
			    aArrowTransform.translate(0.0, -fArrowYLength * fDockingPosition);

			    // prepare polygon length
			    if(fTools::equalZero(fCandidateLength))
			    {
				    fCandidateLength = getLength(rCandidate);
			    }

			    // get the polygon vector we want to plant this arrow on
			    const double fConsumedLength(fArrowYLength * (1.0 - fDockingPosition));
			    const B2DVector aHead(rCandidate.getB2DPoint((bStart) ? 0L : rCandidate.count() - 1L));
			    const B2DVector aTail(getPositionAbsolute(rCandidate, 
				    (bStart) ? fConsumedLength : fCandidateLength - fConsumedLength, fCandidateLength));

			    // from that vector, take the needed rotation and add rotate for arrow to transformation
			    const B2DVector aTargetDirection(aHead - aTail);
			    const double fRotation(atan2(aTargetDirection.getY(), aTargetDirection.getX()) + (90.0 * F_PI180));

			    // rotate around docking position
			    aArrowTransform.rotate(fRotation);

			    // move arrow docking position to polygon head
			    aArrowTransform.translate(aHead.getX(), aHead.getY());

			    // transform retval and close
			    aRetval.transform(aArrowTransform);
			    aRetval.setClosed(true);

			    // if pConsumedLength is asked for, fill it
			    if(pConsumedLength)
			    {
				    *pConsumedLength = fConsumedLength;
			    }
            }

			return aRetval;
		}
	} // end of namespace tools
} // end of namespace basegfx

//////////////////////////////////////////////////////////////////////////////

namespace basegfx
{
	// anonymus namespace for local helpers
	namespace
	{
        bool impIsSimpleEdge(const B2DCubicBezier& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad)
        {
            // isBezier() is true, already tested by caller
            const B2DVector aEdge(rCandidate.getEndPoint() - rCandidate.getStartPoint());

            if(aEdge.equalZero())
            {
                // start and end point the same, but control vectors used -> baloon curve loop
                // is not a simple edge
                return false;
            }

            // get tangentA and scalar with edge
            const B2DVector aTangentA(rCandidate.getTangent(0.0));
    		const double fScalarAE(aEdge.scalar(aTangentA));

            if(fTools::lessOrEqual(fScalarAE, 0.0))
            {
                // angle between TangentA and Edge is bigger or equal 90 degrees
                return false;
            }

            // get self-scalars for E and A
    		const double fScalarE(aEdge.scalar(aEdge));
    		const double fScalarA(aTangentA.scalar(aTangentA));
			const double fLengthCompareE(fScalarE * fMaxPartOfEdgeQuad);

			if(fTools::moreOrEqual(fScalarA, fLengthCompareE))
			{
				// length of TangentA is more than fMaxPartOfEdge of length of edge
				return false;
			}

            if(fTools::lessOrEqual(fScalarAE * fScalarAE, fScalarA * fScalarE * fMaxCosQuad))
            {
                // angle between TangentA and Edge is bigger or equal angle defined by fMaxCos
                return false;
            }

            // get tangentB and scalar with edge
            const B2DVector aTangentB(rCandidate.getTangent(1.0));
    		const double fScalarBE(aEdge.scalar(aTangentB));

            if(fTools::lessOrEqual(fScalarBE, 0.0))
            {
                // angle between TangentB and Edge is bigger or equal 90 degrees
                return false;
            }

            // get self-scalar for B
    		const double fScalarB(aTangentB.scalar(aTangentB));

			if(fTools::moreOrEqual(fScalarB, fLengthCompareE))
			{
				// length of TangentB is more than fMaxPartOfEdge of length of edge
				return false;
			}

			if(fTools::lessOrEqual(fScalarBE * fScalarBE, fScalarB * fScalarE * fMaxCosQuad))
            {
                // angle between TangentB and Edge is bigger or equal defined by fMaxCos
                return false;
            }

            return true;
        }

        void impSubdivideToSimple(const B2DCubicBezier& rCandidate, B2DPolygon& rTarget, double fMaxCosQuad, double fMaxPartOfEdgeQuad, sal_uInt32 nMaxRecursionDepth)
        {
            if(!nMaxRecursionDepth || impIsSimpleEdge(rCandidate, fMaxCosQuad, fMaxPartOfEdgeQuad))
            {
                rTarget.appendBezierSegment(rCandidate.getControlPointA(), rCandidate.getControlPointB(), rCandidate.getEndPoint());
            }
            else
            {
                B2DCubicBezier aLeft, aRight;
                rCandidate.split(0.5, &aLeft, &aRight);
        
                impSubdivideToSimple(aLeft, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1);
                impSubdivideToSimple(aRight, rTarget, fMaxCosQuad, fMaxPartOfEdgeQuad, nMaxRecursionDepth - 1);
            }
        }

        B2DPolygon subdivideToSimple(const B2DPolygon& rCandidate, double fMaxCosQuad, double fMaxPartOfEdgeQuad)
        {
            const sal_uInt32 nPointCount(rCandidate.count());

            if(rCandidate.areControlPointsUsed() && nPointCount)
            {
                const sal_uInt32 nEdgeCount(rCandidate.isClosed() ? nPointCount : nPointCount - 1);
                B2DPolygon aRetval;
				B2DCubicBezier aEdge;

				// prepare edge for loop
				aEdge.setStartPoint(rCandidate.getB2DPoint(0));
				aRetval.append(aEdge.getStartPoint());

                for(sal_uInt32 a(0); a < nEdgeCount; a++)
                {
                    // fill B2DCubicBezier
                    const sal_uInt32 nNextIndex((a + 1) % nPointCount);
                    aEdge.setControlPointA(rCandidate.getNextControlPoint(a));
                    aEdge.setControlPointB(rCandidate.getPrevControlPoint(nNextIndex));
                    aEdge.setEndPoint(rCandidate.getB2DPoint(nNextIndex));
                    
                    // get rid of unnecessary bezier segments
                    aEdge.testAndSolveTrivialBezier();

                    if(aEdge.isBezier())
                    {
						// before splitting recursively with internal simple criteria, use
						// ExtremumPosFinder to remove those
                		::std::vector< double > aExtremumPositions;

                        aExtremumPositions.reserve(4);
		                aEdge.getAllExtremumPositions(aExtremumPositions);
                		
                        const sal_uInt32 nCount(aExtremumPositions.size());

                        if(nCount)
                        {
                            if(nCount > 1)
                            {
                                // create order from left to right
                                ::std::sort(aExtremumPositions.begin(), aExtremumPositions.end());
                            }

                            for(sal_uInt32 b(0); b < nCount;)
                            {
                                // split aEdge at next split pos
                                B2DCubicBezier aLeft;
                                const double fSplitPos(aExtremumPositions[b++]);
                                
                                aEdge.split(fSplitPos, &aLeft, &aEdge);
                                aLeft.testAndSolveTrivialBezier();

                                // consume left part
                                if(aLeft.isBezier())
						        {
	                                impSubdivideToSimple(aLeft, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
						        }
						        else
						        {
	                                aRetval.append(aLeft.getEndPoint());
						        }
                            
                                if(b < nCount)
                                {
                                    // correct the remaining split positions to fit to shortened aEdge
                                    const double fScaleFactor(1.0 / (1.0 - fSplitPos));

                                    for(sal_uInt32 c(b); c < nCount; c++)
                                    {
                                        aExtremumPositions[c] = (aExtremumPositions[c] - fSplitPos) * fScaleFactor;
                                    }
                                }
                            }
                        
                            // test the shortened rest of aEdge
                            aEdge.testAndSolveTrivialBezier();

                            // consume right part
                            if(aEdge.isBezier())
					        {
                                impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
					        }
					        else
					        {
                                aRetval.append(aEdge.getEndPoint());
					        }
                        }
                        else
                        {
	                        impSubdivideToSimple(aEdge, aRetval, fMaxCosQuad, fMaxPartOfEdgeQuad, 6);
                        }
                    }
                    else
                    {
                        // straight edge, add point
                        aRetval.append(aEdge.getEndPoint());
                    }

					// prepare edge for next step
					aEdge.setStartPoint(aEdge.getEndPoint());
                }

				// copy closed flag and check for double points
                aRetval.setClosed(rCandidate.isClosed());
                aRetval.removeDoublePoints();

                return aRetval;
            }
            else
            {
                return rCandidate;
            }
        }

        B2DPolygon createAreaGeometryForEdge(
            const B2DCubicBezier& rEdge, 
            double fHalfLineWidth,
            bool bStartRound,
            bool bEndRound,
            bool bStartSquare,
            bool bEndSquare)
        {
            // create polygon for edge
            // Unfortunately, while it would be geometrically correct to not add
            // the in-between points EdgeEnd and EdgeStart, it leads to rounding
            // errors when converting to integer polygon coordinates for painting
            if(rEdge.isBezier())
            {
                // prepare target and data common for upper and lower
                B2DPolygon aBezierPolygon;
                const B2DVector aPureEdgeVector(rEdge.getEndPoint() - rEdge.getStartPoint());
                const double fEdgeLength(aPureEdgeVector.getLength());
                const bool bIsEdgeLengthZero(fTools::equalZero(fEdgeLength));
                B2DVector aTangentA(rEdge.getTangent(0.0)); aTangentA.normalize();
                B2DVector aTangentB(rEdge.getTangent(1.0)); aTangentB.normalize();
                const B2DVector aNormalizedPerpendicularA(getPerpendicular(aTangentA));
                const B2DVector aNormalizedPerpendicularB(getPerpendicular(aTangentB));

                // create upper displacement vectors and check if they cut
                const B2DVector aPerpendStartA(aNormalizedPerpendicularA * -fHalfLineWidth);
                const B2DVector aPerpendEndA(aNormalizedPerpendicularB * -fHalfLineWidth);
                double fCutA(0.0);
                const tools::CutFlagValue aCutA(tools::findCut(
                    rEdge.getStartPoint(), aPerpendStartA,
                    rEdge.getEndPoint(), aPerpendEndA, 
                    CUTFLAG_ALL, &fCutA));
                const bool bCutA(CUTFLAG_NONE != aCutA);

                // create lower displacement vectors and check if they cut
                const B2DVector aPerpendStartB(aNormalizedPerpendicularA * fHalfLineWidth);
                const B2DVector aPerpendEndB(aNormalizedPerpendicularB * fHalfLineWidth);
                double fCutB(0.0);
                const tools::CutFlagValue aCutB(tools::findCut(
                    rEdge.getEndPoint(), aPerpendEndB,
                    rEdge.getStartPoint(), aPerpendStartB, 
                    CUTFLAG_ALL, &fCutB));
                const bool bCutB(CUTFLAG_NONE != aCutB);

                // check if cut happens
                const bool bCut(bCutA || bCutB);
                B2DPoint aCutPoint;

                // create left edge
                if(bStartRound || bStartSquare)
                {
                    if(bStartRound)
                    {
                        basegfx::B2DPolygon aStartPolygon(tools::createHalfUnitCircle());

                        aStartPolygon.transform(
                            tools::createScaleShearXRotateTranslateB2DHomMatrix(
                                fHalfLineWidth, fHalfLineWidth,
                                0.0,
                                atan2(aTangentA.getY(), aTangentA.getX()) + F_PI2,
                                rEdge.getStartPoint().getX(), rEdge.getStartPoint().getY()));
                        aBezierPolygon.append(aStartPolygon);
                    }
                    else // bStartSquare
                    {
                        const basegfx::B2DPoint aStart(rEdge.getStartPoint() - (aTangentA * fHalfLineWidth));

                        if(bCutB)
                        {
                            aBezierPolygon.append(rEdge.getStartPoint() + aPerpendStartB);
                        }

                        aBezierPolygon.append(aStart + aPerpendStartB);
                        aBezierPolygon.append(aStart + aPerpendStartA);

                        if(bCutA)
                        {
                            aBezierPolygon.append(rEdge.getStartPoint() + aPerpendStartA);
                        }
                    }
                }
                else
                {
                    // append original in-between point
                    aBezierPolygon.append(rEdge.getStartPoint());
                }

                // create upper edge.
                {
                    if(bCutA)
                    {
                        // calculate cut point and add
                        aCutPoint = rEdge.getStartPoint() + (aPerpendStartA * fCutA);
                        aBezierPolygon.append(aCutPoint);
                    }
                    else
                    {
                        // create scaled bezier segment
                        const B2DPoint aStart(rEdge.getStartPoint() + aPerpendStartA);
                        const B2DPoint aEnd(rEdge.getEndPoint() + aPerpendEndA);
                        const B2DVector aEdge(aEnd - aStart);
                        const double fLength(aEdge.getLength());
                        const double fScale(bIsEdgeLengthZero ? 1.0 : fLength / fEdgeLength);
                        const B2DVector fRelNext(rEdge.getControlPointA() - rEdge.getStartPoint());
                        const B2DVector fRelPrev(rEdge.getControlPointB() - rEdge.getEndPoint());

                        aBezierPolygon.append(aStart);
                        aBezierPolygon.appendBezierSegment(aStart + (fRelNext * fScale), aEnd + (fRelPrev * fScale), aEnd);
                    }
                }

                // create right edge
                if(bEndRound || bEndSquare)
                {
                    if(bEndRound)
                    {
                        basegfx::B2DPolygon aEndPolygon(tools::createHalfUnitCircle());

                        aEndPolygon.transform(
                            tools::createScaleShearXRotateTranslateB2DHomMatrix(
                                fHalfLineWidth, fHalfLineWidth,
                                0.0,
                                atan2(aTangentB.getY(), aTangentB.getX()) - F_PI2,
                                rEdge.getEndPoint().getX(), rEdge.getEndPoint().getY()));
                        aBezierPolygon.append(aEndPolygon);
                    }
                    else // bEndSquare
                    {
                        const basegfx::B2DPoint aEnd(rEdge.getEndPoint() + (aTangentB * fHalfLineWidth));

                        if(bCutA)
                        {
                            aBezierPolygon.append(rEdge.getEndPoint() + aPerpendEndA);
                        }

                        aBezierPolygon.append(aEnd + aPerpendEndA);
                        aBezierPolygon.append(aEnd + aPerpendEndB);

                        if(bCutB)
                        {
                            aBezierPolygon.append(rEdge.getEndPoint() + aPerpendEndB);
                        }
                    }
                }
                else
                {
                    // append original in-between point
                    aBezierPolygon.append(rEdge.getEndPoint());
                }

                // create lower edge. 
                {
                    if(bCutB)
                    {
                        // calculate cut point and add
                        aCutPoint = rEdge.getEndPoint() + (aPerpendEndB * fCutB);
                        aBezierPolygon.append(aCutPoint);
                    }
                    else
                    {
                        // create scaled bezier segment
                        const B2DPoint aStart(rEdge.getEndPoint() + aPerpendEndB);
                        const B2DPoint aEnd(rEdge.getStartPoint() + aPerpendStartB);
                        const B2DVector aEdge(aEnd - aStart);
                        const double fLength(aEdge.getLength());
                        const double fScale(bIsEdgeLengthZero ? 1.0 : fLength / fEdgeLength);
                        const B2DVector fRelNext(rEdge.getControlPointB() - rEdge.getEndPoint());
                        const B2DVector fRelPrev(rEdge.getControlPointA() - rEdge.getStartPoint());

                        aBezierPolygon.append(aStart);
                        aBezierPolygon.appendBezierSegment(aStart + (fRelNext * fScale), aEnd + (fRelPrev * fScale), aEnd);
                    }
                }

                // close
                aBezierPolygon.setClosed(true);

                if(bStartRound || bEndRound)
                {
                    // double points possible when round caps are used at start or end
                    aBezierPolygon.removeDoublePoints();
                }

                if(bCut && ((bStartRound || bStartSquare) && (bEndRound || bEndSquare)))
                {
                    // When cut exists and both ends are extended with caps, a self-intersecting polygon
                    // is created; one cut point is known, but there is a 2nd one in the caps geometry.
                    // Solve by using tooling.
                    // Remark: This nearly never happens due to curve preparations to extreme points
                    // and maximum angle turning, but I constructed a test case and checkd that it is 
                    // working propery.
                    const B2DPolyPolygon aTemp(tools::solveCrossovers(aBezierPolygon));
                    const sal_uInt32 nTempCount(aTemp.count());

                    if(nTempCount)
                    {
                        if(nTempCount > 1)
                        {
                            // as expected, multiple polygons (with same orientation). Remove
                            // the one which contains aCutPoint, or better take the one without
                            for (sal_uInt32 a(0); a < nTempCount; a++)
                            {
                                aBezierPolygon = aTemp.getB2DPolygon(a);

                                const sal_uInt32 nCandCount(aBezierPolygon.count());

                                for(sal_uInt32 b(0); b < nCandCount; b++)
                                {
                                    if(aCutPoint.equal(aBezierPolygon.getB2DPoint(b)))
                                    {
                                        aBezierPolygon.clear();
                                        break;
                                    }
                                }

                                if(aBezierPolygon.count())
                                {
                                    break;
                                }
                            }

                            OSL_ENSURE(aBezierPolygon.count(), "Error in line geometry creation, could not solve self-intersection (!)");
                        }
                        else
                        {
                            // none found, use result
                            aBezierPolygon = aTemp.getB2DPolygon(0);
                        }
                    }
                    else
                    {
                        OSL_ENSURE(false, "Error in line geometry creation, could not solve self-intersection (!)");
                    }
                }

                // return
                return aBezierPolygon;
            }
            else
            {
                // Get start and  end point, create tangent and set to needed length
                B2DVector aTangent(rEdge.getEndPoint() - rEdge.getStartPoint());
                aTangent.setLength(fHalfLineWidth);
                
                // prepare return value
                B2DPolygon aEdgePolygon;

                // buffered angle
                double fAngle(0.0);
                bool bAngle(false);

                // buffered perpendicular
                B2DVector aPerpend;
                bool bPerpend(false);

                // create left vertical
                if(bStartRound)
                {
                    aEdgePolygon = tools::createHalfUnitCircle();
                    fAngle = atan2(aTangent.getY(), aTangent.getX());
                    bAngle = true;
                    aEdgePolygon.transform(
                        tools::createScaleShearXRotateTranslateB2DHomMatrix(
                            fHalfLineWidth, fHalfLineWidth,
                            0.0,
                            fAngle + F_PI2,
                            rEdge.getStartPoint().getX(), rEdge.getStartPoint().getY()));
                }
                else
                {
                    aPerpend.setX(-aTangent.getY());
                    aPerpend.setY(aTangent.getX());
                    bPerpend = true;

                    if(bStartSquare)
                    {
                        const basegfx::B2DPoint aStart(rEdge.getStartPoint() - aTangent);

                        aEdgePolygon.append(aStart + aPerpend);
                        aEdgePolygon.append(aStart - aPerpend);
                    }
                    else
                    {
                        aEdgePolygon.append(rEdge.getStartPoint() + aPerpend);
                        aEdgePolygon.append(rEdge.getStartPoint()); // keep the in-between point for numerical reasons
                        aEdgePolygon.append(rEdge.getStartPoint() - aPerpend);
                    }
                }

                // create right vertical
                if(bEndRound)
                {
                    basegfx::B2DPolygon aEndPolygon(tools::createHalfUnitCircle());

                    if(!bAngle)
                    {
                        fAngle = atan2(aTangent.getY(), aTangent.getX());
                    }

                    aEndPolygon.transform(
                        tools::createScaleShearXRotateTranslateB2DHomMatrix(
                            fHalfLineWidth, fHalfLineWidth,
                            0.0,
                            fAngle - F_PI2,
                            rEdge.getEndPoint().getX(), rEdge.getEndPoint().getY()));
                    aEdgePolygon.append(aEndPolygon);
                }
                else
                {
                    if(!bPerpend)
                    {
                        aPerpend.setX(-aTangent.getY());
                        aPerpend.setY(aTangent.getX());
                    }

                    if(bEndSquare)
                    {
                        const basegfx::B2DPoint aEnd(rEdge.getEndPoint() + aTangent);

                        aEdgePolygon.append(aEnd - aPerpend);
                        aEdgePolygon.append(aEnd + aPerpend);
                    }
                    else
                    {
                        aEdgePolygon.append(rEdge.getEndPoint() - aPerpend);
                        aEdgePolygon.append(rEdge.getEndPoint()); // keep the in-between point for numerical reasons
                        aEdgePolygon.append(rEdge.getEndPoint() + aPerpend);
                    }
                }

                // close and return
                aEdgePolygon.setClosed(true);

                return aEdgePolygon;
            }
        }

        B2DPolygon createAreaGeometryForJoin(
            const B2DVector& rTangentPrev, 
            const B2DVector& rTangentEdge,
            const B2DVector& rPerpendPrev, 
            const B2DVector& rPerpendEdge,
            const B2DPoint& rPoint,
            double fHalfLineWidth, 
            B2DLineJoin eJoin, 
            double fMiterMinimumAngle)
		{
			OSL_ENSURE(fHalfLineWidth > 0.0, "createAreaGeometryForJoin: LineWidth too small (!)");
			OSL_ENSURE(B2DLINEJOIN_NONE != eJoin, "createAreaGeometryForJoin: B2DLINEJOIN_NONE not allowed (!)");

            // LineJoin from tangent rPerpendPrev to tangent rPerpendEdge in rPoint
            B2DPolygon aEdgePolygon;
			const B2DPoint aStartPoint(rPoint + rPerpendPrev);
			const B2DPoint aEndPoint(rPoint + rPerpendEdge);
            
			// test if for Miter, the angle is too small and the fallback
			// to bevel needs to be used
			if(B2DLINEJOIN_MITER == eJoin)
			{
				const double fAngle(fabs(rPerpendPrev.angle(rPerpendEdge)));

				if((F_PI - fAngle) < fMiterMinimumAngle)
				{
					// fallback to bevel
					eJoin = B2DLINEJOIN_BEVEL;
				}
			}

			switch(eJoin)
			{
				case B2DLINEJOIN_MITER :
				{
					aEdgePolygon.append(aEndPoint);
					aEdgePolygon.append(rPoint);
					aEdgePolygon.append(aStartPoint);
					
					// Look for the cut point between start point along rTangentPrev and
					// end point along rTangentEdge. -rTangentEdge should be used, but since
					// the cut value is used for interpolating along the first edge, the negation
					// is not needed since the same fCut will be found on the first edge.
					// If it exists, insert it to complete the mitered fill polygon.
    				double fCutPos(0.0);
					tools::findCut(aStartPoint, rTangentPrev, aEndPoint, rTangentEdge, CUTFLAG_ALL, &fCutPos);

					if(0.0 != fCutPos)
					{
						const B2DPoint aCutPoint(aStartPoint + (rTangentPrev * fCutPos));
						aEdgePolygon.append(aCutPoint);
					}
					
					break;
				}
				case B2DLINEJOIN_ROUND :
				{
					// use tooling to add needed EllipseSegment
					double fAngleStart(atan2(rPerpendPrev.getY(), rPerpendPrev.getX()));
					double fAngleEnd(atan2(rPerpendEdge.getY(), rPerpendEdge.getX()));
	                
					// atan2 results are [-PI .. PI], consolidate to [0.0 .. 2PI]
					if(fAngleStart < 0.0)
					{
						fAngleStart += F_2PI;
					}

					if(fAngleEnd < 0.0)
					{
						fAngleEnd += F_2PI;
					}

					const B2DPolygon aBow(tools::createPolygonFromEllipseSegment(rPoint, fHalfLineWidth, fHalfLineWidth, fAngleStart, fAngleEnd));

					if(aBow.count() > 1)
					{
						// #i101491#
						// use the original start/end positions; the ones from bow creation may be numerically
						// different due to their different creation. To guarantee good merging quality with edges
						// and edge roundings (and to reduce point count)
						aEdgePolygon = aBow;
						aEdgePolygon.setB2DPoint(0, aStartPoint);
						aEdgePolygon.setB2DPoint(aEdgePolygon.count() - 1, aEndPoint);
						aEdgePolygon.append(rPoint);

						break;
					}
					else
					{
						// wanted fall-through to default
					}
				}
				default: // B2DLINEJOIN_BEVEL
				{
					aEdgePolygon.append(aEndPoint);
					aEdgePolygon.append(rPoint);
					aEdgePolygon.append(aStartPoint);
					
					break;
				}
			}

            // create last polygon part for edge
            aEdgePolygon.setClosed(true);

            return aEdgePolygon;
        }
    } // end of anonymus namespace

	namespace tools
	{
        B2DPolyPolygon createAreaGeometry(
            const B2DPolygon& rCandidate, 
            double fHalfLineWidth, 
            B2DLineJoin eJoin, 
            com::sun::star::drawing::LineCap eCap,
            double fMaxAllowedAngle, 
			double fMaxPartOfEdge,
            double fMiterMinimumAngle)
		{
            if(fMaxAllowedAngle > F_PI2)
            {
                fMaxAllowedAngle = F_PI2;
            }
            else if(fMaxAllowedAngle < 0.01 * F_PI2)
            {
                fMaxAllowedAngle = 0.01 * F_PI2;
            }

            if(fMaxPartOfEdge > 1.0)
            {
                fMaxPartOfEdge = 1.0;
            }
            else if(fMaxPartOfEdge < 0.01)
            {
                fMaxPartOfEdge = 0.01;
            }

            if(fMiterMinimumAngle > F_PI)
            {
                fMiterMinimumAngle = F_PI;
            }
            else if(fMiterMinimumAngle < 0.01 * F_PI)
            {
                fMiterMinimumAngle = 0.01 * F_PI;
            }

            B2DPolygon aCandidate(rCandidate);
            const double fMaxCos(cos(fMaxAllowedAngle));
            
            aCandidate.removeDoublePoints();
            aCandidate = subdivideToSimple(aCandidate, fMaxCos * fMaxCos, fMaxPartOfEdge * fMaxPartOfEdge);
	        
            const sal_uInt32 nPointCount(aCandidate.count());

			if(nPointCount)
			{
                B2DPolyPolygon aRetval;
				const bool bEventuallyCreateLineJoin(B2DLINEJOIN_NONE != eJoin);
                const bool bIsClosed(aCandidate.isClosed());
                const sal_uInt32 nEdgeCount(bIsClosed ? nPointCount : nPointCount - 1);
                const bool bLineCap(!bIsClosed && com::sun::star::drawing::LineCap_BUTT != eCap);

                if(nEdgeCount)
                {
                    B2DCubicBezier aEdge;
                    B2DCubicBezier aPrev;

                    // prepare edge
                    aEdge.setStartPoint(aCandidate.getB2DPoint(0));

                    if(bIsClosed && bEventuallyCreateLineJoin)
                    {
                        // prepare previous edge
                        const sal_uInt32 nPrevIndex(nPointCount - 1);
                        aPrev.setStartPoint(aCandidate.getB2DPoint(nPrevIndex));
                        aPrev.setControlPointA(aCandidate.getNextControlPoint(nPrevIndex));
                        aPrev.setControlPointB(aCandidate.getPrevControlPoint(0));
                        aPrev.setEndPoint(aEdge.getStartPoint());
                    }

                    for(sal_uInt32 a(0); a < nEdgeCount; a++)
                    {
                        // fill current Edge
                        const sal_uInt32 nNextIndex((a + 1) % nPointCount);
                        aEdge.setControlPointA(aCandidate.getNextControlPoint(a));
                        aEdge.setControlPointB(aCandidate.getPrevControlPoint(nNextIndex));
                        aEdge.setEndPoint(aCandidate.getB2DPoint(nNextIndex));

                        // check and create linejoin
                        if(bEventuallyCreateLineJoin && (bIsClosed || 0 != a))
                        {
                            B2DVector aTangentPrev(aPrev.getTangent(1.0)); aTangentPrev.normalize();
                            B2DVector aTangentEdge(aEdge.getTangent(0.0)); aTangentEdge.normalize();
                            B2VectorOrientation aOrientation(getOrientation(aTangentPrev, aTangentEdge));

                            if(ORIENTATION_NEUTRAL == aOrientation)
                            {
                                   // they are parallell or empty; if they are both not zero and point 
                                   // in opposite direction, a half-circle is needed
                                   if(!aTangentPrev.equalZero() && !aTangentEdge.equalZero())
                                   {
                                    const double fAngle(fabs(aTangentPrev.angle(aTangentEdge)));
        
                                    if(fTools::equal(fAngle, F_PI))
                                    {
                                        // for half-circle production, fallback to positive
                                        // orientation
                                        aOrientation = ORIENTATION_POSITIVE;
                                    }
                                }
                            }

                            if(ORIENTATION_POSITIVE == aOrientation)
                            {
                                const B2DVector aPerpendPrev(getPerpendicular(aTangentPrev) * -fHalfLineWidth);
                                const B2DVector aPerpendEdge(getPerpendicular(aTangentEdge) * -fHalfLineWidth);
                                
                                aRetval.append(
                                    createAreaGeometryForJoin(
                                        aTangentPrev, 
                                        aTangentEdge,
                                        aPerpendPrev, 
                                        aPerpendEdge, 
                                        aEdge.getStartPoint(), 
                                        fHalfLineWidth, 
                                        eJoin, 
                                        fMiterMinimumAngle));
                            }
                            else if(ORIENTATION_NEGATIVE == aOrientation)
                            {
                                const B2DVector aPerpendPrev(getPerpendicular(aTangentPrev) * fHalfLineWidth);
                                const B2DVector aPerpendEdge(getPerpendicular(aTangentEdge) * fHalfLineWidth);

                                aRetval.append(
                                    createAreaGeometryForJoin(
                                        aTangentEdge, 
                                        aTangentPrev,
                                        aPerpendEdge, 
                                        aPerpendPrev, 
                                        aEdge.getStartPoint(), 
                                        fHalfLineWidth, 
                                        eJoin, 
                                        fMiterMinimumAngle));
                            }
                        }

                        // create geometry for edge
                        const bool bLast(a + 1 == nEdgeCount);

                        if(bLineCap)
                        {
                            const bool bFirst(!a);

                            aRetval.append(
                                createAreaGeometryForEdge(
                                    aEdge, 
                                    fHalfLineWidth,
                                    bFirst && com::sun::star::drawing::LineCap_ROUND == eCap,
                                    bLast && com::sun::star::drawing::LineCap_ROUND == eCap,
                                    bFirst && com::sun::star::drawing::LineCap_SQUARE == eCap,
                                    bLast && com::sun::star::drawing::LineCap_SQUARE == eCap));
                        }
                        else
                        {
                            aRetval.append(
                                createAreaGeometryForEdge(
                                    aEdge, 
                                    fHalfLineWidth,
                                    false,
                                    false,
                                    false,
                                    false));
                        }

                        // prepare next step
                        if(!bLast)
                        {
                            if(bEventuallyCreateLineJoin)
                            {
                                aPrev = aEdge;
                            }

                            aEdge.setStartPoint(aEdge.getEndPoint());
                        }
                    }
                }

                return aRetval;
            }
            else
            {
                return B2DPolyPolygon(rCandidate);
            }
        }
    } // end of namespace tools
} // end of namespace basegfx

//////////////////////////////////////////////////////////////////////////////
// eof
