/* -*- 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 <sal/config.h>
 
#include <algorithm>
 
#include <tools/debug.hxx>
#include <tools/poly.hxx>
#include <tools/helpers.hxx>
#include <tools/gen.hxx>
 
#include <svx/xpoly.hxx>
#include <xpolyimp.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/range/b2drange.hxx>
 
 
ImpXPolygon::ImpXPolygon(sal_uInt16 nInitSize, sal_uInt16 _nResize)
    : pOldPointAry(nullptr)
    , bDeleteOldPoints(false)
    , nSize(0)
    , nResize(_nResize)
    , nPoints(0)
{
    Resize(nInitSize);
}
 
ImpXPolygon::ImpXPolygon( const ImpXPolygon& rImpXPoly )
    : pOldPointAry(nullptr)
    , bDeleteOldPoints(false)
    , nSize(0)
    , nResize(rImpXPoly.nResize)
    , nPoints(0)
{
    rImpXPoly.CheckPointDelete();
 
    Resize( rImpXPoly.nSize );
 
    // copy
    nPoints = rImpXPoly.nPoints;
    memcpy( pPointAry.get(), rImpXPoly.pPointAry.get(), nSize*sizeof( Point ) );
    memcpy( pFlagAry.get(), rImpXPoly.pFlagAry.get(), nSize );
}
 
ImpXPolygon::~ImpXPolygon()
{
    pPointAry.reset();
    if ( bDeleteOldPoints )
    {
        delete[] pOldPointAry;
        pOldPointAry = nullptr;
    }
}
 
bool ImpXPolygon::operator==(const ImpXPolygon& rImpXPoly) const
{
    return nPoints==rImpXPoly.nPoints &&
           (nPoints==0 ||
            (memcmp(pPointAry.get(), rImpXPoly.pPointAry.get(), nPoints*sizeof(Point))==0 &&
             memcmp(pFlagAry.get(), rImpXPoly.pFlagAry.get(), nPoints)==0));
}
 
/** Change polygon size
 *
 * @param nNewSize      the new size of the polygon
 * @param bDeletePoints if FALSE, do not delete the point array directly but
 *                      wait for the next call before doing so. This prevents
 *                      errors with XPoly[n] = XPoly[0] where a resize might
 *                      destroy the right side point array too early.
 */
void ImpXPolygon::Resize( sal_uInt16 nNewSize, bool bDeletePoints )
{
    if( nNewSize == nSize )
        return;
 
    PolyFlags*  pOldFlagAry  = pFlagAry.release();
    sal_uInt16  nOldSize     = nSize;
 
    CheckPointDelete();
    pOldPointAry = pPointAry.release();
 
    // Round the new size to a multiple of nResize, if
    // the object was not newly created (nSize != 0)
    if ( nSize != 0 && nNewSize > nSize )
    {
        DBG_ASSERT(nResize, "Trying to resize but nResize = 0 !");
        nNewSize = nSize + ((nNewSize-nSize-1) / nResize + 1) * nResize;
    }
    // create point array
    nSize     = nNewSize;
    pPointAry.reset( new Point[ nSize ] );
 
    // create flag array
    pFlagAry.reset( new PolyFlags[ nSize ] );
    memset( pFlagAry.get(), 0, nSize );
 
    // copy if needed
    if (nOldSize)
    {
        if( nOldSize < nSize )
        {
            memcpy( pPointAry.get(), pOldPointAry, nOldSize*sizeof( Point ) );
            memcpy( pFlagAry.get(),  pOldFlagAry, nOldSize );
        }
        else
        {
            memcpy( pPointAry.get(), pOldPointAry, nSize*sizeof( Point ) );
            memcpy( pFlagAry.get(), pOldFlagAry, nSize );
 
            // adjust number of valid points
            if( nPoints > nSize )
                nPoints = nSize;
        }
    }
    if ( bDeletePoints )
    {
        delete[] pOldPointAry;
        pOldPointAry = nullptr;
    }
    else
        bDeleteOldPoints = true;
    delete[] pOldFlagAry;
}
 
void ImpXPolygon::InsertSpace( sal_uInt16 nPos, sal_uInt16 nCount )
{
    CheckPointDelete();
 
    if ( nPos > nPoints )
        nPos = nPoints;
 
    // if the polygon is too small then enlarge it
    if( (nPoints + nCount) > nSize )
        Resize( nPoints + nCount );
 
    // If the insert is not at the last position, move everything after backwards
    if( nPos < nPoints )
    {
        sal_uInt16 nMove = nPoints - nPos;
        memmove( &pPointAry[nPos+nCount], &pPointAry[nPos],
                 nMove * sizeof(Point) );
        memmove( &pFlagAry[nPos+nCount], &pFlagAry[nPos], nMove );
    }
    std::fill(pPointAry.get() + nPos, pPointAry.get() + nPos + nCount, Point());
    memset( &pFlagAry [nPos], 0, nCount );
 
    nPoints = nPoints + nCount;
}
 
void ImpXPolygon::Remove( sal_uInt16 nPos, sal_uInt16 nCount )
{
    CheckPointDelete();
 
    if( (nPos + nCount) > nPoints )
        return;
 
    sal_uInt16 nMove = nPoints - nPos - nCount;
 
    if( nMove )
    {
        memmove( &pPointAry[nPos], &pPointAry[nPos+nCount],
                 nMove * sizeof(Point) );
        memmove( &pFlagAry[nPos], &pFlagAry[nPos+nCount], nMove );
    }
    std::fill(pPointAry.get() + (nPoints - nCount), pPointAry.get() + nPoints, Point());
    memset( &pFlagAry [nPoints - nCount], 0, nCount );
    nPoints = nPoints - nCount;
}
 
void ImpXPolygon::CheckPointDelete() const
{
    if ( bDeleteOldPoints )
    {
        delete[] pOldPointAry;
        const_cast< ImpXPolygon* >(this)->pOldPointAry = nullptr;
        const_cast< ImpXPolygon* >(this)->bDeleteOldPoints = false;
    }
}
 
XPolygon::XPolygon( sal_uInt16 nSize )
    : m_pImpXPolygon( ImpXPolygon( nSize, 16 ) )
{
}
 
XPolygon::XPolygon( const XPolygon& ) = default;
 
XPolygon::XPolygon( XPolygon&& ) = default;
 
/// create a XPolygon out of a standard polygon
XPolygon::XPolygon( const tools::Polygon& rPoly )
    : m_pImpXPolygon( rPoly.GetSize() )
{
    sal_uInt16 nSize = rPoly.GetSize();
    m_pImpXPolygon->nPoints = nSize;
 
    for( sal_uInt16 i = 0; i < nSize;  i++ )
    {
        m_pImpXPolygon->pPointAry[i] = rPoly[i];
        m_pImpXPolygon->pFlagAry[i] = rPoly.GetFlags( i );
    }
}
 
/// create a rectangle (also with rounded corners) as a Bézier polygon
XPolygon::XPolygon(const tools::Rectangle& rRect, tools::Long nRx, tools::Long nRy)
    : m_pImpXPolygon( 17 )
{
    tools::Long nWh = (rRect.GetWidth()  - 1) / 2;
    tools::Long nHh = (rRect.GetHeight() - 1) / 2;
 
    if ( nRx > nWh )    nRx = nWh;
    if ( nRy > nHh )    nRy = nHh;
 
    // negate Rx => circle clockwise
    nRx = -nRx;
 
    // factor for control points of the Bézier curve: 8/3 * (sin(45g) - 0.5)
    tools::Long    nXHdl = static_cast<tools::Long>(0.552284749 * nRx);
    tools::Long    nYHdl = static_cast<tools::Long>(0.552284749 * nRy);
    sal_uInt16  nPos = 0;
 
    if ( nRx && nRy )
    {
        Point aCenter;
 
        for (sal_uInt16 nQuad = 0; nQuad < 4; nQuad++)
        {
            switch ( nQuad )
            {
                case 0: aCenter = rRect.TopLeft();
                        aCenter.AdjustX( -nRx );
                        aCenter.AdjustY(nRy );
                        break;
                case 1: aCenter = rRect.TopRight();
                        aCenter.AdjustX(nRx );
                        aCenter.AdjustY(nRy );
                        break;
                case 2: aCenter = rRect.BottomRight();
                        aCenter.AdjustX(nRx );
                        aCenter.AdjustY( -nRy );
                        break;
                case 3: aCenter = rRect.BottomLeft();
                        aCenter.AdjustX( -nRx );
                        aCenter.AdjustY( -nRy );
                        break;
            }
            GenBezArc(aCenter, nRx, nRy, nXHdl, nYHdl, 0_deg100, 9000_deg100, nQuad, nPos);
            m_pImpXPolygon->pFlagAry[nPos  ] = PolyFlags::Smooth;
            m_pImpXPolygon->pFlagAry[nPos+3] = PolyFlags::Smooth;
            nPos += 4;
        }
    }
    else
    {
        m_pImpXPolygon->pPointAry[nPos++] = rRect.TopLeft();
        m_pImpXPolygon->pPointAry[nPos++] = rRect.TopRight();
        m_pImpXPolygon->pPointAry[nPos++] = rRect.BottomRight();
        m_pImpXPolygon->pPointAry[nPos++] = rRect.BottomLeft();
    }
    m_pImpXPolygon->pPointAry[nPos] = m_pImpXPolygon->pPointAry[0];
    m_pImpXPolygon->nPoints = nPos + 1;
}
 
/// create an ellipse (curve) as Bézier polygon
XPolygon::XPolygon(const Point& rCenter, tools::Long nRx, tools::Long nRy,
                   Degree100 nStartAngle, Degree100 nEndAngle, bool bClose)
    : m_pImpXPolygon( 17 )
{
    nStartAngle %= 36000_deg100;
    if ( nEndAngle > 36000_deg100 ) nEndAngle %= 36000_deg100;
    bool bFull = (nStartAngle == 0_deg100 && nEndAngle == 36000_deg100);
 
    // factor for control points of the Bézier curve: 8/3 * (sin(45g) - 0.5)
    tools::Long    nXHdl = static_cast<tools::Long>(0.552284749 * nRx);
    tools::Long    nYHdl = static_cast<tools::Long>(0.552284749 * nRy);
    sal_uInt16  nPos = 0;
    bool    bLoopEnd = false;
 
    do
    {
        Degree100 nA1, nA2;
        sal_uInt16 nQuad = nStartAngle.get() / 9000;
        if ( nQuad == 4 ) nQuad = 0;
        bLoopEnd = CheckAngles(nStartAngle, nEndAngle, nA1, nA2);
        GenBezArc(rCenter, nRx, nRy, nXHdl, nYHdl, nA1, nA2, nQuad, nPos);
        nPos += 3;
        if ( !bLoopEnd )
            m_pImpXPolygon->pFlagAry[nPos] = PolyFlags::Smooth;
 
    } while ( !bLoopEnd );
 
    // if not a full circle then connect edges with center point if necessary
    if ( !bFull && bClose )
        m_pImpXPolygon->pPointAry[++nPos] = rCenter;
 
    if ( bFull )
    {
        m_pImpXPolygon->pFlagAry[0   ] = PolyFlags::Smooth;
        m_pImpXPolygon->pFlagAry[nPos] = PolyFlags::Smooth;
    }
    m_pImpXPolygon->nPoints = nPos + 1;
}
 
XPolygon::~XPolygon() = default;
 
void XPolygon::SetPointCount( sal_uInt16 nPoints )
{
    std::as_const(m_pImpXPolygon)->CheckPointDelete();
 
    if( m_pImpXPolygon->nSize < nPoints )
        m_pImpXPolygon->Resize( nPoints );
 
    if ( nPoints < m_pImpXPolygon->nPoints )
    {
        sal_uInt16 nSize = m_pImpXPolygon->nPoints - nPoints;
        std::fill(
            m_pImpXPolygon->pPointAry.get() + nPoints, m_pImpXPolygon->pPointAry.get() + nPoints + nSize, Point());
        memset( &m_pImpXPolygon->pFlagAry [nPoints], 0, nSize );
    }
    m_pImpXPolygon->nPoints = nPoints;
}
 
sal_uInt16 XPolygon::GetSize() const
{
    m_pImpXPolygon->CheckPointDelete();
    return m_pImpXPolygon->nSize;
}
 
sal_uInt16 XPolygon::GetPointCount() const
{
    m_pImpXPolygon->CheckPointDelete();
    return m_pImpXPolygon->nPoints;
}
 
void XPolygon::Insert( sal_uInt16 nPos, const Point& rPt, PolyFlags eFlags )
{
    if (nPos>m_pImpXPolygon->nPoints) nPos=m_pImpXPolygon->nPoints;
    m_pImpXPolygon->InsertSpace( nPos, 1 );
    m_pImpXPolygon->pPointAry[nPos] = rPt;
    m_pImpXPolygon->pFlagAry[nPos]  = eFlags;
}
 
void XPolygon::Insert( sal_uInt16 nPos, const XPolygon& rXPoly )
{
    if (nPos>m_pImpXPolygon->nPoints) nPos=m_pImpXPolygon->nPoints;
 
    sal_uInt16 nPoints = rXPoly.GetPointCount();
 
    m_pImpXPolygon->InsertSpace( nPos, nPoints );
 
    memcpy( &(m_pImpXPolygon->pPointAry[nPos]),
            rXPoly.m_pImpXPolygon->pPointAry.get(),
            nPoints*sizeof( Point ) );
    memcpy( &(m_pImpXPolygon->pFlagAry[nPos]),
            rXPoly.m_pImpXPolygon->pFlagAry.get(),
            nPoints );
}
 
void XPolygon::Remove( sal_uInt16 nPos, sal_uInt16 nCount )
{
    m_pImpXPolygon->Remove( nPos, nCount );
}
 
void XPolygon::Move( tools::Long nHorzMove, tools::Long nVertMove )
{
    if ( !nHorzMove && !nVertMove )
        return;
 
    // move points
    sal_uInt16 nCount = m_pImpXPolygon->nPoints;
    for ( sal_uInt16 i = 0; i < nCount; i++ )
    {
        Point* pPt = &(m_pImpXPolygon->pPointAry[i]);
        pPt->AdjustX( nHorzMove );
        pPt->AdjustY( nVertMove );
    }
}
 
tools::Rectangle XPolygon::GetBoundRect() const
{
    m_pImpXPolygon->CheckPointDelete();
    tools::Rectangle aRetval;
 
    if(m_pImpXPolygon->nPoints)
    {
        // #i37709#
        // For historical reasons the control points are not part of the
        // BoundRect. This makes it necessary to subdivide the polygon to
        // get a relatively correct BoundRect. Numerically, this is not
        // correct and never was.
 
        const basegfx::B2DRange aPolygonRange(basegfx::utils::getRange(getB2DPolygon()));
        aRetval = tools::Rectangle(basegfx::fround<tools::Long>(aPolygonRange.getMinX()),
                                   basegfx::fround<tools::Long>(aPolygonRange.getMinY()),
                                   basegfx::fround<tools::Long>(aPolygonRange.getMaxX()),
                                   basegfx::fround<tools::Long>(aPolygonRange.getMaxY()));
    }
 
    return aRetval;
}
 
const Point& XPolygon::operator[]( sal_uInt16 nPos ) const
{
    DBG_ASSERT(nPos < m_pImpXPolygon->nPoints, "Invalid index at const array access to XPolygon");
 
    m_pImpXPolygon->CheckPointDelete();
    return m_pImpXPolygon->pPointAry[nPos];
}
 
Point& XPolygon::operator[]( sal_uInt16 nPos )
{
    std::as_const(m_pImpXPolygon)->CheckPointDelete();
 
    if( nPos >= m_pImpXPolygon->nSize )
    {
        DBG_ASSERT(m_pImpXPolygon->nResize, "Invalid index at array access to XPolygon");
        m_pImpXPolygon->Resize(nPos + 1, false);
    }
    if( nPos >= m_pImpXPolygon->nPoints )
        m_pImpXPolygon->nPoints = nPos + 1;
 
    return m_pImpXPolygon->pPointAry[nPos];
}
 
XPolygon& XPolygon::operator=( const XPolygon& ) = default;
 
XPolygon& XPolygon::operator=( XPolygon&& ) = default;
 
bool XPolygon::operator==( const XPolygon& rXPoly ) const
{
    m_pImpXPolygon->CheckPointDelete();
    return rXPoly.m_pImpXPolygon == m_pImpXPolygon;
}
 
/// get the flags for the point at the given position
PolyFlags XPolygon::GetFlags( sal_uInt16 nPos ) const
{
    m_pImpXPolygon->CheckPointDelete();
    return m_pImpXPolygon->pFlagAry[nPos];
}
 
/// set the flags for the point at the given position
void XPolygon::SetFlags( sal_uInt16 nPos, PolyFlags eFlags )
{
    std::as_const(m_pImpXPolygon)->CheckPointDelete();
    m_pImpXPolygon->pFlagAry[nPos] = eFlags;
}
 
/// short path to read the CONTROL flag directly (TODO: better explain what the sense behind this flag is!)
bool XPolygon::IsControl(sal_uInt16 nPos) const
{
    return m_pImpXPolygon->pFlagAry[nPos] == PolyFlags::Control;
}
 
/// short path to read the SMOOTH and SYMMTR flag directly (TODO: better explain what the sense behind these flags is!)
bool XPolygon::IsSmooth(sal_uInt16 nPos) const
{
    PolyFlags eFlag = m_pImpXPolygon->pFlagAry[nPos];
    return ( eFlag == PolyFlags::Smooth || eFlag == PolyFlags::Symmetric );
}
 
/** calculate the euclidean distance between two points
 *
 * @param nP1 The first point
 * @param nP2 The second point
 */
double XPolygon::CalcDistance(sal_uInt16 nP1, sal_uInt16 nP2)
{
    const Point& rP1 = m_pImpXPolygon->pPointAry[nP1];
    const Point& rP2 = m_pImpXPolygon->pPointAry[nP2];
    double fDx = rP2.X() - rP1.X();
    double fDy = rP2.Y() - rP1.Y();
    return std::hypot(fDx, fDy);
}
 
void XPolygon::SubdivideBezier(sal_uInt16 nPos, bool bCalcFirst, double fT)
{
    Point*  pPoints = m_pImpXPolygon->pPointAry.get();
    double  fT2 = fT * fT;
    double  fT3 = fT * fT2;
    double  fU = 1.0 - fT;
    double  fU2 = fU * fU;
    double  fU3 = fU * fU2;
    sal_uInt16  nIdx = nPos;
    short   nPosInc, nIdxInc;
 
    if ( bCalcFirst )
    {
        nPos += 3;
        nPosInc = -1;
        nIdxInc = 0;
    }
    else
    {
        nPosInc = 1;
        nIdxInc = 1;
    }
    pPoints[nPos].setX( static_cast<tools::Long>(fU3 *       pPoints[nIdx  ].X() +
                                fT  * fU2 * pPoints[nIdx+1].X() * 3 +
                                fT2 * fU  * pPoints[nIdx+2].X() * 3 +
                                fT3 *       pPoints[nIdx+3].X()) );
    pPoints[nPos].setY( static_cast<tools::Long>(fU3 *       pPoints[nIdx  ].Y() +
                                fT  * fU2 * pPoints[nIdx+1].Y() * 3 +
                                fT2 * fU  * pPoints[nIdx+2].Y() * 3 +
                                fT3 *       pPoints[nIdx+3].Y()) );
    nPos = nPos + nPosInc;
    nIdx = nIdx + nIdxInc;
    pPoints[nPos].setX( static_cast<tools::Long>(fU2 *       pPoints[nIdx  ].X() +
                                fT  * fU *  pPoints[nIdx+1].X() * 2 +
                                fT2 *       pPoints[nIdx+2].X()) );
    pPoints[nPos].setY( static_cast<tools::Long>(fU2 *       pPoints[nIdx  ].Y() +
                                fT  * fU *  pPoints[nIdx+1].Y() * 2 +
                                fT2 *       pPoints[nIdx+2].Y()) );
    nPos = nPos + nPosInc;
    nIdx = nIdx + nIdxInc;
    pPoints[nPos].setX( static_cast<tools::Long>(fU * pPoints[nIdx  ].X() +
                                fT * pPoints[nIdx+1].X()) );
    pPoints[nPos].setY( static_cast<tools::Long>(fU * pPoints[nIdx  ].Y() +
                                fT * pPoints[nIdx+1].Y()) );
}
 
/// Generate a Bézier arc
void XPolygon::GenBezArc(const Point& rCenter, tools::Long nRx, tools::Long nRy,
                         tools::Long nXHdl, tools::Long nYHdl, Degree100 nStart, Degree100 nEnd,
                         sal_uInt16 nQuad, sal_uInt16 nFirst)
{
    Point* pPoints = m_pImpXPolygon->pPointAry.get();
    pPoints[nFirst  ] = rCenter;
    pPoints[nFirst+3] = rCenter;
 
    if ( nQuad == 1 || nQuad == 2 )
    {
        nRx   = -nRx; nXHdl = -nXHdl;
    }
    if ( nQuad == 0 || nQuad == 1 )
    {
        nRy   = -nRy; nYHdl = -nYHdl;
    }
 
    if ( nQuad == 0 || nQuad == 2 )
    {
        pPoints[nFirst].AdjustX( nRx );
        pPoints[nFirst+3].AdjustY( nRy );
    }
    else
    {
        pPoints[nFirst].AdjustY( nRy );
        pPoints[nFirst+3].AdjustX( nRx );
    }
    pPoints[nFirst+1] = pPoints[nFirst];
    pPoints[nFirst+2] = pPoints[nFirst+3];
 
    if ( nQuad == 0 || nQuad == 2 )
    {
        pPoints[nFirst+1].AdjustY( nYHdl );
        pPoints[nFirst+2].AdjustX( nXHdl );
    }
    else
    {
        pPoints[nFirst+1].AdjustX( nXHdl );
        pPoints[nFirst+2].AdjustY( nYHdl );
    }
    if ( nStart > 0_deg100 )
        SubdivideBezier(nFirst, false, static_cast<double>(nStart.get()) / 9000);
    if ( nEnd < 9000_deg100 )
        SubdivideBezier(nFirst, true, static_cast<double>((nEnd-nStart).get()) / (9000_deg100-nStart).get());
    SetFlags(nFirst+1, PolyFlags::Control);
    SetFlags(nFirst+2, PolyFlags::Control);
}
 
bool XPolygon::CheckAngles(Degree100& nStart, Degree100 nEnd, Degree100& nA1, Degree100& nA2)
{
    if ( nStart == 36000_deg100 ) nStart = 0_deg100;
    if ( nEnd == 0_deg100 ) nEnd = 36000_deg100;
    Degree100 nStPrev = nStart;
    Degree100 nMax((nStart.get() / 9000 + 1) * 9000);
    Degree100 nMin = nMax - 9000_deg100;
 
    if ( nEnd >= nMax || nEnd <= nStart )   nA2 = 9000_deg100;
    else                                    nA2 = nEnd - nMin;
    nA1 = nStart - nMin;
    nStart = nMax;
 
    // returns true when the last segment was calculated
    return (nStPrev < nEnd && nStart >= nEnd);
}
 
/** Calculate a smooth transition to connect two Bézier curves
 *
 * This is done by projecting the corresponding point onto a line between
 * two other points.
 *
 * @param nCenter The point at the end or beginning of the curve.
 *                If nCenter is at the end of the polygon the point is moved
 *                to the opposite side.
 * @param nDrag The moved point that specifies the relocation.
 * @param nPnt The point to modify.
 */
void XPolygon::CalcSmoothJoin(sal_uInt16 nCenter, sal_uInt16 nDrag, sal_uInt16 nPnt)
{
    // If nPoint is no control point, i.e. cannot be moved, then
    // move nDrag instead on the line between nCenter and nPnt
    if ( !IsControl(nPnt) )
        std::swap( nDrag, nPnt );
    Point*  pPoints = m_pImpXPolygon->pPointAry.get();
    Point   aDiff   = pPoints[nDrag] - pPoints[nCenter];
    double  fDiv    = CalcDistance(nCenter, nDrag);
 
    if ( fDiv )
    {
        double fRatio = CalcDistance(nCenter, nPnt) / fDiv;
        // keep the length if SMOOTH
        if ( GetFlags(nCenter) == PolyFlags::Smooth || !IsControl(nDrag) )
        {
            aDiff.setX( static_cast<tools::Long>(fRatio * aDiff.X()) );
            aDiff.setY( static_cast<tools::Long>(fRatio * aDiff.Y()) );
        }
        pPoints[nPnt] = pPoints[nCenter] - aDiff;
    }
}
 
/** Calculate tangent between two Bézier curves
 *
 * @param nCenter start or end point of the curves
 * @param nPrev previous reference point
 * @param nNext next reference point
 */
void XPolygon::CalcTangent(sal_uInt16 nCenter, sal_uInt16 nPrev, sal_uInt16 nNext)
{
    double fAbsLen = CalcDistance(nNext, nPrev);
 
    if ( !fAbsLen )
        return;
 
    const Point& rCenter = m_pImpXPolygon->pPointAry[nCenter];
    Point&  rNext = m_pImpXPolygon->pPointAry[nNext];
    Point&  rPrev = m_pImpXPolygon->pPointAry[nPrev];
    Point   aDiff = rNext - rPrev;
    double  fNextLen = CalcDistance(nCenter, nNext) / fAbsLen;
    double  fPrevLen = CalcDistance(nCenter, nPrev) / fAbsLen;
 
    // same length for both sides if SYMMTR
    if ( GetFlags(nCenter) == PolyFlags::Symmetric )
    {
        fPrevLen = (fNextLen + fPrevLen) / 2;
        fNextLen = fPrevLen;
    }
    rNext.setX( rCenter.X() + static_cast<tools::Long>(fNextLen * aDiff.X()) );
    rNext.setY( rCenter.Y() + static_cast<tools::Long>(fNextLen * aDiff.Y()) );
    rPrev.setX( rCenter.X() - static_cast<tools::Long>(fPrevLen * aDiff.X()) );
    rPrev.setY( rCenter.Y() - static_cast<tools::Long>(fPrevLen * aDiff.Y()) );
}
 
/// convert four polygon points into a Bézier curve
void XPolygon::PointsToBezier(sal_uInt16 nFirst)
{
    double  nFullLength, nPart1Length, nPart2Length;
    double  fX0, fY0, fX1, fY1, fX2, fY2, fX3, fY3;
    double  fTx1, fTx2, fTy1, fTy2;
    double  fT1, fU1, fT2, fU2, fV;
    Point*  pPoints = m_pImpXPolygon->pPointAry.get();
 
    if ( nFirst > m_pImpXPolygon->nPoints - 4 || IsControl(nFirst) ||
         IsControl(nFirst+1) || IsControl(nFirst+2) || IsControl(nFirst+3) )
        return;
 
    fTx1 = pPoints[nFirst+1].X();
    fTy1 = pPoints[nFirst+1].Y();
    fTx2 = pPoints[nFirst+2].X();
    fTy2 = pPoints[nFirst+2].Y();
    fX0  = pPoints[nFirst  ].X();
    fY0  = pPoints[nFirst  ].Y();
    fX3  = pPoints[nFirst+3].X();
    fY3  = pPoints[nFirst+3].Y();
 
    nPart1Length = CalcDistance(nFirst, nFirst+1);
    nPart2Length = nPart1Length + CalcDistance(nFirst+1, nFirst+2);
    nFullLength  = nPart2Length + CalcDistance(nFirst+2, nFirst+3);
    if ( nFullLength < 20 )
        return;
 
    if ( nPart2Length == nFullLength )
        nPart2Length -= 1;
    if ( nPart1Length == nFullLength )
        nPart1Length = nPart2Length - 1;
    if ( nPart1Length <= 0 )
        nPart1Length = 1;
    if ( nPart2Length <= 0 || nPart2Length == nPart1Length )
        nPart2Length = nPart1Length + 1;
 
    fT1 = nPart1Length / nFullLength;
    fU1 = 1.0 - fT1;
    fT2 = nPart2Length / nFullLength;
    fU2 = 1.0 - fT2;
    fV = 3 * (1.0 - (fT1 * fU2) / (fT2 * fU1));
 
    fX1 = fTx1 / (fT1 * fU1 * fU1) - fTx2 * fT1 / (fT2 * fT2 * fU1 * fU2);
    fX1 /= fV;
    fX1 -= fX0 * ( fU1 / fT1 + fU2 / fT2) / 3;
    fX1 += fX3 * ( fT1 * fT2 / (fU1 * fU2)) / 3;
 
    fY1 = fTy1 / (fT1 * fU1 * fU1) - fTy2 * fT1 / (fT2 * fT2 * fU1 * fU2);
    fY1 /= fV;
    fY1 -= fY0 * ( fU1 / fT1 + fU2 / fT2) / 3;
    fY1 += fY3 * ( fT1 * fT2 / (fU1 * fU2)) / 3;
 
    fX2 = fTx2 / (fT2 * fT2 * fU2 * 3) - fX0 * fU2 * fU2 / ( fT2 * fT2 * 3);
    fX2 -= fX1 * fU2 / fT2;
    fX2 -= fX3 * fT2 / (fU2 * 3);
 
    fY2 = fTy2 / (fT2 * fT2 * fU2 * 3) - fY0 * fU2 * fU2 / ( fT2 * fT2 * 3);
    fY2 -= fY1 * fU2 / fT2;
    fY2 -= fY3 * fT2 / (fU2 * 3);
 
    pPoints[nFirst+1] = Point(static_cast<tools::Long>(fX1), static_cast<tools::Long>(fY1));
    pPoints[nFirst+2] = Point(static_cast<tools::Long>(fX2), static_cast<tools::Long>(fY2));
    SetFlags(nFirst+1, PolyFlags::Control);
    SetFlags(nFirst+2, PolyFlags::Control);
}
 
/// scale in X- and/or Y-direction
void XPolygon::Scale(double fSx, double fSy)
{
    std::as_const(m_pImpXPolygon)->CheckPointDelete();
 
    sal_uInt16 nPntCnt = m_pImpXPolygon->nPoints;
 
    for (sal_uInt16 i = 0; i < nPntCnt; i++)
    {
        Point& rPnt = m_pImpXPolygon->pPointAry[i];
        rPnt.setX( static_cast<tools::Long>(fSx * rPnt.X()) );
        rPnt.setY( static_cast<tools::Long>(fSy * rPnt.Y()) );
    }
}
 
/**
 * Distort a polygon by scaling its coordinates relative to a reference
 * rectangle into an arbitrary rectangle.
 *
 * Mapping between polygon corners and reference rectangle:
 *     0: top left     0----1
 *     1: top right    |    |
 *     2: bottom right 3----2
 *     3: bottom left
 */
void XPolygon::Distort(const tools::Rectangle& rRefRect,
                       const XPolygon& rDistortedRect)
{
    std::as_const(m_pImpXPolygon)->CheckPointDelete();
 
    tools::Long    Xr, Wr;
    tools::Long    Yr, Hr;
 
    Xr = rRefRect.Left();
    Yr = rRefRect.Top();
    Wr = rRefRect.GetWidth();
    Hr = rRefRect.GetHeight();
 
    if ( !Wr || !Hr )
        return;
 
    tools::Long    X1, X2, X3, X4;
    tools::Long    Y1, Y2, Y3, Y4;
    DBG_ASSERT(rDistortedRect.m_pImpXPolygon->nPoints >= 4,
               "Distort: rectangle too small");
 
    X1 = rDistortedRect[0].X();
    Y1 = rDistortedRect[0].Y();
    X2 = rDistortedRect[1].X();
    Y2 = rDistortedRect[1].Y();
    X3 = rDistortedRect[3].X();
    Y3 = rDistortedRect[3].Y();
    X4 = rDistortedRect[2].X();
    Y4 = rDistortedRect[2].Y();
 
    sal_uInt16 nPntCnt = m_pImpXPolygon->nPoints;
 
    for (sal_uInt16 i = 0; i < nPntCnt; i++)
    {
        double  fTx, fTy, fUx, fUy;
        Point& rPnt = m_pImpXPolygon->pPointAry[i];
 
        fTx = static_cast<double>(rPnt.X() - Xr) / Wr;
        fTy = static_cast<double>(rPnt.Y() - Yr) / Hr;
        fUx = 1.0 - fTx;
        fUy = 1.0 - fTy;
 
        rPnt.setX( static_cast<tools::Long>( fUy * (fUx * X1 + fTx * X2) +
                            fTy * (fUx * X3 + fTx * X4) ) );
        rPnt.setY( static_cast<tools::Long>( fUx * (fUy * Y1 + fTy * Y3) +
                            fTx * (fUy * Y2 + fTy * Y4) ) );
    }
}
 
basegfx::B2DPolygon XPolygon::getB2DPolygon() const
{
    // #i74631# use tools Polygon class for conversion to not have the code doubled
    // here. This needs one more conversion but avoids different converters in
    // the long run
    const tools::Polygon aSource(GetPointCount(), m_pImpXPolygon->pPointAry.get(), m_pImpXPolygon->pFlagAry.get());
 
    return aSource.getB2DPolygon();
}
 
XPolygon::XPolygon(const basegfx::B2DPolygon& rPolygon)
    : m_pImpXPolygon( tools::Polygon( rPolygon ).GetSize() )
{
    // #i74631# use tools Polygon class for conversion to not have the code doubled
    // here. This needs one more conversion but avoids different converters in
    // the long run
 
    const tools::Polygon aSource(rPolygon);
    sal_uInt16 nSize = aSource.GetSize();
    m_pImpXPolygon->nPoints = nSize;
 
    for( sal_uInt16 i = 0; i < nSize;  i++ )
    {
        m_pImpXPolygon->pPointAry[i] = aSource[i];
        m_pImpXPolygon->pFlagAry[i] = aSource.GetFlags( i );
    }
}
 
// XPolyPolygon
XPolyPolygon::XPolyPolygon() = default;
 
XPolyPolygon::XPolyPolygon( const XPolyPolygon& ) = default;
 
XPolyPolygon::XPolyPolygon( XPolyPolygon&& ) = default;
 
XPolyPolygon::XPolyPolygon(const basegfx::B2DPolyPolygon& rPolyPolygon)
{
    for(auto const& rCandidate : rPolyPolygon)
    {
        Insert(XPolygon(rCandidate));
    }
}
 
XPolyPolygon::~XPolyPolygon() = default;
 
void XPolyPolygon::Insert( XPolygon&& rXPoly )
{
    m_pImpXPolyPolygon->aXPolyList.emplace_back( std::move(rXPoly) );
}
 
/// insert all XPolygons of a XPolyPolygon
void XPolyPolygon::Insert( const XPolyPolygon& rXPolyPoly )
{
    for ( size_t i = 0; i < rXPolyPoly.Count(); i++)
    {
        m_pImpXPolyPolygon->aXPolyList.emplace_back( rXPolyPoly[i] );
    }
}
 
void XPolyPolygon::Remove( sal_uInt16 nPos )
{
    m_pImpXPolyPolygon->aXPolyList.erase( m_pImpXPolyPolygon->aXPolyList.begin() + nPos );
}
 
const XPolygon& XPolyPolygon::GetObject( sal_uInt16 nPos ) const
{
    return m_pImpXPolyPolygon->aXPolyList[ nPos ];
}
 
void XPolyPolygon::Clear()
{
    m_pImpXPolyPolygon->aXPolyList.clear();
}
 
sal_uInt16 XPolyPolygon::Count() const
{
    return static_cast<sal_uInt16>(m_pImpXPolyPolygon->aXPolyList.size());
}
 
tools::Rectangle XPolyPolygon::GetBoundRect() const
{
    size_t nXPoly = m_pImpXPolyPolygon->aXPolyList.size();
    tools::Rectangle aRect;
 
    for ( size_t n = 0; n < nXPoly; n++ )
    {
        XPolygon const & rXPoly = m_pImpXPolyPolygon->aXPolyList[ n ];
        aRect.Union( rXPoly.GetBoundRect() );
    }
 
    return aRect;
}
 
XPolygon& XPolyPolygon::operator[]( sal_uInt16 nPos )
{
    return m_pImpXPolyPolygon->aXPolyList[ nPos ];
}
 
XPolyPolygon& XPolyPolygon::operator=( const XPolyPolygon& ) = default;
 
XPolyPolygon& XPolyPolygon::operator=( XPolyPolygon&& ) = default;
 
/**
 * Distort a polygon by scaling its coordinates relative to a reference
 * rectangle into an arbitrary rectangle.
 *
 * Mapping between polygon corners and reference rectangle:
 *     0: top left     0----1
 *     1: top right    |    |
 *     2: bottom right 3----2
 *     3: bottom left
 */
void XPolyPolygon::Distort(const tools::Rectangle& rRefRect,
                           const XPolygon& rDistortedRect)
{
    for (size_t i = 0; i < Count(); i++)
        m_pImpXPolyPolygon->aXPolyList[ i ].Distort(rRefRect, rDistortedRect);
}
 
basegfx::B2DPolyPolygon XPolyPolygon::getB2DPolyPolygon() const
{
    basegfx::B2DPolyPolygon aRetval;
 
    for(sal_uInt16 a(0); a < Count(); a++)
    {
        const XPolygon& rPoly = (*this)[a];
        aRetval.append(rPoly.getB2DPolygon());
    }
 
    return aRetval;
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

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

V575 The 'memcpy' function processes '0' elements. Inspect the third argument.

V575 The 'memcpy' function processes '0' elements. Inspect the third argument.