/* -*- 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 <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
 
#include <rtl/ustring.hxx>
#include <sal/log.hxx>
#include <rtl/math.hxx>
#include <rtl/character.hxx>
#include <stringconversiontools.hxx>
 
namespace
{
 
void putCommandChar(OUStringBuffer& rBuffer,sal_Unicode& rLastSVGCommand, sal_Unicode aChar, bool bToLower,bool bVerbose)
{
    const sal_Unicode aCommand = bToLower ? rtl::toAsciiLowerCase(aChar) : aChar;
 
    if (bVerbose && rBuffer.getLength())
        rBuffer.append(' ');
 
    if (bVerbose || rLastSVGCommand != aCommand)
    {
        rBuffer.append(aCommand);
        rLastSVGCommand = aCommand;
    }
}
 
void putNumberChar(OUStringBuffer& rStr,double fValue, double fOldValue, bool bUseRelativeCoordinates,bool bVerbose)
{
    if (bUseRelativeCoordinates)
        fValue -= fOldValue;
 
    const sal_Int32 aLen(rStr.getLength());
    if (bVerbose || (aLen && basegfx::internal::isOnNumberChar(rStr[aLen - 1], false) && fValue >= 0.0))
        rStr.append(' ');
 
    rStr.append(fValue);
}
 
}
 
namespace basegfx::utils
{
        bool PointIndex::operator<(const PointIndex& rComp) const
        {
            if(rComp.getPolygonIndex() == getPolygonIndex())
            {
                return rComp.getPointIndex() < getPointIndex();
            }
 
            return rComp.getPolygonIndex() < getPolygonIndex();
        }
 
        bool importFromSvgD(
            B2DPolyPolygon& o_rPolyPolygon,
            std::u16string_view rSvgDStatement,
            bool bHandleRelativeNextPointCompatible,
            PointIndexSet* pHelpPointIndexSet)
        {
            o_rPolyPolygon.clear();
            const sal_Int32 nLen(rSvgDStatement.size());
            sal_Int32 nPos(0);
            double nLastX( 0.0 );
            double nLastY( 0.0 );
            B2DPolygon aCurrPoly;
 
            // skip initial whitespace
            basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
 
            while(nPos < nLen)
            {
                bool bRelative(false);
                const sal_Unicode aCurrChar(rSvgDStatement[nPos]);
 
                if(o_rPolyPolygon.count() && !aCurrPoly.count() && aCurrChar != 'm' && aCurrChar != 'M')
                {
                    // we have a new sub-polygon starting, but without a 'moveto' command.
                    // this requires to add the current point as start point to the polygon
                    // (see SVG1.1 8.3.3 The "closepath" command)
                    aCurrPoly.append(B2DPoint(nLastX, nLastY));
                }
 
                switch(aCurrChar)
                {
                    case 'z' :
                    case 'Z' :
                    {
                        // consume CurrChar and whitespace
                        nPos++;
                        basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
 
                        // create closed polygon and reset import values
                        if(aCurrPoly.count())
                        {
                            if(!bHandleRelativeNextPointCompatible)
                            {
                                // SVG defines that "the next subpath starts at the
                                // same initial point as the current subpath", so set the
                                // current point if we do not need to be compatible
                                nLastX = aCurrPoly.getB2DPoint(0).getX();
                                nLastY = aCurrPoly.getB2DPoint(0).getY();
                            }
 
                            aCurrPoly.setClosed(true);
                            o_rPolyPolygon.append(aCurrPoly);
                            aCurrPoly.clear();
                        }
 
                        break;
                    }
 
                    case 'm' :
                    case 'M' :
                    {
                        // create non-closed polygon and reset import values
                        if(aCurrPoly.count())
                        {
                            o_rPolyPolygon.append(aCurrPoly);
                            aCurrPoly.clear();
                        }
                        [[fallthrough]]; // to add coordinate data as 1st point of new polygon
                    }
                    case 'l' :
                    case 'L' :
                    {
                        if(aCurrChar == 'm' || aCurrChar == 'l')
                        {
                            bRelative = true;
                        }
 
                        // consume CurrChar and whitespace
                        nPos++;
                        basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
 
                        while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
                        {
                            double nX, nY;
 
                            if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
                            if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
 
                            if(bRelative)
                            {
                                nX += nLastX;
                                nY += nLastY;
                            }
 
                            // set last position
                            nLastX = nX;
                            nLastY = nY;
 
                            // add point
                            aCurrPoly.append(B2DPoint(nX, nY));
                        }
                        break;
                    }
 
                    case 'h' :
                    {
                        bRelative = true;
                        [[fallthrough]];
                    }
                    case 'H' :
                    {
                        nPos++;
                        basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
 
                        while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
                        {
                            double nX, nY(nLastY);
 
                            if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
 
                            if(bRelative)
                            {
                                nX += nLastX;
                            }
 
                            // set last position
                            nLastX = nX;
 
                            // add point
                            aCurrPoly.append(B2DPoint(nX, nY));
                        }
                        break;
                    }
 
                    case 'v' :
                    {
                        bRelative = true;
                        [[fallthrough]];
                    }
                    case 'V' :
                    {
                        nPos++;
                        basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
 
                        while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
                        {
                            double nX(nLastX), nY;
 
                            if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
 
                            if(bRelative)
                            {
                                nY += nLastY;
                            }
 
                            // set last position
                            nLastY = nY;
 
                            // add point
                            aCurrPoly.append(B2DPoint(nX, nY));
                        }
                        break;
                    }
 
                    case 's' :
                    {
                        bRelative = true;
                        [[fallthrough]];
                    }
                    case 'S' :
                    {
                        nPos++;
                        basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
 
                        while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
                        {
                            double nX, nY;
                            double nX2, nY2;
 
                            if(!basegfx::internal::importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false;
                            if(!basegfx::internal::importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false;
                            if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
                            if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
 
                            if(bRelative)
                            {
                                nX2 += nLastX;
                                nY2 += nLastY;
                                nX += nLastX;
                                nY += nLastY;
                            }
 
                            // ensure existence of start point
                            sal_uInt32 nCurrPolyCount = aCurrPoly.count();
                            if (nCurrPolyCount == 0)
                            {
                                aCurrPoly.append(B2DPoint(nLastX, nLastY));
                                nCurrPolyCount = 1;
                            }
                            assert(nCurrPolyCount > 0 && "coverity 2023.12.2");
 
                            // get first control point. It's the reflection of the PrevControlPoint
                            // of the last point. If not existent, use current point (see SVG)
                            B2DPoint aPrevControl(nLastX, nLastY);
                            const sal_uInt32 nIndex(nCurrPolyCount - 1);
 
                            if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex))
                            {
                                const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex));
                                const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex));
 
                                // use mirrored previous control point
                                aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX());
                                aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY());
                            }
 
                            // append curved edge
                            aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2, nY2), B2DPoint(nX, nY));
 
                            // set last position
                            nLastX = nX;
                            nLastY = nY;
                        }
                        break;
                    }
 
                    case 'c' :
                    {
                        bRelative = true;
                        [[fallthrough]];
                    }
                    case 'C' :
                    {
                        nPos++;
                        basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
 
                        while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
                        {
                            double nX, nY;
                            double nX1, nY1;
                            double nX2, nY2;
 
                            if(!basegfx::internal::importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false;
                            if(!basegfx::internal::importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false;
                            if(!basegfx::internal::importDoubleAndSpaces(nX2, nPos, rSvgDStatement, nLen)) return false;
                            if(!basegfx::internal::importDoubleAndSpaces(nY2, nPos, rSvgDStatement, nLen)) return false;
                            if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
                            if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
 
                            if(bRelative)
                            {
                                nX1 += nLastX;
                                nY1 += nLastY;
                                nX2 += nLastX;
                                nY2 += nLastY;
                                nX += nLastX;
                                nY += nLastY;
                            }
 
                            // ensure existence of start point
                            if(!aCurrPoly.count())
                            {
                                aCurrPoly.append(B2DPoint(nLastX, nLastY));
                            }
 
                            // append curved edge
                            aCurrPoly.appendBezierSegment(B2DPoint(nX1, nY1), B2DPoint(nX2, nY2), B2DPoint(nX, nY));
 
                            // set last position
                            nLastX = nX;
                            nLastY = nY;
                        }
                        break;
                    }
 
                    // #100617# quadratic beziers are imported as cubic ones
                    case 'q' :
                    {
                        bRelative = true;
                        [[fallthrough]];
                    }
                    case 'Q' :
                    {
                        nPos++;
                        basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
 
                        while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
                        {
                            double nX, nY;
                            double nX1, nY1;
 
                            if(!basegfx::internal::importDoubleAndSpaces(nX1, nPos, rSvgDStatement, nLen)) return false;
                            if(!basegfx::internal::importDoubleAndSpaces(nY1, nPos, rSvgDStatement, nLen)) return false;
                            if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
                            if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
 
                            if(bRelative)
                            {
                                nX1 += nLastX;
                                nY1 += nLastY;
                                nX += nLastX;
                                nY += nLastY;
                            }
 
                            // ensure existence of start point
                            if(!aCurrPoly.count())
                            {
                                aCurrPoly.append(B2DPoint(nLastX, nLastY));
                            }
 
                            // append curved edge
                            aCurrPoly.appendQuadraticBezierSegment(B2DPoint(nX1, nY1), B2DPoint(nX, nY));
 
                            // set last position
                            nLastX = nX;
                            nLastY = nY;
                        }
                        break;
                    }
 
                    // #100617# relative quadratic beziers are imported as cubic
                    case 't' :
                    {
                        bRelative = true;
                        [[fallthrough]];
                    }
                    case 'T' :
                    {
                        nPos++;
                        basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
 
                        while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
                        {
                            double nX, nY;
 
                            if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
                            if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
 
                            if(bRelative)
                            {
                                nX += nLastX;
                                nY += nLastY;
                            }
 
                            // ensure existence of start point
                            sal_uInt32 nCurrPolyCount = aCurrPoly.count();
                            if (nCurrPolyCount == 0)
                            {
                                aCurrPoly.append(B2DPoint(nLastX, nLastY));
                                nCurrPolyCount = 1;
                            }
                            assert(nCurrPolyCount > 0 && "coverity 2023.12.2");
 
                            // get first control point. It's the reflection of the PrevControlPoint
                            // of the last point. If not existent, use current point (see SVG)
                            B2DPoint aPrevControl(nLastX, nLastY);
                            const sal_uInt32 nIndex(nCurrPolyCount - 1);
                            const B2DPoint aPrevPoint(aCurrPoly.getB2DPoint(nIndex));
 
                            if(aCurrPoly.areControlPointsUsed() && aCurrPoly.isPrevControlPointUsed(nIndex))
                            {
                                const B2DPoint aPrevControlPoint(aCurrPoly.getPrevControlPoint(nIndex));
 
                                // use mirrored previous control point
                                aPrevControl.setX((2.0 * aPrevPoint.getX()) - aPrevControlPoint.getX());
                                aPrevControl.setY((2.0 * aPrevPoint.getY()) - aPrevControlPoint.getY());
                            }
 
                            if(!aPrevControl.equal(aPrevPoint))
                            {
                                // there is a prev control point, and we have the already mirrored one
                                // in aPrevControl. We also need the quadratic control point for this
                                // new quadratic segment to calculate the 2nd cubic control point
                                const B2DPoint aQuadControlPoint(
                                    ((3.0 * aPrevControl.getX()) - aPrevPoint.getX()) / 2.0,
                                    ((3.0 * aPrevControl.getY()) - aPrevPoint.getY()) / 2.0);
 
                                // calculate the cubic bezier coefficients from the quadratic ones.
                                const double nX2Prime((aQuadControlPoint.getX() * 2.0 + nX) / 3.0);
                                const double nY2Prime((aQuadControlPoint.getY() * 2.0 + nY) / 3.0);
 
                                // append curved edge, use mirrored cubic control point directly
                                aCurrPoly.appendBezierSegment(aPrevControl, B2DPoint(nX2Prime, nY2Prime), B2DPoint(nX, nY));
                            }
                            else
                            {
                                // when no previous control, SVG says to use current point -> straight line.
                                // Just add end point
                                aCurrPoly.append(B2DPoint(nX, nY));
                            }
 
                            // set last position
                            nLastX = nX;
                            nLastY = nY;
                        }
                        break;
                    }
 
                    case 'a' :
                    {
                        bRelative = true;
                        [[fallthrough]];
                    }
                    case 'A' :
                    {
                        nPos++;
                        basegfx::internal::skipSpaces(nPos, rSvgDStatement, nLen);
 
                        while(nPos < nLen && basegfx::internal::isOnNumberChar(rSvgDStatement, nPos))
                        {
                            double nX, nY;
                            double fRX, fRY, fPhi;
                            sal_Int32 bLargeArcFlag, bSweepFlag;
 
                            if(!basegfx::internal::importDoubleAndSpaces(fRX, nPos, rSvgDStatement, nLen)) return false;
                            if(!basegfx::internal::importDoubleAndSpaces(fRY, nPos, rSvgDStatement, nLen)) return false;
                            if(!basegfx::internal::importDoubleAndSpaces(fPhi, nPos, rSvgDStatement, nLen)) return false;
                            if(!basegfx::internal::importFlagAndSpaces(bLargeArcFlag, nPos, rSvgDStatement, nLen)) return false;
                            if(!basegfx::internal::importFlagAndSpaces(bSweepFlag, nPos, rSvgDStatement, nLen)) return false;
                            if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgDStatement, nLen)) return false;
                            if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgDStatement, nLen)) return false;
 
                            if(bRelative)
                            {
                                nX += nLastX;
                                nY += nLastY;
                            }
 
                            if( rtl::math::approxEqual(nX, nLastX) && rtl::math::approxEqual(nY, nLastY) )
                                continue; // start==end -> skip according to SVG spec
 
                            if( fRX == 0.0 || fRY == 0.0 )
                            {
                                // straight line segment according to SVG spec
                                aCurrPoly.append(B2DPoint(nX, nY));
                            }
                            else
                            {
                                // normalize according to SVG spec
                                fRX=fabs(fRX); fRY=fabs(fRY);
 
                                // from the SVG spec, appendix F.6.4
 
                                // |x1'|   |cos phi   sin phi|  |(x1 - x2)/2|
                                // |y1'| = |-sin phi  cos phi|  |(y1 - y2)/2|
                                const B2DPoint p1(nLastX, nLastY);
                                const B2DPoint p2(nX, nY);
                                B2DHomMatrix aTransform(basegfx::utils::createRotateB2DHomMatrix(
                                    -deg2rad(fPhi)));
 
                                const B2DPoint p1_prime( aTransform * B2DPoint(((p1-p2)/2.0)) );
 
                                //           ______________________________________       rx y1'
                                // |cx'|  + /  rx^2 ry^2 - rx^2 y1'^2 - ry^2 x1^2           ry
                                // |cy'| =-/       rx^2y1'^2 + ry^2 x1'^2               - ry x1'
                                //                                                          rx
                                // chose + if f_A != f_S
                                // chose - if f_A  = f_S
                                B2DPoint aCenter_prime;
                                const double fRadicant(
                                    (fRX*fRX*fRY*fRY - fRX*fRX*p1_prime.getY()*p1_prime.getY() - fRY*fRY*p1_prime.getX()*p1_prime.getX())/
                                    (fRX*fRX*p1_prime.getY()*p1_prime.getY() + fRY*fRY*p1_prime.getX()*p1_prime.getX()));
                                if( fRadicant < 0.0 )
                                {
                                    // no solution - according to SVG
                                    // spec, scale up ellipse
                                    // uniformly such that it passes
                                    // through end points (denominator
                                    // of radicant solved for fRY,
                                    // with s=fRX/fRY)
                                    const double fRatio(fRX/fRY);
                                    fRY=std::hypot(p1_prime.getY(), p1_prime.getX()/fRatio);
                                    fRX=fRatio*fRY;
 
                                    // keep center_prime forced to (0,0)
                                }
                                else
                                {
                                    const double fFactor(
                                        (bLargeArcFlag==bSweepFlag ? -1.0 : 1.0) *
                                        sqrt(fRadicant));
 
                                    // actually calculate center_prime
                                    aCenter_prime = B2DPoint(
                                        fFactor*fRX*p1_prime.getY()/fRY,
                                        -fFactor*fRY*p1_prime.getX()/fRX);
                                }
 
                                //              +           u - v
                                // angle(u,v) =  arccos( ------------ )     (take the sign of (ux vy - uy vx))
                                //              -        ||u|| ||v||
 
                                //                  1    | (x1' - cx')/rx |
                                // theta1 = angle((   ), |                | )
                                //                  0    | (y1' - cy')/ry |
                                const B2DPoint aRadii(fRX,fRY);
                                double fTheta1(
                                    B2DVector(1.0,0.0).angle(
                                        (p1_prime-aCenter_prime)/aRadii));
 
                                //                 |1|    |  (-x1' - cx')/rx |
                                // theta2 = angle( | | ,  |                  | )
                                //                 |0|    |  (-y1' - cy')/ry |
                                double fTheta2(
                                    B2DVector(1.0,0.0).angle(
                                        (-p1_prime-aCenter_prime)/aRadii));
 
                                // map both angles to [0,2pi)
                                fTheta1 = fmod(2*M_PI+fTheta1,2*M_PI);
                                fTheta2 = fmod(2*M_PI+fTheta2,2*M_PI);
 
                                // make sure the large arc is taken
                                // (since
                                // createPolygonFromEllipseSegment()
                                // normalizes to e.g. cw arc)
                                if( !bSweepFlag )
                                    std::swap(fTheta1,fTheta2);
 
                                // finally, create bezier polygon from this
                                B2DPolygon aSegment(
                                    utils::createPolygonFromUnitEllipseSegment(
                                        fTheta1, fTheta2 ));
 
                                // transform ellipse by rotation & move to final center
                                aTransform = basegfx::utils::createScaleB2DHomMatrix(fRX, fRY);
                                aTransform.translate(aCenter_prime.getX(),
                                                     aCenter_prime.getY());
                                aTransform.rotate(deg2rad(fPhi));
                                const B2DPoint aOffset((p1+p2)/2.0);
                                aTransform.translate(aOffset.getX(),
                                                     aOffset.getY());
                                aSegment.transform(aTransform);
 
                                // createPolygonFromEllipseSegment()
                                // always creates arcs that are
                                // positively oriented - flip polygon
                                // if we swapped angles above
                                if( !bSweepFlag )
                                    aSegment.flip();
 
                                // remember PointIndex of evtl. added pure helper points
                                sal_uInt32 nPointIndex(aCurrPoly.count() + 1);
                                aCurrPoly.append(aSegment);
 
                                // if asked for, mark pure helper points by adding them to the index list of
                                // helper points
                                if(pHelpPointIndexSet && aCurrPoly.count() > 1)
                                {
                                    const sal_uInt32 nPolyIndex(o_rPolyPolygon.count());
 
                                    for(;nPointIndex + 1 < aCurrPoly.count(); nPointIndex++)
                                    {
                                        pHelpPointIndexSet->insert(PointIndex(nPolyIndex, nPointIndex));
                                    }
                                }
                            }
 
                            // set last position
                            nLastX = nX;
                            nLastY = nY;
                        }
                        break;
                    }
 
                    default:
                    {
                        SAL_WARN("basegfx", "importFromSvgD(): skipping tags in svg:d element (unknown: \""
                                << OUString(aCurrChar)
                                << "\")!");
                        ++nPos;
                        break;
                    }
                }
            }
 
            // if there is polygon data, create non-closed polygon
            if(aCurrPoly.count())
            {
                o_rPolyPolygon.append(aCurrPoly);
            }
 
            return true;
        }
 
        bool importFromSvgPoints( B2DPolygon&            o_rPoly,
                                  std::u16string_view rSvgPointsAttribute )
        {
            o_rPoly.clear();
            const sal_Int32 nLen(rSvgPointsAttribute.size());
            sal_Int32 nPos(0);
            double nX, nY;
 
            // skip initial whitespace
            basegfx::internal::skipSpaces(nPos, rSvgPointsAttribute, nLen);
 
            while(nPos < nLen)
            {
                if(!basegfx::internal::importDoubleAndSpaces(nX, nPos, rSvgPointsAttribute, nLen)) return false;
                if(!basegfx::internal::importDoubleAndSpaces(nY, nPos, rSvgPointsAttribute, nLen)) return false;
 
                // add point
                o_rPoly.append(B2DPoint(nX, nY));
 
                // skip to next number, or finish
                basegfx::internal::skipSpaces(nPos, rSvgPointsAttribute, nLen);
            }
 
            return true;
        }
 
        OUString exportToSvgPoints( const B2DPolygon& rPoly )
        {
            SAL_WARN_IF(rPoly.areControlPointsUsed(), "basegfx", "exportToSvgPoints: Only non-bezier polygons allowed (!)");
            const sal_uInt32 nPointCount(rPoly.count());
            OUStringBuffer aResult;
 
            for(sal_uInt32 a(0); a < nPointCount; a++)
            {
                const basegfx::B2DPoint aPoint(rPoly.getB2DPoint(a));
 
                if(a)
                {
                    aResult.append(' ');
                }
 
                aResult.append(OUString::number(aPoint.getX())
                        + ","
                        + OUString::number(aPoint.getY()));
            }
 
            return aResult.makeStringAndClear();
        }
 
        OUString exportToSvgD(
            const B2DPolyPolygon& rPolyPolygon,
            bool bUseRelativeCoordinates,
            bool bDetectQuadraticBeziers,
            bool bHandleRelativeNextPointCompatible,
            bool bOOXMLMotionPath)
        {
            const sal_uInt32 nCount(rPolyPolygon.count());
            sal_uInt32 nCombinedPointCount = 0;
            for(sal_uInt32 i(0); i < nCount; i++)
            {
                const B2DPolygon& aPolygon(rPolyPolygon.getB2DPolygon(i));
                nCombinedPointCount += aPolygon.count();
            }
 
            OUStringBuffer aResult(std::max<int>(nCombinedPointCount * 32,512));
            B2DPoint aCurrentSVGPosition(0.0, 0.0); // SVG assumes (0,0) as the initial current point
 
            for(sal_uInt32 i(0); i < nCount; i++)
            {
                const B2DPolygon& aPolygon(rPolyPolygon.getB2DPolygon(i));
                const sal_uInt32 nPointCount(aPolygon.count());
 
                if(nPointCount)
                {
                    const bool bPolyUsesControlPoints(aPolygon.areControlPointsUsed());
                    const sal_uInt32 nEdgeCount(aPolygon.isClosed() ? nPointCount : nPointCount - 1);
                    sal_Unicode aLastSVGCommand(' '); // last SVG command char
                    B2DPoint aLeft, aRight; // for quadratic bezier test
 
                    // handle polygon start point
                    B2DPoint aEdgeStart(aPolygon.getB2DPoint(0));
                    bool bUseRelativeCoordinatesForFirstPoint(bUseRelativeCoordinates);
 
                    if(bHandleRelativeNextPointCompatible)
                    {
                        // To get around the error that the start point for the next polygon is the
                        // start point of the current one (and not the last as it was handled up to now)
                        // do force to write an absolute 'M' command as start for the next polygon
                        bUseRelativeCoordinatesForFirstPoint = false;
                    }
 
                    // Write 'moveto' and the 1st coordinates, set aLastSVGCommand to 'lineto'
                    putCommandChar(aResult, aLastSVGCommand, 'M', bUseRelativeCoordinatesForFirstPoint, bOOXMLMotionPath);
                    putNumberChar(aResult, aEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinatesForFirstPoint, bOOXMLMotionPath);
                    putNumberChar(aResult, aEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinatesForFirstPoint, bOOXMLMotionPath);
                    aLastSVGCommand =  bUseRelativeCoordinatesForFirstPoint ? 'l' : 'L';
                    aCurrentSVGPosition = aEdgeStart;
 
                    for(sal_uInt32 nIndex(0); nIndex < nEdgeCount; nIndex++)
                    {
                        // prepare access to next point
                        const sal_uInt32 nNextIndex((nIndex + 1) % nPointCount);
                        const B2DPoint aEdgeEnd(aPolygon.getB2DPoint(nNextIndex));
 
                        // handle edge from (aEdgeStart, aEdgeEnd) using indices (nIndex, nNextIndex)
                        const bool bEdgeIsBezier(bPolyUsesControlPoints
                            && (aPolygon.isNextControlPointUsed(nIndex) || aPolygon.isPrevControlPointUsed(nNextIndex)));
 
                        if(bEdgeIsBezier)
                        {
                            // handle bezier edge
                            const B2DPoint aControlEdgeStart(aPolygon.getNextControlPoint(nIndex));
                            const B2DPoint aControlEdgeEnd(aPolygon.getPrevControlPoint(nNextIndex));
                            bool bIsQuadraticBezier(false);
 
                            // check continuity at current edge's start point. For SVG, do NOT use an
                            // existing continuity since no 'S' or 's' statement should be written. At
                            // import, that 'previous' control vector is not available. SVG documentation
                            // says for interpretation:
 
                            // "(If there is no previous command or if the previous command was
                            // not a C, c, S or s, assume the first control point is coincident
                            // with the current point.)"
 
                            // That's what is done from our import, so avoid exporting it as first statement
                            // is necessary.
                            const bool bSymmetricAtEdgeStart(
                                !bOOXMLMotionPath && nIndex != 0
                                && aPolygon.getContinuityInPoint(nIndex) == B2VectorContinuity::C2);
 
                            if(bDetectQuadraticBeziers)
                            {
                                // check for quadratic beziers - that's
                                // the case if both control points are in
                                // the same place when they are prolonged
                                // to the common quadratic control point
 
                                // Left: P = (3P1 - P0) / 2
                                // Right: P = (3P2 - P3) / 2
                                aLeft = B2DPoint((3.0 * aControlEdgeStart - aEdgeStart) / 2.0);
                                aRight= B2DPoint((3.0 * aControlEdgeEnd - aEdgeEnd) / 2.0);
                                bIsQuadraticBezier = aLeft.equal(aRight);
                            }
 
                            if(bIsQuadraticBezier)
                            {
                                // approximately equal, export as quadratic bezier
                                if(bSymmetricAtEdgeStart)
                                {
                                    putCommandChar(aResult, aLastSVGCommand, 'T', bUseRelativeCoordinates, bOOXMLMotionPath);
 
                                    putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
                                    putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
                                    aCurrentSVGPosition = aEdgeEnd;
                                }
                                else
                                {
                                    putCommandChar(aResult, aLastSVGCommand, 'Q', bUseRelativeCoordinates, bOOXMLMotionPath);
 
                                    putNumberChar(aResult, aLeft.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
                                    putNumberChar(aResult, aLeft.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
                                    putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
                                    putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
                                    aCurrentSVGPosition = aEdgeEnd;
                                }
                            }
                            else
                            {
                                // export as cubic bezier
                                if(bSymmetricAtEdgeStart)
                                {
                                    putCommandChar(aResult, aLastSVGCommand, 'S', bUseRelativeCoordinates, bOOXMLMotionPath);
 
                                    putNumberChar(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
                                    putNumberChar(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
                                    putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
                                    putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
                                    aCurrentSVGPosition = aEdgeEnd;
                                }
                                else
                                {
                                    putCommandChar(aResult, aLastSVGCommand, 'C', bUseRelativeCoordinates, bOOXMLMotionPath);
 
                                    putNumberChar(aResult, aControlEdgeStart.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
                                    putNumberChar(aResult, aControlEdgeStart.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
                                    putNumberChar(aResult, aControlEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
                                    putNumberChar(aResult, aControlEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
                                    putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
                                    putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
                                    aCurrentSVGPosition = aEdgeEnd;
                                }
                            }
                        }
                        else
                        {
                            // straight edge
                            if(nNextIndex == 0)
                            {
                                // it's a closed polygon's last edge and it's not a bezier edge, so there is
                                // no need to write it
                            }
                            else
                            {
                                const bool bXEqual(rtl::math::approxEqual(aEdgeStart.getX(), aEdgeEnd.getX()));
                                const bool bYEqual(rtl::math::approxEqual(aEdgeStart.getY(), aEdgeEnd.getY()));
 
                                if(bXEqual && bYEqual)
                                {
                                    // point is a double point; do not export at all
                                }
                                else if(bXEqual && !bOOXMLMotionPath)
                                {
                                    // export as vertical line
                                    putCommandChar(aResult, aLastSVGCommand, 'V', bUseRelativeCoordinates, bOOXMLMotionPath);
 
                                    putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
                                    aCurrentSVGPosition = aEdgeEnd;
                                }
                                else if(bYEqual && !bOOXMLMotionPath)
                                {
                                    // export as horizontal line
                                    putCommandChar(aResult, aLastSVGCommand, 'H', bUseRelativeCoordinates, bOOXMLMotionPath);
 
                                    putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
                                    aCurrentSVGPosition = aEdgeEnd;
                                }
                                else
                                {
                                    // export as line
                                    putCommandChar(aResult, aLastSVGCommand, 'L', bUseRelativeCoordinates, bOOXMLMotionPath);
 
                                    putNumberChar(aResult, aEdgeEnd.getX(), aCurrentSVGPosition.getX(), bUseRelativeCoordinates, bOOXMLMotionPath);
                                    putNumberChar(aResult, aEdgeEnd.getY(), aCurrentSVGPosition.getY(), bUseRelativeCoordinates, bOOXMLMotionPath);
                                    aCurrentSVGPosition = aEdgeEnd;
                                }
                            }
                        }
 
                        // prepare edge start for next loop step
                        aEdgeStart = aEdgeEnd;
                    }
 
                    // close path if closed poly (Z and z are equivalent here, but looks nicer when case is matched)
                    if(aPolygon.isClosed())
                    {
                        putCommandChar(aResult, aLastSVGCommand, 'Z', bUseRelativeCoordinates, bOOXMLMotionPath);
                    }
                    else if (bOOXMLMotionPath)
                    {
                        putCommandChar(aResult, aLastSVGCommand, 'E', bUseRelativeCoordinates, bOOXMLMotionPath);
                    }
 
                    if(!bHandleRelativeNextPointCompatible)
                    {
                        // SVG defines that "the next subpath starts at the same initial point as the current subpath",
                        // so set aCurrentSVGPosition to the 1st point of the current, now ended and written path
                        aCurrentSVGPosition = aPolygon.getB2DPoint(0);
                    }
                }
            }
 
            return aResult.makeStringAndClear();
        }
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

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

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

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

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

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