xref: /core/svx/source/svdraw/svdopath.cxx (revision 1f625b3f)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <tools/bigint.hxx>
21 #include <tools/helpers.hxx>
22 #include <svx/svdopath.hxx>
23 #include <math.h>
24 #include <svx/xpool.hxx>
25 #include <svx/xpoly.hxx>
26 #include <svx/svdtrans.hxx>
27 #include <svx/svdetc.hxx>
28 #include <svx/svddrag.hxx>
29 #include <svx/svdmodel.hxx>
30 #include <svx/svdpage.hxx>
31 #include <svx/svdhdl.hxx>
32 #include <svx/svdview.hxx>
33 #include <svx/dialmgr.hxx>
34 #include <svx/strings.hrc>
35 
36 #include <svx/xlnwtit.hxx>
37 #include <svx/xlnclit.hxx>
38 #include <svx/xflclit.hxx>
39 #include <svx/svdogrp.hxx>
40 #include <svx/polypolygoneditor.hxx>
41 #include <svx/xlntrit.hxx>
42 #include <sdr/contact/viewcontactofsdrpathobj.hxx>
43 #include <basegfx/matrix/b2dhommatrix.hxx>
44 #include <basegfx/point/b2dpoint.hxx>
45 #include <basegfx/polygon/b2dpolypolygontools.hxx>
46 #include <basegfx/range/b2drange.hxx>
47 #include <basegfx/curve/b2dcubicbezier.hxx>
48 #include <basegfx/polygon/b2dpolygontools.hxx>
49 #include <sdr/attribute/sdrtextattribute.hxx>
50 #include <sdr/primitive2d/sdrattributecreator.hxx>
51 #include <basegfx/matrix/b2dhommatrixtools.hxx>
52 #include <sdr/attribute/sdrformtextattribute.hxx>
53 #include <vcl/ptrstyle.hxx>
54 #include <memory>
55 #include <sal/log.hxx>
56 
57 using namespace sdr;
58 
59 static sal_uInt16 GetPrevPnt(sal_uInt16 nPnt, sal_uInt16 nPntMax, bool bClosed)
60 {
61     if (nPnt>0) {
62         nPnt--;
63     } else {
64         nPnt=nPntMax;
65         if (bClosed) nPnt--;
66     }
67     return nPnt;
68 }
69 
70 static sal_uInt16 GetNextPnt(sal_uInt16 nPnt, sal_uInt16 nPntMax, bool bClosed)
71 {
72     nPnt++;
73     if (nPnt>nPntMax || (bClosed && nPnt>=nPntMax)) nPnt=0;
74     return nPnt;
75 }
76 
77 namespace {
78 
79 struct ImpSdrPathDragData  : public SdrDragStatUserData
80 {
81     XPolygon                    aXP;            // section of the original polygon
82     bool                        bValid;         // FALSE = too few points
83     bool                        bClosed;        // closed object?
84     sal_uInt16                  nPoly;          // number of the polygon in the PolyPolygon
85     sal_uInt16                  nPnt;           // number of point in the above polygon
86     sal_uInt16                  nPointCount;    // number of points of the polygon
87     bool                        bBegPnt;        // dragged point is first point of a Polyline
88     bool                        bEndPnt;        // dragged point is finishing point of a Polyline
89     sal_uInt16                  nPrevPnt;       // index of previous point
90     sal_uInt16                  nNextPnt;       // index of next point
91     bool                        bPrevIsBegPnt;  // previous point is first point of a Polyline
92     bool                        bNextIsEndPnt;  // next point is first point of a Polyline
93     sal_uInt16                  nPrevPrevPnt;   // index of point before previous point
94     sal_uInt16                  nNextNextPnt;   // index of point after next point
95     bool                        bControl;       // point is a control point
96     bool                        bIsNextControl; // point is a control point after a support point
97     bool                        bPrevIsControl; // if nPnt is a support point: a control point comes before
98     bool                        bNextIsControl; // if nPnt is a support point: a control point comes after
99     sal_uInt16                  nPrevPrevPnt0;
100     sal_uInt16                  nPrevPnt0;
101     sal_uInt16                  nPnt0;
102     sal_uInt16                  nNextPnt0;
103     sal_uInt16                  nNextNextPnt0;
104     bool                        bEliminate;     // delete point? (is set by MovDrag)
105 
106     bool const                  mbMultiPointDrag;
107     const XPolyPolygon          maOrig;
108     XPolyPolygon                maMove;
109     std::vector<SdrHdl*>        maHandles;
110 
111 public:
112     ImpSdrPathDragData(const SdrPathObj& rPO, const SdrHdl& rHdl, bool bMuPoDr, const SdrDragStat& rDrag);
113     void ResetPoly(const SdrPathObj& rPO);
114     bool IsMultiPointDrag() const { return mbMultiPointDrag; }
115 };
116 
117 }
118 
119 ImpSdrPathDragData::ImpSdrPathDragData(const SdrPathObj& rPO, const SdrHdl& rHdl, bool bMuPoDr, const SdrDragStat& rDrag)
120     : aXP(5)
121     , bValid(false)
122     , bClosed(false)
123     , nPoly(0)
124     , nPnt(0)
125     , nPointCount(0)
126     , bBegPnt(false)
127     , bEndPnt(false)
128     , nPrevPnt(0)
129     , nNextPnt(0)
130     , bPrevIsBegPnt(false)
131     , bNextIsEndPnt(false)
132     , nPrevPrevPnt(0)
133     , nNextNextPnt(0)
134     , bControl(false)
135     , bIsNextControl(false)
136     , bPrevIsControl(false)
137     , bNextIsControl(false)
138     , nPrevPrevPnt0(0)
139     , nPrevPnt0(0)
140     , nPnt0(0)
141     , nNextPnt0(0)
142     , nNextNextPnt0(0)
143     , bEliminate(false)
144     , mbMultiPointDrag(bMuPoDr)
145     , maOrig(rPO.GetPathPoly())
146     , maHandles(0)
147 {
148     if(mbMultiPointDrag)
149     {
150         const SdrMarkView& rMarkView = *rDrag.GetView();
151         const SdrHdlList& rHdlList = rMarkView.GetHdlList();
152         const size_t nHdlCount = rHdlList.GetHdlCount();
153         const SdrObject* pInteractionObject(nHdlCount && rHdlList.GetHdl(0) ? rHdlList.GetHdl(0)->GetObj() : nullptr);
154 
155         for(size_t a = 0; a < nHdlCount; ++a)
156         {
157             SdrHdl* pTestHdl = rHdlList.GetHdl(a);
158 
159             if(pTestHdl && pTestHdl->IsSelected() && pTestHdl->GetObj() == pInteractionObject)
160             {
161                 maHandles.push_back(pTestHdl);
162             }
163         }
164 
165         maMove = maOrig;
166         bValid = true;
167     }
168     else
169     {
170         sal_uInt16 nPntMax = 0; // maximum index
171         bValid=false;
172         bClosed=rPO.IsClosed();          // closed object?
173         nPoly=static_cast<sal_uInt16>(rHdl.GetPolyNum());            // number of the polygon in the PolyPolygon
174         nPnt=static_cast<sal_uInt16>(rHdl.GetPointNum());            // number of points in the above polygon
175         const XPolygon aTmpXP(rPO.GetPathPoly().getB2DPolygon(nPoly));
176         nPointCount=aTmpXP.GetPointCount();        // number of point of the polygon
177         if (nPointCount==0 || (bClosed && nPointCount==1)) return; // minimum of 1 points for Lines, minimum of 2 points for Polygon
178         nPntMax=nPointCount-1;                  // maximum index
179         bBegPnt=!bClosed && nPnt==0;        // dragged point is first point of a Polyline
180         bEndPnt=!bClosed && nPnt==nPntMax;  // dragged point is finishing point of a Polyline
181         if (bClosed && nPointCount<=3) {        // if polygon is only a line
182             bBegPnt=(nPointCount<3) || nPnt==0;
183             bEndPnt=(nPointCount<3) || nPnt==nPntMax-1;
184         }
185         nPrevPnt=nPnt;                      // index of previous point
186         nNextPnt=nPnt;                      // index of next point
187         if (!bBegPnt) nPrevPnt=GetPrevPnt(nPnt,nPntMax,bClosed);
188         if (!bEndPnt) nNextPnt=GetNextPnt(nPnt,nPntMax,bClosed);
189         bPrevIsBegPnt=bBegPnt || (!bClosed && nPrevPnt==0);
190         bNextIsEndPnt=bEndPnt || (!bClosed && nNextPnt==nPntMax);
191         nPrevPrevPnt=nPnt;                  // index of point before previous point
192         nNextNextPnt=nPnt;                  // index of point after next point
193         if (!bPrevIsBegPnt) nPrevPrevPnt=GetPrevPnt(nPrevPnt,nPntMax,bClosed);
194         if (!bNextIsEndPnt) nNextNextPnt=GetNextPnt(nNextPnt,nPntMax,bClosed);
195         bControl=rHdl.IsPlusHdl();          // point is a control point
196         bIsNextControl=false;               // point is a control point after a support point
197         bPrevIsControl=false;               // if nPnt is a support point: a control point comes before
198         bNextIsControl=false;               // if nPnt is a support point: a control point comes after
199         if (bControl) {
200             bIsNextControl=!aTmpXP.IsControl(nPrevPnt);
201         } else {
202             bPrevIsControl=!bBegPnt && !bPrevIsBegPnt && aTmpXP.GetFlags(nPrevPnt)==PolyFlags::Control;
203             bNextIsControl=!bEndPnt && !bNextIsEndPnt && aTmpXP.GetFlags(nNextPnt)==PolyFlags::Control;
204         }
205         nPrevPrevPnt0=nPrevPrevPnt;
206         nPrevPnt0    =nPrevPnt;
207         nPnt0        =nPnt;
208         nNextPnt0    =nNextPnt;
209         nNextNextPnt0=nNextNextPnt;
210         nPrevPrevPnt=0;
211         nPrevPnt=1;
212         nPnt=2;
213         nNextPnt=3;
214         nNextNextPnt=4;
215         bEliminate=false;
216         ResetPoly(rPO);
217         bValid=true;
218     }
219 }
220 
221 void ImpSdrPathDragData::ResetPoly(const SdrPathObj& rPO)
222 {
223     const XPolygon aTmpXP(rPO.GetPathPoly().getB2DPolygon(nPoly));
224     aXP[0]=aTmpXP[nPrevPrevPnt0];  aXP.SetFlags(0,aTmpXP.GetFlags(nPrevPrevPnt0));
225     aXP[1]=aTmpXP[nPrevPnt0];      aXP.SetFlags(1,aTmpXP.GetFlags(nPrevPnt0));
226     aXP[2]=aTmpXP[nPnt0];          aXP.SetFlags(2,aTmpXP.GetFlags(nPnt0));
227     aXP[3]=aTmpXP[nNextPnt0];      aXP.SetFlags(3,aTmpXP.GetFlags(nNextPnt0));
228     aXP[4]=aTmpXP[nNextNextPnt0];  aXP.SetFlags(4,aTmpXP.GetFlags(nNextNextPnt0));
229 }
230 
231 namespace {
232 
233 struct ImpPathCreateUser  : public SdrDragStatUserData
234 {
235     Point                   aBezControl0;
236     Point                   aBezStart;
237     Point                   aBezCtrl1;
238     Point                   aBezCtrl2;
239     Point                   aBezEnd;
240     Point                   aCircStart;
241     Point                   aCircEnd;
242     Point                   aCircCenter;
243     Point                   aLineStart;
244     Point                   aLineEnd;
245     Point                   aRectP1;
246     Point                   aRectP2;
247     Point                   aRectP3;
248     long                    nCircRadius;
249     long                    nCircStAngle;
250     long                    nCircRelAngle;
251     bool                    bBezier;
252     bool                    bBezHasCtrl0;
253     bool                    bCircle;
254     bool                    bAngleSnap;
255     bool                    bLine;
256     bool                    bLine90;
257     bool                    bRect;
258     bool                    bMixedCreate;
259     sal_uInt16                  nBezierStartPoint;
260     SdrObjKind              eStartKind;
261     SdrObjKind              eCurrentKind;
262 
263 public:
264     ImpPathCreateUser(): nCircRadius(0),nCircStAngle(0),nCircRelAngle(0),
265         bBezier(false),bBezHasCtrl0(false),bCircle(false),bAngleSnap(false),bLine(false),bLine90(false),bRect(false),
266         bMixedCreate(false),nBezierStartPoint(0),eStartKind(OBJ_NONE),eCurrentKind(OBJ_NONE) { }
267 
268     void ResetFormFlags() { bBezier=false; bCircle=false; bLine=false; bRect=false; }
269     bool IsFormFlag() const { return bBezier || bCircle || bLine || bRect; }
270     XPolygon GetFormPoly() const;
271     void CalcBezier(const Point& rP1, const Point& rP2, const Point& rDir, bool bMouseDown);
272     XPolygon GetBezierPoly() const;
273     void CalcCircle(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView);
274     XPolygon GetCirclePoly() const;
275     void CalcLine(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView);
276     static Point    CalcLine(const Point& rCsr, long nDirX, long nDirY, SdrView const * pView);
277     XPolygon GetLinePoly() const;
278     void CalcRect(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView);
279     XPolygon GetRectPoly() const;
280 };
281 
282 }
283 
284 XPolygon ImpPathCreateUser::GetFormPoly() const
285 {
286     if (bBezier) return GetBezierPoly();
287     if (bCircle) return GetCirclePoly();
288     if (bLine)   return GetLinePoly();
289     if (bRect)   return GetRectPoly();
290     return XPolygon();
291 }
292 
293 void ImpPathCreateUser::CalcBezier(const Point& rP1, const Point& rP2, const Point& rDir, bool bMouseDown)
294 {
295     aBezStart=rP1;
296     aBezCtrl1=rP1+rDir;
297     aBezCtrl2=rP2;
298 
299     // #i21479#
300     // Also copy the end point when no end point is set yet
301     if (!bMouseDown || (0 == aBezEnd.X() && 0 == aBezEnd.Y())) aBezEnd=rP2;
302 
303     bBezier=true;
304 }
305 
306 XPolygon ImpPathCreateUser::GetBezierPoly() const
307 {
308     XPolygon aXP(4);
309     aXP[0]=aBezStart; aXP.SetFlags(0,PolyFlags::Smooth);
310     aXP[1]=aBezCtrl1; aXP.SetFlags(1,PolyFlags::Control);
311     aXP[2]=aBezCtrl2; aXP.SetFlags(2,PolyFlags::Control);
312     aXP[3]=aBezEnd;
313     return aXP;
314 }
315 
316 void ImpPathCreateUser::CalcCircle(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView)
317 {
318     long nTangAngle=GetAngle(rDir);
319     aCircStart=rP1;
320     aCircEnd=rP2;
321     aCircCenter=rP1;
322     long dx=rP2.X()-rP1.X();
323     long dy=rP2.Y()-rP1.Y();
324     long dAngle=GetAngle(Point(dx,dy))-nTangAngle;
325     dAngle=NormAngle36000(dAngle);
326     long nTmpAngle=NormAngle36000(9000-dAngle);
327     bool bRet=nTmpAngle!=9000 && nTmpAngle!=27000;
328     long nRad=0;
329     if (bRet) {
330         double cs = cos(nTmpAngle * F_PI18000);
331         double nR=static_cast<double>(GetLen(Point(dx,dy)))/cs/2;
332         nRad=std::abs(FRound(nR));
333     }
334     if (dAngle<18000) {
335         nCircStAngle=NormAngle36000(nTangAngle-9000);
336         nCircRelAngle=NormAngle36000(2*dAngle);
337         aCircCenter.AdjustX(FRound(nRad * cos((nTangAngle + 9000) * F_PI18000)));
338         aCircCenter.AdjustY(-(FRound(nRad * sin((nTangAngle + 9000) * F_PI18000))));
339     } else {
340         nCircStAngle=NormAngle36000(nTangAngle+9000);
341         nCircRelAngle=-NormAngle36000(36000-2*dAngle);
342         aCircCenter.AdjustX(FRound(nRad * cos((nTangAngle - 9000) * F_PI18000)));
343         aCircCenter.AdjustY(-(FRound(nRad * sin((nTangAngle - 9000) * F_PI18000))));
344     }
345     bAngleSnap=pView!=nullptr && pView->IsAngleSnapEnabled();
346     if (bAngleSnap) {
347         long nSA=pView->GetSnapAngle();
348         if (nSA!=0) { // angle snapping
349             bool bNeg=nCircRelAngle<0;
350             if (bNeg) nCircRelAngle=-nCircRelAngle;
351             nCircRelAngle+=nSA/2;
352             nCircRelAngle/=nSA;
353             nCircRelAngle*=nSA;
354             nCircRelAngle=NormAngle36000(nCircRelAngle);
355             if (bNeg) nCircRelAngle=-nCircRelAngle;
356         }
357     }
358     nCircRadius=nRad;
359     if (nRad==0 || std::abs(nCircRelAngle)<5) bRet=false;
360     bCircle=bRet;
361 }
362 
363 XPolygon ImpPathCreateUser::GetCirclePoly() const
364 {
365     if (nCircRelAngle>=0) {
366         XPolygon aXP(aCircCenter,nCircRadius,nCircRadius,
367                      sal_uInt16((nCircStAngle+5)/10),sal_uInt16((nCircStAngle+nCircRelAngle+5)/10),false);
368         aXP[0]=aCircStart; aXP.SetFlags(0,PolyFlags::Smooth);
369         if (!bAngleSnap) aXP[aXP.GetPointCount()-1]=aCircEnd;
370         return aXP;
371     } else {
372         XPolygon aXP(aCircCenter,nCircRadius,nCircRadius,
373                      sal_uInt16(NormAngle36000(nCircStAngle+nCircRelAngle+5)/10),sal_uInt16((nCircStAngle+5)/10),false);
374         sal_uInt16 nCount=aXP.GetPointCount();
375         for (sal_uInt16 nNum=nCount/2; nNum>0;) {
376             nNum--; // reverse XPoly's order of points
377             sal_uInt16 n2=nCount-nNum-1;
378             Point aPt(aXP[nNum]);
379             aXP[nNum]=aXP[n2];
380             aXP[n2]=aPt;
381         }
382         aXP[0]=aCircStart; aXP.SetFlags(0,PolyFlags::Smooth);
383         if (!bAngleSnap) aXP[aXP.GetPointCount()-1]=aCircEnd;
384         return aXP;
385     }
386 }
387 
388 Point ImpPathCreateUser::CalcLine(const Point& aCsr, long nDirX, long nDirY, SdrView const * pView)
389 {
390     long x=aCsr.X();
391     long y=aCsr.Y();
392     bool bHLin=nDirY==0;
393     bool bVLin=nDirX==0;
394     if (bHLin) y=0;
395     else if (bVLin) x=0;
396     else {
397         long x1=BigMulDiv(y,nDirX,nDirY);
398         long y1=y;
399         long x2=x;
400         long y2=BigMulDiv(x,nDirY,nDirX);
401         long l1=std::abs(x1)+std::abs(y1);
402         long l2=std::abs(x2)+std::abs(y2);
403         if ((l1<=l2) != (pView!=nullptr && pView->IsBigOrtho())) {
404             x=x1; y=y1;
405         } else {
406             x=x2; y=y2;
407         }
408     }
409     return Point(x,y);
410 }
411 
412 void ImpPathCreateUser::CalcLine(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView)
413 {
414     aLineStart=rP1;
415     aLineEnd=rP2;
416     bLine90=false;
417     if (rP1==rP2 || (rDir.X()==0 && rDir.Y()==0)) { bLine=false; return; }
418     Point aTmpPt(rP2-rP1);
419     long nDirX=rDir.X();
420     long nDirY=rDir.Y();
421     Point aP1(CalcLine(aTmpPt, nDirX, nDirY,pView)); aP1-=aTmpPt; long nQ1=std::abs(aP1.X())+std::abs(aP1.Y());
422     Point aP2(CalcLine(aTmpPt, nDirY,-nDirX,pView)); aP2-=aTmpPt; long nQ2=std::abs(aP2.X())+std::abs(aP2.Y());
423     if (pView!=nullptr && pView->IsOrtho()) nQ1=0; // Ortho turns off at right angle
424     bLine90=nQ1>2*nQ2;
425     if (!bLine90) { // smooth transition
426         aLineEnd+=aP1;
427     } else {          // rectangular transition
428         aLineEnd+=aP2;
429     }
430     bLine=true;
431 }
432 
433 XPolygon ImpPathCreateUser::GetLinePoly() const
434 {
435     XPolygon aXP(2);
436     aXP[0]=aLineStart; if (!bLine90) aXP.SetFlags(0,PolyFlags::Smooth);
437     aXP[1]=aLineEnd;
438     return aXP;
439 }
440 
441 void ImpPathCreateUser::CalcRect(const Point& rP1, const Point& rP2, const Point& rDir, SdrView const * pView)
442 {
443     aRectP1=rP1;
444     aRectP2=rP1;
445     aRectP3=rP2;
446     if (rP1==rP2 || (rDir.X()==0 && rDir.Y()==0)) { bRect=false; return; }
447     Point aTmpPt(rP2-rP1);
448     long nDirX=rDir.X();
449     long nDirY=rDir.Y();
450     long x=aTmpPt.X();
451     long y=aTmpPt.Y();
452     bool bHLin=nDirY==0;
453     bool bVLin=nDirX==0;
454     if (bHLin) y=0;
455     else if (bVLin) x=0;
456     else {
457         y=BigMulDiv(x,nDirY,nDirX);
458         long nHypLen=aTmpPt.Y()-y;
459         long nTangAngle=-GetAngle(rDir);
460         // sin=g/h, g=h*sin
461         double a = nTangAngle * F_PI18000;
462         double sn=sin(a);
463         double cs=cos(a);
464         double nGKathLen=nHypLen*sn;
465         y+=FRound(nGKathLen*sn);
466         x+=FRound(nGKathLen*cs);
467     }
468     aRectP2.AdjustX(x );
469     aRectP2.AdjustY(y );
470     if (pView!=nullptr && pView->IsOrtho()) {
471         long dx1=aRectP2.X()-aRectP1.X(); long dx1a=std::abs(dx1);
472         long dy1=aRectP2.Y()-aRectP1.Y(); long dy1a=std::abs(dy1);
473         long dx2=aRectP3.X()-aRectP2.X(); long dx2a=std::abs(dx2);
474         long dy2=aRectP3.Y()-aRectP2.Y(); long dy2a=std::abs(dy2);
475         bool b1MoreThan2=dx1a+dy1a>dx2a+dy2a;
476         if (b1MoreThan2 != pView->IsBigOrtho()) {
477             long xtemp=dy2a-dx1a; if (dx1<0) xtemp=-xtemp;
478             long ytemp=dx2a-dy1a; if (dy1<0) ytemp=-ytemp;
479             aRectP2.AdjustX(xtemp );
480             aRectP2.AdjustY(ytemp );
481             aRectP3.AdjustX(xtemp );
482             aRectP3.AdjustY(ytemp );
483         } else {
484             long xtemp=dy1a-dx2a; if (dx2<0) xtemp=-xtemp;
485             long ytemp=dx1a-dy2a; if (dy2<0) ytemp=-ytemp;
486             aRectP3.AdjustX(xtemp );
487             aRectP3.AdjustY(ytemp );
488         }
489     }
490     bRect=true;
491 }
492 
493 XPolygon ImpPathCreateUser::GetRectPoly() const
494 {
495     XPolygon aXP(3);
496     aXP[0]=aRectP1; aXP.SetFlags(0,PolyFlags::Smooth);
497     aXP[1]=aRectP2;
498     if (aRectP3!=aRectP2) aXP[2]=aRectP3;
499     return aXP;
500 }
501 
502 class ImpPathForDragAndCreate
503 {
504     SdrPathObj&                 mrSdrPathObject;
505     XPolyPolygon                aPathPolygon;
506     SdrObjKind const            meObjectKind;
507     std::unique_ptr<ImpSdrPathDragData>
508                                 mpSdrPathDragData;
509     bool                        mbCreating;
510 
511 public:
512     explicit ImpPathForDragAndCreate(SdrPathObj& rSdrPathObject);
513 
514     // drag stuff
515     bool beginPathDrag( SdrDragStat const & rDrag )  const;
516     bool movePathDrag( SdrDragStat& rDrag ) const;
517     bool endPathDrag( SdrDragStat const & rDrag );
518     OUString getSpecialDragComment(const SdrDragStat& rDrag) const;
519     basegfx::B2DPolyPolygon getSpecialDragPoly(const SdrDragStat& rDrag) const;
520 
521     // create stuff
522     void BegCreate(SdrDragStat& rStat);
523     bool MovCreate(SdrDragStat& rStat);
524     bool EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd);
525     bool BckCreate(SdrDragStat const & rStat);
526     void BrkCreate(SdrDragStat& rStat);
527     PointerStyle GetCreatePointer() const;
528 
529     // helping stuff
530     static bool IsClosed(SdrObjKind eKind) { return eKind==OBJ_POLY || eKind==OBJ_PATHPOLY || eKind==OBJ_PATHFILL || eKind==OBJ_FREEFILL || eKind==OBJ_SPLNFILL; }
531     static bool IsFreeHand(SdrObjKind eKind) { return eKind==OBJ_FREELINE || eKind==OBJ_FREEFILL; }
532     static bool IsBezier(SdrObjKind eKind) { return eKind==OBJ_PATHLINE || eKind==OBJ_PATHFILL; }
533     bool IsCreating() const { return mbCreating; }
534 
535     // get the polygon
536     basegfx::B2DPolyPolygon TakeObjectPolyPolygon(const SdrDragStat& rDrag) const;
537     static basegfx::B2DPolyPolygon TakeDragPolyPolygon(const SdrDragStat& rDrag);
538     basegfx::B2DPolyPolygon getModifiedPolyPolygon() const { return  aPathPolygon.getB2DPolyPolygon(); }
539 };
540 
541 ImpPathForDragAndCreate::ImpPathForDragAndCreate(SdrPathObj& rSdrPathObject)
542 :   mrSdrPathObject(rSdrPathObject),
543     aPathPolygon(rSdrPathObject.GetPathPoly()),
544     meObjectKind(mrSdrPathObject.meKind),
545     mbCreating(false)
546 {
547 }
548 
549 bool ImpPathForDragAndCreate::beginPathDrag( SdrDragStat const & rDrag )  const
550 {
551     const SdrHdl* pHdl=rDrag.GetHdl();
552     if(!pHdl)
553         return false;
554 
555     bool bMultiPointDrag(true);
556 
557     if(aPathPolygon[static_cast<sal_uInt16>(pHdl->GetPolyNum())].IsControl(static_cast<sal_uInt16>(pHdl->GetPointNum())))
558         bMultiPointDrag = false;
559 
560     if(bMultiPointDrag)
561     {
562         const SdrMarkView& rMarkView = *rDrag.GetView();
563         const SdrHdlList& rHdlList = rMarkView.GetHdlList();
564         const size_t nHdlCount = rHdlList.GetHdlCount();
565         const SdrObject* pInteractionObject(nHdlCount && rHdlList.GetHdl(0) ? rHdlList.GetHdl(0)->GetObj() : nullptr);
566         sal_uInt32 nSelectedPoints(0);
567 
568         for(size_t a = 0; a < nHdlCount; ++a)
569         {
570             SdrHdl* pTestHdl = rHdlList.GetHdl(a);
571 
572             if(pTestHdl && pTestHdl->IsSelected() && pTestHdl->GetObj() == pInteractionObject)
573             {
574                 nSelectedPoints++;
575             }
576         }
577 
578         if(nSelectedPoints <= 1)
579             bMultiPointDrag = false;
580     }
581 
582     const_cast<ImpPathForDragAndCreate*>(this)->mpSdrPathDragData.reset( new ImpSdrPathDragData(mrSdrPathObject,*pHdl,bMultiPointDrag,rDrag) );
583 
584     if(!mpSdrPathDragData || !mpSdrPathDragData->bValid)
585     {
586         OSL_FAIL("ImpPathForDragAndCreate::BegDrag(): ImpSdrPathDragData is invalid.");
587         const_cast<ImpPathForDragAndCreate*>(this)->mpSdrPathDragData.reset();
588         return false;
589     }
590 
591     return true;
592 }
593 
594 bool ImpPathForDragAndCreate::movePathDrag( SdrDragStat& rDrag ) const
595 {
596     if(!mpSdrPathDragData || !mpSdrPathDragData->bValid)
597     {
598         OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid.");
599         return false;
600     }
601 
602     if(mpSdrPathDragData->IsMultiPointDrag())
603     {
604         Point aDelta(rDrag.GetNow() - rDrag.GetStart());
605 
606         if(aDelta.X() || aDelta.Y())
607         {
608             for(SdrHdl* pHandle : mpSdrPathDragData->maHandles)
609             {
610                 const sal_uInt16 nPolyIndex(static_cast<sal_uInt16>(pHandle->GetPolyNum()));
611                 const sal_uInt16 nPointIndex(static_cast<sal_uInt16>(pHandle->GetPointNum()));
612                 const XPolygon& rOrig = mpSdrPathDragData->maOrig[nPolyIndex];
613                 XPolygon& rMove = mpSdrPathDragData->maMove[nPolyIndex];
614                 const sal_uInt16 nPointCount(rOrig.GetPointCount());
615                 bool bClosed(rOrig[0] == rOrig[nPointCount-1]);
616 
617                 // move point itself
618                 rMove[nPointIndex] = rOrig[nPointIndex] + aDelta;
619 
620                 // when point is first and poly closed, move close point, too.
621                 if(nPointCount > 0 && !nPointIndex && bClosed)
622                 {
623                     rMove[nPointCount - 1] = rOrig[nPointCount - 1] + aDelta;
624 
625                     // when moving the last point it may be necessary to move the
626                     // control point in front of this one, too.
627                     if(nPointCount > 1 && rOrig.IsControl(nPointCount - 2))
628                         rMove[nPointCount - 2] = rOrig[nPointCount - 2] + aDelta;
629                 }
630 
631                 // is a control point before this?
632                 if(nPointIndex > 0 && rOrig.IsControl(nPointIndex - 1))
633                 {
634                     // Yes, move it, too
635                     rMove[nPointIndex - 1] = rOrig[nPointIndex - 1] + aDelta;
636                 }
637 
638                 // is a control point after this?
639                 if(nPointIndex + 1 < nPointCount && rOrig.IsControl(nPointIndex + 1))
640                 {
641                     // Yes, move it, too
642                     rMove[nPointIndex + 1] = rOrig[nPointIndex + 1] + aDelta;
643                 }
644             }
645         }
646     }
647     else
648     {
649         mpSdrPathDragData->ResetPoly(mrSdrPathObject);
650 
651         // copy certain data locally to use less code and have faster access times
652         bool bClosed           =mpSdrPathDragData->bClosed       ; // closed object?
653         sal_uInt16   nPnt          =mpSdrPathDragData->nPnt          ; // number of point in the above polygon
654         bool bBegPnt           =mpSdrPathDragData->bBegPnt       ; // dragged point is first point of a Polyline
655         bool bEndPnt           =mpSdrPathDragData->bEndPnt       ; // dragged point is last point of a Polyline
656         sal_uInt16   nPrevPnt      =mpSdrPathDragData->nPrevPnt      ; // index of previous point
657         sal_uInt16   nNextPnt      =mpSdrPathDragData->nNextPnt      ; // index of next point
658         bool bPrevIsBegPnt     =mpSdrPathDragData->bPrevIsBegPnt ; // previous point is first point of a Polyline
659         bool bNextIsEndPnt     =mpSdrPathDragData->bNextIsEndPnt ; // next point is last point of a Polyline
660         sal_uInt16   nPrevPrevPnt  =mpSdrPathDragData->nPrevPrevPnt  ; // index of the point before the previous point
661         sal_uInt16   nNextNextPnt  =mpSdrPathDragData->nNextNextPnt  ; // index if the point after the next point
662         bool bControl          =mpSdrPathDragData->bControl      ; // point is a control point
663         bool bIsNextControl    =mpSdrPathDragData->bIsNextControl; // point is a control point after a support point
664         bool bPrevIsControl    =mpSdrPathDragData->bPrevIsControl; // if nPnt is a support point: there's a control point before
665         bool bNextIsControl    =mpSdrPathDragData->bNextIsControl; // if nPnt is a support point: there's a control point after
666 
667         // Ortho for lines/polygons: keep angle
668         if (!bControl && rDrag.GetView()!=nullptr && rDrag.GetView()->IsOrtho()) {
669             bool bBigOrtho=rDrag.GetView()->IsBigOrtho();
670             Point  aPos(rDrag.GetNow());      // current position
671             Point  aPnt(mpSdrPathDragData->aXP[nPnt]);      // the dragged point
672             sal_uInt16 nPnt1=0xFFFF,nPnt2=0xFFFF; // its neighboring points
673             Point  aNewPos1,aNewPos2;         // new alternative for aPos
674             bool bPnt1 = false, bPnt2 = false; // are these valid alternatives?
675             if (!bClosed && mpSdrPathDragData->nPointCount>=2) { // minimum of 2 points for lines
676                 if (!bBegPnt) nPnt1=nPrevPnt;
677                 if (!bEndPnt) nPnt2=nNextPnt;
678             }
679             if (bClosed && mpSdrPathDragData->nPointCount>=3) { // minimum of 3 points for polygon
680                 nPnt1=nPrevPnt;
681                 nPnt2=nNextPnt;
682             }
683             if (nPnt1!=0xFFFF && !bPrevIsControl) {
684                 Point aPnt1=mpSdrPathDragData->aXP[nPnt1];
685                 long ndx0=aPnt.X()-aPnt1.X();
686                 long ndy0=aPnt.Y()-aPnt1.Y();
687                 bool bHLin=ndy0==0;
688                 bool bVLin=ndx0==0;
689                 if (!bHLin || !bVLin) {
690                     long ndx=aPos.X()-aPnt1.X();
691                     long ndy=aPos.Y()-aPnt1.Y();
692                     bPnt1=true;
693                     double nXFact=0; if (!bVLin) nXFact=static_cast<double>(ndx)/static_cast<double>(ndx0);
694                     double nYFact=0; if (!bHLin) nYFact=static_cast<double>(ndy)/static_cast<double>(ndy0);
695                     bool bHor=bHLin || (!bVLin && (nXFact>nYFact) ==bBigOrtho);
696                     bool bVer=bVLin || (!bHLin && (nXFact<=nYFact)==bBigOrtho);
697                     if (bHor) ndy=long(ndy0*nXFact);
698                     if (bVer) ndx=long(ndx0*nYFact);
699                     aNewPos1=aPnt1;
700                     aNewPos1.AdjustX(ndx );
701                     aNewPos1.AdjustY(ndy );
702                 }
703             }
704             if (nPnt2!=0xFFFF && !bNextIsControl) {
705                 Point aPnt2=mpSdrPathDragData->aXP[nPnt2];
706                 long ndx0=aPnt.X()-aPnt2.X();
707                 long ndy0=aPnt.Y()-aPnt2.Y();
708                 bool bHLin=ndy0==0;
709                 bool bVLin=ndx0==0;
710                 if (!bHLin || !bVLin) {
711                     long ndx=aPos.X()-aPnt2.X();
712                     long ndy=aPos.Y()-aPnt2.Y();
713                     bPnt2=true;
714                     double nXFact=0; if (!bVLin) nXFact=static_cast<double>(ndx)/static_cast<double>(ndx0);
715                     double nYFact=0; if (!bHLin) nYFact=static_cast<double>(ndy)/static_cast<double>(ndy0);
716                     bool bHor=bHLin || (!bVLin && (nXFact>nYFact) ==bBigOrtho);
717                     bool bVer=bVLin || (!bHLin && (nXFact<=nYFact)==bBigOrtho);
718                     if (bHor) ndy=long(ndy0*nXFact);
719                     if (bVer) ndx=long(ndx0*nYFact);
720                     aNewPos2=aPnt2;
721                     aNewPos2.AdjustX(ndx );
722                     aNewPos2.AdjustY(ndy );
723                 }
724             }
725             if (bPnt1 && bPnt2) { // both alternatives exist (and compete)
726                 BigInt nX1(aNewPos1.X()-aPos.X()); nX1*=nX1;
727                 BigInt nY1(aNewPos1.Y()-aPos.Y()); nY1*=nY1;
728                 BigInt nX2(aNewPos2.X()-aPos.X()); nX2*=nX2;
729                 BigInt nY2(aNewPos2.Y()-aPos.Y()); nY2*=nY2;
730                 nX1+=nY1; // correction distance to square
731                 nX2+=nY2; // correction distance to square
732                 // let the alternative that allows fewer correction win
733                 if (nX1<nX2) bPnt2=false; else bPnt1=false;
734             }
735             if (bPnt1) rDrag.SetNow(aNewPos1);
736             if (bPnt2) rDrag.SetNow(aNewPos2);
737         }
738         rDrag.SetActionRect(tools::Rectangle(rDrag.GetNow(),rDrag.GetNow()));
739 
740         // specially for IBM: Eliminate points if both adjoining lines form near 180 degrees angle anyway
741         if (!bControl && rDrag.GetView()!=nullptr && rDrag.GetView()->IsEliminatePolyPoints() &&
742             !bBegPnt && !bEndPnt && !bPrevIsControl && !bNextIsControl)
743         {
744             Point aPt(mpSdrPathDragData->aXP[nNextPnt]);
745             aPt-=rDrag.GetNow();
746             long nAngle1=GetAngle(aPt);
747             aPt=rDrag.GetNow();
748             aPt-=mpSdrPathDragData->aXP[nPrevPnt];
749             long nAngle2=GetAngle(aPt);
750             long nDiff=nAngle1-nAngle2;
751             nDiff=std::abs(nDiff);
752             mpSdrPathDragData->bEliminate=nDiff<=rDrag.GetView()->GetEliminatePolyPointLimitAngle();
753             if (mpSdrPathDragData->bEliminate) { // adapt position, Smooth is true for the ends
754                 aPt=mpSdrPathDragData->aXP[nNextPnt];
755                 aPt+=mpSdrPathDragData->aXP[nPrevPnt];
756                 aPt/=2;
757                 rDrag.SetNow(aPt);
758             }
759         }
760 
761         // we dragged by this distance
762         Point aDiff(rDrag.GetNow()); aDiff-=mpSdrPathDragData->aXP[nPnt];
763 
764         /* There are 8 possible cases:
765               X      1. A control point neither on the left nor on the right.
766            o--X--o   2. There are control points on the left and the right, we are dragging a support point.
767            o--X      3. There is a control point on the left, we are dragging a support point.
768               X--o   4. There is a control point on the right, we are dragging a support point.
769            x--O--o   5. There are control points on the left and the right, we are dragging the left one.
770            x--O      6. There is a control point on the left, we are dragging it.
771            o--O--x   7. There are control points on the left and the right, we are dragging the right one.
772               O--x   8. There is a control point on the right, we are dragging it.
773            Note: modifying a line (not a curve!) might create a curve on the other end of the line
774            if Smooth is set there (with control points aligned to line).
775         */
776 
777         mpSdrPathDragData->aXP[nPnt]+=aDiff;
778 
779         // now check symmetric plus handles
780         if (bControl) { // cases 5,6,7,8
781             sal_uInt16   nSt; // the associated support point
782             sal_uInt16   nFix;  // the opposing control point
783             if (bIsNextControl) { // if the next one is a control point, the on before has to be a support point
784                 nSt=nPrevPnt;
785                 nFix=nPrevPrevPnt;
786             } else {
787                 nSt=nNextPnt;
788                 nFix=nNextNextPnt;
789             }
790             if (mpSdrPathDragData->aXP.IsSmooth(nSt)) {
791                 mpSdrPathDragData->aXP.CalcSmoothJoin(nSt,nPnt,nFix);
792             }
793         }
794 
795         if (!bControl) { // Cases 1,2,3,4. In case 1, nothing happens; in cases 3 and 4, there is more following below.
796             // move both control points
797             if (bPrevIsControl) mpSdrPathDragData->aXP[nPrevPnt]+=aDiff;
798             if (bNextIsControl) mpSdrPathDragData->aXP[nNextPnt]+=aDiff;
799             // align control point to line, if appropriate
800             if (mpSdrPathDragData->aXP.IsSmooth(nPnt)) {
801                 if (bPrevIsControl && !bNextIsControl && !bEndPnt) { // case 3
802                     mpSdrPathDragData->aXP.CalcSmoothJoin(nPnt,nNextPnt,nPrevPnt);
803                 }
804                 if (bNextIsControl && !bPrevIsControl && !bBegPnt) { // case 4
805                     mpSdrPathDragData->aXP.CalcSmoothJoin(nPnt,nPrevPnt,nNextPnt);
806                 }
807             }
808             // Now check the other ends of the line (nPnt+-1). If there is a
809             // curve (IsControl(nPnt+-2)) with SmoothJoin (nPnt+-1), the
810             // associated control point (nPnt+-2) has to be adapted.
811             if (!bBegPnt && !bPrevIsControl && !bPrevIsBegPnt && mpSdrPathDragData->aXP.IsSmooth(nPrevPnt)) {
812                 if (mpSdrPathDragData->aXP.IsControl(nPrevPrevPnt)) {
813                     mpSdrPathDragData->aXP.CalcSmoothJoin(nPrevPnt,nPnt,nPrevPrevPnt);
814                 }
815             }
816             if (!bEndPnt && !bNextIsControl && !bNextIsEndPnt && mpSdrPathDragData->aXP.IsSmooth(nNextPnt)) {
817                 if (mpSdrPathDragData->aXP.IsControl(nNextNextPnt)) {
818                     mpSdrPathDragData->aXP.CalcSmoothJoin(nNextPnt,nPnt,nNextNextPnt);
819                 }
820             }
821         }
822     }
823 
824     return true;
825 }
826 
827 bool ImpPathForDragAndCreate::endPathDrag(SdrDragStat const & rDrag)
828 {
829     Point aLinePt1;
830     Point aLinePt2;
831     bool bLineGlueMirror(OBJ_LINE == meObjectKind);
832     if (bLineGlueMirror) {
833         XPolygon& rXP=aPathPolygon[0];
834         aLinePt1=rXP[0];
835         aLinePt2=rXP[1];
836     }
837 
838     if(!mpSdrPathDragData || !mpSdrPathDragData->bValid)
839     {
840         OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid.");
841         return false;
842     }
843 
844     if(mpSdrPathDragData->IsMultiPointDrag())
845     {
846         aPathPolygon = mpSdrPathDragData->maMove;
847     }
848     else
849     {
850         const SdrHdl* pHdl=rDrag.GetHdl();
851 
852         // reference the polygon
853         XPolygon& rXP=aPathPolygon[static_cast<sal_uInt16>(pHdl->GetPolyNum())];
854 
855         // the 5 points that might have changed
856         if (!mpSdrPathDragData->bPrevIsBegPnt) rXP[mpSdrPathDragData->nPrevPrevPnt0]=mpSdrPathDragData->aXP[mpSdrPathDragData->nPrevPrevPnt];
857         if (!mpSdrPathDragData->bNextIsEndPnt) rXP[mpSdrPathDragData->nNextNextPnt0]=mpSdrPathDragData->aXP[mpSdrPathDragData->nNextNextPnt];
858         if (!mpSdrPathDragData->bBegPnt)       rXP[mpSdrPathDragData->nPrevPnt0]    =mpSdrPathDragData->aXP[mpSdrPathDragData->nPrevPnt];
859         if (!mpSdrPathDragData->bEndPnt)       rXP[mpSdrPathDragData->nNextPnt0]    =mpSdrPathDragData->aXP[mpSdrPathDragData->nNextPnt];
860         rXP[mpSdrPathDragData->nPnt0]        =mpSdrPathDragData->aXP[mpSdrPathDragData->nPnt];
861 
862         // for closed objects: last point has to be equal to first point
863         if (mpSdrPathDragData->bClosed) rXP[rXP.GetPointCount()-1]=rXP[0];
864 
865         if (mpSdrPathDragData->bEliminate)
866         {
867             basegfx::B2DPolyPolygon aTempPolyPolygon(aPathPolygon.getB2DPolyPolygon());
868             sal_uInt32 nPoly,nPnt;
869 
870             if(PolyPolygonEditor::GetRelativePolyPoint(aTempPolyPolygon, rDrag.GetHdl()->GetSourceHdlNum(), nPoly, nPnt))
871             {
872                 basegfx::B2DPolygon aCandidate(aTempPolyPolygon.getB2DPolygon(nPoly));
873                 aCandidate.remove(nPnt);
874 
875                 if(aCandidate.count() < 2)
876                 {
877                     aTempPolyPolygon.remove(nPoly);
878                 }
879                 else
880                 {
881                     aTempPolyPolygon.setB2DPolygon(nPoly, aCandidate);
882                 }
883             }
884 
885             aPathPolygon = XPolyPolygon(aTempPolyPolygon);
886         }
887 
888         // adapt angle for text beneath a simple line
889         if (bLineGlueMirror)
890         {
891             Point aLinePt1_(aPathPolygon[0][0]);
892             Point aLinePt2_(aPathPolygon[0][1]);
893             bool bXMirr=(aLinePt1_.X()>aLinePt2_.X())!=(aLinePt1.X()>aLinePt2.X());
894             bool bYMirr=(aLinePt1_.Y()>aLinePt2_.Y())!=(aLinePt1.Y()>aLinePt2.Y());
895             if (bXMirr || bYMirr) {
896                 Point aRef1(mrSdrPathObject.GetSnapRect().Center());
897                 if (bXMirr) {
898                     Point aRef2(aRef1);
899                     aRef2.AdjustY( 1 );
900                     mrSdrPathObject.NbcMirrorGluePoints(aRef1,aRef2);
901                 }
902                 if (bYMirr) {
903                     Point aRef2(aRef1);
904                     aRef2.AdjustX( 1 );
905                     mrSdrPathObject.NbcMirrorGluePoints(aRef1,aRef2);
906                 }
907             }
908         }
909     }
910 
911     mpSdrPathDragData.reset();
912 
913     return true;
914 }
915 
916 OUString ImpPathForDragAndCreate::getSpecialDragComment(const SdrDragStat& rDrag) const
917 {
918     OUString aStr;
919     const SdrHdl* pHdl = rDrag.GetHdl();
920     const bool bCreateComment(rDrag.GetView() && &mrSdrPathObject == rDrag.GetView()->GetCreateObj());
921 
922     if(bCreateComment && rDrag.GetUser())
923     {
924         // #i103058# re-add old creation comment mode
925         const ImpPathCreateUser* pU = static_cast<const ImpPathCreateUser*>(rDrag.GetUser());
926         const SdrObjKind eOriginalKind(meObjectKind);
927         mrSdrPathObject.meKind = pU->eCurrentKind;
928         aStr = mrSdrPathObject.ImpGetDescriptionStr(STR_ViewCreateObj);
929         mrSdrPathObject.meKind = eOriginalKind;
930 
931         Point aPrev(rDrag.GetPrev());
932         Point aNow(rDrag.GetNow());
933 
934         if(pU->bLine)
935             aNow = pU->aLineEnd;
936 
937         aNow -= aPrev;
938         aStr += " (";
939 
940         if(pU->bCircle)
941         {
942             aStr += SdrModel::GetAngleString(std::abs(pU->nCircRelAngle))
943                     + " r="
944                     + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(pU->nCircRadius, true);
945         }
946 
947         aStr += "dx="
948                 + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.X(), true)
949                 + " dy="
950                 + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.Y(), true);
951 
952         if(!IsFreeHand(meObjectKind))
953         {
954             sal_Int32 nLen(GetLen(aNow));
955             sal_Int32 nAngle(GetAngle(aNow));
956             aStr += "  l="
957                     + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true)
958                     + " "
959                     + SdrModel::GetAngleString(nAngle);
960         }
961 
962         aStr += ")";
963     }
964     else if(!pHdl)
965     {
966         // #i103058# fallback when no model and/or Handle, both needed
967         // for else-path
968         aStr = mrSdrPathObject.ImpGetDescriptionStr(STR_DragPathObj);
969     }
970     else
971     {
972         // #i103058# standard for modification; model and handle needed
973         ImpSdrPathDragData* pDragData = mpSdrPathDragData.get();
974 
975         if(!pDragData)
976         {
977             // getSpecialDragComment is also used from create, so fallback to GetUser()
978             // when mpSdrPathDragData is not set
979             pDragData = static_cast<ImpSdrPathDragData*>(rDrag.GetUser());
980         }
981 
982         if(!pDragData)
983         {
984             OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid.");
985             return OUString();
986         }
987 
988         if(!pDragData->IsMultiPointDrag() && pDragData->bEliminate)
989         {
990             // point of ...
991             aStr = mrSdrPathObject.ImpGetDescriptionStr(STR_ViewMarkedPoint);
992 
993             // delete %O
994             OUString aStr2(SvxResId(STR_EditDelete));
995 
996             // UNICODE: delete point of ...
997             aStr2 = aStr2.replaceFirst("%1", aStr);
998 
999             return aStr2;
1000         }
1001 
1002         // dx=0.00 dy=0.00                -- both sides bezier
1003         // dx=0.00 dy=0.00  l=0.00 0.00\302\260  -- one bezier/lever on one side, a start, or an ending
1004         // dx=0.00 dy=0.00  l=0.00 0.00\302\260 / l=0.00 0.00\302\260 -- in between
1005         Point aBeg(rDrag.GetStart());
1006         Point aNow(rDrag.GetNow());
1007 
1008         aStr.clear();
1009         aStr += "dx="
1010                 + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.X() - aBeg.X(), true)
1011                 + " dy="
1012                 + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(aNow.Y() - aBeg.Y(), true);
1013 
1014         if(!pDragData->IsMultiPointDrag())
1015         {
1016             sal_uInt16 nPntNum(static_cast<sal_uInt16>(pHdl->GetPointNum()));
1017             const XPolygon& rXPoly = aPathPolygon[static_cast<sal_uInt16>(rDrag.GetHdl()->GetPolyNum())];
1018             sal_uInt16 nPointCount(rXPoly.GetPointCount());
1019             bool bClose(IsClosed(meObjectKind));
1020 
1021             if(bClose)
1022                 nPointCount--;
1023 
1024             if(pHdl->IsPlusHdl())
1025             {
1026                 // lever
1027                 sal_uInt16 nRef(nPntNum);
1028 
1029                 if(rXPoly.IsControl(nPntNum + 1))
1030                     nRef--;
1031                 else
1032                     nRef++;
1033 
1034                 aNow -= rXPoly[nRef];
1035 
1036                 sal_Int32 nLen(GetLen(aNow));
1037                 sal_Int32 nAngle(GetAngle(aNow));
1038                 aStr += "  l="
1039                         + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true)
1040                         + " "
1041                         + SdrModel::GetAngleString(nAngle);
1042             }
1043             else if(nPointCount > 1)
1044             {
1045                 sal_uInt16 nPntMax(nPointCount - 1);
1046                 bool bIsClosed(IsClosed(meObjectKind));
1047                 bool bPt1(nPntNum > 0);
1048                 bool bPt2(nPntNum < nPntMax);
1049 
1050                 if(bIsClosed && nPointCount > 2)
1051                 {
1052                     bPt1 = true;
1053                     bPt2 = true;
1054                 }
1055 
1056                 sal_uInt16 nPt1,nPt2;
1057 
1058                 if(nPntNum > 0)
1059                     nPt1 = nPntNum - 1;
1060                 else
1061                     nPt1 = nPntMax;
1062 
1063                 if(nPntNum < nPntMax)
1064                     nPt2 = nPntNum + 1;
1065                 else
1066                     nPt2 = 0;
1067 
1068                 if(bPt1 && rXPoly.IsControl(nPt1))
1069                     bPt1 = false; // don't display
1070 
1071                 if(bPt2 && rXPoly.IsControl(nPt2))
1072                     bPt2 = false; // of bezier data
1073 
1074                 if(bPt1)
1075                 {
1076                     Point aPt(aNow);
1077                     aPt -= rXPoly[nPt1];
1078 
1079                     sal_Int32 nLen(GetLen(aPt));
1080                     sal_Int32 nAngle(GetAngle(aPt));
1081                     aStr += "  l="
1082                             + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true)
1083                             + " "
1084                             + SdrModel::GetAngleString(nAngle);
1085                 }
1086 
1087                 if(bPt2)
1088                 {
1089                     if(bPt1)
1090                         aStr += " / ";
1091                     else
1092                         aStr += "  ";
1093 
1094                     Point aPt(aNow);
1095                     aPt -= rXPoly[nPt2];
1096 
1097                     sal_Int32 nLen(GetLen(aPt));
1098                     sal_Int32 nAngle(GetAngle(aPt));
1099                     aStr += "l="
1100                             + mrSdrPathObject.getSdrModelFromSdrObject().GetMetricString(nLen, true)
1101                             + " "
1102                             + SdrModel::GetAngleString(nAngle);
1103                 }
1104             }
1105         }
1106     }
1107 
1108     return aStr;
1109 }
1110 
1111 basegfx::B2DPolyPolygon ImpPathForDragAndCreate::getSpecialDragPoly(const SdrDragStat& rDrag) const
1112 {
1113     if(!mpSdrPathDragData || !mpSdrPathDragData->bValid)
1114     {
1115         OSL_FAIL("ImpPathForDragAndCreate::MovDrag(): ImpSdrPathDragData is invalid.");
1116         return basegfx::B2DPolyPolygon();
1117     }
1118 
1119     XPolyPolygon aRetval;
1120 
1121     if(mpSdrPathDragData->IsMultiPointDrag())
1122     {
1123         aRetval.Insert(mpSdrPathDragData->maMove);
1124     }
1125     else
1126     {
1127         const XPolygon& rXP=aPathPolygon[static_cast<sal_uInt16>(rDrag.GetHdl()->GetPolyNum())];
1128         if (rXP.GetPointCount()<=2) {
1129             XPolygon aXPoly(rXP);
1130             aXPoly[static_cast<sal_uInt16>(rDrag.GetHdl()->GetPointNum())]=rDrag.GetNow();
1131             aRetval.Insert(std::move(aXPoly));
1132             return aRetval.getB2DPolyPolygon();
1133         }
1134         // copy certain data locally to use less code and have faster access times
1135         bool bClosed           =mpSdrPathDragData->bClosed       ; // closed object?
1136         sal_uInt16 nPointCount = mpSdrPathDragData->nPointCount; // number of points
1137         sal_uInt16   nPnt          =mpSdrPathDragData->nPnt          ; // number of points in the polygon
1138         bool bBegPnt           =mpSdrPathDragData->bBegPnt       ; // dragged point is the first point of a Polyline
1139         bool bEndPnt           =mpSdrPathDragData->bEndPnt       ; // dragged point is the last point of a Polyline
1140         sal_uInt16   nPrevPnt      =mpSdrPathDragData->nPrevPnt      ; // index of the previous point
1141         sal_uInt16   nNextPnt      =mpSdrPathDragData->nNextPnt      ; // index of the next point
1142         bool bPrevIsBegPnt     =mpSdrPathDragData->bPrevIsBegPnt ; // previous point is first point of a Polyline
1143         bool bNextIsEndPnt     =mpSdrPathDragData->bNextIsEndPnt ; // next point is last point of a Polyline
1144         sal_uInt16   nPrevPrevPnt  =mpSdrPathDragData->nPrevPrevPnt  ; // index of the point before the previous point
1145         sal_uInt16   nNextNextPnt  =mpSdrPathDragData->nNextNextPnt  ; // index of the point after the last point
1146         bool bControl          =mpSdrPathDragData->bControl      ; // point is a control point
1147         bool bIsNextControl    =mpSdrPathDragData->bIsNextControl; //point is a control point after a support point
1148         bool bPrevIsControl    =mpSdrPathDragData->bPrevIsControl; // if nPnt is a support point: there's a control point before
1149         bool bNextIsControl    =mpSdrPathDragData->bNextIsControl; // if nPnt is a support point: there's a control point after
1150         XPolygon aXPoly(mpSdrPathDragData->aXP);
1151         XPolygon aLine1(2);
1152         XPolygon aLine2(2);
1153         XPolygon aLine3(2);
1154         XPolygon aLine4(2);
1155         if (bControl) {
1156             aLine1[1]=mpSdrPathDragData->aXP[nPnt];
1157             if (bIsNextControl) { // is this a control point after the support point?
1158                 aLine1[0]=mpSdrPathDragData->aXP[nPrevPnt];
1159                 aLine2[0]=mpSdrPathDragData->aXP[nNextNextPnt];
1160                 aLine2[1]=mpSdrPathDragData->aXP[nNextPnt];
1161                 if (mpSdrPathDragData->aXP.IsSmooth(nPrevPnt) && !bPrevIsBegPnt && mpSdrPathDragData->aXP.IsControl(nPrevPrevPnt)) {
1162                     aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-1],PolyFlags::Control);
1163                     aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-2],PolyFlags::Normal);
1164                     // leverage lines for the opposing curve segment
1165                     aLine3[0]=mpSdrPathDragData->aXP[nPrevPnt];
1166                     aLine3[1]=mpSdrPathDragData->aXP[nPrevPrevPnt];
1167                     aLine4[0]=rXP[mpSdrPathDragData->nPrevPrevPnt0-2];
1168                     aLine4[1]=rXP[mpSdrPathDragData->nPrevPrevPnt0-1];
1169                 } else {
1170                     aXPoly.Remove(0,1);
1171                 }
1172             } else { // else this is a control point before a support point
1173                 aLine1[0]=mpSdrPathDragData->aXP[nNextPnt];
1174                 aLine2[0]=mpSdrPathDragData->aXP[nPrevPrevPnt];
1175                 aLine2[1]=mpSdrPathDragData->aXP[nPrevPnt];
1176                 if (mpSdrPathDragData->aXP.IsSmooth(nNextPnt) && !bNextIsEndPnt && mpSdrPathDragData->aXP.IsControl(nNextNextPnt)) {
1177                     aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+1],PolyFlags::Control);
1178                     aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+2],PolyFlags::Normal);
1179                     // leverage lines for the opposing curve segment
1180                     aLine3[0]=mpSdrPathDragData->aXP[nNextPnt];
1181                     aLine3[1]=mpSdrPathDragData->aXP[nNextNextPnt];
1182                     aLine4[0]=rXP[mpSdrPathDragData->nNextNextPnt0+2];
1183                     aLine4[1]=rXP[mpSdrPathDragData->nNextNextPnt0+1];
1184                 } else {
1185                     aXPoly.Remove(aXPoly.GetPointCount()-1,1);
1186                 }
1187             }
1188         } else { // else is not a control point
1189             if (mpSdrPathDragData->bEliminate) {
1190                 aXPoly.Remove(2,1);
1191             }
1192             if (bPrevIsControl) aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-1],PolyFlags::Normal);
1193             else if (!bBegPnt && !bPrevIsBegPnt && mpSdrPathDragData->aXP.IsControl(nPrevPrevPnt)) {
1194                 aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-1],PolyFlags::Control);
1195                 aXPoly.Insert(0,rXP[mpSdrPathDragData->nPrevPrevPnt0-2],PolyFlags::Normal);
1196             } else {
1197                 aXPoly.Remove(0,1);
1198                 if (bBegPnt) aXPoly.Remove(0,1);
1199             }
1200             if (bNextIsControl) aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+1],PolyFlags::Normal);
1201             else if (!bEndPnt && !bNextIsEndPnt && mpSdrPathDragData->aXP.IsControl(nNextNextPnt)) {
1202                 aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+1],PolyFlags::Control);
1203                 aXPoly.Insert(XPOLY_APPEND,rXP[mpSdrPathDragData->nNextNextPnt0+2],PolyFlags::Normal);
1204             } else {
1205                 aXPoly.Remove(aXPoly.GetPointCount()-1,1);
1206                 if (bEndPnt) aXPoly.Remove(aXPoly.GetPointCount()-1,1);
1207             }
1208             if (bClosed) { // "pear problem": 2 lines, 1 curve, everything smoothed, a point between both lines is dragged
1209                 if (aXPoly.GetPointCount()>nPointCount && aXPoly.IsControl(1)) {
1210                     sal_uInt16 a=aXPoly.GetPointCount();
1211                     aXPoly[a-2]=aXPoly[2]; aXPoly.SetFlags(a-2,aXPoly.GetFlags(2));
1212                     aXPoly[a-1]=aXPoly[3]; aXPoly.SetFlags(a-1,aXPoly.GetFlags(3));
1213                     aXPoly.Remove(0,3);
1214                 }
1215             }
1216         }
1217         aRetval.Insert(std::move(aXPoly));
1218         if (aLine1.GetPointCount()>1) aRetval.Insert(std::move(aLine1));
1219         if (aLine2.GetPointCount()>1) aRetval.Insert(std::move(aLine2));
1220         if (aLine3.GetPointCount()>1) aRetval.Insert(std::move(aLine3));
1221         if (aLine4.GetPointCount()>1) aRetval.Insert(std::move(aLine4));
1222     }
1223 
1224     return aRetval.getB2DPolyPolygon();
1225 }
1226 
1227 void ImpPathForDragAndCreate::BegCreate(SdrDragStat& rStat)
1228 {
1229     bool bFreeHand(IsFreeHand(meObjectKind));
1230     rStat.SetNoSnap(bFreeHand);
1231     rStat.SetOrtho8Possible();
1232     aPathPolygon.Clear();
1233     mbCreating=true;
1234     bool bMakeStartPoint = true;
1235     SdrView* pView=rStat.GetView();
1236     if (pView!=nullptr && pView->IsUseIncompatiblePathCreateInterface() &&
1237         (meObjectKind==OBJ_POLY || meObjectKind==OBJ_PLIN || meObjectKind==OBJ_PATHLINE || meObjectKind==OBJ_PATHFILL)) {
1238         bMakeStartPoint = false;
1239     }
1240     aPathPolygon.Insert(XPolygon());
1241     aPathPolygon[0][0]=rStat.GetStart();
1242     if (bMakeStartPoint) {
1243         aPathPolygon[0][1]=rStat.GetNow();
1244     }
1245     std::unique_ptr<ImpPathCreateUser> pU(new ImpPathCreateUser);
1246     pU->eStartKind=meObjectKind;
1247     pU->eCurrentKind=meObjectKind;
1248     rStat.SetUser(std::move(pU));
1249 }
1250 
1251 bool ImpPathForDragAndCreate::MovCreate(SdrDragStat& rStat)
1252 {
1253     ImpPathCreateUser* pU=static_cast<ImpPathCreateUser*>(rStat.GetUser());
1254     SdrView* pView=rStat.GetView();
1255     XPolygon& rXPoly=aPathPolygon[aPathPolygon.Count()-1];
1256     if (pView!=nullptr && pView->IsCreateMode()) {
1257         // switch to different CreateTool, if appropriate
1258         sal_uInt16 nIdent;
1259         SdrInventor nInvent;
1260         pView->TakeCurrentObj(nIdent,nInvent);
1261         if (nInvent==SdrInventor::Default && pU->eCurrentKind!=static_cast<SdrObjKind>(nIdent)) {
1262             SdrObjKind eNewKind=static_cast<SdrObjKind>(nIdent);
1263             switch (eNewKind) {
1264                 case OBJ_CARC:
1265                 case OBJ_CIRC:
1266                 case OBJ_CCUT:
1267                 case OBJ_SECT:
1268                     eNewKind=OBJ_CARC;
1269                     [[fallthrough]];
1270                 case OBJ_RECT:
1271                 case OBJ_LINE:
1272                 case OBJ_PLIN:
1273                 case OBJ_POLY:
1274                 case OBJ_PATHLINE:
1275                 case OBJ_PATHFILL:
1276                 case OBJ_FREELINE:
1277                 case OBJ_FREEFILL:
1278                 case OBJ_SPLNLINE:
1279                 case OBJ_SPLNFILL: {
1280                     pU->eCurrentKind=eNewKind;
1281                     pU->bMixedCreate=true;
1282                     pU->nBezierStartPoint=rXPoly.GetPointCount();
1283                     if (pU->nBezierStartPoint>0) pU->nBezierStartPoint--;
1284                 } break;
1285                 default: break;
1286             } // switch
1287         }
1288     }
1289     sal_uInt16 nCurrentPoint=rXPoly.GetPointCount();
1290     if (aPathPolygon.Count()>1 && rStat.IsMouseDown() && nCurrentPoint<2) {
1291         rXPoly[0]=rStat.GetPos0();
1292         rXPoly[1]=rStat.GetNow();
1293         nCurrentPoint=2;
1294     }
1295     if (nCurrentPoint==0) {
1296         rXPoly[0]=rStat.GetPos0();
1297     } else nCurrentPoint--;
1298     bool bFreeHand=IsFreeHand(pU->eCurrentKind);
1299     rStat.SetNoSnap(bFreeHand);
1300     rStat.SetOrtho8Possible(pU->eCurrentKind!=OBJ_CARC && pU->eCurrentKind!=OBJ_RECT && (!pU->bMixedCreate || pU->eCurrentKind!=OBJ_LINE));
1301     rXPoly[nCurrentPoint]=rStat.GetNow();
1302     if (!pU->bMixedCreate && pU->eStartKind==OBJ_LINE && rXPoly.GetPointCount()>=1) {
1303         Point aPt(rStat.GetStart());
1304         if (pView!=nullptr && pView->IsCreate1stPointAsCenter()) {
1305             aPt+=aPt;
1306             aPt-=rStat.GetNow();
1307         }
1308         rXPoly[0]=aPt;
1309     }
1310     OutputDevice* pOut=pView==nullptr ? nullptr : pView->GetFirstOutputDevice();
1311     if (bFreeHand) {
1312         if (pU->nBezierStartPoint>nCurrentPoint) pU->nBezierStartPoint=nCurrentPoint;
1313         if (rStat.IsMouseDown() && nCurrentPoint>0) {
1314             // don't allow two consecutive points to occupy too similar positions
1315             long nMinDist=1;
1316             if (pView!=nullptr) nMinDist=pView->GetFreeHandMinDistPix();
1317             if (pOut!=nullptr) nMinDist=pOut->PixelToLogic(Size(nMinDist,0)).Width();
1318             if (nMinDist<1) nMinDist=1;
1319 
1320             Point aPt0(rXPoly[nCurrentPoint-1]);
1321             Point aPt1(rStat.GetNow());
1322             long dx=aPt0.X()-aPt1.X(); if (dx<0) dx=-dx;
1323             long dy=aPt0.Y()-aPt1.Y(); if (dy<0) dy=-dy;
1324             if (dx<nMinDist && dy<nMinDist) return false;
1325 
1326             // TODO: the following is copied from EndCreate (with a few smaller modifications)
1327             // and should be combined into a method with the code there.
1328 
1329             if (nCurrentPoint-pU->nBezierStartPoint>=3 && ((nCurrentPoint-pU->nBezierStartPoint)%3)==0) {
1330                 rXPoly.PointsToBezier(nCurrentPoint-3);
1331                 rXPoly.SetFlags(nCurrentPoint-1,PolyFlags::Control);
1332                 rXPoly.SetFlags(nCurrentPoint-2,PolyFlags::Control);
1333 
1334                 if (nCurrentPoint>=6 && rXPoly.IsControl(nCurrentPoint-4)) {
1335                     rXPoly.CalcTangent(nCurrentPoint-3,nCurrentPoint-4,nCurrentPoint-2);
1336                     rXPoly.SetFlags(nCurrentPoint-3,PolyFlags::Smooth);
1337                 }
1338             }
1339             rXPoly[nCurrentPoint+1]=rStat.GetNow();
1340             rStat.NextPoint();
1341         } else {
1342             pU->nBezierStartPoint=nCurrentPoint;
1343         }
1344     }
1345 
1346     pU->ResetFormFlags();
1347     if (IsBezier(pU->eCurrentKind)) {
1348         if (nCurrentPoint>=2) {
1349             pU->CalcBezier(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],rStat.IsMouseDown());
1350         } else if (pU->bBezHasCtrl0) {
1351             pU->CalcBezier(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],pU->aBezControl0-rXPoly[nCurrentPoint-1],rStat.IsMouseDown());
1352         }
1353     }
1354     if (pU->eCurrentKind==OBJ_CARC && nCurrentPoint>=2) {
1355         pU->CalcCircle(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],pView);
1356     }
1357     if (pU->eCurrentKind==OBJ_LINE && nCurrentPoint>=2) {
1358         pU->CalcLine(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],pView);
1359     }
1360     if (pU->eCurrentKind==OBJ_RECT && nCurrentPoint>=2) {
1361         pU->CalcRect(rXPoly[nCurrentPoint-1],rXPoly[nCurrentPoint],rXPoly[nCurrentPoint-1]-rXPoly[nCurrentPoint-2],pView);
1362     }
1363 
1364     return true;
1365 }
1366 
1367 bool ImpPathForDragAndCreate::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd)
1368 {
1369     ImpPathCreateUser* pU=static_cast<ImpPathCreateUser*>(rStat.GetUser());
1370     bool bRet = false;
1371     SdrView* pView=rStat.GetView();
1372     bool bIncomp=pView!=nullptr && pView->IsUseIncompatiblePathCreateInterface();
1373     XPolygon& rXPoly=aPathPolygon[aPathPolygon.Count()-1];
1374     sal_uInt16 nCurrentPoint=rXPoly.GetPointCount()-1;
1375     rXPoly[nCurrentPoint]=rStat.GetNow();
1376     if (!pU->bMixedCreate && pU->eStartKind==OBJ_LINE) {
1377         if (rStat.GetPointCount()>=2) eCmd=SdrCreateCmd::ForceEnd;
1378         bRet = eCmd==SdrCreateCmd::ForceEnd;
1379         if (bRet) {
1380             mbCreating = false;
1381             rStat.SetUser(nullptr);
1382         }
1383         return bRet;
1384     }
1385 
1386     if (!pU->bMixedCreate && IsFreeHand(pU->eStartKind)) {
1387         if (rStat.GetPointCount()>=2) eCmd=SdrCreateCmd::ForceEnd;
1388         bRet=eCmd==SdrCreateCmd::ForceEnd;
1389         if (bRet) {
1390             mbCreating=false;
1391             rStat.SetUser(nullptr);
1392         }
1393         return bRet;
1394     }
1395     if (eCmd==SdrCreateCmd::NextPoint || eCmd==SdrCreateCmd::NextObject) {
1396         // don't allow two consecutive points to occupy the same position
1397         if (nCurrentPoint==0 || rStat.GetNow()!=rXPoly[nCurrentPoint-1]) {
1398             if (bIncomp) {
1399                 if (pU->nBezierStartPoint>nCurrentPoint) pU->nBezierStartPoint=nCurrentPoint;
1400                 if (IsBezier(pU->eCurrentKind) && nCurrentPoint-pU->nBezierStartPoint>=3 && ((nCurrentPoint-pU->nBezierStartPoint)%3)==0) {
1401                     rXPoly.PointsToBezier(nCurrentPoint-3);
1402                     rXPoly.SetFlags(nCurrentPoint-1,PolyFlags::Control);
1403                     rXPoly.SetFlags(nCurrentPoint-2,PolyFlags::Control);
1404 
1405                     if (nCurrentPoint>=6 && rXPoly.IsControl(nCurrentPoint-4)) {
1406                         rXPoly.CalcTangent(nCurrentPoint-3,nCurrentPoint-4,nCurrentPoint-2);
1407                         rXPoly.SetFlags(nCurrentPoint-3,PolyFlags::Smooth);
1408                     }
1409                 }
1410             } else {
1411                 if (nCurrentPoint==1 && IsBezier(pU->eCurrentKind) && !pU->bBezHasCtrl0) {
1412                     pU->aBezControl0=rStat.GetNow();
1413                     pU->bBezHasCtrl0=true;
1414                     nCurrentPoint--;
1415                 }
1416                 if (pU->IsFormFlag()) {
1417                     sal_uInt16 nPointCount0=rXPoly.GetPointCount();
1418                     rXPoly.Remove(nCurrentPoint-1,2); // remove last two points and replace by form
1419                     rXPoly.Insert(XPOLY_APPEND,pU->GetFormPoly());
1420                     sal_uInt16 nPointCount1=rXPoly.GetPointCount();
1421                     for (sal_uInt16 i=nPointCount0+1; i<nPointCount1-1; i++) { // to make BckAction work
1422                         if (!rXPoly.IsControl(i)) rStat.NextPoint();
1423                     }
1424                     nCurrentPoint=rXPoly.GetPointCount()-1;
1425                 }
1426             }
1427             nCurrentPoint++;
1428             rXPoly[nCurrentPoint]=rStat.GetNow();
1429         }
1430         if (eCmd==SdrCreateCmd::NextObject) {
1431             if (rXPoly.GetPointCount()>=2) {
1432                 pU->bBezHasCtrl0=false;
1433                 // only a singular polygon may be opened, so close this
1434                 rXPoly[nCurrentPoint]=rXPoly[0];
1435                 XPolygon aXP;
1436                 aXP[0]=rStat.GetNow();
1437                 aPathPolygon.Insert(std::move(aXP));
1438             }
1439         }
1440     }
1441 
1442     sal_uInt16 nPolyCount=aPathPolygon.Count();
1443     if (nPolyCount!=0) {
1444         // delete last point, if necessary
1445         if (eCmd==SdrCreateCmd::ForceEnd) {
1446             XPolygon& rXP=aPathPolygon[nPolyCount-1];
1447             sal_uInt16 nPointCount=rXP.GetPointCount();
1448             if (nPointCount>=2) {
1449                 if (!rXP.IsControl(nPointCount-2)) {
1450                     if (rXP[nPointCount-1]==rXP[nPointCount-2]) {
1451                         rXP.Remove(nPointCount-1,1);
1452                     }
1453                 } else {
1454                     if (rXP[nPointCount-3]==rXP[nPointCount-2]) {
1455                         rXP.Remove(nPointCount-3,3);
1456                     }
1457                 }
1458             }
1459         }
1460         for (sal_uInt16 nPolyNum=nPolyCount; nPolyNum>0;) {
1461             nPolyNum--;
1462             XPolygon& rXP=aPathPolygon[nPolyNum];
1463             sal_uInt16 nPointCount=rXP.GetPointCount();
1464             // delete polygons with too few points
1465             if (nPolyNum<nPolyCount-1 || eCmd==SdrCreateCmd::ForceEnd) {
1466                 if (nPointCount<2) aPathPolygon.Remove(nPolyNum);
1467             }
1468         }
1469     }
1470     pU->ResetFormFlags();
1471     bRet=eCmd==SdrCreateCmd::ForceEnd;
1472     if (bRet) {
1473         mbCreating=false;
1474         rStat.SetUser(nullptr);
1475     }
1476     return bRet;
1477 }
1478 
1479 bool ImpPathForDragAndCreate::BckCreate(SdrDragStat const & rStat)
1480 {
1481     ImpPathCreateUser* pU=static_cast<ImpPathCreateUser*>(rStat.GetUser());
1482     if (aPathPolygon.Count()>0) {
1483         XPolygon& rXPoly=aPathPolygon[aPathPolygon.Count()-1];
1484         sal_uInt16 nCurrentPoint=rXPoly.GetPointCount();
1485         if (nCurrentPoint>0) {
1486             nCurrentPoint--;
1487             // make the last part of a bezier curve a line
1488             rXPoly.Remove(nCurrentPoint,1);
1489             if (nCurrentPoint>=3 && rXPoly.IsControl(nCurrentPoint-1)) {
1490                 // there should never be a bezier segment at the end, so this is just in case...
1491                 rXPoly.Remove(nCurrentPoint-1,1);
1492                 if (rXPoly.IsControl(nCurrentPoint-2)) rXPoly.Remove(nCurrentPoint-2,1);
1493             }
1494         }
1495         nCurrentPoint=rXPoly.GetPointCount();
1496         if (nCurrentPoint>=4) { // no bezier segment at the end
1497             nCurrentPoint--;
1498             if (rXPoly.IsControl(nCurrentPoint-1)) {
1499                 rXPoly.Remove(nCurrentPoint-1,1);
1500                 if (rXPoly.IsControl(nCurrentPoint-2)) rXPoly.Remove(nCurrentPoint-2,1);
1501             }
1502         }
1503         if (rXPoly.GetPointCount()<2) {
1504             aPathPolygon.Remove(aPathPolygon.Count()-1);
1505         }
1506         if (aPathPolygon.Count()>0) {
1507             XPolygon& rLocalXPoly=aPathPolygon[aPathPolygon.Count()-1];
1508             sal_uInt16 nLocalCurrentPoint=rLocalXPoly.GetPointCount();
1509             if (nLocalCurrentPoint>0) {
1510                 nLocalCurrentPoint--;
1511                 rLocalXPoly[nLocalCurrentPoint]=rStat.GetNow();
1512             }
1513         }
1514     }
1515     pU->ResetFormFlags();
1516     return aPathPolygon.Count()!=0;
1517 }
1518 
1519 void ImpPathForDragAndCreate::BrkCreate(SdrDragStat& rStat)
1520 {
1521     aPathPolygon.Clear();
1522     mbCreating=false;
1523     rStat.SetUser(nullptr);
1524 }
1525 
1526 basegfx::B2DPolyPolygon ImpPathForDragAndCreate::TakeObjectPolyPolygon(const SdrDragStat& rDrag) const
1527 {
1528     basegfx::B2DPolyPolygon aRetval(aPathPolygon.getB2DPolyPolygon());
1529     SdrView* pView = rDrag.GetView();
1530 
1531     if(pView && pView->IsUseIncompatiblePathCreateInterface())
1532         return aRetval;
1533 
1534     ImpPathCreateUser* pU = static_cast<ImpPathCreateUser*>(rDrag.GetUser());
1535     basegfx::B2DPolygon aNewPolygon(aRetval.count() ? aRetval.getB2DPolygon(aRetval.count() - 1) : basegfx::B2DPolygon());
1536 
1537     if(pU->IsFormFlag() && aNewPolygon.count() > 1)
1538     {
1539         // remove last segment and replace with current
1540         // do not forget to rescue the previous control point which will be lost when
1541         // the point it's associated with is removed
1542         const sal_uInt32 nChangeIndex(aNewPolygon.count() - 2);
1543         const basegfx::B2DPoint aSavedPrevCtrlPoint(aNewPolygon.getPrevControlPoint(nChangeIndex));
1544 
1545         aNewPolygon.remove(nChangeIndex, 2);
1546         aNewPolygon.append(pU->GetFormPoly().getB2DPolygon());
1547 
1548         if(nChangeIndex < aNewPolygon.count())
1549         {
1550             // if really something was added, set the saved previous control point to the
1551             // point where it belongs
1552             aNewPolygon.setPrevControlPoint(nChangeIndex, aSavedPrevCtrlPoint);
1553         }
1554     }
1555 
1556     if(aRetval.count())
1557     {
1558         aRetval.setB2DPolygon(aRetval.count() - 1, aNewPolygon);
1559     }
1560     else
1561     {
1562         aRetval.append(aNewPolygon);
1563     }
1564 
1565     return aRetval;
1566 }
1567 
1568 basegfx::B2DPolyPolygon ImpPathForDragAndCreate::TakeDragPolyPolygon(const SdrDragStat& rDrag)
1569 {
1570     basegfx::B2DPolyPolygon aRetval;
1571     SdrView* pView = rDrag.GetView();
1572 
1573     if(pView && pView->IsUseIncompatiblePathCreateInterface())
1574         return aRetval;
1575 
1576     const ImpPathCreateUser* pU = static_cast<const ImpPathCreateUser*>(rDrag.GetUser());
1577 
1578     if(pU && pU->bBezier && rDrag.IsMouseDown())
1579     {
1580         // no more XOR, no need for complicated helplines
1581         basegfx::B2DPolygon aHelpline;
1582         aHelpline.append(basegfx::B2DPoint(pU->aBezCtrl2.X(), pU->aBezCtrl2.Y()));
1583         aHelpline.append(basegfx::B2DPoint(pU->aBezEnd.X(), pU->aBezEnd.Y()));
1584         aRetval.append(aHelpline);
1585     }
1586 
1587     return aRetval;
1588 }
1589 
1590 PointerStyle ImpPathForDragAndCreate::GetCreatePointer() const
1591 {
1592     switch (meObjectKind) {
1593         case OBJ_LINE    : return PointerStyle::DrawLine;
1594         case OBJ_POLY    : return PointerStyle::DrawPolygon;
1595         case OBJ_PLIN    : return PointerStyle::DrawPolygon;
1596         case OBJ_PATHLINE: return PointerStyle::DrawBezier;
1597         case OBJ_PATHFILL: return PointerStyle::DrawBezier;
1598         case OBJ_FREELINE: return PointerStyle::DrawFreehand;
1599         case OBJ_FREEFILL: return PointerStyle::DrawFreehand;
1600         case OBJ_SPLNLINE: return PointerStyle::DrawFreehand;
1601         case OBJ_SPLNFILL: return PointerStyle::DrawFreehand;
1602         case OBJ_PATHPOLY: return PointerStyle::DrawPolygon;
1603         case OBJ_PATHPLIN: return PointerStyle::DrawPolygon;
1604         default: break;
1605     } // switch
1606     return PointerStyle::Cross;
1607 }
1608 
1609 SdrPathObjGeoData::SdrPathObjGeoData()
1610     : meKind(OBJ_NONE)
1611 {
1612 }
1613 
1614 SdrPathObjGeoData::~SdrPathObjGeoData()
1615 {
1616 }
1617 
1618 // DrawContact section
1619 
1620 std::unique_ptr<sdr::contact::ViewContact> SdrPathObj::CreateObjectSpecificViewContact()
1621 {
1622     return std::make_unique<sdr::contact::ViewContactOfSdrPathObj>(*this);
1623 }
1624 
1625 
1626 SdrPathObj::SdrPathObj(
1627     SdrModel& rSdrModel,
1628     SdrObjKind eNewKind)
1629 :   SdrTextObj(rSdrModel),
1630     meKind(eNewKind)
1631 {
1632     bClosedObj = IsClosed();
1633 }
1634 
1635 SdrPathObj::SdrPathObj(
1636     SdrModel& rSdrModel,
1637     SdrObjKind eNewKind,
1638     const basegfx::B2DPolyPolygon& rPathPoly)
1639 :   SdrTextObj(rSdrModel),
1640     maPathPolygon(rPathPoly),
1641     meKind(eNewKind)
1642 {
1643     bClosedObj = IsClosed();
1644     ImpForceKind();
1645 }
1646 
1647 SdrPathObj::~SdrPathObj() = default;
1648 
1649 static bool lcl_ImpIsLine(const basegfx::B2DPolyPolygon& rPolyPolygon)
1650 {
1651     return (1 == rPolyPolygon.count() && 2 == rPolyPolygon.getB2DPolygon(0).count());
1652 }
1653 
1654 static tools::Rectangle lcl_ImpGetBoundRect(const basegfx::B2DPolyPolygon& rPolyPolygon)
1655 {
1656     basegfx::B2DRange aRange(basegfx::utils::getRange(rPolyPolygon));
1657 
1658     if (aRange.isEmpty())
1659         return tools::Rectangle();
1660 
1661     return tools::Rectangle(
1662         FRound(aRange.getMinX()), FRound(aRange.getMinY()),
1663         FRound(aRange.getMaxX()), FRound(aRange.getMaxY()));
1664 }
1665 
1666 void SdrPathObj::ImpForceLineAngle()
1667 {
1668     if(OBJ_LINE != meKind || !lcl_ImpIsLine(GetPathPoly()))
1669         return;
1670 
1671     const basegfx::B2DPolygon aPoly(GetPathPoly().getB2DPolygon(0));
1672     const basegfx::B2DPoint aB2DPoint0(aPoly.getB2DPoint(0));
1673     const basegfx::B2DPoint aB2DPoint1(aPoly.getB2DPoint(1));
1674     const Point aPoint0(FRound(aB2DPoint0.getX()), FRound(aB2DPoint0.getY()));
1675     const Point aPoint1(FRound(aB2DPoint1.getX()), FRound(aB2DPoint1.getY()));
1676     const basegfx::B2DPoint aB2DDelt(aB2DPoint1 - aB2DPoint0);
1677     const Point aDelt(FRound(aB2DDelt.getX()), FRound(aB2DDelt.getY()));
1678 
1679     aGeo.nRotationAngle=GetAngle(aDelt);
1680     aGeo.nShearAngle=0;
1681     aGeo.RecalcSinCos();
1682     aGeo.RecalcTan();
1683 
1684     // for SdrTextObj, keep aRect up to date
1685     maRect = tools::Rectangle(aPoint0, aPoint1);
1686     maRect.Justify();
1687 }
1688 
1689 void SdrPathObj::ImpForceKind()
1690 {
1691     if (meKind==OBJ_PATHPLIN) meKind=OBJ_PLIN;
1692     if (meKind==OBJ_PATHPOLY) meKind=OBJ_POLY;
1693 
1694     if(GetPathPoly().areControlPointsUsed())
1695     {
1696         switch (meKind)
1697         {
1698             case OBJ_LINE: meKind=OBJ_PATHLINE; break;
1699             case OBJ_PLIN: meKind=OBJ_PATHLINE; break;
1700             case OBJ_POLY: meKind=OBJ_PATHFILL; break;
1701             default: break;
1702         }
1703     }
1704     else
1705     {
1706         switch (meKind)
1707         {
1708             case OBJ_PATHLINE: meKind=OBJ_PLIN; break;
1709             case OBJ_FREELINE: meKind=OBJ_PLIN; break;
1710             case OBJ_PATHFILL: meKind=OBJ_POLY; break;
1711             case OBJ_FREEFILL: meKind=OBJ_POLY; break;
1712             default: break;
1713         }
1714     }
1715 
1716     if (meKind==OBJ_LINE && !lcl_ImpIsLine(GetPathPoly())) meKind=OBJ_PLIN;
1717     if (meKind==OBJ_PLIN && lcl_ImpIsLine(GetPathPoly())) meKind=OBJ_LINE;
1718 
1719     bClosedObj=IsClosed();
1720 
1721     if (meKind==OBJ_LINE)
1722     {
1723         ImpForceLineAngle();
1724     }
1725     else
1726     {
1727         // #i10659#, for polys with more than 2 points.
1728 
1729         // Here i again need to fix something, because when Path-Polys are Copy-Pasted
1730         // between Apps with different measurements (e.g. 100TH_MM and TWIPS) there is
1731         // a scaling loop started from SdrExchangeView::Paste. In itself, this is not
1732         // wrong, but aRect is wrong here and not even updated by RecalcSnapRect(). If
1733         // this is the case, some size needs to be set here in aRect to avoid that the cycle
1734         // through Rect2Poly - Poly2Rect does something badly wrong since that cycle is
1735         // BASED on aRect. That cycle is triggered in SdrTextObj::NbcResize() which is called
1736         // from the local Resize() implementation.
1737 
1738         // Basic problem is that the member aRect in SdrTextObj basically is a unrotated
1739         // text rectangle for the text object itself and methods at SdrTextObj do handle it
1740         // in that way. Many draw objects derived from SdrTextObj 'abuse' aRect as SnapRect
1741         // which is basically wrong. To make the SdrText methods which deal with aRect directly
1742         // work it is necessary to always keep aRect updated. This e.g. not done after a Clone()
1743         // command for SdrPathObj. Since adding this update mechanism with #101412# to
1744         // ImpForceLineAngle() for lines was very successful, i add it to where ImpForceLineAngle()
1745         // was called, once here below and once on a 2nd place below.
1746 
1747         // #i10659# for SdrTextObj, keep aRect up to date
1748         if(GetPathPoly().count())
1749         {
1750             maRect = lcl_ImpGetBoundRect(GetPathPoly());
1751         }
1752     }
1753 
1754     // #i75974# adapt polygon state to object type. This may include a reinterpretation
1755     // of a closed geometry as open one, but with identical first and last point
1756     for(auto& rPolygon : maPathPolygon)
1757     {
1758         if(IsClosed() != rPolygon.isClosed())
1759         {
1760             // #i80213# really change polygon geometry; else e.g. the last point which
1761             // needs to be identical with the first one will be missing when opening
1762             // due to OBJ_PATH type
1763             if(rPolygon.isClosed())
1764             {
1765                 basegfx::utils::openWithGeometryChange(rPolygon);
1766             }
1767             else
1768             {
1769                 basegfx::utils::closeWithGeometryChange(rPolygon);
1770             }
1771         }
1772     }
1773 }
1774 
1775 void SdrPathObj::ImpSetClosed(bool bClose)
1776 {
1777     if(bClose)
1778     {
1779         switch (meKind)
1780         {
1781             case OBJ_LINE    : meKind=OBJ_POLY;     break;
1782             case OBJ_PLIN    : meKind=OBJ_POLY;     break;
1783             case OBJ_PATHLINE: meKind=OBJ_PATHFILL; break;
1784             case OBJ_FREELINE: meKind=OBJ_FREEFILL; break;
1785             case OBJ_SPLNLINE: meKind=OBJ_SPLNFILL; break;
1786             default: break;
1787         }
1788 
1789         bClosedObj = true;
1790     }
1791     else
1792     {
1793         switch (meKind)
1794         {
1795             case OBJ_POLY    : meKind=OBJ_PLIN;     break;
1796             case OBJ_PATHFILL: meKind=OBJ_PATHLINE; break;
1797             case OBJ_FREEFILL: meKind=OBJ_FREELINE; break;
1798             case OBJ_SPLNFILL: meKind=OBJ_SPLNLINE; break;
1799             default: break;
1800         }
1801 
1802         bClosedObj = false;
1803     }
1804 
1805     ImpForceKind();
1806 }
1807 
1808 void SdrPathObj::TakeObjInfo(SdrObjTransformInfoRec& rInfo) const
1809 {
1810     rInfo.bNoContortion=false;
1811 
1812     bool bCanConv = !HasText() || ImpCanConvTextToCurve();
1813     bool bIsPath = IsBezier() || IsSpline();
1814 
1815     rInfo.bEdgeRadiusAllowed    = false;
1816     rInfo.bCanConvToPath = bCanConv && !bIsPath;
1817     rInfo.bCanConvToPoly = bCanConv && bIsPath;
1818     rInfo.bCanConvToContour = !IsFontwork() && (rInfo.bCanConvToPoly || LineGeometryUsageIsNecessary());
1819 }
1820 
1821 sal_uInt16 SdrPathObj::GetObjIdentifier() const
1822 {
1823     return sal_uInt16(meKind);
1824 }
1825 
1826 SdrPathObj* SdrPathObj::CloneSdrObject(SdrModel& rTargetModel) const
1827 {
1828     return CloneHelper< SdrPathObj >(rTargetModel);
1829 }
1830 
1831 SdrPathObj& SdrPathObj::operator=(const SdrPathObj& rObj)
1832 {
1833     if( this == &rObj )
1834         return *this;
1835     SdrTextObj::operator=(rObj);
1836     maPathPolygon=rObj.GetPathPoly();
1837     return *this;
1838 }
1839 
1840 OUString SdrPathObj::TakeObjNameSingul() const
1841 {
1842     OUStringBuffer sName;
1843 
1844     if(OBJ_LINE == meKind)
1845     {
1846         const char* pId(STR_ObjNameSingulLINE);
1847 
1848         if(lcl_ImpIsLine(GetPathPoly()))
1849         {
1850             const basegfx::B2DPolygon aPoly(GetPathPoly().getB2DPolygon(0));
1851             const basegfx::B2DPoint aB2DPoint0(aPoly.getB2DPoint(0));
1852             const basegfx::B2DPoint aB2DPoint1(aPoly.getB2DPoint(1));
1853 
1854             if(aB2DPoint0 != aB2DPoint1)
1855             {
1856                 if(aB2DPoint0.getY() == aB2DPoint1.getY())
1857                 {
1858                     pId = STR_ObjNameSingulLINE_Hori;
1859                 }
1860                 else if(aB2DPoint0.getX() == aB2DPoint1.getX())
1861                 {
1862                     pId = STR_ObjNameSingulLINE_Vert;
1863                 }
1864                 else
1865                 {
1866                     const double fDx(fabs(aB2DPoint0.getX() - aB2DPoint1.getX()));
1867                     const double fDy(fabs(aB2DPoint0.getY() - aB2DPoint1.getY()));
1868 
1869                     if(fDx == fDy)
1870                     {
1871                         pId = STR_ObjNameSingulLINE_Diag;
1872                     }
1873                 }
1874             }
1875         }
1876 
1877         sName.append(SvxResId(pId));
1878     }
1879     else if(OBJ_PLIN == meKind || OBJ_POLY == meKind)
1880     {
1881         const bool bClosed(OBJ_POLY == meKind);
1882         const char* pId(nullptr);
1883 
1884         if(mpDAC && mpDAC->IsCreating())
1885         {
1886             if(bClosed)
1887             {
1888                 pId = STR_ObjNameSingulPOLY;
1889             }
1890             else
1891             {
1892                 pId = STR_ObjNameSingulPLIN;
1893             }
1894 
1895             sName.append(SvxResId(pId));
1896         }
1897         else
1898         {
1899             // get point count
1900             sal_uInt32 nPointCount(0);
1901 
1902             for(auto const& rPolygon : GetPathPoly())
1903             {
1904                 nPointCount += rPolygon.count();
1905             }
1906 
1907             if(bClosed)
1908             {
1909                 pId = STR_ObjNameSingulPOLY_PointCount;
1910             }
1911             else
1912             {
1913                 pId = STR_ObjNameSingulPLIN_PointCount;
1914             }
1915 
1916             OUString sTemp(SvxResId(pId));
1917             // #i96537#
1918             sName.append(sTemp.replaceFirst("%2", OUString::number(nPointCount)));
1919         }
1920     }
1921     else
1922     {
1923         switch (meKind)
1924         {
1925             case OBJ_PATHLINE: sName.append(SvxResId(STR_ObjNameSingulPATHLINE)); break;
1926             case OBJ_FREELINE: sName.append(SvxResId(STR_ObjNameSingulFREELINE)); break;
1927             case OBJ_SPLNLINE: sName.append(SvxResId(STR_ObjNameSingulNATSPLN)); break;
1928             case OBJ_PATHFILL: sName.append(SvxResId(STR_ObjNameSingulPATHFILL)); break;
1929             case OBJ_FREEFILL: sName.append(SvxResId(STR_ObjNameSingulFREEFILL)); break;
1930             case OBJ_SPLNFILL: sName.append(SvxResId(STR_ObjNameSingulPERSPLN)); break;
1931             default: break;
1932         }
1933     }
1934 
1935     OUString aName(GetName());
1936     if (!aName.isEmpty())
1937     {
1938         sName.append(' ');
1939         sName.append('\'');
1940         sName.append(aName);
1941         sName.append('\'');
1942     }
1943 
1944     return sName.makeStringAndClear();
1945 }
1946 
1947 OUString SdrPathObj::TakeObjNamePlural() const
1948 {
1949     OUString sName;
1950     switch(meKind)
1951     {
1952         case OBJ_LINE    : sName=SvxResId(STR_ObjNamePluralLINE    ); break;
1953         case OBJ_PLIN    : sName=SvxResId(STR_ObjNamePluralPLIN    ); break;
1954         case OBJ_POLY    : sName=SvxResId(STR_ObjNamePluralPOLY    ); break;
1955         case OBJ_PATHLINE: sName=SvxResId(STR_ObjNamePluralPATHLINE); break;
1956         case OBJ_FREELINE: sName=SvxResId(STR_ObjNamePluralFREELINE); break;
1957         case OBJ_SPLNLINE: sName=SvxResId(STR_ObjNamePluralNATSPLN); break;
1958         case OBJ_PATHFILL: sName=SvxResId(STR_ObjNamePluralPATHFILL); break;
1959         case OBJ_FREEFILL: sName=SvxResId(STR_ObjNamePluralFREEFILL); break;
1960         case OBJ_SPLNFILL: sName=SvxResId(STR_ObjNamePluralPERSPLN); break;
1961         default: break;
1962     }
1963     return sName;
1964 }
1965 
1966 basegfx::B2DPolyPolygon SdrPathObj::TakeXorPoly() const
1967 {
1968     return GetPathPoly();
1969 }
1970 
1971 sal_uInt32 SdrPathObj::GetHdlCount() const
1972 {
1973     sal_uInt32 nRetval(0);
1974 
1975     for(auto const& rPolygon : GetPathPoly())
1976     {
1977         nRetval += rPolygon.count();
1978     }
1979 
1980     return nRetval;
1981 }
1982 
1983 void SdrPathObj::AddToHdlList(SdrHdlList& rHdlList) const
1984 {
1985     // keep old stuff to be able to keep old SdrHdl stuff, too
1986     const XPolyPolygon aOldPathPolygon(GetPathPoly());
1987     sal_uInt16 nPolyCnt=aOldPathPolygon.Count();
1988     bool bClosed=IsClosed();
1989     sal_uInt16 nIdx=0;
1990 
1991     for (sal_uInt16 i=0; i<nPolyCnt; i++) {
1992         const XPolygon& rXPoly=aOldPathPolygon.GetObject(i);
1993         sal_uInt16 nPntCnt=rXPoly.GetPointCount();
1994         if (bClosed && nPntCnt>1) nPntCnt--;
1995 
1996         for (sal_uInt16 j=0; j<nPntCnt; j++) {
1997             if (rXPoly.GetFlags(j)!=PolyFlags::Control) {
1998                 const Point& rPnt=rXPoly[j];
1999                 std::unique_ptr<SdrHdl> pHdl(new SdrHdl(rPnt,SdrHdlKind::Poly));
2000                 pHdl->SetPolyNum(i);
2001                 pHdl->SetPointNum(j);
2002                 pHdl->Set1PixMore(j==0);
2003                 pHdl->SetSourceHdlNum(nIdx);
2004                 nIdx++;
2005                 rHdlList.AddHdl(std::move(pHdl));
2006             }
2007         }
2008     }
2009 }
2010 
2011 void SdrPathObj::AddToPlusHdlList(SdrHdlList& rHdlList, SdrHdl& rHdl) const
2012 {
2013     // keep old stuff to be able to keep old SdrHdl stuff, too
2014     const XPolyPolygon aOldPathPolygon(GetPathPoly());
2015     sal_uInt16 nPnt = static_cast<sal_uInt16>(rHdl.GetPointNum());
2016     sal_uInt16 nPolyNum = static_cast<sal_uInt16>(rHdl.GetPolyNum());
2017 
2018     if (nPolyNum>=aOldPathPolygon.Count())
2019         return;
2020 
2021     const XPolygon& rXPoly = aOldPathPolygon[nPolyNum];
2022     sal_uInt16 nPntMax = rXPoly.GetPointCount();
2023 
2024     if (nPntMax<=0)
2025         return;
2026     nPntMax--;
2027     if (nPnt>nPntMax)
2028         return;
2029 
2030     // calculate the number of plus points
2031     sal_uInt16 nCnt = 0;
2032     if (rXPoly.GetFlags(nPnt)!=PolyFlags::Control)
2033     {
2034         if (nPnt==0 && IsClosed())
2035             nPnt=nPntMax;
2036         if (nPnt>0 && rXPoly.GetFlags(nPnt-1)==PolyFlags::Control)
2037             nCnt++;
2038         if (nPnt==nPntMax && IsClosed())
2039             nPnt=0;
2040         if (nPnt<nPntMax && rXPoly.GetFlags(nPnt+1)==PolyFlags::Control)
2041             nCnt++;
2042     }
2043 
2044     // construct the plus points
2045     for (sal_uInt32 nPlusNum = 0; nPlusNum < nCnt; ++nPlusNum)
2046     {
2047         nPnt = static_cast<sal_uInt16>(rHdl.GetPointNum());
2048         std::unique_ptr<SdrHdl> pHdl(new SdrHdlBezWgt(&rHdl));
2049         pHdl->SetPolyNum(rHdl.GetPolyNum());
2050 
2051         if (nPnt==0 && IsClosed())
2052             nPnt=nPntMax;
2053         if (nPnt>0 && rXPoly.GetFlags(nPnt-1)==PolyFlags::Control && nPlusNum==0)
2054         {
2055             pHdl->SetPos(rXPoly[nPnt-1]);
2056             pHdl->SetPointNum(nPnt-1);
2057         }
2058         else
2059         {
2060             if (nPnt==nPntMax && IsClosed())
2061                 nPnt=0;
2062             if (nPnt<rXPoly.GetPointCount()-1 && rXPoly.GetFlags(nPnt+1)==PolyFlags::Control)
2063             {
2064                 pHdl->SetPos(rXPoly[nPnt+1]);
2065                 pHdl->SetPointNum(nPnt+1);
2066             }
2067         }
2068 
2069         pHdl->SetSourceHdlNum(rHdl.GetSourceHdlNum());
2070         pHdl->SetPlusHdl(true);
2071         rHdlList.AddHdl(std::move(pHdl));
2072     }
2073 }
2074 
2075 // dragging
2076 
2077 bool SdrPathObj::hasSpecialDrag() const
2078 {
2079     return true;
2080 }
2081 
2082 bool SdrPathObj::beginSpecialDrag(SdrDragStat& rDrag) const
2083 {
2084     ImpPathForDragAndCreate aDragAndCreate(*const_cast<SdrPathObj*>(this));
2085 
2086     return aDragAndCreate.beginPathDrag(rDrag);
2087 }
2088 
2089 bool SdrPathObj::applySpecialDrag(SdrDragStat& rDrag)
2090 {
2091     ImpPathForDragAndCreate aDragAndCreate(*this);
2092     bool bRetval(aDragAndCreate.beginPathDrag(rDrag));
2093 
2094     if(bRetval)
2095     {
2096         bRetval = aDragAndCreate.movePathDrag(rDrag);
2097     }
2098 
2099     if(bRetval)
2100     {
2101         bRetval = aDragAndCreate.endPathDrag(rDrag);
2102     }
2103 
2104     if(bRetval)
2105     {
2106            NbcSetPathPoly(aDragAndCreate.getModifiedPolyPolygon());
2107     }
2108 
2109     return bRetval;
2110 }
2111 
2112 OUString SdrPathObj::getSpecialDragComment(const SdrDragStat& rDrag) const
2113 {
2114     OUString aRetval;
2115 
2116     if(mpDAC)
2117     {
2118         // #i103058# also get a comment when in creation
2119         const bool bCreateComment(rDrag.GetView() && this == rDrag.GetView()->GetCreateObj());
2120 
2121         if(bCreateComment)
2122         {
2123             aRetval = mpDAC->getSpecialDragComment(rDrag);
2124         }
2125     }
2126     else
2127     {
2128         ImpPathForDragAndCreate aDragAndCreate(*const_cast<SdrPathObj*>(this));
2129         bool bDidWork(aDragAndCreate.beginPathDrag(rDrag));
2130 
2131         if(bDidWork)
2132         {
2133             aRetval = aDragAndCreate.getSpecialDragComment(rDrag);
2134         }
2135     }
2136 
2137     return aRetval;
2138 }
2139 
2140 basegfx::B2DPolyPolygon SdrPathObj::getSpecialDragPoly(const SdrDragStat& rDrag) const
2141 {
2142     basegfx::B2DPolyPolygon aRetval;
2143     ImpPathForDragAndCreate aDragAndCreate(*const_cast<SdrPathObj*>(this));
2144     bool bDidWork(aDragAndCreate.beginPathDrag(rDrag));
2145 
2146     if(bDidWork)
2147     {
2148         aRetval = aDragAndCreate.getSpecialDragPoly(rDrag);
2149     }
2150 
2151     return aRetval;
2152 }
2153 
2154 // creation
2155 
2156 bool SdrPathObj::BegCreate(SdrDragStat& rStat)
2157 {
2158     mpDAC.reset();
2159     impGetDAC().BegCreate(rStat);
2160     return true;
2161 }
2162 
2163 bool SdrPathObj::MovCreate(SdrDragStat& rStat)
2164 {
2165     return impGetDAC().MovCreate(rStat);
2166 }
2167 
2168 bool SdrPathObj::EndCreate(SdrDragStat& rStat, SdrCreateCmd eCmd)
2169 {
2170     bool bRetval(impGetDAC().EndCreate(rStat, eCmd));
2171 
2172     if(bRetval && mpDAC)
2173     {
2174         SetPathPoly(mpDAC->getModifiedPolyPolygon());
2175 
2176         // #i75974# Check for AutoClose feature. Moved here from ImpPathForDragAndCreate::EndCreate
2177         // to be able to use the type-changing ImpSetClosed method
2178         if(!IsClosedObj())
2179         {
2180             SdrView* pView = rStat.GetView();
2181 
2182             if(pView && !pView->IsUseIncompatiblePathCreateInterface())
2183             {
2184                 OutputDevice* pOut = pView->GetFirstOutputDevice();
2185 
2186                 if(pOut)
2187                 {
2188                     if(GetPathPoly().count())
2189                     {
2190                         const basegfx::B2DPolygon aCandidate(GetPathPoly().getB2DPolygon(0));
2191 
2192                         if(aCandidate.count() > 2)
2193                         {
2194                             // check distance of first and last point
2195                             const sal_Int32 nCloseDist(pOut->PixelToLogic(Size(pView->GetAutoCloseDistPix(), 0)).Width());
2196                             const basegfx::B2DVector aDistVector(aCandidate.getB2DPoint(aCandidate.count() - 1) - aCandidate.getB2DPoint(0));
2197 
2198                             if(aDistVector.getLength() <= static_cast<double>(nCloseDist))
2199                             {
2200                                 // close it
2201                                 ImpSetClosed(true);
2202                             }
2203                         }
2204                     }
2205                 }
2206             }
2207         }
2208 
2209         mpDAC.reset();
2210     }
2211 
2212     return bRetval;
2213 }
2214 
2215 bool SdrPathObj::BckCreate(SdrDragStat& rStat)
2216 {
2217     return impGetDAC().BckCreate(rStat);
2218 }
2219 
2220 void SdrPathObj::BrkCreate(SdrDragStat& rStat)
2221 {
2222     impGetDAC().BrkCreate(rStat);
2223     mpDAC.reset();
2224 }
2225 
2226 // polygons
2227 
2228 basegfx::B2DPolyPolygon SdrPathObj::TakeCreatePoly(const SdrDragStat& rDrag) const
2229 {
2230     basegfx::B2DPolyPolygon aRetval;
2231 
2232     if(mpDAC)
2233     {
2234         aRetval = mpDAC->TakeObjectPolyPolygon(rDrag);
2235         aRetval.append(ImpPathForDragAndCreate::TakeDragPolyPolygon(rDrag));
2236     }
2237 
2238     return aRetval;
2239 }
2240 
2241 // during drag or create, allow accessing the so-far created/modified polyPolygon
2242 basegfx::B2DPolyPolygon SdrPathObj::getObjectPolyPolygon(const SdrDragStat& rDrag) const
2243 {
2244     basegfx::B2DPolyPolygon aRetval;
2245 
2246     if(mpDAC)
2247     {
2248         aRetval = mpDAC->TakeObjectPolyPolygon(rDrag);
2249     }
2250 
2251     return aRetval;
2252 }
2253 
2254 basegfx::B2DPolyPolygon SdrPathObj::getDragPolyPolygon(const SdrDragStat& rDrag) const
2255 {
2256     basegfx::B2DPolyPolygon aRetval;
2257 
2258     if(mpDAC)
2259     {
2260         aRetval = ImpPathForDragAndCreate::TakeDragPolyPolygon(rDrag);
2261     }
2262 
2263     return aRetval;
2264 }
2265 
2266 PointerStyle SdrPathObj::GetCreatePointer() const
2267 {
2268     return impGetDAC().GetCreatePointer();
2269 }
2270 
2271 void SdrPathObj::NbcMove(const Size& rSiz)
2272 {
2273     maPathPolygon.transform(basegfx::utils::createTranslateB2DHomMatrix(rSiz.Width(), rSiz.Height()));
2274 
2275     // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
2276     SdrTextObj::NbcMove(rSiz);
2277 }
2278 
2279 void SdrPathObj::NbcResize(const Point& rRef, const Fraction& xFact, const Fraction& yFact)
2280 {
2281     const double fResizeX(xFact);
2282     const double fResizeY(yFact);
2283 
2284     if(basegfx::fTools::equal(fResizeX, 1.0) && basegfx::fTools::equal(fResizeY, 1.0))
2285     {
2286         // tdf#106792 avoid numerical unprecisions: If both scale factors are 1.0, do not
2287         // manipulate at all - that may change aGeo rapidly (and wrongly) in
2288         // SdrTextObj::NbcResize. Combined with the UNO API trying to not 'apply'
2289         // a rotation but to manipulate the existing one, this is fatal. So just
2290         // avoid this error as long as we have to deal with imprecise geometry
2291         // manipulations
2292         return;
2293     }
2294 
2295     basegfx::B2DHomMatrix aTrans(basegfx::utils::createTranslateB2DHomMatrix(-rRef.X(), -rRef.Y()));
2296     aTrans = basegfx::utils::createScaleTranslateB2DHomMatrix(
2297         double(xFact), double(yFact), rRef.X(), rRef.Y()) * aTrans;
2298     maPathPolygon.transform(aTrans);
2299 
2300     // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
2301     SdrTextObj::NbcResize(rRef,xFact,yFact);
2302 }
2303 
2304 void SdrPathObj::NbcRotate(const Point& rRef, long nAngle, double sn, double cs)
2305 {
2306     // Thank JOE, the angles are defined mirrored to the mathematical meanings
2307     const basegfx::B2DHomMatrix aTrans(
2308         basegfx::utils::createRotateAroundPoint(rRef.X(), rRef.Y(), -nAngle * F_PI18000));
2309     maPathPolygon.transform(aTrans);
2310 
2311     // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
2312     SdrTextObj::NbcRotate(rRef,nAngle,sn,cs);
2313 }
2314 
2315 void SdrPathObj::NbcShear(const Point& rRefPnt, long nAngle, double fTan, bool bVShear)
2316 {
2317     basegfx::B2DHomMatrix aTrans(basegfx::utils::createTranslateB2DHomMatrix(-rRefPnt.X(), -rRefPnt.Y()));
2318 
2319     if(bVShear)
2320     {
2321         // Thank JOE, the angles are defined mirrored to the mathematical meanings
2322         aTrans.shearY(-fTan);
2323     }
2324     else
2325     {
2326         aTrans.shearX(-fTan);
2327     }
2328 
2329     aTrans.translate(rRefPnt.X(), rRefPnt.Y());
2330     maPathPolygon.transform(aTrans);
2331 
2332     // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
2333     SdrTextObj::NbcShear(rRefPnt,nAngle,fTan,bVShear);
2334 }
2335 
2336 void SdrPathObj::NbcMirror(const Point& rRefPnt1, const Point& rRefPnt2)
2337 {
2338     const double fDiffX(rRefPnt2.X() - rRefPnt1.X());
2339     const double fDiffY(rRefPnt2.Y() - rRefPnt1.Y());
2340     const double fRot(atan2(fDiffY, fDiffX));
2341     basegfx::B2DHomMatrix aTrans(basegfx::utils::createTranslateB2DHomMatrix(-rRefPnt1.X(), -rRefPnt1.Y()));
2342     aTrans.rotate(-fRot);
2343     aTrans.scale(1.0, -1.0);
2344     aTrans.rotate(fRot);
2345     aTrans.translate(rRefPnt1.X(), rRefPnt1.Y());
2346     maPathPolygon.transform(aTrans);
2347 
2348     // Do Joe's special handling for lines when mirroring, too
2349     ImpForceKind();
2350 
2351     // #i19871# first modify locally, then call parent (to get correct SnapRect with GluePoints)
2352     SdrTextObj::NbcMirror(rRefPnt1,rRefPnt2);
2353 }
2354 
2355 void SdrPathObj::TakeUnrotatedSnapRect(tools::Rectangle& rRect) const
2356 {
2357     if(!aGeo.nRotationAngle)
2358     {
2359         rRect = GetSnapRect();
2360     }
2361     else
2362     {
2363         XPolyPolygon aXPP(GetPathPoly());
2364         RotateXPoly(aXPP,Point(),-aGeo.nSin,aGeo.nCos);
2365         rRect=aXPP.GetBoundRect();
2366         Point aTmp(rRect.TopLeft());
2367         RotatePoint(aTmp,Point(),aGeo.nSin,aGeo.nCos);
2368         aTmp-=rRect.TopLeft();
2369         rRect.Move(aTmp.X(),aTmp.Y());
2370     }
2371 }
2372 
2373 void SdrPathObj::RecalcSnapRect()
2374 {
2375     if(GetPathPoly().count())
2376     {
2377         maSnapRect = lcl_ImpGetBoundRect(GetPathPoly());
2378     }
2379 }
2380 
2381 void SdrPathObj::NbcSetSnapRect(const tools::Rectangle& rRect)
2382 {
2383     tools::Rectangle aOld(GetSnapRect());
2384     if (aOld.IsEmpty())
2385     {
2386         Fraction aX(1,1);
2387         Fraction aY(1,1);
2388         NbcResize(aOld.TopLeft(), aX, aY);
2389         NbcMove(Size(rRect.Left() - aOld.Left(), rRect.Top() - aOld.Top()));
2390         return;
2391     }
2392 
2393     // Take empty into account when calculating scale factors
2394     long nMulX = rRect.IsWidthEmpty() ? 0 : rRect.Right()  - rRect.Left();
2395 
2396     long nDivX = aOld.Right()   - aOld.Left();
2397 
2398     // Take empty into account when calculating scale factors
2399     long nMulY = rRect.IsHeightEmpty() ? 0 : rRect.Bottom() - rRect.Top();
2400 
2401     long nDivY = aOld.Bottom()  - aOld.Top();
2402     if ( nDivX == 0 ) { nMulX = 1; nDivX = 1; }
2403     if ( nDivY == 0 ) { nMulY = 1; nDivY = 1; }
2404     if ( nDivX == nMulX ) { nMulX = 1; nDivX = 1; }
2405     if ( nDivY == nMulY ) { nMulY = 1; nDivY = 1; }
2406     Fraction aX(nMulX,nDivX);
2407     Fraction aY(nMulY,nDivY);
2408     NbcResize(aOld.TopLeft(), aX, aY);
2409     NbcMove(Size(rRect.Left() - aOld.Left(), rRect.Top() - aOld.Top()));
2410 }
2411 
2412 sal_uInt32 SdrPathObj::GetSnapPointCount() const
2413 {
2414     return GetHdlCount();
2415 }
2416 
2417 Point SdrPathObj::GetSnapPoint(sal_uInt32 nSnapPnt) const
2418 {
2419     sal_uInt32 nPoly,nPnt;
2420     if(!PolyPolygonEditor::GetRelativePolyPoint(GetPathPoly(), nSnapPnt, nPoly, nPnt))
2421     {
2422         SAL_WARN("svx", "SdrPathObj::GetSnapPoint: Point nSnapPnt does not exist.");
2423     }
2424 
2425     const basegfx::B2DPoint aB2DPoint(GetPathPoly().getB2DPolygon(nPoly).getB2DPoint(nPnt));
2426     return Point(FRound(aB2DPoint.getX()), FRound(aB2DPoint.getY()));
2427 }
2428 
2429 bool SdrPathObj::IsPolyObj() const
2430 {
2431     return true;
2432 }
2433 
2434 sal_uInt32 SdrPathObj::GetPointCount() const
2435 {
2436     sal_uInt32 nRetval(0);
2437 
2438     for(auto const& rPolygon : GetPathPoly())
2439     {
2440         nRetval += rPolygon.count();
2441     }
2442 
2443     return nRetval;
2444 }
2445 
2446 Point SdrPathObj::GetPoint(sal_uInt32 nHdlNum) const
2447 {
2448     Point aRetval;
2449     sal_uInt32 nPoly,nPnt;
2450 
2451     if(PolyPolygonEditor::GetRelativePolyPoint(GetPathPoly(), nHdlNum, nPoly, nPnt))
2452     {
2453         const basegfx::B2DPolygon aPoly(GetPathPoly().getB2DPolygon(nPoly));
2454         const basegfx::B2DPoint aPoint(aPoly.getB2DPoint(nPnt));
2455         aRetval = Point(FRound(aPoint.getX()), FRound(aPoint.getY()));
2456     }
2457 
2458     return aRetval;
2459 }
2460 
2461 void SdrPathObj::NbcSetPoint(const Point& rPnt, sal_uInt32 nHdlNum)
2462 {
2463     sal_uInt32 nPoly,nPnt;
2464 
2465     if(PolyPolygonEditor::GetRelativePolyPoint(GetPathPoly(), nHdlNum, nPoly, nPnt))
2466     {
2467         basegfx::B2DPolygon aNewPolygon(GetPathPoly().getB2DPolygon(nPoly));
2468         aNewPolygon.setB2DPoint(nPnt, basegfx::B2DPoint(rPnt.X(), rPnt.Y()));
2469         maPathPolygon.setB2DPolygon(nPoly, aNewPolygon);
2470 
2471         if(meKind==OBJ_LINE)
2472         {
2473             ImpForceLineAngle();
2474         }
2475         else
2476         {
2477             if(GetPathPoly().count())
2478             {
2479                 // #i10659# for SdrTextObj, keep aRect up to date
2480                 maRect = lcl_ImpGetBoundRect(GetPathPoly());
2481             }
2482         }
2483 
2484         SetRectsDirty();
2485     }
2486 }
2487 
2488 sal_uInt32 SdrPathObj::NbcInsPointOld(const Point& rPos, bool bNewObj)
2489 {
2490     sal_uInt32 nNewHdl;
2491 
2492     if(bNewObj)
2493     {
2494         nNewHdl = NbcInsPoint(rPos, true);
2495     }
2496     else
2497     {
2498         // look for smallest distance data
2499         const basegfx::B2DPoint aTestPoint(rPos.X(), rPos.Y());
2500         sal_uInt32 nSmallestPolyIndex(0);
2501         sal_uInt32 nSmallestEdgeIndex(0);
2502         double fSmallestCut;
2503         basegfx::utils::getSmallestDistancePointToPolyPolygon(GetPathPoly(), aTestPoint, nSmallestPolyIndex, nSmallestEdgeIndex, fSmallestCut);
2504 
2505         nNewHdl = NbcInsPoint(rPos, false);
2506     }
2507 
2508     ImpForceKind();
2509     return nNewHdl;
2510 }
2511 
2512 sal_uInt32 SdrPathObj::NbcInsPoint(const Point& rPos, bool bNewObj)
2513 {
2514     sal_uInt32 nNewHdl;
2515 
2516     if(bNewObj)
2517     {
2518         basegfx::B2DPolygon aNewPoly;
2519         const basegfx::B2DPoint aPoint(rPos.X(), rPos.Y());
2520         aNewPoly.append(aPoint);
2521         aNewPoly.setClosed(IsClosed());
2522         maPathPolygon.append(aNewPoly);
2523         SetRectsDirty();
2524         nNewHdl = GetHdlCount();
2525     }
2526     else
2527     {
2528         // look for smallest distance data
2529         const basegfx::B2DPoint aTestPoint(rPos.X(), rPos.Y());
2530         sal_uInt32 nSmallestPolyIndex(0);
2531         sal_uInt32 nSmallestEdgeIndex(0);
2532         double fSmallestCut;
2533         basegfx::utils::getSmallestDistancePointToPolyPolygon(GetPathPoly(), aTestPoint, nSmallestPolyIndex, nSmallestEdgeIndex, fSmallestCut);
2534         basegfx::B2DPolygon aCandidate(GetPathPoly().getB2DPolygon(nSmallestPolyIndex));
2535         const bool bBefore(!aCandidate.isClosed() && 0 == nSmallestEdgeIndex && 0.0 == fSmallestCut);
2536         const bool bAfter(!aCandidate.isClosed() && aCandidate.count() == nSmallestEdgeIndex + 2 && 1.0 == fSmallestCut);
2537 
2538         if(bBefore)
2539         {
2540             // before first point
2541             aCandidate.insert(0, aTestPoint);
2542 
2543             if(aCandidate.areControlPointsUsed())
2544             {
2545                 if(aCandidate.isNextControlPointUsed(1))
2546                 {
2547                     aCandidate.setNextControlPoint(0, interpolate(aTestPoint, aCandidate.getB2DPoint(1), (1.0 / 3.0)));
2548                     aCandidate.setPrevControlPoint(1, interpolate(aTestPoint, aCandidate.getB2DPoint(1), (2.0 / 3.0)));
2549                 }
2550             }
2551 
2552             nNewHdl = 0;
2553         }
2554         else if(bAfter)
2555         {
2556             // after last point
2557             aCandidate.append(aTestPoint);
2558 
2559             if(aCandidate.areControlPointsUsed())
2560             {
2561                 if(aCandidate.isPrevControlPointUsed(aCandidate.count() - 2))
2562                 {
2563                     aCandidate.setNextControlPoint(aCandidate.count() - 2, interpolate(aCandidate.getB2DPoint(aCandidate.count() - 2), aTestPoint, (1.0 / 3.0)));
2564                     aCandidate.setPrevControlPoint(aCandidate.count() - 1, interpolate(aCandidate.getB2DPoint(aCandidate.count() - 2), aTestPoint, (2.0 / 3.0)));
2565                 }
2566             }
2567 
2568             nNewHdl = aCandidate.count() - 1;
2569         }
2570         else
2571         {
2572             // in between
2573             bool bSegmentSplit(false);
2574             const sal_uInt32 nNextIndex((nSmallestEdgeIndex + 1) % aCandidate.count());
2575 
2576             if(aCandidate.areControlPointsUsed())
2577             {
2578                 if(aCandidate.isNextControlPointUsed(nSmallestEdgeIndex) || aCandidate.isPrevControlPointUsed(nNextIndex))
2579                 {
2580                     bSegmentSplit = true;
2581                 }
2582             }
2583 
2584             if(bSegmentSplit)
2585             {
2586                 // rebuild original segment to get the split data
2587                 basegfx::B2DCubicBezier aBezierA, aBezierB;
2588                 const basegfx::B2DCubicBezier aBezier(
2589                     aCandidate.getB2DPoint(nSmallestEdgeIndex),
2590                     aCandidate.getNextControlPoint(nSmallestEdgeIndex),
2591                     aCandidate.getPrevControlPoint(nNextIndex),
2592                     aCandidate.getB2DPoint(nNextIndex));
2593 
2594                 // split and insert hit point
2595                 aBezier.split(fSmallestCut, &aBezierA, &aBezierB);
2596                 aCandidate.insert(nSmallestEdgeIndex + 1, aTestPoint);
2597 
2598                 // since we inserted hit point and not split point, we need to add an offset
2599                 // to the control points to get the C1 continuity we want to achieve
2600                 const basegfx::B2DVector aOffset(aTestPoint - aBezierA.getEndPoint());
2601                 aCandidate.setNextControlPoint(nSmallestEdgeIndex, aBezierA.getControlPointA() + aOffset);
2602                 aCandidate.setPrevControlPoint(nSmallestEdgeIndex + 1, aBezierA.getControlPointB() + aOffset);
2603                 aCandidate.setNextControlPoint(nSmallestEdgeIndex + 1, aBezierB.getControlPointA() + aOffset);
2604                 aCandidate.setPrevControlPoint((nSmallestEdgeIndex + 2) % aCandidate.count(), aBezierB.getControlPointB() + aOffset);
2605             }
2606             else
2607             {
2608                 aCandidate.insert(nSmallestEdgeIndex + 1, aTestPoint);
2609             }
2610 
2611             nNewHdl = nSmallestEdgeIndex + 1;
2612         }
2613 
2614         maPathPolygon.setB2DPolygon(nSmallestPolyIndex, aCandidate);
2615 
2616         // create old polygon index from it
2617         for(sal_uInt32 a(0); a < nSmallestPolyIndex; a++)
2618         {
2619             nNewHdl += GetPathPoly().getB2DPolygon(a).count();
2620         }
2621     }
2622 
2623     ImpForceKind();
2624     return nNewHdl;
2625 }
2626 
2627 SdrObject* SdrPathObj::RipPoint(sal_uInt32 nHdlNum, sal_uInt32& rNewPt0Index)
2628 {
2629     SdrPathObj* pNewObj = nullptr;
2630     const basegfx::B2DPolyPolygon aLocalPolyPolygon(GetPathPoly());
2631     sal_uInt32 nPoly, nPnt;
2632 
2633     if(PolyPolygonEditor::GetRelativePolyPoint(aLocalPolyPolygon, nHdlNum, nPoly, nPnt))
2634     {
2635         if(0 == nPoly)
2636         {
2637             const basegfx::B2DPolygon& aCandidate(aLocalPolyPolygon.getB2DPolygon(nPoly));
2638             const sal_uInt32 nPointCount(aCandidate.count());
2639 
2640             if(nPointCount)
2641             {
2642                 if(IsClosed())
2643                 {
2644                     // when closed, RipPoint means to open the polygon at the selected point. To
2645                     // be able to do that, it is necessary to make the selected point the first one
2646                     basegfx::B2DPolygon aNewPolygon(basegfx::utils::makeStartPoint(aCandidate, nPnt));
2647                     SetPathPoly(basegfx::B2DPolyPolygon(aNewPolygon));
2648                     ToggleClosed();
2649 
2650                     // give back new position of old start point (historical reasons)
2651                     rNewPt0Index = (nPointCount - nPnt) % nPointCount;
2652                 }
2653                 else
2654                 {
2655                     if(nPointCount >= 3 && nPnt != 0 && nPnt + 1 < nPointCount)
2656                     {
2657                         // split in two objects at point nPnt
2658                         basegfx::B2DPolygon aSplitPolyA(aCandidate, 0, nPnt + 1);
2659                         SetPathPoly(basegfx::B2DPolyPolygon(aSplitPolyA));
2660 
2661                         pNewObj = CloneSdrObject(getSdrModelFromSdrObject());
2662                         basegfx::B2DPolygon aSplitPolyB(aCandidate, nPnt, nPointCount - nPnt);
2663                         pNewObj->SetPathPoly(basegfx::B2DPolyPolygon(aSplitPolyB));
2664                     }
2665                 }
2666             }
2667         }
2668     }
2669 
2670     return pNewObj;
2671 }
2672 
2673 SdrObjectUniquePtr SdrPathObj::DoConvertToPolyObj(bool bBezier, bool bAddText) const
2674 {
2675     // #i89784# check for FontWork with activated HideContour
2676     const drawinglayer::attribute::SdrTextAttribute aText(
2677         drawinglayer::primitive2d::createNewSdrTextAttribute(GetObjectItemSet(), *getText(0)));
2678     const bool bHideContour(
2679         !aText.isDefault() && !aText.getSdrFormTextAttribute().isDefault() && aText.isHideContour());
2680 
2681     SdrObjectUniquePtr pRet;
2682 
2683     if(!bHideContour)
2684     {
2685         SdrPathObjUniquePtr pPath = ImpConvertMakeObj(GetPathPoly(), IsClosed(), bBezier);
2686 
2687         if(pPath->GetPathPoly().areControlPointsUsed())
2688         {
2689             if(!bBezier)
2690             {
2691                 // reduce all bezier curves
2692                 pPath->SetPathPoly(basegfx::utils::adaptiveSubdivideByAngle(pPath->GetPathPoly()));
2693             }
2694         }
2695         else
2696         {
2697             if(bBezier)
2698             {
2699                 // create bezier curves
2700                 pPath->SetPathPoly(basegfx::utils::expandToCurve(pPath->GetPathPoly()));
2701             }
2702         }
2703         pRet = std::move(pPath);
2704     }
2705 
2706     if(bAddText)
2707     {
2708         pRet = ImpConvertAddText(std::move(pRet), bBezier);
2709     }
2710 
2711     return pRet;
2712 }
2713 
2714 SdrObjGeoData* SdrPathObj::NewGeoData() const
2715 {
2716     return new SdrPathObjGeoData;
2717 }
2718 
2719 void SdrPathObj::SaveGeoData(SdrObjGeoData& rGeo) const
2720 {
2721     SdrTextObj::SaveGeoData(rGeo);
2722     SdrPathObjGeoData& rPGeo = static_cast<SdrPathObjGeoData&>( rGeo );
2723     rPGeo.maPathPolygon=GetPathPoly();
2724     rPGeo.meKind=meKind;
2725 }
2726 
2727 void SdrPathObj::RestGeoData(const SdrObjGeoData& rGeo)
2728 {
2729     SdrTextObj::RestGeoData(rGeo);
2730     const SdrPathObjGeoData& rPGeo=static_cast<const SdrPathObjGeoData&>(rGeo);
2731     maPathPolygon=rPGeo.maPathPolygon;
2732     meKind=rPGeo.meKind;
2733     ImpForceKind(); // to set bClosed (among other things)
2734 }
2735 
2736 void SdrPathObj::NbcSetPathPoly(const basegfx::B2DPolyPolygon& rPathPoly)
2737 {
2738     if(GetPathPoly() != rPathPoly)
2739     {
2740         maPathPolygon=rPathPoly;
2741         ImpForceKind();
2742         SetRectsDirty();
2743     }
2744 }
2745 
2746 void SdrPathObj::SetPathPoly(const basegfx::B2DPolyPolygon& rPathPoly)
2747 {
2748     if(GetPathPoly() != rPathPoly)
2749     {
2750         tools::Rectangle aBoundRect0; if (pUserCall!=nullptr) aBoundRect0=GetLastBoundRect();
2751         NbcSetPathPoly(rPathPoly);
2752         SetChanged();
2753         BroadcastObjectChange();
2754         SendUserCall(SdrUserCallType::Resize,aBoundRect0);
2755     }
2756 }
2757 
2758 void SdrPathObj::ToggleClosed()
2759 {
2760     tools::Rectangle aBoundRect0;
2761     if(pUserCall != nullptr)
2762         aBoundRect0 = GetLastBoundRect();
2763     ImpSetClosed(!IsClosed()); // set new ObjKind
2764     ImpForceKind(); // because we want Line -> Poly -> PolyLine instead of Line -> Poly -> Line
2765     SetRectsDirty();
2766     SetChanged();
2767     BroadcastObjectChange();
2768     SendUserCall(SdrUserCallType::Resize, aBoundRect0);
2769 }
2770 
2771 ImpPathForDragAndCreate& SdrPathObj::impGetDAC() const
2772 {
2773     if(!mpDAC)
2774     {
2775         const_cast<SdrPathObj*>(this)->mpDAC.reset(new ImpPathForDragAndCreate(*const_cast<SdrPathObj*>(this)));
2776     }
2777 
2778     return *mpDAC;
2779 }
2780 
2781 
2782 // transformation interface for StarOfficeAPI. This implements support for
2783 // homogeneous 3x3 matrices containing the transformation of the SdrObject. At the
2784 // moment it contains a shearX, rotation and translation, but for setting all linear
2785 // transforms like Scale, ShearX, ShearY, Rotate and Translate are supported.
2786 
2787 
2788 // gets base transformation and rectangle of object. If it's an SdrPathObj it fills the PolyPolygon
2789 // with the base geometry and returns TRUE. Otherwise it returns FALSE.
2790 bool SdrPathObj::TRGetBaseGeometry(basegfx::B2DHomMatrix& rMatrix, basegfx::B2DPolyPolygon& rPolyPolygon) const
2791 {
2792     double fRotate(0.0);
2793     double fShearX(0.0);
2794     basegfx::B2DTuple aScale(1.0, 1.0);
2795     basegfx::B2DTuple aTranslate(0.0, 0.0);
2796 
2797     if(GetPathPoly().count())
2798     {
2799         // copy geometry
2800         basegfx::B2DHomMatrix aMoveToZeroMatrix;
2801         rPolyPolygon = GetPathPoly();
2802 
2803         if(OBJ_LINE == meKind)
2804         {
2805             // ignore shear and rotate, just use scale and translate
2806             OSL_ENSURE(GetPathPoly().count() > 0 && GetPathPoly().getB2DPolygon(0).count() > 1, "OBJ_LINE with too few polygons (!)");
2807             // #i72287# use polygon without control points for range calculation. Do not change rPolyPolygon
2808             // itself, else this method will no longer return the full polygon information (curve will
2809             // be lost)
2810             const basegfx::B2DRange aPolyRangeNoCurve(basegfx::utils::getRange(rPolyPolygon));
2811             aScale = aPolyRangeNoCurve.getRange();
2812             aTranslate = aPolyRangeNoCurve.getMinimum();
2813 
2814             // define matrix for move polygon to zero point
2815             aMoveToZeroMatrix.translate(-aTranslate.getX(), -aTranslate.getY());
2816         }
2817         else
2818         {
2819             if(aGeo.nShearAngle || aGeo.nRotationAngle)
2820             {
2821                 // get rotate and shear in drawingLayer notation
2822                 fRotate = aGeo.nRotationAngle * F_PI18000;
2823                 fShearX = aGeo.nShearAngle * F_PI18000;
2824 
2825                 // build mathematically correct (negative shear and rotate) object transform
2826                 // containing shear and rotate to extract unsheared, unrotated polygon
2827                 basegfx::B2DHomMatrix aObjectMatrix;
2828                 aObjectMatrix.shearX(tan((36000 - aGeo.nShearAngle) * F_PI18000));
2829                 aObjectMatrix.rotate((36000 - aGeo.nRotationAngle) * F_PI18000);
2830 
2831                 // create inverse from it and back-transform polygon
2832                 basegfx::B2DHomMatrix aInvObjectMatrix(aObjectMatrix);
2833                 aInvObjectMatrix.invert();
2834                 rPolyPolygon.transform(aInvObjectMatrix);
2835 
2836                 // get range from unsheared, unrotated polygon and extract scale and translate.
2837                 // transform topLeft from it back to transformed state to get original
2838                 // topLeft (rotation center)
2839                 // #i72287# use polygon without control points for range calculation. Do not change rPolyPolygon
2840                 // itself, else this method will no longer return the full polygon information (curve will
2841                 // be lost)
2842                 const basegfx::B2DRange aCorrectedRangeNoCurve(basegfx::utils::getRange(rPolyPolygon));
2843                 aTranslate = aObjectMatrix * aCorrectedRangeNoCurve.getMinimum();
2844                 aScale = aCorrectedRangeNoCurve.getRange();
2845 
2846                 // define matrix for move polygon to zero point
2847                 // #i112280# Added missing minus for Y-Translation
2848                 aMoveToZeroMatrix.translate(-aCorrectedRangeNoCurve.getMinX(), -aCorrectedRangeNoCurve.getMinY());
2849             }
2850             else
2851             {
2852                 // get scale and translate from unsheared, unrotated polygon
2853                 // #i72287# use polygon without control points for range calculation. Do not change rPolyPolygon
2854                 // itself, else this method will no longer return the full polygon information (curve will
2855                 // be lost)
2856                 const basegfx::B2DRange aPolyRangeNoCurve(basegfx::utils::getRange(rPolyPolygon));
2857                 aScale = aPolyRangeNoCurve.getRange();
2858                 aTranslate = aPolyRangeNoCurve.getMinimum();
2859 
2860                 // define matrix for move polygon to zero point
2861                 aMoveToZeroMatrix.translate(-aTranslate.getX(), -aTranslate.getY());
2862             }
2863         }
2864 
2865         // move polygon to zero point with pre-defined matrix
2866         rPolyPolygon.transform(aMoveToZeroMatrix);
2867     }
2868 
2869     // position maybe relative to anchorpos, convert
2870     if( getSdrModelFromSdrObject().IsWriter() )
2871     {
2872         if(GetAnchorPos().X() || GetAnchorPos().Y())
2873         {
2874             aTranslate -= basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y());
2875         }
2876     }
2877 
2878     // build return value matrix
2879     rMatrix = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix(
2880         aScale,
2881         basegfx::fTools::equalZero(fShearX) ? 0.0 : tan(fShearX),
2882         basegfx::fTools::equalZero(fRotate) ? 0.0 : -fRotate,
2883         aTranslate);
2884 
2885     return true;
2886 }
2887 
2888 // Sets the base geometry of the object using infos contained in the homogeneous 3x3 matrix.
2889 // If it's an SdrPathObj it will use the provided geometry information. The Polygon has
2890 // to use (0,0) as upper left and will be scaled to the given size in the matrix.
2891 void SdrPathObj::TRSetBaseGeometry(const basegfx::B2DHomMatrix& rMatrix, const basegfx::B2DPolyPolygon& rPolyPolygon)
2892 {
2893     // break up matrix
2894     basegfx::B2DTuple aScale;
2895     basegfx::B2DTuple aTranslate;
2896     double fRotate, fShearX;
2897     rMatrix.decompose(aScale, aTranslate, fRotate, fShearX);
2898 
2899     // #i75086# Old DrawingLayer (GeoStat and geometry) does not support holding negative scalings
2900     // in X and Y which equal a 180 degree rotation. Recognize it and react accordingly
2901     if(basegfx::fTools::less(aScale.getX(), 0.0) && basegfx::fTools::less(aScale.getY(), 0.0))
2902     {
2903         aScale.setX(fabs(aScale.getX()));
2904         aScale.setY(fabs(aScale.getY()));
2905         fRotate = fmod(fRotate + F_PI, F_2PI);
2906     }
2907 
2908     // copy poly
2909     basegfx::B2DPolyPolygon aNewPolyPolygon(rPolyPolygon);
2910 
2911     // reset object shear and rotations
2912     aGeo.nRotationAngle = 0;
2913     aGeo.RecalcSinCos();
2914     aGeo.nShearAngle = 0;
2915     aGeo.RecalcTan();
2916 
2917     if( getSdrModelFromSdrObject().IsWriter() )
2918     {
2919         // if anchor is used, make position relative to it
2920         if(GetAnchorPos().X() || GetAnchorPos().Y())
2921         {
2922             aTranslate += basegfx::B2DTuple(GetAnchorPos().X(), GetAnchorPos().Y());
2923         }
2924     }
2925 
2926     // create transformation for polygon, set values at aGeo direct
2927     basegfx::B2DHomMatrix aTransform;
2928 
2929     // #i75086#
2930     // Given polygon is already scaled (for historical reasons), but not mirrored yet.
2931     // Thus, when scale is negative in X or Y, apply the needed mirroring accordingly.
2932     double fScaleX(basegfx::fTools::less(aScale.getX(), 0.0) ? -1.0 : 1.0);
2933     double fScaleY(basegfx::fTools::less(aScale.getY(), 0.0) ? -1.0 : 1.0);
2934 
2935     // tdf#98565, tdf#98584. While loading a shape, svg:width and svg:height is used to scale
2936     // the polygon. But draw:transform might introduce additional scaling factors, which need to
2937     // be applied to the polygon too, so aScale cannot be ignored while loading.
2938     // I use "maSnapRect.IsEmpty() && GetPathPoly().count()" to detect this case. Any better
2939     // idea? The behavior in other cases is the same as it was before this fix.
2940     if (maSnapRect.IsEmpty() && GetPathPoly().count())
2941     {
2942         // In case of a Writer document, the scaling factors were converted to twips. That is not
2943         // correct here, because width and height are already in the points coordinates and aScale
2944         // is no length but only a factor here. Convert back.
2945         if (getSdrModelFromSdrObject().IsWriter())
2946         {
2947             aScale.setX(aScale.getX() * 127.0 / 72.0);
2948             aScale.setY(aScale.getY() * 127.0 / 72.0);
2949         }
2950         fScaleX *= fabs(aScale.getX());
2951         fScaleY *= fabs(aScale.getY());
2952     }
2953 
2954     if (fScaleX != 1.0 || fScaleY != 1.0)
2955         aTransform.scale(fScaleX, fScaleY);
2956 
2957     if(!basegfx::fTools::equalZero(fShearX))
2958     {
2959         aTransform.shearX(tan(-atan(fShearX)));
2960         aGeo.nShearAngle = FRound(atan(fShearX) / F_PI18000);
2961         aGeo.RecalcTan();
2962     }
2963 
2964     if(!basegfx::fTools::equalZero(fRotate))
2965     {
2966         // #i78696#
2967         // fRotate is mathematically correct for linear transformations, so it's
2968         // the one to use for the geometry change
2969         aTransform.rotate(fRotate);
2970 
2971         // #i78696#
2972         // fRotate is mathematically correct, but aGeoStat.nRotationAngle is
2973         // mirrored -> mirror value here
2974         aGeo.nRotationAngle = NormAngle36000(FRound(-fRotate / F_PI18000));
2975         aGeo.RecalcSinCos();
2976     }
2977 
2978     if(!aTranslate.equalZero())
2979     {
2980         // #i39529# absolute positioning, so get current position (without control points (!))
2981         const basegfx::B2DRange aCurrentRange(basegfx::utils::getRange(aNewPolyPolygon));
2982         aTransform.translate(aTranslate.getX() - aCurrentRange.getMinX(), aTranslate.getY() - aCurrentRange.getMinY());
2983     }
2984 
2985     // transform polygon and trigger change
2986     aNewPolyPolygon.transform(aTransform);
2987     SetPathPoly(aNewPolyPolygon);
2988 }
2989 
2990 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
2991