1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <osl/endian.h>
21 #include <osl/diagnose.h>
22 #include <sal/log.hxx>
23 #include <tools/bigint.hxx>
24 #include <tools/debug.hxx>
25 #include <tools/helpers.hxx>
26 #include <tools/stream.hxx>
27 #include <tools/vcompat.hxx>
28 #include <tools/gen.hxx>
30 #include <o3tl/safeint.hxx>
31 #include <tools/line.hxx>
32 #include <tools/poly.hxx>
33 #include <basegfx/polygon/b2dpolygon.hxx>
34 #include <basegfx/point/b2dpoint.hxx>
35 #include <basegfx/vector/b2dvector.hxx>
36 #include <basegfx/polygon/b2dpolygontools.hxx>
37 #include <basegfx/curve/b2dcubicbezier.hxx>
47 constexpr int EDGE_LEFT
= 1;
48 constexpr int EDGE_TOP
= 2;
49 constexpr int EDGE_RIGHT
= 4;
50 constexpr int EDGE_BOTTOM
= 8;
51 constexpr int EDGE_HORZ
= EDGE_RIGHT
| EDGE_LEFT
;
52 constexpr int EDGE_VERT
= EDGE_TOP
| EDGE_BOTTOM
;
53 constexpr double SMALL_DVALUE
= 0.0000001;
54 #define FSQRT2 1.4142135623730950488016887242097
56 static double ImplGetParameter( const Point
& rCenter
, const Point
& rPt
, double fWR
, double fHR
)
58 const double nDX
= static_cast<double>(rPt
.X()) - rCenter
.X();
59 const double nDY
= static_cast<double>(rCenter
.Y()) - rPt
.Y();
60 double fAngle
= atan2(nDY
, nDX
);
62 return atan2(fWR
*sin(fAngle
), fHR
*cos(fAngle
));
65 ImplPolygon::ImplPolygon( sal_uInt16 nInitSize
)
67 ImplInitSize(nInitSize
, false);
70 ImplPolygon::ImplPolygon( const ImplPolygon
& rImpPoly
)
72 if ( rImpPoly
.mnPoints
)
74 mxPointAry
.reset(new Point
[rImpPoly
.mnPoints
]);
75 memcpy(mxPointAry
.get(), rImpPoly
.mxPointAry
.get(), rImpPoly
.mnPoints
* sizeof(Point
));
77 if( rImpPoly
.mxFlagAry
)
79 mxFlagAry
.reset(new PolyFlags
[rImpPoly
.mnPoints
]);
80 memcpy(mxFlagAry
.get(), rImpPoly
.mxFlagAry
.get(), rImpPoly
.mnPoints
);
84 mnPoints
= rImpPoly
.mnPoints
;
87 ImplPolygon::ImplPolygon( sal_uInt16 nInitSize
, const Point
* pInitAry
, const PolyFlags
* pInitFlags
)
91 mxPointAry
.reset(new Point
[nInitSize
]);
92 memcpy(mxPointAry
.get(), pInitAry
, nInitSize
* sizeof(Point
));
96 mxFlagAry
.reset(new PolyFlags
[nInitSize
]);
97 memcpy(mxFlagAry
.get(), pInitFlags
, nInitSize
);
101 mnPoints
= nInitSize
;
104 ImplPolygon::ImplPolygon( const tools::Rectangle
& rRect
)
106 if ( !rRect
.IsEmpty() )
109 mxPointAry
[0] = rRect
.TopLeft();
110 mxPointAry
[1] = rRect
.TopRight();
111 mxPointAry
[2] = rRect
.BottomRight();
112 mxPointAry
[3] = rRect
.BottomLeft();
113 mxPointAry
[4] = rRect
.TopLeft();
119 ImplPolygon::ImplPolygon( const tools::Rectangle
& rRect
, sal_uInt32 nHorzRound
, sal_uInt32 nVertRound
)
121 if ( !rRect
.IsEmpty() )
123 tools::Rectangle
aRect( rRect
);
124 aRect
.Normalize(); // SJ: i9140
126 nHorzRound
= std::min( nHorzRound
, static_cast<sal_uInt32
>(std::abs( aRect
.GetWidth() >> 1 )) );
127 nVertRound
= std::min( nVertRound
, static_cast<sal_uInt32
>(std::abs( aRect
.GetHeight() >> 1 )) );
129 if( !nHorzRound
&& !nVertRound
)
132 mxPointAry
[0] = aRect
.TopLeft();
133 mxPointAry
[1] = aRect
.TopRight();
134 mxPointAry
[2] = aRect
.BottomRight();
135 mxPointAry
[3] = aRect
.BottomLeft();
136 mxPointAry
[4] = aRect
.TopLeft();
140 const Point
aTL( aRect
.Left() + nHorzRound
, aRect
.Top() + nVertRound
);
141 const Point
aTR( aRect
.Right() - nHorzRound
, aRect
.Top() + nVertRound
);
142 const Point
aBR( aRect
.Right() - nHorzRound
, aRect
.Bottom() - nVertRound
);
143 const Point
aBL( aRect
.Left() + nHorzRound
, aRect
.Bottom() - nVertRound
);
144 tools::Polygon
aEllipsePoly( Point(), nHorzRound
, nVertRound
);
145 sal_uInt16 i
, nEnd
, nSize4
= aEllipsePoly
.GetSize() >> 2;
147 ImplInitSize(aEllipsePoly
.GetSize() + 1);
149 const Point
* pSrcAry
= aEllipsePoly
.GetConstPointAry();
150 Point
* pDstAry
= mxPointAry
.get();
152 for( i
= 0, nEnd
= nSize4
; i
< nEnd
; i
++ )
153 pDstAry
[ i
] = pSrcAry
[ i
] + aTR
;
155 for( nEnd
= nEnd
+ nSize4
; i
< nEnd
; i
++ )
156 pDstAry
[ i
] = pSrcAry
[ i
] + aTL
;
158 for( nEnd
= nEnd
+ nSize4
; i
< nEnd
; i
++ )
159 pDstAry
[ i
] = pSrcAry
[ i
] + aBL
;
161 for( nEnd
= nEnd
+ nSize4
; i
< nEnd
; i
++ )
162 pDstAry
[ i
] = pSrcAry
[ i
] + aBR
;
164 pDstAry
[ nEnd
] = pDstAry
[ 0 ];
171 ImplPolygon::ImplPolygon( const Point
& rCenter
, tools::Long nRadX
, tools::Long nRadY
)
176 // Compute default (depends on size)
178 const bool bOverflow
= o3tl::checked_multiply(nRadX
, nRadY
, nRadXY
);
181 nPoints
= std::clamp(
182 ( M_PI
* ( 1.5 * ( nRadX
+ nRadY
) -
183 sqrt( static_cast<double>(std::abs(nRadXY
)) ) ) ),
191 if( ( nRadX
> 32 ) && ( nRadY
> 32 ) && ( nRadX
+ nRadY
) < 8192 )
194 // Ceil number of points until divisible by four
195 nPoints
= (nPoints
+ 3) & ~3;
196 ImplInitSize(nPoints
);
199 sal_uInt16 nPoints2
= nPoints
>> 1;
200 sal_uInt16 nPoints4
= nPoints
>> 2;
202 double nAngleStep
= M_PI_2
/ ( nPoints4
- 1 );
204 for( i
=0, nAngle
= 0.0; i
< nPoints4
; i
++, nAngle
+= nAngleStep
)
206 tools::Long nX
= FRound( nRadX
* cos( nAngle
) );
207 tools::Long nY
= FRound( -nRadY
* sin( nAngle
) );
209 Point
* pPt
= &(mxPointAry
[i
]);
210 pPt
->setX( nX
+ rCenter
.X() );
211 pPt
->setY( nY
+ rCenter
.Y() );
212 pPt
= &(mxPointAry
[nPoints2
-i
-1]);
213 pPt
->setX( -nX
+ rCenter
.X() );
214 pPt
->setY( nY
+ rCenter
.Y() );
215 pPt
= &(mxPointAry
[i
+nPoints2
]);
216 pPt
->setX( -nX
+ rCenter
.X() );
217 pPt
->setY( -nY
+ rCenter
.Y() );
218 pPt
= &(mxPointAry
[nPoints
-i
-1]);
219 pPt
->setX( nX
+ rCenter
.X() );
220 pPt
->setY( -nY
+ rCenter
.Y() );
227 ImplPolygon::ImplPolygon(const tools::Rectangle
& rBound
, const Point
& rStart
, const Point
& rEnd
,
228 PolyStyle eStyle
, const bool bClockWiseArcDirection
)
230 const auto nWidth
= rBound
.GetWidth();
231 const auto nHeight
= rBound
.GetHeight();
233 if ((nWidth
!= 0) && (nHeight
!= 0))
235 const Point
aCenter(rBound
.Center());
236 // tdf#142268 Get Top Left corner of rectangle (the rectangle is not always correctly created)
237 const auto aBoundLeft
= rBound
.Left() < aCenter
.X() ? rBound
.Left() : rBound
.Right();
238 const auto aBoundTop
= rBound
.Top() < aCenter
.Y() ? rBound
.Top() : rBound
.Bottom();
239 const auto nRadX
= o3tl::saturating_sub(aCenter
.X(), aBoundLeft
);
240 const auto nRadY
= o3tl::saturating_sub(aCenter
.Y(), aBoundTop
);
244 const bool bOverflow
= o3tl::checked_multiply(nRadX
, nRadY
, nRadXY
);
247 nPoints
= std::clamp(
248 ( M_PI
* ( 1.5 * ( nRadX
+ nRadY
) -
249 sqrt( static_cast<double>(std::abs(nRadXY
)) ) ) ),
258 if (nRadX
> 32 && nRadY
> 32 && o3tl::saturating_add(nRadX
, nRadY
) < 8192)
262 const double fRadX
= nRadX
;
263 const double fRadY
= nRadY
;
264 const double fCenterX
= aCenter
.X();
265 const double fCenterY
= aCenter
.Y();
266 double fStart
= ImplGetParameter( aCenter
, rStart
, fRadX
, fRadY
);
267 double fEnd
= ImplGetParameter( aCenter
, rEnd
, fRadX
, fRadY
);
268 double fDiff
= fEnd
- fStart
;
273 if (bClockWiseArcDirection
== false)
275 // #i73608# If startPoint is equal to endPoint, then draw full circle instead of nothing (as Metafiles spec)
281 fDiff
= (2. * M_PI
) - fDiff
;
282 if (fDiff
> 2. * M_PI
)
286 // Proportionally shrink number of points( fDiff / (2PI) );
287 nPoints
= std::max(static_cast<sal_uInt16
>((fDiff
/ (2. * M_PI
)) * nPoints
), sal_uInt16(16));
288 fStep
= fDiff
/ (nPoints
- 1);
289 if (bClockWiseArcDirection
== true)
292 if (PolyStyle::Pie
== eStyle
)
294 const Point
aCenter2(FRound(fCenterX
), FRound(fCenterY
));
298 ImplInitSize(nPoints
+ 2);
299 mxPointAry
[0] = aCenter2
;
300 mxPointAry
[nEnd
] = aCenter2
;
304 ImplInitSize( ( PolyStyle::Chord
== eStyle
) ? ( nPoints
+ 1 ) : nPoints
);
309 for(; nStart
< nEnd
; nStart
++, fStart
+= fStep
)
311 Point
& rPt
= mxPointAry
[nStart
];
313 rPt
.setX( FRound( fCenterX
+ fRadX
* cos( fStart
) ) );
314 rPt
.setY( FRound( fCenterY
- fRadY
* sin( fStart
) ) );
317 if( PolyStyle::Chord
== eStyle
)
318 mxPointAry
[nPoints
] = mxPointAry
[0];
324 ImplPolygon::ImplPolygon( const Point
& rBezPt1
, const Point
& rCtrlPt1
,
325 const Point
& rBezPt2
, const Point
& rCtrlPt2
, sal_uInt16 nPoints
)
327 nPoints
= ( 0 == nPoints
) ? 25 : ( ( nPoints
< 2 ) ? 2 : nPoints
);
329 const double fInc
= 1.0 / ( nPoints
- 1 );
330 double fK_1
= 0.0, fK1_1
= 1.0;
331 double fK_2
, fK_3
, fK1_2
, fK1_3
;
332 const double fX0
= rBezPt1
.X();
333 const double fY0
= rBezPt1
.Y();
334 const double fX1
= 3.0 * rCtrlPt1
.X();
335 const double fY1
= 3.0 * rCtrlPt1
.Y();
336 const double fX2
= 3.0 * rCtrlPt2
.X();
337 const double fY2
= 3.0 * rCtrlPt2
.Y();
338 const double fX3
= rBezPt2
.X();
339 const double fY3
= rBezPt2
.Y();
341 ImplInitSize(nPoints
);
343 for( sal_uInt16 i
= 0; i
< nPoints
; i
++, fK_1
+= fInc
, fK1_1
-= fInc
)
345 Point
& rPt
= mxPointAry
[i
];
355 double fK12
= fK_1
* fK1_2
;
356 double fK21
= fK_2
* fK1_1
;
358 rPt
.setX( FRound( fK1_3
* fX0
+ fK12
* fX1
+ fK21
* fX2
+ fK_3
* fX3
) );
359 rPt
.setY( FRound( fK1_3
* fY0
+ fK12
* fY1
+ fK21
* fY2
+ fK_3
* fY3
) );
363 // constructor to convert from basegfx::B2DPolygon
364 // #i76891# Needed to change from adding all control points (even for unused
365 // edges) and creating a fixed-size Polygon in the first run to creating the
366 // minimal Polygon. This requires a temporary Point- and Flag-Array for curves
367 // and a memcopy at ImplPolygon creation, but contains no zero-controlpoints
368 // for straight edges.
369 ImplPolygon::ImplPolygon(const basegfx::B2DPolygon
& rPolygon
)
372 const bool bCurve(rPolygon
.areControlPointsUsed());
373 const bool bClosed(rPolygon
.isClosed());
374 sal_uInt32
nB2DLocalCount(rPolygon
.count());
378 // #127979# Reduce source point count hard to the limit of the tools Polygon
379 if(nB2DLocalCount
> ((0x0000ffff / 3) - 1))
381 OSL_FAIL("Polygon::Polygon: Too many points in given B2DPolygon, need to reduce hard to maximum of tools Polygon (!)");
382 nB2DLocalCount
= ((0x0000ffff / 3) - 1);
385 // calculate target point count
386 const sal_uInt32
nLoopCount(bClosed
? nB2DLocalCount
: (nB2DLocalCount
? nB2DLocalCount
- 1 : 0 ));
390 // calculate maximum array size and allocate; prepare insert index
391 const sal_uInt32
nMaxTargetCount((nLoopCount
* 3) + 1);
392 ImplInitSize(static_cast< sal_uInt16
>(nMaxTargetCount
), true);
394 // prepare insert index and current point
395 sal_uInt32
nArrayInsert(0);
396 basegfx::B2DCubicBezier aBezier
;
397 aBezier
.setStartPoint(rPolygon
.getB2DPoint(0));
399 for(sal_uInt32
a(0); a
< nLoopCount
; a
++)
401 // add current point (always) and remember StartPointIndex for evtl. later corrections
402 const Point
aStartPoint(FRound(aBezier
.getStartPoint().getX()), FRound(aBezier
.getStartPoint().getY()));
403 const sal_uInt32
nStartPointIndex(nArrayInsert
);
404 mxPointAry
[nStartPointIndex
] = aStartPoint
;
405 mxFlagAry
[nStartPointIndex
] = PolyFlags::Normal
;
408 // prepare next segment
409 const sal_uInt32
nNextIndex((a
+ 1) % nB2DLocalCount
);
410 aBezier
.setEndPoint(rPolygon
.getB2DPoint(nNextIndex
));
411 aBezier
.setControlPointA(rPolygon
.getNextControlPoint(a
));
412 aBezier
.setControlPointB(rPolygon
.getPrevControlPoint(nNextIndex
));
414 if(aBezier
.isBezier())
416 // if one is used, add always two control points due to the old schema
417 mxPointAry
[nArrayInsert
] = Point(FRound(aBezier
.getControlPointA().getX()), FRound(aBezier
.getControlPointA().getY()));
418 mxFlagAry
[nArrayInsert
] = PolyFlags::Control
;
421 mxPointAry
[nArrayInsert
] = Point(FRound(aBezier
.getControlPointB().getX()), FRound(aBezier
.getControlPointB().getY()));
422 mxFlagAry
[nArrayInsert
] = PolyFlags::Control
;
426 // test continuity with previous control point to set flag value
427 if(aBezier
.getControlPointA() != aBezier
.getStartPoint() && (bClosed
|| a
))
429 const basegfx::B2VectorContinuity
eCont(rPolygon
.getContinuityInPoint(a
));
431 if(basegfx::B2VectorContinuity::C1
== eCont
)
433 mxFlagAry
[nStartPointIndex
] = PolyFlags::Smooth
;
435 else if(basegfx::B2VectorContinuity::C2
== eCont
)
437 mxFlagAry
[nStartPointIndex
] = PolyFlags::Symmetric
;
441 // prepare next polygon step
442 aBezier
.setStartPoint(aBezier
.getEndPoint());
447 // add first point again as closing point due to old definition
448 mxPointAry
[nArrayInsert
] = mxPointAry
[0];
449 mxFlagAry
[nArrayInsert
] = PolyFlags::Normal
;
454 // add last point as closing point
455 const basegfx::B2DPoint
aClosingPoint(rPolygon
.getB2DPoint(nB2DLocalCount
- 1));
456 const Point
aEnd(FRound(aClosingPoint
.getX()), FRound(aClosingPoint
.getY()));
457 mxPointAry
[nArrayInsert
] = aEnd
;
458 mxFlagAry
[nArrayInsert
] = PolyFlags::Normal
;
462 DBG_ASSERT(nArrayInsert
<= nMaxTargetCount
, "Polygon::Polygon from basegfx::B2DPolygon: wrong max point count estimation (!)");
464 if(nArrayInsert
!= nMaxTargetCount
)
466 ImplSetSize(static_cast< sal_uInt16
>(nArrayInsert
));
472 // #127979# Reduce source point count hard to the limit of the tools Polygon
473 if(nB2DLocalCount
> (0x0000ffff - 1))
475 OSL_FAIL("Polygon::Polygon: Too many points in given B2DPolygon, need to reduce hard to maximum of tools Polygon (!)");
476 nB2DLocalCount
= (0x0000ffff - 1);
481 // point list creation
482 const sal_uInt32
nTargetCount(nB2DLocalCount
+ (bClosed
? 1 : 0));
483 ImplInitSize(static_cast< sal_uInt16
>(nTargetCount
));
484 sal_uInt16
nIndex(0);
486 for(sal_uInt32
a(0); a
< nB2DLocalCount
; a
++)
488 basegfx::B2DPoint
aB2DPoint(rPolygon
.getB2DPoint(a
));
489 Point
aPoint(FRound(aB2DPoint
.getX()), FRound(aB2DPoint
.getY()));
490 mxPointAry
[nIndex
++] = aPoint
;
495 // add first point as closing point
496 mxPointAry
[nIndex
] = mxPointAry
[0];
502 bool ImplPolygon::operator==( const ImplPolygon
& rCandidate
) const
504 return mnPoints
== rCandidate
.mnPoints
&&
505 mxFlagAry
.get() == rCandidate
.mxFlagAry
.get() &&
506 mxPointAry
.get() == rCandidate
.mxPointAry
.get();
509 void ImplPolygon::ImplInitSize(sal_uInt16 nInitSize
, bool bFlags
)
513 mxPointAry
.reset(new Point
[nInitSize
]);
518 mxFlagAry
.reset(new PolyFlags
[nInitSize
]);
519 memset(mxFlagAry
.get(), 0, nInitSize
);
522 mnPoints
= nInitSize
;
525 void ImplPolygon::ImplSetSize( sal_uInt16 nNewSize
, bool bResize
)
527 if( mnPoints
== nNewSize
)
530 std::unique_ptr
<Point
[]> xNewAry
;
534 const std::size_t nNewSz(static_cast<std::size_t>(nNewSize
)*sizeof(Point
));
535 xNewAry
.reset(new Point
[nNewSize
]);
539 // Copy the old points
540 if ( mnPoints
< nNewSize
)
542 // New points are already implicitly initialized to zero
543 const std::size_t nOldSz(mnPoints
* sizeof(Point
));
545 memcpy(xNewAry
.get(), mxPointAry
.get(), nOldSz
);
550 memcpy(xNewAry
.get(), mxPointAry
.get(), nNewSz
);
555 mxPointAry
= std::move(xNewAry
);
557 // take FlagArray into account, if applicable
560 std::unique_ptr
<PolyFlags
[]> xNewFlagAry
;
564 xNewFlagAry
.reset(new PolyFlags
[nNewSize
]);
568 // copy the old flags
569 if ( mnPoints
< nNewSize
)
571 // initialize new flags to zero
572 memset(xNewFlagAry
.get() + mnPoints
, 0, nNewSize
-mnPoints
);
573 memcpy(xNewFlagAry
.get(), mxFlagAry
.get(), mnPoints
);
576 memcpy(xNewFlagAry
.get(), mxFlagAry
.get(), nNewSize
);
580 mxFlagAry
= std::move(xNewFlagAry
);
586 bool ImplPolygon::ImplSplit( sal_uInt16 nPos
, sal_uInt16 nSpace
, ImplPolygon
const * pInitPoly
)
588 //Can't fit this in :-(, throw ?
589 if (mnPoints
+ nSpace
> USHRT_MAX
)
591 SAL_WARN("tools", "Polygon needs " << mnPoints
+ nSpace
<< " points, but only " << USHRT_MAX
<< " possible");
595 const sal_uInt16 nNewSize
= mnPoints
+ nSpace
;
596 const std::size_t nSpaceSize
= static_cast<std::size_t>(nSpace
) * sizeof(Point
);
598 if( nPos
>= mnPoints
)
600 // Append at the back
602 ImplSetSize( nNewSize
);
606 memcpy(mxPointAry
.get() + nPos
, pInitPoly
->mxPointAry
.get(), nSpaceSize
);
608 if (pInitPoly
->mxFlagAry
)
609 memcpy(mxFlagAry
.get() + nPos
, pInitPoly
->mxFlagAry
.get(), nSpace
);
614 const sal_uInt16 nSecPos
= nPos
+ nSpace
;
615 const sal_uInt16 nRest
= mnPoints
- nPos
;
617 std::unique_ptr
<Point
[]> xNewAry(new Point
[nNewSize
]);
618 memcpy(xNewAry
.get(), mxPointAry
.get(), nPos
* sizeof(Point
));
621 memcpy(xNewAry
.get() + nPos
, pInitPoly
->mxPointAry
.get(), nSpaceSize
);
623 memcpy(xNewAry
.get() + nSecPos
, mxPointAry
.get() + nPos
, nRest
* sizeof(Point
));
624 mxPointAry
= std::move(xNewAry
);
626 // consider FlagArray
629 std::unique_ptr
<PolyFlags
[]> xNewFlagAry(new PolyFlags
[nNewSize
]);
631 memcpy(xNewFlagAry
.get(), mxFlagAry
.get(), nPos
);
633 if (pInitPoly
&& pInitPoly
->mxFlagAry
)
634 memcpy(xNewFlagAry
.get() + nPos
, pInitPoly
->mxFlagAry
.get(), nSpace
);
636 memset(xNewFlagAry
.get() + nPos
, 0, nSpace
);
638 memcpy(xNewFlagAry
.get() + nSecPos
, mxFlagAry
.get() + nPos
, nRest
);
639 mxFlagAry
= std::move(xNewFlagAry
);
648 void ImplPolygon::ImplCreateFlagArray()
652 mxFlagAry
.reset(new PolyFlags
[mnPoints
]);
653 memset(mxFlagAry
.get(), 0, mnPoints
);
659 class ImplPointFilter
662 virtual void LastPoint() = 0;
663 virtual void Input( const Point
& rPoint
) = 0;
666 ~ImplPointFilter() {}
669 class ImplPolygonPointFilter
: public ImplPointFilter
674 explicit ImplPolygonPointFilter(sal_uInt16 nDestSize
)
680 virtual ~ImplPolygonPointFilter()
684 virtual void LastPoint() override
;
685 virtual void Input( const Point
& rPoint
) override
;
687 ImplPolygon
& get() { return maPoly
; }
692 void ImplPolygonPointFilter::Input( const Point
& rPoint
)
694 if ( !mnSize
|| (rPoint
!= maPoly
.mxPointAry
[mnSize
-1]) )
697 if ( mnSize
> maPoly
.mnPoints
)
698 maPoly
.ImplSetSize( mnSize
);
699 maPoly
.mxPointAry
[mnSize
-1] = rPoint
;
703 void ImplPolygonPointFilter::LastPoint()
705 if ( mnSize
< maPoly
.mnPoints
)
706 maPoly
.ImplSetSize( mnSize
);
711 class ImplEdgePointFilter
: public ImplPointFilter
715 ImplPointFilter
& mrNextFilter
;
716 const tools::Long mnLow
;
717 const tools::Long mnHigh
;
723 ImplEdgePointFilter( int nEdge
, tools::Long nLow
, tools::Long nHigh
,
724 ImplPointFilter
& rNextFilter
) :
725 mrNextFilter( rNextFilter
),
734 virtual ~ImplEdgePointFilter() {}
736 Point
EdgeSection( const Point
& rPoint
, int nEdge
) const;
737 int VisibleSide( const Point
& rPoint
) const;
738 bool IsPolygon() const
739 { return maFirstPoint
== maLastPoint
; }
741 virtual void Input( const Point
& rPoint
) override
;
742 virtual void LastPoint() override
;
747 inline int ImplEdgePointFilter::VisibleSide( const Point
& rPoint
) const
749 if ( mnEdge
& EDGE_HORZ
)
751 return rPoint
.X() < mnLow
? EDGE_LEFT
:
752 rPoint
.X() > mnHigh
? EDGE_RIGHT
: 0;
756 return rPoint
.Y() < mnLow
? EDGE_TOP
:
757 rPoint
.Y() > mnHigh
? EDGE_BOTTOM
: 0;
761 Point
ImplEdgePointFilter::EdgeSection( const Point
& rPoint
, int nEdge
) const
763 tools::Long lx
= maLastPoint
.X();
764 tools::Long ly
= maLastPoint
.Y();
765 tools::Long md
= rPoint
.X() - lx
;
766 tools::Long mn
= rPoint
.Y() - ly
;
770 if ( nEdge
& EDGE_VERT
)
772 nNewY
= (nEdge
== EDGE_TOP
) ? mnLow
: mnHigh
;
773 tools::Long dy
= nNewY
- ly
;
776 else if ( (LONG_MAX
/ std::abs(md
)) >= std::abs(dy
) )
777 nNewX
= (dy
* md
) / mn
+ lx
;
793 nNewX
= static_cast<tools::Long
>(ady
) + lx
;
798 nNewX
= (nEdge
== EDGE_LEFT
) ? mnLow
: mnHigh
;
799 tools::Long dx
= nNewX
- lx
;
802 else if ( (LONG_MAX
/ std::abs(mn
)) >= std::abs(dx
) )
803 nNewY
= (dx
* mn
) / md
+ ly
;
819 nNewY
= static_cast<tools::Long
>(adx
) + ly
;
823 return Point( nNewX
, nNewY
);
826 void ImplEdgePointFilter::Input( const Point
& rPoint
)
828 int nOutside
= VisibleSide( rPoint
);
832 maFirstPoint
= rPoint
;
835 mrNextFilter
.Input( rPoint
);
837 else if ( rPoint
== maLastPoint
)
839 else if ( !nOutside
)
842 mrNextFilter
.Input( EdgeSection( rPoint
, mnLastOutside
) );
843 mrNextFilter
.Input( rPoint
);
845 else if ( !mnLastOutside
)
846 mrNextFilter
.Input( EdgeSection( rPoint
, nOutside
) );
847 else if ( nOutside
!= mnLastOutside
)
849 mrNextFilter
.Input( EdgeSection( rPoint
, mnLastOutside
) );
850 mrNextFilter
.Input( EdgeSection( rPoint
, nOutside
) );
853 maLastPoint
= rPoint
;
854 mnLastOutside
= nOutside
;
857 void ImplEdgePointFilter::LastPoint()
861 int nOutside
= VisibleSide( maFirstPoint
);
863 if ( nOutside
!= mnLastOutside
)
864 Input( maFirstPoint
);
865 mrNextFilter
.LastPoint();
872 tools::Polygon
Polygon::SubdivideBezier( const tools::Polygon
& rPoly
)
874 tools::Polygon aPoly
;
876 // #100127# Use adaptive subdivide instead of fixed 25 segments
877 rPoly
.AdaptiveSubdivide( aPoly
);
882 Polygon::Polygon() : mpImplPolygon(ImplPolygon())
886 Polygon::Polygon( sal_uInt16 nSize
) : mpImplPolygon(ImplPolygon(nSize
))
890 Polygon::Polygon( sal_uInt16 nPoints
, const Point
* pPtAry
, const PolyFlags
* pFlagAry
) : mpImplPolygon(ImplPolygon(nPoints
, pPtAry
, pFlagAry
))
894 Polygon::Polygon( const tools::Polygon
& rPoly
) : mpImplPolygon(rPoly
.mpImplPolygon
)
898 Polygon::Polygon( tools::Polygon
&& rPoly
) noexcept
899 : mpImplPolygon(std::move(rPoly
.mpImplPolygon
))
903 Polygon::Polygon( const tools::Rectangle
& rRect
) : mpImplPolygon(ImplPolygon(rRect
))
907 Polygon::Polygon( const tools::Rectangle
& rRect
, sal_uInt32 nHorzRound
, sal_uInt32 nVertRound
)
908 : mpImplPolygon(ImplPolygon(rRect
, nHorzRound
, nVertRound
))
912 Polygon::Polygon( const Point
& rCenter
, tools::Long nRadX
, tools::Long nRadY
)
913 : mpImplPolygon(ImplPolygon(rCenter
, nRadX
, nRadY
))
917 Polygon::Polygon(const tools::Rectangle
& rBound
, const Point
& rStart
, const Point
& rEnd
,
918 PolyStyle eStyle
, const bool bClockWiseArcDirection
)
919 : mpImplPolygon(ImplPolygon(rBound
, rStart
, rEnd
, eStyle
, bClockWiseArcDirection
))
923 Polygon::Polygon( const Point
& rBezPt1
, const Point
& rCtrlPt1
,
924 const Point
& rBezPt2
, const Point
& rCtrlPt2
,
925 sal_uInt16 nPoints
) : mpImplPolygon(ImplPolygon(rBezPt1
, rCtrlPt1
, rBezPt2
, rCtrlPt2
, nPoints
))
933 Point
* Polygon::GetPointAry()
935 return mpImplPolygon
->mxPointAry
.get();
938 const Point
* Polygon::GetConstPointAry() const
940 return mpImplPolygon
->mxPointAry
.get();
943 const PolyFlags
* Polygon::GetConstFlagAry() const
945 return mpImplPolygon
->mxFlagAry
.get();
948 void Polygon::SetPoint( const Point
& rPt
, sal_uInt16 nPos
)
950 DBG_ASSERT( nPos
< mpImplPolygon
->mnPoints
,
951 "Polygon::SetPoint(): nPos >= nPoints" );
953 mpImplPolygon
->mxPointAry
[nPos
] = rPt
;
956 void Polygon::SetFlags( sal_uInt16 nPos
, PolyFlags eFlags
)
958 DBG_ASSERT( nPos
< mpImplPolygon
->mnPoints
,
959 "Polygon::SetFlags(): nPos >= nPoints" );
961 // we do only want to create the flag array if there
962 // is at least one flag different to PolyFlags::Normal
963 if ( eFlags
!= PolyFlags::Normal
)
965 mpImplPolygon
->ImplCreateFlagArray();
966 mpImplPolygon
->mxFlagAry
[ nPos
] = eFlags
;
970 const Point
& Polygon::GetPoint( sal_uInt16 nPos
) const
972 DBG_ASSERT( nPos
< mpImplPolygon
->mnPoints
,
973 "Polygon::GetPoint(): nPos >= nPoints" );
975 return mpImplPolygon
->mxPointAry
[nPos
];
978 PolyFlags
Polygon::GetFlags( sal_uInt16 nPos
) const
980 DBG_ASSERT( nPos
< mpImplPolygon
->mnPoints
,
981 "Polygon::GetFlags(): nPos >= nPoints" );
982 return mpImplPolygon
->mxFlagAry
983 ? mpImplPolygon
->mxFlagAry
[ nPos
]
987 bool Polygon::HasFlags() const
989 return bool(mpImplPolygon
->mxFlagAry
);
992 bool Polygon::IsRect() const
994 bool bIsRect
= false;
995 if (!mpImplPolygon
->mxFlagAry
)
997 if ( ( ( mpImplPolygon
->mnPoints
== 5 ) && ( mpImplPolygon
->mxPointAry
[ 0 ] == mpImplPolygon
->mxPointAry
[ 4 ] ) ) ||
998 ( mpImplPolygon
->mnPoints
== 4 ) )
1000 if ( ( mpImplPolygon
->mxPointAry
[ 0 ].X() == mpImplPolygon
->mxPointAry
[ 3 ].X() ) &&
1001 ( mpImplPolygon
->mxPointAry
[ 0 ].Y() == mpImplPolygon
->mxPointAry
[ 1 ].Y() ) &&
1002 ( mpImplPolygon
->mxPointAry
[ 1 ].X() == mpImplPolygon
->mxPointAry
[ 2 ].X() ) &&
1003 ( mpImplPolygon
->mxPointAry
[ 2 ].Y() == mpImplPolygon
->mxPointAry
[ 3 ].Y() ) )
1010 void Polygon::SetSize( sal_uInt16 nNewSize
)
1012 if( nNewSize
!= mpImplPolygon
->mnPoints
)
1014 mpImplPolygon
->ImplSetSize( nNewSize
);
1018 sal_uInt16
Polygon::GetSize() const
1020 return mpImplPolygon
->mnPoints
;
1023 void Polygon::Clear()
1025 mpImplPolygon
= ImplType(ImplPolygon());
1028 double Polygon::CalcDistance( sal_uInt16 nP1
, sal_uInt16 nP2
) const
1030 DBG_ASSERT( nP1
< mpImplPolygon
->mnPoints
,
1031 "Polygon::CalcDistance(): nPos1 >= nPoints" );
1032 DBG_ASSERT( nP2
< mpImplPolygon
->mnPoints
,
1033 "Polygon::CalcDistance(): nPos2 >= nPoints" );
1035 const Point
& rP1
= mpImplPolygon
->mxPointAry
[ nP1
];
1036 const Point
& rP2
= mpImplPolygon
->mxPointAry
[ nP2
];
1037 const double fDx
= rP2
.X() - rP1
.X();
1038 const double fDy
= rP2
.Y() - rP1
.Y();
1040 return std::hypot( fDx
, fDy
);
1043 void Polygon::Optimize( PolyOptimizeFlags nOptimizeFlags
)
1045 sal_uInt16 nSize
= mpImplPolygon
->mnPoints
;
1047 if( !(bool(nOptimizeFlags
) && nSize
) )
1050 if( nOptimizeFlags
& PolyOptimizeFlags::EDGES
)
1052 const tools::Rectangle
aBound( GetBoundRect() );
1053 const double fArea
= ( aBound
.GetWidth() + aBound
.GetHeight() ) * 0.5;
1054 const sal_uInt16 nPercent
= 50;
1056 Optimize( PolyOptimizeFlags::NO_SAME
);
1057 ImplReduceEdges( *this, fArea
, nPercent
);
1059 else if( nOptimizeFlags
& PolyOptimizeFlags::NO_SAME
)
1061 tools::Polygon aNewPoly
;
1062 const Point
& rFirst
= mpImplPolygon
->mxPointAry
[ 0 ];
1064 while( nSize
&& ( mpImplPolygon
->mxPointAry
[ nSize
- 1 ] == rFirst
) )
1069 sal_uInt16 nLast
= 0, nNewCount
= 1;
1071 aNewPoly
.SetSize( nSize
);
1072 aNewPoly
[ 0 ] = rFirst
;
1074 for( sal_uInt16 i
= 1; i
< nSize
; i
++ )
1076 if( mpImplPolygon
->mxPointAry
[ i
] != mpImplPolygon
->mxPointAry
[ nLast
])
1079 aNewPoly
[ nNewCount
++ ] = mpImplPolygon
->mxPointAry
[ i
];
1083 if( nNewCount
== 1 )
1086 aNewPoly
.SetSize( nNewCount
);
1092 nSize
= mpImplPolygon
->mnPoints
;
1096 if( ( nOptimizeFlags
& PolyOptimizeFlags::CLOSE
) &&
1097 ( mpImplPolygon
->mxPointAry
[ 0 ] != mpImplPolygon
->mxPointAry
[ nSize
- 1 ] ) )
1099 SetSize( mpImplPolygon
->mnPoints
+ 1 );
1100 mpImplPolygon
->mxPointAry
[ mpImplPolygon
->mnPoints
- 1 ] = mpImplPolygon
->mxPointAry
[ 0 ];
1106 /** Recursively subdivide cubic bezier curve via deCasteljau.
1109 Output vector, where the subdivided polylines are written to.
1112 Squared difference of curve to a straight line
1115 Exactly four points, interpreted as support and control points of
1116 a cubic bezier curve. Must be in device coordinates, since stop
1117 criterion is based on the following assumption: the device has a
1118 finite resolution, it is thus sufficient to stop subdivision if the
1119 curve does not deviate more than one pixel from a straight line.
1122 static void ImplAdaptiveSubdivide( std::vector
<Point
>& rPoints
,
1123 const double old_d2
,
1126 const double P1x
, const double P1y
,
1127 const double P2x
, const double P2y
,
1128 const double P3x
, const double P3y
,
1129 const double P4x
, const double P4y
)
1131 // Hard limit on recursion depth, empiric number.
1132 enum {maxRecursionDepth
=128};
1134 // Perform bezier flatness test (lecture notes from R. Schaback,
1135 // Mathematics of Computer-Aided Design, Uni Goettingen, 2000)
1137 // ||P(t) - L(t)|| <= max ||b_j - b_0 - j/n(b_n - b_0)||
1140 // What is calculated here is an upper bound to the distance from
1141 // a line through b_0 and b_3 (P1 and P4 in our notation) and the
1142 // curve. We can drop 0 and n from the running indices, since the
1143 // argument of max becomes zero for those cases.
1144 const double fJ1x( P2x
- P1x
- 1.0/3.0*(P4x
- P1x
) );
1145 const double fJ1y( P2y
- P1y
- 1.0/3.0*(P4y
- P1y
) );
1146 const double fJ2x( P3x
- P1x
- 2.0/3.0*(P4x
- P1x
) );
1147 const double fJ2y( P3y
- P1y
- 2.0/3.0*(P4y
- P1y
) );
1148 const double distance2( ::std::max( fJ1x
*fJ1x
+ fJ1y
*fJ1y
,
1149 fJ2x
*fJ2x
+ fJ2y
*fJ2y
) );
1151 // stop if error measure does not improve anymore. This is a
1152 // safety guard against floating point inaccuracies.
1153 // stop at recursion level 128. This is a safety guard against
1154 // floating point inaccuracies.
1155 // stop if distance from line is guaranteed to be bounded by d
1157 recursionDepth
< maxRecursionDepth
&&
1159 rPoints
.size() < SAL_MAX_UINT16
)
1161 // deCasteljau bezier arc, split at t=0.5
1162 // Foley/vanDam, p. 508
1163 const double L1x( P1x
), L1y( P1y
);
1164 const double L2x( (P1x
+ P2x
)*0.5 ), L2y( (P1y
+ P2y
)*0.5 );
1165 const double Hx ( (P2x
+ P3x
)*0.5 ), Hy ( (P2y
+ P3y
)*0.5 );
1166 const double L3x( (L2x
+ Hx
)*0.5 ), L3y( (L2y
+ Hy
)*0.5 );
1167 const double R4x( P4x
), R4y( P4y
);
1168 const double R3x( (P3x
+ P4x
)*0.5 ), R3y( (P3y
+ P4y
)*0.5 );
1169 const double R2x( (Hx
+ R3x
)*0.5 ), R2y( (Hy
+ R3y
)*0.5 );
1170 const double R1x( (L3x
+ R2x
)*0.5 ), R1y( (L3y
+ R2y
)*0.5 );
1171 const double L4x( R1x
), L4y( R1y
);
1173 // subdivide further
1175 ImplAdaptiveSubdivide(rPoints
, distance2
, recursionDepth
, d2
, L1x
, L1y
, L2x
, L2y
, L3x
, L3y
, L4x
, L4y
);
1176 ImplAdaptiveSubdivide(rPoints
, distance2
, recursionDepth
, d2
, R1x
, R1y
, R2x
, R2y
, R3x
, R3y
, R4x
, R4y
);
1180 // requested resolution reached.
1181 // Add end points to output iterator.
1182 // order is preserved, since this is so to say depth first traversal.
1183 rPoints
.push_back(Point(FRound(P1x
), FRound(P1y
)));
1187 void Polygon::AdaptiveSubdivide( Polygon
& rResult
, const double d
) const
1189 if (!mpImplPolygon
->mxFlagAry
)
1196 sal_uInt16
nPts( GetSize() );
1197 ::std::vector
< Point
> aPoints
;
1198 aPoints
.reserve( nPts
);
1202 if( ( i
+ 3 ) < nPts
)
1204 PolyFlags
P1( mpImplPolygon
->mxFlagAry
[ i
] );
1205 PolyFlags
P4( mpImplPolygon
->mxFlagAry
[ i
+ 3 ] );
1207 if( ( PolyFlags::Normal
== P1
|| PolyFlags::Smooth
== P1
|| PolyFlags::Symmetric
== P1
) &&
1208 ( PolyFlags::Control
== mpImplPolygon
->mxFlagAry
[ i
+ 1 ] ) &&
1209 ( PolyFlags::Control
== mpImplPolygon
->mxFlagAry
[ i
+ 2 ] ) &&
1210 ( PolyFlags::Normal
== P4
|| PolyFlags::Smooth
== P4
|| PolyFlags::Symmetric
== P4
) )
1212 ImplAdaptiveSubdivide( aPoints
, d
*d
+1.0, 0, d
*d
,
1213 mpImplPolygon
->mxPointAry
[ i
].X(), mpImplPolygon
->mxPointAry
[ i
].Y(),
1214 mpImplPolygon
->mxPointAry
[ i
+1 ].X(), mpImplPolygon
->mxPointAry
[ i
+1 ].Y(),
1215 mpImplPolygon
->mxPointAry
[ i
+2 ].X(), mpImplPolygon
->mxPointAry
[ i
+2 ].Y(),
1216 mpImplPolygon
->mxPointAry
[ i
+3 ].X(), mpImplPolygon
->mxPointAry
[ i
+3 ].Y() );
1222 aPoints
.push_back(mpImplPolygon
->mxPointAry
[i
++]);
1224 if (aPoints
.size() >= SAL_MAX_UINT16
)
1226 OSL_ENSURE(aPoints
.size() < SAL_MAX_UINT16
,
1227 "Polygon::AdaptiveSubdivision created polygon too many points;"
1228 " using original polygon instead");
1230 // The resulting polygon can not hold all the points
1231 // that we have created so far. Stop the subdivision
1232 // and return a copy of the unmodified polygon.
1238 // fill result polygon
1239 rResult
= tools::Polygon( static_cast<sal_uInt16
>(aPoints
.size()) ); // ensure sufficient size for copy
1240 ::std::copy(aPoints
.begin(), aPoints
.end(), rResult
.mpImplPolygon
->mxPointAry
.get());
1252 explicit Vector2D( const Point
& rPoint
) : mfX( rPoint
.X() ), mfY( rPoint
.Y() ) {};
1253 double GetLength() const { return hypot( mfX
, mfY
); }
1254 Vector2D
& operator-=( const Vector2D
& rVec
) { mfX
-= rVec
.mfX
; mfY
-= rVec
.mfY
; return *this; }
1255 double Scalar( const Vector2D
& rVec
) const { return mfX
* rVec
.mfX
+ mfY
* rVec
.mfY
; }
1256 Vector2D
& Normalize();
1257 bool IsPositive( Vector2D
const & rVec
) const { return ( mfX
* rVec
.mfY
- mfY
* rVec
.mfX
) >= 0.0; }
1258 bool IsNegative( Vector2D
const & rVec
) const { return !IsPositive( rVec
); }
1263 Vector2D
& Vector2D::Normalize()
1265 double fLen
= Scalar( *this );
1267 if( ( fLen
!= 0.0 ) && ( fLen
!= 1.0 ) )
1269 fLen
= sqrt( fLen
);
1280 void Polygon::ImplReduceEdges( tools::Polygon
& rPoly
, const double& rArea
, sal_uInt16 nPercent
)
1282 const double fBound
= 2000.0 * ( 100 - nPercent
) * 0.01;
1283 sal_uInt16 nNumNoChange
= 0,
1286 while( nNumNoChange
< 2 )
1288 sal_uInt16 nPntCnt
= rPoly
.GetSize(), nNewPos
= 0;
1289 tools::Polygon
aNewPoly( nPntCnt
);
1290 bool bChangeInThisRun
= false;
1292 for( sal_uInt16 n
= 0; n
< nPntCnt
; n
++ )
1294 bool bDeletePoint
= false;
1296 if( ( n
+ nNumRuns
) % 2 )
1298 sal_uInt16 nIndPrev
= !n
? nPntCnt
- 1 : n
- 1;
1299 sal_uInt16 nIndPrevPrev
= !nIndPrev
? nPntCnt
- 1 : nIndPrev
- 1;
1300 sal_uInt16 nIndNext
= ( n
== nPntCnt
-1 ) ? 0 : n
+ 1;
1301 sal_uInt16 nIndNextNext
= ( nIndNext
== nPntCnt
- 1 ) ? 0 : nIndNext
+ 1;
1302 Vector2D
aVec1( rPoly
[ nIndPrev
] ); aVec1
-= Vector2D(rPoly
[ nIndPrevPrev
]);
1303 Vector2D
aVec2( rPoly
[ n
] ); aVec2
-= Vector2D(rPoly
[ nIndPrev
]);
1304 Vector2D
aVec3( rPoly
[ nIndNext
] ); aVec3
-= Vector2D(rPoly
[ n
]);
1305 Vector2D
aVec4( rPoly
[ nIndNextNext
] ); aVec4
-= Vector2D(rPoly
[ nIndNext
]);
1306 double fDist1
= aVec1
.GetLength(), fDist2
= aVec2
.GetLength();
1307 double fDist3
= aVec3
.GetLength(), fDist4
= aVec4
.GetLength();
1308 double fTurnB
= aVec2
.Normalize().Scalar( aVec3
.Normalize() );
1310 if( fabs( fTurnB
) < ( 1.0 + SMALL_DVALUE
) && fabs( fTurnB
) > ( 1.0 - SMALL_DVALUE
) )
1311 bDeletePoint
= true;
1314 Vector2D
aVecB( rPoly
[ nIndNext
] );
1315 aVecB
-= Vector2D(rPoly
[ nIndPrev
] );
1316 double fDistB
= aVecB
.GetLength();
1317 double fLenWithB
= fDist2
+ fDist3
;
1318 double fLenFact
= ( fDistB
!= 0.0 ) ? fLenWithB
/ fDistB
: 1.0;
1319 double fTurnPrev
= aVec1
.Normalize().Scalar( aVec2
);
1320 double fTurnNext
= aVec3
.Scalar( aVec4
.Normalize() );
1321 double fGradPrev
, fGradB
, fGradNext
;
1323 if( fabs( fTurnPrev
) < ( 1.0 + SMALL_DVALUE
) && fabs( fTurnPrev
) > ( 1.0 - SMALL_DVALUE
) )
1326 fGradPrev
= basegfx::rad2deg(acos(fTurnPrev
)) * (aVec1
.IsNegative(aVec2
) ? -1 : 1);
1328 fGradB
= basegfx::rad2deg(acos(fTurnB
)) * (aVec2
.IsNegative(aVec3
) ? -1 : 1);
1330 if( fabs( fTurnNext
) < ( 1.0 + SMALL_DVALUE
) && fabs( fTurnNext
) > ( 1.0 - SMALL_DVALUE
) )
1333 fGradNext
= basegfx::rad2deg(acos(fTurnNext
)) * (aVec3
.IsNegative(aVec4
) ? -1 : 1);
1335 if( ( fGradPrev
> 0.0 && fGradB
< 0.0 && fGradNext
> 0.0 ) ||
1336 ( fGradPrev
< 0.0 && fGradB
> 0.0 && fGradNext
< 0.0 ) )
1338 if( ( fLenFact
< ( FSQRT2
+ SMALL_DVALUE
) ) &&
1339 ( ( ( fDist1
+ fDist4
) / ( fDist2
+ fDist3
) ) * 2000.0 ) > fBound
)
1341 bDeletePoint
= true;
1346 double fRelLen
= 1.0 - sqrt( fDistB
/ rArea
);
1350 else if( fRelLen
> 1.0 )
1353 if( ( std::round( ( fLenFact
- 1.0 ) * 1000000.0 ) < fBound
) &&
1354 ( fabs( fGradB
) <= ( fRelLen
* fBound
* 0.01 ) ) )
1356 bDeletePoint
= true;
1363 aNewPoly
[ nNewPos
++ ] = rPoly
[ n
];
1365 bChangeInThisRun
= true;
1368 if( bChangeInThisRun
&& nNewPos
)
1370 aNewPoly
.SetSize( nNewPos
);
1381 void Polygon::Move( tools::Long nHorzMove
, tools::Long nVertMove
)
1383 // This check is required for DrawEngine
1384 if ( !nHorzMove
&& !nVertMove
)
1388 sal_uInt16 nCount
= mpImplPolygon
->mnPoints
;
1389 for ( sal_uInt16 i
= 0; i
< nCount
; i
++ )
1391 Point
& rPt
= mpImplPolygon
->mxPointAry
[i
];
1392 rPt
.AdjustX(nHorzMove
);
1393 rPt
.AdjustY(nVertMove
);
1397 void Polygon::Translate(const Point
& rTrans
)
1399 for ( sal_uInt16 i
= 0, nCount
= mpImplPolygon
->mnPoints
; i
< nCount
; i
++ )
1400 mpImplPolygon
->mxPointAry
[ i
] += rTrans
;
1403 void Polygon::Scale( double fScaleX
, double fScaleY
)
1405 for ( sal_uInt16 i
= 0, nCount
= mpImplPolygon
->mnPoints
; i
< nCount
; i
++ )
1407 Point
& rPnt
= mpImplPolygon
->mxPointAry
[i
];
1408 rPnt
.setX( static_cast<tools::Long
>( fScaleX
* rPnt
.X() ) );
1409 rPnt
.setY( static_cast<tools::Long
>( fScaleY
* rPnt
.Y() ) );
1413 void Polygon::Rotate( const Point
& rCenter
, Degree10 nAngle10
)
1415 nAngle10
%= 3600_deg10
;
1419 const double fAngle
= toRadians(nAngle10
);
1420 Rotate( rCenter
, sin( fAngle
), cos( fAngle
) );
1424 void Polygon::Rotate( const Point
& rCenter
, double fSin
, double fCos
)
1426 tools::Long nCenterX
= rCenter
.X();
1427 tools::Long nCenterY
= rCenter
.Y();
1429 for( sal_uInt16 i
= 0, nCount
= mpImplPolygon
->mnPoints
; i
< nCount
; i
++ )
1431 Point
& rPt
= mpImplPolygon
->mxPointAry
[ i
];
1433 const tools::Long nX
= rPt
.X() - nCenterX
;
1434 const tools::Long nY
= rPt
.Y() - nCenterY
;
1435 rPt
.setX( FRound(fCos
* nX
+ fSin
* nY
+ nCenterX
) );
1436 rPt
.setY( FRound(-(fSin
* nX
- fCos
* nY
- nCenterY
)) );
1440 void Polygon::Clip( const tools::Rectangle
& rRect
)
1442 // This algorithm is broken for bezier curves, they would get converted to lines.
1443 // Use PolyPolygon::Clip().
1444 assert( !HasFlags());
1446 // #105251# Normalize rect before edge filtering
1447 tools::Rectangle
aJustifiedRect( rRect
);
1448 aJustifiedRect
.Normalize();
1450 sal_uInt16 nSourceSize
= mpImplPolygon
->mnPoints
;
1451 ImplPolygonPointFilter
aPolygon( nSourceSize
);
1452 ImplEdgePointFilter
aHorzFilter( EDGE_HORZ
, aJustifiedRect
.Left(), aJustifiedRect
.Right(),
1454 ImplEdgePointFilter
aVertFilter( EDGE_VERT
, aJustifiedRect
.Top(), aJustifiedRect
.Bottom(),
1457 for ( sal_uInt16 i
= 0; i
< nSourceSize
; i
++ )
1458 aVertFilter
.Input( mpImplPolygon
->mxPointAry
[i
] );
1459 if ( aVertFilter
.IsPolygon() )
1460 aVertFilter
.LastPoint();
1462 aPolygon
.LastPoint();
1464 mpImplPolygon
= ImplType(aPolygon
.get());
1467 tools::Rectangle
Polygon::GetBoundRect() const
1469 // Removing the assert. Bezier curves have the attribute that each single
1470 // curve segment defined by four points can not exit the four-point polygon
1471 // defined by that points. This allows to say that the curve segment can also
1472 // never leave the Range of its defining points.
1473 // The result is that Polygon::GetBoundRect() may not create the minimal
1474 // BoundRect of the Polygon (to get that, use basegfx::B2DPolygon classes),
1475 // but will always create a valid BoundRect, at least as long as this method
1476 // 'blindly' travels over all points, including control points.
1478 // DBG_ASSERT( !mpImplPolygon->mxFlagAry.get(), "GetBoundRect could fail with beziers!" );
1480 sal_uInt16 nCount
= mpImplPolygon
->mnPoints
;
1482 return tools::Rectangle();
1484 tools::Long nXMin
, nXMax
, nYMin
, nYMax
;
1486 const Point
& pFirstPt
= mpImplPolygon
->mxPointAry
[0];
1487 nXMin
= nXMax
= pFirstPt
.X();
1488 nYMin
= nYMax
= pFirstPt
.Y();
1490 for ( sal_uInt16 i
= 0; i
< nCount
; i
++ )
1492 const Point
& rPt
= mpImplPolygon
->mxPointAry
[i
];
1494 if (rPt
.X() < nXMin
)
1496 if (rPt
.X() > nXMax
)
1498 if (rPt
.Y() < nYMin
)
1500 if (rPt
.Y() > nYMax
)
1504 return tools::Rectangle( nXMin
, nYMin
, nXMax
, nYMax
);
1507 bool Polygon::Contains( const Point
& rPoint
) const
1509 DBG_ASSERT( !mpImplPolygon
->mxFlagAry
, "IsInside could fail with beziers!" );
1511 const tools::Rectangle
aBound( GetBoundRect() );
1512 const Line
aLine( rPoint
, Point( aBound
.Right() + 100, rPoint
.Y() ) );
1513 sal_uInt16 nCount
= mpImplPolygon
->mnPoints
;
1514 sal_uInt16 nPCounter
= 0;
1516 if ( ( nCount
> 2 ) && aBound
.Contains( rPoint
) )
1518 Point
aPt1( mpImplPolygon
->mxPointAry
[ 0 ] );
1519 Point aIntersection
;
1520 Point aLastIntersection
;
1522 while ( ( aPt1
== mpImplPolygon
->mxPointAry
[ nCount
- 1 ] ) && ( nCount
> 3 ) )
1525 for ( sal_uInt16 i
= 1; i
<= nCount
; i
++ )
1527 const Point
& rPt2
= mpImplPolygon
->mxPointAry
[ ( i
< nCount
) ? i
: 0 ];
1529 if ( aLine
.Intersection( Line( aPt1
, rPt2
), aIntersection
) )
1531 // This avoids insertion of double intersections
1534 if ( aIntersection
!= aLastIntersection
)
1536 aLastIntersection
= aIntersection
;
1542 aLastIntersection
= aIntersection
;
1551 // is inside, if number of intersection points is odd
1552 return ( ( nPCounter
& 1 ) == 1 );
1555 void Polygon::Insert( sal_uInt16 nPos
, const Point
& rPt
)
1557 if( nPos
>= mpImplPolygon
->mnPoints
)
1558 nPos
= mpImplPolygon
->mnPoints
;
1560 if (mpImplPolygon
->ImplSplit(nPos
, 1))
1561 mpImplPolygon
->mxPointAry
[ nPos
] = rPt
;
1564 void Polygon::Insert( sal_uInt16 nPos
, const tools::Polygon
& rPoly
)
1566 const sal_uInt16 nInsertCount
= rPoly
.mpImplPolygon
->mnPoints
;
1570 if( nPos
>= mpImplPolygon
->mnPoints
)
1571 nPos
= mpImplPolygon
->mnPoints
;
1573 if (rPoly
.mpImplPolygon
->mxFlagAry
)
1574 mpImplPolygon
->ImplCreateFlagArray();
1576 mpImplPolygon
->ImplSplit( nPos
, nInsertCount
, rPoly
.mpImplPolygon
.get() );
1580 Point
& Polygon::operator[]( sal_uInt16 nPos
)
1582 assert( nPos
< mpImplPolygon
->mnPoints
);
1584 return mpImplPolygon
->mxPointAry
[nPos
];
1587 tools::Polygon
& Polygon::operator=( const tools::Polygon
& rPoly
)
1589 mpImplPolygon
= rPoly
.mpImplPolygon
;
1593 tools::Polygon
& Polygon::operator=( tools::Polygon
&& rPoly
) noexcept
1595 mpImplPolygon
= std::move(rPoly
.mpImplPolygon
);
1599 bool Polygon::operator==( const tools::Polygon
& rPoly
) const
1601 return (mpImplPolygon
== rPoly
.mpImplPolygon
);
1604 bool Polygon::IsEqual( const tools::Polygon
& rPoly
) const
1606 bool bIsEqual
= true;
1608 if ( GetSize() != rPoly
.GetSize() )
1612 for ( i
= 0; i
< GetSize(); i
++ )
1614 if ( ( GetPoint( i
) != rPoly
.GetPoint( i
) ) ||
1615 ( GetFlags( i
) != rPoly
.GetFlags( i
) ) )
1625 SvStream
& ReadPolygon( SvStream
& rIStream
, tools::Polygon
& rPoly
)
1627 sal_uInt16
nPoints(0);
1629 // read all points and create array
1630 rIStream
.ReadUInt16( nPoints
);
1632 const size_t nMaxRecordsPossible
= rIStream
.remainingSize() / (2 * sizeof(sal_Int32
));
1633 if (nPoints
> nMaxRecordsPossible
)
1635 SAL_WARN("tools", "Polygon claims " << nPoints
<< " records, but only " << nMaxRecordsPossible
<< " possible");
1636 nPoints
= nMaxRecordsPossible
;
1639 rPoly
.mpImplPolygon
->ImplSetSize( nPoints
, false );
1641 for (sal_uInt16 i
= 0; i
< nPoints
; i
++)
1643 sal_Int32
nTmpX(0), nTmpY(0);
1644 rIStream
.ReadInt32(nTmpX
).ReadInt32(nTmpY
);
1645 rPoly
.mpImplPolygon
->mxPointAry
[i
].setX(nTmpX
);
1646 rPoly
.mpImplPolygon
->mxPointAry
[i
].setY(nTmpY
);
1652 SvStream
& WritePolygon( SvStream
& rOStream
, const tools::Polygon
& rPoly
)
1654 sal_uInt16 nPoints
= rPoly
.GetSize();
1656 // Write number of points
1657 rOStream
.WriteUInt16( nPoints
);
1659 for (sal_uInt16 i
= 0; i
< nPoints
; i
++)
1661 rOStream
.WriteInt32(rPoly
.mpImplPolygon
->mxPointAry
[i
].X())
1662 .WriteInt32(rPoly
.mpImplPolygon
->mxPointAry
[i
].Y());
1668 void Polygon::ImplRead( SvStream
& rIStream
)
1670 sal_uInt8
bHasPolyFlags(0);
1672 ReadPolygon( rIStream
, *this );
1673 rIStream
.ReadUChar( bHasPolyFlags
);
1675 if ( bHasPolyFlags
)
1677 mpImplPolygon
->mxFlagAry
.reset(new PolyFlags
[mpImplPolygon
->mnPoints
]);
1678 auto nRead
= rIStream
.ReadBytes(mpImplPolygon
->mxFlagAry
.get(), mpImplPolygon
->mnPoints
);
1679 if (nRead
!= mpImplPolygon
->mnPoints
)
1681 SAL_WARN("tools", "Short read");
1682 memset(mpImplPolygon
->mxFlagAry
.get() + nRead
, 0, mpImplPolygon
->mnPoints
- nRead
);
1687 void Polygon::Read( SvStream
& rIStream
)
1689 VersionCompatRead
aCompat(rIStream
);
1691 ImplRead( rIStream
);
1694 void Polygon::ImplWrite( SvStream
& rOStream
) const
1696 bool bHasPolyFlags(mpImplPolygon
->mxFlagAry
);
1697 WritePolygon( rOStream
, *this );
1698 rOStream
.WriteBool(bHasPolyFlags
);
1700 if ( bHasPolyFlags
)
1701 rOStream
.WriteBytes(mpImplPolygon
->mxFlagAry
.get(), mpImplPolygon
->mnPoints
);
1704 void Polygon::Write( SvStream
& rOStream
) const
1706 VersionCompatWrite
aCompat(rOStream
, 1);
1708 ImplWrite( rOStream
);
1711 // #i74631#/#i115917# numerical correction method for B2DPolygon
1712 static void impCorrectContinuity(basegfx::B2DPolygon
& roPolygon
, sal_uInt32 nIndex
, PolyFlags nCFlag
)
1714 const sal_uInt32
nPointCount(roPolygon
.count());
1715 OSL_ENSURE(nIndex
< nPointCount
, "impCorrectContinuity: index access out of range (!)");
1717 if(nIndex
>= nPointCount
|| (PolyFlags::Smooth
!= nCFlag
&& PolyFlags::Symmetric
!= nCFlag
))
1720 if(!roPolygon
.isPrevControlPointUsed(nIndex
) || !roPolygon
.isNextControlPointUsed(nIndex
))
1723 // #i115917# Patch from osnola (modified, thanks for showing the problem)
1725 // The correction is needed because an integer polygon with control points
1726 // is converted to double precision. When C1 or C2 is used the involved vectors
1727 // may not have the same directions/lengths since these come from integer coordinates
1728 // and may have been snapped to different nearest integer coordinates. The snap error
1729 // is in the range of +-1 in y and y, thus 0.0 <= error <= sqrt(2.0). Nonetheless,
1730 // it needs to be corrected to be able to detect the continuity in this points
1733 // We only have the integer data here (already in double precision form, but no mantissa
1734 // used), so the best correction is to use:
1736 // for C1: The longest vector since it potentially has best preserved the original vector.
1737 // Even better the sum of the vectors, weighted by their length. This gives the
1738 // normal vector addition to get the vector itself, lengths need to be preserved.
1739 // for C2: The mediated vector(s) since both should be the same, but mirrored
1741 // extract the point and vectors
1742 const basegfx::B2DPoint
aPoint(roPolygon
.getB2DPoint(nIndex
));
1743 const basegfx::B2DVector
aNext(roPolygon
.getNextControlPoint(nIndex
) - aPoint
);
1744 const basegfx::B2DVector
aPrev(aPoint
- roPolygon
.getPrevControlPoint(nIndex
));
1746 // calculate common direction vector, normalize
1747 const basegfx::B2DVector
aDirection(aNext
+ aPrev
);
1748 const double fDirectionLen
= aDirection
.getLength();
1749 if (fDirectionLen
== 0.0)
1752 if (PolyFlags::Smooth
== nCFlag
)
1754 // C1: apply common direction vector, preserve individual lengths
1755 const double fInvDirectionLen(1.0 / fDirectionLen
);
1756 roPolygon
.setNextControlPoint(nIndex
, basegfx::B2DPoint(aPoint
+ (aDirection
* (aNext
.getLength() * fInvDirectionLen
))));
1757 roPolygon
.setPrevControlPoint(nIndex
, basegfx::B2DPoint(aPoint
- (aDirection
* (aPrev
.getLength() * fInvDirectionLen
))));
1759 else // PolyFlags::Symmetric
1761 // C2: get mediated length. Taking half of the unnormalized direction would be
1762 // an approximation, but not correct.
1763 const double fMedLength((aNext
.getLength() + aPrev
.getLength()) * (0.5 / fDirectionLen
));
1764 const basegfx::B2DVector
aScaledDirection(aDirection
* fMedLength
);
1766 // Bring Direction to correct length and apply
1767 roPolygon
.setNextControlPoint(nIndex
, basegfx::B2DPoint(aPoint
+ aScaledDirection
));
1768 roPolygon
.setPrevControlPoint(nIndex
, basegfx::B2DPoint(aPoint
- aScaledDirection
));
1772 // convert to basegfx::B2DPolygon and return
1773 basegfx::B2DPolygon
Polygon::getB2DPolygon() const
1775 basegfx::B2DPolygon aRetval
;
1776 const sal_uInt16
nCount(mpImplPolygon
->mnPoints
);
1780 if (mpImplPolygon
->mxFlagAry
)
1782 // handling for curves. Add start point
1783 const Point
aStartPoint(mpImplPolygon
->mxPointAry
[0]);
1784 PolyFlags
nPointFlag(mpImplPolygon
->mxFlagAry
[0]);
1785 aRetval
.append(basegfx::B2DPoint(aStartPoint
.X(), aStartPoint
.Y()));
1786 Point aControlA
, aControlB
;
1788 for(sal_uInt16
a(1); a
< nCount
;)
1790 bool bControlA(false);
1791 bool bControlB(false);
1793 if(PolyFlags::Control
== mpImplPolygon
->mxFlagAry
[a
])
1795 aControlA
= mpImplPolygon
->mxPointAry
[a
++];
1799 if(a
< nCount
&& PolyFlags::Control
== mpImplPolygon
->mxFlagAry
[a
])
1801 aControlB
= mpImplPolygon
->mxPointAry
[a
++];
1805 // assert invalid polygons
1806 OSL_ENSURE(bControlA
== bControlB
, "Polygon::getB2DPolygon: Invalid source polygon (!)");
1810 const Point
aEndPoint(mpImplPolygon
->mxPointAry
[a
]);
1815 aRetval
.appendBezierSegment(
1816 basegfx::B2DPoint(aControlA
.X(), aControlA
.Y()),
1817 basegfx::B2DPoint(aControlB
.X(), aControlB
.Y()),
1818 basegfx::B2DPoint(aEndPoint
.X(), aEndPoint
.Y()));
1820 impCorrectContinuity(aRetval
, aRetval
.count() - 2, nPointFlag
);
1824 // no bezier edge, add end point
1825 aRetval
.append(basegfx::B2DPoint(aEndPoint
.X(), aEndPoint
.Y()));
1828 nPointFlag
= mpImplPolygon
->mxFlagAry
[a
++];
1832 // if exist, remove double first/last points, set closed and correct control points
1833 basegfx::utils::checkClosed(aRetval
);
1835 if(aRetval
.isClosed())
1837 // closeWithGeometryChange did really close, so last point(s) were removed.
1838 // Correct the continuity in the changed point
1839 impCorrectContinuity(aRetval
, 0, mpImplPolygon
->mxFlagAry
[0]);
1844 // extra handling for non-curves (most-used case) for speedup
1845 for(sal_uInt16
a(0); a
< nCount
; a
++)
1847 // get point and add
1848 const Point
aPoint(mpImplPolygon
->mxPointAry
[a
]);
1849 aRetval
.append(basegfx::B2DPoint(aPoint
.X(), aPoint
.Y()));
1853 basegfx::utils::checkClosed(aRetval
);
1860 Polygon::Polygon(const basegfx::B2DPolygon
& rPolygon
) : mpImplPolygon(ImplPolygon(rPolygon
))
1864 } // namespace tools
1866 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */