/* -*- 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 <o3tl/unit_conversion.hxx>
#include <tools/bigint.hxx>
#include <tools/helpers.hxx>
#include <svx/svdopath.hxx>
#include <math.h>
#include <svx/xpoly.hxx>
#include <svx/svdtrans.hxx>
#include <svx/svddrag.hxx>
#include <svx/svdmodel.hxx>
#include <svx/svdhdl.hxx>
#include <svx/svdview.hxx>
#include <svx/dialmgr.hxx>
#include <svx/strings.hrc>

#include <svx/polypolygoneditor.hxx>
#include <sdr/contact/viewcontactofsdrpathobj.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/point/b2dpoint.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/range/b2drange.hxx>
#include <basegfx/curve/b2dcubicbezier.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <sdr/attribute/sdrtextattribute.hxx>
#include <sdr/primitive2d/sdrattributecreator.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <sdr/attribute/sdrformtextattribute.hxx>
#include <utility>
#include <vcl/ptrstyle.hxx>
#include <memory>
#include <sal/log.hxx>
#include <osl/diagnose.h>

using namespace sdr;

static sal_uInt16 GetPrevPnt(sal_uInt16 nPnt, sal_uInt16 nPntMax, bool bClosed)
{
    if (nPnt>0) {
        nPnt--;
    } else {
        nPnt=nPntMax;
        if (bClosed) nPnt--;
    }
    return nPnt;
}

static sal_uInt16 GetNextPnt(sal_uInt16 nPnt, sal_uInt16 nPntMax, bool bClosed)
{
    nPnt++;
    if (nPnt>nPntMax || (bClosed && nPnt>=nPntMax)) nPnt=0;
    return nPnt;
}

namespace {

struct ImpSdrPathDragData  : public SdrDragStatUserData
{
    XPolygon                    aXP;            // section of the original polygon
    bool                        bValid;         // FALSE = too few points
    bool                        bClosed;        // closed object?
    sal_uInt16                  nPoly;          // number of the polygon in the PolyPolygon
    sal_uInt16                  nPnt;           // number of point in the above polygon
    sal_uInt16                  nPointCount;    // number of points of the polygon
    bool                        bBegPnt;        // dragged point is first point of a Polyline
    bool                        bEndPnt;        // dragged point is finishing point of a Polyline
    sal_uInt16                  nPrevPnt;       // index of previous point
    sal_uInt16                  nNextPnt;       // index of next point
    bool                        bPrevIsBegPnt;  // previous point is first point of a Polyline
    bool                        bNextIsEndPnt;  // next point is first point of a Polyline
    sal_uInt16                  nPrevPrevPnt;   // index of point before previous point
    sal_uInt16                  nNextNextPnt;   // index of point after next point
    bool                        bControl;       // point is a control point
    bool                        bIsNextControl; // point is a control point after a support point
    bool                        bPrevIsControl; // if nPnt is a support point: a control point comes before
    bool                        bNextIsControl; // if nPnt is a support point: a control point comes after
    sal_uInt16                  nPrevPrevPnt0;
    sal_uInt16                  nPrevPnt0;
    sal_uInt16                  nPnt0;
    sal_uInt16                  nNextPnt0;
    sal_uInt16                  nNextNextPnt0;
    bool                        bEliminate;     // delete point? (is set by MovDrag)

    bool                        mbMultiPointDrag;
    const XPolyPolygon          maOrig;
    XPolyPolygon                maMove;
    std::vector<SdrHdl*>        maHandles;

public:
    ImpSdrPathDragData(const SdrPathObj& rPO, const SdrHdl& rHdl, bool bMuPoDr, const SdrDragStat& rDrag);
    void ResetPoly(const SdrPathObj& rPO);
    bool IsMultiPointDrag() const { return mbMultiPointDrag; }
};

}

ImpSdrPathDragData::ImpSdrPathDragData(const SdrPathObj& rPO, const SdrHdl& rHdl, bool bMuPoDr, const SdrDragStat& rDrag)
    : aXP(5)
    , bValid(false)
    , bClosed(false)
    , nPoly(0)
    , nPnt(0)
    , nPointCount(0)
    , bBegPnt(false)
    , bEndPnt(false)
    , nPrevPnt(0)
    , nNextPnt(0)
    , bPrevIsBegPnt(false)
    , bNextIsEndPnt(false)
    , nPrevPrevPnt(0)
    , nNextNextPnt(0)
    , bControl(false)
    , bIsNextControl(false)
    , bPrevIsControl(false)
    , bNextIsControl(false)
    , nPrevPrevPnt0(0)
    , nPrevPnt0(0)
    , nPnt0(0)
    , nNextPnt0(0)
    , nNextNextPnt0(0)
    , bEliminate(false)
    , mbMultiPointDrag(bMuPoDr)
    , maOrig(rPO.GetPathPoly())
    , maHandles(0)
{
    if(mbMultiPointDrag)
    {
        const SdrMarkView& rMarkView = *rDrag.GetView();
        const SdrHdlList& rHdlList = rMarkView.GetHdlList();
        const size_t nHdlCount = rHdlList.GetHdlCount();
        const SdrObject* pInteractionObject(nHdlCount && rHdlList.GetHdl(0) ? rHdlList.GetHdl(0)->GetObj() : nullptr);

        for(size_t a = 0; a < nHdlCount; ++a)
        {
            SdrHdl* pTestHdl = rHdlList.GetHdl(a);

            if(pTestHdl && pTestHdl->IsSelected() && pTestHdl->GetObj() == pInteractionObject)
            {
                maHandles.push_back(pTestHdl);
            }
        }

        maMove = maOrig;
        bValid = true;
    }
    else
    {
        sal_uInt16 nPntMax = 0; // maximum index
        bValid=false;
        bClosed=rPO.IsClosed();          // closed object?
        nPoly=static_cast<sal_uInt16>(rHdl.GetPolyNum());            // number of the polygon in the PolyPolygon
        nPnt=static_cast<sal_uInt16>(rHdl.GetPointNum());            // number of points in the above polygon
        const XPolygon aTmpXP(rPO.GetPathPoly().getB2DPolygon(nPoly));
        nPointCount=aTmpXP.GetPointCount();        // number of point of the polygon
        if (nPointCount==0 || (bClosed && nPointCount==1)) return; // minimum of 1 points for Lines, minimum of 2 points for Polygon
        nPntMax=nPointCount-1;                  // maximum index
        bBegPnt=!bClosed && nPnt==0;        // dragged point is first point of a Polyline
        bEndPnt=!bClosed && nPnt==nPntMax;  // dragged point is finishing point of a Polyline
        if (bClosed && nPointCount<=3) {        // if polygon is only a line
            bBegPnt=(nPointCount<3) || nPnt==0;
            bEndPnt=(nPointCount<3) || nPnt==nPntMax-1;
        }
        nPrevPnt=nPnt;                      // index of previous point
        nNextPnt=nPnt;                      // index of next point
        if (!bBegPnt) nPrevPnt=GetPrevPnt(nPnt,nPntMax,bClosed);
        if (!bEndPnt) nNextPnt=GetNextPnt(nPnt,nPntMax,bClosed);
        bPrevIsBegPnt=bBegPnt || (!bClosed && nPrevPnt==0);
        bNextIsEndPnt=bEndPnt || (!bClosed && nNextPnt==nPntMax);
        nPrevPrevPnt=nPnt;                  // index of point before previous point
        nNextNextPnt=nPnt;                  // index of point after next point
        if (!bPrevIsBegPnt) nPrevPrevPnt=GetPrevPnt(nPrevPnt,nPntMax,bClosed);
        if (!bNextIsEndPnt) nNextNextPnt=GetNextPnt(nNextPnt,nPntMax,bClosed);
        bControl=rHdl.IsPlusHdl();          // point is a control point
        bIsNextControl=false;               // point is a control point after a support point
        bPrevIsControl=false;               // if nPnt is a support point: a control point comes before
        bNextIsControl=false;               // if nPnt is a support point: a control point comes after
        if (bControl) {
            bIsNextControl=!aTmpXP.IsControl(nPrevPnt);
        } else {
            bPrevIsControl=!bBegPnt && !bPrevIsBegPnt && aTmpXP.GetFlags(nPrevPnt)==PolyFlags::Control;
            bNextIsControl=!bEndPnt && !bNextIsEndPnt && aTmpXP.GetFlags(nNextPnt)==PolyFlags::Control;
        }
        nPrevPrevPnt0=nPrevPrevPnt;
        nPrevPnt0    =nPrevPnt;
        nPnt0        =nPnt;
        nNextPnt0    =nNextPnt;
        nNextNextPnt0=nNextNextPnt;
        nPrevPrevPnt=0;
        nPrevPnt=1;
        nPnt=2;
        nNextPnt=3;
        nNextNextPnt=4;
        bEliminate=false;
        ResetPoly(rPO);
        bValid=true;
    }
}

void ImpSdrPathDragData::ResetPoly(const SdrPathObj& rPO)
{
    const XPolygon aTmpXP(rPO.GetPathPoly().getB2DPolygon(nPoly));
    aXP[0]=aTmpXP[nPrevPrevPnt0];  aXP.SetFlags(0,aTmpXP.GetFlags(nPrevPrevPnt0));
    aXP[1]=aTmpXP[nPrevPnt0];      aXP.SetFlags(1,aTmpXP.GetFlags(nPrevPnt0));
    aXP[2]=aTmpXP[nPnt0];          aXP.SetFlags(2,aTmpXP.GetFlags(nPnt0));
    aXP[3]=aTmpXP[nNextPnt0];      aXP.SetFlags(3,aTmpXP.GetFlags(nNextPnt0));
    aXP[4]=aTmpXP[nNextNextPnt0];  aXP.SetFlags(4,aTmpXP.GetFlags(nNextNextPnt0));
}

namespace {

struct ImpPathCreateUser  : public SdrDragStatUserData
{
    Point                   aBezControl0;
    Point                   aBezStart;
    Point                   aBezCtrl1;
    Point                   aBezCtrl2;
    Point                   aBezEnd;
    Point                   aCircStart;
    Point                   aCircEnd;
    Point                   aCircCenter;
    Point                   aLineStart;
    Point                   aLineEnd;
    Point                   aRectP1;
    Point                   aRectP2;
    Point                   aRectP3;
    tools::Long                    nCircRadius;
    Degree100               nCircStAngle;
    Degree100               nCircRelAngle;
    bool                    bBezier;
    bool                    bBezHasCtrl0;
    bool                    bCircle;
    bool                    bAngleSnap;
    bool                    bLine;
    bool                    bLine90;
    bool                    bRect;
    bool                    bMixedCreate;
    sal_uInt16                  nBezierStartPoint;
    SdrObjKind              eStartKind;
    SdrObjKind              eCurrentKind;

public:
    ImpPathCreateUser(): nCircRadius(0),nCircStAngle(0),nCircRelAngle(0),
        bBezier(false),bBezHasCtrl0(false),bCircle(false),bAngleSnap(false),bLine(false),bLine90(false),bRect(false),
        bMixedCreate(false),nBezierStartPoint(0),eStartKind(SdrObjKind::NONE),eCurrentKind(SdrObjKind::NONE) { }

    void ResetFormFlags() { bBezier=false; bCircle=false; bLine=false; bRect=false; }
    bool IsFormFlag() const { return bBezier || bCircle || bLine || bRect; }
    XPolygon GetFormPoly() const;
    void CalcBezier(const Point& rP1, const Point& rP2, const Point& rDir, bool bMouseDown);
    XPolygon GetBezierPoly() const;
    void CalcCircle(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView);
    XPolygon GetCirclePoly() const;
    void CalcLine(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView);
    static Point    CalcLine(const Point& rCsr, tools::Long nDirX, tools::Long nDirY, SdrView const * pView);
    XPolygon GetLinePoly() const;
    void CalcRect(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView);
    XPolygon GetRectPoly() const;
};

}

XPolygon ImpPathCreateUser::GetFormPoly() const
{
    if (bBezier) return GetBezierPoly();
    if (bCircle) return GetCirclePoly();
    if (bLine)   return GetLinePoly();
    if (bRect)   return GetRectPoly();
    return XPolygon();
}

void ImpPathCreateUser::CalcBezier(const Point& rP1, const Point& rP2, const Point& rDir, bool bMouseDown)
{
    aBezStart=rP1;
    aBezCtrl1=rP1+rDir;
    aBezCtrl2=rP2;

    // #i21479#
    // Also copy the end point when no end point is set yet
    if (!bMouseDown || (0 == aBezEnd.X() && 0 == aBezEnd.Y())) aBezEnd=rP2;

    bBezier=true;
}

XPolygon ImpPathCreateUser::GetBezierPoly() const
{
    XPolygon aXP(4);
    aXP[0]=aBezStart; aXP.SetFlags(0,PolyFlags::Smooth);
    aXP[1]=aBezCtrl1; aXP.SetFlags(1,PolyFlags::Control);
    aXP[2]=aBezCtrl2; aXP.SetFlags(2,PolyFlags::Control);
    aXP[3]=aBezEnd;
    return aXP;
}

void ImpPathCreateUser::CalcCircle(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView)
{
    Degree100 nTangAngle=GetAngle(rDir);
    aCircStart=rP1;
    aCircEnd=rP2;
    aCircCenter=rP1;
    tools::Long dx=rP2.X()-rP1.X();
    tools::Long dy=rP2.Y()-rP1.Y();
    Degree100 dAngle=GetAngle(Point(dx,dy))-nTangAngle;
    dAngle=NormAngle36000(dAngle);
    Degree100 nTmpAngle=NormAngle36000(9000_deg100-dAngle);
    bool bRet=nTmpAngle!=9000_deg100 && nTmpAngle!=27000_deg100;
    tools::Long nRad=0;
    if (bRet) {
        double cs = cos(toRadians(nTmpAngle));
        double nR=static_cast<double>(GetLen(Point(dx,dy)))/cs/2;
        nRad = std::abs(basegfx::fround<tools::Long>(nR));
    }
    if (dAngle<18000_deg100) {
        nCircStAngle=NormAngle36000(nTangAngle-9000_deg100);
        nCircRelAngle=NormAngle36000(2_deg100*dAngle);
        aCircCenter.AdjustX(basegfx::fround<tools::Long>(nRad * cos(toRadians(nTangAngle + 9000_deg100))));
        aCircCenter.AdjustY(basegfx::fround<tools::Long>(nRad * -sin(toRadians(nTangAngle + 9000_deg100))));
    } else {
        nCircStAngle=NormAngle36000(nTangAngle+9000_deg100);
        nCircRelAngle=-NormAngle36000(36000_deg100-2_deg100*dAngle);
        aCircCenter.AdjustX(basegfx::fround<tools::Long>(nRad * cos(toRadians(nTangAngle - 9000_deg100))));
        aCircCenter.AdjustY(basegfx::fround<tools::Long>(nRad * -sin(toRadians(nTangAngle - 9000_deg100))));
    }
    bAngleSnap=pView!=nullptr && pView->IsAngleSnapEnabled();
    if (bAngleSnap) {
        Degree100 nSA=pView->GetSnapAngle();
        if (nSA) { // angle snapping
            bool bNeg=nCircRelAngle<0_deg100;
            if (bNeg) nCircRelAngle=-nCircRelAngle;
            nCircRelAngle+=nSA/2_deg100;
            nCircRelAngle/=nSA;
            nCircRelAngle*=nSA;
            nCircRelAngle=NormAngle36000(nCircRelAngle);
            if (bNeg) nCircRelAngle=-nCircRelAngle;
        }
    }
    nCircRadius=nRad;
    if (nRad==0 || abs(nCircRelAngle).get()<5) bRet=false;
    bCircle=bRet;
}

XPolygon ImpPathCreateUser::GetCirclePoly() const
{
    if (nCircRelAngle>=0_deg100) {
        XPolygon aXP(aCircCenter,nCircRadius,nCircRadius,
                     nCircStAngle, nCircStAngle+nCircRelAngle,false);
        aXP[0]=aCircStart; aXP.SetFlags(0,PolyFlags::Smooth);
        if (!bAngleSnap) aXP[aXP.GetPointCount()-1]=aCircEnd;
        return aXP;
    } else {
        XPolygon aXP(aCircCenter,nCircRadius,nCircRadius,
                     NormAngle36000(nCircStAngle+nCircRelAngle), nCircStAngle,false);
        sal_uInt16 nCount=aXP.GetPointCount();
        for (sal_uInt16 nNum=nCount/2; nNum>0;) {
            nNum--; // reverse XPoly's order of points
            sal_uInt16 n2=nCount-nNum-1;
            Point aPt(aXP[nNum]);
            aXP[nNum]=aXP[n2];
            aXP[n2]=aPt;
        }
        aXP[0]=aCircStart; aXP.SetFlags(0,PolyFlags::Smooth);
        if (!bAngleSnap) aXP[aXP.GetPointCount()-1]=aCircEnd;
        return aXP;
    }
}

Point ImpPathCreateUser::CalcLine(const Point& aCsr, tools::Long nDirX, tools::Long nDirY, SdrView const * pView)
{
    tools::Long x=aCsr.X();
    tools::Long y=aCsr.Y();
    bool bHLin=nDirY==0;
    bool bVLin=nDirX==0;
    if (bHLin) y=0;
    else if (bVLin) x=0;
    else {
        tools::Long x2=BigMulDiv(y,nDirX,nDirY);
        tools::Long y2=BigMulDiv(x,nDirY,nDirX);
        tools::Long l1=std::abs(x2)+std::abs(y);
        tools::Long l2=std::abs(x)+std::abs(y2);
        if ((l1<=l2) != (pView!=nullptr && pView->IsBigOrtho())) {
            x = x2;
        } else {
            y = y2;
        }
    }
    return Point(x,y);
}

void ImpPathCreateUser::CalcLine(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView)
{
    aLineStart=rP1;
    aLineEnd=rP2;
    bLine90=false;
    if (rP1==rP2 || (rDir.X()==0 && rDir.Y()==0)) { bLine=false; return; }
    Point aTmpPt(rP2-rP1);
    tools::Long nDirX=rDir.X();
    tools::Long nDirY=rDir.Y();
    Point aP1(CalcLine(aTmpPt, nDirX, nDirY,pView)); aP1-=aTmpPt; tools::Long nQ1=std::abs(aP1.X())+std::abs(aP1.Y());
    Point aP2(CalcLine(aTmpPt, nDirY,-nDirX,pView)); aP2-=aTmpPt; tools::Long nQ2=std::abs(aP2.X())+std::abs(aP2.Y());
    if (pView!=nullptr && pView->IsOrtho()) nQ1=0; // Ortho turns off at right angle
    bLine90=nQ1>2*nQ2;
    if (!bLine90) { // smooth transition
        aLineEnd+=aP1;
    } else {          // rectangular transition
        aLineEnd+=aP2;
    }
    bLine=true;
}

XPolygon ImpPathCreateUser::GetLinePoly() const
{
    XPolygon aXP(2);
    aXP[0]=aLineStart; if (!bLine90) aXP.SetFlags(0,PolyFlags::Smooth);
    aXP[1]=aLineEnd;
    return aXP;
}

void ImpPathCreateUser::CalcRect(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView)
{
    aRectP1=rP1;
    aRectP2=rP1;
    aRectP3=rP2;
    if (rP1==rP2 || (rDir.X()==0 && rDir.Y()==0)) { bRect=false; return; }
    Point aTmpPt(rP2-rP1);
    tools::Long nDirX=rDir.X();
    tools::Long nDirY=rDir.Y();
    tools::Long x=aTmpPt.X();
    tools::Long y=aTmpPt.Y();
    bool bHLin=nDirY==0;
    bool bVLin=nDirX==0;
    if (bHLin) y=0;
    else if (bVLin) x=0;
    else {
        y=BigMulDiv(x,nDirY,nDirX);
        tools::Long nHypLen=aTmpPt.Y()-y;
        Degree100 nTangAngle=-GetAngle(rDir);
        // sin=g/h, g=h*sin
        double a = toRadians(nTangAngle);
        double sn=sin(a);
        double cs=cos(a);
        double nGKathLen=nHypLen*sn;
        y += basegfx::fround<tools::Long>(nGKathLen * sn);
        x += basegfx::fround<tools::Long>(nGKathLen * cs);
    }
    aRectP2.AdjustX(x );
    aRectP2.AdjustY(y );
    if (pView!=nullptr && pView->IsOrtho()) {
        tools::Long dx1=aRectP2.X()-aRectP1.X(); tools::Long dx1a=std::abs(dx1);
        tools::Long dy1=aRectP2.Y()-aRectP1.Y(); tools::Long dy1a=std::abs(dy1);
        tools::Long dx2=aRectP3.X()-aRectP2.X(); tools::Long dx2a=std::abs(dx2);
        tools::Long dy2=aRectP3.Y()-aRectP2.Y(); tools::Long dy2a=std::abs(dy2);
        bool b1MoreThan2=dx1a+dy1a>dx2a+dy2a;
        if (b1MoreThan2 != pView->IsBigOrtho()) {
            tools::Long xtemp=dy2a-dx1a; if (dx1<0) xtemp=-xtemp;
            tools::Long ytemp=dx2a-dy1a; if (dy1<0) ytemp=-ytemp;
            aRectP2.AdjustX(xtemp );
            aRectP2.AdjustY(ytemp );
            aRectP3.AdjustX(xtemp );
            aRectP3.AdjustY(ytemp );
        } else {
            tools::Long xtemp=dy1a-dx2a; if (dx2<0) xtemp=-xtemp;
            tools::Long ytemp=dx1a-dy2a; if (dy2<0) ytemp=-ytemp;
            aRectP3.AdjustX(xtemp );
            aRectP3.AdjustY(ytemp );
        }
    }
    bRect=true;
}

XPolygon ImpPathCreateUser::GetRectPoly() const
{
    XPolygon aXP(3);
    aXP[0]=aRectP1; aXP.SetFlags(0,PolyFlags::Smooth);
    aXP[1]=aRectP2;
    if (aRectP3!=aRectP2) aXP[2]=aRectP3;
    return aXP;
}

class ImpPathForDragAndCreate
{
    SdrPathObj&                 mrSdrPathObject;
    XPolyPolygon                aPathPolygon;
    SdrObjKind                  meObjectKind;
    std::unique_ptr<ImpSdrPathDragData>
                                mpSdrPathDragData;
    bool                        mbCreating;

public:
    explicit ImpPathForDragAndCreate(SdrPathObj& rSdrPathObject);

    // drag stuff
    bool beginPathDrag( SdrDragStat const & rDrag )  const;
    bool movePathDrag( SdrDragStat& rDrag ) const;
    bool endPathDrag( SdrDragStat const & rDrag );
    OUString getSpecialDragComment(const SdrDragStat& rDrag) const;
    basegfx::B2DPolyPolygon getSpecialDragPoly(const SdrDragStat& rDrag) const;

    // create stuff
    void BegCreate(SdrDragStat& rStat);
    bool MovCreate(SdrDragStat& rStat);
    bool EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd);
    bool BckCreate(SdrDragStat const & rStat);
    void BrkCreate(SdrDragStat& rStat);
    PointerStyle GetCreatePointer() const;

    // helping stuff
    static bool IsClosed(SdrObjKind eKind) { return eKind==SdrObjKind::Polygon || eKind==SdrObjKind::PathPoly || eKind==SdrObjKind::PathFill || eKind==SdrObjKind::FreehandFill; }
    static bool IsFreeHand(SdrObjKind eKind) { return eKind==SdrObjKind::FreehandLine || eKind==SdrObjKind::FreehandFill; }
    static bool IsBezier(SdrObjKind eKind) { return eKind==SdrObjKind::PathLine || eKind==SdrObjKind::PathFill; }
    bool IsCreating() const { return mbCreating; }

    // get the polygon
    basegfx::B2DPolyPolygon TakeObjectPolyPolygon(const SdrDragStat& rDrag) const;
    static basegfx::B2DPolyPolygon TakeDragPolyPolygon(const SdrDragStat& rDrag);
    basegfx::B2DPolyPolygon getModifiedPolyPolygon() const { return  aPathPolygon.getB2DPolyPolygon(); }
};

ImpPathForDragAndCreate::ImpPathForDragAndCreate(SdrPathObj& rSdrPathObject)
:   mrSdrPathObject(rSdrPathObject),
    aPathPolygon(rSdrPathObject.GetPathPoly()),
    meObjectKind(mrSdrPathObject.meKind),
    mbCreating(false)
{
}

bool ImpPathForDragAndCreate::beginPathDrag( SdrDragStat const & rDrag )  const
{
    const SdrHdl* pHdl=rDrag.GetHdl();
    if(!pHdl)
        return false;

    bool bMultiPointDrag(true);

    if(aPathPolygon[static_cast<sal_uInt16>(pHdl->GetPolyNum())].IsControl(static_cast<sal_uInt16>(pHdl->GetPointNum())))
        bMultiPointDrag = false;

    if(bMultiPointDrag)
    {
        const SdrMarkView& rMarkView = *rDrag.GetView();
        const SdrHdlList& rHdlList = rMarkView.GetHdlList();
        const size_t nHdlCount = rHdlList.GetHdlCount();
        const SdrObject* pInteractionObject(nHdlCount && rHdlList.GetHdl(0) ? rHdlList.GetHdl(0)->GetObj() : nullptr);
        sal_uInt32 nSelectedPoints(0);

        for(size_t a = 0; a < nHdlCount; ++a)
        {
            SdrHdl* pTestHdl = rHdlList.GetHdl(a);

            if(pTestHdl && pTestHdl->IsSelected() && pTestHdl->GetObj() == pInteractionObject)
            {
                nSelectedPoints++;
            }
        }

        if(nSelectedPoints <= 1)
            bMultiPointDrag = false;
    }

    const_cast<ImpPathForDragAndCreate*>(this)->mpSdrPathDragData.reset( new ImpSdrPathDragData(mrSdrPathObject,*pHdl,bMultiPointDrag,rDrag) );

    if(!mpSdrPathDragData || !mpSdrPathDragData->bValid)
    {
        OSL_FAIL("ImpPathForDragAndCreate::BegDrag(): ImpSdrPathDragData is invalid.");
        const_cast<ImpPathForDragAndCreate*>(this)->mpSdrPathDragData.reset();
        return false;
    }

    return true;
}

bool ImpPathForDragAndCreate::movePathDrag( SdrDragStat& rDrag ) const
{
    if(!mpSdrPathDragData || !mpSdrPathDragData->bValid)
    {
        OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid.");
        return false;
    }

    if(mpSdrPathDragData->IsMultiPointDrag())
    {
        Point aDelta(rDrag.GetNow() - rDrag.GetStart());

        if(aDelta.X() || aDelta.Y())
        {
            for(SdrHdl* pHandle : mpSdrPathDragData->maHandles)
            {
                const sal_uInt16 nPolyIndex(static_cast<sal_uInt16>(pHandle->GetPolyNum()));
                const sal_uInt16 nPointIndex(static_cast<sal_uInt16>(pHandle->GetPointNum()));
                const XPolygon& rOrig = mpSdrPathDragData->maOrig[nPolyIndex];
                XPolygon& rMove = mpSdrPathDragData->maMove[nPolyIndex];
                const sal_uInt16 nPointCount(rOrig.GetPointCount());
                bool bClosed(rOrig[0] == rOrig[nPointCount-1]);

                // move point itself
                rMove[nPointIndex] = rOrig[nPointIndex] + aDelta;

                // when point is first and poly closed, move close point, too.
                if(nPointCount > 0 && !nPointIndex && bClosed)
                {
                    rMove[nPointCount - 1] = rOrig[nPointCount - 1] + aDelta;

                    // when moving the last point it may be necessary to move the
                    // control point in front of this one, too.
                    if(nPointCount > 1 && rOrig.IsControl(nPointCount - 2))
                        rMove[nPointCount - 2] = rOrig[nPointCount - 2] + aDelta;
                }

                // is a control point before this?
                if(nPointIndex > 0 && rOrig.IsControl(nPointIndex - 1))
                {
                    // Yes, move it, too
                    rMove[nPointIndex - 1] = rOrig[nPointIndex - 1] + aDelta;
                }

                // is a control point after this?
                if(nPointIndex + 1 < nPointCount && rOrig.IsControl(nPointIndex + 1))
                {
                    // Yes, move it, too
                    rMove[nPointIndex + 1] = rOrig[nPointIndex + 1] + aDelta;
                }
            }
        }
    }
    else
    {
        mpSdrPathDragData->ResetPoly(mrSdrPathObject);

        // copy certain data locally to use less code and have faster access times
        bool bClosed           =mpSdrPathDragData->bClosed       ; // closed object?
        sal_uInt16   nPnt          =mpSdrPathDragData->nPnt          ; // number of point in the above polygon
        bool bBegPnt           =mpSdrPathDragData->bBegPnt       ; // dragged point is first point of a Polyline
        bool bEndPnt           =mpSdrPathDragData->bEndPnt       ; // dragged point is last point of a Polyline
        sal_uInt16   nPrevPnt      =mpSdrPathDragData->nPrevPnt      ; // index of previous point
        sal_uInt16   nNextPnt      =mpSdrPathDragData->nNextPnt      ; // index of next point
        bool bPrevIsBegPnt     =mpSdrPathDragData->bPrevIsBegPnt ; // previous point is first point of a Polyline
        bool bNextIsEndPnt     =mpSdrPathDragData->bNextIsEndPnt ; // next point is last point of a Polyline
        sal_uInt16   nPrevPrevPnt  =mpSdrPathDragData->nPrevPrevPnt  ; // index of the point before the previous point
        sal_uInt16   nNextNextPnt  =mpSdrPathDragData->nNextNextPnt  ; // index if the point after the next point
        bool bControl          =mpSdrPathDragData->bControl      ; // point is a control point
        bool bIsNextControl    =mpSdrPathDragData->bIsNextControl; // point is a control point after a support point
        bool bPrevIsControl    =mpSdrPathDragData->bPrevIsControl; // if nPnt is a support point: there's a control point before
        bool bNextIsControl    =mpSdrPathDragData->bNextIsControl; // if nPnt is a support point: there's a control point after

        // Ortho for lines/polygons: keep angle
        if (!bControl && rDrag.GetView()!=nullptr && rDrag.GetView()->IsOrtho()) {
            bool bBigOrtho=rDrag.GetView()->IsBigOrtho();
            Point  aPos(rDrag.GetNow());      // current position
            Point  aPnt(mpSdrPathDragData->aXP[nPnt]);      // the dragged point
            sal_uInt16 nPnt1=0xFFFF,nPnt2=0xFFFF; // its neighboring points
            Point  aNewPos1,aNewPos2;         // new alternative for aPos
            bool bPnt1 = false, bPnt2 = false; // are these valid alternatives?
            if (!bClosed && mpSdrPathDragData->nPointCount>=2) { // minimum of 2 points for lines
                if (!bBegPnt) nPnt1=nPrevPnt;
                if (!bEndPnt) nPnt2=nNextPnt;
            }
            if (bClosed && mpSdrPathDragData->nPointCount>=3) { // minimum of 3 points for polygon
                nPnt1=nPrevPnt;
                nPnt2=nNextPnt;
            }
            if (nPnt1!=0xFFFF && !bPrevIsControl) {
                Point aPnt1=mpSdrPathDragData->aXP[nPnt1];
                tools::Long ndx0=aPnt.X()-aPnt1.X();
                tools::Long ndy0=aPnt.Y()-aPnt1.Y();
                bool bHLin=ndy0==0;
                bool bVLin=ndx0==0;
                if (!bHLin || !bVLin) {
                    tools::Long ndx=aPos.X()-aPnt1.X();
                    tools::Long ndy=aPos.Y()-aPnt1.Y();
                    bPnt1=true;
                    double nXFact=0; if (!bVLin) nXFact=static_cast<double>(ndx)/static_cast<double>(ndx0);
                    double nYFact=0; if (!bHLin) nYFact=static_cast<double>(ndy)/static_cast<double>(ndy0);
                    bool bHor=bHLin || (!bVLin && (nXFact>nYFact) ==bBigOrtho);
                    bool bVer=bVLin || (!bHLin && (nXFact<=nYFact)==bBigOrtho);
                    if (bHor) ndy=tools::Long(ndy0*nXFact);
                    if (bVer) ndx=tools::Long(ndx0*nYFact);
                    aNewPos1=aPnt1;
                    aNewPos1.AdjustX(ndx );
                    aNewPos1.AdjustY(ndy );
                }
            }
            if (nPnt2!=0xFFFF && !bNextIsControl) {
                Point aPnt2=mpSdrPathDragData->aXP[nPnt2];
                tools::Long ndx0=aPnt.X()-aPnt2.X();
                tools::Long ndy0=aPnt.Y()-aPnt2.Y();
                bool bHLin=ndy0==0;
                bool bVLin=ndx0==0;
                if (!bHLin || !bVLin) {
                    tools::Long ndx=aPos.X()-aPnt2.X();
                    tools::Long ndy=aPos.Y()-aPnt2.Y();
                    bPnt2=true;
                    double nXFact=0; if (!bVLin) nXFact=static_cast<double>(ndx)/static_cast<double>(ndx0);
                    double nYFact=0; if (!bHLin) nYFact=static_cast<double>(ndy)/static_cast<double>(ndy0);
                    bool bHor=bHLin || (!bVLin && (nXFact>nYFact) ==bBigOrtho);
                    bool bVer=bVLin || (!bHLin && (nXFact<=nYFact)==bBigOrtho);
                    if (bHor) ndy=tools::Long(ndy0*nXFact);
                    if (bVer) ndx=tools::Long(ndx0*nYFact);
                    aNewPos2=aPnt2;
                    aNewPos2.AdjustX(ndx );
                    aNewPos2.AdjustY(ndy );
                }
            }
            if (bPnt1 && bPnt2) { // both alternatives exist (and compete)
                BigInt nX1(aNewPos1.X()-aPos.X()); nX1*=nX1;
                BigInt nY1(aNewPos1.Y()-aPos.Y()); nY1*=nY1;
                BigInt nX2(aNewPos2.X()-aPos.X()); nX2*=nX2;
                BigInt nY2(aNewPos2.Y()-aPos.Y()); nY2*=nY2;
                nX1+=nY1; // correction distance to square
                nX2+=nY2; // correction distance to square
                // let the alternative that allows fewer correction win
                if (nX1<nX2) bPnt2=false; else bPnt1=false;
            }
            if (bPnt1) rDrag.SetNow(aNewPos1);
            if (bPnt2) rDrag.SetNow(aNewPos2);
        }
        rDrag.SetActionRect(tools::Rectangle(rDrag.GetNow(),rDrag.GetNow()));

        // specially for IBM: Eliminate points if both adjoining lines form near 180 degrees angle anyway
        if (!bControl && rDrag.GetView()!=nullptr && rDrag.GetView()->IsEliminatePolyPoints() &&
            !bBegPnt && !bEndPnt && !bPrevIsControl && !bNextIsControl)
        {
            Point aPt(mpSdrPathDragData->aXP[nNextPnt]);
            aPt-=rDrag.GetNow();
            Degree100 nAngle1=GetAngle(aPt);
            aPt=rDrag.GetNow();
            aPt-=mpSdrPathDragData->aXP[nPrevPnt];
            Degree100 nAngle2=GetAngle(aPt);
            Degree100 nDiff=nAngle1-nAngle2;
            nDiff=abs(nDiff);
            mpSdrPathDragData->bEliminate=nDiff<=rDrag.GetView()->GetEliminatePolyPointLimitAngle();
            if (mpSdrPathDragData->bEliminate) { // adapt position, Smooth is true for the ends
                aPt=mpSdrPathDragData->aXP[nNextPnt];
                aPt+=mpSdrPathDragData->aXP[nPrevPnt];
                aPt/=2;
                rDrag.SetNow(aPt);
            }
        }

        // we dragged by this distance
        Point aDiff(rDrag.GetNow()); aDiff-=mpSdrPathDragData->aXP[nPnt];

        /* There are 8 possible cases:
              X      1. A control point neither on the left nor on the right.
           o--X--o   2. There are control points on the left and the right, we are dragging a support point.
           o--X      3. There is a control point on the left, we are dragging a support point.
              X--o   4. There is a control point on the right, we are dragging a support point.
           x--O--o   5. There are control points on the left and the right, we are dragging the left one.
           x--O      6. There is a control point on the left, we are dragging it.
           o--O--x   7. There are control points on the left and the right, we are dragging the right one.
              O--x   8. There is a control point on the right, we are dragging it.
           Note: modifying a line (not a curve!) might create a curve on the other end of the line
           if Smooth is set there (with control points aligned to line).
        */

        mpSdrPathDragData->aXP[nPnt]+=aDiff;

        // now check symmetric plus handles
        if (bControl) { // cases 5,6,7,8
            sal_uInt16   nSt; // the associated support point
            sal_uInt16   nFix;  // the opposing control point
            if (bIsNextControl) { // if the next one is a control point, the on before has to be a support point
                nSt=nPrevPnt;
                nFix=nPrevPrevPnt;
            } else {
                nSt=nNextPnt;
                nFix=nNextNextPnt;
            }
            if (mpSdrPathDragData->aXP.IsSmooth(nSt)) {
                mpSdrPathDragData->aXP.CalcSmoothJoin(nSt,nPnt,nFix);
            }
        }

        if (!bControl) { // Cases 1,2,3,4. In case 1, nothing happens; in cases 3 and 4, there is more following below.
            // move both control points
            if (bPrevIsControl) mpSdrPathDragData->aXP[nPrevPnt]+=aDiff;
            if (bNextIsControl) mpSdrPathDragData->aXP[nNextPnt]+=aDiff;
            // align control point to line, if appropriate
            if (mpSdrPathDragData->aXP.IsSmooth(nPnt)) {
                if (bPrevIsControl && !bNextIsControl && !bEndPnt) { // case 3
                    mpSdrPathDragData->aXP.CalcSmoothJoin(nPnt,nNextPnt,nPrevPnt);
                }
                if (bNextIsControl && !bPrevIsControl && !bBegPnt) { // case 4
                    mpSdrPathDragData->aXP.CalcSmoothJoin(nPnt,nPrevPnt,nNextPnt);
                }
            }
            // Now check the other ends of the line (nPnt+-1). If there is a
            // curve (IsControl(nPnt+-2)) with SmoothJoin (nPnt+-1), the
            // associated control point (nPnt+-2) has to be adapted.
            if (!bBegPnt && !bPrevIsControl && !bPrevIsBegPnt && mpSdrPathDragData->aXP.IsSmooth(nPrevPnt)) {
                if (mpSdrPathDragData->aXP.IsControl(nPrevPrevPnt)) {
                    mpSdrPathDragData->aXP.CalcSmoothJoin(nPrevPnt,nPnt,nPrevPrevPnt);
                }
            }
            if (!bEndPnt && !bNextIsControl && !bNextIsEndPnt && mpSdrPathDragData->aXP.IsSmooth(nNextPnt)) {
                if (mpSdrPathDragData->aXP.IsControl(nNextNextPnt)) {
                    mpSdrPathDragData->aXP.CalcSmoothJoin(nNextPnt,nPnt,nNextNextPnt);
                }
            }
        }
    }

    return true;
}

bool ImpPathForDragAndCreate::endPathDrag(SdrDragStat const & rDrag)
{
    Point aLinePt1;
    Point aLinePt2;
    bool bLineGlueMirror(SdrObjKind::Line == meObjectKind);
    if (bLineGlueMirror) {
        XPolygon& rXP=aPathPolygon[0];
        aLinePt1=rXP[0];
        aLinePt2=rXP[1];
    }

    if(!mpSdrPathDragData || !mpSdrPathDragData->bValid)
    {
        OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid.");
        return false;
    }

    if(mpSdrPathDragData->IsMultiPointDrag())
    {
        aPathPolygon = mpSdrPathDragData->maMove;
    }
    else
    {
        const SdrHdl* pHdl=rDrag.GetHdl();

        // reference the polygon
        XPolygon& rXP=aPathPolygon[static_cast<sal_uInt16>(pHdl->GetPolyNum())];

        // the 5 points that might have changed
        if (!mpSdrPathDragData->bPrevIsBegPnt) rXP[mpSdrPathDragData->nPrevPrevPnt0]=mpSdrPathDragData->aXP[mpSdrPathDragData->nPrevPrevPnt];
        if (!mpSdrPathDragData->bNextIsEndPnt) rXP[mpSdrPathDragData->nNextNextPnt0]=mpSdrPathDragData->aXP[mpSdrPathDragData->nNextNextPnt];
        if (!mpSdrPathDragData->bBegPnt)       rXP[mpSdrPathDragData->nPrevPnt0]    =mpSdrPathDragData->aXP[mpSdrPathDragData->nPrevPnt];
        if (!mpSdrPathDragData->bEndPnt)       rXP[mpSdrPathDragData->nNextPnt0]    =mpSdrPathDragData->aXP[mpSdrPathDragData->nNextPnt];
        rXP[mpSdrPathDragData->nPnt0]        =mpSdrPathDragData->aXP[mpSdrPathDragData->nPnt];

        // for closed objects: last point has to be equal to first point
        if (mpSdrPathDragData->bClosed) rXP[rXP.GetPointCount()-1]=rXP[0];

        if (mpSdrPathDragData->bEliminate)
        {
            basegfx::B2DPolyPolygon aTempPolyPolygon(aPathPolygon.getB2DPolyPolygon());
            sal_uInt32 nPoly,nPnt;

            if(PolyPolygonEditor::GetRelativePolyPoint(aTempPolyPolygon, rDrag.GetHdl()->GetSourceHdlNum(), nPoly, nPnt))
            {
                basegfx::B2DPolygon aCandidate(aTempPolyPolygon.getB2DPolygon(nPoly));
                aCandidate.remove(nPnt);

                if(aCandidate.count() < 2)
                {
                    aTempPolyPolygon.remove(nPoly);
                }
                else
                {
                    aTempPolyPolygon.setB2DPolygon(nPoly, aCandidate);
                }
            }

            aPathPolygon = XPolyPolygon(aTempPolyPolygon);
        }

        // adapt angle for text beneath a simple line
        if (bLineGlueMirror)
        {
            Point aLinePt1_(aPathPolygon[0][0]);
            Point aLinePt2_(aPathPolygon[0][1]);
            bool bXMirr=(aLinePt1_.X()>aLinePt2_.X())!=(aLinePt1.X()>aLinePt2.X());
            bool bYMirr=(aLinePt1_.Y()>aLinePt2_.Y())!=(aLinePt1.Y()>aLinePt2.Y());
            if (bXMirr || bYMirr) {
                Point aRef1(mrSdrPathObject.GetSnapRect().Center());
                if (bXMirr) {
                    Point aRef2(aRef1);
                    aRef2.AdjustY( 1 );
                    mrSdrPathObject.NbcMirrorGluePoints(aRef1,aRef2);
                }
                if (bYMirr) {
                    Point aRef2(aRef1);
                    aRef2.AdjustX( 1 );
                    mrSdrPathObject.NbcMirrorGluePoints(aRef1,aRef2);
                }
            }
        }
    }

    mpSdrPathDragData.reset();

    return true;
}

OUString ImpPathForDragAndCreate::getSpecialDragComment(const SdrDragStat& rDrag) const
{
    OUString aStr;
    const SdrHdl* pHdl = rDrag.GetHdl();
    const bool bCreateComment(rDrag.GetView() && &mrSdrPathObject == rDrag.GetView()->GetCreateObj());

    if(bCreateComment && rDrag.GetUser())
    {
        // #i103058# re-add old creation comment mode
        const ImpPathCreateUser* pU = static_cast<const ImpPathCreateUser*>(rDrag.GetUser());
        const SdrObjKind eOriginalKind(meObjectKind);
        mrSdrPathObject.meKind = pU->eCurrentKind;
        aStr = mrSdrPathObject.ImpGetDescriptionStr(STR_ViewCreateObj);
        mrSdrPathObject.meKind = eOriginalKind;

        Point aPrev(rDrag.GetPrev());
        Point aNow(rDrag.GetNow());

        if(pU->bLine)
            aNow = pU->aLineEnd;

        aNow -= aPrev;
        aStr += " (";

        if(pU->bCircle)
        {
            aStr += SdrModel::GetAngleString(abs(pU->nCircRelAngle))
                    + " r="
                    + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(pU->nCircRadius, true);
        }

        aStr += "dx="
                + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.X(), true)
                + " dy="
                + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.Y(), true);

        if(!IsFreeHand(meObjectKind))
        {
            sal_Int32 nLen(GetLen(aNow));
            Degree100 nAngle(GetAngle(aNow));
            aStr += "  l="
                    + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true)
                    + " "
                    + SdrModel::GetAngleString(nAngle);
        }

        aStr += ")";
    }
    else if(!pHdl)
    {
        // #i103058# fallback when no model and/or Handle, both needed
        // for else-path
        aStr = mrSdrPathObject.ImpGetDescriptionStr(STR_DragPathObj);
    }
    else
    {
        // #i103058# standard for modification; model and handle needed
        ImpSdrPathDragData* pDragData = mpSdrPathDragData.get();

        if(!pDragData)
        {
            // getSpecialDragComment is also used from create, so fallback to GetUser()
            // when mpSdrPathDragData is not set
            pDragData = static_cast<ImpSdrPathDragData*>(rDrag.GetUser());
        }

        if(!pDragData)
        {
            OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid.");
            return OUString();
        }

        if(!pDragData->IsMultiPointDrag() && pDragData->bEliminate)
        {
            // point of ...
            aStr = mrSdrPathObject.ImpGetDescriptionStr(STR_ViewMarkedPoint);

            // delete %O
            OUString aStr2(SvxResId(STR_EditDelete));

            // UNICODE: delete point of ...
            aStr2 = aStr2.replaceFirst("%1", aStr);

            return aStr2;
        }

        // dx=0.00 dy=0.00                -- both sides bezier
        // dx=0.00 dy=0.00  l=0.00 0.00\302\260  -- one bezier/lever on one side, a start, or an ending
        // dx=0.00 dy=0.00  l=0.00 0.00\302\260 / l=0.00 0.00\302\260 -- in between
        Point aBeg(rDrag.GetStart());
        Point aNow(rDrag.GetNow());

        aStr.clear();
        aStr += "dx="
                + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.X() - aBeg.X(), true)
                + " dy="
                + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.Y() - aBeg.Y(), true);

        if(!pDragData->IsMultiPointDrag())
        {
            sal_uInt16 nPntNum(static_cast<sal_uInt16>(pHdl->GetPointNum()));
            const XPolygon& rXPoly = aPathPolygon[static_cast<sal_uInt16>(rDrag.GetHdl()->GetPolyNum())];
            sal_uInt16 nPointCount(rXPoly.GetPointCount());
            bool bClose(IsClosed(meObjectKind));

            if(bClose)
                nPointCount--;

            if(pHdl->IsPlusHdl())
            {
                // lever
                sal_uInt16 nRef(nPntNum);

                if(rXPoly.IsControl(nPntNum + 1))
                    nRef--;
                else
                    nRef++;

                aNow -= rXPoly[nRef];

                sal_Int32 nLen(GetLen(aNow));
                Degree100 nAngle(GetAngle(aNow));
                aStr += "  l="
                        + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true)
                        + " "
                        + SdrModel::GetAngleString(nAngle);
            }
            else if(nPointCount > 1)
            {
                sal_uInt16 nPntMax(nPointCount - 1);
                bool bIsClosed(IsClosed(meObjectKind));
                bool bPt1(nPntNum > 0);
                bool bPt2(nPntNum < nPntMax);

                if(bIsClosed && nPointCount > 2)
                {
                    bPt1 = true;
                    bPt2 = true;
                }

                sal_uInt16 nPt1,nPt2;

                if(nPntNum > 0)
                    nPt1 = nPntNum - 1;
                else
                    nPt1 = nPntMax;

                if(nPntNum < nPntMax)
                    nPt2 = nPntNum + 1;
                else
                    nPt2 = 0;

                if(bPt1 && rXPoly.IsControl(nPt1))
                    bPt1 = false; // don't display

                if(bPt2 && rXPoly.IsControl(nPt2))
                    bPt2 = false; // of bezier data

                if(bPt1)
                {
                    Point aPt(aNow);
                    aPt -= rXPoly[nPt1];

                    sal_Int32 nLen(GetLen(aPt));
                    Degree100 nAngle(GetAngle(aPt));
                    aStr += "  l="
                            + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true)
                            + " "
                            + SdrModel::GetAngleString(nAngle);
                }

                if(bPt2)
                {
                    if(bPt1)
                        aStr += " / ";
                    else
                        aStr += "  ";

                    Point aPt(aNow);
                    aPt -= rXPoly[nPt2];

                    sal_Int32 nLen(GetLen(aPt));
                    Degree100 nAngle(GetAngle(aPt));
                    aStr += "l="
                            + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true)
                            + " "
                            + SdrModel::GetAngleString(nAngle);
                }
            }
        }
    }

    return aStr;
}

basegfx::B2DPolyPolygon ImpPathForDragAndCreate::getSpecialDragPoly(const SdrDragStat& rDrag) const
{
    if(!mpSdrPathDragData || !mpSdrPathDragData->bValid)
    {
        OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid.");
        return basegfx::B2DPolyPolygon();
    }

    XPolyPolygon aRetval;

    if(mpSdrPathDragData->IsMultiPointDrag())
    {
        aRetval.Insert(mpSdrPathDragData->maMove);
    }
    else
    {
        const XPolygon& rXP=aPathPolygon[static_cast<sal_uInt16>(rDrag.GetHdl()->GetPolyNum())];
        if (rXP.GetPointCount()<=2) {
            XPolygon aXPoly(rXP);
            aXPoly[static_cast<sal_uInt16>(rDrag.GetHdl()->GetPointNum())]=rDrag.GetNow();
            aRetval.Insert(std::move(aXPoly));
            return aRetval.getB2DPolyPolygon();
        }
        // copy certain data locally to use less code and have faster access times
        bool bClosed           =mpSdrPathDragData->bClosed       ; // closed object?
        sal_uInt16 nPointCount = mpSdrPathDragData->nPointCount; // number of points
        sal_uInt16   nPnt          =mpSdrPathDragData->nPnt          ; // number of points in the polygon
        bool bBegPnt           =mpSdrPathDragData->bBegPnt       ; // dragged point is the first point of a Polyline
        bool bEndPnt           =mpSdrPathDragData->bEndPnt       ; // dragged point is the last point of a Polyline
        sal_uInt16   nPrevPnt      =mpSdrPathDragData->nPrevPnt      ; // index of the previous point
        sal_uInt16   nNextPnt      =mpSdrPathDragData->nNextPnt      ; // index of the next point
        bool bPrevIsBegPnt     =mpSdrPathDragData->bPrevIsBegPnt ; // previous point is first point of a Polyline
        bool bNextIsEndPnt     =mpSdrPathDragData->bNextIsEndPnt ; // next point is last point of a Polyline
        sal_uInt16   nPrevPrevPnt  =mpSdrPathDragData->nPrevPrevPnt  ; // index of the point before the previous point
        sal_uInt16   nNextNextPnt  =mpSdrPathDragData->nNextNextPnt  ; // index of the point after the last point
        bool bControl          =mpSdrPathDragData->bControl      ; // point is a control point
        bool bIsNextControl    =mpSdrPathDragData->bIsNextControl; //point is a control point after a support point
        bool bPrevIsControl    =mpSdrPathDragData->bPrevIsControl; // if nPnt is a support point: there's a control point before
        bool bNextIsControl    =mpSdrPathDragData->bNextIsControl; // if nPnt is a support point: there's a control point after
        XPolygon aXPoly(mpSdrPathDragData->aXP);
        XPolygon aLine1(2);
        XPolygon aLine2(2);
        XPolygon aLine3(2);
        XPolygon aLine4(2);
        if (bControl) {
            aLine1[1]=mpSdrPathDragData->aXP[nPnt];
            if (bIsNextControl) { // is this a control point after the support point?
                aLine1[0]=mpSdrPathDragData->aXP[nPrevPnt];
                aLine2[0]=mpSdrPathDragData->aXP[nNextNextPnt];
                aLine2[1]=mpSdrPathDragData->aXP[nNextPnt];
                if (mpSdrPathDragData->aXP.IsSmooth(nPrevPnt) && !bPrevIsBegPnt && mpSdrPathDragData->aXP.IsControl(nPrevPrevPnt)) {
                    aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-1],PolyFlags::Control);
                    aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-2],PolyFlags::Normal);
                    // leverage lines for the opposing curve segment
                    aLine3[0]=mpSdrPathDragData->aXP[nPrevPnt];
                    aLine3[1]=mpSdrPathDragData->aXP[nPrevPrevPnt];
                    aLine4[0]=rXP[mpSdrPathDragData->nPrevPrevPnt0-2];
                    aLine4[1]=rXP[mpSdrPathDragData->nPrevPrevPnt0-1];
                } else {
                    aXPoly.Remove(0,1);
                }
            } else { // else this is a control point before a support point
                aLine1[0]=mpSdrPathDragData->aXP[nNextPnt];
                aLine2[0]=mpSdrPathDragData->aXP[nPrevPrevPnt];
                aLine2[1]=mpSdrPathDragData->aXP[nPrevPnt];
                if (mpSdrPathDragData->aXP.IsSmooth(nNextPnt) && !bNextIsEndPnt && mpSdrPathDragData->aXP.IsControl(nNextNextPnt)) {
                    aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+1],PolyFlags::Control);
                    aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+2],PolyFlags::Normal);
                    // leverage lines for the opposing curve segment
                    aLine3[0]=mpSdrPathDragData->aXP[nNextPnt];
                    aLine3[1]=mpSdrPathDragData->aXP[nNextNextPnt];
                    aLine4[0]=rXP[mpSdrPathDragData->nNextNextPnt0+2];
                    aLine4[1]=rXP[mpSdrPathDragData->nNextNextPnt0+1];
                } else {
                    aXPoly.Remove(aXPoly.GetPointCount()-1,1);
                }
            }
        } else { // else is not a control point
            if (mpSdrPathDragData->bEliminate) {
                aXPoly.Remove(2,1);
            }
            if (bPrevIsControl) aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-1],PolyFlags::Normal);
            else if (!bBegPnt && !bPrevIsBegPnt && mpSdrPathDragData->aXP.IsControl(nPrevPrevPnt)) {
                aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-1],PolyFlags::Control);
                aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-2],PolyFlags::Normal);
            } else {
                aXPoly.Remove(0,1);
                if (bBegPnt) aXPoly.Remove(0,1);
            }
            if (bNextIsControl) aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+1],PolyFlags::Normal);
            else if (!bEndPnt && !bNextIsEndPnt && mpSdrPathDragData->aXP.IsControl(nNextNextPnt)) {
                aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+1],PolyFlags::Control);
                aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+2],PolyFlags::Normal);
            } else {
                aXPoly.Remove(aXPoly.GetPointCount()-1,1);
                if (bEndPnt) aXPoly.Remove(aXPoly.GetPointCount()-1,1);
            }
            if (bClosed) { // "pear problem": 2 lines, 1 curve, everything smoothed, a point between both lines is dragged
                if (aXPoly.GetPointCount()>nPointCount && aXPoly.IsControl(1)) {
                    sal_uInt16 a=aXPoly.GetPointCount();
                    aXPoly[a-2]=aXPoly[2]; aXPoly.SetFlags(a-2,aXPoly.GetFlags(2));
                    aXPoly[a-1]=aXPoly[3]; aXPoly.SetFlags(a-1,aXPoly.GetFlags(3));
                    aXPoly.Remove(0,3);
                }
            }
        }
        aRetval.Insert(std::move(aXPoly));
        if (aLine1.GetPointCount()>1) aRetval.Insert(std::move(aLine1));
        if (aLine2.GetPointCount()>1) aRetval.Insert(std::move(aLine2));
        if (aLine3.GetPointCount()>1) aRetval.Insert(std::move(aLine3));
        if (aLine4.GetPointCount()>1) aRetval.Insert(std::move(aLine4));
    }

    return aRetval.getB2DPolyPolygon();
}

void ImpPathForDragAndCreate::BegCreate(SdrDragStat& rStat)
{
    bool bFreeHand(IsFreeHand(meObjectKind));
    rStat.SetNoSnap(bFreeHand);
    rStat.SetOrtho8Possible();
    aPathPolygon.Clear();
    mbCreating=true;
    bool bMakeStartPoint = true;
    SdrView* pView=rStat.GetView();
    if (pView!=nullptr && pView->IsUseIncompatiblePathCreateInterface() &&
        (meObjectKind==SdrObjKind::Polygon || meObjectKind==SdrObjKind::PolyLine || meObjectKind==SdrObjKind::PathLine || meObjectKind==SdrObjKind::PathFill)) {
        bMakeStartPoint = false;
    }
    aPathPolygon.Insert(XPolygon());
    aPathPolygon[0][0]=rStat.GetStart();
    if (bMakeStartPoint) {
        aPathPolygon[0][1]=rStat.GetNow();
    }
    std::unique_ptr<ImpPathCreateUser> pU(new ImpPathCreateUser);
    pU->eStartKind=meObjectKind;
    pU->eCurrentKind=meObjectKind;
    rStat.SetUser(std::move(pU));
}

bool ImpPathForDragAndCreate::MovCreate(SdrDragStat& rStat)
{
    ImpPathCreateUser* pU=static_cast<ImpPathCreateUser*>(rStat.GetUser());
    SdrView* pView=rStat.GetView();
    XPolygon& rXPoly=aPathPolygon[aPathPolygon.Count()-1];
    if (pView!=nullptr && pView->IsCreateMode()) {
        // switch to different CreateTool, if appropriate
        SdrObjKind nIdent;
        SdrInventor nInvent;
        pView->TakeCurrentObj(nIdent,nInvent);
        if (nInvent==SdrInventor::Default && pU->eCurrentKind != nIdent) {
            SdrObjKind eNewKind = nIdent;
            switch (eNewKind) {
                case SdrObjKind::CircleArc:
                case SdrObjKind::CircleOrEllipse:
                case SdrObjKind::CircleCut:
                case SdrObjKind::CircleSection:
                    eNewKind=SdrObjKind::CircleArc;
                    [[fallthrough]];
                case SdrObjKind::Rectangle:
                case SdrObjKind::Line:
                case SdrObjKind::PolyLine:
                case SdrObjKind::Polygon:
                case SdrObjKind::PathLine:
                case SdrObjKind::PathFill:
                case SdrObjKind::FreehandLine:
                case SdrObjKind::FreehandFill:
                {
                    pU->eCurrentKind=eNewKind;
                    pU->bMixedCreate=true;
                    pU->nBezierStartPoint=rXPoly.GetPointCount();
                    if (pU->nBezierStartPoint>0) pU->nBezierStartPoint--;
                } break;
                default: break;
            } // switch
        }
    }
    sal_uInt16 nCurrentPoint=rXPoly.GetPointCount();
    if (aPathPolygon.Count()>1 && rStat.IsMouseDown() && nCurrentPoint<2) {
        rXPoly[0]=rStat.GetPos0();
        rXPoly[1]=rStat.GetNow();
        nCurrentPoint=2;
    }
    if (nCurrentPoint==0) {
        rXPoly[0]=rStat.GetPos0();
    } else nCurrentPoint--;
    bool bFreeHand=IsFreeHand(pU->eCurrentKind);
    rStat.SetNoSnap(bFreeHand);
    rStat.SetOrtho8Possible(pU->eCurrentKind!=SdrObjKind::CircleArc && pU->eCurrentKind!=SdrObjKind::Rectangle && (!pU->bMixedCreate || pU->eCurrentKind!=SdrObjKind::Line));
    rXPoly[nCurrentPoint]=rStat.GetNow();
    if (!pU->bMixedCreate && pU->eStartKind==SdrObjKind::Line && rXPoly.GetPointCount()>=1) {
        Point aPt(rStat.GetStart());
        if (pView!=nullptr && pView->IsCreate1stPointAsCenter()) {
            aPt+=aPt;
            aPt-=rStat.GetNow();
        }
        rXPoly[0]=aPt;
    }
    OutputDevice* pOut=pView==nullptr ? nullptr : pView->GetFirstOutputDevice();
    if (bFreeHand) {
        if (pU->nBezierStartPoint>nCurrentPoint) pU->nBezierStartPoint=nCurrentPoint;
        if (rStat.IsMouseDown() && nCurrentPoint>0) {
            // don't allow two consecutive points to occupy too similar positions
            tools::Long nMinDist=1;
            if (pView!=nullptr) nMinDist=pView->GetFreeHandMinDistPix();
            if (pOut!=nullptr) nMinDist=pOut->PixelToLogic(Size(nMinDist,0)).Width();
            if (nMinDist<1) nMinDist=1;

            Point aPt0(rXPoly[nCurrentPoint-1]);
            Point aPt1(rStat.GetNow());
            tools::Long dx=aPt0.X()-aPt1.X(); if (dx<0) dx=-dx;
            tools::Long dy=aPt0.Y()-aPt1.Y(); if (dy<0) dy=-dy;
            if (dx<nMinDist && dy<nMinDist) return false;

            // TODO: the following is copied from EndCreate (with a few smaller modifications)
            // and should be combined into a method with the code there.

            if (nCurrentPoint-pU->nBezierStartPoint>=3 && ((nCurrentPoint-pU->nBezierStartPoint)%3)==0) {
                rXPoly.PointsToBezier(nCurrentPoint-3);
                rXPoly.SetFlags(nCurrentPoint-1,PolyFlags::Control);
                rXPoly.SetFlags(nCurrentPoint-2,PolyFlags::Control);

                if (nCurrentPoint>=6 && rXPoly.IsControl(nCurrentPoint-4)) {
                    rXPoly.CalcTangent(nCurrentPoint-3,nCurrentPoint-4,nCurrentPoint-2);
                    rXPoly.SetFlags(nCurrentPoint-3,PolyFlags::Smooth);
                }
            }
            rXPoly[nCurrentPoint+1]=rStat.GetNow();
            rStat.NextPoint();
        } else {
            pU->nBezierStartPoint=nCurrentPoint;
        }
    }

    pU->ResetFormFlags();
    if (IsBezier(pU->eCurrentKind)) {
        if (nCurrentPoint>=2) {
            pU->CalcBezier(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],rStat.IsMouseDown());
        } else if (pU->bBezHasCtrl0) {
            pU->CalcBezier(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],pU->aBezControl0-rXPoly[nCurrentPoint-1],rStat.IsMouseDown());
        }
    }
    if (pU->eCurrentKind==SdrObjKind::CircleArc && nCurrentPoint>=2) {
        pU->CalcCircle(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],pView);
    }
    if (pU->eCurrentKind==SdrObjKind::Line && nCurrentPoint>=2) {
        pU->CalcLine(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],pView);
    }
    if (pU->eCurrentKind==SdrObjKind::Rectangle && nCurrentPoint>=2) {
        pU->CalcRect(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],pView);
    }

    return true;
}

bool ImpPathForDragAndCreate::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd)
{
    ImpPathCreateUser* pU=static_cast<ImpPathCreateUser*>(rStat.GetUser());
    bool bRet = false;
    SdrView* pView=rStat.GetView();
    bool bIncomp=pView!=nullptr && pView->IsUseIncompatiblePathCreateInterface();
    XPolygon& rXPoly=aPathPolygon[aPathPolygon.Count()-1];
    sal_uInt16 nCurrentPoint=rXPoly.GetPointCount()-1;
    rXPoly[nCurrentPoint]=rStat.GetNow();
    if (!pU->bMixedCreate && pU->eStartKind==SdrObjKind::Line) {
        if (rStat.GetPointCount()>=2) eCmd=SdrCreateCmd::ForceEnd;
        bRet = eCmd==SdrCreateCmd::ForceEnd;
        if (bRet) {
            mbCreating = false;
            rStat.SetUser(nullptr);
        }
        return bRet;
    }

    if (!pU->bMixedCreate && IsFreeHand(pU->eStartKind)) {
        if (rStat.GetPointCount()>=2) eCmd=SdrCreateCmd::ForceEnd;
        bRet=eCmd==SdrCreateCmd::ForceEnd;
        if (bRet) {
            mbCreating=false;
            rStat.SetUser(nullptr);
        }
        return bRet;
    }
    if (eCmd==SdrCreateCmd::NextPoint || eCmd==SdrCreateCmd::NextObject) {
        // don't allow two consecutive points to occupy the same position
        if (nCurrentPoint==0 || rStat.GetNow()!=rXPoly[nCurrentPoint-1]) {
            if (bIncomp) {
                if (pU->nBezierStartPoint>nCurrentPoint) pU->nBezierStartPoint=nCurrentPoint;
                if (IsBezier(pU->eCurrentKind) && nCurrentPoint-pU->nBezierStartPoint>=3 && ((nCurrentPoint-pU->nBezierStartPoint)%3)==0) {
                    rXPoly.PointsToBezier(nCurrentPoint-3);
                    rXPoly.SetFlags(nCurrentPoint-1,PolyFlags::Control);
                    rXPoly.SetFlags(nCurrentPoint-2,PolyFlags::Control);

                    if (nCurrentPoint>=6 && rXPoly.IsControl(nCurrentPoint-4)) {
                        rXPoly.CalcTangent(nCurrentPoint-3,nCurrentPoint-4,nCurrentPoint-2);
                        rXPoly.SetFlags(nCurrentPoint-3,PolyFlags::Smooth);
                    }
                }
            } else {
                if (nCurrentPoint==1 && IsBezier(pU->eCurrentKind) && !pU->bBezHasCtrl0) {
                    pU->aBezControl0=rStat.GetNow();
                    pU->bBezHasCtrl0=true;
                    nCurrentPoint--;
                }
                if (pU->IsFormFlag()) {
                    sal_uInt16 nPointCount0=rXPoly.GetPointCount();
                    rXPoly.Remove(nCurrentPoint-1,2); // remove last two points and replace by form
                    rXPoly.Insert(XPOLY_APPEND,pU->GetFormPoly());
                    sal_uInt16 nPointCount1=rXPoly.GetPointCount();
                    for (sal_uInt16 i=nPointCount0+1; i<nPointCount1-1; i++) { // to make BckAction work
                        if (!rXPoly.IsControl(i)) rStat.NextPoint();
                    }
                    nCurrentPoint=rXPoly.GetPointCount()-1;
                }
            }
            nCurrentPoint++;
            rXPoly[nCurrentPoint]=rStat.GetNow();
        }
        if (eCmd==SdrCreateCmd::NextObject) {
            if (rXPoly.GetPointCount()>=2) {
                pU->bBezHasCtrl0=false;
                // only a singular polygon may be opened, so close this
                rXPoly[nCurrentPoint]=rXPoly[0];
                XPolygon aXP;
                aXP[0]=rStat.GetNow();
                aPathPolygon.Insert(std::move(aXP));
            }
        }
    }

    sal_uInt16 nPolyCount=aPathPolygon.Count();
    if (nPolyCount!=0) {
        // delete last point, if necessary
        if (eCmd==SdrCreateCmd::ForceEnd) {
            XPolygon& rXP=aPathPolygon[nPolyCount-1];
            sal_uInt16 nPointCount=rXP.GetPointCount();
            if (nPointCount>=2) {
                if (!rXP.IsControl(nPointCount-2)) {
                    if (rXP[nPointCount-1]==rXP[nPointCount-2]) {
                        rXP.Remove(nPointCount-1,1);
                    }
                } else {
                    if (rXP[nPointCount-3]==rXP[nPointCount-2]) {
                        rXP.Remove(nPointCount-3,3);
                    }
                }
            }
        }
        for (sal_uInt16 nPolyNum=nPolyCount; nPolyNum>0;) {
            nPolyNum--;
            XPolygon& rXP=aPathPolygon[nPolyNum];
            sal_uInt16 nPointCount=rXP.GetPointCount();
            // delete polygons with too few points
            if (nPolyNum<nPolyCount-1 || eCmd==SdrCreateCmd::ForceEnd) {
                if (nPointCount<2) aPathPolygon.Remove(nPolyNum);
            }
        }
    }
    pU->ResetFormFlags();
    bRet=eCmd==SdrCreateCmd::ForceEnd;
    if (bRet) {
        mbCreating=false;
        rStat.SetUser(nullptr);
    }
    return bRet;
}

bool ImpPathForDragAndCreate::BckCreate(SdrDragStat const & rStat)
{
    ImpPathCreateUser* pU=static_cast<ImpPathCreateUser*>(rStat.GetUser());
    if (aPathPolygon.Count()>0) {
        XPolygon& rXPoly=aPathPolygon[aPathPolygon.Count()-1];
        sal_uInt16 nCurrentPoint=rXPoly.GetPointCount();
        if (nCurrentPoint>0) {
            nCurrentPoint--;
            // make the last part of a bezier curve a line
            rXPoly.Remove(nCurrentPoint,1);
            if (nCurrentPoint>=3 && rXPoly.IsControl(nCurrentPoint-1)) {
                // there should never be a bezier segment at the end, so this is just in case...
                rXPoly.Remove(nCurrentPoint-1,1);
                if (rXPoly.IsControl(nCurrentPoint-2)) rXPoly.Remove(nCurrentPoint-2,1);
            }
        }
        nCurrentPoint=rXPoly.GetPointCount();
        if (nCurrentPoint>=4) { // no bezier segment at the end
            nCurrentPoint--;
            if (rXPoly.IsControl(nCurrentPoint-1)) {
                rXPoly.Remove(nCurrentPoint-1,1);
                if (rXPoly.IsControl(nCurrentPoint-2)) rXPoly.Remove(nCurrentPoint-2,1);
            }
        }
        if (rXPoly.GetPointCount()<2) {
            aPathPolygon.Remove(aPathPolygon.Count()-1);
        }
        if (aPathPolygon.Count()>0) {
            XPolygon& rLocalXPoly=aPathPolygon[aPathPolygon.Count()-1];
            sal_uInt16 nLocalCurrentPoint=rLocalXPoly.GetPointCount();
            if (nLocalCurrentPoint>0) {
                nLocalCurrentPoint--;
                rLocalXPoly[nLocalCurrentPoint]=rStat.GetNow();
            }
        }
    }
    pU->ResetFormFlags();
    return aPathPolygon.Count()!=0;
}

void ImpPathForDragAndCreate::BrkCreate(SdrDragStat& rStat)
{
    aPathPolygon.Clear();
    mbCreating=false;
    rStat.SetUser(nullptr);
}

basegfx::B2DPolyPolygon ImpPathForDragAndCreate::TakeObjectPolyPolygon(const SdrDragStat& rDrag) const
{
    basegfx::B2DPolyPolygon aRetval(aPathPolygon.getB2DPolyPolygon());
    SdrView* pView = rDrag.GetView();

    if(pView && pView->IsUseIncompatiblePathCreateInterface())
        return aRetval;

    ImpPathCreateUser* pU = static_cast<ImpPathCreateUser*>(rDrag.GetUser());
    basegfx::B2DPolygon aNewPolygon(aRetval.count() ? aRetval.getB2DPolygon(aRetval.count() - 1) : basegfx::B2DPolygon());

    if(pU->IsFormFlag() && aNewPolygon.count() > 1)
    {
        // remove last segment and replace with current
        // do not forget to rescue the previous control point which will be lost when
        // the point it's associated with is removed
        const sal_uInt32 nChangeIndex(aNewPolygon.count() - 2);
        const basegfx::B2DPoint aSavedPrevCtrlPoint(aNewPolygon.getPrevControlPoint(nChangeIndex));

        aNewPolygon.remove(nChangeIndex, 2);
        aNewPolygon.append(pU->GetFormPoly().getB2DPolygon());

        if(nChangeIndex < aNewPolygon.count())
        {
            // if really something was added, set the saved previous control point to the
            // point where it belongs
            aNewPolygon.setPrevControlPoint(nChangeIndex, aSavedPrevCtrlPoint);
        }
    }

    if(aRetval.count())
    {
        aRetval.setB2DPolygon(aRetval.count() - 1, aNewPolygon);
    }
    else
    {
        aRetval.append(aNewPolygon);
    }

    return aRetval;
}

basegfx::B2DPolyPolygon ImpPathForDragAndCreate::TakeDragPolyPolygon(const SdrDragStat& rDrag)
{
    basegfx::B2DPolyPolygon aRetval;
    SdrView* pView = rDrag.GetView();

    if(pView && pView->IsUseIncompatiblePathCreateInterface())
        return aRetval;

    const ImpPathCreateUser* pU = static_cast<const ImpPathCreateUser*>(rDrag.GetUser());

    if(pU && pU->bBezier && rDrag.IsMouseDown())
    {
        // no more XOR, no need for complicated helplines
        basegfx::B2DPolygon aHelpline;
        aHelpline.append(basegfx::B2DPoint(pU->aBezCtrl2.X(), pU->aBezCtrl2.Y()));
        aHelpline.append(basegfx::B2DPoint(pU->aBezEnd.X(), pU->aBezEnd.Y()));
        aRetval.append(aHelpline);
    }

    return aRetval;
}

PointerStyle ImpPathForDragAndCreate::GetCreatePointer() const
{
    switch (meObjectKind) {
        case SdrObjKind::Line    : return PointerStyle::DrawLine;
        case SdrObjKind::Polygon    : return PointerStyle::DrawPolygon;
        case SdrObjKind::PolyLine    : return PointerStyle::DrawPolygon;
        case SdrObjKind::PathLine: return PointerStyle::DrawBezier;
        case SdrObjKind::PathFill: return PointerStyle::DrawBezier;
        case SdrObjKind::FreehandLine: return PointerStyle::DrawFreehand;
        case SdrObjKind::FreehandFill: return PointerStyle::DrawFreehand;
        case SdrObjKind::PathPoly: return PointerStyle::DrawPolygon;
        case SdrObjKind::PathPolyLine: return PointerStyle::DrawPolygon;
        default: break;
    } // switch
    return PointerStyle::Cross;
}

SdrPathObjGeoData::SdrPathObjGeoData()
    : meKind(SdrObjKind::NONE)
{
}

SdrPathObjGeoData::~SdrPathObjGeoData()
{
}

// DrawContact section

std::unique_ptr<sdr::contact::ViewContact> SdrPathObj::CreateObjectSpecificViewContact()
{
    return std::make_unique<sdr::contact::ViewContactOfSdrPathObj>(*this);
}


SdrPathObj::SdrPathObj(
    SdrModel& rSdrModel,
    SdrObjKind eNewKind)
:   SdrTextObj(rSdrModel),
    meKind(eNewKind)
{
    m_bClosedObj = IsClosed();
}

SdrPathObj::SdrPathObj(SdrModel& rSdrModel, SdrPathObj const & rSource)
:   SdrTextObj(rSdrModel, rSource),
    meKind(rSource.meKind)
{
    m_bClosedObj = IsClosed();
    maPathPolygon = rSource.GetPathPoly();
}

SdrPathObj::SdrPathObj(
    SdrModel& rSdrModel,
    SdrObjKind eNewKind,
    basegfx::B2DPolyPolygon aPathPoly)
:   SdrTextObj(rSdrModel),
    maPathPolygon(std::move(aPathPoly)),
    meKind(eNewKind)
{
    m_bClosedObj = IsClosed();
    ImpForceKind();
}

SdrPathObj::~SdrPathObj() = default;

static bool lcl_ImpIsLine(const basegfx::B2DPolyPolygon& rPolyPolygon)
{
    return (1 == rPolyPolygon.count() && 2 == rPolyPolygon.getB2DPolygon(0).count());
}

static tools::Rectangle lcl_ImpGetBoundRect(const basegfx::B2DPolyPolygon& rPolyPolygon)
{
    basegfx::B2DRange aRange(rPolyPolygon.getB2DRange());

    if (aRange.isEmpty())
        return tools::Rectangle();

    return tools::Rectangle(
        basegfx::fround<tools::Long>(aRange.getMinX()), basegfx::fround<tools::Long>(aRange.getMinY()),
        basegfx::fround<tools::Long>(aRange.getMaxX()), basegfx::fround<tools::Long>(aRange.getMaxY()));
}

void SdrPathObj::ImpForceLineAngle()
{
    if(SdrObjKind::Line != meKind || !lcl_ImpIsLine(GetPathPoly()))
        return;

    const basegfx::B2DPolygon aPoly(GetPathPoly().getB2DPolygon(0));
    const basegfx::B2DPoint aB2DPoint0(aPoly.getB2DPoint(0));
    const basegfx::B2DPoint aB2DPoint1(aPoly.getB2DPoint(1));
    const Point aPoint0(basegfx::fround<tools::Long>(aB2DPoint0.getX()),
                        basegfx::fround<tools::Long>(aB2DPoint0.getY()));
    const Point aPoint1(basegfx::fround<tools::Long>(aB2DPoint1.getX()),
                        basegfx::fround<tools::Long>(aB2DPoint1.getY()));
    const basegfx::B2DPoint aB2DDelt(aB2DPoint1 - aB2DPoint0);
    const Point aDelt(basegfx::fround<tools::Long>(aB2DDelt.getX()),
                      basegfx::fround<tools::Long>(aB2DDelt.getY()));

    maGeo.m_nRotationAngle=GetAngle(aDelt);
    maGeo.m_nShearAngle=0_deg100;
    maGeo.RecalcSinCos();
    maGeo.RecalcTan();

    // for SdrTextObj, keep aRect up to date
    setRectangle(tools::Rectangle::Normalize(aPoint0, aPoint1));
}

void SdrPathObj::ImpForceKind()
{
    if (meKind==SdrObjKind::PathPolyLine) meKind=SdrObjKind::PolyLine;
    if (meKind==SdrObjKind::PathPoly) meKind=SdrObjKind::Polygon;

    if(GetPathPoly().areControlPointsUsed())
    {
        switch (meKind)
        {
            case SdrObjKind::Line: meKind=SdrObjKind::PathLine; break;
            case SdrObjKind::PolyLine: meKind=SdrObjKind::PathLine; break;
            case SdrObjKind::Polygon: meKind=SdrObjKind::PathFill; break;
            default: break;
        }
    }
    else
    {
        switch (meKind)
        {
            case SdrObjKind::PathLine: meKind=SdrObjKind::PolyLine; break;
            case SdrObjKind::FreehandLine: meKind=SdrObjKind::PolyLine; break;
            case SdrObjKind::PathFill: meKind=SdrObjKind::Polygon; break;
            case SdrObjKind::FreehandFill: meKind=SdrObjKind::Polygon; break;
            default: break;
        }
    }

    if (meKind==SdrObjKind::Line && !lcl_ImpIsLine(GetPathPoly())) meKind=SdrObjKind::PolyLine;
    if (meKind==SdrObjKind::PolyLine && lcl_ImpIsLine(GetPathPoly())) meKind=SdrObjKind::Line;

    m_bClosedObj=IsClosed();

    if (meKind==SdrObjKind::Line)
    {
        ImpForceLineAngle();
    }
    else
    {
        // #i10659#, for polys with more than 2 points.

        // Here i again need to fix something, because when Path-Polys are Copy-Pasted
        // between Apps with different measurements (e.g. 100TH_MM and TWIPS) there is
        // a scaling loop started from SdrExchangeView::Paste. In itself, this is not
        // wrong, but aRect is wrong here and not even updated by RecalcSnapRect(). If
        // this is the case, some size needs to be set here in aRect to avoid that the cycle
        // through Rect2Poly - Poly2Rect does something badly wrong since that cycle is
        // BASED on aRect. That cycle is triggered in SdrTextObj::NbcResize() which is called
        // from the local Resize() implementation.

        // Basic problem is that the member aRect in SdrTextObj basically is a unrotated
        // text rectangle for the text object itself and methods at SdrTextObj do handle it
        // in that way. Many draw objects derived from SdrTextObj 'abuse' aRect as SnapRect
        // which is basically wrong. To make the SdrText methods which deal with aRect directly
        // work it is necessary to always keep aRect updated. This e.g. not done after a Clone()
        // command for SdrPathObj. Since adding this update mechanism with #101412# to
        // ImpForceLineAngle() for lines was very successful, i add it to where ImpForceLineAngle()
        // was called, once here below and once on a 2nd place below.

        // #i10659# for SdrTextObj, keep aRect up to date
        if(GetPathPoly().count())
        {
            setRectangle(lcl_ImpGetBoundRect(GetPathPoly()));
        }
    }

    // #i75974# adapt polygon state to object type. This may include a reinterpretation
    // of a closed geometry as open one, but with identical first and last point
    for(auto& rPolygon : maPathPolygon)
    {
        if(IsClosed() != rPolygon.isClosed())
        {
            // #i80213# really change polygon geometry; else e.g. the last point which
            // needs to be identical with the first one will be missing when opening
            // due to OBJ_PATH type
            if(rPolygon.isClosed())
            {
                basegfx::utils::openWithGeometryChange(rPolygon);
            }
            else
            {
                basegfx::utils::closeWithGeometryChange(rPolygon);
            }
        }
    }
}

void SdrPathObj::ImpSetClosed(bool bClose)
{
    if(bClose)
    {
        switch (meKind)
        {
            case SdrObjKind::Line    : meKind=SdrObjKind::Polygon;     break;
            case SdrObjKind::PolyLine    : meKind=SdrObjKind::Polygon;     break;
            case SdrObjKind::PathLine: meKind=SdrObjKind::PathFill; break;
            case SdrObjKind::FreehandLine: meKind=SdrObjKind::FreehandFill; break;
            default: break;
        }

        m_bClosedObj = true;
    }
    else
    {
        switch (meKind)
        {
            case SdrObjKind::Polygon    : meKind=SdrObjKind::PolyLine;     break;
            case SdrObjKind::PathFill: meKind=SdrObjKind::PathLine; break;
            case SdrObjKind::FreehandFill: meKind=SdrObjKind::FreehandLine; break;
            default: break;
        }

        m_bClosedObj = false;
    }

    ImpForceKind();
}

void SdrPathObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const
{
    rInfo.bNoContortion=false;

    bool bCanConv = !HasText() || ImpCanConvTextToCurve();
    bool bIsPath = IsBezier();

    rInfo.bEdgeRadiusAllowed    = false;
    rInfo.bCanConvToPath = bCanConv && !bIsPath;
    rInfo.bCanConvToPoly = bCanConv && bIsPath;
    rInfo.bCanConvToContour = !IsFontwork() && (rInfo.bCanConvToPoly || LineGeometryUsageIsNecessary());
}

SdrObjKind SdrPathObj::GetObjIdentifier() const
{
    return meKind;
}

rtl::Reference<SdrObject> SdrPathObj::CloneSdrObject(SdrModel& rTargetModel) const
{
    return new SdrPathObj(rTargetModel, *this);
}

OUString SdrPathObj::TakeObjNameSingul() const
{
    OUString sName;

    if(SdrObjKind::Line == meKind)
    {
        TranslateId pId(STR_ObjNameSingulLINE);

        if(lcl_ImpIsLine(GetPathPoly()))
        {
            const basegfx::B2DPolygon aPoly(GetPathPoly().getB2DPolygon(0));
            const basegfx::B2DPoint aB2DPoint0(aPoly.getB2DPoint(0));
            const basegfx::B2DPoint aB2DPoint1(aPoly.getB2DPoint(1));

            if(aB2DPoint0 != aB2DPoint1)
            {
                if(aB2DPoint0.getY() == aB2DPoint1.getY())
                {
                    pId = STR_ObjNameSingulLINE_Hori;
                }
                else if(aB2DPoint0.getX() == aB2DPoint1.getX())
                {
                    pId = STR_ObjNameSingulLINE_Vert;
                }
                else
                {
                    const double fDx(fabs(aB2DPoint0.getX() - aB2DPoint1.getX()));
                    const double fDy(fabs(aB2DPoint0.getY() - aB2DPoint1.getY()));

                    if(fDx == fDy)
                    {
                        pId = STR_ObjNameSingulLINE_Diag;
                    }
                }
            }
        }

        sName = SvxResId(pId);
    }
    else if(SdrObjKind::PolyLine == meKind || SdrObjKind::Polygon == meKind)
    {
        const bool bClosed(SdrObjKind::Polygon == meKind);
        TranslateId pId;

        if(mpDAC && mpDAC->IsCreating())
        {
            if(bClosed)
            {
                pId = STR_ObjNameSingulPOLY;
            }
            else
            {
                pId = STR_ObjNameSingulPLIN;
            }

            sName = SvxResId(pId);
        }
        else
        {
            // get point count
            sal_uInt32 nPointCount(0);

            for(auto const& rPolygon : GetPathPoly())
            {
                nPointCount += rPolygon.count();
            }

            if(bClosed)
            {
                pId = STR_ObjNameSingulPOLY_PointCount;
            }
            else
            {
                pId = STR_ObjNameSingulPLIN_PointCount;
            }

            // #i96537#
            sName = SvxResId(pId).replaceFirst("%2", OUString::number(nPointCount));
        }
    }
    else
    {
        switch (meKind)
        {
            case SdrObjKind::PathLine: sName = SvxResId(STR_ObjNameSingulPATHLINE); break;
            case SdrObjKind::FreehandLine: sName = SvxResId(STR_ObjNameSingulFREELINE); break;
            case SdrObjKind::PathFill: sName = SvxResId(STR_ObjNameSingulPATHFILL); break;
            case SdrObjKind::FreehandFill: sName = SvxResId(STR_ObjNameSingulFREEFILL); break;
            default: break;
        }
    }

    OUString aName(GetName());
    if (!aName.isEmpty())
        sName += " '" + aName + "'";

    return sName;
}

OUString SdrPathObj::TakeObjNamePlural() const
{
    OUString sName;
    switch(meKind)
    {
        case SdrObjKind::Line    : sName=SvxResId(STR_ObjNamePluralLINE    ); break;
        case SdrObjKind::PolyLine    : sName=SvxResId(STR_ObjNamePluralPLIN    ); break;
        case SdrObjKind::Polygon    : sName=SvxResId(STR_ObjNamePluralPOLY    ); break;
        case SdrObjKind::PathLine: sName=SvxResId(STR_ObjNamePluralPATHLINE); break;
        case SdrObjKind::FreehandLine: sName=SvxResId(STR_ObjNamePluralFREELINE); break;
        case SdrObjKind::PathFill: sName=SvxResId(STR_ObjNamePluralPATHFILL); break;
        case SdrObjKind::FreehandFill: sName=SvxResId(STR_ObjNamePluralFREEFILL); break;
        default: break;
    }
    return sName;
}

basegfx::B2DPolyPolygon SdrPathObj::TakeXorPoly() const
{
    return GetPathPoly();
}

sal_uInt32 SdrPathObj::GetHdlCount() const
{
    sal_uInt32 nRetval(0);

    for(auto const& rPolygon : GetPathPoly())
    {
        nRetval += rPolygon.count();
    }

    return nRetval;
}

void SdrPathObj::AddToHdlList(SdrHdlList& rHdlList) const
{
    // keep old stuff to be able to keep old SdrHdl stuff, too
    const XPolyPolygon aOldPathPolygon(GetPathPoly());
    sal_uInt16 nPolyCnt=aOldPathPolygon.Count();
    bool bClosed=IsClosed();
    sal_uInt16 nIdx=0;

    for (sal_uInt16 i=0; i<nPolyCnt; i++) {
        const XPolygon& rXPoly=aOldPathPolygon.GetObject(i);
        sal_uInt16 nPntCnt=rXPoly.GetPointCount();
        if (bClosed && nPntCnt>1) nPntCnt--;

        for (sal_uInt16 j=0; j<nPntCnt; j++) {
            if (rXPoly.GetFlags(j)!=PolyFlags::Control) {
                const Point& rPnt=rXPoly[j];
                std::unique_ptr<SdrHdl> pHdl(new SdrHdl(rPnt,SdrHdlKind::Poly));
                pHdl->SetPolyNum(i);
                pHdl->SetPointNum(j);
                pHdl->Set1PixMore(j==0);
                pHdl->SetSourceHdlNum(nIdx);
                nIdx++;
                rHdlList.AddHdl(std::move(pHdl));
            }
        }
    }
}

void SdrPathObj::AddToPlusHdlList(SdrHdlList& rHdlList, SdrHdl& rHdl) const
{
    // keep old stuff to be able to keep old SdrHdl stuff, too
    const XPolyPolygon aOldPathPolygon(GetPathPoly());
    sal_uInt16 nPnt = static_cast<sal_uInt16>(rHdl.GetPointNum());
    sal_uInt16 nPolyNum = static_cast<sal_uInt16>(rHdl.GetPolyNum());

    if (nPolyNum>=aOldPathPolygon.Count())
        return;

    const XPolygon& rXPoly = aOldPathPolygon[nPolyNum];
    sal_uInt16 nPntMax = rXPoly.GetPointCount();

    if (nPntMax<=0)
        return;
    nPntMax--;
    if (nPnt>nPntMax)
        return;

    // calculate the number of plus points
    sal_uInt16 nCnt = 0;
    if (rXPoly.GetFlags(nPnt)!=PolyFlags::Control)
    {
        if (nPnt==0 && IsClosed())
            nPnt=nPntMax;
        if (nPnt>0 && rXPoly.GetFlags(nPnt-1)==PolyFlags::Control)
            nCnt++;
        if (nPnt==nPntMax && IsClosed())
            nPnt=0;
        if (nPnt<nPntMax && rXPoly.GetFlags(nPnt+1)==PolyFlags::Control)
            nCnt++;
    }

    // construct the plus points
    for (sal_uInt32 nPlusNum = 0; nPlusNum < nCnt; ++nPlusNum)
    {
        nPnt = static_cast<sal_uInt16>(rHdl.GetPointNum());
        std::unique_ptr<SdrHdl> pHdl(new SdrHdlBezWgt(&rHdl));
        pHdl->SetPolyNum(rHdl.GetPolyNum());

        if (nPnt==0 && IsClosed())
            nPnt=nPntMax;
        if (nPnt>0 && rXPoly.GetFlags(nPnt-1)==PolyFlags::Control && nPlusNum==0)
        {
            pHdl->SetPos(rXPoly[nPnt-1]);
            pHdl->SetPointNum(nPnt-1);
        }
        else
        {
            if (nPnt==nPntMax && IsClosed())
                nPnt=0;
            if (nPnt<rXPoly.GetPointCount()-1 && rXPoly.GetFlags(nPnt+1)==PolyFlags::Control)
            {
                pHdl->SetPos(rXPoly[nPnt+1]);
                pHdl->SetPointNum(nPnt+1);
            }
        }

        pHdl->SetSourceHdlNum(rHdl.GetSourceHdlNum());
        pHdl->SetPlusHdl(true);
        rHdlList.AddHdl(std::move(pHdl));
    }
}

// tdf#123321: Make sure that SdrPathObj (e.g. line) has big enough extent for
// visibility. This is realised by ensuring GetLogicRect() is the same as
// GetSnapRect() for the SdrPathObj. Other SdrTextObj objects like
// SdrObjCustomShape will still use a different version of this method that
// does not consider the rotation. Otherwise, the rotated SdrObjCustomShape
// would become mistakenly larger after save and reload (tdf#91687).
// The invocation of the GetLogicRect() method that caused tdf#123321 was in
// PlcDrawObj::WritePlc().
const tools::Rectangle &SdrPathObj::GetLogicRect() const
{
    return GetSnapRect();
}

// dragging

bool SdrPathObj::hasSpecialDrag() const
{
    return true;
}

bool SdrPathObj::beginSpecialDrag(SdrDragStat& rDrag) const
{
    ImpPathForDragAndCreate aDragAndCreate(*const_cast<SdrPathObj*>(this));

    return aDragAndCreate.beginPathDrag(rDrag);
}

bool SdrPathObj::applySpecialDrag(SdrDragStat& rDrag)
{
    ImpPathForDragAndCreate aDragAndCreate(*this);
    bool bRetval(aDragAndCreate.beginPathDrag(rDrag));

    if(bRetval)
    {
        bRetval = aDragAndCreate.movePathDrag(rDrag);
    }

    if(bRetval)
    {
        bRetval = aDragAndCreate.endPathDrag(rDrag);
    }

    if(bRetval)
    {
           NbcSetPathPoly(aDragAndCreate.getModifiedPolyPolygon());
    }

    return bRetval;
}

OUString SdrPathObj::getSpecialDragComment(const SdrDragStat& rDrag) const
{
    OUString aRetval;

    if(mpDAC)
    {
        // #i103058# also get a comment when in creation
        const bool bCreateComment(rDrag.GetView() && this == rDrag.GetView()->GetCreateObj());

        if(bCreateComment)
        {
            aRetval = mpDAC->getSpecialDragComment(rDrag);
        }
    }
    else
    {
        ImpPathForDragAndCreate aDragAndCreate(*const_cast<SdrPathObj*>(this));
        bool bDidWork(aDragAndCreate.beginPathDrag(rDrag));

        if(bDidWork)
        {
            aRetval = aDragAndCreate.getSpecialDragComment(rDrag);
        }
    }

    return aRetval;
}

basegfx::B2DPolyPolygon SdrPathObj::getSpecialDragPoly(const SdrDragStat& rDrag) const
{
    basegfx::B2DPolyPolygon aRetval;
    ImpPathForDragAndCreate aDragAndCreate(*const_cast<SdrPathObj*>(this));
    bool bDidWork(aDragAndCreate.beginPathDrag(rDrag));

    if(bDidWork)
    {
        aRetval = aDragAndCreate.getSpecialDragPoly(rDrag);
    }

    return aRetval;
}

// creation

bool SdrPathObj::BegCreate(SdrDragStat& rStat)
{
    mpDAC.reset();
    impGetDAC().BegCreate(rStat);
    return true;
}

bool SdrPathObj::MovCreate(SdrDragStat& rStat)
{
    return impGetDAC().MovCreate(rStat);
}

bool SdrPathObj::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd)
{
    bool bRetval(impGetDAC().EndCreate(rStat, eCmd));

    if(bRetval && mpDAC)
    {
        SetPathPoly(mpDAC->getModifiedPolyPolygon());

        // #i75974# Check for AutoClose feature. Moved here from ImpPathForDragAndCreate::EndCreate
        // to be able to use the type-changing ImpSetClosed method
        if(!IsClosedObj())
        {
            SdrView* pView = rStat.GetView();

            if(pView && !pView->IsUseIncompatiblePathCreateInterface())
            {
                OutputDevice* pOut = pView->GetFirstOutputDevice();

                if(pOut)
                {
                    if(GetPathPoly().count())
                    {
                        const basegfx::B2DPolygon aCandidate(GetPathPoly().getB2DPolygon(0));

                        if(aCandidate.count() > 2)
                        {
                            // check distance of first and last point
                            const sal_Int32 nCloseDist(pOut->PixelToLogic(Size(pView->GetAutoCloseDistPix(), 0)).Width());
                            const basegfx::B2DVector aDistVector(aCandidate.getB2DPoint(aCandidate.count() - 1) - aCandidate.getB2DPoint(0));

                            if(aDistVector.getLength() <= static_cast<double>(nCloseDist))
                            {
                                // close it
                                ImpSetClosed(true);
                            }
                        }
                    }
                }
            }
        }

        mpDAC.reset();
    }

    return bRetval;
}

bool SdrPathObj::BckCreate(SdrDragStat& rStat)
{
    return impGetDAC().BckCreate(rStat);
}

void SdrPathObj::BrkCreate(SdrDragStat& rStat)
{
    impGetDAC().BrkCreate(rStat);
    mpDAC.reset();
}

// polygons

basegfx::B2DPolyPolygon SdrPathObj::TakeCreatePoly(const SdrDragStat& rDrag) const
{
    basegfx::B2DPolyPolygon aRetval;

    if(mpDAC)
    {
        aRetval = mpDAC->TakeObjectPolyPolygon(rDrag);
        aRetval.append(ImpPathForDragAndCreate::TakeDragPolyPolygon(rDrag));
    }

    return aRetval;
}

// during drag or create, allow accessing the so-far created/modified polyPolygon
basegfx::B2DPolyPolygon SdrPathObj::getObjectPolyPolygon(const SdrDragStat& rDrag) const
{
    basegfx::B2DPolyPolygon aRetval;

    if(mpDAC)
    {
        aRetval = mpDAC->TakeObjectPolyPolygon(rDrag);
    }

    return aRetval;
}

basegfx::B2DPolyPolygon SdrPathObj::getDragPolyPolygon(const SdrDragStat& rDrag) const
{
    basegfx::B2DPolyPolygon aRetval;

    if(mpDAC)
    {
        aRetval = ImpPathForDragAndCreate::TakeDragPolyPolygon(rDrag);
    }

    return aRetval;
}

PointerStyle SdrPathObj::GetCreatePointer() const
{
    return impGetDAC().GetCreatePointer();
}

void SdrPathObj::NbcMove(const Size& rSiz)
{
    maPathPolygon.translate(rSiz.Width(), rSiz.Height());

    // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
    SdrTextObj::NbcMove(rSiz);
}

void SdrPathObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact)
{
    const double fResizeX(xFact);
    const double fResizeY(yFact);

    if(basegfx::fTools::equal(fResizeX, 1.0) && basegfx::fTools::equal(fResizeY, 1.0))
    {
        // tdf#106792 avoid numerical unprecisions: If both scale factors are 1.0, do not
        // manipulate at all - that may change maGeo rapidly (and wrongly) in
        // SdrTextObj::NbcResize. Combined with the UNO API trying to not 'apply'
        // a rotation but to manipulate the existing one, this is fatal. So just
        // avoid this error as long as we have to deal with imprecise geometry
        // manipulations
        return;
    }

    basegfx::B2DHomMatrix aTrans(basegfx::utils::createTranslateB2DHomMatrix(-rRef.X(), -rRef.Y()));
    aTrans = basegfx::utils::createScaleTranslateB2DHomMatrix(
        double(xFact), double(yFact), rRef.X(), rRef.Y()) * aTrans;
    maPathPolygon.transform(aTrans);

    // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
    SdrTextObj::NbcResize(rRef,xFact,yFact);
}

void SdrPathObj::NbcRotate(const Point& rRef, Degree100 nAngle, double sn, double cs)
{
    // Thank JOE, the angles are defined mirrored to the mathematical meanings
    const basegfx::B2DHomMatrix aTrans(
        basegfx::utils::createRotateAroundPoint(rRef.X(), rRef.Y(), -toRadians(nAngle)));
    maPathPolygon.transform(aTrans);

    // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
    SdrTextObj::NbcRotate(rRef,nAngle,sn,cs);
}

void SdrPathObj::NbcShear(const Point& rRefPnt, Degree100 nAngle, double fTan, bool bVShear)
{
    basegfx::B2DHomMatrix aTrans(basegfx::utils::createTranslateB2DHomMatrix(-rRefPnt.X(), -rRefPnt.Y()));

    if(bVShear)
    {
        // Thank JOE, the angles are defined mirrored to the mathematical meanings
        aTrans.shearY(-fTan);
    }
    else
    {
        aTrans.shearX(-fTan);
    }

    aTrans.translate(rRefPnt.X(), rRefPnt.Y());
    maPathPolygon.transform(aTrans);

    // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
    SdrTextObj::NbcShear(rRefPnt,nAngle,fTan,bVShear);
}

void SdrPathObj::NbcMirror(const Point& rRefPnt1, const Point& rRefPnt2)
{
    const double fDiffX(rRefPnt2.X() - rRefPnt1.X());
    const double fDiffY(rRefPnt2.Y() - rRefPnt1.Y());
    const double fRot(atan2(fDiffY, fDiffX));
    basegfx::B2DHomMatrix aTrans(basegfx::utils::createTranslateB2DHomMatrix(-rRefPnt1.X(), -rRefPnt1.Y()));
    aTrans.rotate(-fRot);
    aTrans.scale(1.0, -1.0);
    aTrans.rotate(fRot);
    aTrans.translate(rRefPnt1.X(), rRefPnt1.Y());
    maPathPolygon.transform(aTrans);

    // Do Joe's special handling for lines when mirroring, too
    ImpForceKind();

    // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
    SdrTextObj::NbcMirror(rRefPnt1,rRefPnt2);
}

void SdrPathObj::TakeUnrotatedSnapRect(tools::Rectangle& rRect) const
{
    if(!maGeo.m_nRotationAngle)
    {
        rRect = GetSnapRect();
    }
    else
    {
        XPolyPolygon aXPP(GetPathPoly());
        RotateXPoly(aXPP,Point(),-maGeo.mfSinRotationAngle,maGeo.mfCosRotationAngle);
        rRect=aXPP.GetBoundRect();
        Point aTmp(rRect.TopLeft());
        RotatePoint(aTmp,Point(),maGeo.mfSinRotationAngle,maGeo.mfCosRotationAngle);
        aTmp-=rRect.TopLeft();
        rRect.Move(aTmp.X(),aTmp.Y());
    }
}

void SdrPathObj::RecalcSnapRect()
{
    if(GetPathPoly().count())
    {
        maSnapRect = lcl_ImpGetBoundRect(GetPathPoly());
    }
}

void SdrPathObj::NbcSetSnapRect(const tools::Rectangle& rRect)
{
    tools::Rectangle aOld(GetSnapRect());
    if (aOld.IsEmpty())
    {
        Fraction aX(1,1);
        Fraction aY(1,1);
        NbcResize(aOld.TopLeft(), aX, aY);
        NbcMove(Size(rRect.Left() - aOld.Left(), rRect.Top() - aOld.Top()));
        return;
    }

    // Take empty into account when calculating scale factors
    tools::Long nMulX = rRect.IsWidthEmpty() ? 0 : rRect.Right()  - rRect.Left();

    tools::Long nDivX = aOld.Right()   - aOld.Left();

    // Take empty into account when calculating scale factors
    tools::Long nMulY = rRect.IsHeightEmpty() ? 0 : rRect.Bottom() - rRect.Top();

    tools::Long nDivY = aOld.Bottom()  - aOld.Top();
    if ( nDivX == 0 ) { nMulX = 1; nDivX = 1; }
    if ( nDivY == 0 ) { nMulY = 1; nDivY = 1; }
    if ( nDivX == nMulX ) { nMulX = 1; nDivX = 1; }
    if ( nDivY == nMulY ) { nMulY = 1; nDivY = 1; }
    Fraction aX(nMulX,nDivX);
    Fraction aY(nMulY,nDivY);
    NbcResize(aOld.TopLeft(), aX, aY);
    NbcMove(Size(rRect.Left() - aOld.Left(), rRect.Top() - aOld.Top()));
}

sal_uInt32 SdrPathObj::GetSnapPointCount() const
{
    return GetHdlCount();
}

Point SdrPathObj::GetSnapPoint(sal_uInt32 nSnapPnt) const
{
    sal_uInt32 nPoly,nPnt;
    if(!PolyPolygonEditor::GetRelativePolyPoint(GetPathPoly(), nSnapPnt, nPoly, nPnt))
    {
        SAL_WARN("svx", "SdrPathObj::GetSnapPoint: Point nSnapPnt does not exist.");
    }

    const basegfx::B2DPoint aB2DPoint(GetPathPoly().getB2DPolygon(nPoly).getB2DPoint(nPnt));
    return Point(basegfx::fround<tools::Long>(aB2DPoint.getX()),
                 basegfx::fround<tools::Long>(aB2DPoint.getY()));
}

bool SdrPathObj::IsPolyObj() const
{
    return true;
}

sal_uInt32 SdrPathObj::GetPointCount() const
{
    sal_uInt32 nRetval(0);

    for(auto const& rPolygon : GetPathPoly())
    {
        nRetval += rPolygon.count();
    }

    return nRetval;
}

Point SdrPathObj::GetPoint(sal_uInt32 nHdlNum) const
{
    Point aRetval;
    sal_uInt32 nPoly,nPnt;

    if(PolyPolygonEditor::GetRelativePolyPoint(GetPathPoly(), nHdlNum, nPoly, nPnt))
    {
        const basegfx::B2DPolygon aPoly(GetPathPoly().getB2DPolygon(nPoly));
        const basegfx::B2DPoint aPoint(aPoly.getB2DPoint(nPnt));
        aRetval = Point(basegfx::fround<tools::Long>(aPoint.getX()),
                        basegfx::fround<tools::Long>(aPoint.getY()));
    }

    return aRetval;
}

void SdrPathObj::NbcSetPoint(const Point& rPnt, sal_uInt32 nHdlNum)
{
    sal_uInt32 nPoly,nPnt;

    if(!PolyPolygonEditor::GetRelativePolyPoint(GetPathPoly(), nHdlNum, nPoly, nPnt))
        return;

    basegfx::B2DPolygon aNewPolygon(GetPathPoly().getB2DPolygon(nPoly));
    aNewPolygon.setB2DPoint(nPnt, basegfx::B2DPoint(rPnt.X(), rPnt.Y()));
    maPathPolygon.setB2DPolygon(nPoly, aNewPolygon);

    if(meKind==SdrObjKind::Line)
    {
        ImpForceLineAngle();
    }
    else
    {
        if(GetPathPoly().count())
        {
            // #i10659# for SdrTextObj, keep aRect up to date
            setRectangle(lcl_ImpGetBoundRect(GetPathPoly()));
        }
    }

    SetBoundAndSnapRectsDirty();
}

sal_uInt32 SdrPathObj::NbcInsPointOld(const Point& rPos, bool bNewObj)
{
    sal_uInt32 nNewHdl;

    if(bNewObj)
    {
        nNewHdl = NbcInsPoint(rPos, true);
    }
    else
    {
        // look for smallest distance data
        const basegfx::B2DPoint aTestPoint(rPos.X(), rPos.Y());
        sal_uInt32 nSmallestPolyIndex(0);
        sal_uInt32 nSmallestEdgeIndex(0);
        double fSmallestCut;
        basegfx::utils::getSmallestDistancePointToPolyPolygon(GetPathPoly(), aTestPoint, nSmallestPolyIndex, nSmallestEdgeIndex, fSmallestCut);

        nNewHdl = NbcInsPoint(rPos, false);
    }

    ImpForceKind();
    return nNewHdl;
}

sal_uInt32 SdrPathObj::NbcInsPoint(const Point& rPos, bool bNewObj)
{
    sal_uInt32 nNewHdl;

    if(bNewObj)
    {
        basegfx::B2DPolygon aNewPoly;
        const basegfx::B2DPoint aPoint(rPos.X(), rPos.Y());
        aNewPoly.append(aPoint);
        aNewPoly.setClosed(IsClosed());
        maPathPolygon.append(aNewPoly);
        SetBoundAndSnapRectsDirty();
        nNewHdl = GetHdlCount();
    }
    else
    {
        // look for smallest distance data
        const basegfx::B2DPoint aTestPoint(rPos.X(), rPos.Y());
        sal_uInt32 nSmallestPolyIndex(0);
        sal_uInt32 nSmallestEdgeIndex(0);
        double fSmallestCut;
        basegfx::utils::getSmallestDistancePointToPolyPolygon(GetPathPoly(), aTestPoint, nSmallestPolyIndex, nSmallestEdgeIndex, fSmallestCut);
        basegfx::B2DPolygon aCandidate(GetPathPoly().getB2DPolygon(nSmallestPolyIndex));
        const bool bBefore(!aCandidate.isClosed() && 0 == nSmallestEdgeIndex && 0.0 == fSmallestCut);
        const bool bAfter(!aCandidate.isClosed() && aCandidate.count() == nSmallestEdgeIndex + 2 && 1.0 == fSmallestCut);

        if(bBefore)
        {
            // before first point
            aCandidate.insert(0, aTestPoint);

            if(aCandidate.areControlPointsUsed())
            {
                if(aCandidate.isNextControlPointUsed(1))
                {
                    aCandidate.setNextControlPoint(0, interpolate(aTestPoint, aCandidate.getB2DPoint(1), (1.0 / 3.0)));
                    aCandidate.setPrevControlPoint(1, interpolate(aTestPoint, aCandidate.getB2DPoint(1), (2.0 / 3.0)));
                }
            }

            nNewHdl = 0;
        }
        else if(bAfter)
        {
            // after last point
            aCandidate.append(aTestPoint);

            if(aCandidate.areControlPointsUsed())
            {
                if(aCandidate.isPrevControlPointUsed(aCandidate.count() - 2))
                {
                    aCandidate.setNextControlPoint(aCandidate.count() - 2, interpolate(aCandidate.getB2DPoint(aCandidate.count() - 2), aTestPoint, (1.0 / 3.0)));
                    aCandidate.setPrevControlPoint(aCandidate.count() - 1, interpolate(aCandidate.getB2DPoint(aCandidate.count() - 2), aTestPoint, (2.0 / 3.0)));
                }
            }

            nNewHdl = aCandidate.count() - 1;
        }
        else
        {
            // in between
            bool bSegmentSplit(false);
            const sal_uInt32 nNextIndex((nSmallestEdgeIndex + 1) % aCandidate.count());

            if(aCandidate.areControlPointsUsed())
            {
                if(aCandidate.isNextControlPointUsed(nSmallestEdgeIndex) || aCandidate.isPrevControlPointUsed(nNextIndex))
                {
                    bSegmentSplit = true;
                }
            }

            if(bSegmentSplit)
            {
                // rebuild original segment to get the split data
                basegfx::B2DCubicBezier aBezierA, aBezierB;
                const basegfx::B2DCubicBezier aBezier(
                    aCandidate.getB2DPoint(nSmallestEdgeIndex),
                    aCandidate.getNextControlPoint(nSmallestEdgeIndex),
                    aCandidate.getPrevControlPoint(nNextIndex),
                    aCandidate.getB2DPoint(nNextIndex));

                // split and insert hit point
                aBezier.split(fSmallestCut, &aBezierA, &aBezierB);
                aCandidate.insert(nSmallestEdgeIndex + 1, aTestPoint);

                // since we inserted hit point and not split point, we need to add an offset
                // to the control points to get the C1 continuity we want to achieve
                const basegfx::B2DVector aOffset(aTestPoint - aBezierA.getEndPoint());
                aCandidate.setNextControlPoint(nSmallestEdgeIndex, aBezierA.getControlPointA() + aOffset);
                aCandidate.setPrevControlPoint(nSmallestEdgeIndex + 1, aBezierA.getControlPointB() + aOffset);
                aCandidate.setNextControlPoint(nSmallestEdgeIndex + 1, aBezierB.getControlPointA() + aOffset);
                aCandidate.setPrevControlPoint((nSmallestEdgeIndex + 2) % aCandidate.count(), aBezierB.getControlPointB() + aOffset);
            }
            else
            {
                aCandidate.insert(nSmallestEdgeIndex + 1, aTestPoint);
            }

            nNewHdl = nSmallestEdgeIndex + 1;
        }

        maPathPolygon.setB2DPolygon(nSmallestPolyIndex, aCandidate);

        // create old polygon index from it
        for(sal_uInt32 a(0); a < nSmallestPolyIndex; a++)
        {
            nNewHdl += GetPathPoly().getB2DPolygon(a).count();
        }
    }

    ImpForceKind();
    return nNewHdl;
}

rtl::Reference<SdrPathObj> SdrPathObj::RipPoint(sal_uInt32 nHdlNum, sal_uInt32& rNewPt0Index)
{
    rtl::Reference<SdrPathObj> pNewObj;
    const basegfx::B2DPolyPolygon aLocalPolyPolygon(GetPathPoly());
    sal_uInt32 nPoly, nPnt;

    if(PolyPolygonEditor::GetRelativePolyPoint(aLocalPolyPolygon, nHdlNum, nPoly, nPnt))
    {
        if(0 == nPoly)
        {
            const basegfx::B2DPolygon& aCandidate(aLocalPolyPolygon.getB2DPolygon(nPoly));
            const sal_uInt32 nPointCount(aCandidate.count());

            if(nPointCount)
            {
                if(IsClosed())
                {
                    // when closed, RipPoint means to open the polygon at the selected point. To
                    // be able to do that, it is necessary to make the selected point the first one
                    basegfx::B2DPolygon aNewPolygon(basegfx::utils::makeStartPoint(aCandidate, nPnt));
                    SetPathPoly(basegfx::B2DPolyPolygon(aNewPolygon));
                    ToggleClosed();

                    // give back new position of old start point (historical reasons)
                    rNewPt0Index = (nPointCount - nPnt) % nPointCount;
                }
                else
                {
                    if(nPointCount >= 3 && nPnt != 0 && nPnt + 1 < nPointCount)
                    {
                        // split in two objects at point nPnt
                        basegfx::B2DPolygon aSplitPolyA(aCandidate, 0, nPnt + 1);
                        SetPathPoly(basegfx::B2DPolyPolygon(aSplitPolyA));

                        pNewObj = SdrObject::Clone(*this, getSdrModelFromSdrObject());
                        basegfx::B2DPolygon aSplitPolyB(aCandidate, nPnt, nPointCount - nPnt);
                        pNewObj->SetPathPoly(basegfx::B2DPolyPolygon(aSplitPolyB));
                    }
                }
            }
        }
    }

    return pNewObj;
}

rtl::Reference<SdrObject> SdrPathObj::DoConvertToPolyObj(bool bBezier, bool bAddText) const
{
    // #i89784# check for FontWork with activated HideContour
    const drawinglayer::attribute::SdrTextAttribute aText(
        drawinglayer::primitive2d::createNewSdrTextAttribute(GetObjectItemSet(), *getText(0)));
    const bool bHideContour(
        !aText.isDefault() && !aText.getSdrFormTextAttribute().isDefault() && aText.isHideContour());

    rtl::Reference<SdrObject> pRet;

    if(!bHideContour)
    {
        rtl::Reference<SdrPathObj> pPath = ImpConvertMakeObj(GetPathPoly(), IsClosed(), bBezier);

        if(pPath->GetPathPoly().areControlPointsUsed())
        {
            if(!bBezier)
            {
                // reduce all bezier curves
                pPath->SetPathPoly(basegfx::utils::adaptiveSubdivideByAngle(pPath->GetPathPoly()));
            }
        }
        else
        {
            if(bBezier)
            {
                // create bezier curves
                pPath->SetPathPoly(basegfx::utils::expandToCurve(pPath->GetPathPoly()));
            }
        }
        pRet = std::move(pPath);
    }

    if(bAddText)
    {
        pRet = ImpConvertAddText(std::move(pRet), bBezier);
    }

    return pRet;
}

std::unique_ptr<SdrObjGeoData> SdrPathObj::NewGeoData() const
{
    return std::make_unique<SdrPathObjGeoData>();
}

void SdrPathObj::SaveGeoData(SdrObjGeoData& rGeo) const
{
    SdrTextObj::SaveGeoData(rGeo);
    SdrPathObjGeoData& rPGeo = static_cast<SdrPathObjGeoData&>( rGeo );
    rPGeo.maPathPolygon=GetPathPoly();
    rPGeo.meKind=meKind;
}

void SdrPathObj::RestoreGeoData(const SdrObjGeoData& rGeo)
{
    SdrTextObj::RestoreGeoData(rGeo);
    const SdrPathObjGeoData& rPGeo=static_cast<const SdrPathObjGeoData&>(rGeo);
    maPathPolygon=rPGeo.maPathPolygon;
    meKind=rPGeo.meKind;
    ImpForceKind(); // to set bClosed (among other things)
}

void SdrPathObj::NbcSetPathPoly(const basegfx::B2DPolyPolygon& rPathPoly)
{
    if(GetPathPoly() != rPathPoly)
    {
        maPathPolygon=rPathPoly;
        ImpForceKind();
        SetBoundAndSnapRectsDirty();
    }
}

void SdrPathObj::SetPathPoly(const basegfx::B2DPolyPolygon& rPathPoly)
{
    if(GetPathPoly() != rPathPoly)
    {
        tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetLastBoundRect();
        NbcSetPathPoly(rPathPoly);
        SetChanged();
        BroadcastObjectChange();
        SendUserCall(SdrUserCallType::Resize,aBoundRect0);
    }
}

void SdrPathObj::ToggleClosed()
{
    tools::Rectangle aBoundRect0;
    if(m_pUserCall != nullptr)
        aBoundRect0 = GetLastBoundRect();
    ImpSetClosed(!IsClosed()); // set new ObjKind
    ImpForceKind(); // because we want Line -> Poly -> PolyLine instead of Line -> Poly -> Line
    SetBoundAndSnapRectsDirty();
    SetChanged();
    BroadcastObjectChange();
    SendUserCall(SdrUserCallType::Resize, aBoundRect0);
}

ImpPathForDragAndCreate& SdrPathObj::impGetDAC() const
{
    if(!mpDAC)
    {
        const_cast<SdrPathObj*>(this)->mpDAC.reset(new ImpPathForDragAndCreate(*const_cast<SdrPathObj*>(this)));
    }

    return *mpDAC;
}


// transformation interface for StarOfficeAPI. This implements support for
// homogeneous 3x3 matrices containing the transformation of the SdrObject. At the
// moment it contains a shearX, rotation and translation, but for setting all linear
// transforms like Scale, ShearX, ShearY, Rotate and Translate are supported.


// gets base transformation and rectangle of object. If it's an SdrPathObj it fills the PolyPolygon
// with the base geometry and returns TRUE. Otherwise it returns FALSE.
bool SdrPathObj::TRGetBaseGeometry(basegfx::B2DHomMatrix& rMatrix, basegfx::B2DPolyPolygon& rPolyPolygon) const
{
    double fRotate(0.0);
    double fShearX(0.0);
    basegfx::B2DTuple aScale(1.0, 1.0);
    basegfx::B2DTuple aTranslate(0.0, 0.0);

    if(GetPathPoly().count())
    {
        // copy geometry
        basegfx::B2DHomMatrix aMoveToZeroMatrix;
        rPolyPolygon = GetPathPoly();

        if(SdrObjKind::Line == meKind)
        {
            // ignore shear and rotate, just use scale and translate
            OSL_ENSURE(GetPathPoly().count() > 0 && GetPathPoly().getB2DPolygon(0).count() > 1, "OBJ_LINE with too few polygons (!)");
            // #i72287# use polygon without control points for range calculation. Do not change rPolyPolygon
            // itself, else this method will no longer return the full polygon information (curve will
            // be lost)
            const basegfx::B2DRange aPolyRangeNoCurve(rPolyPolygon.getB2DRange());
            aScale = aPolyRangeNoCurve.getRange();
            aTranslate = aPolyRangeNoCurve.getMinimum();

            // define matrix for move polygon to zero point
            aMoveToZeroMatrix.translate(-aTranslate.getX(), -aTranslate.getY());
        }
        else
        {
            if(maGeo.m_nShearAngle || maGeo.m_nRotationAngle)
            {
                // get rotate and shear in drawingLayer notation
                fRotate = toRadians(maGeo.m_nRotationAngle);
                fShearX = toRadians(maGeo.m_nShearAngle);

                // build mathematically correct (negative shear and rotate) object transform
                // containing shear and rotate to extract unsheared, unrotated polygon
                basegfx::B2DHomMatrix aObjectMatrix;
                aObjectMatrix.shearX(-maGeo.mfTanShearAngle);
                aObjectMatrix.rotate(toRadians(36000_deg100 - maGeo.m_nRotationAngle));

                // create inverse from it and back-transform polygon
                basegfx::B2DHomMatrix aInvObjectMatrix(aObjectMatrix);
                aInvObjectMatrix.invert();
                rPolyPolygon.transform(aInvObjectMatrix);

                // get range from unsheared, unrotated polygon and extract scale and translate.
                // transform topLeft from it back to transformed state to get original
                // topLeft (rotation center)
                // #i72287# use polygon without control points for range calculation. Do not change rPolyPolygon
                // itself, else this method will no longer return the full polygon information (curve will
                // be lost)
                const basegfx::B2DRange aCorrectedRangeNoCurve(rPolyPolygon.getB2DRange());
                aTranslate = aObjectMatrix * aCorrectedRangeNoCurve.getMinimum();
                aScale = aCorrectedRangeNoCurve.getRange();

                // define matrix for move polygon to zero point
                // #i112280# Added missing minus for Y-Translation
                aMoveToZeroMatrix.translate(-aCorrectedRangeNoCurve.getMinX(), -aCorrectedRangeNoCurve.getMinY());
            }
            else
            {
                // get scale and translate from unsheared, unrotated polygon
                // #i72287# use polygon without control points for range calculation. Do not change rPolyPolygon
                // itself, else this method will no longer return the full polygon information (curve will
                // be lost)
                const basegfx::B2DRange aPolyRangeNoCurve(rPolyPolygon.getB2DRange());
                if (!aPolyRangeNoCurve.isEmpty())
                {
                    aScale = aPolyRangeNoCurve.getRange();
                    aTranslate = aPolyRangeNoCurve.getMinimum();

                    // define matrix for move polygon to zero point
                    aMoveToZeroMatrix.translate(-aTranslate.getX(), -aTranslate.getY());
                }
            }
        }

        // move polygon to zero point with pre-defined matrix
        rPolyPolygon.transform(aMoveToZeroMatrix);
    }

    // position maybe relative to anchorpos, convert
    if( getSdrModelFromSdrObject().IsWriter() )
    {
        if(GetAnchorPos().X() || GetAnchorPos().Y())
        {
            aTranslate -= basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y());
        }
    }

    // build return value matrix
    rMatrix = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix(
        aScale,
        basegfx::fTools::equalZero(fShearX) ? 0.0 : tan(fShearX),
        -fRotate,
        aTranslate);

    return true;
}

void SdrPathObj::SetHandleScale(bool bHandleScale)
{
    mbHandleScale = bHandleScale;
}

// Sets the base geometry of the object using infos contained in the homogeneous 3x3 matrix.
// If it's an SdrPathObj it will use the provided geometry information. The Polygon has
// to use (0,0) as upper left and will be scaled to the given size in the matrix.
void SdrPathObj::TRSetBaseGeometry(const basegfx::B2DHomMatrix& rMatrix, const basegfx::B2DPolyPolygon& rPolyPolygon)
{
    // break up matrix
    basegfx::B2DTuple aScale;
    basegfx::B2DTuple aTranslate;
    double fRotate, fShearX;
    rMatrix.decompose(aScale, aTranslate, fRotate, fShearX);

    // #i75086# Old DrawingLayer (GeoStat and geometry) does not support holding negative scalings
    // in X and Y which equal a 180 degree rotation. Recognize it and react accordingly
    if(aScale.getX() < 0.0 && aScale.getY() < 0.0)
    {
        aScale.setX(fabs(aScale.getX()));
        aScale.setY(fabs(aScale.getY()));
        fRotate = fmod(fRotate + M_PI, 2 * M_PI);
    }

    // copy poly
    basegfx::B2DPolyPolygon aNewPolyPolygon(rPolyPolygon);

    // reset object shear and rotations
    maGeo.m_nRotationAngle = 0_deg100;
    maGeo.RecalcSinCos();
    maGeo.m_nShearAngle = 0_deg100;
    maGeo.RecalcTan();

    if( getSdrModelFromSdrObject().IsWriter() )
    {
        // if anchor is used, make position relative to it
        if(GetAnchorPos().X() || GetAnchorPos().Y())
        {
            aTranslate += basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y());
        }
    }

    // create transformation for polygon, set values at maGeo direct
    basegfx::B2DHomMatrix aTransform;

    // #i75086#
    // Given polygon is already scaled (for historical reasons), but not mirrored yet.
    // Thus, when scale is negative in X or Y, apply the needed mirroring accordingly.
    double fScaleX(aScale.getX() < 0.0 ? -1.0 : 1.0);
    double fScaleY(aScale.getY() < 0.0 ? -1.0 : 1.0);

    // tdf#98565, tdf#98584. While loading a shape, svg:width and svg:height is used to scale
    // the polygon. But draw:transform might introduce additional scaling factors, which need to
    // be applied to the polygon too, so aScale cannot be ignored while loading.
    // I use "maSnapRect.IsEmpty() && GetPathPoly().count()" to detect this case. Any better
    // idea? The behavior in other cases is the same as it was before this fix.
    if (maSnapRect.IsEmpty() && GetPathPoly().count() && mbHandleScale)
    {
        // In case of a Writer document, the scaling factors were converted to twips. That is not
        // correct here, because width and height are already in the points coordinates and aScale
        // is no length but only a factor here. Convert back.
        if (getSdrModelFromSdrObject().IsWriter())
        {
            aScale.setX(o3tl::convert(aScale.getX(), o3tl::Length::twip, o3tl::Length::mm100));
            aScale.setY(o3tl::convert(aScale.getY(), o3tl::Length::twip, o3tl::Length::mm100));
        }
        fScaleX *= fabs(aScale.getX());
        fScaleY *= fabs(aScale.getY());
    }

    if (fScaleX != 1.0 || fScaleY != 1.0)
        aTransform.scale(fScaleX, fScaleY);

    if(!basegfx::fTools::equalZero(fShearX))
    {
        aTransform.shearX(tan(-atan(fShearX)));
        maGeo.m_nShearAngle = Degree100(basegfx::fround(basegfx::rad2deg<100>(atan(fShearX))));
        maGeo.RecalcTan();
    }

    if(!basegfx::fTools::equalZero(fRotate))
    {
        // #i78696#
        // fRotate is mathematically correct for linear transformations, so it's
        // the one to use for the geometry change
        aTransform.rotate(fRotate);

        // #i78696#
        // fRotate is mathematically correct, but aGeoStat.nRotationAngle is
        // mirrored -> mirror value here
        maGeo.m_nRotationAngle = NormAngle36000(Degree100(basegfx::fround(-basegfx::rad2deg<100>(fRotate))));
        maGeo.RecalcSinCos();
    }

    if(!aTranslate.equalZero())
    {
        // #i39529# absolute positioning, so get current position (without control points (!))
        const basegfx::B2DRange aCurrentRange(aNewPolyPolygon.getB2DRange());
        aTransform.translate(aTranslate.getX() - aCurrentRange.getMinX(), aTranslate.getY() - aCurrentRange.getMinY());
    }

    // transform polygon and trigger change
    aNewPolyPolygon.transform(aTransform);
    SetPathPoly(aNewPolyPolygon);
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
