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