Avoid potential negative array index access to cached text.
[LibreOffice.git] / tools / source / generic / poly.cxx
blob89405eea410a42bf05e81a2845656ebd811c1caa
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
29 #include <poly.h>
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>
39 #include <memory>
40 #include <vector>
41 #include <algorithm>
42 #include <cassert>
43 #include <cstring>
44 #include <limits.h>
45 #include <cmath>
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 )
89 if ( nInitSize )
91 mxPointAry.reset(new Point[nInitSize]);
92 memcpy(mxPointAry.get(), pInitAry, nInitSize * sizeof(Point));
94 if( pInitFlags )
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() )
108 ImplInitSize(5);
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();
115 else
116 mnPoints = 0;
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 )
131 ImplInitSize(5);
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();
138 else
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 ];
167 else
168 mnPoints = 0;
171 ImplPolygon::ImplPolygon( const Point& rCenter, tools::Long nRadX, tools::Long nRadY )
173 if( nRadX && nRadY )
175 sal_uInt16 nPoints;
176 // Compute default (depends on size)
177 tools::Long nRadXY;
178 const bool bOverflow = o3tl::checked_multiply(nRadX, nRadY, nRadXY);
179 if (!bOverflow)
181 nPoints = std::clamp(
182 ( M_PI * ( 1.5 * ( nRadX + nRadY ) -
183 sqrt( static_cast<double>(std::abs(nRadXY)) ) ) ),
184 32.0, 256.0 );
186 else
188 nPoints = 256;
191 if( ( nRadX > 32 ) && ( nRadY > 32 ) && ( nRadX + nRadY ) < 8192 )
192 nPoints >>= 1;
194 // Ceil number of points until divisible by four
195 nPoints = (nPoints + 3) & ~3;
196 ImplInitSize(nPoints);
198 sal_uInt16 i;
199 sal_uInt16 nPoints2 = nPoints >> 1;
200 sal_uInt16 nPoints4 = nPoints >> 2;
201 double nAngle;
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() );
223 else
224 mnPoints = 0;
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);
241 sal_uInt16 nPoints;
243 tools::Long nRadXY;
244 const bool bOverflow = o3tl::checked_multiply(nRadX, nRadY, nRadXY);
245 if (!bOverflow)
247 nPoints = std::clamp(
248 ( M_PI * ( 1.5 * ( nRadX + nRadY ) -
249 sqrt( static_cast<double>(std::abs(nRadXY)) ) ) ),
250 32.0, 256.0 );
252 else
254 nPoints = 256;
258 if (nRadX > 32 && nRadY > 32 && o3tl::saturating_add(nRadX, nRadY) < 8192)
259 nPoints >>= 1;
261 // compute threshold
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;
269 double fStep;
270 sal_uInt16 nStart;
271 sal_uInt16 nEnd;
273 if (bClockWiseArcDirection == false)
275 // #i73608# If startPoint is equal to endPoint, then draw full circle instead of nothing (as Metafiles spec)
276 if (fDiff <= 0.)
277 fDiff += 2. * M_PI;
279 else
281 fDiff = (2. * M_PI) - fDiff;
282 if (fDiff > 2. * M_PI)
283 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)
290 fStep = -fStep;
292 if (PolyStyle::Pie == eStyle)
294 const Point aCenter2(FRound(fCenterX), FRound(fCenterY));
296 nStart = 1;
297 nEnd = nPoints + 1;
298 ImplInitSize(nPoints + 2);
299 mxPointAry[0] = aCenter2;
300 mxPointAry[nEnd] = aCenter2;
302 else
304 ImplInitSize( ( PolyStyle::Chord == eStyle ) ? ( nPoints + 1 ) : nPoints );
305 nStart = 0;
306 nEnd = 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];
320 else
321 mnPoints = 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];
347 fK_2 = fK_1;
348 fK_2 *= fK_1;
349 fK_3 = fK_2;
350 fK_3 *= fK_1;
351 fK1_2 = fK1_1;
352 fK1_2 *= fK1_1;
353 fK1_3 = fK1_2;
354 fK1_3 *= fK1_1;
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)
370 : mnPoints(0)
372 const bool bCurve(rPolygon.areControlPointsUsed());
373 const bool bClosed(rPolygon.isClosed());
374 sal_uInt32 nB2DLocalCount(rPolygon.count());
376 if(bCurve)
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 ));
388 if(nLoopCount)
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;
406 nArrayInsert++;
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;
419 nArrayInsert++;
421 mxPointAry[nArrayInsert] = Point(FRound(aBezier.getControlPointB().getX()), FRound(aBezier.getControlPointB().getY()));
422 mxFlagAry[nArrayInsert] = PolyFlags::Control;
423 nArrayInsert++;
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());
445 if(bClosed)
447 // add first point again as closing point due to old definition
448 mxPointAry[nArrayInsert] = mxPointAry[0];
449 mxFlagAry[nArrayInsert] = PolyFlags::Normal;
450 nArrayInsert++;
452 else
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;
459 nArrayInsert++;
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));
470 else
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);
479 if(nB2DLocalCount)
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;
493 if(bClosed)
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)
511 if (nInitSize)
513 mxPointAry.reset(new Point[nInitSize]);
516 if (bFlags)
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 )
528 return;
530 std::unique_ptr<Point[]> xNewAry;
532 if (nNewSize)
534 const std::size_t nNewSz(static_cast<std::size_t>(nNewSize)*sizeof(Point));
535 xNewAry.reset(new Point[nNewSize]);
537 if ( bResize )
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));
544 if (mxPointAry)
545 memcpy(xNewAry.get(), mxPointAry.get(), nOldSz);
547 else
549 if (mxPointAry)
550 memcpy(xNewAry.get(), mxPointAry.get(), nNewSz);
555 mxPointAry = std::move(xNewAry);
557 // take FlagArray into account, if applicable
558 if( mxFlagAry )
560 std::unique_ptr<PolyFlags[]> xNewFlagAry;
562 if( nNewSize )
564 xNewFlagAry.reset(new PolyFlags[nNewSize]);
566 if( bResize )
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);
575 else
576 memcpy(xNewFlagAry.get(), mxFlagAry.get(), nNewSize);
580 mxFlagAry = std::move(xNewFlagAry);
583 mnPoints = nNewSize;
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");
592 return false;
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
601 nPos = mnPoints;
602 ImplSetSize( nNewSize );
604 if( pInitPoly )
606 memcpy(mxPointAry.get() + nPos, pInitPoly->mxPointAry.get(), nSpaceSize);
608 if (pInitPoly->mxFlagAry)
609 memcpy(mxFlagAry.get() + nPos, pInitPoly->mxFlagAry.get(), nSpace);
612 else
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));
620 if( pInitPoly )
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
627 if (mxFlagAry)
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);
635 else
636 memset(xNewFlagAry.get() + nPos, 0, nSpace);
638 memcpy(xNewFlagAry.get() + nSecPos, mxFlagAry.get() + nPos, nRest);
639 mxFlagAry = std::move(xNewFlagAry);
642 mnPoints = nNewSize;
645 return true;
648 void ImplPolygon::ImplCreateFlagArray()
650 if (!mxFlagAry)
652 mxFlagAry.reset(new PolyFlags[mnPoints]);
653 memset(mxFlagAry.get(), 0, mnPoints);
657 namespace {
659 class ImplPointFilter
661 public:
662 virtual void LastPoint() = 0;
663 virtual void Input( const Point& rPoint ) = 0;
665 protected:
666 ~ImplPointFilter() {}
669 class ImplPolygonPointFilter : public ImplPointFilter
671 ImplPolygon maPoly;
672 sal_uInt16 mnSize;
673 public:
674 explicit ImplPolygonPointFilter(sal_uInt16 nDestSize)
675 : maPoly(nDestSize)
676 , mnSize(0)
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]) )
696 mnSize++;
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 );
709 namespace {
711 class ImplEdgePointFilter : public ImplPointFilter
713 Point maFirstPoint;
714 Point maLastPoint;
715 ImplPointFilter& mrNextFilter;
716 const tools::Long mnLow;
717 const tools::Long mnHigh;
718 const int mnEdge;
719 int mnLastOutside;
720 bool mbFirst;
722 public:
723 ImplEdgePointFilter( int nEdge, tools::Long nLow, tools::Long nHigh,
724 ImplPointFilter& rNextFilter ) :
725 mrNextFilter( rNextFilter ),
726 mnLow( nLow ),
727 mnHigh( nHigh ),
728 mnEdge( nEdge ),
729 mnLastOutside( 0 ),
730 mbFirst( true )
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;
754 else
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;
767 tools::Long nNewX;
768 tools::Long nNewY;
770 if ( nEdge & EDGE_VERT )
772 nNewY = (nEdge == EDGE_TOP) ? mnLow : mnHigh;
773 tools::Long dy = nNewY - ly;
774 if ( !md )
775 nNewX = lx;
776 else if ( (LONG_MAX / std::abs(md)) >= std::abs(dy) )
777 nNewX = (dy * md) / mn + lx;
778 else
780 BigInt ady = dy;
781 ady *= md;
782 if( ady.IsNeg() )
783 if( mn < 0 )
784 ady += mn/2;
785 else
786 ady -= (mn-1)/2;
787 else
788 if( mn < 0 )
789 ady -= (mn+1)/2;
790 else
791 ady += mn/2;
792 ady /= mn;
793 nNewX = static_cast<tools::Long>(ady) + lx;
796 else
798 nNewX = (nEdge == EDGE_LEFT) ? mnLow : mnHigh;
799 tools::Long dx = nNewX - lx;
800 if ( !mn )
801 nNewY = ly;
802 else if ( (LONG_MAX / std::abs(mn)) >= std::abs(dx) )
803 nNewY = (dx * mn) / md + ly;
804 else
806 BigInt adx = dx;
807 adx *= mn;
808 if( adx.IsNeg() )
809 if( md < 0 )
810 adx += md/2;
811 else
812 adx -= (md-1)/2;
813 else
814 if( md < 0 )
815 adx -= (md+1)/2;
816 else
817 adx += md/2;
818 adx /= md;
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 );
830 if ( mbFirst )
832 maFirstPoint = rPoint;
833 mbFirst = false;
834 if ( !nOutside )
835 mrNextFilter.Input( rPoint );
837 else if ( rPoint == maLastPoint )
838 return;
839 else if ( !nOutside )
841 if ( mnLastOutside )
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()
859 if ( !mbFirst )
861 int nOutside = VisibleSide( maFirstPoint );
863 if ( nOutside != mnLastOutside )
864 Input( maFirstPoint );
865 mrNextFilter.LastPoint();
869 namespace tools
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 );
879 return 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))
929 Polygon::~Polygon()
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 ]
984 : PolyFlags::Normal;
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() ) )
1004 bIsRect = true;
1007 return bIsRect;
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) )
1048 return;
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 ) )
1065 nSize--;
1067 if( nSize > 1 )
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 ])
1078 nLast = i;
1079 aNewPoly[ nNewCount++ ] = mpImplPolygon->mxPointAry[ i ];
1083 if( nNewCount == 1 )
1084 aNewPoly.Clear();
1085 else
1086 aNewPoly.SetSize( nNewCount );
1089 *this = aNewPoly;
1092 nSize = mpImplPolygon->mnPoints;
1094 if( nSize > 1 )
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.
1108 @param rPoints
1109 Output vector, where the subdivided polylines are written to.
1111 @param d
1112 Squared difference of curve to a straight line
1114 @param P*
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,
1124 int recursionDepth,
1125 const double 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)||
1138 // 0<=j<=n
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
1156 if( old_d2 > d2 &&
1157 recursionDepth < maxRecursionDepth &&
1158 distance2 >= d2 &&
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
1174 ++recursionDepth;
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);
1178 else
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)
1191 rResult = *this;
1193 else
1195 sal_uInt16 i;
1196 sal_uInt16 nPts( GetSize() );
1197 ::std::vector< Point > aPoints;
1198 aPoints.reserve( nPts );
1200 for(i=0; i<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() );
1217 i += 3;
1218 continue;
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.
1233 rResult = *this;
1234 return;
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());
1244 namespace {
1246 class Vector2D
1248 private:
1249 double mfX;
1250 double mfY;
1251 public:
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 );
1270 if( fLen != 0.0 )
1272 mfX /= fLen;
1273 mfY /= fLen;
1277 return *this;
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,
1284 nNumRuns = 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;
1312 else
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 ) )
1324 fGradPrev = 0.0;
1325 else
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 ) )
1331 fGradNext = 0.0;
1332 else
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;
1344 else
1346 double fRelLen = 1.0 - sqrt( fDistB / rArea );
1348 if( fRelLen < 0.0 )
1349 fRelLen = 0.0;
1350 else if( fRelLen > 1.0 )
1351 fRelLen = 1.0;
1353 if( ( std::round( ( fLenFact - 1.0 ) * 1000000.0 ) < fBound ) &&
1354 ( fabs( fGradB ) <= ( fRelLen * fBound * 0.01 ) ) )
1356 bDeletePoint = true;
1362 if( !bDeletePoint )
1363 aNewPoly[ nNewPos++ ] = rPoly[ n ];
1364 else
1365 bChangeInThisRun = true;
1368 if( bChangeInThisRun && nNewPos )
1370 aNewPoly.SetSize( nNewPos );
1371 rPoly = aNewPoly;
1372 nNumNoChange = 0;
1374 else
1375 nNumNoChange++;
1377 nNumRuns++;
1381 void Polygon::Move( tools::Long nHorzMove, tools::Long nVertMove )
1383 // This check is required for DrawEngine
1384 if ( !nHorzMove && !nVertMove )
1385 return;
1387 // Move points
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;
1417 if( nAngle10 )
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(),
1453 aPolygon );
1454 ImplEdgePointFilter aVertFilter( EDGE_VERT, aJustifiedRect.Top(), aJustifiedRect.Bottom(),
1455 aHorzFilter );
1457 for ( sal_uInt16 i = 0; i < nSourceSize; i++ )
1458 aVertFilter.Input( mpImplPolygon->mxPointAry[i] );
1459 if ( aVertFilter.IsPolygon() )
1460 aVertFilter.LastPoint();
1461 else
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;
1481 if( ! nCount )
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)
1495 nXMin = rPt.X();
1496 if (rPt.X() > nXMax)
1497 nXMax = rPt.X();
1498 if (rPt.Y() < nYMin)
1499 nYMin = rPt.Y();
1500 if (rPt.Y() > nYMax)
1501 nYMax = rPt.Y();
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 ) )
1523 nCount--;
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
1532 if ( nPCounter )
1534 if ( aIntersection != aLastIntersection )
1536 aLastIntersection = aIntersection;
1537 nPCounter++;
1540 else
1542 aLastIntersection = aIntersection;
1543 nPCounter++;
1547 aPt1 = rPt2;
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;
1568 if( nInsertCount )
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;
1590 return *this;
1593 tools::Polygon& Polygon::operator=( tools::Polygon&& rPoly ) noexcept
1595 mpImplPolygon = std::move(rPoly.mpImplPolygon);
1596 return *this;
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;
1607 sal_uInt16 i;
1608 if ( GetSize() != rPoly.GetSize() )
1609 bIsEqual = false;
1610 else
1612 for ( i = 0; i < GetSize(); i++ )
1614 if ( ( GetPoint( i ) != rPoly.GetPoint( i ) ) ||
1615 ( GetFlags( i ) != rPoly.GetFlags( i ) ) )
1617 bIsEqual = false;
1618 break;
1622 return bIsEqual;
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);
1649 return rIStream;
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());
1665 return rOStream;
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))
1718 return;
1720 if(!roPolygon.isPrevControlPointUsed(nIndex) || !roPolygon.isNextControlPointUsed(nIndex))
1721 return;
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
1731 // correctly.
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)
1750 return;
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);
1778 if (nCount)
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++];
1796 bControlA = true;
1799 if(a < nCount && PolyFlags::Control == mpImplPolygon->mxFlagAry[a])
1801 aControlB = mpImplPolygon->mxPointAry[a++];
1802 bControlB = true;
1805 // assert invalid polygons
1806 OSL_ENSURE(bControlA == bControlB, "Polygon::getB2DPolygon: Invalid source polygon (!)");
1808 if(a < nCount)
1810 const Point aEndPoint(mpImplPolygon->mxPointAry[a]);
1812 if(bControlA)
1814 // bezier edge, add
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);
1822 else
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]);
1842 else
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()));
1852 // set closed flag
1853 basegfx::utils::checkClosed(aRetval);
1857 return aRetval;
1860 Polygon::Polygon(const basegfx::B2DPolygon& rPolygon) : mpImplPolygon(ImplPolygon(rPolygon))
1864 } // namespace tools
1866 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */